/* ═══════════════════════════════════════════════════════════════════════════
   DRIIF — BASE + COMPONENTS v1.0
   Every value here resolves to a token. No literals except the three
   breakpoint widths (custom properties can't be used inside @media).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Fonts, served locally ──────────────────────────────────────────────────
   Two variable woff2 files, self-hosted. They replace four render-blocking
   requests to fonts.googleapis.com/fonts.gstatic.com (plus two DNS lookups and
   two TLS handshakes to third-party origins) with two same-origin requests.

   Both are VARIABLE fonts, which is why there are two files and not six: one
   `wght` axis covers every weight the site uses. Google's own CSS declared
   300/400/500/700 for Space Grotesk and 400/500 for IBM Plex Sans but served
   exactly these two files behind them.

   Subset to Latin + the punctuation the site actually renders: 165KB of
   downloaded originals became 44KB. The `wght` ranges below are deliberate —
   Space Grotesk's axis is its full 300–700, IBM Plex Sans is clamped to
   400–500 because nothing applies a heavier body weight, so <strong> resolves
   to 500 exactly as it did on the CDN. Widening either range would silently
   change rendering.

   font-display:swap matches the CDN's `&display=swap` — text paints in the
   fallback immediately rather than staying invisible.

   NOTE: neither font contains U+2192 (→) or U+21BA (↺). Google's `latin`
   subset did not either, so those characters have always fallen back to a
   system font. Nothing regressed; do not "fix" it by widening the subset.
   ─────────────────────────────────────────────────────────────────────────── */
@font-face{
  font-family:'Space Grotesk';
  src:url('../fonts/space-grotesk-var.woff2') format('woff2-variations'),
      url('../fonts/space-grotesk-var.woff2') format('woff2');
  font-weight:300 700;
  font-style:normal;
  font-display:swap;
}
@font-face{
  font-family:'IBM Plex Sans';
  src:url('../fonts/ibm-plex-sans-var.woff2') format('woff2-variations'),
      url('../fonts/ibm-plex-sans-var.woff2') format('woff2');
  font-weight:400 500;
  font-style:normal;
  font-display:swap;
}

*{ box-sizing:border-box; }
html{
  background:var(--color-bg);
  scroll-behavior:smooth;
  /* the nav is sticky — without this, anchored sections land underneath it */
  scroll-padding-top:calc(var(--space-8) + var(--space-4));
}
body{
  margin:0; background:var(--color-bg); color:var(--color-text);
  font-family:var(--font-body); font-size:var(--text-body); line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
}
img,svg,video{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; cursor:pointer; }
::selection{ background:var(--color-accent); color:var(--color-accent-ink); }

/* ── LAYOUT ────────────────────────────────────────────────────────────────
   .bleed is the only container. Full viewport width, --edge padding, NO
   max-width — the layout is uncapped by design. Text is held back by the
   measure tokens instead, never by the container.
   ───────────────────────────────────────────────────────────────────────── */
.bleed{ width:100%; padding-inline:var(--edge); }

/* The asymmetric split: headline left ~65%, supporting copy right ~35%,
   with a large gutter between. This is what fills a wide canvas without
   stretching a single line of text. Collapses to one column under md. */
.split{ display:grid; grid-template-columns:1fr; gap:var(--space-6); }
@media (min-width:1024px){
  .split{ grid-template-columns:minmax(0,11fr) minmax(0,6fr); column-gap:var(--gutter-lg); align-items:start; }
}

.section{ padding-block:var(--section-y); border-top:var(--hair) solid var(--color-border-subtle); }
.section-sm{ padding-block:var(--section-y-sm); }
/* A PAGE THAT OPENS ON A PLAIN SECTION HAS TO CLEAR THE FIXED NAV.
   Most pages open on a hero, which is tall, full-bleed, and designed for the
   nav to sit over its artwork. Four do not — glossary, contact,
   privacy-policy and terms-of-use open directly on a .section — and there
   the nav covered the first thing on the page. Measured at 375px: the
   glossary eyebrow and the contact heading both started at y=65 with the
   nav's bottom edge at y=77, so their first 12px were underneath it.

   .section's own padding is --section-y, which bottoms out at 64px and is
   simply shorter than the 77px nav. This adds the nav on top of that rather
   than inflating --section-y, so the section keeps the same internal rhythm
   as every other section once the nav is accounted for.

   STRUCTURAL, not a class to remember. `main > .section:first-child` is true
   of exactly the pages with this problem and false of the hero pages, whose
   first child carries `.hero` and not `.section` — verified across all nine.
   A class would be one more piece of hand-copied chrome to forget on page
   ten, which is the drift this codebase keeps producing; a new hero-less
   page gets the clearance without anyone knowing the rule exists.

   calc(--space-8 + --space-4) is the site's existing effective-nav-height
   expression, the same one html uses for scroll-padding-top just above and
   the sticky tab bar uses in components.css. Reused so a retuned nav height
   is one edit, not three. */
