/* ══════════════════════════════════════════════════════════════════
   NavNex — the ride
   Layered on styles.css, which supplies the tokens, the type scale,
   the controls and every tail section. This file owns three things
   and nothing else: the pinned sideways travel, the scroll-paced
   cross-fade between photographs, and the ember thread that runs
   from card to card — and, since 3 Sep, the TWO SEATS: the gate that
   shows one of the two heroes and one of the two rides, the doors, the
   masthead switch, and the shipowner's placements beside the charterer's.

   The cards themselves are NOT designed here. assets/cards.css owns
   .stage-card and .stage-card-copy outright, loads BEFORE this file,
   and nothing below restyles either of them. What this file does own
   at the seam is placement: where a card sits in its beat, where its
   copy sits against the card, the film that keeps white copy legible
   on a photograph, and the scroll-driven fill of beat 1's form.
   .beat-card is now a bare hook class on the card article — no look,
   just the handle ride.js measures the thread from.

   Ground rules obeyed:
   · the wheel is never taken over — scroll position drives transform
   · only transform and opacity animate
   · photo tone is baked into the JPEGs, so no runtime filters
   ══════════════════════════════════════════════════════════════════ */

:root {
  /* travel — Emil's ruling, 2 Sep 2026: the ride must read as the screen
     moving RIGHT, not as the page moving down. A step nearly a screen wide
     is what buys that: every card enters past the right edge and leaves
     past the left one, so it cannot be read as a fade in place.

     Why 96vw (was 92 while the cards were ~25vw wide; Emil doubled them on
     3 Sep). The step has a floor and a ceiling. The FLOOR is the sliver:
     a card must have cleared the left edge by the time the next one has
     settled, and that is step > ½vw + dx + ½card — with a 56vw card
     pushed 17vw right, that is 94vw. The CEILING is the empty stretch:
     if both cards are off screen at once you get a stretch of picture
     with nothing on it, the cross-fade alone, which is the very thing
     that read as standing still. With 50–56vw cards the leaver and the
     arriver share the frame for most of the travel whatever the step,
     so the ceiling has moved far out and the floor is what sets it.
     96vw keeps one card going out left as the next comes in right, in
     the same frame — which is what actually sells the movement. */
  --beat-step:   96vw;                    /* horizontal distance per beat */
  --ride-scroll: 620vh;                   /* scroll spent on the whole ride */
  --step-unit:   90vh;                    /* one weighted story step */

  /* Each card sizes itself: the article carries its own --sc-w, tuned
     per beat by the card track. Nothing here sets a card width. */
  --copy-w:      min(360px, 26vw);        /* the stage copy beside a card */
  --copy-gap:    clamp(26px, 2.6vw, 44px);

  --thread-w:    1.5px;
}

/* No overflow override here on purpose. The sideways track is absolutely
   positioned inside .ride__pin, which clips it, so nothing ever overflows
   the page horizontally. Setting overflow-x on body or html propagates to
   the viewport and stops the page scrolling at all — which is exactly what
   it did the first time round. */


/* ═══════════════ LEDE — the opening, over the hero photograph ═══════════
   Emil's ruling, 2 Sep 2026: the opening carries a full-bleed photograph
   — a container ship seen from straight above, near-black water, a white
   wake fanning behind. Everything below the fold still sits on flat
   ground; this is the one place a marketing sentence is set on a picture.

   Emil, 3 Sep, two more rulings:
     · the headline grows (about 1.25× at 1440) and moves right, to start
       roughly a quarter of the way across, over the dark water above the
       ship — never over the ship, in either state below;
     · as the reader scrolls, the photograph turns into its own ASCII
       rendering (ride.js, the hero module).

   So the opening is now two boxes and four layers:

     .lede-driver   the scroll budget. 240vh with script, so the reader has
                    140vh of travel to watch the picture rewrite itself
                    before the page moves on into the ride. Without script
                    (or under reduced motion) it is just the stage's height.
     .lede          the stage: 100svh, stuck to the top of the driver.

     .lede__photo   the photograph — an <img data-hero> in the DOM, so that
                    a reader with no script gets a plain photo hero, and so
                    the sampler reads whichever picture is VISIBLE rather
                    than a filename (a later step gives the shipowner's seat
                    its own hero photograph: a second <img data-hero>).
     .lede__ascii   the glyph canvas: empty at the top of the page, written
                    cell by cell as the reader scrolls.
     .lede__scrim   two films — one across the frame, one down it — so the
                    masthead and the headline have a settled ground on the
                    photograph and on the type alike. With the headline over
                    the top band of dark water the film across the frame no
                    longer has to bury the left; it is eased so the wake can
                    show.
     .lede__inner   the headline.

   Pinning and the canvas are gated on html[data-dissolve], which only the
   hero module sets and never under prefers-reduced-motion. --hero-focus
   is the photograph's framing, and the sampler reads it back off the <img>
   so the type lands where the picture is. */

