/*
  cpp-grail design framework -- shared layer
  =========================================

  This file is theme-agnostic. It declares the rhythm tokens every theme inherits and
  bridges the `--g-*` contract onto the Material variables that style the site chrome.

  A theme is one file in `website/themes/`. It never edits this file, and this file
  never names a color or a typeface.

  The contract a theme must fill in:

    Surfaces   --g-bg --g-surface --g-surface-alt --g-code-bg
    Ink        --g-fg --g-fg-muted --g-fg-faint
    Lines      --g-border --g-border-strong --g-border-width --g-rule-width
    Accent     --g-accent --g-accent-strong --g-accent-text --g-on-accent
               --g-accent-wash
    Levels     --g-beginner --g-intermediate --g-advanced --g-trivia
    Type       --g-font-display --g-font-body --g-font-mono
               --g-display-weight --g-display-tracking --g-display-transform
               --g-h1-size --g-body-size --g-line-height --g-numeral-size
    Shape      --g-radius --g-radius-lg --g-shadow --g-shadow-raised

  `tools/themes.py` checks every theme against that list at build time, so a theme that
  forgets a token fails the build with the token named rather than rendering wrong.

  Light and dark are not a separate axis. Each theme declares `scheme: light` or
  `scheme: dark` and supplies one palette, so choosing a theme is the only choice a
  reader makes.

  Measure is deliberately NOT a theme token: the reading width is the same in every
  theme, like the rest of the layout. Nor is the type scale, the spacing rhythm or the
  focus ring -- a theme sets the base size and the face, and every other step is derived
  from it here, so no component invents a size of its own.

  The accent is two tokens because it plays two roles that pull apart: as a fill it wants
  to be vivid, as text on the page it wants to be dark enough to read. `tools/themes.py`
  checks each one against the surface it actually sits on.

  Selectors are written against `:root` rather than `[data-grail-theme]` so the page is
  fully themed even when the attribute is never set -- that is, with JavaScript disabled.
*/

html {
  /* Theme-independent. Rhythm and motion stay constant so only the identity changes. */

  /* Reading width, in characters: prose is capped so lines stay comfortable, and the
     column itself is wider, because code needs more room than prose does.

     The column was 48rem, but Material's grid is 61rem wide and two 12.1rem rails were
     eating it, so the real measure was 36.8rem -- about 66 monospaced characters. One
     code line in eight in the collection is longer than that, and every one of them
     broke. Both rails are hidden on entry pages now (see the `hide:` front matter the
     generator writes), which leaves the column free to hold the longest line in the
     repository, at 98 characters, without breaking or scrolling. */
  --g-measure: 72ch;
  --g-content-width: 54rem;

  /* Type scale. Every step is a ratio of the theme's own --g-body-size, so a theme
     changes the base and the whole scale follows in proportion. Components pick a step;
     they never name a size.

     The two multipliers are how the scale narrows on small screens. Scaling them here
     moves body copy and display type by different amounts in one place, instead of
     each component carrying its own mobile exception. */
  --g-text-scale: 1;
  --g-display-scale: 1;

  --g-text-2xs: calc(var(--g-body-size) * 0.7 * var(--g-text-scale));
  --g-text-xs: calc(var(--g-body-size) * 0.8 * var(--g-text-scale));
  --g-text-sm: calc(var(--g-body-size) * 0.88 * var(--g-text-scale));
  --g-text-base: calc(var(--g-body-size) * var(--g-text-scale));
  --g-text-md: calc(var(--g-body-size) * 1.1 * var(--g-text-scale));
  --g-text-lg: calc(var(--g-body-size) * 1.25 * var(--g-text-scale));
  --g-text-xl: calc(var(--g-body-size) * 1.55 * var(--g-text-scale));
  --g-text-code: calc(var(--g-body-size) * 0.86 * var(--g-text-scale));

  /* Spacing rhythm. */
  --g-step: 0.25rem;
  --g-gap-xs: 0.3rem;
  --g-gap-sm: 0.45rem;
  --g-gap: 0.75rem;
  --g-gap-lg: 1.5rem;

  /* One focus treatment for the whole site, in the active theme's accent. */
  --g-focus-width: 2px;
  --g-focus-offset: 2px;

  --g-transition: 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  html {
    --g-transition: 0ms;
  }
}

:root body {
  --md-text-font-family: var(--g-font-body);
  --md-code-font-family: var(--g-font-mono);

  --md-default-bg-color: var(--g-bg);
  --md-default-fg-color: var(--g-fg);
  --md-default-fg-color--light: var(--g-fg-muted);
  --md-default-fg-color--lighter: var(--g-fg-faint);
  --md-default-fg-color--lightest: var(--g-border);
  --md-typeset-color: var(--g-fg);
  --md-typeset-a-color: var(--g-accent-text);
  --md-accent-fg-color: var(--g-accent-strong);
  --md-code-hl-color: var(--g-accent-wash);
  --md-accent-bg-color: var(--g-on-accent);
  --md-code-bg-color: var(--g-code-bg);
  --md-code-fg-color: var(--g-fg);
  --md-typeset-mark-color: var(--g-accent-wash);

  /* Material declares these at `:root`, in terms of other --md-* variables:

       --md-admonition-fg-color: var(--md-default-fg-color);
       --md-admonition-bg-color: var(--md-default-bg-color);

     A custom property is substituted where it is *declared*, and this block sets its
     values on <body> -- a descendant of :root. So both resolved against Material's own
     light-scheme defaults and inherited down as near-black ink, whatever theme was
     active. The answer reveal is a <details>, which is what made it show up there:
     dark surface, black text. Bridging them here is what puts them on the theme. */
  --md-admonition-fg-color: var(--g-fg);
  --md-admonition-bg-color: var(--g-surface);
  --md-typeset-del-color: var(--g-accent-wash);
  --md-typeset-ins-color: var(--g-accent-wash);
  --md-typeset-table-color: var(--g-border);

  /* The header wears the page surface in every theme; identity comes from type
     and rules, not from a coloured bar. */
  --md-primary-fg-color: var(--g-bg);
  --md-primary-fg-color--light: var(--g-surface);
  --md-primary-fg-color--dark: var(--g-surface-alt);
  --md-primary-bg-color: var(--g-fg);
  --md-primary-bg-color--light: var(--g-fg-muted);

  --md-footer-bg-color: var(--g-surface-alt);
  --md-footer-bg-color--dark: var(--g-surface-alt);
  --md-footer-fg-color: var(--g-fg);
  --md-footer-fg-color--light: var(--g-fg-muted);
  --md-footer-fg-color--lighter: var(--g-fg-faint);

  --md-shadow-z1: var(--g-shadow);
  --md-shadow-z2: var(--g-shadow-raised);

  background-color: var(--g-bg);
  color: var(--g-fg);
  font-family: var(--g-font-body);
}

/* --------------------------------------------------------------------- focus */

/* Keyboard focus is always visible, in one treatment, everywhere. Anything that sets
   `outline: none` without replacing it is a bug -- a keyboard user loses their place. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--g-focus-width) solid var(--g-accent);
  outline-offset: var(--g-focus-offset);
}
