/* ==========================================================================
   SYS.00 → SYS.08, in document order:
   hero · partner strip · stakes + incident panel · modules deck · pipeline ·
   why + telemetry · case-file reel · signal quotes · operators · transmissions.
   ========================================================================== */

/* ---------- hero ---------- */
.hero{ min-height:100svh; display:flex; flex-direction:column; justify-content:center; padding-top:14vh; padding-bottom:8vh; }
.hero .eyebrow{ display:inline-flex; align-items:center; gap:12px; font-size:var(--fs-sm); letter-spacing:.26em; text-transform:uppercase; color:var(--muted); border:1px solid var(--line); padding:10px 16px; width:fit-content; max-width:100%; }
  .hero .eyebrow .dot{ flex:0 0 auto; }
  @media (max-width:560px){ .hero .eyebrow{ letter-spacing:.14em; padding:9px 12px; gap:9px; } }
.hero .eyebrow .dot{ width:7px; height:7px; background:var(--accent); box-shadow:0 0 0 0 color-mix(in oklab, var(--accent) 55%, transparent); animation:pulse 2.4s var(--ease-out) infinite; }
@keyframes pulse{ 0%{box-shadow:0 0 0 0 color-mix(in oklab, var(--accent) 50%, transparent)} 70%{box-shadow:0 0 0 10px transparent} 100%{box-shadow:0 0 0 0 transparent} }
.hero h1{ margin-top:4.5vh; font-family:var(--headline); font-weight:550; font-size:clamp(2rem,11.2cqi,5.6rem); line-height:1.02; letter-spacing:0; max-width:none; }
@supports not (font-size:1cqi){ .hero h1{ font-size:clamp(2rem,6.2vw,5.6rem); max-width:12ch; } }
.hero h1 .line{ display:block; overflow:hidden; padding:.08em 0; }
.hero h1 .ch{ display:inline-block; will-change:transform; }
.hero h1 .outline{ color:transparent; -webkit-text-stroke:1.15px var(--accent); }
/* Statement on the left, terminal on the right. The 1.06/0.94 split gives the
   headline the wider half; both tracks are minmax(0,…) so a long word inside
   either one cannot push the grid wider than the page. */
/* align-items:stretch, not center: the row is as tall as the text column, so
   the terminal takes that full height instead of floating at 51% of it with
   114px of dead space beneath and no edge aligned to anything. */
.hero-grid{ display:grid; grid-template-columns:minmax(0,1.06fr) minmax(0,.94fr); gap:clamp(30px,3.4vw,60px); align-items:stretch; }
/* The text column keeps its own vertical centring inside the stretched row. */
.hero-grid > .hero-col{ display:flex; flex-direction:column; justify-content:center; min-width:0; }
.hero-col{ min-width:0; container-type:inline-size; }
.hero .footrow{ margin-top:5vh; display:flex; flex-wrap:wrap; gap:30px 60px; align-items:flex-end; justify-content:flex-end; }
/* The hero used to end on a scroll cue and nothing else — no link, no button,
   and no statement of who this is for. Both now sit directly under the lede,
   which is where a visitor decides whether to keep going. */
/* NOT sized in ch, deliberately -- this is the one cap on the page that is a
   LAYOUT bound rather than a reading measure, and ch is a text unit: it is the
   advance width of "0" IN THE ELEMENT'S OWN FONT. When the body face moved
   from IBM Plex Mono to Source Sans 3, 1ch went from 9.60px to 7.95px and
   every ch-based cap on the site silently narrowed by 17.2%. For the
   paragraphs that is harmless -- correct, even, since ch means "about N
   characters per line" and the measured count went UP, from 46 to 58 on the
   lede. But this box also contains .hero-cta, whose two buttons are set in
   MONO and did not shrink with it, so at 445px they no longer fitted side by
   side and the second button dropped onto its own line.
   34rem reproduces the previous rendered width (544px vs 537.6px) and cannot
   move again if the body face is ever changed. The lede keeps its own 52ch
   reading measure, so nothing here constrains the text. */
.hero-say{ display:grid; gap:clamp(16px,2.2vh,26px); max-width:34rem; }
.hero-cta{ display:flex; flex-wrap:wrap; gap:12px; align-items:center; }
.hero .lede{ max-width:52ch; font-size:clamp(14.5px,1.05vw,16.5px); line-height:1.85; color:var(--muted); }
.btn-primary,.btn-ghost{
  display:inline-flex; align-items:center; justify-content:center; gap:12px;
  min-height:52px; padding:0 26px; font-size:var(--fs-sm); letter-spacing:.16em;
  text-transform:uppercase; white-space:nowrap;
  transition:transform .4s var(--ease-out), filter .4s var(--ease-out), background .4s var(--ease-out), color .4s var(--ease-out), border-color .4s var(--ease-out);
}
.btn-primary{ background:var(--accent); color:var(--on-accent); border:1px solid var(--accent); }
.btn-ghost{ background:none; color:var(--ink); border:1px solid var(--line); }
@media (hover:hover){
  .btn-primary:hover{ transform:translateY(-2px); filter:brightness(1.08); }
  .btn-ghost:hover{ border-color:var(--accent); color:var(--accent); }
}
@media (max-width:520px){ .btn-primary,.btn-ghost{ flex:1 1 100%; } }

.hud{ margin-top:6vh; display:flex; flex-wrap:wrap; gap:2px; border:1px solid var(--line); background:var(--line); }
.hud .hud-item{ flex:1 1 180px; background:var(--bg); padding:16px 20px; display:flex; flex-direction:column; gap:7px; }
.hud .hk{ font-size:var(--fs-2xs); letter-spacing:.24em; text-transform:uppercase; color:var(--faint); }
.hud .hv{ font-size:var(--fs-md); letter-spacing:.1em; text-transform:uppercase; color:var(--ink); display:flex; align-items:center; gap:9px; }
.hud .hv b{ color:var(--accent); font-weight:500; }
.hud .hv .dot{ width:7px; height:7px; border-radius:50%; background:var(--accent); animation:pulse 2.4s var(--ease-out) infinite; }

/* ---------- hero terminal ---------- */
.hero-term{ min-width:0; }
/* Fills the height of its grid track rather than floating in the middle of
   it. Before this the panel was centred in a 464px column at a fixed 235px,
   which left 114px of dead space under it and aligned its edges to nothing —
   it read as a card dropped on the hero rather than the hero's second column. */
/* The panel is taken OUT of flow inside its track. With the transcript now
   persisting rather than erasing, a panel in normal flow grew with its own
   content and — because the grid row is as tall as its tallest item — pushed
   the whole hero down, dropping the headline and shoving the HUD off screen.
   Absolute inside a relative track, it fills exactly what it is given and
   contributes nothing back. min-height carries the stacked layout, where
   there is no track to fill. */
