/* =============================================================
   Design tokens — JSHW

   THIS IS A REFACTOR, NOT A REDESIGN.

   Every value below must be EXTRACTED from the current rendered
   site, not invented. Procedure:

     1. Open the live site in DevTools.
     2. Read computed values for each role below.
     3. Paste the real value in, delete the TODO.
     4. Where the extracted colour fails 4.5:1 contrast, adjust it
        and record the change in docs/AUDIT.md. That is the only
        legitimate reason for a value to differ from the original.

   No hex value may appear anywhere outside this file.
   ============================================================= */

/* =============================================================
   Webfonts — solved 27 Aug 2026 (AUDIT.md TOK-5).

   Named but never loaded until now: --font-display/--font-body below
   named Roboto/Lato, but there was no @font-face, no font files, and
   the <link> in base.njk was commented out — everyone got a system-ui
   fallback, which didn't match the live site's actual typography.

   Self-hosted, not loaded from Google's CDN, because the CDN would
   have added a live third-party request on every page load —
   contradicting the "no fonts, scripts or embeds from other
   companies" claim on /privacy/ the moment it shipped. Self-hosting
   keeps that claim true: these two files are the only network
   requests either font ever makes, both same-origin.

   Files sourced from fonts.gstatic.com, latin subset only — the
   language subsets Google also serves (cyrillic, greek, vietnamese,
   math, symbols) aren't needed for this site's English/Afrikaans
   copy, and shipping them would be exactly the unused-weight bloat
   BASELINE.md §4 flags builder output for. Both are SIL Open Font
   License 1.1, confirmed by reading it directly out of each file's
   own name table (fontTools), not assumed — full text and
   attribution in src/assets/fonts/OFL.txt, which travels with the
   files as the license requires.

   Roboto is a variable font (wght axis 100–900) — ONE file
   legitimately covers the 400/500/700 below; Google's own generated
   CSS serves the identical file for all three weight declarations,
   confirmed by diffing the URLs. Lato only ships weight 400 from
   Google at all; there is no font-weight: 200 file to load, which is
   the other half of the bug this replaces (see --weight-normal below).
   ============================================================= */

@font-face {
  font-family: Roboto;
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/roboto-latin-variable.woff2") format("woff2");
  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: Lato;
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/lato-latin-400.woff2") format("woff2");
  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;
}

