/* ═══════════════════════════════════════════════════════════════════════════
   DRIIF — NEXUS page-specific layout only.
   Anything reusable belongs in components, anything numeric in tokens. Every
   value below resolves to a token; the only literals are small SVG/positioning
   figures too fine-grained to tokenise, the same exception home.css's own
   diagrams (.pd-node, .hatch) already take.

   The hero shell, the problem-resolution "many inputs → one result" diagram,
   the flow strip, the picture-tabs pattern (now also driving the Operating
   Model section), and the Define section's points-list-against-pinned-media
   pattern are all shared with viper.html now and live in components.css —
   see the comment at the top of viper.css for how that split happened.
   What's below is what's actually specific to this page: the loop-back
   caption and the single-line scrolling tabs under Operating Model.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── THE GAPS COMPOUND: photo on the right, copy stays put ───────────────
   Was #problem's own .problem-resolve block (image right, copy left) —
   moved to its own section/id (#problem-gaps, nexus.html) once #problem
   became VIPER Fold-2's pinned scene walk instead; these rules just moved
   with it; the alignment/column choices are unchanged.

   Went through a fragmented-feeds-diagram version in between (Home's own
   .prob-diagram/.pd-stack motif) — a direct follow-up ("keep only the
   image in this not the text on top") dropped the diagram/labels back to
   a plain photo, so this is back to the same shape .pic-media already
   uses everywhere else on the site: no bespoke rules needed beyond the
   grid placement below.

   .problem-resolve (components.css) is a shared side-by-side grid with
   align-items:center, which is the right call here too. Below 1024px
   this still stacks to a single column (copy, then photo) via the shared
   base rule. */
@media (min-width:1024px){
  /* both explicitly on row 1 — without this, sparse auto-placement puts
     gap-copy on its own row 2 once pic-media's column:2 has advanced the
     placement cursor past column 1 on row 1. */
  #problem-gaps .pic-media{ grid-column:2; grid-row:1; }
  #problem-gaps .gap-copy{ grid-column:1; grid-row:1; }
}
#problem-gaps .gap-copy{ max-width:var(--measure-lead); }


/* ── THE COUNTER-UXV CHALLENGE, PINNED: scroll-scrubbed two-scene reveal ──
   Direct request: "convert [this] to look like viper's 2nd fold, the text
   reveal" — mirrors #gap's own base+pinned split (viper.css) almost
   exactly; see that file's own comments for the fuller reasoning behind
   each piece, this is just the same shape applied here.

   Base rule (always, and the <1024px/reduced-motion/no-GSAP fallback):
   centred, stacked, full-bleed-width-but-measure-capped, no eyebrow, set
   in heading sizes rather than body copy — reads as one deliberate
   statement, same as #gap. problem-scroll.js only ever adds behaviour on
   top via the [data-motion="pinned"] rules below, never removes this. */
#problem .problem-copy{ display:flex; flex-direction:column; gap:var(--space-8);
  max-width:var(--measure-body); margin-inline:auto; text-align:center; }

/* Pinned only (>=1024px, GSAP, motion allowed — problem-scroll.js).
   background-image direct on the SECTION itself, not a separate element —
   deliberately, not by default: an absolutely-positioned sibling backdrop
   with its own opacity fade was tried on VIPER's own #gap-result fold for
   this same "photo behind a pinned scene" job and root-caused two separate
   real bugs before landing on this exact section-background approach (see
   viper.css's own #gap-result[data-motion="pinned"] .gap-result-bg
   comments for the full story) — #problem doesn't need an independently
   fading backdrop (nothing here asks the photo itself to fade in/out
   separately from the scenes), so there's no reason to take on that
   complexity here; the section's own background-image is the simpler,
   already-proven-reliable version of the same trick, same as #gap uses.

   background-attachment:fixed: #problem itself is never visually fixed to
   the viewport (problem-scroll.js pins .problem-copy, not the section) —
   it's an ordinary tall block spanning the whole pin's scroll distance, so
   a plain scroll-attached background would size/centre against that whole
   tall box instead of whatever slice is actually on screen.
   background-size:cover (not contain, unlike #gap-result's close-up
   photo): this is a full-bleed atmospheric backdrop meant to fill the
   frame edge-to-edge behind centred text, not a specific photo whose own
   proportions need preserving. */