.hero-term{ position:relative; min-width:0; min-height:var(--term-h); }
.hterm{
  position:absolute; inset:0; min-width:0;
  display:flex; flex-direction:column; overflow:hidden;
  border:1px solid var(--line); background:var(--panel);
  box-shadow:0 30px 70px -46px rgba(0,0,0,.7);
  font-family:var(--mono);
}
.hterm-bar{ display:flex; align-items:center; gap:13px; flex:0 0 auto; padding:11px 15px; border-bottom:1px solid var(--line); background:var(--elev); }
.hterm-dots{ display:flex; gap:6px; flex:0 0 auto; }
.hterm-dots i{ width:9px; height:9px; border-radius:50%; background:var(--field-line); }
.hterm-dots i:first-child{ background:var(--amber); }
.hterm-dots i:last-child{ background:var(--accent); }
.hterm-title{ min-width:0; font-size:var(--fs-2xs); letter-spacing:.17em; text-transform:uppercase; color:var(--faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Fixed height at every breakpoint. The transcript scrolls inside it as it
   types, so the hero never grows and the buttons below never move. */
/* The transcript starts at the TOP and types downward, the way a prompt
   actually behaves, then scrolls once it outgrows the box — hero-term.js
   pins scrollTop to the bottom after every line, so the newest line stays in
   view exactly as a real terminal does.
   It was briefly bottom-anchored, back when each screen was erased before the
   next and the panel could only ever hold one screen's worth. Now that the
   history persists and fills the box within about half a minute, anchoring to
   the bottom only inverted the reading order for no gain. */
.hterm-body{
  /* basis 0 and min-height 0: the body takes the space left over after the
     bar and the footer, and never sizes itself to the transcript inside it. */
  flex:1 1 0; min-height:0; overflow:hidden;
  padding:15px 17px; font-size:var(--fs-md); line-height:1.68;
  display:flex; flex-direction:column; justify-content:flex-start;
}
.hterm .tl{ display:block; white-space:pre-wrap; overflow-wrap:anywhere; }
.hterm .tl-cmd{ color:var(--ink); }
.hterm .tl-cmd::before{ content:"$ "; color:var(--accent); }
.hterm .tl-out{ color:var(--muted); padding-left:1.35em; }
.hterm .tl-ok{ color:var(--accent); padding-left:1.35em; }
/* A task mid-spin: dim, because it has not resolved yet. It becomes .tl-ok
   the moment it does, so the colour change IS the resolution. */
.hterm .tl-task{ color:var(--faint); padding-left:1.35em; }
.hterm .tl-bar{ color:var(--muted); padding-left:1.35em; letter-spacing:-.04em; }
.hterm .tl-bar-done{ color:var(--accent); }
.hterm .tl-gap{ height:.55em; }
/* Screens that have already run. Dimmed rather than deleted, so the panel
   reads as a session with history above the live line. */
.hterm .tl-past{ opacity:.42; transition:opacity .5s var(--ease-out); }
.hterm-caret{ display:inline-block; width:.55em; height:1.02em; margin-left:.1em; vertical-align:-.16em; background:var(--accent); animation:caret 1.05s steps(1) infinite; }
@keyframes caret{ 0%,50%{opacity:1} 51%,100%{opacity:0} }
@media (prefers-reduced-motion:reduce){ .hterm-caret{ animation:none; } }
/* The floor, not the height. On the two-column layout the panel stretches to
   its grid track and this rarely binds; stacked, where the terminal sits above
   nothing and there is no track to fill, it is what stops the box resizing
   between screens and nudging the page on every cycle. */
.hero{ --term-h:196px; }

/* ---------- terminal footer ---------- */
.hterm-foot{
  flex:0 0 auto; display:flex; align-items:center; justify-content:space-between;
  gap:14px; padding:10px 15px; border-top:1px solid var(--line); background:var(--elev);
  font-size:var(--fs-2xs); letter-spacing:.16em; text-transform:uppercase; color:var(--faint);
}
.hf-live{ display:inline-flex; align-items:center; gap:8px; color:var(--accent); flex:0 0 auto; }
.hf-live i{ width:7px; height:7px; border-radius:50%; background:currentColor; animation:pulse 2.4s var(--ease-out) infinite; }
.hf-note{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
          font-size:var(--fs-xs); letter-spacing:.01em; text-transform:none; }
@media (prefers-reduced-motion:reduce){ .hf-live i{ animation:none; } }

/* Below this the two columns are each too narrow to hold their own content —
   the headline starts breaking mid-word and the transcript wraps on every
   line. Stack instead, terminal under the buttons, where it reads as a
   footnote to the pitch rather than competing with it.

   The threshold was 1000px, chosen by eye, and it was too low. MEASURED: the
   hero's button row is 539px wide and the text column runs about
   0.46 x viewport, so the column drops under the buttons at roughly 1176px —
   which left a band from 1024 to 1176 where the hero was still two columns
   but "See what we do" had been pushed onto its own line.

   The cut is 1240 rather than 1176 so the narrowest two-column layout keeps
   real slack: at 1241px the column is ~32px wider than the buttons need,
   where 1180 left only 2px and any label edit would have reopened the band.
   Re-measure if the buttons' labels, padding or tracking ever change. */
@media (max-width:1240px){
  .hero-grid{ grid-template-columns:minmax(0,1fr); gap:clamp(26px,4.5vh,44px); align-items:start; }
  /* --term-h sizes the WHOLE panel, and the panel gained a title bar (~47px)
     and a footer (~40px) that were never added to this figure. At 168px that
     left 64px of inner body — three lines, where the tallest screen is a
     command plus four task rows. 236px shows six, so one command and its
     entire output are visible with a line of scrollback above.
     Stacked only: on the two-column layout the panel stretches to its grid
     track and this is just a floor. */
  .hero{ --term-h:236px; }
  .hterm-body{ font-size:var(--fs-sm); line-height:1.72; }
  .hero h1{ font-size:clamp(2rem,7.4vw,4.6rem); }
  @supports not (font-size:1cqi){ .hero h1{ max-width:14ch; } }
}
/* A phone in landscape has almost no height to spare; the terminal is
   atmosphere and the buttons are not, so it stands down rather than pushing
   them under the fold. */
@media (max-height:640px) and (max-width:1000px){ .hero-term{ display:none; } }
/* Phone held sideways: ~375px of height cannot hold a display headline, a
   paragraph and two buttons at full size. Nothing is hidden - the type and the
   rhythm tighten instead, which brings the primary button roughly 120px up the
   page. */
@media (max-height:520px) and (min-width:600px){
  .hero{ padding-top:6vh; padding-bottom:3vh; }
  .hero h1{ margin-top:1.4vh; font-size:clamp(1.55rem,4.3vw,2.3rem); }
  .hero .eyebrow{ padding:6px 12px; letter-spacing:.16em; }
  .hero-say{ gap:10px; max-width:none; }
  /* The 52ch / 44ch caps are a reading-measure choice for when height is
     plentiful. Here the opposite holds: 731px of column sits mostly empty
     while the sentence wraps to 5 lines and pushes the CTA off-screen. Letting
     both run to 78ch removes the wrapped lines without shrinking type or
     cutting a word. */
  .hero .lede{ line-height:1.5; max-width:78ch; }
  .hero-cta{ margin-top:2px; }
  .hero .footrow{ margin-top:1.4vh; }
}

/* Short viewports: the call to action matters more than the atmosphere, so the
   HUD steps aside rather than pushing the buttons below the fold. It is
   aria-hidden decoration, and the qualifier line now states the coverage it
   was carrying. */
@media (max-height:700px){
  .hero{ padding-top:10vh; padding-bottom:4vh; --term-h:156px; }
  .hero h1{ margin-top:3vh; }
  .hero .footrow{ margin-top:3.5vh; }
  .hero-say{ gap:14px; }
  .hud{ display:none; }
}
.hero .lede strong{ color:var(--ink); font-weight:500; }
.hero .scrollcue{ font-size:var(--fs-sm); letter-spacing:.28em; text-transform:uppercase; color:var(--muted); display:flex; align-items:center; gap:12px; }
.hero .scrollcue .bar{ width:54px; height:1px; background:var(--line); position:relative; overflow:hidden; }
.hero .scrollcue .bar::after{ content:""; position:absolute; inset:0; background:var(--accent); transform:translateX(-100%); animation:sweep 2.4s var(--ease-out) infinite; }
@keyframes sweep{ 0%{transform:translateX(-100%)} 60%,100%{transform:translateX(100%)} }
/* ---------- strip ---------- */
.strip{ border-top:1px solid var(--line); border-bottom:1px solid var(--line); overflow:hidden; padding:20px 0; display:flex; align-items:center; gap:30px; }
.strip .lbl{ flex:0 0 auto; padding-left:5vw; font-size:var(--fs-xs); letter-spacing:.26em; text-transform:uppercase; color:var(--amber); }
.strip .mq{ overflow:hidden; flex:1; min-width:0; -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent); mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent); }

/* logo-strip.js adds .mq-live once it takes over, swapping the CSS marquee for
   a real scroll container so the strip can be dragged, swiped and shift-wheeled.
   Without JS the animation below still runs, so nothing is lost. */
