/* =============================================================
   Warm adaptive theme: Direction D.
   One design language, two skins: a bright warm-light skin for
   daytime (Direction C) and a calm warm-dark skin for the evening
   (Direction A). theme.js flips the skin on London sunrise/sunset,
   with a manual override toggle in the nav.

   Every surface colour in the app is a variable defined here, so a
   page only needs to reference var(--...) and it themes for free.
   Dark is the default (:root); light overrides under [data-theme="light"].

   Pure CSS, no build step. Tune the whole app's palette from here.
   ============================================================= */

/* ---- Brand typeface: Hanken Grotesk, self-hosted ----
   Hanken Grotesk is (c) 2021 The Hanken Grotesk Project Authors and licensed
   under the SIL Open Font License 1.1, which allows self-hosting and
   redistribution; the full licence ships beside the files as /fonts/OFL.txt.
   The woff2 files are Google Fonts' own variable subsets (v12, weight axis
   100 to 900), fetched from its CSS2 API and served byte for byte.

   Until 23/09/2026 this was an @import of Google Fonts' stylesheet, a render
   blocking chain (HTML, then this sheet, then Google's CSS, then the font,
   across two extra origins) measured at 0.8 s on every page on mobile. Each
   page now preloads the latin file, so it arrives alongside this sheet.

   The url()s are absolute: a relative one resolves against the sheet, which
   is fine here, but the same text pasted into a page's inline <style> would
   break under a nested path. The filename carries the upstream version, so a
   new font is a new URL and the long cache in _headers never serves stale.
   latin-ext only downloads when a page actually draws one of its characters
   (a place name on the map, say). */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/hanken-grotesk-latin-ext-v12.woff2") format("woff2");
  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;
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/hanken-grotesk-latin-v12.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;
}

/* ---- Structural tokens: the same in both skins ----
   Any spacing, size, radius or duration not on these scales is a bug. */