#problem[data-motion="pinned"]{
  padding-block:var(--section-y-sm);
  background-image:linear-gradient(rgba(6,6,6,.6),rgba(6,6,6,.6)),
    url("../img/nexus-problem.webp");
  background-size:cover; background-position:center; background-repeat:no-repeat;
  background-attachment:fixed;
}
#problem[data-motion="pinned"] .problem-copy{
  position:relative; min-height:92svh; max-width:var(--measure-lead); justify-content:center;
}
#problem[data-motion="pinned"] .problem-scene{
  position:absolute; left:0; right:0; top:50%; transform:translateY(-50%);
  opacity:0; pointer-events:none; transition:none;
}
#problem[data-motion="pinned"] div.problem-scene{ display:flex; flex-direction:column; gap:var(--space-6); }
/* Whole-phrase fade, not letter-by-letter — the one piece of gap-scroll.js
   this deliberately doesn't carry over; that split was a separate, later
   VIPER-specific refinement (see problem-scroll.js's own header), and this
   plainer version is the same mechanic problem-brighten.js already uses
   on Home's own Problem section, just scrubbed by the pin instead of by
   the paragraph's own scroll position. */
#problem[data-motion="pinned"] .brighten{ opacity:.3; transition:none; }
/* Word-by-word reveal (problem-scroll.js's splitWords) — same split-w
   wrapper viper.css's letter-split uses, one level up: inline-block so a
   word can't be torn apart by a line-break (matters more here than it did
   there, since gap-scroll.js's .split-c inline letters relied on .split-w
   for exactly this — this file just never needed that class until now).
   transition:none for the same reason every other scrubbed value in this
   block gets it: nothing here should ease in after scroll stops. */
#problem .split-w{ display:inline-block; transition:none; }

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

/* Below 1024: the same fold as desktop, sized for a phone. The stage is a
   full-screen panel with the same background image + scrim, pulled out to
   the viewport edges past .bleed's padding. With JS (data-motion="scrub",
   problem-scroll.js) .problem-copy becomes a tall track and the stage is
   position:sticky inside it: the panel holds still while the track scrolls,
   and that scroll reveals the heading word by word; once it is fully lit,
   the heading fades out and the paragraph fades in once in the same spot. Without JS or with
   reduced motion it is the same panel with both texts stacked at full
   strength. Sticky, not a ScrollTrigger pin (a pin of this full-bleed panel
   came out narrower than the screen). background-attachment is NOT fixed:
   iOS ignores fixed and scales the image to the whole page. */
@media (max-width:1023px){
  #problem{ padding-top:0; }
  #problem .problem-copy{ max-width:none; margin-inline:calc(var(--edge) * -1); gap:0; }
  #problem .problem-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,.6),rgba(6,6,6,.6)),
      url("../img/nexus-problem.webp");
    background-size:cover; background-position:center; background-repeat:no-repeat;
  }
  #problem[data-motion="scrub"] .problem-copy{ height:calc(100svh + var(--scrub-distance, 195svh)); }
  #problem[data-motion="scrub"] .problem-stage{ position:sticky; top:0; height:100svh; display:block; }
  /* heading words are scrubbed by JS; the heading/paragraph swap is a
     one-off fade (classes toggled at the hand-off), not tied to scroll */
  #problem[data-motion="scrub"] .problem-scene{
    position:absolute; left:var(--edge); right:var(--edge); top:50%; transform:translateY(-50%);
    transition:opacity var(--dur-enter) var(--ease-out);
  }
  #problem[data-motion="scrub"] .problem-scene + .problem-scene,
  #problem[data-motion="scrub"] .problem-scene.is-out{ opacity:0; pointer-events:none; }
  #problem[data-motion="scrub"] .problem-scene.is-in{ opacity:1; pointer-events:auto; }
}



/* ── DECISION LAYER — INTRO DIVIDER ───────────────────────────────────────
   .split (base.css) is shared by every section-intro on the site — eyebrow
   plus heading on the left, lead paragraph on the right — so the line goes
   on this one instance (#define .split) rather than on .split itself,
   which would draw it under every other section's intro too.

   The two columns don't share a baseline (the lead paragraph starts lower
   than the heading and the two run to different lengths), so a line drawn
   right under the text would either cut through the taller column or float
   above the shorter one. Grid rows size to their tallest cell, so a
   border-bottom on the grid itself — not on either column — always lands
   at the true bottom of whichever column runs longer, which keeps it
   level and reads as one deliberate rule under both boxes rather than
   two different lines. padding-bottom holds it off the text itself,
   then .decision-panels' own margin-top (below) picks up the rest of
   the gap down to the cards. */
/* #define no longer HAS a .split — the eyebrow and lead moved into the left
   column of .decision-panels on 2026-11-12. The rule that used to draw a
   hairline under that header went with it: the header is now beside the
   visual, not above it, so a rule under it would cut the fold in half. */

