/* GENERATED BY build.js FROM `palette` IN THE EVENT CONFIG. DO NOT EDIT.
 * Change a colour in src/events/chili2026-2026-09-09.js and rebuild. */
:root {
  --c-charcoal: #1F2024;
  --c-red: #A32530;
  --c-lime: #8CB33A;
  --c-gold: #ca963f;
  --c-cream: #FDFBF7;
  --c-panel: #F3F2F9;
  --c-ink: #36231C;
  --c-white: #ffffff;
  --c-muted: #72645e;
  --c-cream-dim: #bab9b8;
  --c-hair: #e5e1dd;
  --c-panel-edge: #e0dde3;
  --c-input-edge: #c1bab5;
  --c-dash: #c5bfba;
  --c-lime-wash: #f4f7eb;
  --c-red-dark: #821e26;
}

/* THE CHILI COOK-OFF STYLESHEET.
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * 🔑 THERE ARE NO COLOUR LITERALS IN THIS FILE, AND THAT IS ENFORCED.
 * ═══════════════════════════════════════════════════════════════════════════
 * Every colour is `var(--c-*)`. The custom properties themselves are GENERATED
 * by build.js from `palette` in src/events/chili2026-2026-09-09.js and prepended
 * to this file before it is hashed, so the config is the only place the seven
 * campaign colours exist. tests/chili-2026-09-09.test.mjs fails the build if a
 * hex literal ever appears below, because a hex literal here is a second copy
 * of a colour that is supposed to have one.
 *
 * ⚠️ WHY GENERATED AND NOT AN INLINE <style>: the site's CSP is
 * `style-src 'self'` with no 'unsafe-inline', and it is not being widened for a
 * palette. The build writes a real stylesheet; the browser loads it from our own
 * origin like every other asset here.
 *
 * WHY ITS OWN FILE AND NOT A SECTION OF event.css: event.css is the giveaway's,
 * it is content-hashed, and every byte of it would ship to this page for a
 * layout that shares none of its sections. The two events are permanent and
 * independent; their stylesheets are too. The @font-face blocks are repeated
 * here on purpose — the URLs are rewritten by build.js to the SAME hashed font
 * files event.css points at, so a visitor who has seen either page has the font
 * cached for the other.
 *
 * 🔴 NO PAGE-LOAD ANIMATION ANYWHERE IN THIS FILE. Not a fade, not a rise, not
 * a stagger. Deliberate: the page's job is to be readable the instant it paints.
 */

