/* ==========================================================================
   GRUMLAW — BASE
   Reset, document defaults, typography, layout primitives, utilities.
   Consumes semantic tokens only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. RESET
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Offset anchor targets so a sticky header never covers the heading. */
  scroll-padding-top: calc(var(--header-h, 72px) + var(--space-lg));
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  min-height: 100svh;

  /* THE PAGE GROUND — mixed here rather than layered over it.

     html carries no background, so this one propagates to the CANVAS, which
     the browser paints across the whole window: behind the toolbar, under the
     status bar, and through the rubber-band overscroll at both ends. Nothing
     positioned can reach those areas. The tint used to be a `position: fixed`
     backdrop at inset: 0, and iOS Safari collapses its toolbar mid-scroll and
     reveals the strip underneath in the same frame — faster than a fixed box
     is re-laid-out — so the strip came up in the untinted ground and the whole
     background read as having been shoved up off the bottom edge. Mixed into
     the ground it IS the canvas, and there is nothing left to shove.

     initShowcase() sets --tint-o (0..1) on the root; every other page leaves
     it unset and this resolves to a plain --surface-page.

     Two declarations: a browser without color-mix keeps the solid ground from
     the first rather than losing the background entirely. */
  background-color: var(--surface-page);
  background-color: color-mix(in srgb,
                      var(--surface-tint) calc(var(--tint-o, 0) * 100%),
                      var(--surface-page));
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--weight-normal);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* Transition the TOKENS, not background-color. A theme change swaps the two
     ground colours and should ease; --tint-o changes on every frame of a
     scroll and has to land instantly. Easing the property they share would
     smear the scroll into a lag chasing the finger — the one thing this tint
     must not do. */
  transition: --surface-page var(--dur-normal) var(--ease-out),
              --surface-tint var(--dur-normal) var(--ease-out),
              color var(--dur-normal) var(--ease-out);
}

/* Suppress the theme transition during the initial theme write so the page
   never visibly cross-fades on load. theme.js removes this class on rAF. */
.theme-init * { transition: none !important; }

img, picture, svg, video, canvas {
  display: block;
  max-width: 100%;
  height: auto;
}

input, button, textarea, select { font: inherit; color: inherit; }

p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

ul[role="list"], ol[role="list"] { list-style: none; padding: 0; }

:target { scroll-margin-top: calc(var(--header-h, 72px) + var(--space-lg)); }

/* --------------------------------------------------------------------------
   2. FOCUS
   A single visible focus treatment everywhere. Never remove without replacing.
   -------------------------------------------------------------------------- */
:focus-visible {
  outline: var(--border-width-thick) solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: var(--space-sm);
  top: var(--space-sm);
  z-index: calc(var(--z-modal) + 1);
  padding: var(--space-2xs) var(--space-sm);
  background: var(--surface-accent);
  color: var(--text-on-accent);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); }

/* --------------------------------------------------------------------------
   3. TYPOGRAPHY

   Helvetica is the primary face and carries everything that is read.

   ROBOTO CONDENSED USAGE RULE: compressed structural labels and numerals
   only — eyebrows, tags, date numerals, step numbers, small uppercase
   headings. Never reading text, never headings, never a paragraph. It is
   the minority face by design; if it starts appearing in body copy the
   system has drifted.
   -------------------------------------------------------------------------- */

/* ROBOTO CONDENSED — self-hosted.
   Two rules over one 45KB variable file, exactly as Google Fonts serves it:
   the wght axis is clamped to 400 on one face and 700 on the other, so this
   renders identically to the hosted version it replaces.

   It is local because it was in the critical path of the largest text on the
   site. A hosted font is a render-blocking stylesheet from one third party
   followed by a font file from a second, and only after both does the
   condensed text stop being drawn in Arial Narrow — which is wider, heavier,
   and reflows the line. How long that takes is a cold-cache lottery, so the
   swap is invisible on one load and obvious on the next. Same-origin, and
   preloaded from the document head, there is no round trip to lose.

   Roboto Condensed, Apache License 2.0 — see assets/fonts/LICENSE.txt. */
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/roboto-condensed-latin.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: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/roboto-condensed-latin.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;
}

