/* IGRM design tokens — the single source of truth for both sheets.
 *
 * WHY THIS FILE EXISTS, twice over.
 *
 * Design: the site had thirteen distinct font sizes (11.2, 11.52, 12,
 * 12.48, 12.8, 13.6, 14.08, 14.4, 16, 19.2, 21.6, 27.2, 80px). Values
 * like 11.52 and 14.08 are not choices, they are nested em compounding
 * — a scale that happened rather than a scale that was decided. An
 * information-dense instrument needs a small deliberate ramp so that
 * size means rank, everywhere, on every page.
 *
 * Structure: style.css (homepage) and site.css (nineteen subpages) each
 * declared the same palette independently, under a comment reading
 * "Keep the two sheets' values in step." That discipline had already
 * failed once — the homepage palette names resolved empty in site.css
 * on 2026-07-31, which crashed the gap chart's gradient and greyed
 * every other chart on the page. Two hand-synchronised copies of one
 * palette is a defect waiting for the next edit, so there is now one
 * copy and both sheets import it.
 *
 * Character: dark, calm, technical. Night is the committed default;
 * light is a persisted choice and must stay equally legible. The
 * reference points are FT data journalism, the Economist's data pages,
 * and FRED.
 *
 * This header used to read "Nothing here is decorative — no gradients,
 * no glass, no glow", and that was simply FALSE about the file it sits
 * on top of. The site ships a blurred gradient field, a glass masthead,
 * a glow on the headline number and gradient-clipped eyebrow text — an
 * "Elevation v1.2, the cinematic pass" landed on 2026-08-04 and the
 * sentence was never revisited. A rule the code does not follow is not
 * a strict rule, it is a lie that makes the next reader distrust the
 * rules that ARE real. The actual rule:
 *
 *   Atmosphere may sit BEHIND the data or AROUND it. Nothing that
 *   carries a number may animate indefinitely, and no effect may change
 *   a value's apparent magnitude.
 *
 * So the background field (z-index -1, no information) may drift
 * forever; the band tick, which shows where today's value sits between
 * calm and severe, may not — it used to pulse on a 3.2s loop and now
 * arrives once. Gradients that encode a scale are legends, not
 * decoration. `test_only_the_background_field_animates_forever` is the
 * enforceable half of this.
 */