@font-face {
  font-family: "Cormorant";
  src: url("/assets/fonts/cormorant-var-latin.766e1b19.woff2") format("woff2");
  font-weight: 300 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Cormorant";
  src: url("/assets/fonts/cormorant-var-latin.766e1b19.woff2") format("woff2");
  font-weight: 300 700; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Roboto";
  src: url("/assets/fonts/roboto-var-latin.1404ca34.woff2") format("woff2");
  font-weight: 300 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Sacramento";
  src: url("/assets/fonts/sacramento-latin.2fcd867d.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --serif: "Cormorant", Georgia, serif;
  --sans: "Roboto", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --script: "Sacramento", cursive;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--c-cream);
  color: var(--c-ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
}
img, svg { max-width: 100%; }

/* 380px IS THE FLOOR THIS PAGE IS BUILT TO. The wrap padding drops to 18px
   below 420px (see the media query at the bottom), which leaves 344px of
   content on a 380px screen — enough for the longest unbreakable string on the
   page, the address line. */
.wrap { max-width: 940px; margin: 0 auto; padding: 0 24px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * FOCUS. LIME GREEN, VISIBLE, AND ON EVERYTHING FOCUSABLE.
 *
 * 🔴 A SINGLE RULE ON `:focus-visible` FOR THE WHOLE PAGE, not one per control.
 * The giveaway stylesheet styles `.field input:focus` and `.field select:focus`
 * and nothing else, which means its buttons, links and checkboxes fall back to
 * whatever the browser draws. This page has radio CARDS — big clickable panels
 * whose native focus ring is a hairline around an invisible input — so the
 * outline is drawn on the LABEL as well, further down.
 *
 * `:focus-visible` rather than `:focus`: a mouse click on a radio card should
 * not leave a ring behind, but a Tab press must.
 * ═══════════════════════════════════════════════════════════════════════════ */
:focus-visible {
  outline: 3px solid var(--c-lime);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------- header -- */
.site-header { background: var(--c-panel); border-bottom: 1px solid var(--c-panel-edge); }
.site-header .wrap {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding-top: 18px; padding-bottom: 18px; flex-wrap: wrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * ONE WORDMARK, CENTRED. /chili2026 carries `site-header--solo`.
 * ═══════════════════════════════════════════════════════════════════════════
 * 🔴 A MODIFIER, NOT A REDEFINITION OF `.site-header`. Centring the base rule
 * would be simpler and would be wrong twice over: it would silently restyle any
 * future page in this stylesheet that still carries two marks, and it would
 * hide the fact that this page is the exception. The rule above is left exactly
 * as it was so the two-mark header remains the default here as well.
 *
 * ⚠️ THE GIVEAWAY IS NOT AFFECTED BY ANY OF THIS. /fallgive2026 is styled by
 * event.css, a different file, which is untouched. This stylesheet has never
 * been served to it.
 *
 * `justify-content: center` alone is enough because there is only one child;
 * `text-align: center` covers the wrapped case, where the mark breaks onto two
 * lines on a very narrow screen. */
.site-header--solo .wrap {
  justify-content: center;
  text-align: center;
}

/* The tracking is applied to the RIGHT of every letter including the last one,
   so a centred line sits half a letter-space left of true centre. Same defect
   and same correction as `.hero-title`. Scoped to the solo header because on a
   left-aligned mark the indent would just push it right. */
.site-header--solo .brand { text-indent: .16em; }

/* THE WORDMARK TREATMENT IS UNCHANGED — same Cormorant, same .16em tracking,
   same weight, same size, same charcoal. Only its position in the bar moved. */
.brand {
  font-family: var(--serif); font-size: 1.35rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--c-charcoal);
}

/* ⚠️ RETAINED AND CURRENTLY UNUSED. /chili2026 stopped emitting `.brand-right`
   on 2026-09-09. The rule stays because the markup is one line away from coming
   back and because the base `.site-header .wrap` above still describes a
   two-mark header. It styles nothing today. */
.brand-right {
  font-family: var(--sans); font-size: .72rem; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--c-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * HERO — charcoal ground, cream text, centred.
 * ═══════════════════════════════════════════════════════════════════════════ */
.hero {
  background: var(--c-charcoal); color: var(--c-cream);
  text-align: center; position: relative; overflow: hidden;
}
.hero .wrap { padding-top: 52px; padding-bottom: 58px; position: relative; z-index: 1; }

.hero-kicker {
  font-family: var(--serif); font-style: italic; font-weight: 400;
  font-size: clamp(1.1rem, 3.2vw, 1.5rem);
  color: var(--c-cream); margin: 0; opacity: .9;
}

/* ONE <h1>, TWO VISUAL WORDS.
   "Chili" and "COOK-OFF" are two spans inside a single heading, both
   aria-hidden, with the plain string "Chili Cook-Off" in an .sr-only sibling.
   Two separate headings would announce the event as two things; a single
   heading containing the script word would be read letter-perfect but styled
   as one block, and these two need different faces and different sizes. */
.hero-h1 { margin: 0; font-weight: 400; }

/* THE SCRIPT WORD. Sacramento has a long descender on its 'h' and a tall
   ascender on its 'l', so the line box is much taller than the letters look;
   the tight line-height pulls "COOK-OFF" back up under it rather than leaving
   a gap that reads as a mistake. */
.hero-script {
  display: block;
  font-family: var(--script); font-weight: 400; color: var(--c-lime);
  font-size: clamp(4.6rem, 20vw, 10rem);
  line-height: .82;
  /* 🔴 THE GAP IS IN `em`, NOT PIXELS, AND IT IS NOT DECORATIVE SPACING.
     Sacramento's ascenders overflow ABOVE their own line box, so at 4px of
     margin the 'h' and 'l' of "Chili" climbed into "Join us for a . . ." and
     the two lines touched — caught on a screenshot of the deployed preview,
     not in review. `em` is relative to this element's own font-size, so the
     clearance scales with the clamp instead of being right at one width. */
  margin: .2em 0 0;
}
.hero-title {
  display: block;
  font-family: var(--serif); font-weight: 500; color: var(--c-cream);
  font-size: clamp(1.9rem, 8vw, 3.6rem);
  letter-spacing: .22em; text-transform: uppercase;
  line-height: 1.05; margin: 6px 0 0;
  /* The tracking is applied to the right of each letter including the last one,
     which pushes a centred line visibly off-centre. This puts it back. */
  text-indent: .22em;
}

.hero-when {
  font-family: var(--sans); font-weight: 500; color: var(--c-cream);
  font-size: clamp(.95rem, 2.6vw, 1.15rem);
  margin: 26px 0 0; letter-spacing: .02em;
}
.hero-where {
  font-family: var(--sans); color: var(--c-cream-dim);
  font-size: clamp(.9rem, 2.4vw, 1.05rem); margin: 6px 0 0;
}

/* THE FREE BADGE. Lime block, charcoal text — the one place on the page where
   those two swap roles, which is what makes it read as a stamp. */
.hero-free {
  display: inline-block; margin: 28px 0 0;
  background: var(--c-lime); color: var(--c-charcoal);
  font-family: var(--sans); font-weight: 700;
  font-size: clamp(.72rem, 2.2vw, .84rem);
  letter-spacing: .13em; text-transform: uppercase;
  padding: 13px 20px;
}
.hero-tagline {
  font-family: var(--serif); font-style: italic; color: var(--c-cream);
  font-size: clamp(1.15rem, 3.4vw, 1.6rem); margin: 26px 0 0;
}

/* THE PEPPER. Decoration and nothing else — aria-hidden in the markup, faint,
   and it never overlaps text: it sits in the bottom-right corner of the hero
   and is clipped by the section's own overflow on a narrow screen. */
.hero-pepper {
  position: absolute; right: 18px; bottom: 6px;
  width: clamp(110px, 24vw, 220px); height: auto;
  opacity: .16; z-index: 0; pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE JUDGING STRIP. Chili red, cream text, one line.
 * ═══════════════════════════════════════════════════════════════════════════ */
.strip { background: var(--c-red); color: var(--c-cream); }
.strip .wrap { padding-top: 18px; padding-bottom: 18px; }
.strip p {
  margin: 0; text-align: center;
  font-size: clamp(.88rem, 2.4vw, 1rem); line-height: 1.5;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE FORM. Cream ground, white card.
 * ═══════════════════════════════════════════════════════════════════════════ */
.signup { background: var(--c-cream); }
.signup .wrap { padding-top: 52px; padding-bottom: 56px; }

.card {
  background: var(--c-white); border: 1px solid var(--c-hair);
  max-width: 640px; margin: 0 auto; padding: 36px 32px;
}
.card[hidden] { display: none; }

.card-heading {
  font-family: var(--serif); font-weight: 500; color: var(--c-charcoal);
  font-size: clamp(1.9rem, 5vw, 2.4rem); margin: 0; text-align: center;
}
.card-lede {
  margin: 12px 0 28px; text-align: center;
  color: var(--c-muted); font-size: .95rem; line-height: 1.6;
}

/* ------------------------------------------------------- the choice block -- */
/* A REAL <fieldset> WITH A REAL <legend>. The two cards are one question and a
   screen reader has to hear it that way; a pair of loose radios under a <p>
   announces the blurbs and never the question. */
.choice {
  border: 2px solid var(--c-lime); margin: 0 0 26px; padding: 18px 18px 6px;
}
.choice legend {
  font-family: var(--sans); font-weight: 600; font-size: .74rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--c-charcoal);
  padding: 0 8px;
}

/* 🔴 `position: relative` IS LOAD-BEARING, NOT TIDINESS.
   The radio below is `position: absolute`, and without a positioned ancestor it
   resolves against the initial containing block instead of its own card. It did
   exactly that: measured at (923, 599) on the deployed preview — a 1px
   invisible form control sitting loose in the middle of the page, hundreds of
   pixels from the card it belongs to, where it can land on top of unrelated
   content and take the click meant for it. Caught by reading the element's
   bounding box in the browser, not by looking at the page. */
.choice-card { display: block; position: relative; margin: 0 0 12px; cursor: pointer; }

/* ═══════════════════════════════════════════════════════════════════════════
 * 🔴 THE REAL RADIO IS THE CONTROL. THERE IS NO FAKE ONE.
 * ═══════════════════════════════════════════════════════════════════════════
 * This started as the usual trick — `opacity: 0` on the input, a circle drawn
 * with `::before` on the label — and that trick is WRONG here. Measured on the
 * deployed preview: with `opacity: 0` Chrome dropped both radios out of the
 * accessibility tree entirely. `read_page` listed the five text fields and the
 * guest select and NOT the two radios, which is what a screen reader would have
 * found too: the question this form opens with, unreachable.
 *
 * So the input is a real, visible radio, positioned in the card's left gutter
 * and tinted with `accent-color`. It is what the browser draws, what the
 * assistive tree exposes, what Tab reaches and what the label activates. There
 * is nothing to keep in sync because there is only one control.
 * ═══════════════════════════════════════════════════════════════════════════ */
.choice-card input {
  position: absolute; top: 17px; left: 16px;
  width: 16px; height: 16px; margin: 0;
  accent-color: var(--c-lime);
  /* ⚠️ `z-index` IS REQUIRED, NOT DEFENSIVE. `.choice-inner` is a positioned
     element that comes AFTER this one in the DOM and paints an opaque
     background, so at the default z-index it covers the radio completely — the
     gutter rendered empty on the preview and the control was invisible even
     though it was there. Later-sibling-wins is the rule; this opts out of it. */
  z-index: 1;
}
.choice-inner {
  display: block; border: 1px solid var(--c-hair); padding: 14px 16px 14px 44px;
  position: relative; background: var(--c-white);
}
/* ⚠️ THE SELECTED CARD GAINS AN INSET RING, NOT A THICKER BORDER. Growing the
   border from 1px to 2px moves every line of text inside the card by a pixel,
   so choosing an option makes the other one twitch. The ring is drawn inside
   the existing box and shifts nothing. */
.choice-card input:checked + .choice-inner {
  border-color: var(--c-lime);
  box-shadow: inset 0 0 0 1px var(--c-lime);
  background: var(--c-lime-wash);
}
/* THE FOCUS RING GOES ON THE CARD, not on the 1px input nobody can see. */
.choice-card input:focus-visible + .choice-inner {
  outline: 3px solid var(--c-lime); outline-offset: 2px;
}
.choice-label {
  display: block; font-family: var(--sans); font-weight: 600;
  font-size: 1rem; color: var(--c-charcoal);
}
.choice-blurb {
  display: block; margin-top: 3px; font-size: .87rem;
  line-height: 1.45; color: var(--c-muted);
}

/* ------------------------------------------------------------- the fields -- */
.field { margin: 0 0 18px; }
.field label {
  display: block; margin-bottom: 6px; font-size: .85rem; font-weight: 500;
  letter-spacing: .02em; color: var(--c-charcoal);
}
.field input, .field select, .field textarea {
  width: 100%; padding: 12px 13px; font: inherit; font-size: 1rem;
  color: var(--c-ink); background: var(--c-white);
  border: 1px solid var(--c-input-edge); border-radius: 0;
}
.field textarea { min-height: 88px; resize: vertical; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

/* INLINE, SPECIFIC, AND ATTACHED TO THE FIELD IT IS ABOUT. Never a single
   "please complete every required field" at the bottom — that sentence makes
   somebody hunt for the one box they missed. */
.field-error {
  margin: 6px 0 0; font-size: .82rem; line-height: 1.45;
  color: var(--c-red); font-weight: 500;
}
.field-error[hidden] { display: none; }
.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--c-red); border-width: 2px;
}

/* ---------------------------------------------------------- the chef block -- */
/* SHOWN ONLY WHEN "Enter a chili" IS SELECTED — the red left rule is what marks
   it as a branch of the form rather than more of the same form. */
.chef-block {
  border-left: 4px solid var(--c-red);
  padding: 4px 0 4px 20px; margin: 4px 0 22px;
}
.chef-block[hidden] { display: none; }
.chef-heading {
  font-family: var(--serif); font-weight: 500; color: var(--c-red);
  font-size: 1.4rem; margin: 0 0 16px;
}

/* ---------------------------------------------------------------- checks -- */
.check {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 0 0 18px; font-size: .9rem; line-height: 1.5;
}
.check input {
  margin: 3px 0 0; flex: 0 0 auto; width: 17px; height: 17px;
  accent-color: var(--c-lime);
}

/* The honeypot. Off-screen rather than display:none, for the reason given in
   the giveaway stylesheet: some bots skip hidden fields, very few skip
   off-screen ones. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ----------------------------------------------------------------- button -- */
.cta {
  display: block; width: 100%; margin: 24px 0 0; padding: 17px 20px;
  background: var(--c-red); color: var(--c-cream);
  font-family: var(--sans); font-size: 1rem; font-weight: 600;
  letter-spacing: .04em; border: 0; cursor: pointer;
}
.cta:hover { background: var(--c-red-dark); }
.cta:disabled { opacity: .45; cursor: not-allowed; }

.fine-print {
  margin: 18px 0 0; text-align: center;
  font-size: .74rem; line-height: 1.6; color: var(--c-muted);
}
/* THREE PARAGRAPHS NOW, AND THEY MUST READ AS THREE. The 18px above is the gap
   from the BUTTON; between the paragraphs it would be nearly a full blank line
   at this size and the block would drift apart. 10px is enough to separate the
   credit, the photo notice and the terms while keeping them one block of fine
   print. Adjacent-sibling, so the first paragraph keeps its distance from the
   button whatever the others do. */
.fine-print + .fine-print { margin-top: 10px; }
.form-msg { margin: 14px 0 0; font-size: .92rem; text-align: center; }
.form-msg.is-error { color: var(--c-red); }

/* ═══════════════════════════════════════════════════════════════════════════
 * THE THANK-YOU CARD. Same white card, with a lime top rule.
 * ═══════════════════════════════════════════════════════════════════════════ */
.thanks { border-top: 6px solid var(--c-lime); }
.thanks h2 {
  font-family: var(--serif); font-weight: 500; color: var(--c-charcoal);
  font-size: clamp(1.8rem, 5vw, 2.3rem); margin: 0 0 16px; text-align: center;
}
.thanks p {
  margin: 0 0 14px; font-size: 1rem; line-height: 1.65; color: var(--c-ink);
}
.thanks p:last-child { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
 * THE WRAP-UP (2026-10-14). The event is over; the page says thank you.
 * ═══════════════════════════════════════════════════════════════════════════ */
/* The headline is mixed case and longer than "COOK-OFF", so it is set in the
   serif at a size that holds on one or two lines at 361px, not in the tracked
   capitals the signup title uses. */
.wrapup-h1 {
  font-family: var(--serif); font-weight: 500; color: var(--c-lime);
  font-size: clamp(2.6rem, 11vw, 4.6rem); line-height: 1.05; margin: 10px 0 0;
}
.wrapup-card { border-top: 6px solid var(--c-lime); text-align: center; }
.wrapup-text {
  margin: 0 0 14px; font-size: 1.05rem; line-height: 1.65; color: var(--c-ink);
}
/* A LINK DRESSED AS THE BUTTON. It is a real <a href>; there is no script on
   this page any more. */
.wrapup-link { text-decoration: none; text-align: center; box-sizing: border-box; }
.wrapup-link:focus-visible { outline-offset: 3px; }

/* ═══════════════════════════════════════════════════════════════════════════
 * THE HOSTS ROW.
 * ═══════════════════════════════════════════════════════════════════════════ */
/* ⚠️ EVERY RULE IN THIS BLOCK IS RETAINED AND CURRENTLY UNUSED. The hosts row
   was removed from /chili2026 on 2026-09-09 and nothing on the page matches
   these selectors any more. They stay, with the config array and `hostsRow()`
   in build.js, so restoring the row is one <section> in the template rather
   than a rebuild. Do not delete them as dead CSS. */
.hosts { background: var(--c-cream); border-top: 1px solid var(--c-hair); }
.hosts .wrap { padding-top: 40px; padding-bottom: 44px; text-align: center; }
.hosts-kicker {
  margin: 0 0 24px; font-family: var(--sans); font-weight: 600;
  font-size: .7rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--c-muted);
}
.hosts-row {
  display: flex; flex-wrap: wrap; justify-content: center;
  align-items: center; gap: 18px;
}
.host {
  flex: 1 1 180px; max-width: 220px; min-height: 76px;
  display: flex; align-items: center; justify-content: center;
  padding: 12px 14px;
}
/* THE PLACEHOLDER. A dashed box reads as "a logo goes here"; a broken <img>
   reads as a bug, and a bare name reads as a decision nobody made. */
.host--placeholder { border: 1px dashed var(--c-dash); }
.host-name {
  margin: 0; font-family: var(--serif); font-weight: 500;
  font-size: 1.02rem; line-height: 1.3; color: var(--c-charcoal);
}
.host img { max-height: 56px; width: auto; }

/* ---------------------------------------------------------------- footer -- */
.site-footer { background: var(--c-charcoal); color: var(--c-cream-dim); }
.site-footer .wrap { padding-top: 26px; padding-bottom: 30px; }
.site-footer p {
  margin: 0; text-align: center; font-size: .8rem; letter-spacing: .04em;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * RESPONSIVE. Two steps, and the page holds at 380px.
 * ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .card { padding: 28px 22px; }
  .host { flex: 1 1 140px; }
}

@media (max-width: 420px) {
  /* 24px of padding each side leaves 332px on a 380px screen. 18px leaves 344px,
     which is what the address line and the free badge need without wrapping
     mid-word. */
  .wrap { padding: 0 18px; }
  .card { padding: 24px 18px; }
  .site-header .wrap { gap: 4px; }
  .brand { font-size: 1.15rem; letter-spacing: .12em; }
  /* The centring correction has to track the tracking it is correcting. */
  .site-header--solo .brand { text-indent: .12em; }
  .hero .wrap { padding-top: 38px; padding-bottom: 44px; }
  .hero-free { letter-spacing: .08em; padding: 12px 14px; }
  .choice-inner { padding: 13px 13px 13px 40px; }
  .choice-card input { left: 13px; }
  .chef-block { padding-left: 14px; }
  /* Two logo boxes per row rather than four squeezed ones. */
  .host { flex: 1 1 44%; max-width: none; min-height: 64px; }
}

/* Somebody who has asked their system for less motion gets no transition at
   all. There are no page-load animations here to switch off — this covers the
   button's hover colour change, which is the only movement on the page. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
