/* ═══ MISSION OS PAGE (mission-os.html) ═══════════════════════════════════
   Linked last, after motion.css. Deliberately near-empty: every section on
   this page is built from shared components (.split, .pillars + .card-notch,
   .metrics-grid, .cta-band, .hero) so the same sections can sit on VIPER,
   NEXUS or Home without restyling. Only rules nothing else could use go here.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── THE COORDINATION GAP + ONE MISSION NETWORK ───────────────────────────
   Both folds are a split header over the shared capability cards
   (.pillars--compact, components.css; SECTION-TYPES.md type 9). The only
   page-level rule is the gap's close statement, held to the lead's measure
   so a t-h2 does not run the full bleed. */
#coordination .mos-close{ max-width:var(--measure-lead); }

/* ── 01 MISSION ORCHESTRATION: the "rewire" (SECTION-TYPES.md type 11) ────
   Without vs With Mission OS, one OODA step per column. Static (no JS,
   reduced motion, below 1024): the diagram shows its finished With state
   and every step shows both lines, Without above With. Pinned (>=1024,
   rewire-scroll.js sets data-motion="pinned"): scroll walks the diagram
   from four separate stacks to one Mission OS node and swaps each step's
   line in place. Every scrubbed value is written inline by the module;
   nothing here transitions it (guidelines, Motion: scroll IS progress).

   Colour: lines and boxes are the active pole at opacity, so the fold works
   on either surface. The Mission OS disc is this viewport's one accent
   moment. On dark its ring and the dashed tasking lines are accent too (one
   object); on light they go to ink, since accent strokes vanish on cream
   (rule 11) and a fill is the only accent the light pole takes.

   Above the drawing, ONE label: "Without Mission OS" until Observe begins,
   then "With Mission OS" for the rest of the fold (asked for 2026-10-28;
   it was a Without ——— With meter filling across all four steps). Both
   spans share one grid cell; rewire-scroll.js swaps them, the old one
   leaving upward as the new one rises in. Static (no JS, reduced motion)
   it reads With, matching the drawing, which is the With state.

   Steps wear the site's tab marks (.step-tab, components.css): the step
   being drawn is bold with the 3px bar filling under it; a finished step
   turns into a solid box in the pole's ink (white on dark). */
.rewire{ margin-top:var(--section-y-sm); display:flex; flex-direction:column; gap:var(--space-7); }

.rewire-state{ display:grid; justify-items:start; overflow:hidden; }
.rewire-state > .t-label{ grid-area:1 / 1; color:var(--color-text); }
.rewire-state-without{ opacity:0; }

.rewire-svg{ display:block; width:100%; height:auto; overflow:visible; }
.rw-box{ fill:var(--color-bg); stroke:var(--color-border-strong); stroke-width:var(--hair); }
.rw-box--on{ stroke:var(--color-pill-border); }
/* Screens and operators are solid boxes (asked for 2026-10-23): on the
   light pole a black box with a white label, the same move as a dark card
   on a light ground (guidelines, Surfaces). On dark a black box would
   vanish into the ground, so there it keeps the outline. */
.rw-box--solid{ stroke:var(--color-pill-border); }
.surface-light .rw-box--solid{ fill:var(--black); stroke:none; }
.rw-line{ fill:none; stroke:var(--color-border-strong); stroke-width:calc(var(--hair) * 1.5); }
.rw-path{ fill:none; stroke:var(--color-text-muted); stroke-width:calc(var(--hair) * 1.5);
  stroke-dasharray:1; stroke-dashoffset:0; }
.rw-task{ fill:none; stroke:var(--color-accent); stroke-width:calc(var(--hair) * 1.5); stroke-dasharray:4 6; }
.rw-ring{ fill:none; stroke:var(--color-accent); stroke-width:3; stroke-dasharray:1; stroke-dashoffset:0; }
.rw-disc{ fill:var(--color-accent); }
.surface-light .rw-task, .surface-light .rw-ring{ stroke:var(--color-text); }
/* Label sizes are viewBox units, so they scale with the drawing: 1.5x the
   14px UI size reads at ~16px when the diagram is ~900px wide. */
.rw-text{ font-family:var(--font-display); font-weight:400; font-size:calc(var(--ui-sm) * 1.5);
  letter-spacing:var(--track-label); text-transform:uppercase; fill:var(--color-text-muted); }
.rw-text--on{ fill:var(--color-text); }
.rw-label{ fill:var(--color-text); }
.surface-light .rw-label{ fill:var(--white); }
.rw-core-label{ font-family:var(--font-display); font-weight:500; font-size:calc(var(--ui-md) * 1.25);
  fill:var(--color-accent-ink); }

/* Static default IS the With state: the Without-only parts are not drawn. */
[data-rw="oplink"], [data-rw="op"], [data-rw="scr"]{ opacity:0; }
/* one line per feed: the module bends it from a straight drop (Without)
   into a curve to the node (With) */
