/* ============================================================================
   NAVI FONT LOADING
   Self-hosted, variable, metric-matched. Drop-in replacement for the
   Google Fonts <link>.

   WHY NOT GOOGLE FONTS. Payload was never the problem: both families are
   variable fonts totalling ~59KB for latin, which is small. The problem is
   the REQUEST CHAIN. A Google-hosted setup makes the browser do this, in
   strict order, before a single word renders in the right typeface:

     1. parse HTML, find <link> to fonts.googleapis.com
     2. DNS + TCP + TLS handshake to fonts.googleapis.com      <- new origin
     3. download and parse the CSS (9.8KB)
     4. only NOW discover the font URLs on fonts.gstatic.com
     5. DNS + TCP + TLS handshake to fonts.gstatic.com         <- 2nd origin
     6. download the fonts

   Two third-party origins and a round trip that cannot even START until the
   CSS arrives. Self-hosting collapses steps 2-5 to zero: the fonts come off
   the same already-open connection as the HTML, and <link rel=preload> lets
   the browser start fetching them immediately instead of waiting for CSS.

   MEASURED (2026-07-18, via Google's own css2 endpoint):
     geist-latin.woff2      28356 B      onest-latin.woff2      32240 B
     geist-latin-ext.woff2  15292 B      onest-latin-ext.woff2  15744 B
     English visitor 60596 B  |  Czech visitor 91632 B

   CZECH. The cs/ pages need latin-ext: e s c r z u d t n all live in
   U+0100-017F. It is split into its own file below, so English-only
   visitors never download it. Do not merge the subsets into one file.

   USAGE. In <head>, BEFORE this stylesheet:
     <link rel="preload" href="fonts/onest-latin.woff2" as="font"
           type="font/woff2" crossorigin>
     <link rel="preload" href="fonts/geist-latin.woff2" as="font"
           type="font/woff2" crossorigin>
   Preload ONLY the latin files, and only the two that render above the fold.
   Preloading everything is worse than preloading nothing: it competes with
   the images for bandwidth.
   ============================================================================ */

/* ---------- Onest - display. Variable, axis clamped to the locked range ---------- */
@font-face{
  font-family:'Onest';
  src:url('fonts/onest-latin.woff2') format('woff2');
  font-weight:500 700;          /* DIRECTION.md locks Onest to 500-700 */
  font-style:normal;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
                U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,
                U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Onest';
  src:url('fonts/onest-latin-ext.woff2') format('woff2');
  font-weight:500 700;
  font-style:normal;
  font-display:swap;
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
                U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,
                U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ---------- Geist - body. Variable, axis clamped to the locked range ---------- */
@font-face{
  font-family:'Geist';
  src:url('fonts/geist-latin.woff2') format('woff2');
  font-weight:400 600;          /* DIRECTION.md locks Geist to 400-600 */
  font-style:normal;
  font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
                U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,
                U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Geist';
  src:url('fonts/geist-latin-ext.woff2') format('woff2');
  font-weight:400 600;
  font-style:normal;
  font-display:swap;
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
                U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,
                U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ---------- Metric-matched fallbacks ----------
   font-display:swap renders fallback text first, then swaps. Without this
   block the swap RESHUFFLES THE LAYOUT, because Arial's metrics differ from
   Geist's and Onest's. That is a visible jolt on first paint and it is a
   Core Web Vital (CLS).

   Values measured from the real fonts via canvas at 100px:
     Geist  ascent 101  descent 29     Onest  ascent 97  descent 30
     Arial  ascent  91  descent 21

   size-adjust MUST be derived from RUNNING PROSE, not from an alphabet
   string. A first attempt used "ABC...xyz" and produced 98.99% for Geist,
   which made drift WORSE than no matching at all (3.40% vs Arial's 2.37%),
   because real sentences have different letter frequencies and far more
   spaces than an alphabet. Measured over a real Navi sentence:
     Geist 0.4583em   Onest 0.4769em   Arial 0.4469em
     size-adjust = target advance / Arial advance
   ascent/descent = target ratio / size-adjust  (overrides apply to the
                    ALREADY size-adjusted em, so they must be divided by it)
   Verified in font-loading-proof.html, which fails the build if drift >2%.  */
@font-face{
  font-family:'Geist Fallback';
  src:local('Arial'),local('Helvetica Neue'),local('Liberation Sans');
  size-adjust:102.55%;
  ascent-override:98.49%;
  descent-override:28.28%;
  line-gap-override:0%;
}
@font-face{
  font-family:'Onest Fallback';
  src:local('Arial'),local('Helvetica Neue'),local('Liberation Sans');
  size-adjust:106.70%;
  ascent-override:90.91%;
  descent-override:28.12%;
  line-gap-override:0%;
}

/* ---------- The only two stacks the site should reference ---------- */
:root{
  --font-display:'Onest','Onest Fallback',system-ui,sans-serif;
  --font-body:'Geist','Geist Fallback',system-ui,sans-serif;
}
