/* Assembly design tokens - the single source for every surface.
 *
 * Linked once from config/ui.ts `app.head.link`, so every route gets it without
 * repeating it. It is deliberately NOT in config/crosswind.ts `preflights`:
 * on the stx serve path `preflights` is spread into the generator config and
 * then never emitted, so a token placed there reaches no page at all.
 *
 * These names are the contract config/crosswind.ts maps its semantic utilities
 * onto - `bg-canvas`, `bg-panel`, `border-line`, `text-ink`, `text-muted`,
 * `text-subtle`, `text-accent`. Renaming one here silently breaks the matching
 * utility everywhere, because an undefined var() resolves to the property's
 * initial value rather than erroring.
 *
 * Both the media query and the [data-theme] attribute are covered. The pairing
 * is deliberate: the attribute is what stx's pre-paint boot script and
 * useColorMode write, and it has to beat the OS preference so an explicit
 * choice survives a reload.
 *
 * Every contrast ratio below was computed from these exact hex values against
 * these exact surfaces, not estimated. WCAG AA wants 4.5:1 for body text;
 * nothing here ships below 4.5:1. The chart series passed the dataviz
 * validator's six checks (lightness band, chroma floor, adjacent CVD
 * separation, normal-vision floor, contrast) in both modes.
 */

:root {
  /* Native UI - select popups, scrollbars, date pickers, the caret - is
   * painted by the browser from `color-scheme`, not from custom properties.
   * Declared in all four blocks and never once as `light dark` on :root,
   * because that means "either is fine, follow the OS", which is wrong the
   * moment someone picks a theme. */
  color-scheme: light;

  /* Warm paper canvas, true white panels. The warmth is the point: schools
   * read as institutional the moment the page goes cool grey, and the paper
   * tone keeps a printed-programme feel under the ivy accent. */
  --bg: #fbfaf7;
  --panel: #ffffff;
  --border: rgba(20, 24, 28, 0.10);

  /* 17.09:1 on --bg, 17.84:1 on --panel. */
  --text: #14181c;
  /* 7.63:1 on --bg, 7.96:1 on --panel. Body copy and secondary labels. */
  --text-2: #4a5160;
  /* 4.64:1 on --bg, 4.85:1 on --panel. The smallest step that still clears AA
   * on the worse surface, so "subtle" reads subtle without failing. */
  --text-3: #6a7280;

  /* Ivy. A campus green rather than framework blue, and dark enough to be
   * legible as link text and not only as a fill: 6.21:1 on --bg, 6.49:1 on
   * --panel. It is deliberately a different hue from --pos below, which is
   * lighter and always paired with a direction glyph, so "brand" and
   * "this number went up" never look like the same statement. */
  --accent: #1a6b47;
  --accent-soft: rgba(26, 107, 71, 0.08);
  /* Ink for text sitting ON a saturated fill. It cannot be a hardcoded #fff:
   * dark mode makes the fill lighter, where white measures 2.4:1. White on the
   * light accent is 6.49:1; the dark counterpart is 8.09:1 on its own fill. */
  --accent-ink: #ffffff;

  /* Status. Always paired with a glyph or a word in the UI, because colour
   * alone fails for the ~8% of men with a colour vision deficiency, and an
   * event roster cannot make "cancelled" invisible to them.
   * pos 4.81:1, neg 6.02:1, warn 4.81:1 on --bg. */
  --pos: #15803d;
  --neg: #be123c;
  --warn: #b45309;

  /* Outfit for display, Instrument Sans for text, JetBrains Mono for figures
   * so columns of counts and currency align on the digit.
   *
   * Sans display rather than the editorial serif this brief invites: a serif
   * headline is the reflex for anything "school" or "gala", and it lands the
   * product somewhere between a wedding invitation and a museum. Outfit is
   * warm enough for a school and plain enough for software. */
  --display: 'Outfit', ui-sans-serif, system-ui, sans-serif;
  --sans: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Chart series: five categorical colours plus a neutral for the folded tail.
   *
   * Computed, not chosen - run through the dataviz validator against this
   * exact surface. FIVE rather than six: six hues cannot clear the separation
   * floors inside dark mode's narrow lightness band without repeating a hue
   * family, and the standard's remedy is to cut series rather than invent one.
   * The sixth slot is neutral and carries the "Other" bucket.
   *
   * Green is absent on purpose. The accent is green, and a series that matches
   * the brand fill reads as "the highlighted one" in every chart it appears in.
   * The one adjacent pair in the 6-8 tritan band (gold against magenta) is
   * permitted ONLY with secondary encoding, which is why every multi-series
   * chart here ships a legend, up to four series are also direct-labelled, and
   * adjacent fills carry a 2px surface gap. */
  --series-1: #6860f2;
  --series-2: #ae1f61;
  --series-3: #9d7120;
  --series-4: #2383b4;
  --series-5: #4b7d2f;
  --series-other: #6a7280;
  /* Grid lines and axis rules sit under the data and must never compete with
   * it. Both are alpha values so they hold up over a tinted panel. */
  --grid: rgba(20, 24, 28, 0.08);
  --axis: rgba(20, 24, 28, 0.28);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --bg: #12141a;
    --panel: #181b22;
    --border: rgba(238, 240, 244, 0.12);

    /* 16.14:1 on --bg, 15.10:1 on --panel. */
    --text: #eef0f4;
    /* 9.45:1 on --bg, 8.84:1 on --panel. */
    --text-2: #b3bac8;
    /* 5.96:1 on --bg, 5.58:1 on --panel. */
    --text-3: #8b93a3;

    /* The ivy lightened for a dark surface rather than the same hex reused:
     * 7.78:1 on --bg, 7.28:1 on --panel. */
    --accent: #4fbc86;
    --accent-soft: rgba(79, 188, 134, 0.14);
    --accent-ink: #04120b;

    --pos: #4ade80;
    --neg: #fb7185;
    --warn: #fbbf24;

    /* The same five hues stepped for the dark surface, so a series never
     * changes hue family when someone switches theme. */
    --series-1: #7d75ee;
    --series-2: #d6497f;
    --series-3: #b0862c;
    --series-4: #3f96c6;
    --series-5: #6a9f46;
    --series-other: #8b93a3;
    --grid: rgba(238, 240, 244, 0.10);
    --axis: rgba(238, 240, 244, 0.32);
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --bg: #12141a;
  --panel: #181b22;
  --border: rgba(238, 240, 244, 0.12);

  --text: #eef0f4;
  --text-2: #b3bac8;
  --text-3: #8b93a3;

  --accent: #4fbc86;
  --accent-soft: rgba(79, 188, 134, 0.14);
  --accent-ink: #04120b;

  --pos: #4ade80;
  --neg: #fb7185;
  --warn: #fbbf24;

  --series-1: #7d75ee;
  --series-2: #d6497f;
  --series-3: #b0862c;
  --series-4: #3f96c6;
  --series-5: #6a9f46;
  --series-other: #8b93a3;
  --grid: rgba(238, 240, 244, 0.10);
  --axis: rgba(238, 240, 244, 0.32);
}

:root[data-theme='light'] {
  color-scheme: light;
}

/* Base surface. Set here rather than in a template so every route - app pages,
 * dashboard overrides, framework error pages - lands on the same ground. */
html {
  background-color: var(--bg);
}

body {
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* Figures: counts, currency, times, axis ticks. Tabular so a column of numbers
 * does not shift as it updates during a live auction. */
.tnum {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