.rw-feed{ fill:none; stroke:var(--color-text-muted); stroke-width:calc(var(--hair) * 1.5); }
/* the node's four layer bars: each fades in as its screen fades out */
.rw-bar{ fill:var(--color-accent-ink); }

.rewire-steps{ list-style:none; margin:0; padding:0; display:grid; grid-template-columns:1fr; gap:var(--space-6) var(--gutter); }
.rewire-step{ position:relative; }
/* static: nothing is "current", every step name reads at full ink */
.rewire-step-name{ margin:0 0 var(--space-4); color:var(--color-text);
  padding-top:var(--space-4); border-top:var(--hair) solid var(--color-border); }
.rewire-step .step-bar{ display:none; }
.rewire-copy{ display:grid; gap:var(--space-4); }
.rewire-copy p{ margin:0; display:flex; flex-direction:column; gap:var(--space-2); max-width:var(--measure-card); }

/* .rewire-art and .rewire-state show on phones too (were display:none below
   640; reversed with the .pic-media hide in components.css). */
@media (min-width:640px){ .rewire-steps{ grid-template-columns:repeat(2,minmax(0,1fr)); } }

/* Two drawings of the rewire diagram: the wide one from 1024 up, the
   portrait one (.rewire-svg--m, two columns of chips) below, with bigger
   labels so they read on a phone: 2x --ui-sm in viewBox units, which is
   about 13px on screen at phone width. */
.rewire-svg--m{ display:none; }
.rewire-svg--m .rw-text{ font-size:calc(var(--ui-sm) * 2); }
@media (max-width:1023px){
  .rewire-svg:not(.rewire-svg--m){ display:none; }
  .rewire-svg--m{ display:block; max-width:var(--measure-lead); margin-inline:auto; }
}

/* PINNED ON A PHONE (data-motion="card", rewire-scroll.js, below 1024): one
   screen tall, held while the scroll plays the same choreography as
   desktop. The Without/With label on top (clear of the fixed nav), the
   drawing filling the middle, and ONE step card at the bottom: every step
   sits in the same grid cell and only the reached one (.is-shown) is
   visible; inside it the Without line gives way to the With line as JS
   cross-fades them, exactly as on desktop. */