.strip.mq-live .mq{ overflow-x:auto; overflow-y:hidden; overscroll-behavior-x:contain; scrollbar-width:none; -ms-overflow-style:none; cursor:grab; }
.strip.mq-live .mq::-webkit-scrollbar{ display:none; }
.strip.mq-live .mq.dragging{ cursor:grabbing; }
.strip.mq-live .mq-track{ animation:none; }
.strip .mq-track{ display:flex; gap:0; width:max-content; animation:mq 38s linear infinite; will-change:transform; }
.strip .mq-track .logo{ display:inline-flex; align-items:center; padding:0 2.4vw; }
/* --lo is the optical factor logo-strip.js computes per mark from its real
   ink bounds; the three aspect-ratio buckets this replaced could not tell a
   1:1 icon from a 3:1 wordmark that had been exported into a square canvas.

   --brand is the mark's own hue, set only on the six glyphs that paint in
   currentColor and would otherwise stay white however the filter is removed;
   everything else falls through to --ink and is unchanged. fill:currentColor
   goes on the <svg> and NEVER on `svg path` — on the element it is inherited
   only by children that set no fill of their own, which is exactly the reach
   wanted; on the path selector it would overwrite the gradients and flat
   brand fills the eight coloured marks carry and flatten the whole strip. */
.strip .mq-track .logo svg{
  /* The floor is 27, not 21. --lo scales this DOWN for wide marks — the
     widest is 0.46 — so a 21px base put Hikvision at 9.7px on anything under
     1100. Logos are not body text and do not need to shrink much; 27-30 is a
     1.1x range that keeps the smallest mark above 12px at every width. */
  height:calc(var(--lo,1) * clamp(27px,2.3vw,30px)); width:auto;
  color:var(--brand, var(--ink)); fill:currentColor;
  filter:grayscale(1) brightness(1.9) opacity(.55); transition:filter .5s var(--ease-out);
}
/* Brands with no official open artwork run as wordmarks. Same height, same
   dimmed-then-lit treatment, so the strip reads as one set rather than as
   logos with gaps in it. */
.strip .mq-track .logo-word{ font-family:var(--display); font-weight:700; font-size:clamp(12.5px,1.35vw,16px); letter-spacing:.015em; line-height:1; white-space:nowrap; color:var(--ink); opacity:.5; transition:opacity .5s var(--ease-out); }
.strip:hover .mq-track .logo-word{ opacity:.9; }
[data-theme="light"] .strip .mq-track .logo svg{
  color:var(--brand-l, var(--brand, var(--ink)));
  filter:grayscale(1) brightness(.65) opacity(.6);
}
.strip .mq-track .logo::after{ content:""; width:4px; height:4px; background:var(--accent); opacity:.6; margin-left:2.4vw; }
.strip:hover .mq-track{ animation-play-state:paused; }
.strip:hover .mq-track .logo svg{ filter:none; }
@keyframes mq{ to{ transform:translateX(-50%); } }
@media (max-width:760px){
  .strip{ flex-direction:column; align-items:stretch; gap:11px; padding:16px 0; }
  .strip .lbl{ padding-left:5vw; padding-right:5vw; }
  .strip .mq{ width:100%; flex:none; }
  .strip .mq-track .logo{ padding:0 5vw; }
  .strip .mq-track .logo::after{ margin-left:5vw; }
}

/* ---------- SYS.01 stakes ----------
   Replaces the old .manifesto block, which was two display paragraphs stacked
   in a full-width container: 1.28 screens of scroll for 47 words, with the
   right 60% of the viewport empty. The statement now shares a row with the
   incident panel that makes it concrete, and the close runs full width as the
   answer to it. */
.stakes{ padding-block:16vh 14vh; }
.stakes-grid{
  margin-top:clamp(48px,9vh,110px);
  display:grid; grid-template-columns:1.05fr .95fr;
  gap:clamp(34px,5vw,90px); align-items:center;
}
.stakes-say{ min-width:0; }
.stakes-lead{
  font-family:var(--display); font-weight:500; text-transform:none;
  font-size:clamp(1.5rem,3.1vw,2.8rem); line-height:1.16; letter-spacing:-.02em;
  max-width:18ch;
}
.stakes-sub{ margin-top:clamp(20px,3vh,32px); max-width:46ch; font-size:var(--fs-base); line-height:1.85; color:var(--muted); }
/* Mirrors .stakes-grid — panel left, statement right — so the section reads
   as a matched pair: the morning it breaks, then every other morning. */
.stakes-close-grid{
  margin-top:clamp(56px,10vh,120px);
  display:grid; grid-template-columns:.95fr 1.05fr;
  gap:clamp(34px,5vw,90px); align-items:center;
}
.stakes-close-grid .steady{ order:-1; }
.stakes-close{
  font-family:var(--display); font-weight:500; text-transform:none;
  font-size:clamp(1.5rem,3.1vw,2.8rem); line-height:1.16; letter-spacing:-.02em;
  max-width:20ch;
}
.stakes-close em, .mission em{ font-style:normal; color:var(--accent); }
.word{ display:inline-block; }

/* ---------- SYS.01 incident panel ----------
   Same construction as the .hterm hero panel so the page reads as one system:
   a titled bar over a body, hairline border, panel fill. It is a figure, not a
   terminal — no prompts, no caret — because it reports state rather than
   running commands. */
.incident{
  min-width:0; margin:0;
  border:1px solid var(--line); background:var(--panel);
  box-shadow:0 30px 70px -46px rgba(0,0,0,.7);
}
.incident-head{
  display:flex; align-items:baseline; gap:14px; flex-wrap:wrap;
  padding:14px clamp(16px,1.8vw,22px);
  border-bottom:1px solid var(--line); background:var(--elev);
}
.ih-tag{
  font-size:var(--fs-2xs); letter-spacing:.2em; text-transform:uppercase;
  color:var(--amber); border:1px solid currentColor; padding:2px 8px; flex:0 0 auto;
}
.ih-title{ font-size:var(--fs-xs); letter-spacing:.2em; text-transform:uppercase; color:var(--faint); }

.incident-log{ list-style:none; margin:0; padding:6px 0; }
.ev{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center;
  gap:clamp(10px,1.6vw,20px);
  padding:13px clamp(16px,1.8vw,22px);
  border-bottom:1px solid var(--line);
  /* Resting state is the animated-out one; .live is added by incident.js, and
     by the reduced-motion / no-GSAP fallback immediately. */
  opacity:0; transform:translateY(7px);
  transition:opacity .55s var(--ease-out), transform .55s var(--ease-out);
}
.ev:last-child{ border-bottom:none; }
.ev.live{ opacity:1; transform:none; }
.ev-t{ font-size:var(--fs-2xs); letter-spacing:.1em; color:var(--faint); font-variant-numeric:tabular-nums; }
.ev-n{ font-size:var(--fs-md); color:var(--ink); min-width:0; }
.ev-s{
  display:inline-flex; align-items:center; gap:8px; flex:0 0 auto;
  font-size:var(--fs-2xs); letter-spacing:.14em; text-transform:uppercase;
  white-space:nowrap;
}
.ev-s::before{ content:""; width:7px; height:7px; border-radius:50%; background:currentColor; flex:0 0 auto; }

/* Severity climbs down the list, which is the whole point of it. */
.ev[data-state="ok"]   .ev-s{ color:var(--accent); }
.ev[data-state="warn"] .ev-s{ color:var(--amber); }
.ev[data-state="down"] .ev-s{ color:var(--alert); }
.ev[data-state="halt"] .ev-s{ color:var(--alert); }
/* The last row is the human cost rather than another system, so it is the one
   that reads as a statement instead of a reading. */
.ev[data-state="halt"]{ background:color-mix(in oklab, var(--alert) 7%, transparent); }
.ev[data-state="halt"] .ev-n{ font-weight:600; }
.ev[data-state="halt"] .ev-s::before{ animation:pulse 2.4s var(--ease-out) infinite; box-shadow:0 0 0 0 color-mix(in oklab, var(--alert) 55%, transparent); }

.incident-foot{
  padding:14px clamp(16px,1.8vw,22px);
  border-top:1px solid var(--line); background:var(--elev);
  font-size:var(--fs-xs); letter-spacing:.01em; text-transform:none; color:var(--muted);
  opacity:0; transition:opacity .6s var(--ease-out);
}
.incident-foot.live{ opacity:1; }