:root {
  /* ---- Palette: night (default) ------------------------------------
   * Deepened and desaturated from the previous #0E141E/#26303F. The old
   * rule colour was heavy enough that tables read as grids first and
   * data second; rules should be felt, not seen. */
  --paper:      #0B1017;   /* page ground */
  --surface:    #121924;   /* cards, raised blocks */
  --ink:        #E6E4DC;   /* primary text */
  --muted:      #8C95A4;   /* secondary text, captions */
  --faint:      #7A8492;   /* tertiary: axis labels, fine print.
                            * 5.04:1 on --paper. The first value here
                            * was #626C7C at 3.59:1 -- AA-large only,
                            * which is NOT AA for the small text this
                            * token exists to colour. Measured before
                            * anything used it. */
  --rule:       #1F2833;   /* borders, table rules */
  --rule-soft:  #161E29;   /* hairlines inside dense tables */

  /* Identity accent. Terracotta is the project's colour and links wear
   * it too. A separate --link tone was defined and then removed: it
   * would have recoloured every link on the site, which is a rebrand,
   * and the brief asks to improve the execution rather than rebrand. */
  --accent:     #D96548;
  --accent-dim: rgba(217, 101, 72, 0.14);

  /* State: calm / elevated / severe. Data-driven, the only place
   * colour carries meaning about the world. */
  --calm:       #3FA697;
  --elevated:   #D9A43B;
  --severe:     #D96548;
  --state:      var(--calm);
  --ok:         var(--calm);
  --bad:        var(--severe);

  /* Channel series. Five hues that differ in BOTH hue and lightness, so
   * they survive greyscale printing and the common colour-vision
   * deficiencies — a legend that only works for trichromats is not a
   * legend. Desaturated on purpose: five saturated lines on a dark
   * ground is a fairground, not an instrument. */
  --ch-pakistan-west: #E08464;
  --ch-china-east:    #7FB6A1;
  --ch-gulf-energy:   #E0B75E;
  --ch-us-trade:      #7FA9D6;
  --ch-shipping:      #A98BC4;

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.34), 0 10px 32px rgba(0, 0, 0, 0.38);

  /* ---- Type scale --------------------------------------------------
   * Seven steps plus a display size. Every rule in both sheets should
   * name one of these; a literal font-size outside this ramp is a bug
   * report about the ramp. */
  --fs-micro:   0.6875rem;  /* 11px — table headers, eyebrows */
  --fs-fine:    0.75rem;    /* 12px — fine print, footnotes */
  --fs-small:   0.8125rem;  /* 13px — captions, chart notes */
  --fs-dense:   0.875rem;   /* 14px — table cells, dense UI */
  --fs-body:    1rem;       /* 16px — reading prose */
  --fs-lead:    1.125rem;   /* 18px — standfirst, section leads */
  --fs-h2:      1.25rem;    /* 20px — section heads */
  --fs-stat:    1.5rem;     /* 24px — inline stat numbers (cards, rows) */
  --fs-h1:      1.75rem;    /* 28px — page title */
  --fs-display-sm: 4rem;    /* 64px — subpage headline number */

  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --lh-body:  1.6;   /* the one line-height the sheets name */

  /* ---- Spacing -----------------------------------------------------
   * NOT systematised, deliberately, and this note exists so nobody
   * mistakes that for an oversight.
   *
   * A --sp-1..--sp-16 ramp shipped here on 2026-08-07 and was used
   * exactly zero times, while DESIGN_SYSTEM.md claimed the system had a
   * "spacing rhythm". That is the same promise-without-a-fact this
   * project spent the day removing elsewhere, so the dead tokens are
   * gone rather than left to look like rigour.
   *
   * Migrating ~50 padding/margin literals across two live stylesheets
   * is a real regression risk, verified only at two viewports, for a
   * modest gain: the existing values already cluster on a 2px grid and
   * spacing does not carry meaning the way size carries rank. When it
   * is done it should be done as its own change, measured page by page.
   */
  --radius:      6px;

  /* ---- Motion ------------------------------------------------------
   * Calm means short and linear-ish. Nothing bounces, nothing springs,
   * nothing announces itself. Durations are named by intent so a future
   * edit changes meaning rather than a magic number. */
  --dur-instant: 90ms;   /* press feedback, hover tint */
  --dur-quick:   160ms;  /* colour and border changes */
  --dur-calm:    260ms;  /* size, padding, layout settle */
  --dur-slow:    420ms;  /* deliberate reveals, the band tick arriving */
  --dur-ambient: 32s;    /* the one background loop; see below */

  /* One easing, and it does NOT overshoot. The sheets used
   * cubic-bezier(0.22, 1, 0.36, 1), which rises past its target and
   * settles back — a bounce. On an instrument that reads as the number
   * wobbling, which is the opposite of calm. This curve decelerates
   * into place and stops. */
  --ease:        cubic-bezier(0.2, 0, 0.2, 1);

  /* TWO named exceptions, and they are exceptions because the default
   * curve would be WRONG here, not because it was inconvenient.
   *
   * --ease-linear: the reading-progress bar. A progress indicator is a
   *   position readout, so an eased one reports a position the reader
   *   is not at. Linear is the honest curve for a measurement.
   *
   * --ease-loop: the ambient background drift, which alternates
   *   forever. An asymmetric curve reverses at full speed at each
   *   turnaround and visibly snaps; a symmetric one arrives at rest.
   *   Only an infinite alternating animation may use this.
   *
   * Anything else must use --ease. `test_one_easing` enforces it and
   * names these two, so a third exception has to be argued for in the
   * test rather than added quietly to a rule. */
  --ease-linear: linear;
  --ease-loop:   ease-in-out;
}

