/* ═══ GAP FOLD: scroll-revealed text, skyline, close (shared) ═══════════════
   The pinned "Every system sees a piece" fold, moved out of viper.css on
   2026-11-03 so Mission OS fold 2 (#coordination) can use it too. Each rule
   below carries both pages' ids: #gap / #gap-result on viper.html (currently
   hidden) and #coordination / #coordination-result on mission-os.html.
   JS: gap-scroll.js and gap-result-scroll.js (both find either id).
   ═══════════════════════════════════════════════════════════════════════ */

/* ── FOLD 2: EVERY SYSTEM SEES A PIECE — plain text passage ────────────────
   No visuals, no card treatment, no eyebrow, no muted/grey copy anywhere —
   just four groups (heading+subtext, the "but these systems..." paragraph,
   the constrained-by-interfaces list, the closing statement), centred, all
   full white, all set in the heading sizes (t-h1/t-h2/t-h3 — see the
   markup, there's no t-body left in this section) rather than reading-body
   scale, so the passage reads as one large deliberate statement instead of
   regular copy. Capped to the body measure and centred within the full
   bleed width so it doesn't run edge-to-edge at wide widths. This base
   rule is also the <1024px/reduced-motion/no-GSAP fallback: gap-scroll.js
   only ever adds behaviour on top via the [data-motion="pinned"] rules
   below, never removes this stacked layout. */
:is(#gap,#coordination) .gap-copy{ display:flex; flex-direction:column; gap:var(--space-8);
  max-width:var(--measure-body); margin-inline:auto; text-align:center; }
/* Bigger than the surrounding body copy so it reads as the section's
   punchline, not just another paragraph — "in big font" per the brief. */