@media (max-width:1023px){
  .rewire[data-motion="card"]{
    height:100svh; margin-top:0; gap:var(--space-5);
    padding:calc(var(--space-8) + var(--space-5)) 0 var(--space-5);
  }
  .rewire[data-motion="card"] .rewire-art{ flex:1 1 auto; min-height:0; display:flex; align-items:center; }
  .rewire[data-motion="card"] .rewire-svg{ max-height:100%; }
  .rewire[data-motion="card"] .rewire-steps{ flex:none; display:grid; grid-template-columns:1fr; }
  .rewire[data-motion="card"] .rewire-step{
    grid-area:1 / 1; padding:var(--space-5); background:var(--color-bg);
    border:var(--hair) solid var(--color-border-strong);
    opacity:0 !important; visibility:hidden;
    transition:opacity var(--dur-ui) var(--ease-out), visibility 0s linear var(--dur-ui);
  }
  .rewire[data-motion="card"] .rewire-step.is-shown{
    opacity:1 !important; visibility:visible; transition:opacity var(--dur-ui) var(--ease-out);
  }
  .rewire[data-motion="card"] .rewire-step-name{ margin-bottom:var(--space-3); padding-top:0; border-top:0; }
  /* the card's own progress: the site's tab bar (.step-bar, components.css)
     along its bottom border, filling through the current step's beat */
  .rewire[data-motion="card"] .rewire-step .step-bar{
    display:block; bottom:calc(var(--hair) * -1); transform:scaleX(var(--card-progress, 0));
  }
  .rewire[data-motion="card"] .rewire-copy > p{ grid-area:1 / 1; }
  .rewire[data-motion="card"] .rewire-without .t-meta,
  .rewire[data-motion="card"] .rewire-with .t-meta{ display:none; }
}
@media (min-width:1024px){
  .rewire-steps{ grid-template-columns:repeat(4,minmax(0,1fr)); }

  /* PINNED: this fold gets its own, roomier layout. The drawing is wide
     (about 2.3:1), so stacking the steps under it starved it of height on
     short laptop screens and left it a small strip in a sea of black.
     Pinned, the steps become a narrow list on the left and the drawing
     takes the rest of the width at full height, with a wide gutter
     between them. The Without/With label sits over the drawing. */
  .rewire[data-motion="pinned"]{
    min-height:100svh; margin-top:0; padding-block:var(--space-8);
    display:grid; grid-template-columns:minmax(0,4fr) minmax(0,9fr); grid-template-rows:auto auto;
    column-gap:var(--space-9); row-gap:var(--space-7); align-items:center; align-content:center;
  }
  .rewire[data-motion="pinned"] .rewire-state{ grid-column:2; grid-row:1; align-self:end; }
  .rewire[data-motion="pinned"] .rewire-art{ grid-column:2; grid-row:2; align-self:start; }
  .rewire[data-motion="pinned"] .rewire-svg{
    max-height:calc(100svh - var(--space-8) * 2 - var(--space-9)); }
  .rewire[data-motion="pinned"] .rewire-steps{ grid-column:1; grid-row:1 / 3; align-self:center;
    grid-template-columns:1fr; gap:var(--space-3); }

  /* Pinned, the steps are a progress track for the diagram beside them.
     The step being drawn is current (.is-current: bold name, the tab bar
     filling along its bottom edge with the beat, --step-progress written
     by rewire-scroll.js). When its change lands (.is-on, the same point
     the copy swaps) the box fills solid in the pole's ink: white on dark,
     black on light. Accent stays on the disc alone (asked for 2026-10-23:
     finished steps in white, not yellow). Steps still to come sit dimmed
     (opacity, scrubbed by the module). */
  .rewire[data-motion="pinned"] .rewire-step{
    padding:var(--space-4) var(--space-5);
    border:var(--hair) solid var(--color-border-strong); background:var(--color-bg);
    transition:background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
  }
  .rewire[data-motion="pinned"] .rewire-step-name{ margin-bottom:var(--space-3); padding-top:0; border-top:0;
    color:var(--color-text-muted); }
  .rewire[data-motion="pinned"] .rewire-step-name.is-current{ color:var(--color-text); }
  .rewire[data-motion="pinned"] .rewire-step.is-current .step-bar{ display:block;
    left:calc(var(--hair) * -1); right:calc(var(--hair) * -1); bottom:calc(var(--hair) * -1); }
  .rewire[data-motion="pinned"] .rewire-step.is-on{ background:var(--color-text); border-color:var(--color-text); }
  .rewire[data-motion="pinned"] .rewire-step.is-on,
  .rewire[data-motion="pinned"] .rewire-step.is-on .t-body,
  .rewire[data-motion="pinned"] .rewire-step.is-on .rewire-step-name,
  .rewire[data-motion="pinned"] .rewire-step.is-on .rewire-step-name .num{ color:var(--color-bg); }
  .rewire[data-motion="pinned"] .rewire-copy > p{ grid-area:1 / 1; }
  .rewire[data-motion="pinned"] .rewire-without .t-meta,
  .rewire[data-motion="pinned"] .rewire-with .t-meta{ display:none; }
}
/* Short laptop screens (a 1080p screen at 150% is ~1280x590 in the
   browser): the four steps outgrew the pinned frame and cut Act off at the
   bottom (reported 2026-10-24). Below 760px tall the frame tightens, one
   step down the spacing scale, so all four always fit. */
@media (min-width:1024px) and (max-height:760px){
  .rewire[data-motion="pinned"]{ padding-block:var(--space-6); row-gap:var(--space-5); }
  .rewire[data-motion="pinned"] .rewire-svg{
    max-height:calc(100svh - var(--space-6) * 2 - var(--space-8)); }
  .rewire[data-motion="pinned"] .rewire-step{ padding:var(--space-3) var(--space-5); }
  .rewire[data-motion="pinned"] .rewire-step-name{ margin-bottom:var(--space-2); }
}

/* ── 02 EDGE-TO-EDGE MESH: the live mesh (SECTION-TYPES.md type 12) ──────
   One network, six states. mesh-states.js sets .mesh-figure[data-state]
   and .is-active on the matching capability; everything visual below is
   keyed off those two. State changes are UI, not scroll scrubs, so they
   use short transitions. The link "flow" is a looping dash offset, the
   same device as VIPER's Mission Layer (.mn-line, viper.css).

   The picture under the overlay is a 3D diorama (mesh-scene.js): a
   tactical map tile with low-poly white assets that move on their own
   (Oct 2026). Assets are white by request (the accent versions read as
   toy-like), so the scene spends no accent on own assets; degraded or
   jammed is shown dashed, at reduced opacity. */
.mesh{ margin-top:var(--section-y-sm); display:grid; gap:var(--space-7); }
/* below 1024 the drawing sits ABOVE the list, so tapping a capability
   changes the picture right where the visitor is looking */
@media (max-width:1023px){ .mesh-figure{ order:-1; } }
@media (min-width:1024px){
  .mesh{ grid-template-columns:minmax(0,4fr) minmax(0,9fr); column-gap:var(--space-9); align-items:center; }
}

.mesh-caps{ list-style:none; margin:0; padding:0; border-top:var(--hair) solid var(--color-border); }
.mesh-cap{ position:relative; border-bottom:var(--hair) solid var(--color-border); }
/* the capability names wear the site's tab marks (.step-tab, components.css):
   muted, bold when open, the 3px accent bar under the open one */