/* ---------- SYS.01 steady-state panel ----------
   Same shell as .incident so the two read as one component in two states.
   Its own body, because the incident panel is a log of events and this is a
   set of live lanes: rows of label, trace, state. */
.steady{ min-width:0; margin:0; border:1px solid var(--line); background:var(--panel); box-shadow:0 30px 70px -46px rgba(0,0,0,.7); }
.ih-tag-ok{ color:var(--accent); }
.steady-lanes{ padding:6px 0; }
.lane{
  display:grid; grid-template-columns:70px 1fr auto; align-items:center;
  gap:clamp(10px,1.6vw,18px);
  padding:14px clamp(16px,1.8vw,22px);
  border-bottom:1px solid var(--line);
}
.lane:last-child{ border-bottom:none; }
.lane-n{ font-size:var(--fs-md); color:var(--ink); }
/* The trace is decoration over a text label, never the label itself: state is
   carried by .lane-s in words, so it survives greyscale and forced-colors. */
.lane-c{ display:block; width:100%; height:30px; }
.lane-s{
  display:inline-flex; align-items:center; gap:8px; flex:0 0 auto;
  font-size:var(--fs-2xs); letter-spacing:.14em; text-transform:uppercase;
  color:var(--accent); white-space:nowrap;
}
.lane-s::before{ content:""; width:7px; height:7px; border-radius:50%; background:currentColor; flex:0 0 auto; }
/* Unlike .incident-foot this does not wait to be revealed — there is no
   cascade here to wait for. */
.steady-foot{ opacity:1; }

@media (max-width:920px){
  .stakes-close-grid{ grid-template-columns:1fr; gap:clamp(30px,5vh,48px); align-items:start; }
  /* Stacked, the statement should lead and the panel answer it. */
  .stakes-close-grid .steady{ order:1; }
  .stakes-close{ max-width:22ch; }
}
@media (max-width:460px){
  .lane{ grid-template-columns:64px 1fr; row-gap:8px; }
  .lane-s{ grid-column:2; justify-self:start; }
  .lane-c{ height:26px; }
}

@media (max-width:920px){
  .stakes{ padding-block:12vh 11vh; }
  .stakes-grid{ grid-template-columns:1fr; gap:clamp(30px,5vh,48px); align-items:start; }
  .stakes-lead{ max-width:22ch; }
}
@media (max-width:460px){
  /* Three columns will not hold: the state label wraps and the row loses its
     alignment. Time and system on one line, state beneath. */
  .ev{ grid-template-columns:auto 1fr; row-gap:7px; }
  .ev-s{ grid-column:2; justify-self:start; }
}
@media (prefers-reduced-motion:reduce){
  .ev,.incident-foot{ transition:none; }
  .ev[data-state="halt"] .ev-s::before{ animation:none; }
}

/* ---------- SYS.02 modules (stacking cards) ---------- */
.mods{ padding-block:12vh 16vh; }
.mod{ position:sticky; top:calc(var(--stacktop) + var(--i,0)*var(--stackstep)); margin-top:9vh; min-height:66vh; display:grid; grid-template-columns:1.05fr 1fr; background:var(--panel); border:1px solid var(--line); will-change:transform; }
.mod:first-of-type{ margin-top:8vh; }
.mod .m-info{ padding:clamp(26px,3.2vw,54px); display:flex; flex-direction:column; justify-content:space-between; gap:30px; min-width:0; }
.mod .m-top .m-sys{ font-size:var(--fs-sm); letter-spacing:.22em; color:var(--amber); text-transform:uppercase; }
.mod .m-top h3{ margin-top:18px; font-family:var(--display); font-weight:700; text-transform:uppercase; font-size:clamp(1.5rem,2.9vw,2.7rem); line-height:1; letter-spacing:-.02em; }
.mod .m-top .m-sum{ margin-top:18px; font-size:var(--fs-base); line-height:1.8; color:var(--muted); max-width:46ch; }
.mod .m-bot{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px 30px; }
/* A keyword list, up to 76 characters of it -- "Cabling / Terminations /
   CCTV / Access control / Workstations / Server rooms". Uppercase and .12em
   tracking is the readout treatment, and it is wrong at this length: it
   removes the word shapes a reader skims by and pushes the line past the
   card. The middots already say "this is a list", so the case does not have
   to. Natural case, tracking near normal; it stays mono and stays --faint,
   so it still reads as metadata under the summary rather than as prose. */
.mod .m-tags{ font-size:var(--fs-xs); letter-spacing:.02em; text-transform:none; color:var(--faint); }
.mod .m-open{ display:inline-flex; align-items:center; justify-content:center; gap:10px; min-height:46px; background:none; border:1px solid var(--line); padding:13px 22px; font-size:var(--fs-sm); letter-spacing:.16em; text-transform:uppercase; color:var(--ink); cursor:pointer; transition:background .45s var(--ease-out), border-color .45s var(--ease-out), color .45s var(--ease-out); }
@media (hover:hover){ .mod .m-open:hover{ background:var(--accent); border-color:var(--accent); color:var(--on-accent); } }
@media (pointer:coarse){ .mod .m-open{ cursor:pointer; } }
.mod .m-idx{ position:absolute; right:clamp(14px,1.6vw,26px); bottom:6px; z-index:2; font-family:var(--display); font-weight:800; font-size:clamp(3.2rem,7vw,6.4rem); line-height:.78; letter-spacing:-.04em; color:transparent; -webkit-text-stroke:1px var(--line); pointer-events:none; }
.mod .m-media{ position:relative; overflow:hidden; min-height:300px; background:var(--media-bg); }
.mod .m-media img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; filter:grayscale(.5) brightness(.6) contrast(1.06); transition:filter .7s var(--ease-out), scale .7s var(--ease-out); scale:1.04; }
.mod .m-media .duo{ position:absolute; inset:0; background:linear-gradient(100deg, rgba(var(--duo-rgb),.85), rgba(var(--duo-rgb),.25) 55%, rgba(var(--duo-rgb),.05)); }
@media (hover:hover){ .mod:hover .m-media img{ filter:grayscale(.05) brightness(.8); scale:1; } }
[data-theme="light"] .mod .m-media img{ filter:grayscale(.25) brightness(.9); }
[data-theme="light"] .mod:hover .m-media img{ filter:grayscale(0) brightness(1); }
@media (max-width:860px){
  :root{ --stacktop:9vh; --stackstep:12px; }
  .mod{ grid-template-columns:1fr; min-height:0; }
  .mod .m-media{ order:-1; min-height:0; aspect-ratio:16/9; }
  .mod .m-idx{ font-size:3.4rem; bottom:auto; top:10px; }
}

/* ---------- SYS.03 pipeline terminal ---------- */
.term{ margin-top:8vh; background:var(--panel); border:1px solid var(--line); }
.term-head{ display:flex; align-items:center; justify-content:space-between; gap:20px; padding:14px clamp(18px,2.4vw,34px); border-bottom:1px solid var(--line); font-size:var(--fs-xs); letter-spacing:.2em; text-transform:uppercase; color:var(--faint); }
.term-head b{ color:var(--accent); font-weight:500; }
.term-body{ padding:clamp(22px,3vw,48px) clamp(18px,2.6vw,40px); display:grid; gap:clamp(34px,5vh,56px); }
.ph{ display:grid; grid-template-columns:1fr .8fr; gap:clamp(20px,4vw,70px); align-items:start; }
.ph .ph-shell{ min-width:0; font-size:clamp(13px,1vw,15px); line-height:2; }
.ph .ph-line{ color:var(--muted); }
.ph .ph-line .pr{ color:var(--accent); margin-right:10px; }
.ph .ph-cmd{ color:var(--ink); font-weight:500; word-break:break-all; }
.ph .ph-cmd::after{ content:"▌"; color:var(--accent); animation:caret 1s steps(2) infinite; margin-left:2px; }
.ph.done .ph-cmd::after{ content:""; animation:none; }
@keyframes caret{ 50%{opacity:0} }
.ph .ph-out{ list-style:none; margin-top:6px; }
.ph .ph-out li{ color:var(--muted); opacity:0; transform:translateY(6px); }
.ph .ph-out li::before{ content:"→ "; color:var(--faint); }
.ph .ph-out li em{ font-style:normal; color:var(--accent); }
.ph .ph-ok{ margin-top:8px; color:var(--faint); opacity:0; transform:translateY(6px); }
.ph.done .ph-ok{ color:var(--accent); }
.ph .ph-meta .pm-tag{ font-size:var(--fs-xs); letter-spacing:.22em; text-transform:uppercase; color:var(--amber); }
.ph .ph-meta h3{ margin-top:12px; font-family:var(--display); font-weight:700; text-transform:uppercase; font-size:clamp(1.28rem,2vw,1.85rem); letter-spacing:-.01em; line-height:1.05; }
.ph .ph-meta p{ margin-top:14px; font-size:var(--fs-base); line-height:1.8; color:var(--muted); max-width:44ch; }
@media (max-width:760px){ .ph{ grid-template-columns:1fr; gap:18px; } .ph .ph-meta{ order:-1; } }