/* Emil, 3 Sep: the masthead lockup doubles; masthead and lede hug a
   tighter edge than the body column below. */
.mast, .lede { --edge: calc(var(--gut) * .6); }

.mast { padding: 26px var(--edge); }
.mast__brand { gap: 22px; }
.mast__mark { width: 52px; height: 46px; }
.mast__word { width: 192px; height: 36px; }

.lede-driver { position: relative; background: var(--ground); }

.lede {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 100svh;
  display: flex;
  /* stretch, not flex-start: the inner box fills the stage so the doors
     can sit at its foot (see THE DOORS below) */
  align-items: stretch;
  padding: clamp(116px, 13vh, 140px) 0 clamp(72px, 10vh, 118px);
  /* the ground is a colour under everything, so it is still there while
     the photograph is on its way */
  background-color: var(--ground);

  --hero-focus: 50% 50%;
  /* narrow screens: the type runs the full width, so the film across the
     frame stays even the whole way over */
  --hero-scrim-x:
    linear-gradient(180deg, rgba(11, 12, 14, .58), rgba(11, 12, 14, .58));
  --h1-x: var(--gut);
}

.lede__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-focus);
  will-change: opacity;
}
/* Both hero photographs use the stage's shared focus: centred on phones,
   then 62% across on wider screens. The sampler reads the rendered crop
   back from the visible image, whichever seat is chosen. */
.lede__ascii {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.lede__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* topmost first: the film across, then the film down */
  background:
    var(--hero-scrim-x),
    linear-gradient(180deg, rgba(11, 12, 14, .70) 0%, rgba(11, 12, 14, .08) 24%,
                            rgba(11, 12, 14, .10) 62%, rgba(11, 12, 14, .80) 100%);
}

@media (min-width: 768px) {
  .lede {
    /* the frame pushed right so the ship and its wake sit in the open
       half; the headline sits in the band of black water above the ship */
    --hero-focus: 62% 50%;
    --hero-scrim-x:
      linear-gradient(97deg, rgba(11, 12, 14, .62) 0%, rgba(11, 12, 14, .44) 26%,
                             rgba(11, 12, 14, .26) 56%, rgba(11, 12, 14, .14) 100%);
    /* Emil, 3 Sep: the headline starts about a quarter of the way across.
       23vw is as far right as it goes with the first line still whole: at
       1440 "Revolutionising chartering" is 11.9em wide, and 23vw + 11.9em at
       6.2vw ends level with the masthead's right edge. */
    --h1-x: 23vw;
  }
  /* three lines, always — the size is proportional to the width, so a
     line that holds at one width holds at every width */
  .lede__h1l { white-space: nowrap; }
}

/* the pin, script only */
html[data-dissolve] .lede-driver { height: 240vh; }
html[data-dissolve] .lede {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 0;
}
html[data-dissolve] .lede__ascii { display: block; }

.lede .lede__inner {
  position: relative;
  z-index: 3;
  width: 100%;
  max-width: none;
  margin: 0;
  padding-left: var(--h1-x);
  padding-right: var(--edge);
  /* headline at the top, the doors at the foot of the stage */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(26px, 4vh, 40px);
}
.lede__h1 { margin: 0; }
/* Emil's line, set in Space Grotesk on his order (3 Sep). Sized by width
   AND height together: 6.2vw is
   what lets "Revolutionising chartering" hold as one line from 23vw in
   (89px at 1440, up from 72), and 10svh is what keeps the third line's ink
   clear of the ship (the ship's top edge sits at about 43% of the frame;
   three lines at .98 from a 13vh top finish at about 40%). The smaller of
   the two wins on every screen, so neither the wrap nor the ship ever
   gives way. Bigger than this and the first line has to start further
   left than a quarter, or break — Emil's call, not the stylesheet's. */