.mesh-cap-btn{ width:100%; padding:var(--space-5) 0;
  background:none; border:0; text-align:left; cursor:default; }
.mesh:not([data-ready]) .mesh-cap-btn{ color:var(--color-text); } /* no JS: all six listed, none open */
.mesh-cap-desc{ margin:0 0 var(--space-5); max-width:var(--measure-card); }
.mesh-cap-bar{ display:none; bottom:calc(var(--hair) * -1); }

/* With JS: one capability open at a time, the rest are buttons. The open
   one's bar draws full unless something fills it (below). */
.mesh[data-ready] .mesh-cap-btn{ cursor:pointer; }
.mesh[data-ready] .mesh-cap:not(.is-active) .mesh-cap-desc{ display:none; }
.mesh[data-ready] .mesh-cap.is-active .mesh-cap-bar{ display:block; }
@media (hover:hover) and (pointer:fine){
  .mesh[data-ready] .mesh-cap-btn:hover{ color:var(--color-text); }
}
/* PINNED (>=1024, mesh-states.js): the section holds the screen while the
   scroll walks the six capabilities and turns the ground. The bar under
   the open capability is its share of the scroll (--beat, written by the
   module, no transition). */
@media (min-width:1024px){
  .mesh[data-motion="pinned"]{ min-height:100svh; margin-top:0; padding-block:var(--space-7); align-content:center;
    --step-progress:var(--beat, 0); }
  .mesh[data-motion="pinned"] .mesh-svg{ max-height:calc(100svh - var(--space-7) * 3); }
}
/* PINNED ON A PHONE (data-motion="card", below 1024, mesh-states.js): the
   same scroll-driven walk as desktop, free-scrolling. One screen tall,
   clear of the fixed nav: the diorama fills what the card leaves, its
   caption row under it, and ONE capability card at the bottom (every cap
   sits in the same grid cell; only .is-active shows) with its bar filling
   through that capability's share of the scroll (--beat). */
@media (max-width:1023px){
  .mesh[data-motion="card"]{
    height:100svh; margin-top:0; padding:calc(var(--space-8) + var(--space-4)) 0 var(--space-5);
    display:flex; flex-direction:column; gap:var(--space-4); --step-progress:var(--beat, 0);
  }
  .mesh[data-motion="card"] .mesh-figure{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
  .mesh[data-motion="card"] .mesh-stage{ flex:1 1 auto; min-height:0; display:flex; align-items:center; }
  /* inside the page margins, not edge to edge, with labels free to run into
     the margin: at full bleed the outermost labels (Command, Recon UAV)
     were cut off by the screen edge */
  .mesh[data-motion="card"] .mesh-figure{ margin-inline:0; }
  .mesh[data-motion="card"] .mesh-foot{ padding-inline:0; }
  .mesh[data-motion="card"] .mesh-svg{ max-height:100%; overflow:visible; }
  .mesh[data-motion="card"] .mesh-caps{ flex:none; display:grid; border-top:0; }
  .mesh[data-motion="card"] .mesh-cap{
    grid-area:1 / 1; padding:0 var(--space-5); border:var(--hair) solid var(--color-border-strong);
    background:var(--color-bg); opacity:0; visibility:hidden;
    transition:opacity var(--dur-ui) var(--ease-out), visibility 0s linear var(--dur-ui);
  }
  .mesh[data-motion="card"] .mesh-cap.is-active{
    opacity:1; visibility:visible; transition:opacity var(--dur-ui) var(--ease-out);
  }
  .mesh[data-motion="card"] .mesh-cap-btn{ padding:var(--space-5) 0 var(--space-3); cursor:default; }
}
/* auto-advance (small screens): the bar fills under the open capability while it cycles */
.mesh[data-auto="on"] .mesh-cap.is-active .mesh-cap-bar{
  animation:mesh-fill var(--mesh-dwell, 4500ms) linear both;
}
@keyframes mesh-fill{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }

.mesh-figure{ margin:0; }
/* phones: the drawing runs edge to edge (its own margins are empty), so
   it and its labels are as large as the screen allows; the caption row
   keeps the page edge */
@media (max-width:639px){
  .mesh-figure{ margin-inline:calc(var(--edge) * -1); }
  .mesh-foot{ padding-inline:var(--edge); }
}
/* The stage: the still picture, the live 3D canvas (mesh-scene.js) and
   the SVG overlay (labels, links, marks), stacked in one box. The SVG sizes
   the box; the still matches its letterboxing (object-fit:contain, like
   the viewBox's xMidYMid meet) and the canvas is placed by the module.
   The canvas fades in over the still once its first frame is drawn, then
   the still is hidden. No JS, no WebGL or a lost context: the still stays. */
.mesh-stage{ position:relative; }
.mesh-still{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; pointer-events:none; }
.mesh-gl{ position:absolute; pointer-events:none; opacity:0; transition:opacity var(--dur-enter) var(--ease-out); }
.mesh.is-3d .mesh-gl{ opacity:1; }
.mesh.is-3d .mesh-still{ visibility:hidden; transition:visibility 0s linear var(--dur-enter); }
.mesh-svg{ position:relative; z-index:1; display:block; width:100%; height:auto; overflow:hidden;
  max-height:calc(100svh - var(--space-10)); }
/* caption row: what the current state shows, and the motion control
   (WCAG 2.2.2: the links move on their own and loop forever, so there has
   to be a way to stop them; on small screens it also stops the auto-walk) */
.mesh-foot{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-5); margin-top:var(--space-5); }
.mesh-caption{ display:block; min-height:1em; color:var(--color-text); }
.mesh-motion{ display:none; flex:none; padding:0; background:none; border:0; color:var(--color-text-muted);
  cursor:pointer; transition:color var(--dur-fast) var(--ease-out); }