/* OPTICAL STROKE — see the note in tokens.css.
   Declared on `*` rather than inherited from <body> on purpose:
   -webkit-text-stroke-width is an inherited property, so an em value set once
   would compute to a fixed px at <body> and every descendant would inherit
   that same px — which is exactly the non-scaling behaviour this replaces.
   The universal selector forces em to re-resolve against each element's own
   font-size.

   paint-order is what keeps it legible: -webkit-text-stroke centres the stroke
   on the glyph outline, so half of it grows inward and closes up counters at
   small sizes. Painting the stroke behind the fill leaves only the outward
   half — thicker and softer, with the counters still open. */
* {
  -webkit-text-stroke: var(--stroke-text) currentColor;
  paint-order: stroke fill;
}

.display,
h1, h2, h3 {
  font-family: var(--font-sans);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
  text-wrap: balance;
  -webkit-text-stroke: var(--stroke-display) currentColor;
}

h1, .display-xl { font-size: var(--display-xl); }
.display-lg     { font-size: var(--display-lg); }
h2, .display-md { font-size: var(--display-md); }
h3, .display-sm { font-size: var(--display-sm); }

h4, h5, h6 {
  font-family: var(--font-sans);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-ui);
  color: var(--text-primary);
}
h4 { font-size: var(--text-xl); }
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-base); }

.lead {
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.prose { max-width: 68ch; }
.prose > * + * { margin-top: var(--space-sm); }
.prose p { line-height: var(--leading-relaxed); color: var(--text-secondary); }
.prose h2, .prose h3 { margin-top: var(--space-xl); }
.prose h4 { margin-top: var(--space-lg); }
/* Ink text with a sage rule. A link that differs from body copy by hue alone
   fails 1.4.1 anyway; the underline carries it, and carries the brand colour. */
.prose a {
  color: var(--text-accent);
  text-decoration: underline;
  text-decoration-color: var(--accent-mark);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.15em;
}
.prose ul, .prose ol { padding-left: 1.25em; color: var(--text-secondary); }
.prose li + li { margin-top: var(--space-3xs); }
.prose strong { color: var(--text-primary); font-weight: var(--weight-bold); }

/* Roboto Condensed, per the usage rule above. The eyebrow marks a section
   without needing a rule, an icon or a coloured chip. */
.eyebrow {
  display: block;
  font-family: var(--font-condensed);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-xs);
}
.eyebrow--accent { color: var(--text-accent); }

/* Opt-in helper for the condensed face, so the usage rule stays explicit at
   every call site rather than being buried in a component. */
.condensed { font-family: var(--font-condensed); }

.text-muted     { color: var(--text-muted); }
.text-secondary { color: var(--text-secondary); }
.text-accent    { color: var(--text-accent); }

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }

/* --------------------------------------------------------------------------
   4. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--container-narrow); }
.container--wide   { max-width: none; }

.section    { padding-block: var(--section-y); }
.section--tight { padding-block: var(--section-y-tight); }
.section--flush-top { padding-top: 0; }
/* Sections stack their paddings, so the gap BELOW a block is its own bottom
   padding plus the next section's top. Zeroing the bottom makes the gap above
   and below a block equal — the next section's top padding becomes the only
   spacer, so one value sets both sides. */
.section--flush-bottom { padding-bottom: 0; }

.stack > * + * { margin-top: var(--flow, var(--space-sm)); }
.stack--sm { --flow: var(--space-2xs); }
.stack--lg { --flow: var(--space-md); }
.stack--xl { --flow: var(--space-lg); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap, var(--space-xs));
  align-items: center;
}
.cluster--between { justify-content: space-between; }

.grid { display: grid; gap: var(--gap, var(--space-md)); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }

/* Explicit thirds. auto-fit would give two columns at tablet; the live site
   goes straight from one column to three at the desktop breakpoint. */
.grid--thirds { grid-template-columns: 1fr; }
@media (min-width: 64rem) { .grid--thirds { grid-template-columns: repeat(3, 1fr); } }

/* Asymmetric split — the primary structural rhythm on this site. Text column
   is deliberately narrower than the media column, and it does not alternate. */