.lede__h1b {
  display: block;
  font-family: "Space Grotesk", var(--sans), system-ui, sans-serif;
  font-size: clamp(2.3rem, min(6.2vw, 10svh), 8rem);
  font-weight: 700;
  line-height: .98;
  letter-spacing: -.035em;
  color: var(--ink);
  text-wrap: balance;
}
.lede__h1l { display: block; }
.lede__lead { margin-bottom: 32px; line-height: 1.5; max-width: 64ch; }
.lede .facts { max-width: 880px; }

/* the scroll cue is the one thing that hints the page travels */
.lede__cue {
  margin: 34px 0 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--faint);
  display: flex;
  align-items: center;
  gap: 10px;
}
.lede__cue::before {
  content: "";
  width: 34px; height: 1px;
  background: var(--ember);
  flex: none;
}


/* ═══════════════ THE TWO SEATS ═══════════════
   Emil's ruling, 3 Sep 2026: TWO DOORS at the opening, one page. The
   opening asks "I have cargo / I have a ship"; the chosen seat decides
   the H1, the hero photograph and which six-beat ride plays. The
   charterer is the default seat — the page reads with no click — and
   everything from #what onward is one shared copy in the DOM.

   THE GATE — three states, in the order of who decides:
   · no script — html carries no [data-enhance]. The charterer's H1 and
     photograph show, the owner's are hidden, and BOTH rides stack in
     source order with a jump link between them.
   · script, before seat.js — the head script has set html[data-seat]
     from ?seat= / localStorage / the default. The other seat is
     display:none from the first paint, so there is no flash of the
     wrong seat, whichever way the reader arrived.
   · script, after seat.js — the same, plus `hidden` and aria-hidden on
     the other seat's elements, so assistive technology agrees with the
     eye. [hidden] alone would lose to img{display:block} and to the
     positioned .lede__photo, hence the !important pair. */
html:not([data-enhance]) .lede [data-seat="owner"] { display: none; }
html[data-enhance][data-seat="charterer"] [data-seat="owner"],
html[data-enhance][data-seat="owner"] [data-seat="charterer"] { display: none !important; }
[data-seat][hidden] { display: none !important; }
.ride[hidden] { display: none !important; }
html[data-enhance] .seat-jump { display: none; }

/* the no-script jump between the two stacked rides */
.seat-jump {
  margin: 0;
  padding: 30px var(--gut);
  text-align: center;
  background: var(--ground);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}
.seat-jump a { color: var(--ink); text-underline-offset: 6px; }

/* THE DOORS. Two ink-outline controls; the chosen door is filled ink.
   Never ember — the ember above the fold belongs to the thread, and
   these are a choice, not an action. No ▸: nothing is being submitted.

   WHERE: under the headline, at the FOOT of the stage, on the headline's
   own left edge. Directly beneath the third line they land on the ship's
   bow in the charterer's photograph (the ship's top edge sits at about
   43% of the frame, the headline finishes at 40%) and on the ensign in
   the owner's. The foot of the frame is dark water on one photograph
   and the scrimmed deck on the other, and the film down the frame is at
   its heaviest there. So the lede's inner box fills the stage (align-
   items: stretch, above) and the doors sit at its bottom — the stage's
   own bottom padding is their clearance.

   The unchosen door carries a film of ground behind its outline, the
   same ink every other scrim on the page uses, so its label clears
   whatever the photograph does under it — the outline is still the
   control; the film is legibility, as it is for the stage copy. */
.doors {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0;
}
.ctl.door {
  border-color: var(--ink);
  color: var(--ink);
  background: rgba(11, 12, 14, .55);
  transition: background-color .18s var(--ease), color .18s var(--ease);
}
.ctl.door:hover { background: rgba(11, 12, 14, .78); }
.ctl.door[aria-current] { background: var(--ink); color: var(--ground); }
.ctl.door[aria-current]:hover { background: var(--ink); }

/* THE SWITCH. The nav's one ink-outline control: Cargo | Ship. Two
   links inside one rule, the chosen half filled ink. Sits with the
   nav's own type. */