.mesh[data-ready] .mesh-motion{ display:inline-flex; }
@media (hover:hover) and (pointer:fine){ .mesh-motion:hover{ color:var(--color-text); } }
.mesh-cap-btn:focus-visible, .mesh-motion:focus-visible{ outline:2px solid var(--color-accent); outline-offset:4px; }

/* drawing */
.m-link{ fill:none; stroke:var(--color-text-muted); stroke-width:3; stroke-linecap:round;
  stroke-dasharray:.1 12; animation:mesh-flow 1.4s linear infinite;
  transition:opacity var(--dur-ui) var(--ease-out), stroke var(--dur-ui) var(--ease-out); }
/* thin links still carry meaning, so they hold 3:1 against the ground
   (WCAG 1.4.11): muted at two thirds is about 3.5:1; border-strong was 2.7 */
.m-link--thin{ stroke:var(--color-text-muted); stroke-opacity:.67; stroke-width:2; }
.m-link--long{ stroke-dasharray:.1 16; animation-name:mesh-flow-long; }
@keyframes mesh-flow{ to{ stroke-dashoffset:-24.2; } }
@keyframes mesh-flow-long{ to{ stroke-dashoffset:-32.2; } }
@keyframes mesh-flow-hop{ to{ stroke-dashoffset:-28; } }

.m-hold{ fill:none; stroke:var(--color-text-muted); stroke-width:1.5; stroke-dasharray:4 6; opacity:0;
  transition:opacity var(--dur-ui) var(--ease-out); }
/* Labels scale with the drawing, so on a narrow screen they shrank to 6px.
   --m-fit (mesh-states.js) is viewBox units per screen px; the max() keeps
   every label at --m-label-min on screen or larger (--k is a leftover of
   the perspective line art and stays 1: the diorama's camera has no
   perspective). Phones get 10px: any larger and labels collide (checked
   across every state, over time). */
.mesh-svg{ --m-label-min:12px; }
/* 12px, not 10, on phones too: the pinned phone fold gives the drawing the
   full screen width, and 10px labels did not read. */
.m-label{ font-family:var(--font-display);
  font-size:max(calc(var(--ui-sm) * 1.4), calc(var(--m-label-min) * var(--m-fit, 0) / var(--k, 1)));
  letter-spacing:var(--track-label); text-transform:uppercase; fill:var(--color-text-muted);
  paint-order:stroke; stroke:var(--color-bg); stroke-width:8px; stroke-linejoin:round; }
.m-bearer{ fill:var(--color-text); }
.m-x{ fill:none; stroke:var(--color-text); stroke-width:3; stroke-linecap:round; }
.m-ew ellipse{ fill:none; stroke:var(--color-text-muted); stroke-width:1.5; stroke-dasharray:6 8; }

/* The terrain, the assets, their rings and drop lines are the 3D scene
   now (mesh-scene.js): a tactical-map diorama with low-poly white assets
   (Oct 2026). This file styles only the overlay above it. */
.m-label--zone{ fill:var(--color-text); }

/* state-only parts start hidden */
.m-x, .m-bearer, .m-sat, .m-ew, .m-swarm{ opacity:0; transition:opacity var(--dur-ui) var(--ease-out); }

/* 01 Decentralised: command blinks out and back on a loop; the mesh flows on */
[data-state="decentralised"] .m-cmd,
[data-state="decentralised"] .m-up{ animation:mesh-drop var(--mesh-dwell, 4500ms) var(--ease-in-out) infinite; }
[data-state="decentralised"] .m-up{ animation:mesh-drop var(--mesh-dwell, 4500ms) var(--ease-in-out) infinite,
  mesh-flow 1.4s linear infinite; }
@keyframes mesh-drop{ 0%, 15%{ opacity:1; } 30%, 85%{ opacity:.12; } 100%{ opacity:1; } }