:root[data-theme="light"] {
  --paper:      #FAFAF7;
  --surface:    #FFFFFF;
  --ink:        #12233D;
  --muted:      #5C6675;
  --faint:      #666E7C;   /* 4.91:1 on light paper, same reason */
  --rule:       #D8D6CE;
  --rule-soft:  #E7E5DD;

  --accent:     #A2361F;
  --accent-dim: rgba(162, 54, 31, 0.10);

  --calm:       #1E6E67;
  --elevated:   #B07C1F;
  --severe:     #A2361F;

  --ch-pakistan-west: #B5502F;
  --ch-china-east:    #2F7A63;
  --ch-gulf-energy:   #96701A;
  --ch-us-trade:      #2C5C8F;
  --ch-shipping:      #6B4C8A;

  --shadow: 0 1px 2px rgba(18, 35, 61, 0.04), 0 8px 28px rgba(18, 35, 61, 0.06);
}

/* Honour the system preference for anyone who has never touched the
 * toggle. The inline script in each page sets data-theme from
 * localStorage; this covers first visits only, and the explicit
 * attribute always wins because it is more specific. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --paper: #FAFAF7; --surface: #FFFFFF;    --ink: #12233D; --muted: #5C6675; --faint: #666E7C;
    --rule: #D8D6CE; --rule-soft: #E7E5DD;
    --accent: #A2361F; --accent-dim: rgba(162, 54, 31, 0.10);
    --calm: #1E6E67; --elevated: #B07C1F; --severe: #A2361F;
    --ch-pakistan-west: #B5502F; --ch-china-east: #2F7A63;
    --ch-gulf-energy: #96701A; --ch-us-trade: #2C5C8F;
    --ch-shipping: #6B4C8A;
  }
}

/* Motion is a courtesy, never a requirement. Anyone who has asked their
 * system for less of it gets none: no transitions, no smooth scroll,
 * no reveals. The page must be fully usable and identical in content. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-instant: 0ms;
    --dur-quick:   0ms;
    --dur-calm:    0ms;
    /* --dur-slow was missing here for a day. The catch-all below hid it,
     * which is exactly why it went unnoticed: a rule that works by
     * accident looks identical to one that works on purpose until the
     * accident stops. Every duration in the scale is listed, and a test
     * now compares the two lists rather than trusting this comment. */
    --dur-slow:    0ms;
    --dur-ambient: 0s;
  }
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    /* Delays too. Zeroing the duration of a staggered reveal still
     * leaves the element waiting its turn to appear, so the page
     * assembles itself in silence instead of being present. */
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
  }
}

/* ---- Motion primitives -----------------------------------------------
 * These live here for the same reason the palette does. `igrm-drift` and
 * `igrm-pagein` were each defined TWICE -- once in style.css and once in
 * site.css -- under no comment at all. That is the failure that broke the
 * gap chart on 2026-07-31 (two copies of the palette, one edited), moved
 * one dimension over: edit the homepage's copy of a keyframe and the
 * other nineteen pages keep the old one, silently, forever.
 *
 * Both sheets @import this file, so a keyframe defined here is available
 * to every page and exists exactly once.
 */

/* Page and section entrance. 8px is deliberate: far enough to read as
 * arrival, short enough that nothing appears to travel. */
@keyframes igrm-pagein { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes igrm-rise   { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* The one ambient loop, on the background field behind everything. */
@keyframes igrm-drift {
  from { transform: translate3d(-3%, -2%, 0) rotate(-1deg) scale(1); }
  to   { transform: translate3d(3%, 3%, 0) rotate(1.5deg) scale(1.08); }
}

/* The band tick ARRIVES; it does not breathe.
 *
 * This was `tickpulse ... infinite`: a glow that pulsed forever on the
 * marker showing where today's value sits on the calm-to-severe scale.
 * The single loudest element on the page was a permanent animation
 * attached to the reading itself -- on a phone, a compositor job that
 * never ends. Motion on data has to MEAN something, and "still here"
 * is not a finding. Now it settles into place once, on load, and stops.
 */
@keyframes igrm-tick-arrive {
  from { transform: translateX(-6px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