.split {
  display: grid;
  gap: var(--space-xl);
  align-items: center;
}
@media (min-width: 62rem) {
  .split { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--space-2xl); }
  .split--media-first > :first-child { order: 2; }
}

.section-head {
  display: grid;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
}
/* Centred heading for sections whose body is asymmetric — the sticky showcase
   runs image-left/content-right, and a left-aligned heading over it made the
   whole section lean. Centring the head holds the section's axis instead. The
   measure is capped so the two display lines break where they were written to
   break rather than running the full container width. */
.section-head--center {
  justify-items: center;
  text-align: center;
}
.section-head--center > * { max-width: 20ch; }

@media (min-width: 48rem) {
  .section-head--split {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--space-lg);
  }
}

.divider {
  border: 0;
  border-top: var(--border-width) solid var(--border-subtle);
}

/* --------------------------------------------------------------------------
   5. SURFACE UTILITIES
   Let a section opt into a different ground while keeping every descendant
   token correct — including text and borders.
   -------------------------------------------------------------------------- */
.surface-cream {
  background-color: var(--surface-cream);
  color: var(--text-primary);
}
.surface-sunken { background-color: var(--surface-sunken); }
.surface-raised { background-color: var(--surface-raised); }
/* The tinted ground the showcase backdrop fades to, available as a static
   section surface for pages that want the same step away from the page. */
.surface-tint {
  background-color: var(--surface-tint);
  /* --text-muted is tuned to the PAGE ground, where stone-500 measures 4.59:1.
     On this one it falls to 3.22:1, which the 11px eyebrows fail — they are
     small bold caps, not large text. One step down the ramp restores them to
     5.12:1 and leaves the muted/secondary relationship intact. Dark mode steps
     up rather than down, for the same reason in the other direction. */
  --text-muted: var(--gl-stone-600);
}
[data-theme="dark"] .surface-tint { --text-muted: var(--gl-stone-400); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .surface-tint {
    --text-muted: var(--gl-stone-400);
  }
}

/* Permanently dark, in BOTH themes — unlike .surface-inverse, which flips to
   paper in dark mode because inverting is its whole job. A section that is
   meant to READ as an ink band (and is marked data-tone="dark" for the header)
   must not become the lightest thing on the page when the theme changes. */
.surface-ink {
  background-color: var(--surface-media);
  color: var(--gl-stone-50);
  --text-primary:   var(--gl-stone-50);
  --text-secondary: rgb(250 250 249 / 0.78);
  --text-muted:     rgb(250 250 249 / 0.62);
  --border-subtle:  rgb(250 250 249 / 0.18);
  --border-default: rgb(250 250 249 / 0.28);
  --border-control: rgb(250 250 249 / 0.55);
  --text-accent:    var(--gl-sage-400);
  --accent-mark:    var(--gl-sage-400);
  --focus-ring:     var(--gl-sage-400);
}

.surface-inverse {
  background-color: var(--surface-inverse);
  color: var(--text-on-inverse);
  --text-primary:   var(--text-on-inverse);
  --text-secondary: color-mix(in srgb, var(--text-on-inverse) 78%, transparent);
  --text-muted:     color-mix(in srgb, var(--text-on-inverse) 62%, transparent);
  --border-subtle:  var(--border-inverse);
  --border-default: color-mix(in srgb, var(--text-on-inverse) 28%, transparent);
  --text-accent:    var(--gl-sage-400);
  --focus-ring:     var(--gl-sage-400);
}

/* --------------------------------------------------------------------------
   6. UTILITIES
   -------------------------------------------------------------------------- */
/* Responsive line break. A hardcoded <br> forces the same break at every
   width; this lets a headline run on one line on mobile and break where the
   design wants it from tablet up. */
.brk { display: inline; }
.brk::after { content: " "; }
@media (min-width: 48rem) {
  .brk { display: block; }
  .brk::after { content: none; }
}

.visually-hidden:not(:focus):not(:active) {
  clip-path: inset(50%);
  height: 1px; width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

.text-center { text-align: center; }
.mx-auto { margin-inline: auto; }
.full-bleed { width: 100vw; margin-inline: calc(50% - 50vw); }

[hidden] { display: none !important; }