main > .section:first-child{
  padding-top:calc(var(--section-y) + var(--space-8) + var(--space-4));
}
section.surface-dark, section.surface-light, .ground{ background:var(--color-bg); }

.accent{ color:var(--color-accent); }

/* ── TYPE ROLES ────────────────────────────────────────────────────────────
   One class per role. A role owns its size, weight, line-height, tracking
   and measure together — that's what keeps them from drifting apart.
   ───────────────────────────────────────────────────────────────────────── */
.eyebrow{
  font-family:var(--font-display); font-weight:500;
  font-size:var(--text-eyebrow); line-height:var(--lh-flat);
  letter-spacing:var(--caps-track,var(--track-eyebrow)); text-transform:uppercase;
  color:var(--color-eyebrow); margin:0 0 var(--space-5);
}
h1,h2,h3,h4{ font-family:var(--font-display); margin:0; text-wrap:balance; }

/* ── CAPS — casing is a utility, not a property of a role ──────────────────
   Any element, any family, any size can take .caps. Uppercase needs positive
   tracking to breathe and the right amount depends on size, so it ships three
   tiers; override --caps-track locally for anything in between.

     <h2 class="t-h1 caps caps-tight">…      large heading set in caps
     <span class="t-body caps">…             mid-size caps
     <p class="eyebrow">…                    chrome roles are caps already

   Headings are MIXED CASE by default. 72% of an earlier build was uppercase,
   which flattened every element to one volume — caps are chrome here, not
   content. Opt in where a heading genuinely wants to shout.
   ───────────────────────────────────────────────────────────────────────── */
.caps{ text-transform:uppercase; letter-spacing:var(--caps-track); }
.caps-wide{  --caps-track:.16em; }  /* 12–20px labels, eyebrows */
.caps-tight{ --caps-track:.02em; }  /* 26px+ headings — big caps need almost none */
/* The inverse of .caps, scoped to one character: a plural "s" tacked onto an
   acronym (UXVs, UAVs) reads as part of the word, not another initial, when
   it stays lowercase even inside a .caps heading — wrap just the trailing
   s in this rather than fighting the parent's text-transform another way. */
.i-lower{ text-transform:lowercase; }

.t-display{
  font-size:var(--text-display); font-weight:500;
  line-height:var(--lh-display); letter-spacing:var(--track-display);
  max-width:var(--measure-display);
}
.t-h1{
  font-size:var(--text-h1); font-weight:400;
  line-height:var(--lh-tight); letter-spacing:var(--track-tight);
}
.t-h2{
  font-size:var(--text-h2); font-weight:400;
  line-height:var(--lh-tight); letter-spacing:var(--track-tight);
}
.t-h3{
  font-size:var(--text-h3); font-weight:500;
  line-height:var(--lh-snug); letter-spacing:var(--track-tight);
}
.t-lead{
  font-family:var(--font-body); font-size:var(--text-lead); font-weight:400;
  line-height:var(--lh-body); max-width:var(--measure-lead);
  text-wrap:pretty; margin:0;
}
.t-body{
  font-family:var(--font-body); font-size:var(--text-body); font-weight:400;
  line-height:var(--lh-body); max-width:var(--measure-body);
  text-wrap:pretty; margin:0;
}
.t-label{
  font-family:var(--font-display); font-weight:500; font-size:var(--ui-xs);
  line-height:var(--lh-flat); letter-spacing:var(--caps-track,var(--track-label));
  text-transform:uppercase;
}
.t-meta{
  font-family:var(--font-display); font-weight:400; font-size:var(--ui-sm);
  line-height:var(--lh-flat); letter-spacing:var(--caps-track,var(--track-label));
  text-transform:uppercase; color:var(--color-text-muted);
}
.muted{ color:var(--color-text-muted); }
/* visually hidden but read aloud — the trace is a picture of an argument, so
   it needs a text equivalent for anyone who can't see it */
/* clip-path is the modern half and clip the legacy fallback — both are needed.
   Tailwind's own .sr-only used to supply clip-path on top of this rule; when
   Tailwind was removed it went with it, and these captions became visible to
   sighted users in browsers that ignore the deprecated `clip`. */
.sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%);
  white-space:nowrap; border:0; }
/* any number that changes at runtime — counters, indices, timers */
.num{ font-variant-numeric:tabular-nums; }

/* ── MOTION ────────────────────────────────────────────────────────────────
   Reveals are gated on .js, added by script. With JS off nothing is hidden,
   so the page is never blank. Reduced motion renders final states at once.
   ───────────────────────────────────────────────────────────────────────── */
.js .reveal{ opacity:0; transform:translateY(24px);
  transition:opacity .9s var(--ease), transform .9s var(--ease); }
.js .reveal.in{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  .js .reveal{ opacity:1; transform:none; transition:none; }
  *,*::before,*::after{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