/* ── DECISION LAYER — bordered icon-card grid + static visual ────────────
   Replaces the click-to-expand accordion this section used to run
   (.decision-grid/.decision-point*, components.css — still there,
   untouched, for any future section that wants that pattern; this one
   just no longer opts in) with a static two-up layout built from a Figma
   mockup: a bordered 2x2 grid of icon+copy cards on the left, one
   full-height image on the right. Nothing here swaps or expands — every
   card is visible at once, so there's no JS to wire up.

   Went 5fr:7fr (the original .decision-grid split, ~44:56, matching the
   Figma mockup) → 6fr:7fr → 1fr:1fr → 8fr:5fr, each step per direct
   request to give "Airspace Context" (the longest of the four titles)
   enough room to stop wrapping to two lines — 8fr:5fr was the point
   that held it to one line all the way down to the 1024px floor.
   Brought back to 5fr:7fr here, also per direct request, trading that
   back the other way: the image reads bigger again, but measured live
   at this ratio, Airspace Context is back to wrapping to two lines from
   1024px up to somewhere around 1500–1600px wide, and is fine above
   that (the other three titles held one line at every width checked,
   1024 to 2400). If that two-line stretch reads as broken rather than
   intentional at typical laptop/desktop widths, the fix is the same
   trade as before — more width to the cards, less to the image — just
   dial it in less far than 8fr:5fr if the goal is only to clear the
   common range rather than the 1024px floor. */
.decision-panels{
  display:grid; grid-template-columns:1fr; gap:var(--space-7);
  margin-top:var(--space-7);
}
@media (min-width:1024px){
  .decision-panels{
    grid-template-columns:minmax(0,5fr) minmax(0,7fr);
    /* No gutter between the cards and the picture, per direct request: the
       grid's outer hairline runs straight into the image so the two read as
       one seamless block rather than two boxes with a gap. */
    column-gap:0; align-items:stretch;
  }
}

/* --lead: the same two-up grid holding COPY on the left instead of the card
   table (2026-11-12, by request). Two things have to come back that the card
   version deliberately removed — a real gutter, because the seamless
   hairline-into-image join only made sense when the left column was a
   bordered grid, and top alignment, because the visual is 1145x1374 and
   centring four lines of lead against something that tall leaves them
   floating in the middle of the fold with no edge to read from. */
.decision-panels--lead{ align-items:start; }
@media (min-width:1024px){
  .decision-panels--lead{ column-gap:var(--gutter); align-items:start; }
}
.decision-intro .eyebrow{ margin-bottom:var(--space-5); }
.decision-intro .t-lead{ margin:0; }

/* The 2x2 "table" look is one outer hairline box plus two interior
   dividers, not four borders glued together at the corners: every card
   draws its own trailing edge (border-bottom always, border-right once
   there are two columns to divide), and the true last row/column just
   has its trailing edge switched back off — cheaper than a border-image
   and it can't show doubled or misaligned lines at the seams the way
   stacking four independent borders can. */
.decision-cards{
  display:grid; grid-template-columns:1fr;
  border:var(--hair) solid var(--color-border-subtle);
}
.decision-card{
  padding:var(--space-6);
  border-bottom:var(--hair) solid var(--color-border-subtle);
}
.decision-card:last-child{ border-bottom:0; }
/* sm (640px), the system's own smallest of its three breakpoints — see
   tokens.css's "three, and only three" note — rather than a one-off
   figure invented just for this grid. */
@media (min-width:640px){
  .decision-cards{ grid-template-columns:1fr 1fr; }
  .decision-card:nth-child(odd){ border-right:var(--hair) solid var(--color-border-subtle); }
  .decision-card:nth-child(3){ border-bottom:0; }
}

/* --numbered: the same table grid standing on its own in #counter-uas rather
   than filling one column of a two-up, so it gets the full bleed and goes
   4-across at 1024 instead of staying 2x2. At full width a 2x2 put the body
   copy on ~700px lines, well past --measure-body, with no column beside it to
   do the capping. The trailing-edge logic has to be re-stated for 4 columns:
   the "last row" is now every card (there is only one row), and the "last
   column" is the fourth, not the even ones. */
@media (min-width:1024px){
  .decision-cards--numbered{ grid-template-columns:repeat(4,minmax(0,1fr)); }
  .decision-cards--numbered .decision-card{ border-bottom:0;
    border-right:var(--hair) solid var(--color-border-subtle); }
  .decision-cards--numbered .decision-card:nth-child(4){ border-right:0; }
}
/* the numeral sits where the icon used to, and takes the icon's own bottom
   gap so the title lands on the same line it always did */