/* ---------- SYS.04 why + telemetry ---------- */
/* ---------- SYS.04 why ----------
   Was .why-open: a 2x2 grid of bare text, eyebrow / heading / paragraph, with
   a 36px accent tick above each that grew to 72px on hover. Four arguments
   with nothing to look at and nothing to compare.

   Now four instrument panels welded into one rack by 1px hairline gutters —
   the same joinery as .ways in SYS.06, so the two sections read as siblings —
   each with a recessed well at its foot holding the diagram that carries its
   argument. The well is what distinguishes it from .ways, whose cards carry a
   bare 44px axis strip and no ground of their own.

   Diagram styling (everything inside .wyg) lives in diagrams.css, next to the
   SYS.02 module drawings, because it is the same job. */
.why-grid{
  margin-top:clamp(50px,9vh,110px);
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1px;
  background:var(--line); border:1px solid var(--line);
}
.wy{
  min-width:0; background:var(--panel);
  padding:clamp(24px,2.4vw,38px) clamp(24px,2.4vw,38px) 0;
  display:flex; flex-direction:column; gap:clamp(14px,1.6vh,20px);
  transition:background .45s var(--ease-out);
}
@media (hover:hover){ .wy:hover{ background:var(--hover); } }

.wy-head{ display:flex; align-items:baseline; gap:14px; }
/* The index is a label, not a step: these four are a set, not a sequence.
   It is kept because the whole page indexes things this way (SYS.xx, MOD.xx),
   not because there is an order to follow. */
.wy-i{
  font-family:var(--mono); font-size:var(--fs-sm); letter-spacing:.18em;
  color:var(--amber); flex:0 0 auto;
}
.wy-k{
  font-family:var(--mono); font-size:var(--fs-2xs); letter-spacing:.22em;
  text-transform:uppercase; color:var(--faint);
}
.wy h3{
  font-family:var(--display); font-weight:700; text-transform:uppercase;
  font-size:clamp(1.1rem,1.7vw,1.55rem); letter-spacing:-.01em; line-height:1.06;
}
/* flex:1 pushes the well to the panel's foot, so all four wells sit on one
   line across the rack however long the paragraphs run. */
.wy p{
  flex:1 1 auto; margin:0; font-size:var(--fs-md); line-height:1.78; color:var(--muted);
}

/* The well. Recessed below the panel and bled to its edges — the drawing is
   the panel's floor, not a picture hung on it. Same blueprint ground as the
   SYS.02 media panels, at the same 58px pitch, so the grain matches. */
.wy-fig{
  margin:clamp(6px,1vh,14px) calc(-1 * clamp(24px,2.4vw,38px)) 0;
  padding:clamp(16px,1.8vw,26px) clamp(18px,2vw,30px);
  border-top:1px solid var(--line);
  background-color:var(--media-bg);
  /* Deliberately fainter than the SYS.02 media wells, which carry the same
     grid at full --grid-line. Those sit behind drawings that are mostly
     fills; these sit behind pure line work, and at full strength the 58px
     rules read as part of the diagram — in WHY.01 they line up with the
     stems almost exactly. A ground has to stay a ground. */
  background-image:
    linear-gradient(to right, color-mix(in oklab, var(--grid-line) 42%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in oklab, var(--grid-line) 42%, transparent) 1px, transparent 1px);
  background-size:58px 58px;
}
.wyg{ display:block; width:100%; height:auto; overflow:visible; }

@media (max-width:860px){
  .why-grid{ grid-template-columns:minmax(0,1fr); }
}

/* ---------- SYS.05 case files (horizontal reel) ---------- */
.showcase{ position:relative; }
.showcase .head{ padding-block:8vh 3vh; }
.h-viewport{ overflow:hidden; }
.h-track{ display:flex; width:max-content; will-change:transform; }
/* --rp-reserve keeps the last results row clear of .reel-progress, which is
   position:fixed and so contributes nothing to the panel's own layout: the
   bar's offset from the viewport foot, plus its own ~30px block, plus a gap. */
.showcase{ --rp-reserve:calc(clamp(20px,4.5vh,46px) + 48px); }
.h-panel{ position:relative; width:min(92vw,1060px); min-height:calc(100svh - 13vh); flex:0 0 auto; display:flex; flex-direction:column; justify-content:center; padding:clamp(34px,5vh,64px) 5vw max(clamp(38px,5.5vh,72px), var(--rp-reserve)); border-left:1px solid var(--line); overflow:visible; }
.h-panel .panel-media{ position:absolute; inset:0; overflow:hidden; z-index:0; }
.h-panel .panel-media>img{ width:100%; height:100%; object-fit:cover; transform:scale(1.08); filter:grayscale(.6) brightness(.85); transition:transform 1.2s var(--ease-out), filter 1.2s var(--ease-out); }
.h-panel .panel-media .duo{ position:absolute; inset:0; background:linear-gradient(100deg, rgba(var(--duo-rgb),.97) 0%, rgba(var(--duo-rgb),.85) 42%, rgba(var(--duo-rgb),.4) 100%); }
.h-panel>*:not(.panel-media){ position:relative; z-index:2; }
@media (hover:hover){ .h-panel:hover .panel-media>img{ transform:scale(1); filter:grayscale(.25) brightness(.6); } }
[data-theme="light"] .h-panel .panel-media>img{ filter:grayscale(.4) brightness(1.16) contrast(.82); }
[data-theme="light"] .h-panel:hover .panel-media>img{ filter:grayscale(0) brightness(1.1) contrast(.9); }
/* The scrim has to carry more of the work in light mode: dark type over a
   photograph with bright highlights is unreliable at any average luminance. */