/* 02 Degraded & Denied: two links jammed, the alternates carry the load */
[data-state="degraded"] .m-jam{ stroke-dasharray:6 10; opacity:.3; animation:mesh-flow-jam 3s linear infinite; }
@keyframes mesh-flow-jam{ to{ stroke-dashoffset:-16; } }
[data-state="degraded"] .m-x{ opacity:1; }
[data-state="degraded"] .m-alt{ stroke:var(--color-text); }

/* 03 Zero-RF: every link dark, every node holding state */
[data-state="zerorf"] .m-link{ opacity:.14; }
[data-state="zerorf"] .m-hold{ opacity:1; }

/* 04 Any Bearer: SATCOM joins, each link type named */
[data-state="bearer"] .m-sat,
[data-state="bearer"] .m-bearer{ opacity:1; }

/* 05 Frequency-Agile: inside the EW zone the links hop instead of dropping */
[data-state="agile"] .m-ew{ opacity:1; }
[data-state="agile"] .m-inzone{ stroke:var(--color-text); stroke-dasharray:8 6; animation:mesh-flow-hop .5s linear infinite; }

/* 06 Built for Fleets: the swarm joins the mesh */
[data-state="fleets"] .m-swarm{ opacity:1; }

/* Paused by the visitor (.mesh-motion), or off screen (mesh-states.js):
   every loop holds where it is. Off screen it saves the CPU and battery
   that 30-odd looping lines would otherwise burn while nobody sees them.
   Specific enough to beat the state rules' animation shorthands above. */
.mesh[data-paused] .mesh-figure *,
.mesh[data-offscreen] .mesh-figure *{ animation-play-state:paused; }

@media (prefers-reduced-motion:reduce){
  .m-link, [data-state="decentralised"] .m-cmd, [data-state="decentralised"] .m-up,
  [data-state="degraded"] .m-jam, [data-state="agile"] .m-inzone, .mesh-cap-bar{ animation:none; }
  .mesh[data-ready] .mesh-motion{ display:none; } /* nothing moves on its own: no control */
  [data-state="decentralised"] .m-cmd, [data-state="decentralised"] .m-up{ opacity:.12; }
}

/* ── 03 MISSION AUTONOMY: the ladder, told through one mission ───────────
   SECTION-TYPES.md type 13. Four rungs across the top, the copy for the
   current rung on the left, one scroll-driven video on the right (a
   PLACEHOLDER render until the design team's video lands). The rung, the
   copy and the bar follow the video's chapters (autonomy-scroll.js). The
   rungs are a tab row in the site's tab marks (.step-tab, components.css):
   the current rung bold, its bar filling with the chapter (ink here: this
   section is light, and accent is never legible on the light pole). The
   scene sits on a dark ground panel (same move as VIPER's Edge Autonomy
   grid) because the placeholder render's own assets are accent. */
.au{ margin-top:var(--section-y-sm); display:flex; flex-direction:column; gap:var(--space-7); }

/* One row that scrolls sideways rather than wrapping: a wrapped row split
   the ladder over two lines, which read as two rows of tabs rather than one
   sequence. The hairline is an inset shadow, not a border, and the bar sits
   at bottom:0, because overflow-x:auto also clips vertically: a bar hung
   1px below the box (the old bottom:-hair) would be cut off. The scrollbar
   is hidden; the row is swipeable, and autonomy-scroll.js scrolls the
   current rung into view as the chapters change. */
.au-rungs{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:nowrap; gap:0 var(--space-6);
  overflow-x:auto; overscroll-behavior-x:contain; scrollbar-width:none;
  box-shadow:inset 0 calc(var(--hair) * -1) 0 var(--color-border-subtle); }
.au-rungs::-webkit-scrollbar{ display:none; }
.au-rung{ position:relative; flex:none; }
.au-rung-btn{ padding:var(--space-4) var(--space-5); background:none; border:0; text-align:left;
  white-space:nowrap; cursor:default; }
.au:not([data-ready]) .au-rung-btn{ color:var(--color-text); } /* no JS: an index, none current */
.au-rung .step-bar{ display:none; bottom:0; }
.au[data-ready] .au-rung.is-active .step-bar{ display:block; }

.au-body{ display:grid; gap:var(--space-7); }

/* PHONES AND TABLETS (below 1024, autonomy-scroll.js): no pin; the rungs
   and a prev/next pair under the video step through the ladder, each step
   playing its chapter from a light phone encode. The rung
   row drops to --ui-sm with tighter spacing so all four fit on one line on
   a tablet; on a phone it still swipes sideways (above). The pair is the
   site's shared .strip-controls; it only shows once JS is driving. */