.seat {
  display: inline-flex;
  border: 1px solid var(--ink);
  margin-left: clamp(4px, 1vw, 12px);
}
.mast__nav .seat__o {
  display: inline-flex;
  align-items: center;
  padding: 7px 13px;
  font-size: 10.5px;
  letter-spacing: .12em;
  color: var(--dim);
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.mast__nav .seat__o + .seat__o { border-left: 1px solid var(--ink); }
.mast__nav .seat__o:hover { color: var(--ink); }
.mast__nav .seat__o[aria-current] { background: var(--ink); color: var(--ground); }
.mast__nav .seat__o[aria-current]:hover { color: var(--ground); }

/* phone: the nav is wordmark + switch only (styles.css hides the whole
   nav here; this puts it back with just the switch in it) */
@media (max-width: 860px) {
  .mast__nav { display: flex; }
  .mast__nav > a { display: none; }
  .seat { margin-left: 0; }
}
/* the doubled lockup (Emil, 3 Sep) plus the switch do not both fit a
   375px masthead — the switch was pushed off the right edge. The lockup
   comes back to its styles.css size on a phone; the switch tightens. */
@media (max-width: 560px) {
  .mast__brand { gap: 12px; }
  .mast__mark { width: 34px; height: 30px; }
  .mast__word { width: 126px; height: 24px; }
  .mast__nav .seat__o { padding: 6px 10px; font-size: 10px; letter-spacing: .1em; }
}
/* the doors stack once the two labels no longer fit side by side */
@media (max-width: 560px) {
  .doors { flex-direction: column; align-items: stretch; }
  .doors .ctl { justify-content: center; }
}


/* ═══════════════ THE RIDE ═══════════════
   BASE = the stacked reading, which is what mobile, reduced motion
   and no-JS all get. The pinned ride is layered on top of it inside
   one media query, so every fallback is the default rather than an
   afterthought. */

.ride { position: relative; background: var(--ground); }
.ride__pin { position: relative; }

/* the stacked layout paints each beat's own photograph; the hoisted
   stack is only assembled by JS for the pinned ride */
.ride__scenes,
.ride__scrim,
.ride__thread { display: none; }

.ride__acts { display: none; }
.ride__acthead {
  width: 100%;
  max-width: 440px;
  margin: 0 auto clamp(18px, 3vh, 28px);
  font-family: "Fragment Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
  letter-spacing: .16em;
  color: rgba(255,255,255,.72);
  text-transform: uppercase;
}

.ride__beat {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* the page's own convention for anchored sections ([id] in styles.css):
     landing on a beat in the stacked reading clears the fixed masthead */
  scroll-margin-top: 96px;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(64px, 9vh, 104px) var(--gut);
}

.ride__scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}

/* Structural, not decoration: the ember thread and the card edge both
   need a settled ground under them. Tone is already baked into the
   JPEGs, so this stays light. */
.ride__beat::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(11,12,14,.58) 0%, rgba(11,12,14,.20) 30%,
                            rgba(11,12,14,.20) 70%, rgba(11,12,14,.58) 100%),
    linear-gradient(90deg, rgba(11,12,14,.26), rgba(11,12,14,.26));
}

/* the thread, stacked: one continuous ember line down the column,
   running behind every card so it reads as a single unbroken run */
.ride__beat::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: var(--thread-w);
  z-index: 0;
  background: var(--ember);
  opacity: .5;
  transform: translateX(-50%) scaleY(0);
  transform-origin: 50% 0;
  transition: transform .85s var(--ease);
}
.ride__beat.is-in::before { transform: translateX(-50%) scaleY(1); }
.ride__beat:first-of-type::before { top: 50%; }
.ride__beat:last-of-type::before  { bottom: 50%; }

/* BASE = the stacked reading. Copy above its card, both centred, so the
   vertical thread runs straight through the card's two diamonds. Source
   order is copy then card, which is also the right reading order: the
   heading announces the screen underneath it. */
.ride__slot {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(20px, 3.2vh, 32px);
  width: 100%;
}

/* The stage copy. cards.css owns the voice — size, weight, colour, the
   13ch/40ch measures. This owns only where it sits and what it sits on. */
.ride__copy {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 440px;
}