.decision-card-n{ margin:0 0 var(--space-5); color:var(--color-text-muted); }
/* Fixed height, width auto: the four source icons don't share one
   aspect ratio (a wide terrain plate next to a near-square crosshair),
   so a fixed width would leave them reading as different sizes next to
   each other. Locking height instead keeps all four the same visual
   weight in the grid regardless of their own proportions. */
.decision-card-icon{
  display:block; height:clamp(52px,5vw,76px); width:auto;
  margin-bottom:var(--space-5);
}
.decision-card-title{ margin-bottom:var(--space-3); }

/* Deliberately outside the two-pole+accent system every other surface
   on the site holds to (see tokens.css) — this one panel is meant to
   read as a dark viewport onto the diagram, not as UI chrome, so it
   gets the system's black rather than a new colour. align-items/
   justify-content centre the image if its own ratio ever leaves slack
   at either edge of the stretched panel. */
.decision-visual{
  background:var(--black);
  display:flex; align-items:center; justify-content:center;
  padding:0; /* the artwork's own pure-black edge runs flush to the panel — any padding showed the system black (#0C0E0E) as a grey frame around it */
}
.decision-visual img{ display:block; width:100%; height:auto; }

/* ── DECISION VISUAL — live scene ──────────────────────────────────────────
   Replaces the old still + SVG line mesh + glow dots + intercept loop
   (2026-11-13). .decision-visual-wobble keeps its name; it just holds the
   poster (the final frame) and the canvas that draws the scene over it.
   The canvas is absolutely positioned over the poster inside a box with the
   scene's own 1680x1293 ratio, and fades in once it has drawn its first frame
   (nexus-scene.js adds .is-live to .decision-visual). Until then, with
   scripts off, or on any failure, the poster simply stays. */