.au-controls{ display:none; }
@media (max-width:1023px){
  .au[data-ready] .au-controls{ display:flex; padding-inline:0; margin-top:0; }
  .au-rungs{ gap:0 var(--space-4); }
  .au-rung-btn{ padding:var(--space-4) var(--space-3); font-size:var(--ui-sm); }
  .au-body{ gap:var(--space-5); }
}
@media (min-width:1024px){
  .au-body{ grid-template-columns:minmax(0,4fr) minmax(0,9fr); column-gap:var(--space-9); align-items:center; }
}
.au-copy{ display:grid; gap:var(--space-7); }
.au-step h3{ margin:0 0 var(--space-4); }
.au-step p{ margin:0; max-width:var(--measure-card); }
.au-step-op{ margin-top:var(--space-5) !important; padding-top:var(--space-4); border-top:var(--hair) solid var(--color-border);
  display:flex; gap:var(--space-3); color:var(--color-text-muted); }
.au-step-op span{ color:var(--color-text); }

.au-scene{ margin:0; padding:var(--space-5); background:var(--color-bg); }
/* the video keeps its 16:9 box before it loads, so nothing shifts */
.au-media{ position:relative; }
.au-video{ display:block; width:100%; height:auto; aspect-ratio:16 / 9; background:var(--color-bg); }
.au-cap{ display:block; margin-top:var(--space-3); }

/* driven: one rung's copy at a time, stacked in one cell so nothing jumps */
.au[data-ready] .au-rung-btn{ cursor:pointer; }
@media (hover:hover) and (pointer:fine){ .au[data-ready] .au-rung-btn:hover{ color:var(--color-text); } }
.au[data-ready] .au-copy > .au-step{ grid-area:1 / 1; opacity:0; visibility:hidden;
  transition:opacity var(--dur-ui) var(--ease-out), visibility 0s linear var(--dur-ui); }
.au[data-ready] .au-copy > .au-step.is-active{ opacity:1; visibility:visible; transition:opacity var(--dur-ui) var(--ease-out); }

@media (min-width:1024px){
  .au[data-motion="pinned"]{ min-height:100svh; margin-top:0; padding-block:var(--space-7); justify-content:center; gap:var(--space-6); }
  .au[data-motion="pinned"] .au-video{ max-height:calc(100svh - var(--space-10) * 2); object-fit:contain; }
}

/* ── 06 PRODUCTS: the home page's product cards, as dropdowns ──────────
   .card-product--disclose (components.css) since 2026-10-28. The rule
   that zeroed the lead's reserved three lines and gap is gone: the "More
   on" bar now sits under the lead, and the reserve keeps both cards' bars
   level. */

/* ── PLACEHOLDER BADGE ─────────────────────────────────────────────────
   Sits on the Mission Autonomy video while it is a stand-in for the design
   team's final render, so no one mistakes a draft for the finished thing.
   Remove the badge when the final asset lands. */
.ph-badge{ position:absolute; top:var(--space-3); right:var(--space-3); z-index:2; pointer-events:none;
  padding:var(--space-2) var(--space-3); border:var(--hair) dashed var(--color-text-muted);
  background:var(--color-bg); color:var(--color-text); line-height:var(--lh-flat); }


/* ── HUMAN AUTHORITY (SECTION-TYPES.md type 18) ──────────────────────────
   Header, film, points, stacked. The header is centred so it reads as a
   title card for the film rather than another .split. The film sits
   outside .bleed and runs to both viewport edges: 16:9 on phones, a
   letterbox from 1024 so it does not swallow the screen. Poster <img>
   under the <video>: under reduced motion components.css's rule is not in
   play here, so the video is hidden below and the still holds.
   Where scroll-driven animations exist (Chromium, Safari 26) the film
   opens from the content column (inset by --edge) to full bleed as it
   scrolls in; elsewhere it is simply full bleed. That runs on .ha-frame,
   not the figure: soft-reveal.js gives the figure .reveal-soft, whose .in
   state sets animation:none at higher specificity. */
.ha-head{ display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:var(--space-6); margin-inline:auto; max-width:var(--measure-body); }
.ha-head .eyebrow{ margin-bottom:0; }
.ha-head .t-lead{ max-width:var(--measure-lead); margin:0; }

.ha-film{ margin:var(--section-y-sm) 0 0; }
.ha-frame{ position:relative; aspect-ratio:16/9; overflow:hidden; background:var(--color-surface); }
.ha-frame img, .ha-frame video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
@media (min-width:1024px){ .ha-frame{ aspect-ratio:21/9; } }
@media (prefers-reduced-motion:reduce){ .ha-frame video{ display:none; } }
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion:no-preference){
    .ha-frame{ animation:ha-open linear both; animation-timeline:view(); animation-range:entry 0% cover 50%; }
  }
}
@keyframes ha-open{
  from{ clip-path:inset(0 var(--edge)); }
  to{ clip-path:inset(0 0); }
}

/* Four points in a row from 1024, two up from 640. A hairline over each
   instead of card chrome: the film above is the section's weight, and the
   points read as its caption. */
.ha-points{ list-style:none; margin:var(--section-y-sm) 0 0; padding:0;
  display:grid; grid-template-columns:1fr; gap:var(--space-7) var(--gutter); }