/* The film. White display type has to clear whatever the photograph does
   underneath it, and the photographs change. Same ink as every other scrim
   on the page, feathered to nothing well before its own edge, so it lifts
   contrast without reading as a panel behind the words. */
.ride__copy::before {
  content: "";
  position: absolute;
  inset: -30px -46px -34px -44px;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(58% 54% at 40% 50%,
              rgba(11, 12, 14, .74) 0%, rgba(11, 12, 14, .50) 52%,
              rgba(11, 12, 14, .16) 78%, rgba(11, 12, 14, 0) 100%);
}

/* Cards settle as they arrive — the stacked equivalent of the sideways
   ride's focus falloff. Gated on [data-enhance], which only ever gets set
   by script, so a reader with no JS is never left with hidden journey cards. */
html[data-enhance] .ride__beat .ride__slot {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
html[data-enhance] .ride__beat.is-in .ride__slot { opacity: 1; transform: none; }


/* ═══════════════ THE PINNED RIDE ═══════════════
   Desktop, motion allowed, JS present. Anything else keeps the
   stacked reading above. */

@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
  html[data-js] .ride {
    height: calc(100svh + var(--ride-scroll));
  }

  html[data-js] .ride__pin {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;
  }

  html[data-js] .ride__acts {
    display: flex;
    position: absolute;
    top: 96px;
    left: var(--edge, var(--gut));
    z-index: 3;
    gap: clamp(14px, 1.8vw, 28px);
    align-items: center;
  }
  html[data-js] .ride__acts button {
    appearance: none;
    border: 0;
    border-bottom: 1px solid transparent;
    background: transparent;
    padding: 0 0 5px;
    color: rgba(255,255,255,.42);
    font: 500 10.5px/1.2 "Fragment Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .12em;
    text-transform: uppercase;
    cursor: pointer;
  }
  html[data-js] .ride__acts button:hover,
  html[data-js] .ride__acts button:focus-visible { color: rgba(255,255,255,.78); }
  html[data-js] .ride__acts button[aria-current] {
    color: #fff;
    border-bottom-color: #fff;
  }
  html[data-js] .ride__acthead { display: none; }

  /* The photographs cross-fade in place, and drift a hair against the
     travel — 1.2vw a beat, against the cards' 112vw. Far too little to
     read as the picture moving; just enough that the frame feels driven
     rather than parked. ride.js writes the transform, scale included;
     the scale is what keeps the drift from exposing an edge. */
  html[data-js] .ride__scenes {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    will-change: transform;
  }
  html[data-js] .ride__scenes .ride__scene {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: auto;
    opacity: 0;
    will-change: opacity;
  }
  /* the first photograph is the ground the ride starts on */
  html[data-js] .ride__scenes .ride__scene:first-child { opacity: 1; }

  html[data-js] .ride__scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
    background:
      linear-gradient(180deg, rgba(11,12,14,.52) 0%, rgba(11,12,14,.16) 26%,
                              rgba(11,12,14,.16) 72%, rgba(11,12,14,.56) 100%),
      linear-gradient(90deg, rgba(11,12,14,.22), rgba(11,12,14,.22));
  }

  /* the cards travel; the pictures do not */
  html[data-js] .ride__track {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: max-content;
    display: flex;
    align-items: center;
    z-index: 2;
    will-change: transform;
  }

  html[data-js] .ride__thread {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    overflow: visible;
    pointer-events: none;
  }

  html[data-js] .ride__beat {
    width: var(--beat-step);
    flex: none;
    height: 100%;
    min-height: 0;
    padding: 0;
    overflow: visible;
    z-index: 1;
  }
  /* stacked-only furniture off: the SVG thread and the shared scrim
     take over from the per-beat pseudo-elements */
  html[data-js] .ride__beat::before,
  html[data-js] .ride__beat::after { content: none; }

  html[data-js] .ride__beat > .ride__scene { display: none; }

  /* WHERE EACH CARD SITS — all 23 stages have an explicit seat-and-stage
     placement. Repeated plates remain visually continuous while the cards
     move to a fresh position; copy stays inboard of its card. These values
     are tuned at 1440×900, while the narrow and stacked rules below take
     over before card-plus-copy can exceed the available width. The thread
     is measured from the resulting card boxes, so it follows every move. */
  /* Matches the [data-enhance] entry-fade rule for specificity and comes
     later, so the pinned ride always wins: slots start visible and their
     opacity is driven per frame instead of by a transition.

     The slot goes back to block flow with the CARD as its only in-flow
     child, so the slot box IS the card box. That matters twice over: the
     ruled --dx/--dy below still mean exactly what they meant when the card
     was the only thing in the slot, and ride.js measures the thread off
     this same box. The copy is lifted out of flow and hung off it. */
  html[data-js] .ride__beat .ride__slot {
    display: block;
    transform: translate(var(--dx, 0), var(--dy, 0));
    opacity: 1;
    transition: none;
    width: auto;
  }
  html[data-js] .ride__beat .ride__slot[data-far] { visibility: hidden; }

  html[data-js] .ride[data-seat="charterer"] [data-beat-i="1"],
  html[data-js] .ride[data-seat="charterer"] [data-beat-i="6"] { --step: 1.25; }
  html[data-js] .ride[data-seat="charterer"] [data-beat-i="4"],
  html[data-js] .ride[data-seat="charterer"] [data-beat-i="8"],
  html[data-js] .ride[data-seat="charterer"] [data-beat-i="10"] { --step: 1.5; }
  html[data-js] .ride[data-seat="owner"] [data-beat-i="2"],
  html[data-js] .ride[data-seat="owner"] [data-beat-i="4"] { --step: 1.25; }
  html[data-js] .ride[data-seat="owner"] [data-beat-i="1"],
  html[data-js] .ride[data-seat="owner"] [data-beat-i="5"],
  html[data-js] .ride[data-seat="owner"] [data-beat-i="7"] { --step: 1.5; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S1"]  .ride__slot { --dx: -19vw; --dy: -2.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S2"]  .ride__slot { --dx:  21vw; --dy:  9.5vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S3"]  .ride__slot { --dx: -20vw; --dy:  9.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S4"]  .ride__slot { --dx:  19vw; --dy: -0.5vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S5"]  .ride__slot { --dx: -16vw; --dy:  7.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S6"]  .ride__slot { --dx: -21.75vw; --dy:  2.5vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S7"]  .ride__slot { --dx:  21vw; --dy:  0; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S8"]  .ride__slot { --dx: -18vw; --dy:  5.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S9"]  .ride__slot { --dx:  20vw; --dy:  2.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S10"] .ride__slot { --dx: -20vw; --dy:  7.4vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S11"] .ride__slot { --dx:  17vw; --dy:  7.0vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S12"] .ride__slot { --dx:  17vw; --dy: -0.8vh; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S13"] .ride__slot { --dx: -21vw; --dy:  2.0vh; }

  /* WHERE EACH CARD'S COPY SITS — the words travel with their card and
     hang from its box, on the quieter, inboard side of the photograph.
     The vertical offset also leaves the measured thread clear. */
  html[data-js] .ride__copy {
    position: absolute;
    width: var(--copy-w);
    max-width: none;
    left:   var(--cl, auto);
    right:  var(--cr, auto);
    top:    var(--ct, auto);
    bottom: var(--cb, auto);
  }
  /* 1 · harvester up the middle → card left; copy inboard right, high */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S1"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 8%; }
  /* 2 · anchorage, ships low and scattered → inboard left, low, in the haze */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S2"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 6%; }
  /* 3 · tug right of centre → inboard right, up in the open sky */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S3"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 40%; }
  /* 4 · same frame → inboard left, low, other side */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S4"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 10%; }
  /* 5 · shiploader boom crosses the top left → inboard right, clear of it */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S5"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 27%; }
  /* 6 · pit benches centre-left → inboard left, low; the whole frame is dark */
  html[data-js] .ride[data-seat="charterer"] [data-stage="S6"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 20%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S7"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 9%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S8"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 23%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S9"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 12%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S10"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 17%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S11"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 14%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S12"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 11%; }
  html[data-js] .ride[data-seat="charterer"] [data-stage="S13"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 13%; }

  /* The owner's ten placements follow the same inboard-copy rule, tuned
     independently against that seat's six photographs at 1440×900. */
  html[data-js] .ride[data-seat="owner"] [data-stage="O1"]  .ride__slot { --dx:  18vw; --dy: -5.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O2"]  .ride__slot { --dx: -20vw; --dy:  7.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O3"]  .ride__slot { --dx:  20vw; --dy:  4.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O4"]  .ride__slot { --dx:  -6vw; --dy:  4.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O5"]  .ride__slot { --dx:  19vw; --dy: -5.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O6"]  .ride__slot { --dx: -18vw; --dy:  3.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O7"]  .ride__slot { --dx:   6vw; --dy:  0; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O8"]  .ride__slot { --dx:   8vw; --dy:  1.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O9"]  .ride__slot { --dx: -18vw; --dy:  2.0vh; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O10"] .ride__slot { --dx:  20vw; --dy: -3.0vh; }

  /* 1 · card left, high → copy inboard right, high, in the flat sky */
  html[data-js] .ride[data-seat="owner"] [data-stage="O1"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 13%; }
  /* 2 · card right → copy inboard left, at the foot, in the cut rows */
  html[data-js] .ride[data-seat="owner"] [data-stage="O2"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 40%; }
  /* 3 · card left → copy inboard right, high, open sky above the wheelhouse */
  html[data-js] .ride[data-seat="owner"] [data-stage="O3"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: -10%; }
  /* 4 · card right → copy inboard left, low */
  html[data-js] .ride[data-seat="owner"] [data-stage="O4"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 43%; }
  /* 5 · card right over the water → copy inboard left, low, on the dark hull */
  html[data-js] .ride[data-seat="owner"] [data-stage="O5"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 6%; }
  /* 6 · card left over the mast → copy inboard right, high, over the horizon */
  html[data-js] .ride[data-seat="owner"] [data-stage="O6"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 25%; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O7"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: -15%; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O8"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 10%; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O9"] .ride__copy { --cl: calc(100% + var(--copy-gap)); --cb: 30%; }
  html[data-js] .ride[data-seat="owner"] [data-stage="O10"] .ride__copy { --cr: calc(100% + var(--copy-gap)); --cb: 8%; }
}

/* ═══════════════ THE NARROW PINNED RIDE · 768–1099px ═══════════════
   Between a phone and a laptop (a tablet, a small window) the ride still
   pins, but a 640–700px card and 26vw of copy no longer share the width.
   The copy goes ABOVE the card, left-aligned to the card's left edge, and
   the card gives the copy its height: --sc-room tells the card track that
   404px of the screen — masthead, copy, gap, the thread's dip — is spoken
   for, so the tallest card still fits under it. One placement for every
   beat here: on a 768px frame a 700px card has 34px a side to move in. */
@media (min-width: 768px) and (max-width: 1099px) and (prefers-reduced-motion: no-preference) {
  html[data-js] .ride__beat .ride__slot { --sc-room: calc(100svh - 404px); }
  html[data-js] .ride[data-seat] [data-beat-i] .ride__slot { --dx: 0; --dy: 126px; }
  html[data-js] .ride[data-seat] [data-beat-i] .ride__copy {
    --cl: 0; --cr: auto; --ct: auto; --cb: calc(100% + 20px);
    width: min(100%, 460px);
  }
}

/* The thread is two strokes on the same curve: a faint rail that is
   always there, so you can see where the ride goes, and the ember run
   drawn on top of it as you travel. The ember is the live element. */
.ride__thread-rail {
  fill: none;
  stroke: var(--ember);
  stroke-width: var(--thread-w);
  stroke-linecap: round;
  opacity: .22;
}
.ride__thread-seg {
  fill: none;
  stroke: var(--ember);
  stroke-width: var(--thread-w);
  stroke-linecap: round;
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
}
/* The thread draws no node marks of its own any more. Each card already
   carries Sedna's white diamonds at its top and bottom centre, and the
   thread now begins and ends on them — an ember dot printed on top of a
   white diamond would only foul it. */


/* ═══════════════ BEAT 1 · THE FORM FILLS ITSELF ═══════════════
   The first card is a cargo posted. The seven fields and the button carry
   data-fill-step="1..8" and appear in order as the reader scrolls through
   beat 1's rest, so the form is written rather than presented. The owner's
   listing form stays complete throughout its stage so its vessel details
   remain readable. These rules apply only to cards carrying data-fill.

   Driven by --beat-q, which ride.js writes on the card: the RAW share of
   the first step travelled, before the dwell easing. --beat-p cannot do
   this job — it is pinned at 1 for the whole of beat 1's rest, because
   the ride opens with beat 1 already in focus — so the engine exposes
   both. Nothing here measures or animates layout: only opacity moves, and
   the labels and rules stay put, so the card never changes height.

   Scrubbing back un-writes it, which is correct: this is a pure function
   of scroll position in both directions.

   The default in every var() below is 1. That is the fallback the whole
   thing rests on: no script, reduced motion, mobile, or a browser that
   drops the property, and the form is simply complete. */

@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
  html[data-js] .stage-card[data-fill] [data-fill-step] .stage-card__fvalue,
  html[data-js] .stage-card[data-fill] [data-fill-step] .stage-card__chips,
  html[data-js] .stage-card[data-fill] .stage-card__actrow[data-fill-step] .stage-card__act {
    /* opacity clamps its own out-of-range values, which is what carries
       each step from 0 to 1 across its own slice and holds it there */
    opacity: calc((var(--beat-q, 1) - var(--fill-at, 0)) * 14);
  }
  html[data-js] .stage-card[data-fill] [data-fill-step="1"] { --fill-at: .020; }
  html[data-js] .stage-card[data-fill] [data-fill-step="2"] { --fill-at: .056; }
  html[data-js] .stage-card[data-fill] [data-fill-step="3"] { --fill-at: .091; }
  html[data-js] .stage-card[data-fill] [data-fill-step="4"] { --fill-at: .127; }
  html[data-js] .stage-card[data-fill] [data-fill-step="5"] { --fill-at: .162; }
  html[data-js] .stage-card[data-fill] [data-fill-step="6"] { --fill-at: .198; }
  html[data-js] .stage-card[data-fill] [data-fill-step="7"] { --fill-at: .233; }
  html[data-js] .stage-card[data-fill] [data-fill-step="8"] { --fill-at: .269; }
}


/* ═══════════════ TAIL ═══════════════ */

.pull--lede { margin: 56px 0 0; }

@media (max-width: 767px) {
  /* one beat per screen, and the card gets the room it needs. The varied
     slot positions are a desktop-only affair — they live inside the pinned
     ride's media query, so this stays a clean vertical stack. */
  .ride__beat { min-height: 100svh; padding: clamp(56px, 8vh, 88px) var(--gut); }
  /* The card sizes itself — cards.css holds it to 100vw minus Sedna's own
     24px gutters — so nothing here touches its width. The slot just keeps
     the pair to a sane measure and centres it, which puts the card's two
     diamonds on the vertical thread. */
  .ride__slot { max-width: none; margin-inline: auto; }
  .ride__beat[data-hold] {
    min-height: auto;
    padding-top: 40px;
    padding-bottom: 40px;
    background: var(--ground);
  }
  .ride__beat[data-hold] .ride__scene { display: none; }
  .ride__beat[data-hold]::after { background: var(--ground); }
  .mast { padding: 16px var(--gut); }
  .lede { min-height: auto; padding: 120px 0 72px; }
  /* pinned, the stage is a full screen here too — the thumb has 120vh of
     travel to watch the rewrite, a little less than the desktop's 140 */
  html[data-dissolve] .lede-driver { height: 220vh; }
  html[data-dissolve] .lede { height: 100svh; }
  /* the H1's three lines go back to being one sentence that wraps */
  .lede__h1l { display: inline; }
}

@media (prefers-reduced-motion: reduce) {
  /* a static composition: everything laid out, nothing driven */
  .ride__beat::before { transform: translateX(-50%) scaleY(1); transition: none; }
  html[data-enhance] .ride__beat .ride__slot,
  .ride__beat .ride__slot { opacity: 1; transform: none; transition: none; }
  .ride__thread-seg { stroke-dashoffset: 0; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Emil, 3 Sep: everything from #what onward ("NavNex does not replace the
   broker with a better broker.") runs the full screen width, not the 1160px
   column. Sections keep the masthead's edge inset; readable line lengths on
   paragraphs (ch limits) stay, the containers do not. The pull-quote
   "One deal. Two seats. Same numbers on both screens." was removed on the
   same order. */
.sec, .foot { --edge: calc(var(--gut) * .6); }
.sec .wrap, .foot .wrap { max-width: none; padding-left: var(--edge); padding-right: var(--edge); }
.sec .cols { max-width: none; }