:root {
  /* Spacing, 4 px base. Inline gap 1, list rows 2, grid gap 3, card padding
     4 (phone) or 5, between sections 6, page foot 12. Page side padding
     stays 16 px (layout.css). */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-12: 48px;

  /* Type ramp. Hanken Grotesk only, seven sizes, and 11 px is the floor:
     nothing on any page renders smaller than --fs-label. */
  --fs-display: clamp(32px, 1.6vw + 24px, 44px); /* hero figure, one per card at most */
  --fs-kpi:     clamp(24px, 0.8vw + 20px, 30px); /* KPI band values */
  --fs-title:   22px;   /* page title */
  --fs-head:    17px;   /* card answer sentence, list primary */
  --fs-body:    14px;   /* body and list rows (15 px on phones, below) */
  --fs-meta:    12.5px; /* captions, sub-lines, data age */
  --fs-label:   11px;   /* eyebrows, legends, axis labels: the floor */
  --tracking-label: 0.08em;

  /* Radius. Nested: inner = outer minus padding, never below --r-sm. */
  --r-xs: 3px;    /* pixel cells, bar ends */
  --r-sm: 8px;    /* chips, inputs, small pills, tooltip */
  --r-md: 12px;   /* buttons, icon-btn, inner panels */
  --r-lg: 18px;   /* .card */
  --r-pill: 999px;

  /* Motion. --spring is for tactile feedback only (tap-spark, press), never
     on data. */
  --dur-1: 120ms; /* press, hover, colour */
  --dur-2: 200ms; /* toggles, segment switch, disclosure */
  --dur-3: 320ms; /* skeleton to content crossfade, sheet enter */
  --dur-4: 600ms; /* number tween, chart first draw (the maximum) */
  --ease-premium: cubic-bezier(0.16, 1, 0.3, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: var(--ease-premium);            /* enter, tween */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* view switch */

  --font-sans: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Numbers + micro-labels use Hanken too (with tabular-nums for column
     alignment) so a big Hanken figure and its mono neighbour no longer clash.
     Was ui-monospace; kept the --font-mono name so existing var(--font-mono)
     references all switch in one place. */
  --font-mono: var(--font-sans);
}
@media (max-width: 559px) {
  :root { --fs-body: 15px; }
}

/* ---- Colour tokens ----
   Every text colour clears WCAG AA (4.5:1) on --bg, --card and --card-2 in
   both skins, and scripts/lf-contrast.test.mjs recomputes the ratios from
   this file on every run. The old tertiary failed in both skins (3.85:1 on a
   light card, 3.16:1 on a dark one) and the old light --warning sat at 3.89.
   Hierarchy between secondary and tertiary now comes from size and weight,
   not from a contrast gap. Tertiary is for meta and label text, never body.
   Light tertiary is #716858, a shade under the audit's #746B5B, because that
   one read 4.36:1 on the nav bar (--bar-bg) where the tab labels sit.

   --accent is for marks and fills that carry no text (3:1 is enough for
   graphics). Accent text, filled controls with text on them and the active
   nav chip use --accent-strong, which carries body-size text. */
:root {
  /* calm warm dark (evening) */
  --bg:            #16150F;
  --text-primary:  #ECE8DF;
  --text-secondary:#ADA894;
  --text-tertiary: #948F7D;
  --card:          rgba(255,255,255,0.035);
  --card-2:        rgba(255,255,255,0.06);
  --border:        rgba(255,255,255,0.08);
  --border-soft:   rgba(255,255,255,0.055);
  --accent:        #C8795D;
  --accent-strong: #D88A6E;
  --accent-ink:    #34130A;
  --accent-soft:   rgba(200,121,93,0.15);
  --focus:         #D88A6E;
  --success:       #A6C0A8;
  --success-soft:  rgba(143,166,142,0.13);
  --warning:       #E0B36A;
  --danger:        #E08A8A;
  --info:          #7FA7CF;
  /* Status tints, 12 % of the status colour over whatever surface is below. */
  --warning-soft:  color-mix(in srgb, var(--warning) 12%, transparent);
  --danger-soft:   color-mix(in srgb, var(--danger) 12%, transparent);
  --info-soft:     color-mix(in srgb, var(--info) 12%, transparent);
  /* Elevation, four levels: e0 the page (--bg), e1 a card (--card,
     --shadow-1), e2 a popover, tooltip or sticky bar (--surface-raised,
     --shadow-2), e3 a modal or sheet (--surface-overlay, --shadow-3, --scrim).
     Dark is lifted by a lighter surface and a brighter hairline, not by
     shadow: a black shadow on a warm near-black only muddies it. The "none"
     shadows are written as a zero transparent shadow so they stay valid
     inside a comma list (box-shadow: var(--shadow-1), inset ...).
     --shadow is the legacy single level. Cards moved to --shadow-1; the
     floating elements still on --shadow keep it until they move to e2. */
  --shadow:        0 8px 24px rgba(0,0,0,0.30);
  --shadow-1:      0 0 #0000;
  --shadow-2:      0 0 #0000;
  --shadow-3:      0 0 #0000;
  --surface-raised:  color-mix(in srgb, #fff 9%, var(--bg));  /* e2: popover, tooltip, map detail, sticky nav */
  --surface-overlay: color-mix(in srgb, #fff 12%, var(--bg)); /* e3: modal, sheet */
  --border-raised:   rgba(255,255,255,0.12);
  --scrim:           rgba(0,0,0,0.60);
  --bar-bg:        #100F0A;
}

:root[data-theme="light"] {
  /* daylight warm (daytime / office monitor) */
  --bg:            #F3F1EC;
  --text-primary:  #1F1B14;
  --text-secondary:#5E574A;
  --text-tertiary: #716858;
  --card:          #FFFFFF;
  --card-2:        #FBFAF6;
  --border:        rgba(0,0,0,0.09);
  --border-soft:   rgba(0,0,0,0.06);
  --accent:        #D85A30;
  --accent-strong: #B84A22;
  --accent-ink:    #FFFFFF;
  --accent-soft:   #FBE7DE;
  --focus:         #B84A22;
  --success:       #0F6E56;
  --success-soft:  #E1F5EE;
  --warning:       #8F5E0A;
  --danger:        #B23B3B;
  --info:          #2E6FA8;
  --warning-soft:  color-mix(in srgb, var(--warning) 10%, var(--card));
  --danger-soft:   color-mix(in srgb, var(--danger) 10%, var(--card));
  --info-soft:     color-mix(in srgb, var(--info) 10%, var(--card));
  --shadow:        0 1px 3px rgba(0,0,0,0.06), 0 10px 30px rgba(0,0,0,0.05);
  --shadow-1:      0 1px 2px rgba(0,0,0,0.05), 0 6px 20px rgba(0,0,0,0.04);
  --shadow-2:      0 4px 12px rgba(0,0,0,0.08), 0 16px 40px rgba(0,0,0,0.08);
  --shadow-3:      0 24px 64px rgba(0,0,0,0.18);
  --surface-raised:  #FFFFFF;
  --surface-overlay: #FFFFFF;
  --border-raised:   rgba(0,0,0,0.09);
  --scrim:           rgba(0,0,0,0.40);
  --bar-bg:        #ECEAE3;
}

/* Chart series and aliases. Declared once: each resolves against whichever
   skin is active. Series 1 to 4 in this order everywhere, so a colour means
   the same thing on every chart. --stale marks data that has stopped
   arriving: amber, never red. */
:root {
  --series-1: var(--accent);
  --series-2: var(--success);
  --series-3: var(--info);
  --series-4: var(--text-tertiary);
  --stale:    var(--warning);
  --stale-soft: var(--warning-soft);
}

/* Numbers line up by default: tabular, lining figures on every page, rather
   than 128 separate declarations. */
body { font-variant-numeric: tabular-nums lining-nums; }

/* Theme-switch transition: only on bulk surfaces, kept short so it
   reads as a gentle dawn/dusk rather than a flash. isolation makes body
   a stacking context so the .lf-atmos glow can sit at z-index:-1, above
   the body background but below every card on every page, without any
   per-page z-index lifting. */
body { transition: background-color .4s ease, color .4s ease; isolation: isolate; }

/* ---- Theme toggle button (injected into the nav by theme.js) ---- */
.lf-theme-toggle {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border-radius: 10px;
  color: var(--text-secondary);
  background: var(--card);
  border: 1px solid var(--border);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s, color .15s, border-color .15s;
}
.lf-theme-toggle:hover { color: var(--text-primary); border-color: var(--border-soft); }
.lf-theme-toggle svg { width: 17px; height: 17px; display: block; }
/* Touch: 40x36 to look at, level with the nav chips, with a 44x44 hit
   area from an invisible ::before (nav.js gives the row the room for it). */
@media (pointer: coarse) {
  .lf-theme-toggle { position: relative; width: 40px; height: 36px; }
  .lf-theme-toggle::before { content: ''; position: absolute; inset: -4px -2px; }
}
.lf-theme-toggle .lf-auto-dot {
  position: absolute; margin: -14px 0 0 13px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}

/* ---- Ambient backdrop layer ----
   Injected by nav.js on every page. Two soft radial glows, fixed behind
   all content, pointer-transparent so it never blocks taps. z-index:-1
   inside body's isolated stacking context paints it above the body
   background but below every card, even opaque light-skin ones.
   Static (no animation), themed for both skins. ---- */
.lf-atmos {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1100px 640px at 50% -8%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 62%),
    radial-gradient(900px 520px at 50% 108%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 60%);
}
:root[data-theme="light"] .lf-atmos {
  background:
    radial-gradient(1100px 640px at 50% -8%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 62%),
    radial-gradient(900px 520px at 50% 108%, color-mix(in srgb, var(--accent) 3%, transparent), transparent 60%);
}

/* No zoom on a phone (Louis, 24/09/2026). iOS zoomed in whenever a text box
   under 16px took focus, and in the home-screen app there was no easy way
   back out. maximum-scale=1 in every page's viewport stops that zoom;
   touch-action stops a double tap zooming too. */
html { touch-action: manipulation; }
