/* ═══════════════════════════════════════════════════════════
   SEA AIR CARGO SYSTEMS — Motion System (W2)
   ALL motion lives here, separate from layout, so the
   reduced-motion contract is auditable in one place.
   Loaded after shared.css (Codex adds the <link> to every page).

   Rule, site-wide: content is visible by default. Motion may only
   animate FROM a visible state, never TO one. The hidden state is
   applied BY JS (.js-motion on <html>), only once JS has confirmed
   it can remove it — nothing ever depends on JS to become visible.
   ═══════════════════════════════════════════════════════════ */

/* ── REVEALS (§2.2) — capped stagger, below the fold only ── */
/* Reveals never hide content from crawlers or no-JS users:
   the hidden state is applied BY JS, only once JS has confirmed it can remove it. */
.js-motion [data-reveal]{opacity:0;transform:translateY(20px)}
[data-reveal]{
  transition:opacity var(--dur-move) var(--ease-out),
             transform var(--dur-move) var(--ease-out);
  transition-delay:var(--d,0ms)
}
[data-reveal].is-in{opacity:1;transform:none}

/* Variants — pick by what the motion MEANS, not for variety.
   rise  = new content arriving (default)
   settle= a figure/card taking its place
   draw  = a rule, rail or divider extending  */
.js-motion [data-reveal="settle"]{opacity:0;transform:translateY(12px) scale(.985)}
.js-motion [data-reveal="draw"]{opacity:1;clip-path:inset(0 100% 0 0)}
[data-reveal="draw"]{transition:clip-path var(--dur-focal) var(--ease-out);transition-delay:var(--d,0ms)}
[data-reveal="draw"].is-in{clip-path:inset(0 0 0 0)}

/* ── TRANSIT RAIL (§2.3) — the signature element on guide pages ──
   A 1px gold hairline down the left edge of the article that fills as
   the reader scrolls, with a checkpoint node at each <h2>.
   Codex applies class="rail" to the main article wrapper on guide pages
   only — never the homepage, tools, or contact. */
.rail{position:relative;padding-left:34px}
/* the track + nodes sit 4px in from the rail's left edge: corpus applies .rail to
   a full-width <main>, so left:0 glues the track to the window edge and the 7px
   checkpoint dots (centered on the track) render half-clipped off-screen */
.rail::before{                       /* the route */
  content:"";position:absolute;left:var(--rail-x,4px);top:var(--rail-top,50%);bottom:var(--rail-bottom,50%);width:var(--rail-w);
  background:var(--tint-3)           /* --tint-2 (10% gold on navy) did not read as a line */
}
.rail::after{                        /* the progress fill */
  content:"";position:absolute;left:var(--rail-x,4px);top:var(--rail-top,50%);bottom:var(--rail-bottom,50%);width:var(--rail-w);
  background:linear-gradient(to bottom,var(--gold),var(--gold2));
  transform-origin:top;transform:scaleY(var(--p,0))
}
/* checkpoints are JS-built spans parented to the rail itself (shared.js) —
   corpus h2s sit inside padded cards, so h2-relative dots floated at each
   card's inner edge; rail-parented dots sit on the track at any nesting depth.
   z-index:1 keeps the dots above the ::after fill, which paints after them. */
.rail-node{
  position:absolute;left:var(--rail-x,4px);z-index:1;width:7px;height:7px;margin-left:-3px;margin-top:-3px;
  border-radius:50%;background:var(--navy);border:1px solid var(--tint-4);
  transition:background-color var(--dur-state) var(--ease-out),
             border-color var(--dur-state) var(--ease-out),
             transform var(--dur-state) var(--ease-out)
}
.rail-node.is-passed{background:var(--gold);border-color:var(--gold);transform:scale(1.25)}

/* Scroll-driven where the browser supports it — zero JS, runs off the compositor */
@supports (animation-timeline: view()){
  .rail::after{
    transform:scaleY(1);
    animation:railFill linear both;
    animation-timeline:view();
    animation-range:entry 12% exit 60%
  }
  @keyframes railFill{from{transform:scaleY(0)}to{transform:scaleY(1)}}
}

@media(max-width:768px){.rail{padding-left:22px}}

/* ── Pause non-essential loops while the tab is hidden (§2.6) ── */
.is-hidden .hero-aurora,.is-hidden .hero-beams,.is-hidden .hb,
.is-hidden .ph-flow-lane,.is-hidden .ph-flow-node,
.is-hidden .pjl-spine,.is-hidden .pjl-glow,.is-hidden .pjl-p,
.is-hidden .psl-ring,.is-hidden .psl-hub,.is-hidden .psl-dot,.is-hidden .psl-line,
.is-hidden .routes-hero .phbg,.is-hidden .page-hero .phbg,.is-hidden .hero-bg,
.is-hidden .status-dot,.is-hidden .ch-av,.is-hidden #chat-tog::after,
.is-hidden .routes-map-drift,.is-hidden .routes-map-arc-glow,.is-hidden .routes-map-pulse{
  animation-play-state:paused
}