@media (min-width:640px){ .ha-points{ grid-template-columns:repeat(2,1fr); } }
@media (min-width:1024px){ .ha-points{ grid-template-columns:repeat(4,1fr); } }
.ha-points li{ display:flex; flex-direction:column; gap:var(--space-3);
  padding-top:var(--space-5); border-top:var(--hair) solid var(--color-border-strong); }
.ha-points .num{ color:var(--color-accent); }
.ha-points h3{ margin:0; }


/* ── MISSION NETWORK SCENE (SECTION-TYPES.md type 17) ─────────────────────
   Static first: heading + lead, the still of the finished network, then the
   three cards. network-scene.js sets data-motion="pinned" (>=1024px, motion
   allowed, WebGL up, models loaded) and everything below the @media line takes over:
   a tall track, a sticky 100svh stage, the 3D canvas (.net-gl) with the SVG
   overlay (links, tags, labels) over it, the caption rail in place of the cards.
   Two deliberate departures from the usual rules, scoped to this section:
   the scrim over the map's left edge, which is a gradient, and the map
   imagery itself. They are here because this is a cinematic set piece;
   neither is a precedent for anything else. */
.net{ padding-block:0; }
.net-stage{ display:flex; flex-direction:column; }
.net-top{ order:1; padding-top:var(--section-y); }
.net-visual{ order:2; position:relative; margin-top:var(--space-7); aspect-ratio:16 / 9; overflow:hidden; background:var(--black); }
.net-still{ display:block; width:100%; height:100%; object-fit:cover; }
.net-svg{ display:none; }
.net-gl{ position:absolute; inset:0; width:100%; height:100%; display:none; pointer-events:none; }
.net-rail, .net-bar{ display:none; }
.net-cards{ order:3; padding-block:var(--space-7) var(--section-y); }

@media (min-width:1024px){
  .net[data-motion="pinned"] .net-track{ height:1300vh; }
  .net[data-motion="pinned"] .net-stage{ position:sticky; top:0; height:100vh; height:100svh; overflow:hidden; display:block; }
  .net[data-motion="pinned"] .net-visual{ position:absolute; inset:0; margin:0; aspect-ratio:auto; }
  .net[data-motion="pinned"] .net-visual::after{ content:""; position:absolute; inset:0; pointer-events:none;
    background:linear-gradient(90deg, rgba(var(--rgb-black),.82) 0%, rgba(var(--rgb-black),.55) 28%, rgba(var(--rgb-black),0) 52%); }
  .net[data-motion="pinned"] .net-still{ display:none; }
  .net[data-motion="pinned"] .net-gl{ display:block; }
  .net[data-motion="pinned"] .net-svg{ display:block; position:absolute; inset:0; z-index:1; width:100%; height:100%; }
  .net[data-motion="pinned"] .net-cards{ display:none; }

  .net[data-motion="pinned"] .net-top{ position:absolute; inset:0 0 auto 0; z-index:2; padding-top:var(--space-8); pointer-events:none; }
  .net[data-motion="pinned"] .net-top .split{ grid-template-columns:minmax(0,1fr) minmax(0,24rem); }
  .net[data-motion="pinned"] .net-top .split > div:first-child{ max-width:34rem; }
  .net[data-motion="pinned"] .net-lead{ font-size:var(--text-eyebrow); line-height:1.5; max-width:24rem; justify-self:end; padding-top:var(--space-2); }

  .net[data-motion="pinned"] .net-rail{ display:grid; position:absolute; z-index:2; left:var(--edge); bottom:var(--space-9);
    width:min(19rem, 26vw); margin:0; padding:0; list-style:none; }
  .net-rail-step{ grid-area:1 / 1; align-self:end; pointer-events:none; border-top:var(--hair) solid var(--color-border); padding-block:var(--space-3); opacity:0; transform:translateY(var(--space-3));
    transition:opacity var(--dur-ui) var(--ease-out), transform var(--dur-ui) var(--ease-out); }
  .net-rail-step.on{ opacity:1; transform:none; pointer-events:auto; }
  .net-rail-n{ display:block; color:var(--color-text-muted); }
  .net-rail-n{ color:var(--color-accent); }
  .net-rail-step h3{ margin-top:var(--space-1); font-size:var(--text-lead); line-height:1.25; }
  .net-rail-step p{ margin:var(--space-2) 0 0; color:var(--color-text-muted); font-size:var(--text-eyebrow); line-height:1.45; }

  .net[data-motion="pinned"] .net-bar{ display:block; position:absolute; z-index:2; left:var(--edge); bottom:calc(var(--space-9) - var(--space-3));
    width:min(19rem, 26vw); height:var(--hair); background:var(--color-border); }
  .net-bar i{ display:block; height:100%; width:0; background:var(--color-accent); }
}
.net-svg text{ font-family:var(--font-display); }
