/* ==========================================================================
   G R SITES — tokens.css
   Implements docs/design-system.md §0, §2, §3, §4.1.

   THE ONLY FILE IN THIS PROJECT THAT MAY CONTAIN A RAW HEX, A RAW PX, OR AN
   ASSET PATH. A raw value in base.css or components.css is a bug — grep for
   `#` and `px` in those two files before calling any page done.

   Load order: tokens.css → base.css → components.css
   ========================================================================== */

/* --------------------------------------------------------------------------
   FACES — self-hosted woff2, subset to Latin. Two files, no external host.

   Was four static cuts (jost-500, jost-600, inter-400, inter-600). Upstream
   now ships both families as VARIABLE fonts with a wght axis, so the four
   requests collapse to two files that carry every weight between them:
   Jost 100–900 and Inter 100–900. Four static files would have been two
   identical pairs — 150 KB and four round trips to deliver 75 KB of glyphs.

   The declared `font-weight` RANGE is what makes this work: the browser
   instances the axis at whatever weight the cascade asks for, so every
   --font-* token below resolves unchanged. Do not narrow these ranges to a
   single value — that pins the axis and 500 renders as 600.

   Source: Google Fonts, latin subset (Inter v20, Jost v20). Both SIL OFL 1.1,
   self-hosting permitted. Files must exist at ./fonts/ before first paint.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Jost";
  src: url("fonts/jost-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("fonts/inter-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {

  /* ========================================================================
     BRAND — read directly out of assets/logos/svg/*.svg. The logo files are
     the source of truth; these are their exact values. DO NOT SUBSTITUTE.

     The whole kit contains exactly five brand hexes (plus #EAFBFB, a
     highlight used only inside mark-ramp-sweep.svg). Every value below is
     one of those five, or is derived from them by a stated rule.
     All contrast figures re-measured 2026-08-02 against these values.
     ======================================================================== */

  --ink:        #123F3F;   /* 11.61:1 on white. Ramp stop 0. */
  --teal-deep:  #1F595A;   /*  7.97:1 on white. DERIVED: the exact midpoint
                               of ramp stop 0 and ramp stop 52% — verified
                               ((0x12+0x2C)/2, (0x3F+0x73)/2, (0x3F+0x75)/2).
                               Not a declared stop, but genuinely on the ramp. */
  --teal:       #2C7375;   /*  5.50:1 on white. Ramp stop 52% — the median.
                               Primary interactive value. */
  --teal-light: #62B4B9;   /*  2.40:1 on white. Ramp stop 100%.
                               MARK SHAPES AND GRADIENT TERMINAL ONLY —
                               fails even the 3:1 non-text floor, so it may
                               never carry text OR a UI border on any light
                               ground. Used in exactly one place: the light
                               end of --gradient, where nothing sits on it. */
  --cyan:       #12D6E8;   /*  The channel chip. See CYAN RULE below. */
  --surface:    #F2F8F7;   /*  Frost light. 10.81:1 under --ink. */
  --line:       #D6E3E3;   /*  DERIVED: no logo source. UI rule tone only,
                               never a text or icon colour. */

  /* CYAN RULE, as measured against the logo's #12D6E8:
       --cyan on --ink        6.53:1   text permitted, including body
       --cyan on --teal-deep  4.48:1   GRAPHIC ONLY — misses 4.5 for text
       --cyan on white        1.78:1   FORBIDDEN
       --cyan on --surface    1.66:1   FORBIDDEN
     Cyan never appears on --white or --surface. Max 4 occurrences per
     viewport; the designed budget is normally 1. Cyan is never a button
     fill, a heading colour, a link colour, or an underline. */

  /* ========================================================================
     DERIVED GROUNDS — no new hues; grounds and rules only.
     ======================================================================== */

  --white:              #FFFFFF;
  --on-ink:             var(--surface);           /* 10.81:1 on --ink */
  --rule-on-ink:        rgba(242, 248, 247, 0.18);   /* --surface at 18% */
  --rule-on-ink-strong: rgba(242, 248, 247, 0.40);   /* --surface at 40% */

  /* Dialog / drawer scrim. --ink at 60%. Declared here rather than composed
     in components.css so the two files stay free of raw values.
     ::backdrop inherits from the originating element in current engines, so
     var() resolves there; the .cart__scrim fallback covers the rest. */
  --scrim:              rgba(18, 63, 63, 0.60);

  /* ========================================================================
     GROUND SYSTEM — every component reads these, never a brand token
     directly. .band--ink / .band--surface reassign them, so no component
     needs to know what ground it has landed on.
     Default state below = the WHITE reading ground.
     ======================================================================== */

  --ground:      var(--white);
  --fg:          var(--ink);
  --fg-mute:     var(--teal-deep);   /* 7.97 on white, 7.36 on surface */
  --fg-accent:   var(--teal);
  --link:        var(--teal);
  --rule:        var(--line);
  --rule-strong: var(--teal-deep);
  --focus-c:     var(--teal);

  /* ========================================================================
     GRADIENT — the logo's own three-stop ramp, exactly as it appears in
     assets/logos/svg/mark-colour.svg:
         #123F3F 0%  →  #2C7375 52%  →  #62B4B9 100%
     Twice per page maximum. Allowed: hero ground, primary button, one
     signature rule. Forbidden: body text, cards, icons, full-section grounds.

     The SVG paints this ramp along (8,20)→(116,100), which is ~126.5deg in
     CSS terms. 135deg is kept because the angle is a layout decision stated
     as a rule in CLAUDE.md, not a sampled brand value — the STOPS are what
     the logo defines, and those are now exact.
     ======================================================================== */

  --gradient: linear-gradient(
    135deg,
    var(--ink) 0%,
    var(--teal) 52%,
    var(--teal-light) 100%
  );

  /* --gradient-btn is the FIRST HALF of the same ramp — stop 0 to stop 52%,
     same axis, no invented colour. Reason: white on the ramp's light
     terminal is 2.40:1 and fails AA badly. White across this range is
     11.61:1 → 5.50:1 and passes at both ends. The full-range --gradient is
     used only where nothing sits on it. */
  --gradient-btn: linear-gradient(135deg, var(--ink), var(--teal));

  /* ========================================================================
     TYPE
     ======================================================================== */

  --font-display: "Jost", "Poppins", system-ui, sans-serif;
  --font-body:    "Inter", system-ui, -apple-system, sans-serif;

  --fs-micro:    0.75rem;    /* 12 */
  --fs-fine:     0.875rem;   /* 14 */
  --fs-base:     1rem;       /* 16 */
  --fs-lede:     1.125rem;   /* 18 */
  --fs-sub:      1.375rem;   /* 22 */
  --fs-head:     1.75rem;    /* 28 */
  --fs-title:    2.25rem;    /* 36 */
  --fs-masthead: 3rem;       /* 48 */

  --leading-micro:    1.4;
  --leading-fine:     1.5;
  --leading-base:     1.65;   /* the rhythm unit derives from this */
  --leading-lede:     1.55;
  --leading-sub:      1.3;
  --leading-head:     1.2;
  --leading-title:    1.15;
  --leading-masthead: 1.1;

  --track-caps: 0.14em;
  --weight-body: 400;
  --weight-bold: 600;
  --weight-caps: 500;

  /* ========================================================================
     RHYTHM — the unit is the body line, not eight pixels.
     1rem * 1.65 = 1.65rem = 26.4px. All vertical space is a count of lines.
     Fractional pixels are accepted deliberately (design-system.md §3.1).
     ======================================================================== */

  --lh:    1.65rem;     /* 1 line  — 26.4px */
  --sp-q:  0.4125rem;   /* ¼ line  —  6.6px */
  --sp-h:  0.825rem;    /* ½ line  — 13.2px */
  --sp-1:  1.65rem;     /* 1 line  — 26.4px */
  --sp-1h: 2.475rem;    /* 1½ line — 39.6px */
  --sp-2:  3.3rem;      /* 2 lines — 52.8px */
  --sp-3:  4.95rem;     /* 3 lines — 79.2px */
  --sp-4:  6.6rem;      /* 4 lines — 105.6px */
  --sp-6:  9.9rem;      /* 6 lines — 158.4px */

  /* ========================================================================
     METRICS
     ======================================================================== */

  --hair:         1px;
  --rule-section: 2px;
  --rule-sig:     6px;

  --wrap:       72rem;       /* 1152 — outer measure */
  --measure:    66ch;        /* prose column */
  --gutter:     7rem;        /* accession column, >=64rem — holds "01" */
  /* The marginal column holds a SENTENCE, not a number. At 7rem it set one
     or two words per line and was unreadable; 13rem gives roughly 26
     characters, which is tight but workable for a marginal note. */
  --gutter-note: 13rem;
  --masthead-h: var(--sp-3); /* identical on all seven pages */
  --tap:        44px;        /* minimum target */
  --radius:     0;           /* there are no rounded boxes on this site */

  --cart-w:     30rem;
  --config-w:   40rem;
  --stepper-w:  4ch;

  /* Breakpoints exist as documentation only — CSS cannot use a var() in a
     media query. Keep these in sync with the @media rules by hand.
       48rem  768   table -> stacked entry, single -> two column
       64rem  1024  manifest grid gains its gutters
     ======================================================================== */

  /* ========================================================================
     MOTIF — n equal strokes on a fixed pitch, one chip offset on the last.
     n ∈ {3, 15}. The 3-stroke form is a crop of the 15-stroke form.
     ======================================================================== */

  --motif-hair: 2px;
  --motif-gap:  0.375rem;    /* pitch — 6px */
  --motif-h:    var(--lh);   /* one line tall */
  --motif-chip: 0.3125rem;   /* 5px */
  --motif-n:    3;

  /* centre the chip on the final stroke */
  --motif-chip-nudge: calc((var(--motif-chip) - var(--motif-hair)) / -2);
  /* distance from the container's right edge, for the right-anchored form */
  --motif-chip-inset: calc(
    var(--motif-gap) - var(--motif-chip) / 2 - var(--motif-hair) / 2
  );
  /* width that fully contains the overhanging chip */
  --motif-w: calc(
    var(--motif-gap) * 2 + var(--motif-hair) / 2 + var(--motif-chip) / 2
  );

  --motif-stroke-c: var(--teal);
  --motif-chip-c:   var(--teal-deep);

  /* ========================================================================
     MOTION — every transition is wrapped in prefers-reduced-motion.
     Two families, and they are not interchangeable:

       RESPONSE  (--dur-fast / --dur with --ease) — the page answering a
                 pointer or a key. Under 200ms, because anything slower
                 reads as lag rather than as feedback.
       ENTRANCE  (--dur-slow with --ease-entrance) — a section arriving
                 once, on scroll. Slower and heavily decelerated, so it
                 settles instead of sliding; it runs one time per element
                 and is never reversed on scroll-up.

     The reduced-motion contract is in components.css §12: neither family
     has a "fast version". Under `reduce`, the entrance layer does not
     exist and the response layer becomes an instant state change, so every
     affordance survives with nothing left moving.
     ======================================================================== */

  --dur-fast: 160ms;
  --dur:      200ms;   /* hover micro-interactions stay under 200ms */
  --ease:     cubic-bezier(0.2, 0, 0.2, 1);

  --dur-slow:      520ms;
  --ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);

  /* The entrance travel is half a line — the rhythm unit, not a round
     number of pixels. Far enough to read as arrival, short enough that a
     reader who scrolls fast never sees a section still in flight. */
  --reveal-rise: var(--sp-h);

  /* Press displacement. One device pixel: the button acknowledges the
     pointer without the label leaving its baseline grid. */
  --press: 1px;

  /* ========================================================================
     LAYERS
     ======================================================================== */

  --z-masthead: 100;
  --z-cart-tab: 200;
  --z-scrim:    300;
  --z-drawer:   310;
  --z-dialog:   400;
}