:root {
  /* --- Colour: roles, not names.
         "brand-strong" survives a rebrand; "dark-blue" does not.

         Extracted 26 Aug 2026 from computed styles on live xyz.jshw.xyz
         (see docs/BASELINE.md §1). Two values deviate from what was
         measured — both logged in docs/AUDIT.md as new findings:

           - --color-text-muted: live site computes an effective ~#929292
             (rgba(87,87,87,.624) over the page background), which is
             2.86:1 on --color-bg — fails WCAG 4.5:1. Darkened per the
             rule in this file's own header comment.
           - --color-focus: not extracted — the live site has no visible
             focus style to extract. Needs a value that stays ≥3:1 on
             BOTH --color-bg and --color-brand-strong (used as the
             footer background), which no single flat colour on this
             palette actually achieves well. Flagged for the manual
             accessibility pass (ACCESSIBILITY-TEST-SCRIPT.md) rather
             than guessed here. --- */
  --color-bg:            #f5f5f5;
  --color-bg-subtle:     #f0f2f1;
  --color-surface:       /* TODO extract — no distinct card/surface element observed yet; revisit once a page with cards is built */ #fff;
  --color-text:          #1a1a1a;
  --color-text-muted:    /* adjusted for contrast — see comment above */ #595959;
  --color-text-inverse:  #f5f5f5;
  --color-brand:         #cead36;
  --color-brand-strong:  #1c2b39;
  --color-accent:        #cead36;
  --color-border:        #cacccb;

  /* Focus indicator — solved 26 Aug 2026 (AUDIT.md TOK-3). No single flat
     colour in this palette clears 3:1 (WCAG 1.4.11) against both the page
     background AND --color-brand-strong (used as the footer background):
     the maths only leaves a mid-grey band (luminance 0.17–0.27) as a
     colour that passes both, and a mid-grey focus ring is weak and off-
     brand. Using a two-ring halo instead — an inner ring in brand-strong
     navy, an outer ring in text-inverse — reusing the two colours already
     validated elsewhere in this file. On a light background the navy ring
     provides ~13:1 contrast and the halo is invisible (blends into the
     page). On the dark navy footer the navy ring blends in, but the halo
     provides that same ~13:1 contrast instead. Every real background in
     this palette has at least one visible ring. See the *:focus-visible
     rule below. Still needs the manual pass in ACCESSIBILITY-TEST-SCRIPT.md
     once real pages exist to test it on — the maths says it works, a
     screen and a keyboard should confirm it. */
  --color-focus:         var(--color-brand-strong);
  --color-focus-halo:    var(--color-text-inverse);
  --color-error:         /* first real use: /insights/ report chips and data tables (status "fail") */ #b3261e;
  --color-success:       /* first real use: /insights/ report chips and data tables (status "pass") */ #1b5e20;

  /* Third semantic tone the error/success pair never needed until now —
     /insights/ report chips and tables have a genuine third status
     ("open"/"in progress"), not just pass/fail. Same darkened-for-AA-text
     treatment as the two above: a legible ink derived from --color-brand
     (#cead36) rather than the raw accent, which is too light for body
     text on --color-bg. */
  --color-warning:       #7a6414;

  /* --- Type ---
         Extracted 26 Aug 2026, actually loaded 27 Aug 2026 (TOK-5,
         @font-face block above). The live site requests Lato at
         font-weight 200 for body copy, but Google Fonts only serves
         Lato at 400 — no font file for 200 exists to load, so the
         live site's "200" was always a browser-synthesised fake-thin
         weight, never a real design decision. Using the weight that
         actually exists: 400. --- */
  --font-display: "Roboto", system-ui, sans-serif;
  --font-body:    "Lato", system-ui, sans-serif;

  /* Fluid scale. Every clamp() has a rem min and a rem max so it
     still responds to the user's browser font size (WCAG 1.4.4). */
  --text-xs:   clamp(0.75rem, 0.72rem + 0.15vw, 0.8125rem);
  --text-sm:   clamp(0.875rem, 0.85rem + 0.15vw, 0.9375rem);
  --text-base: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
  --text-lg:   clamp(1.125rem, 1.05rem + 0.4vw, 1.375rem);
  --text-xl:   clamp(1.375rem, 1.2rem + 0.8vw, 1.875rem);
  --text-2xl:  clamp(1.75rem, 1.4rem + 1.6vw, 2.75rem);
  --text-3xl:  clamp(2.25rem, 1.6rem + 3vw, 4rem);
  --leading-tight: 1.15;
  --leading-body:  1.6;   /* 1.5 minimum for WCAG 1.4.12 */
  --weight-normal: 400;

  /* Corrected 27 Aug 2026, alongside solving TOK-5. Extracted as 600 from
     h2's computed style, but Roboto only ships 100/300/400/500/700/900 —
     600 was never a real, loadable weight, just like the Lato 200 finding
     above. Now that the real variable font is loaded, asking for 600 would
     make the browser synthesise a fake weight again — exactly the bug this
     was meant to fix. 500 is the true Roboto weight closest to what was
     extracted. */
  --weight-medium: 500;
  --weight-bold:   700;

  /* --- Space: 4px base --- */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs:  0.75rem;
  --space-s:   1rem;
  --space-m:   1.5rem;
  --space-l:   2rem;
  --space-xl:  3rem;
  --space-2xl: 4rem;
  --space-3xl: 6rem;

  /* --- Layout --- */
  --measure: 65ch;              /* reading width */
  --container-max: 75rem;
  --container-gutter: var(--space-m);

  /* --- Interaction --- */
  --radius-s: /* TODO extract — no small-radius element (card/input) observed yet */ 4px;
  --radius-m: /* TODO extract — no medium-radius element observed yet */ 8px;
  --radius-pill: /* extracted from live buttons/links (64px, i.e. fully round) */ 999px;

  /* Organic "blob" masks for photography, extracted 27 Aug 2026 from real
     figures on the live site (docs/AUDIT.md section I) — SiteJet's
     distinctive treatment for photos, each corner a different radius %.
     Two real samples found; using both for variety rather than repeating
     one shape on every image. */
  --radius-organic-1: 50% 40% 80% 30%;  /* live Collaboration page */
  --radius-organic-2: 20% 20% 20% 10%;  /* live Impact page */
  --target-min: 24px;           /* WCAG 2.2 SC 2.5.8 minimum */
  --target-comfortable: 44px;   /* what to actually aim for */
  --focus-width: 3px;
  --focus-offset: 2px;
  --shadow-s: /* TODO extract */ 0 1px 2px rgb(0 0 0 / 8%);
  --shadow-m: /* TODO extract */ 0 4px 12px rgb(0 0 0 / 10%);
  --duration-fast: 120ms;
  --duration-base: 200ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
}

/* Motion is opt-out at the token level, so no component has to remember. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 0.01ms;
    --duration-base: 0.01ms;
  }
}

/* -------------------------------------------------------------
   Non-negotiable base rules. Do not override these per component.
   ------------------------------------------------------------- */

*:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);

  /* The halo ring — see --color-focus-halo comment above. */
  box-shadow: 0 0 0 calc(var(--focus-width) + var(--focus-offset) * 2) var(--color-focus-halo);
}

/* The live site has a working skip link. Keep it working:
   off-screen until focused, then visible and in front of everything. */
.skip-link {
  position: absolute;
  top: var(--space-2xs);
  left: var(--space-2xs);
  z-index: 999;
  padding: var(--space-2xs) var(--space-s);
  background: var(--color-surface);
  color: var(--color-text);
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Reserve space for media so nothing shifts on load (CLS). */
img, svg, video, iframe {
  max-width: 100%;
  height: auto;
}