/* ═══════════════════════════════════════════════════════════
   THE REDUCED-MOTION CONTRACT (§2.6)
   Reduced motion means LESS movement, not NO feedback — hover,
   focus, error and loading states must remain legible.
   ═══════════════════════════════════════════════════════════ */
@media(prefers-reduced-motion:reduce){
  /* 1. Kill ambient/decorative loops outright */
  .hero-aurora,.hero-beams,.hb,.pjl-particles,.psl-origin,.psl-dots,.psl-lines{
    animation:none!important
  }
  .hero-beams,.pjl-particles{display:none}
  .ph-flow-lane,.ph-flow-node,.pjl-spine,.pjl-glow,.pjl-p,.psl-ring,.psl-hub,.psl-dot,
  .psl-line,.routes-hero .phbg,.page-hero .phbg,.hero-bg{animation:none!important}

  /* 2. Remove spatial movement, keep opacity + color (they carry meaning) */
  [data-reveal],.rv,.rv-l,.rv-r,.rv-s{opacity:1!important;transform:none!important}
  .rail::after{transform:scaleY(1)!important;animation:none!important}
  *{scroll-behavior:auto!important}

  /* 3. Feedback SURVIVES — shortened, not deleted */
  .btn-p,.btn-o,.field input,.field .field-err,.crumbs a,.rail-node{
    transition-duration:100ms!important
  }
  .btn-p:active,.btn-o:active{transform:none}
  .btn-p[aria-busy="true"]::after{animation-duration:1.4s}  /* keep the spinner legible */
}

/* ── HERO PLATE (routes.html + services.html ONLY — not a site-wide pattern) ──
   L0 = graded still, L1 = optional ambient loop injected by shared.js.
   The scrim is measured against the BRIGHTEST frame of the loop, not the poster.
   Left stops raised from the drafted .97/.93/.62 after measurement (G5): at those
   values the steel <p> under the loop's brightest region reached only 6.58:1;
   .985/.95/.68 gives white 18.3:1 and steel 7.0:1 against the p95 composited
   background of the headline column. */
.hero-plate{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none;background:var(--navy)}
/* The plate is a CSS background, not an <img>: a full-hero <img> became the LCP
   element and cost +240ms on both pages (measured, G1). A background paints the
   same pixels without entering LCP candidacy; the h1 keeps its text LCP. */
.hero-plate .plate-bg{
  position:absolute;inset:0;
  background-size:cover;background-position:var(--plate-pos,50% 50%)
}
.hero-plate video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:var(--plate-pos,50% 50%)
}
.hero-plate video{opacity:0;transition:opacity 900ms var(--ease-out)}
.hero-plate video.is-live{opacity:1}

/* Two-axis scrim: the horizontal pass protects the headline column, the vertical
   pass clears the fixed navbar at the top and dissolves into the page at the bottom
   so there is no seam where the section ends. */
.hero-plate::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(90deg,rgba(4,13,28,.985) 0%,rgba(4,13,28,.95) 34%,
                          rgba(4,13,28,.68) 62%,rgba(4,13,28,.40) 100%),
    linear-gradient(180deg,rgba(4,13,28,.88) 0%,rgba(4,13,28,0) 26%,
                           rgba(4,13,28,0) 58%,var(--navy) 100%)
}
/* Below the text/plate split point the copy sits ON the image, so the scrim
   becomes vertical-only and much heavier. Mid stop raised .82 → .88 (G5):
   the mobile crop centres on the bright city-light band and .82 gave the
   steel <p> 6.58:1; .88 brings it to ~6.7:1 (AA pass) without erasing the plate.
   The plate image itself is not loaded at all at ≤900px (G1): on the throttled
   mobile link its 17–32KB cost ~150–200ms of LCP against the text baseline,
   so mobile keeps the pre-existing gradient hero (which sits underneath and
   was the shipping design) — the photographic plate is a ≥901px enhancement. */
@media(max-width:900px){
  .hero-plate{background:transparent}
  .hero-plate .plate-bg{background-image:none}
  .hero-plate::after{
    background:linear-gradient(180deg,rgba(4,13,28,.96) 0%,rgba(4,13,28,.88) 45%,var(--navy) 100%)
  }
}
@media(prefers-reduced-motion:reduce){
  .hero-plate video{display:none}
}

/* Chain pip belt-and-braces (services.html): the rAF driver never mounts these
   under reduced motion and does not run in hidden tabs — these guards only catch
   embedding cases outside that driver's control. */
.is-hidden .chain-pip,.is-hidden .chain-rail-fill{animation-play-state:paused}
@media(prefers-reduced-motion:reduce){
  .chain-pip,.chain-rail-fill{display:none}
}