:is(#gap,#coordination) .gap-statement{ margin:0; }
/* The 5-point interfaces list used to live here as a bordered .copy-list —
   moved out to its own #gap-result section/skyline (viper.html, and the
   rules further down this file) once the pin was split in two. Nothing
   #gap-scoped needed for .copy-list any more; the shared component itself
   (components.css) is untouched and still serves every other page that
   uses it (applications/nexus/privacy/terms). */

/* ── FOLD 2, PINNED: scroll-scrubbed, one scene revealed at a time ─────────
   >=1024px, GSAP present, motion not reduced (gap-scroll.js).

   Every value that changes in here — which scene is showing, how bright
   each phrase is, that box border's grey-to-white climb — is written
   directly onto the element as an inline style by gap-scroll.js on every
   scroll-driven update, not toggled by a class. transition:none
   throughout this block is deliberate: a lingering CSS transition would
   keep easing each new inline value in after the fact, which is exactly
   the "it keeps changing after I've stopped scrolling" feel this was
   rewritten to remove — scroll is the only thing that moves anything
   here, forward or back.

   No extra section padding either: the pinned stage below is already a
   full screen on its own, so the usual top/bottom section rhythm padding
   on top of it just added dead space around an already-full-height block
   — that's what was reading as too much space above and below. min-height
   is pulled in from a full 100svh too, for the same reason: a short scene
   (the opening heading+subtext, mainly) was floating in a lot of empty
   black above and below it.

   A narrower measure than the base rule above (max-width) — at these
   larger sizes, the full body measure would run so wide each scene sits
   on one or two lines only; capping it tighter here means more, shorter
   lines, closer to how the brief's reference reads.

   Scenes are stacked on top of each other (position:absolute, all sharing
   the same box) and cross-fade via opacity — gap-scroll.js computes that
   fade directly from scroll progress at each scene boundary, not a class
   toggle. .gap-copy needs an explicit height and position:relative for
   that stack to have something to centre inside and anchor to; centring
   is a plain top:50%/-50% shift rather than flex's justify-content:center,
   since flex can only centre a single in-flow child and these are all
   absolutely positioned on top of one another instead.

   The network-mesh illustration sits behind the whole walk, not per scene
   — background-attachment:fixed pins it to the viewport for as long as
   #gap is pinned, so it reads as one continuous backdrop the text plays
   over rather than four separate images swapping with the scenes. Scoped
   to [data-motion="pinned"] only, so it's a >=1024px thing; fixed
   attachment has known jank on mobile browsers anyway, and below that
   width the section is the plain stacked fallback with no full-bleed
   backdrop to hang it on. The flat scrim underneath the image is a
   plain-black overlay, not the section's own surface-dark token, because
   it has to stay dark enough for white text and yellow box outlines to
   read clearly without burying the terrain under it — .74 was doing the
   latter at the cost of the former (the image was barely readable);
   pulled down to .5, which still checked clean for text/border contrast
   against the image in testing. This is the one spot on the page tuned
   by eye against the image rather than pulled from the surface tokens. */
:is(#gap,#coordination)[data-motion="pinned"]{
  padding-block:var(--section-y-sm);
  background-image:linear-gradient(rgba(6,6,6,.5),rgba(6,6,6,.5)),
    url("../img/viper-fold2-network.webp");
  background-size:cover; background-position:center; background-repeat:no-repeat;
  background-attachment:fixed;
}
:is(#gap,#coordination)[data-motion="pinned"] .gap-copy{
  position:relative; min-height:92svh; max-width:var(--measure-lead);
}
:is(#gap,#coordination)[data-motion="pinned"] .gap-scene{
  position:absolute; left:0; right:0; top:50%; transform:translateY(-50%);
  opacity:0; pointer-events:none; transition:none;
}
/* Only one div-wrapped scene left (heading+subtext) now that the
   intro+list scene and the closing statement have moved out to
   #gap-result below — still needs its own flex column + gap between
   children, unlike the remaining bare-<p> scene ("but these systems...",
   a single text node's worth of .brighten spans, which reads fine as
   plain block text; forcing flex on it would turn those inline spans into
   flex items and lay them out side by side instead of wrapping as a
   sentence). */
:is(#gap,#coordination)[data-motion="pinned"] div.gap-scene{ display:flex; flex-direction:column; gap:var(--space-6); }

/* Every phrase/sentence/list item inside the active scene (.brighten,
   wrapped per phrase in the markup) starts dim and climbs to full white
   as gap-scroll.js scrubs its opacity in directly from scroll position —
   see the file header above for why there's no transition on it. */
:is(#gap,#coordination)[data-motion="pinned"] .gap-scene .brighten{ opacity:.3; transition:none; }

/* .gap-stage wraps the two scenes. On desktop it is invisible to layout, so
   the pinned rules above see the scenes as .gap-copy's own children. */
:is(#gap,#coordination) .gap-stage{ display:contents; }

/* Below 1024: the same fold as desktop, sized for a phone (same pattern as
   NEXUS's #problem, nexus.css). The stage is a full-screen panel with the
   same background image + scrim, pulled out to the viewport edges. With JS
   (data-motion="scrub", gap-scroll.js) .gap-copy becomes a tall track and the
   stage is position:sticky inside it: scrolling reveals the first scene
   letter by letter, then it fades out and the second scene fades in once
   in the same spot. Without JS or with reduced motion it is the same panel
   with both scenes stacked at full strength. No background-attachment:fixed
   (iOS ignores it and scales the image to the whole page). */
@media (max-width:1023px){
  :is(#gap,#coordination){ padding-top:0; }
  :is(#gap,#coordination) .gap-copy{ max-width:none; margin-inline:calc(var(--edge) * -1); gap:0; }
  :is(#gap,#coordination) .gap-stage{
    display:flex; flex-direction:column; justify-content:center; gap:var(--space-8);
    min-height:100svh; padding:var(--section-y-sm) var(--edge);
    background-image:linear-gradient(rgba(6,6,6,.5),rgba(6,6,6,.5)),
      url("../img/viper-fold2-network.webp");
    background-size:cover; background-position:center; background-repeat:no-repeat;
  }
  :is(#gap,#coordination)[data-motion="scrub"] .gap-copy{ height:calc(100svh + var(--scrub-distance, 210svh)); }
  :is(#gap,#coordination)[data-motion="scrub"] .gap-stage{ position:sticky; top:0; height:100svh; display:block; }
  :is(#gap,#coordination)[data-motion="scrub"] .gap-scene{
    position:absolute; left:var(--edge); right:var(--edge); top:50%; transform:translateY(-50%);
    transition:opacity var(--dur-enter) var(--ease-out);
  }
  :is(#gap,#coordination)[data-motion="scrub"] div.gap-scene{ display:flex; flex-direction:column; gap:var(--space-6); }
  :is(#gap,#coordination)[data-motion="scrub"] .gap-scene + .gap-scene,
  :is(#gap,#coordination)[data-motion="scrub"] .gap-scene.is-out{ opacity:0; pointer-events:none; }
  :is(#gap,#coordination)[data-motion="scrub"] .gap-scene.is-in{ opacity:1; pointer-events:auto; }
}


/* ── FOLD 2b: THE RESULT — five-point skyline, then the merge ─────────────
   Base rule here is also the <1024px/reduced-motion/no-GSAP fallback,
   same convention as #gap above: gap-result-scroll.js only ever adds
   behaviour on top via the [data-motion="pinned"] rules further down,
   never removes this stacked, everything-visible layout. */
:is(#gap-result,#coordination-result) .gap-result-stage{ display:flex; flex-direction:column;
  align-items:center; gap:var(--space-8); max-width:var(--measure-lead);
  margin-inline:auto; text-align:center; }
:is(#gap-result,#coordination-result) .gap-result-title{ margin:0; }
:is(#gap-result,#coordination-result) .gap-skyline{ list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:var(--space-5); width:100%; }
/* Smaller than the section's own big-type default (t-h2 title above) —
   plain --text-body rather than echoing it as another heading size — but
   not the caption-scale .8 multiplier this briefly carried; bumped back
   up a step per direct follow-up ("too small now, make it a little big")
   while staying visibly more compact than the title above it. */
:is(#gap-result,#coordination-result) .gap-point-text{ margin:0; font-size:var(--text-body);
  font-weight:400; line-height:var(--lh-snug); }
/* The stem + travelling dot are a >=1024px/pinned-only flourish — five
   columns of vertical lines side by side would be cramped reading on a
   narrow viewport, so the fallback here is the plain stacked list above,
   same as #gap's own boxed points used to fall back to. */
:is(#gap-result,#coordination-result) .gap-stem, :is(#gap-result,#coordination-result) .gap-dot{ display:none; }
:is(#gap-result,#coordination-result) .gap-result-close{ margin:0; }
/* Same >=1024px/pinned-only flourish as the stem/dot above — this is the
   photo that only shows up behind the final merge beat (gap-result-bg,
   below), not something the plain stacked fallback needs to carry. */
:is(#gap-result,#coordination-result) .gap-result-bg{ display:none; }

/* RAIL (below 1024, gap-result-scroll.js sets data-motion="rail"): the five
   points become a timeline down a rail on the left. The rail is a hairline
   with an accent fill that grows with the scroll (--rail, 0..1, written by
   JS); each point's dot sits on it and lights (.is-lit) as the fill reaches
   it, while JS brightens the text. Left-aligned so the rail reads as one
   line. Without JS this never applies and the plain centred list stands. */
@media (max-width:1023px){
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-result-stage{ align-items:stretch; text-align:left; }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-skyline{
    position:relative; gap:var(--space-7); padding:var(--space-2) 0 var(--space-2) var(--space-7);
  }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-skyline::before,
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-skyline::after{
    content:""; position:absolute; left:calc(var(--space-2) - var(--hair) / 2); top:0; bottom:0;
    width:var(--hair); background:var(--color-border);
  }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-skyline::after{
    width:2px; background:var(--color-accent);
    transform-origin:top center; transform:scaleY(var(--rail, 0));
  }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-point{ position:relative; }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-point-text{ transition:none; }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-dot{
    display:block; position:absolute; top:.45em; z-index:1;
    left:calc(var(--space-2) - var(--space-7) - var(--space-3) / 2);
    width:var(--space-3); height:var(--space-3); border-radius:50%;
    background:var(--color-bg); border:2px solid var(--color-border-strong);
    transition:background var(--dur-ui) var(--ease-out), border-color var(--dur-ui) var(--ease-out);
  }
  :is(#gap-result,#coordination-result)[data-motion="rail"] .gap-point.is-lit .gap-dot{ background:var(--color-accent); border-color:var(--color-accent); }
}

/* ── FOLD 2b, PINNED: scroll-scrubbed skyline + merge ──────────────────────
   >=1024px, GSAP present, motion not reduced (gap-result-scroll.js) — same
   "scroll position IS reveal progress" rule as #gap's own pin (see
   gap-scroll.js's file header for the full reasoning); nothing in here is
   a class toggle or a timed transition, everything is written straight
   from scroll progress on every ScrollTrigger update.

   The title stays in normal flow at the top of the stage (it's there from
   the start, not part of the point-by-point walk — "the title will be
   there" per the brief) and full brightness through all five points, only
   fading — per a later direct request — alongside the skyline once the
   merge slice starts. The skyline itself sits below it, also in normal
   flow. The close statement is the one thing pulled out of flow —
   absolutely centred over the SAME spot the skyline occupies, opacity 0
   until the final scroll slice — so the merge reads as the skyline (and
   the title above it) dissolving into the statement in place, not the
   statement arriving underneath them. */
/* position:relative here (not just on .gap-result-stage below) is what
   lets .gap-result-bg break out of .bleed's own side padding and sit
   truly edge-to-edge — the section itself spans the full viewport width
   with no inline padding of its own (.bleed carries that), so anchoring
   the photo to the SECTION's box instead of the padded .bleed/stage
   inside it is what "keep the image end to end on screen" needed; it was
   only filling the padded content column before. */
/* z-index:0 alongside position:relative is load-bearing, not decorative
   — position:relative on its own does NOT create a new stacking context
   (only an explicit z-index, or a handful of other properties, does), so
   without it .gap-result-bg's z-index:-1 below wasn't stacking against
   THIS section at all: with no ancestor between it and the page root
   establishing a context either, it painted at the very bottom of the
   ENTIRE page's stacking order — behind every other section's own
   opaque background — instead of just behind this section's own text.
   Root-caused after a direct "I can't see the bg image" report; z-index
   alone on .gap-result-bg wouldn't have fixed it without this. */
:is(#gap-result,#coordination-result)[data-motion="pinned"]{ padding-block:var(--section-y-sm); position:relative; z-index:0; }
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-result-stage{
  position:relative; min-height:92svh; max-width:none; justify-content:center;
}
/* The merge-beat photo (direct request: a background image for THIS
   reveal section, only appearing "when this text comes" — not for the
   five-point walk before it). Absolutely covers the whole stage, same
   cover/center/no-repeat treatment #gap's own pinned background uses
   above, but doesn't reuse that background-attachment:fixed trick: this
   one lives and dies with a single scroll-scrubbed opacity value rather
   than riding along as a fixed backdrop for a multi-scene walk, so it's a
   plain absolutely-positioned layer instead. z-index:-1 (not DOM order)
   is what actually keeps it behind the title/skyline/close text — this
   stage is already position:relative, so a negative z-index here paints
   behind every one of its normal-flow and positioned children without
   needing z-index bumped on any of them in turn. Ships pre-tinted and
   grained (the source photo, not a filter here), so this file only ever
   needs a light scrim on top for text contrast, same idea as #gap's own
   flat-black overlay above. Starts at opacity:0 — gap-result-scroll.js
   scrubs it up to 1 in lockstep with the close statement's own
   mergeLocal fade, so the photo arrives WITH "VIPER is built to close
   that gap.", never before it. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-result-bg{
  display:block; position:absolute; inset:0; z-index:-1;
  background-image:linear-gradient(rgba(6,6,6,.45),rgba(6,6,6,.45)),
    url("../img/gap-result-close-bg.webp");
  /* contain, not cover — direct follow-up ("why is it so zoomed? keep it
     proportional and true to size"): cover was scaling the photo up
     past its own aspect ratio to fill this full-bleed section edge to
     edge with no crop left over, which read as an aggressive zoom on a
     section this wide relative to the image's native 1774x887. contain
     shows the whole photo at its own proportions with nothing cropped or
     stretched; the section's own surface-dark fill (base.css) shows
     through as plain letterboxing on whichever axis has room to spare,
     which reads as intentional rather than a stray gap since the whole
     section is already that same dark tone. */
  background-size:contain; background-position:center; background-repeat:no-repeat;
  background-attachment:fixed;
  opacity:0; transition:none; will-change:opacity;
}
/* position:absolute + background-attachment:fixed (NOT position:fixed on
   the element itself) — a position:fixed version of this box was tried
   and reverted: data-motion="pinned" is set once, near-immediately, by
   matchMedia('(min-width:1024px)') and stays set for the rest of the
   page's life at that width — it does NOT toggle per pin start/end. A
   position:fixed .gap-result-bg is therefore viewport-pinned PERMANENTLY
   once the page is desktop-width, not just while this fold is on screen;
   once the scroll-scrubbed opacity locks at 1 at the end of the merge
   (nothing ever resets it back down as you keep scrolling past), the
   photo kept covering every section below it too ("extending to the
   entire page" — direct follow-up bug report). position:absolute keeps
   the element's own box tied to the SECTION's normal document-flow
   position instead: it can only ever paint while some part of that box
   is on screen, i.e. only for as long as the fold itself is, exactly
   like every other absolutely-positioned overlay on the site.

   background-attachment:fixed is what makes that compatible with contain/
   center still reading correctly: #gap-result itself (unlike
   .gap-result-stage, the thing GSAP actually pins) is never visually
   fixed to the viewport — it's an ordinary tall block spanning this
   whole fold's scroll distance, several viewport-heights tall — so a
   plain scroll-attached background would size/centre the photo against
   that entire tall box instead of whatever slice is actually visible.
   background-attachment:fixed makes the background's own positioning
   area the viewport regardless of the element's box size, while the
   element's box (confined to the section, per above) still gates
   whether anything paints at all. Same split #gap's own pinned backdrop
   relies on, just done with an extra element instead of the section's
   own background-image, since this one needs an independently
   scroll-scrubbed opacity the section's own background can't carry.

   will-change:opacity below is the fix for the SEPARATE "image is
   missing on localhost" bug, root-caused by direct testing, not guessed:
   this element sits at display:none until the pin context above is
   created, then at opacity:0 from this rule for however long the user
   takes to scroll down to this fold — often several seconds with
   nothing about it changing. Chromium was found to sometimes never
   (re)promote a proper paint layer for an element that's gone that long
   fully transparent before its opacity is later driven upward by JS:
   swapping in a freshly-inserted clone of the exact same element
   rendered correctly instantly, proving the element's own stale DOM/
   layer identity — not its CSS, position, or the pin mechanism — was
   what made it invisible. will-change:opacity tells the browser up
   front that this element's opacity will change later, which keeps a
   live, correctly composited layer for it the whole time instead of
   letting a long static opacity:0 period lead it to stop being properly
   repainted. */
/* Title now also carries a scrubbed opacity (gap-result-scroll.js) — full
   brightness through all five points, fading out in step with the skyline
   once the merge slice starts ("the heading should disappear" once the
   close statement lands), so transition:none here matters just as much as
   it does on .brighten: no lingering .reveal fade-transition fighting the
   inline value gap-result-scroll.js writes every frame. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-result-title{ transition:none; }
:is(#gap-result,#coordination-result)[data-motion="pinned"] .brighten{ opacity:.3; transition:none; }
/* margin-top pulled way in from --space-9 — per direct follow-up, point 3
   now goes tall enough to want to sit close under the title, not a fixed
   96px below it regardless of how tall the peak gets. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-skyline{
  flex-direction:row; align-items:flex-end; justify-content:space-between;
  gap:var(--space-4); margin-top:var(--space-5); transition:none;
}
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-point{
  display:flex; flex-direction:column; align-items:center; flex:1 1 0;
}
/* Narrower measure than the fallback's default — a tighter wrap keeps
   each point reading as a compact little block instead of a wide caption.
   Widened a touch from 15 to 18ch alongside the font-size bump above, so
   the bigger type doesn't wrap any tighter than it did before.

   min-height/flex/justify-content:flex-end is the fix for the skyline not
   actually reading as a skyline: these 5 sentences wrap to very different
   line counts at 18ch (6/4/6/5/5 measured), and with text stacked above
   the stem in a bottom-aligned column, that text height was the dominant
   term — point 1's 6-line block was taller overall than point 2's
   shorter-stem-but-4-line block despite point 2 having the taller stem,
   which is exactly the "2nd/4th should read taller than 1st/5th" bug
   reported live. Reserving the longest sentence's own line count (6) as a
   shared min-height for every point, and bottom-aligning each one's actual
   text inside that box fixes it: a <p>'s inline content becomes one
   anonymous flex item under display:flex — flex-direction:column here so
   the box's own width (max-width above) still governs where the text
   wraps, same as a plain block would; justify-content:flex-end then pins
   that whole text block, however many lines it wraps to, flush to the
   bottom of the reserved box, and align-items:center keeps it centred the
   same way text-align:center already reads for each wrapped line. Makes
   every point's text TOP land at the same height regardless of its own
   line count, so stem height is the only thing left driving how high each
   point's peak actually reads — which is what a skyline needs. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-point-text{
  max-width:18ch; margin:0 0 var(--space-3);
  min-height:calc(var(--text-body) * var(--lh-snug) * 6);
  display:flex; flex-direction:column; justify-content:flex-end; align-items:center;
}
/* pathLength isn't available on a plain CSS box the way the mission
   diagram's SVG paths get it (motion.css), so the stem draws via
   clip-path instead — same inset() technique #problem's connector-scan
   loop uses (motion.css's pd-scan), just driven by scroll progress here
   instead of a CSS animation. inset(100% 0 0 0) is fully hidden (the
   whole box clipped away from the top); gap-result-scroll.js scrubs that
   top inset down to 0% as the dot climbs, so the line appears to draw
   itself upward in lockstep with the dot's own travel. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-stem{
  display:block; width:var(--hair); height:var(--stem-h,var(--space-9));
  background:var(--color-border-strong); clip-path:inset(100% 0 0 0); transition:none;
}
/* Starts at the stem's own foot (no transform) and travels up by the
   stem's full height as its point's slice plays — gap-result-scroll.js
   reads --stem-h off each stem via getBoundingClientRect() rather than
   this file duplicating the raw pixel value, so the two never drift out
   of sync. Fades out over the last part of the climb, timed to land at
   0 exactly as its point's text reaches full brightness — see
   gap-result-scroll.js's CLIMB_END for where that handoff starts. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-dot{
  display:block; width:6px; height:6px; border-radius:50%; margin-top:-1px;
  background:var(--color-accent); opacity:0; transition:none;
}
/* Resting state before the final slice: sat over the skyline's own space
   (see .gap-result-stage above), invisible and non-interactive until
   gap-result-scroll.js scrubs it in during the merge. */
:is(#gap-result,#coordination-result)[data-motion="pinned"] .gap-result-close{
  position:absolute; left:0; right:0; top:60%; transform:translateY(-50%);
  opacity:0; pointer-events:none; transition:none; text-align:center;
}




/* ── LETTER SPLIT (gap-scroll.js) ──────────────────────────────────────────
   .split-w is an inline-block so a word cannot break across lines — without
   it, per-letter spans let the browser break mid-word and the headings came
   apart. .split-c stays inline: inline-block on a letter would kill the
   kerning between every pair.

   transition:none is the same reasoning as the .brighten rules above — these
   opacities are written straight from scroll position every frame, so a
   transition would make them chase the scrollbar instead of tracking it. */
.split-w{ display:inline-block; }
.split-c{ display:inline; transition:none; }


/* ── THREE-POINT VARIANT (Mission OS #coordination-result) ────────────────
   Each point is a h3 + one sentence, in three columns instead of five, so
   the text gets a wider column than the five-up skyline's 18ch. The tallest
   stem is the middle one (set per point in the markup). */
#coordination-result .gap-point-text{ display:flex; flex-direction:column; gap:var(--space-2); }
#coordination-result .gap-point-text h3{ margin:0; }
#coordination-result .gap-point-text p{ margin:0; }
#coordination-result .gap-skyline{ text-align:center; }
@media (min-width:1024px){
  #coordination-result[data-motion="pinned"] .gap-skyline{ justify-content:center; gap:var(--space-7); max-width:1100px; margin-inline:auto; }
  #coordination-result[data-motion="pinned"] .gap-point{ max-width:320px; }
  #coordination-result[data-motion="pinned"] .gap-point-text{
    max-width:none; align-items:center;
    min-height:calc(var(--text-body) * var(--lh-snug) * 5 + var(--text-h3, 1.5rem) * 2.6);
  }
  /* the close is two sentences at t-h1 (the size of VIPER's "close that gap" line), so it keeps a measure and stays centred */
  #coordination-result[data-motion="pinned"] .gap-result-close{ max-width:min(1100px,100%); margin-inline:auto; }
}
#coordination-result .gap-result-close{ max-width:min(1100px,100%); margin-inline:auto; }

/* No bottom padding once pinned: the fixed photo would otherwise show as a
   loose band under the close statement after the pin lets go. */
@media (min-width:1024px){
  #coordination-result[data-motion="pinned"]{ padding-bottom:0; }
}

/* Mission OS lead under the heading: regular weight, not the t-h3 medium. */
#coordination .gap-scene p.t-h3{ font-weight:400; }