[data-theme="light"] .h-panel .panel-media .duo{ background:linear-gradient(100deg, rgba(var(--duo-rgb),.99) 0%, rgba(var(--duo-rgb),.95) 42%, rgba(var(--duo-rgb),.74) 100%); }
.h-panel .p-idx{ font-size:var(--fs-sm); letter-spacing:.26em; color:var(--amber); text-transform:uppercase; }
.h-panel .p-client{ font-size:var(--fs-sm); letter-spacing:.22em; text-transform:uppercase; color:var(--muted); margin-top:14px; }
.h-panel h3{ font-family:var(--display); font-weight:800; text-transform:uppercase; font-size:clamp(1.4rem,2.5vw,2.35rem); line-height:1.04; letter-spacing:-.02em; margin-top:12px; max-width:24ch; }
.h-panel .p-desc{ margin-top:14px; max-width:52ch; font-size:var(--fs-md); line-height:1.65; color:var(--muted); }
.h-panel .p-results{ display:flex; flex-wrap:wrap; gap:16px 40px; margin-top:clamp(16px,2.6vh,30px); }
.h-panel .p-results .r .rv{ font-family:var(--display); font-weight:800; font-size:clamp(1.5rem,3vw,2.4rem); line-height:.9; color:var(--accent); }
.h-panel .p-results .r .rl{ margin-top:8px; font-size:var(--fs-2xs); letter-spacing:.14em; text-transform:uppercase; color:var(--muted); max-width:18ch; }
.h-panel .p-quote{ margin-top:clamp(16px,2.6vh,30px); font-family:var(--display); font-weight:500; font-size:clamp(.95rem,1.25vw,1.15rem); line-height:1.35; max-width:42ch; color:var(--ink); }
.h-panel .p-quote cite{ display:block; margin-top:13px; font-family:var(--mono); font-style:normal; font-size:var(--fs-2xs); letter-spacing:.16em; text-transform:uppercase; color:var(--muted); }
.cs-open{ margin-top:clamp(14px,2vh,26px); align-self:flex-start; display:inline-flex; align-items:center; justify-content:center; gap:10px; min-height:46px; font-family:var(--mono); font-size:var(--fs-sm); letter-spacing:.18em; text-transform:uppercase; color:var(--ink); background:transparent; border:1px solid var(--line); padding:13px 22px; cursor:pointer; transition:background .45s var(--ease-out), border-color .45s var(--ease-out), color .45s var(--ease-out); }
@media (hover:hover){ .cs-open:hover{ background:var(--accent); border-color:var(--accent); color:var(--on-accent); } }
@media (pointer:coarse){ .cs-open{ cursor:pointer; } }
@media (max-height:820px) and (min-width:769px){ .showcase .head{ padding-block:7vh 2vh; } .h-panel{ justify-content:flex-start; min-height:calc(100svh - 12vh); padding-top:4vh; padding-bottom:max(6vh, var(--rp-reserve)); } .h-panel h3{ font-size:clamp(1.45rem,2.6vw,2.25rem); } .h-panel .p-desc{ line-height:1.55; margin-top:12px; max-width:68ch; } .h-panel .p-results{ margin-top:18px; } .h-panel .p-quote{ margin-top:18px; } .cs-open{ margin-top:18px; } }
@media (max-height:700px) and (min-width:769px){ .h-panel{ padding-top:4vh; padding-bottom:max(5vh, var(--rp-reserve)); } .h-panel .p-desc{ margin-top:11px; } .h-panel .p-results{ margin-top:13px; gap:12px 34px; } .cs-open{ margin-top:13px; } }
@media (max-width:768px){ .h-viewport{ overflow:visible; } .h-track{ flex-direction:column; width:100%; } .h-panel{ width:100%; min-height:0; padding:10vh 5vw; border-left:none; border-top:1px solid var(--line); } .h-panel h3{ font-size:clamp(1.5rem,6.6vw,2.5rem); } .h-panel .p-results{ gap:22px 30px; } }
@media (max-width:480px){ .h-panel{ padding:9vh 5vw; } .h-panel .p-client{ letter-spacing:.16em; } .h-panel .p-results{ display:grid; grid-template-columns:1fr; } .cs-open{ width:100%; } }
/* Closing panel of the reel — a sign-off rather than a case file, so it
   centres its own short stack instead of inheriting the case-file rhythm. */
.h-panel-end{ justify-content:center; gap:clamp(18px,3vh,34px); }
.h-panel-end .p-idx{ color:var(--faint); }
.reel-cta{ display:inline-block; max-width:18ch; font-size:clamp(1.15rem,2vw,1.7rem); line-height:1.2; text-transform:none; letter-spacing:0; border-bottom:1px solid var(--accent); padding-bottom:8px; transition:color .4s var(--ease-out); }
@media (hover:hover){ .reel-cta:hover{ color:var(--accent); } }

.reel-progress{ position:fixed; left:5vw; right:5vw; bottom:clamp(20px,4.5vh,46px); z-index:40; pointer-events:none; opacity:0; transition:opacity .45s var(--ease-out); }
.reel-progress.show{ opacity:1; }
.rp-meta{ display:flex; justify-content:space-between; align-items:baseline; font-size:var(--fs-xs); letter-spacing:.18em; text-transform:uppercase; color:var(--muted); margin-bottom:12px; }
.rp-count{ color:var(--accent); }
.rp-bar{ position:relative; height:2px; background:var(--line); overflow:hidden; }
.rp-bar>i{ position:absolute; inset:0; transform-origin:left center; transform:scaleX(0); background:var(--accent); will-change:transform; }
@media (max-width:768px){ .reel-progress{ display:none; } }

/* ---------- SYS.07 references ---------- */
/* The .creds credentials block that shared this rule was removed from the page
   on request; the whole .cred* group went with it rather than being left as
   dead CSS. Both markup and styles are recoverable from git history if partner
   tiers or certifications are ever worth publishing. */
.refs{ margin-top:clamp(52px,9vh,110px); border-top:1px solid var(--line); padding-top:clamp(26px,4vh,46px); }
.refs .quotes-open{ margin-top:22px; }

/* Unfilled slots render visibly unfinished on purpose. This site has already
   shipped once with invented content; a placeholder that looks like finished
   copy is how that happens. Delete this rule when the slots are filled. */
[data-placeholder]{
  color:var(--muted); font-style:normal;
  border-bottom:1px dashed color-mix(in oklab, var(--amber) 60%, transparent);
  text-decoration:none;
}
/* ...except on the footer's social glyphs. The dashed rule is a marker for an
   unfinished TEXT slot -- it reads as "copy still to come" under a sentence.
   Under an icon it reads as a broken link, which is the opposite of what the
   marker is for. The row is shown deliberately while the profiles are pending,
   so it has to look finished; the icons stay inert either way (no href, not in
   the tab order, aria-hidden). */
.foot-social .fs-link[data-placeholder]{ border-bottom:0; }

/* PARKED — the testimonial layout below is now used by .refs above:
   a lead quote over a two-up row. Reinstate it in SYS.06 (or its own section)
   the moment a client gives written permission to be quoted by name. Kept
   rather than deleted so a real quote is a markup change, not a rebuild. */