.decision-visual-wobble{
  position:relative; width:100%; line-height:0; aspect-ratio:1680 / 1293;
}
.nx-poster{ position:absolute; inset:0; width:100%; height:100%; display:block; transition:opacity .5s ease; }
.nx-gl{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.decision-visual.is-live .nx-poster{ opacity:0; }
.nx-scene{
  position:absolute; inset:0; width:100%; height:100%; display:block;
  opacity:0; transition:opacity .5s ease;
}
.decision-visual.is-live .nx-scene{ opacity:1; }
@media (prefers-reduced-motion:reduce){ .nx-scene,.nx-poster{ transition:none; } }

/* ── LOOP-BACK NOTE ───────────────────────────────────────────────────────
   The flow strip (.flow-strip/.flow-step, shared with VIPER) closes its
   loop here: Respond feeds back into Sense, so a small centred caption
   states that explicitly rather than leaving the strip looking linear.
   Now a .decision-grid child rather than a sibling after it (see the
   markup comment in nexus.html), so grid-column:1/-1 is what keeps it
   spanning under both columns instead of landing in column one alone —
   below 1024px the grid is single-column anyway and this is a no-op. */
/* A hairline above the note, same device #define's own intro divider
   (.split, above) and VIPER's .mission-set use — draws a deliberate line
   under the row above rather than leaving the gap to it unmarked. Border
   sits at the very top of the margin box; margin-top is the space above
   the line, padding-top the space below it before the note's own text —
   matched to the same token so the line sits dead centre in its gap
   rather than closer to one side. */
.loop-note{ grid-column:1/-1; text-align:center; margin-top:var(--space-7);
  padding-top:var(--space-7); border-top:var(--hair) solid var(--color-border-subtle); }

/* Pinned mode only: the grid's own row-gap (components.css, tightened to
   --space-5 specifically for this pinned second row) already separates
   this from the points+media row above it, so the base rule's margin-top
   would double that spacing on top of it — harmless on the static/mobile
   fallback below 1024px (plenty of scroll room there, and the wider gap
   reads fine in a normal scroll), but at pin width every extra px pushes
   the line closer to the bottom edge of the pin's one-viewport-tall
   visible area. Margin zeroed and padding-top matched to the same
   --space-5 the row-gap uses — same reasoning as the base rule above,
   just at the pinned scale — rather than in the base rule so the
   fallback keeps its own, more generous spacing; the border stays in the
   base rule since both states want the line. */
[data-motion="pinned"] .loop-note{ margin-top:0; padding-top:var(--space-5); }


/* Operational Loop went through two other shapes before settling back on
   Define's own points-against-pinned-media pattern (.decision-grid, shared
   in components.css) — same layout, same pin, same [data-video] swap via
   decision-scroll.js, just this section's own four points/clips. Nothing
   loop-specific left to style here beyond the loop-back note above. */


/* ── OPERATING MODEL: static text-only card grid ──────────────────────────
   Replaces the click-to-switch picture tabs this section used to run
   (.pic-tabs/.pic-panels + tabs.js/tab-panels.js — still there, untouched,
   for viper.html's own picture-tabs section and the Operate Stage section
   further up this same page, which both still opt in) with a static,
   always-visible set of five cards. Per direct request: white section
   like the home page's Mission Layer, same card shape, no icon or image —
   so every card is on screen at once and there's no JS to wire up here.

   Reuses .pillars + .card-notch (components.css) rather than a new card —
   the same pair the home page's Mission Layer (#mission, home.css) uses
   for its own six-card set. This section overrides three things, all
   scoped to this page only:

   1. Column count — was bumped to a 5-across single row at 1600px (the
      old "widen instead of wrap" fix). Direct follow-up ("3x2 grid,
      smaller and compact") drops that: 3 columns from the same 1024px
      desktop breakpoint the rest of the site uses, so five cards read
      as a 3-over-2 grid (a widowed pair, not a widowed single) at every
      desktop width, not just 1600px+.
   2. Card size — min-height and inner padding both pulled in from the
      shared defaults (components.css: 200px baseline, --space-6 padding)
      for the same "smaller and compact" request, now that 3-up cards
      have more width to spare than the old 5-up row did.
   3. Heading/subtext gap — margin-top pulled in from the shared
      --space-5 default (components.css) to --space-3, matching the
      home page's own Mission Layer cards (#mission, home.css) exactly,
      per direct follow-up ("reduce gap between heading and subtext
      inside the boxes like home page mission layer section"). */
#edge-authority .card-notch{ min-height:160px; }
#edge-authority .card-notch .inner{ padding:var(--space-5); }
#edge-authority .card-notch p{ margin-top:var(--space-3); }
@media (min-width:1024px){
  #edge-authority .pillars{ grid-template-columns:repeat(3,1fr); gap:var(--space-5); }
}


/* ── HETEROGENEOUS INTRO LEAD ─────────────────────────────────────────────
   .arch-support (components.css) assumes it follows the stack — this
   section's supporting line comes before it instead, so it gets its own
   centred, auto-margin rule rather than repurposing arch-support's spacing
   for a placement it wasn't built for. */
.hetero-lead{ max-width:var(--measure-lead); margin:0 auto var(--space-8); }


/* ── CAPABILITY COUNT OVERRIDE ────────────────────────────────────────────
   .pillars (components.css) defaults to 3-up at 1600px, tuned for VIPER's
   six cards. NEXUS has four, and three columns would widow a single card
   into its own row — capped at two here instead, scoped to this page only. */
@media (min-width:1600px){
  #capabilities .pillars{ grid-template-columns:repeat(2,1fr); }
}

/* ── HERO POSTER ─────────────────────────────────────────────────────────── */
/* The black floor and sizing live in motion.css; only the image is per page.
   Without this the NEXUS hero has no fallback behind the video. */
.hero .media{ background-image:url("../img/nexus-hero-poster.jpg"); }

/* ── HERO INTRO: lead/CTA pushed later ───────────────────────────────────
   motion.css's shared .intro-d3/.intro-d4 delays (480ms/600ms) were tuned
   for a single-line heading. This hero's own heading is now two lines
   (.intro-line:nth-of-type(2), motion.css), whose rise doesn't finish
   until ~520ms+900ms=1420ms, so the old delays landed the lead and CTA
   arriving before the second line had even started. Pushed back scoped
   to this page only, rather than in the shared file — Applications still
   has a single-line heading and wants the original timing. */
#top .hero-side .intro-d3{ animation-delay:700ms; }
#top .hero-side .intro-d4{ animation-delay:850ms; }

/* ── #mission-os "Runs on Driif Mission OS": numbered hairline columns ───────
   The three pillars reuse .replat-cap-list / .replat-cap from components.css (the home page's capability
   columns); only the follow-on link needs styling here, since .card-link was scoped to the notched cards. */
.nx-os-list{ margin-top:var(--space-7); }
.nx-os-list .card-link{ display:inline-block; margin-top:var(--space-5); color:var(--color-accent); text-decoration:none; }
.nx-os-list .card-link:hover, .nx-os-list .card-link:focus-visible{ text-decoration:underline; text-underline-offset:4px; }