.quotes-open{ margin-top:9vh; }
.qo blockquote{ font-family:var(--display); font-weight:500; letter-spacing:-.01em; color:var(--ink); }
.qo blockquote::before{ content:"// "; color:var(--accent); }
.qo-lead{ padding-bottom:clamp(42px,6vh,80px); border-bottom:1px solid var(--line); }
.qo-lead blockquote{ font-size:clamp(1.35rem,2.7vw,2.4rem); line-height:1.26; max-width:28ch; }
.qo-row{ margin-top:clamp(42px,6vh,80px); display:grid; grid-template-columns:1fr 1fr; gap:clamp(40px,6vw,110px); }
.qo-row .qo blockquote{ font-size:clamp(1rem,1.5vw,1.3rem); line-height:1.45; }
.qo figcaption{ margin-top:clamp(20px,2.6vh,34px); display:flex; flex-direction:column; gap:6px; }
.tq-name{ font-size:var(--fs-sm); letter-spacing:.16em; text-transform:uppercase; color:var(--ink); }
.tq-role{ font-size:var(--fs-xs); letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
@media (max-width:760px){ .qo-row{ grid-template-columns:1fr; gap:44px; } }

/* ---------- SYS.07 operators (about) ---------- */
.mission{ font-family:var(--display); font-weight:500; font-size:clamp(1.45rem,3.2vw,2.7rem); line-height:1.2; letter-spacing:-.02em; max-width:26ch; margin-top:7vh; }
/* Two rows, two columns, named so the stacked order can differ from the
   desktop one. Row 1 is the claim beside the facts that qualify it; row 2 is
   the story beside the map. Both pairs land within ~30px of each other, where
   the previous single row finished 294px apart. */
.about-grid{
  display:grid; margin-top:7vh; align-items:start;
  grid-template-columns:1fr 1.02fr;
  grid-template-areas:
    "mission facts"
    "story   map";
  gap:clamp(36px,5vh,64px) clamp(30px,4.5vw,72px);
}
.about-grid > .mission{ grid-area:mission; margin-top:0; }
.about-grid > .facts-wrap{ grid-area:facts; min-width:0; }
/* Gives the table the same labelled-panel parity the map has, so the top
   row reads as two titled things rather than a statement and a stray list. */
.facts-wrap .cn-k{ margin-bottom:14px; }
.about-grid .about-facts{ margin:0; }
.about-grid > .as-text{ grid-area:story; }
.about-grid > .cover{ grid-area:map; }

/* ---------- SYS.07 coverage plot ----------
   Same shell as the SYS.01 panels, so the three read as one component family.
   Points are real coordinates on a linear equirectangular projection: accurate
   without a traced coastline, and in the same blueprint language as the
   hero HUD's 27.26°N / 33.81°E. */
.cover{ margin:0; border:1px solid var(--line); background:var(--panel); box-shadow:0 30px 70px -46px rgba(0,0,0,.7); }
.cover-plot{ padding:clamp(10px,1.4vw,18px); }
/* --cvt is the label size in USER UNITS, and the SVG scales to its panel, so
   an 11-unit label renders at 11 x (panel width / 470): 13px in a 560px panel
   and 7px in a 302px one. Two things hold it steady.

   The max-width caps the top: past 560px the drawing stops growing, so every
   panel wider than that renders at one scale and needs one size — which is
   why the wide desktop and the stacked tablet share the base value.

   The ladder handles the rest, and the odd-looking bounded query is real: the
   about grid stacks at 960px and the map gets a wide column back, so the
   scale JUMPS UP there. Squeezed two-column widths between 961 and 1180 are
   the smallest the map ever gets on a desktop and the only band that needs
   its own step going up. Every number is measured, not guessed: the result is
   10.8px to 13.3px from 360 through 1680. */
.cover-plot svg{ display:block; width:100%; max-width:560px; height:auto; margin-inline:auto; --cvt:11px; }
@media (min-width:961px) and (max-width:1180px){ .cover-plot svg{ --cvt:13px; } }
@media (max-width:600px){ .cover-plot svg{ --cvt:12.5px; } }
@media (max-width:530px){ .cover-plot svg{ --cvt:14px; } }
@media (max-width:440px){ .cover-plot svg{ --cvt:16px; } }
@media (max-width:395px){ .cover-plot svg{ --cvt:18px; } }
.cv-grid line{ stroke:var(--grid-line); stroke-width:1; }
/* Both axes label from the same rule; the y ticks are the only ones that
   hang off to the left, so they are the only ones that override the anchor. */
.cv-ax text{ fill:var(--faint); font-family:var(--mono); font-size:calc(var(--cvt) * .96); letter-spacing:.1em; text-anchor:middle; }
.cv-ax-y{ text-anchor:end; }
/* The 2px ring is the surface colour, not a stroke you can see: it keeps a
   reach line passing behind a dot from appearing to graze it. Three of the
   six lines leave the base within a narrow angle, so several pass close. */
.cv-city circle{ fill:var(--muted); stroke:var(--panel); stroke-width:2; }
/* Centred above the dot for every city — the anchor lives here rather than
   on each <text>, so the convention cannot drift one label at a time. */
.cv-city text{ fill:var(--muted); font-family:var(--mono); font-size:var(--cvt); letter-spacing:.08em; text-anchor:middle; }
.cv-base text{ fill:var(--accent); font-family:var(--mono); font-size:var(--cvt); letter-spacing:.14em; text-anchor:end; }
.cv-pin{ fill:var(--accent); stroke:var(--panel); stroke-width:2; }
.cv-halo{ fill:none; stroke:var(--accent); stroke-width:1.5; opacity:.4; }
/* Reach from the base outward — the claim the plot exists to make. */
/* Three of the six leave the base inside a 25-degree fan, so at .32 they
   read as one loud teal wedge rather than as reach. Quiet enough to be a
   field the dots sit on. */
.cv-reach line{ stroke:var(--accent); stroke-width:1; opacity:.22; stroke-dasharray:var(--l,300); stroke-dashoffset:var(--l,300); transition:stroke-dashoffset 1.1s var(--ease-out); }
.cover.drawn .cv-reach line{ stroke-dashoffset:0; }
/* The light theme's accent is a deep green and the panel is near-white, so
   the same .22 lands at 1.43:1 against the panel where the dark theme gets
   1.66:1 — the fan all but disappeared. .30 matches it at 1.60:1. */
[data-theme="light"] .cv-reach line{ opacity:.3; }
.cv-city circle,.cv-city text{ opacity:0; transition:opacity .55s var(--ease-out); }
.cover.drawn .cv-city circle,.cover.drawn .cv-city text{ opacity:1; }
.cv-halo{ transform-box:fill-box; transform-origin:center; }
.cover.drawn .cv-halo{ animation:cv-ping 3.4s var(--ease-out) infinite; }
@keyframes cv-ping{ 0%{ transform:scale(.72); opacity:.5 } 70%{ transform:scale(1.25); opacity:0 } 100%{ opacity:0 } }
@media (prefers-reduced-motion:reduce){
  .cv-reach line{ transition:none; stroke-dashoffset:0; }
  .cv-city circle,.cv-city text{ transition:none; opacity:1; }
  .cover.drawn .cv-halo{ animation:none; opacity:.4; }
}
/* No max-width: the grid track sets the measure now, which is the point of
   putting it in a track. At 1440px that is ~66 characters — the cap was
   leaving 104px of the column empty and making the row look mis-set. */
.about-grid .as-text{ display:grid; gap:22px; min-width:0; }
.about-grid .as-text p{ font-size:var(--fs-base); line-height:1.9; color:var(--muted); }
/* Lifts the Cable-to-Cloud line out of the muted body copy without a second
   type size, matching how .hero .lede treats its own <strong>. */
.about-grid .as-text p strong{ color:var(--ink); font-weight:500; }
.about-facts{ border-top:1px solid var(--line); }
.about-facts > div{ display:flex; justify-content:space-between; gap:20px; padding:15px 0; border-bottom:1px solid var(--line); }
.about-facts dt{ font-size:var(--fs-xs); letter-spacing:.16em; text-transform:uppercase; color:var(--muted); }
.about-facts dd{ font-size:var(--fs-sm); letter-spacing:.06em; text-transform:uppercase; color:var(--ink); text-align:right; }
@media (max-width:960px){
  /* Stacked, the facts move last: they are a summary of the story, and a
     reader has not met it yet at the top of a phone screen. */
  .about-grid{
    grid-template-columns:1fr;
    grid-template-areas:"mission" "story" "map" "facts";
    gap:clamp(30px,4.5vh,44px);
  }
  /* Given a full 810px column the plot grew to dominate everything under it.
     The svg's own 560px cap does most of this now; the panel cap keeps the
     header and footer bars from stretching past the drawing they frame. */
  .about-grid > .cover{ max-width:620px; }
}
.team-row{ display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(20px,2.4vw,40px); margin-top:clamp(60px,11vh,150px); }
.team-row .tm .m-photo{ position:relative; overflow:hidden; aspect-ratio:4/5; background:var(--media-bg); margin-bottom:18px; }
.team-row .tm .m-photo img{ width:100%; height:100%; object-fit:cover; filter:grayscale(1) brightness(.82) contrast(1.05); transition:filter .8s var(--ease-out), scale .8s var(--ease-out); scale:1.04; }
.team-row .tm .m-photo .duo{ position:absolute; inset:0; background:linear-gradient(180deg, transparent 55%, rgba(var(--duo-rgb),.55)); }
.team-row .tm .m-photo .scan{ position:absolute; left:0; right:0; top:-20%; height:18%; background:linear-gradient(180deg, transparent, color-mix(in oklab, var(--accent) 22%, transparent), transparent); opacity:0; transition:opacity .3s; pointer-events:none; }
@media (hover:hover){
  .team-row .tm:hover .m-photo img{ filter:grayscale(0) brightness(.95); scale:1; }
  .team-row .tm:hover .m-photo .scan{ opacity:1; animation:scan 1.4s linear infinite; }
}
@keyframes scan{ to{ transform:translateY(660%); } }
[data-theme="light"] .team-row .tm .m-photo img{ filter:grayscale(1) brightness(1.02); }
[data-theme="light"] .team-row .tm:hover .m-photo img{ filter:grayscale(0) brightness(1.02); }
.team-row .tm .nm{ font-family:var(--display); font-weight:700; text-transform:uppercase; font-size:1.05rem; letter-spacing:-.01em; }
.team-row .tm .ro{ margin-top:8px; font-size:var(--fs-2xs); letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
.team-row .tm p{ margin-top:13px; font-size:var(--fs-md); line-height:1.7; color:var(--muted); }
@media (max-width:860px){ .team-row{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:480px){ .team-row{ grid-template-columns:1fr; } }
/* ---------- SYS.06 ways in ----------
   Was .values: three full-width text rows, title in a 1fr column that sat
   mostly empty, and no way to compare the three at a glance. Cards put them
   side by side, and each carries the one diagram that distinguishes it. */
/* ---------- SYS.06 progression rail ----------
   Three nodes on the column centres of the .ways grid below, a fill that
   reaches the active step, and a marker that travels to it. --step is set by
   ways-rail.js on hover; everything visual is here.

   16.666% is the centre of the first of three equal columns, 33.333% the
   distance between centres — so the marker lands dead centre over each card
   without JS measuring anything. */
.ways-lead{
  margin-top:clamp(50px,9vh,110px);
  display:flex; align-items:baseline; gap:16px; flex-wrap:wrap;
}
.wl-k{ font-size:var(--fs-2xs); letter-spacing:.22em; text-transform:uppercase; color:var(--amber); }
.wl-t{ font-size:var(--fs-md); line-height:1.6; color:var(--muted); }

.ways-rail{
  --step:0;
  position:relative; height:34px; margin-top:22px;
}
/* Inset to the outer node centres. A track running the full width leaves a
   couple of hundred pixels of rail past 01 and 03 that mean nothing — a
   stepper's line belongs between its first and last steps. */
.wr-track{ position:absolute; left:16.666%; right:16.666%; top:50%; height:1px; margin-top:-.5px; background:var(--line); }
.wr-fill{
  position:absolute; left:0; top:0; bottom:0; display:block; background:var(--accent);
  width:calc(var(--step) * 50%);          /* 0 / 50 / 100 across the inset track */
  transition:width .55s var(--ease-out);
}
.wr-nodes{ position:absolute; inset:0; display:grid; grid-template-columns:repeat(3,1fr); align-items:center; }
.wr-node{
  justify-self:center; width:9px; height:9px; border-radius:50%;
  background:var(--bg); border:1px solid var(--field-line);
  transition:border-color .45s var(--ease-out), background .45s var(--ease-out);
}
.wr-node.on{ border-color:var(--accent); background:var(--accent); }
/* The travelling marker: a ring around whichever node is current. */
.wr-mark{
  position:absolute; top:50%; width:22px; height:22px; margin:-11px 0 0 -11px;
  border-radius:50%; border:1.5px solid var(--accent); background:var(--bg);
  left:calc(16.666% + var(--step) * 33.333%);
  transition:left .55s var(--ease-out);
}
.wr-mark::after{
  content:""; position:absolute; inset:5px; border-radius:50%; background:var(--accent);
}
@media (max-width:1000px){
  /* Stacked cards make a horizontal rail meaningless. The 01/02/03 indices
     and the caption above still carry the order. */
  .ways-rail{ display:none; }
  .ways-lead{ margin-bottom:6px; }
}
@media (prefers-reduced-motion:reduce){
  .wr-fill,.wr-mark{ transition:none; }
}

.ways{
  margin-top:22px;
  display:grid; grid-template-columns:repeat(3,1fr); gap:1px;
  background:var(--line); border:1px solid var(--line);
}
.way{
  background:var(--panel); padding:clamp(24px,2.4vw,36px);
  display:flex; flex-direction:column; gap:clamp(18px,2vh,26px);
  transition:background .45s var(--ease-out);
}
@media (hover:hover){ .way:hover{ background:var(--hover); } }
.way-top{ display:flex; align-items:baseline; gap:14px; }
.way-i{ font-size:var(--fs-sm); letter-spacing:.22em; color:var(--amber); flex:0 0 auto; }
.way h3{ font-family:var(--display); font-weight:700; text-transform:uppercase; font-size:clamp(1.05rem,1.5vw,1.4rem); letter-spacing:-.01em; line-height:1.05; }
.way-p{ font-size:var(--fs-md); line-height:1.75; color:var(--muted); flex:1 1 auto; }
.way-p strong{ color:var(--ink); font-weight:600; }

/* The diagram. One axis, and the mark that describes the engagement's shape
   in time — a point, a bounded segment, a line that runs past both edges. */
.way-shape{ display:block; width:100%; height:44px; overflow:visible; }
.ws-axis{ stroke:var(--line); stroke-width:1; stroke-dasharray:2 5; }
.ws-cap,.ws-bar,.ws-ticks line{ stroke:var(--accent); stroke-linecap:round; }
.ws-bar{ stroke-width:3; }
.ws-cap{ stroke-width:2; }
.ws-ticks line{ stroke-width:1.5; opacity:.5; }
.ws-dot{ fill:var(--accent); }
.ws-ring{ fill:none; stroke:var(--accent); stroke-width:1.5; opacity:.45; }
/* Managed support runs off both edges, so it fades rather than stopping.
   The gradient's stops are coloured here rather than in the markup —
   var() is not valid in an SVG presentation attribute. */
.ws-run{ stroke:url(#runfade); }
#runfade stop{ stop-color:var(--accent); }

/* Draw-in, added by diagrams.js when the card reaches the viewport. */
.way-shape .ws-bar,.way-shape .ws-cap,.way-shape .ws-ticks line{
  stroke-dasharray:var(--len,220); stroke-dashoffset:var(--len,220);
  transition:stroke-dashoffset .9s var(--ease-out);
}
.way-shape .ws-dot,.way-shape .ws-ring{ opacity:0; transition:opacity .5s var(--ease-out) .25s; }
.way-shape .ws-ring{ transition-delay:.45s; }
.way.drawn .way-shape .ws-bar,
.way.drawn .way-shape .ws-cap,
.way.drawn .way-shape .ws-ticks line{ stroke-dashoffset:0; }
.way.drawn .way-shape .ws-dot{ opacity:1; }
.way.drawn .way-shape .ws-ring{ opacity:.45; }

.way-facts{ border-top:1px solid var(--line); padding-top:clamp(14px,1.8vh,20px); display:grid; gap:9px; }
.way-facts > div{ display:flex; justify-content:space-between; align-items:baseline; gap:14px; }
.way-facts dt{ font-size:var(--fs-2xs); letter-spacing:.16em; text-transform:uppercase; color:var(--faint); flex:0 0 auto; }
.way-facts dd{ margin:0; font-size:var(--fs-2xs); letter-spacing:.08em; text-transform:uppercase; color:var(--ink); text-align:right; }

@media (max-width:1000px){ .ways{ grid-template-columns:1fr; } }
@media (prefers-reduced-motion:reduce){
  .way-shape .ws-bar,.way-shape .ws-cap,.way-shape .ws-ticks line{ transition:none; stroke-dashoffset:0; }
  .way-shape .ws-dot{ transition:none; opacity:1; }
  .way-shape .ws-ring{ transition:none; opacity:.45; }
}

/* Shortest desktop viewports (≈1280×700 laptops). The case-file reel has to
   fit its header and a whole panel into one screen, so the longest panel gets
   one more notch of compaction rather than clipping its results row. */
@media (max-height:730px) and (min-width:769px){
  .showcase .head{ padding-block:6vh 2vh; }
  .h-panel h3{ font-size:clamp(1.25rem,2.1vw,1.75rem); }
  .h-panel .p-desc{ margin-top:10px; line-height:1.6; }
  .h-panel .p-results{ margin-top:14px; gap:12px 34px; }
  .h-panel .p-quote{ margin-top:14px; font-size:.95rem; }
  .cs-open{ margin-top:12px; }
}

/* Closing line under the SYS.06 engagement rows. */
.eng-note{ margin-top:clamp(28px,4vh,52px); font-size:var(--fs-base); line-height:1.8; color:var(--muted); max-width:60ch; }
