/* The site stylesheet. One file, no framework, no build step.
 *
 * The palette is the digital one — the dark field and terracotta accent the
 * video series and the knowledge-base pages already use. Marketing/NAMING.md's
 * navy and blue is the product-and-print brand; see docs/WEBSITE_PLAN.md §8 for
 * why the two differ on purpose.
 *
 * WHAT WAS WRONG WITH THE FIRST VERSION OF THIS FILE, since it was not the
 * colours and not the typeface: there was one shape on the site. Every page,
 * including the one a visitor lands on, was a 38rem column of paragraphs with a
 * hairline above it. That is a fine stylesheet for an essay and it is not a
 * website — no page could begin, nothing could be a figure, a grid or an action,
 * and twenty-one captures of the running product sat unused in media/ because
 * prose has nowhere to put them.
 *
 * So there are now two shapes. A DOCUMENT (`main.prose`) is the reading column,
 * unchanged, because an 1,800-word article on roughness wants exactly that. A
 * PAGE (`main.landing`) is a stack of bands, each one full-bleed with its
 * content held in `.inner` at the frame width. Which one a page gets is
 * declared in the register, not guessed from its content.
 */

:root {
  --bg:#1b1b1f; --surface:#232328; --card:#2a2a30; --raise:#32323a;
  --fg:#f0ede7; --body:#c4c0b7; --muted:#a5a199; --dim:#8e8a83;
  --accent:#c47046; --accent-hi:#dd8456; --accent-soft:rgba(196,112,70,.14);
  --cool:#7ba8b8;
  --border:rgba(240,237,231,.11); --border-hi:rgba(240,237,231,.2);
  --sans:'Sora', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono:ui-monospace, 'DejaVu Sans Mono', Menlo, monospace;

  /* Two widths, and the difference between them is the whole layout. */
  --measure:38rem;          /* a column of text, at a length that is readable */
  --frame:74rem;            /* the page itself */
  --gutter:1.5rem;
  --radius:14px;
  --shadow:0 1px 2px rgba(0,0,0,.3), 0 12px 32px -12px rgba(0,0,0,.55);
}

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

/* A reader who has asked for less motion gets none of it.
   `animation-duration: 0s` was not enough on its own: a scroll-driven
   animation ignores duration entirely and takes its progress from the scroll
   position, so an element parked at the start of its keyframes would have
   stayed there. The timeline and the name both have to go. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior:auto; }
  *, *::before, *::after {
    transition-duration:.01ms !important;
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    animation-timeline:auto !important;
    animation-name:none !important;
    scroll-behavior:auto !important;
  }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation:none !important;
  }
}

/* --body is for reading and --muted for chrome. They were one token, and the
   result was 5.4:1 body copy — legal, and not generous for 900 words. */
body {
  margin:0; background:var(--bg); color:var(--body);
  font-family:var(--sans); font-size:clamp(16px, 15.2px + .25vw, 18px); line-height:1.7;
  text-rendering:optimizeLegibility; -webkit-font-smoothing:antialiased;
}

a { color:var(--accent); text-decoration:none; border-bottom:1px solid rgba(196,112,70,.35);
    transition:color .15s ease, border-color .15s ease; }
a:hover, a:focus-visible { color:var(--fg); border-bottom-color:var(--fg); }
:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }

/* Keyboard users reach the content without walking the nav. */
.skip { position:absolute; left:-9999px; }
.skip:focus { left:1rem; top:1rem; z-index:20; background:var(--card); color:var(--fg);
              padding:.6rem 1rem; border-radius:8px; border:1px solid var(--border-hi); }

/* ── the masthead ─────────────────────────────────────────────────────────
   Full width, because the page under it is now full width; the earlier version
   was clamped to the reading measure so that the wordmark lined up with the
   first word of the prose, which was the right call when prose was all there
   was. It is sticky and translucent, and it carries the one thing that was
   missing from every page of this site: a link into the application. */
header.site {
  position:sticky; top:0; z-index:10;
  background:rgba(27,27,31,.82);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  backdrop-filter:saturate(140%) blur(14px);
  border-bottom:1px solid var(--border);
}
header.site .bar {
  max-width:var(--frame); margin:0 auto; padding:.85rem var(--gutter);
  display:flex; align-items:center; gap:1.25rem;
}
.wordmark {
  font-weight:700; font-size:1.22rem; letter-spacing:-.035em; color:var(--fg);
  border:0; white-space:nowrap; flex:none;
}
.wordmark b { color:var(--accent); font-weight:700; }
header.site nav {
  display:flex; align-items:center; gap:1.15rem; flex:1 1 auto; min-width:0;
  font-size:.875rem; line-height:1.4; overflow-x:auto; scrollbar-width:none;
  /* One line that scrolls, not two lines that wrap: eight items wrapping onto
     a second row on a phone pushes the first sentence of every page below the
     fold. The full set is in the footer, which is where completeness belongs. */
  mask-image:linear-gradient(90deg, #000 0, #000 calc(100% - 1.5rem), transparent 100%);
}
header.site nav::-webkit-scrollbar { display:none; }
header.site nav a { color:var(--muted); border:0; white-space:nowrap; padding:.2rem 0;
                    position:relative; }
header.site nav a:hover { color:var(--fg); }
header.site nav a[aria-current="page"] { color:var(--fg); }
/* The rule under the current item, and the one that draws in on hover, are the
   same rule — inside the padding box, because the nav is a horizontal scroller
   and anything hanging below it would give the scroller a second axis. */
header.site nav a::after {
  content:''; position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--accent); transform:scaleX(0); transform-origin:left;
  transition:transform .22s cubic-bezier(.2,.6,.2,1);
}
header.site nav a:hover::after, header.site nav a[aria-current="page"]::after { transform:scaleX(1); }
header.site .btn { flex:none; }
/* Utility, not an action. Nobody can self-register, so a sign-in button is a
   door for people who are already inside — and it was the only button on the
   page. This is the quiet half of that correction. */
.signin { flex:none; color:var(--muted); border:0; font-size:.875rem; white-space:nowrap; }
.signin:hover { color:var(--fg); }
@media (max-width:34rem) { .signin { display:none; } }

/* ── actions ─────────────────────────────────────────────────────────────── */
.btn {
  display:inline-flex; align-items:center; gap:.4rem;
  background:var(--accent); color:#fff; border:1px solid var(--accent);
  padding:.72rem 1.15rem; border-radius:9px;
  font-size:.95rem; font-weight:600; line-height:1.2; letter-spacing:-.005em;
  transition:background .15s, border-color .15s, color .15s, transform .15s;
}
.btn:hover, .btn:focus-visible { background:var(--accent-hi); border-color:var(--accent-hi);
                                 color:#fff; transform:translateY(-1px); }
.btn.ghost { background:transparent; color:var(--fg); border-color:var(--border-hi); }
.btn.ghost:hover, .btn.ghost:focus-visible { background:var(--accent-soft); border-color:var(--accent);
                                             color:var(--fg); }
.btn.small { padding:.5rem .85rem; font-size:.85rem; border-radius:8px; }
.actions { display:flex; flex-wrap:wrap; gap:.7rem; margin:1.6rem 0 0; }
.actions:empty { display:none; }
/* An arrow on the one action a page is arguing for, and not on the alternative
   beside it: two arrows side by side point at nothing. */
.actions .btn:not(.ghost)::after { content:'\2192'; transition:transform .18s ease; }
.actions .btn:not(.ghost):hover::after { transform:translateX(3px); }

/* ── type ─────────────────────────────────────────────────────────────────
   Fluid, so a 1,500px laptop and a 390px phone each get a scale rather than
   one scale and a breakpoint that corrects it. Sora is a variable face capped
   at 700, so hierarchy comes from size and tracking, not from a heavier cut. */
h1, h2, h3, h4 { color:var(--fg); font-weight:700; line-height:1.15; letter-spacing:-.025em; }
h1 { font-size:clamp(2.1rem, 1.6rem + 2.1vw, 3.1rem); margin:0 0 1.4rem; }
h2 { font-size:clamp(1.5rem, 1.3rem + .8vw, 2rem); margin:3.2rem 0 1rem; letter-spacing:-.022em; }
h3 { font-size:1.14rem; font-weight:600; margin:2rem 0 .6rem; letter-spacing:-.012em; }
h4 { font-size:1rem; font-weight:600; margin:1.5rem 0 .5rem; letter-spacing:-.005em; }
p  { margin:0 0 1.15rem; }
strong { color:var(--fg); font-weight:600; }
em { font-style:italic; }

/* An article's H1 does not need the hero's size: it sits over a 38rem column,
   where 3rem wraps a headline onto four lines. */
main.prose h1 { font-size:clamp(2rem, 1.65rem + 1.4vw, 2.65rem); }

/* Where the reader is, and when this was written — on a site whose argument is
   that a figure without its year is not a figure. */
.kicker {
  max-width:var(--measure); margin:0 auto; padding:2.5rem var(--gutter) 0;
  display:flex; flex-wrap:wrap; gap:.6rem 1rem; align-items:baseline;
  font-size:.82rem; letter-spacing:.06em; text-transform:uppercase;
}
.kicker a { color:var(--accent); border:0; font-weight:600; }
.kicker a:hover { color:var(--fg); }
.kicker time { color:var(--dim); }
main.prose.article { padding-top:1.25rem; }

/* The lede: the first paragraph of an article, which its markdown italicises. */
.article > p:first-of-type { font-size:1.18rem; line-height:1.6; color:var(--fg); }
.article > p:first-of-type em { font-style:normal; }

ul, ol { margin:0 0 1.15rem; padding-left:1.4rem; }
li { margin:0 0 .5rem; }
li::marker { color:var(--dim); }

blockquote {
  margin:1.75rem 0; padding:.2rem 0 .2rem 1.25rem;
  border-left:3px solid var(--accent); color:var(--fg);
}
blockquote p:last-child { margin-bottom:0; }

hr { border:0; border-top:1px solid var(--border); margin:3rem 0; }

code { font-family:var(--mono); font-size:.88em; background:var(--accent-soft);
       color:var(--fg); padding:.12em .35em; border-radius:4px; }

/* ── a document ───────────────────────────────────────────────────────────
   Unchanged in substance: one column at a readable length. An article is not
   improved by being made into a dashboard. */
main.prose { max-width:var(--measure); margin:0 auto; padding:3rem var(--gutter) 4.5rem; }

/* ── a page ───────────────────────────────────────────────────────────────
   A stack of bands. Each is full-bleed so a tint or a rule can run edge to
   edge; `.inner` holds its content at the frame width. */
main.landing { padding:0 0 1rem; }
.band { padding:clamp(3rem, 6vw, 5.5rem) 0; }
.band > .inner { max-width:var(--frame); margin:0 auto; padding:0 var(--gutter); }
/* A page's own lead, on a banded page: it keeps the reading measure but starts
   at the frame's edge like every band under it. Constraining the CONTAINER
   centred it, so the intro sat in the middle of the page while the shelf below
   began at the left — two different pages stacked. */
.band > .inner.prose > * { max-width:var(--measure); }
.band h2:first-child, .band h3:first-child, .band h1:first-child { margin-top:0; }

/* Two reading bands in a row, which is what /trust and /library are: one answer
   per band. Without a rule between them the band padding alone reads as a wide
   paragraph gap rather than as a boundary, so the eight procurement answers run
   back together into the one column they were split out of. Scoped to the
   ADJACENT pair, so the lede band at the top of /knowledge or /what-it-does —
   which is never followed by another text band — gains no stray line. */
.band.text + .band.text { border-top:1px solid var(--border); }
/* And less air than a shelf gets. A cards or gallery band needs the full
   padding because nothing else separates it from what is above; a reading band
   has a heading of its own and, where it matters, the rule above. At the band
   default, /trust came out fifteen hundred pixels taller than the single column
   it replaced, which is paying for separation twice. */
.band.text { padding:clamp(2.25rem, 3.6vw, 3.25rem) 0; }

/* The hero. The product's own screen beside the argument for it, because a
   council officer deciding whether to read on is asking whether this is real,
   and a picture of the running software answers that faster than a paragraph. */
.band.hero {
  padding-top:clamp(2.75rem, 5vw, 4.5rem); padding-bottom:clamp(3rem, 5vw, 4.5rem);
  background:
    radial-gradient(60rem 28rem at 15% -10%, rgba(196,112,70,.17), transparent 70%),
    radial-gradient(44rem 26rem at 92% 8%, rgba(123,168,184,.10), transparent 70%);
}
.band.hero .inner { display:grid; gap:clamp(2rem, 4vw, 3.5rem); align-items:center; }
.band.hero .copy > :first-child { margin-top:0; }
.band.hero h1 { letter-spacing:-.035em; }
.band.hero .copy p { font-size:clamp(1.05rem, 1rem + .35vw, 1.22rem); line-height:1.6; max-width:34rem; }
.band.hero .copy p:first-of-type { color:var(--fg); }
.band.hero .shot figure { margin:0; }
@media (min-width:62rem) {
  .band.hero .inner { grid-template-columns:minmax(0,1fr) minmax(0,1.08fr); }
}

/* The figures, read from the registries. Big numerals, because the point of the
   band is that they are small numbers somebody can hold. */
.band.stats { padding:0; }
.band.stats > .inner { padding-top:0; padding-bottom:0; }
.band.stats ul {
  display:grid; gap:0; margin:0; padding:0; list-style:none;
  border-top:1px solid var(--border); border-bottom:1px solid var(--border);
  grid-template-columns:repeat(auto-fit, minmax(13rem, 1fr));
}
.band.stats li {
  margin:0; padding:1.6rem 1.5rem 1.7rem; border-left:1px solid var(--border);
  display:flex; flex-direction:column; gap:.3rem;
}
.band.stats li:first-child { border-left:0; }
.band.stats b {
  font-size:clamp(2rem, 1.6rem + 1.4vw, 2.7rem); font-weight:700;
  letter-spacing:-.04em; line-height:1; color:var(--fg);
  font-variant-numeric:tabular-nums;
}
.band.stats span { font-size:.9rem; line-height:1.5; color:var(--muted); }

/* A grid of things. `auto-fit` rather than a fixed count, because the registry
   decides how many capabilities there are and the stylesheet does not get a
   vote. */
.band.cards .lead { max-width:46rem; margin-bottom:2.5rem; }
.band.cards .lead > :last-child { margin-bottom:0; }
.band.cards .grid { display:grid; gap:1.1rem; grid-template-columns:repeat(auto-fit, minmax(17rem, 1fr)); }
.card {
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); padding:1.5rem 1.5rem 1.35rem;
  transition:border-color .15s, background .15s, transform .15s;
}
.card:hover { border-color:var(--border-hi); background:var(--card); transform:translateY(-2px); }
.card h3 { margin:0 0 .55rem; font-size:1.05rem; }
.card h3 a { color:var(--fg); border:0; transition:color .15s ease; }
.card:hover h3 a { color:var(--accent); }
.card p { margin:0; font-size:.95rem; line-height:1.62; color:var(--body); }
.card p + p { margin-top:.7rem; }
.band.cards .actions { margin-top:2.25rem; }

/* Prose beside a figure. The figure is the evidence for the paragraph, so they
   belong on the same screen rather than one above the other. */
/* A shelf of media. Same `auto-fit` rule as the cards above, at a width that
   keeps a 16:9 cut legible: below ~22rem a screenshot of a works programme is a
   picture of a table nobody can read, so the grid drops to one column rather
   than shrinking past it. The figures lose the document margin they get in a
   reading column — in here they fill their cell, which is the point. */
.band.gallery .lead { max-width:46rem; margin-bottom:2.5rem; }
.band.gallery .lead > :last-child { margin-bottom:0; }
.band.gallery .grid {
  display:grid; gap:clamp(1.25rem, 2.4vw, 2rem);
  grid-template-columns:repeat(auto-fit, minmax(22rem, 1fr));
}
.band.gallery figure { margin:0; }
.band.gallery figcaption { margin-top:.65rem; }
.band.gallery .actions { margin-top:2.25rem; }

.band.split { border-top:1px solid var(--border); }
.band.split .inner { display:grid; gap:clamp(2rem, 4vw, 3.5rem); align-items:center; }
.band.split .copy { max-width:38rem; }
.band.split .copy > :last-child { margin-bottom:0; }
.band.split .shot figure { margin:0; }
@media (min-width:62rem) {
  .band.split .inner { grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); }
  /* Alternate, so two splits in a row do not read as one long column. Which
     one flips is counted in the renderer; a :nth-of-type here would count
     every <section> on the page and flip both or neither. */
  .band.split.flip .inner { grid-template-columns:minmax(0,1.15fr) minmax(0,1fr); }
  .band.split.flip .copy { order:2; }
}

/* The callout. On this site it carries the sentence the whole argument rests
   on — that nobody uses this yet — so it is the one band that is tinted. */
.band.note > .inner {
  max-width:calc(var(--frame) - 8rem);
  background:var(--surface); border:1px solid var(--border);
  border-left:3px solid var(--accent);
  border-radius:var(--radius); padding:2rem clamp(1.5rem, 3vw, 2.5rem);
}
.band.note .inner > :last-child { margin-bottom:0; }
.band.note p { max-width:44rem; }

/* The close. One action, centred, with nothing competing with it. */
.band.close { border-top:1px solid var(--border); text-align:center; }
.band.close > .inner { max-width:46rem; }
.band.close p { max-width:40rem; margin-left:auto; margin-right:auto; }
.band.close .actions { justify-content:center; }
.band.close p:last-child { margin-top:1.75rem; margin-bottom:0; font-size:.875rem; color:var(--dim); }

/* ── tables ───────────────────────────────────────────────────────────────
   Tables are the one thing that genuinely needs more width than prose should
   have. Rather than widen the measure for everything, a table in a DOCUMENT
   breaks out of it, centred, up to the viewport — and below that falls back to
   scrolling. A three-column comparison clipped at the text edge is the table
   nobody reads. Inside a band it is already at the frame width and needs no
   breakout, which is why the rule is scoped. */
.table-wrap { overflow-x:auto; margin:1.75rem 0; }
@media (min-width:52rem) {
  main.prose > .table-wrap { width:min(54rem, calc(100vw - 3rem)); margin-left:50%; transform:translateX(-50%); }
  .band > .inner.prose > .table-wrap { max-width:min(54rem, 100%); }
}
table { border-collapse:collapse; width:100%; font-size:.93rem; }
th, td { text-align:left; padding:.65rem .85rem; border-bottom:1px solid var(--border);
         vertical-align:top; }
th { color:var(--fg); font-weight:600; font-size:.78rem; letter-spacing:.07em;
     text-transform:uppercase; white-space:nowrap; }
tbody tr:last-child td { border-bottom:0; }

/* ── figures ──────────────────────────────────────────────────────────────
   A capture of the running product is the site's main evidence, so it is
   framed rather than dropped in: a border so it reads as a screen, a radius so
   it does not fight the cards, and a shadow so it sits on the field instead of
   in it. In a document it breaks out of the measure, as a table does — a
   screenshot of a works programme at 38rem is a picture of a table nobody can
   read. In a band it fills its column. */
figure { margin:2.25rem 0; }
figure img, figure video {
  display:block; width:100%; height:auto; border-radius:12px;
  border:1px solid var(--border-hi); background:var(--card); box-shadow:var(--shadow);
}
figcaption { margin-top:.7rem; font-size:.875rem; line-height:1.5; color:var(--dim); }
@media (min-width:52rem) {
  main.prose > figure { width:min(54rem, calc(100vw - 3rem)); margin-left:50%; transform:translateX(-50%); }
  main.prose > figure figcaption { max-width:38rem; margin-left:auto; margin-right:auto; }
  .band > .inner.prose > figure { max-width:min(54rem, 100%); }
}
figure.video video { aspect-ratio:16/9; }

/* ── the footer ───────────────────────────────────────────────────────────
   In columns, because a flat row of the same links as the header tells a
   reader nothing — and because it let /changelog become a page reachable only
   from a sentence in the middle of another one. */
footer.site {
  border-top:1px solid var(--border); background:var(--surface);
  padding:clamp(2.75rem, 5vw, 4rem) 0 2.5rem; font-size:.9rem;
}
footer.site .cols, footer.site .fine {
  max-width:var(--frame); margin:0 auto; padding:0 var(--gutter);
}
footer.site .cols {
  display:grid; gap:2.25rem 2rem;
  grid-template-columns:repeat(auto-fit, minmax(11rem, 1fr));
}
@media (min-width:58rem) {
  footer.site .cols { grid-template-columns:2fr 1fr 1fr 1fr 1fr; }
}
footer.site .who { max-width:24rem; }
footer.site .who .wordmark { display:inline-block; margin-bottom:.85rem; }
footer.site .who p { font-size:.875rem; line-height:1.6; }
/* The one sentence on this site that must never be skimmed past sits in the
   footer, and the fine-print colour is not where you put it. */
footer.site .who .meta { color:var(--muted); font-size:.875rem; }
footer.site nav { display:flex; flex-direction:column; gap:.55rem; align-items:flex-start; }
footer.site nav h2 {
  margin:0 0 .35rem; font-size:.74rem; font-weight:600; letter-spacing:.09em;
  text-transform:uppercase; color:var(--dim);
}
footer.site nav a { color:var(--muted); border:0; font-size:.875rem; line-height:1.4; }
footer.site nav a:hover { color:var(--fg); }
footer.site .meta { color:var(--dim); font-size:.82rem; }
footer.site .fine {
  margin-top:2.75rem; padding-top:1.5rem; border-top:1px solid var(--border);
  display:flex; flex-wrap:wrap; gap:.5rem 1.5rem; justify-content:space-between;
}
footer.site .fine p { margin:0; }
footer.site .fine a { color:var(--dim); border-bottom-color:transparent; }
footer.site .fine a:hover { color:var(--fg); }

/* ── narrow ───────────────────────────────────────────────────────────────── */
@media (max-width:40rem) {
  :root { --gutter:1.15rem; }
  main.prose { padding-top:2.25rem; }
  .band.stats li { padding:1.25rem 1.15rem 1.35rem; border-left:0; border-top:1px solid var(--border); }
  .band.stats li:first-child { border-top:0; }
  .band.note > .inner { border-radius:12px; padding:1.5rem 1.25rem; }
  .actions .btn { flex:1 1 auto; justify-content:center; }
}

/* ── MOTION ───────────────────────────────────────────────────────────────
 *
 * All of it is CSS. This site ships no executable JavaScript — the only
 * `<script>` in it is the JSON-LD block, which is data — and that is a promise
 * about how the page reads rather than a coincidence of how it was built, so
 * a scroll library would have cost more than the animation is worth. The SITE
 * check fails on a `<script>` that is not JSON-LD, which is what keeps this
 * honest the next time something looks easier with a library.
 *
 * THE RULE THAT MAKES IT SAFE: no element is ever given a hidden state of its
 * own. Every reveal lives in a `@keyframes from`, so the finished page is the
 * element's natural style and a browser without scroll timelines, a reader who
 * asked for less motion, a printer and anything that cancels animations all
 * get the whole page rather than a blank one. A reveal that can hide content
 * is not worth having on a page whose argument is that it is readable.
 *
 * It is also kept short and kept early: the range completes while a band is
 * still a quarter of the way up the viewport, because an animation a reader
 * has to wait for is a slower site, not a livelier one.
 */

@keyframes rise { from { opacity:0; transform:translateY(14px); } }
@keyframes rise-sm { from { opacity:0; transform:translateY(9px); } }

/* The opening, on load. Not scroll-driven: the hero is above the fold, so
   there is no scroll to drive it with. The stagger is small enough to read as
   one movement rather than a sequence. */
@media (prefers-reduced-motion: no-preference) {
  .band.hero .copy > *, .band.hero .shot {
    animation:rise .65s cubic-bezier(.2,.6,.2,1) backwards;
  }
  .band.hero .copy > :nth-child(1) { animation-delay:.02s; }
  .band.hero .copy > :nth-child(2) { animation-delay:.08s; }
  .band.hero .copy > :nth-child(3) { animation-delay:.14s; }
  .band.hero .copy > :nth-child(n+4) { animation-delay:.2s; }
  .band.hero .shot { animation-delay:.16s; animation-duration:.8s; }
}

/* Everything below the fold, driven by the scroll rather than by a timer, so a
   reader who scrolls fast is never waiting on a fade. Gated on support twice
   over: the @supports, and the fact that the hidden state only exists inside
   the keyframes. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band.cards > .inner > .lead,
    .band.cards > .inner > .actions,
    .band.cards .card,
    .band.stats li,
    .band.split > .inner > .copy,
    .band.split > .inner > .shot,
    .band.note > .inner,
    .band.close > .inner > *,
    .band.text > .inner > *,
    main.prose > figure,
    main.prose > .table-wrap {
      animation:rise-sm linear both;
      animation-timeline:view();
      /* Both ends are `entry`, and that is load-bearing rather than taste.
         A range ending in `cover` reads "when the element is this far up the
         screen", which never arrives for the last band of a page that already
         fits in the window — the footer-end paragraphs sat permanently at 0.4
         opacity on a tall display, and a full-page capture showed the same.
         `entry 92%` is reached by anything fully in view, scrolling or not. */
      animation-range:entry 8% entry 92%;
    }
    /* One reveal per thing and never two nested: the grid itself is left
       alone so each card gets its own timeline, which staggers a row of them
       by position without a single declared delay. */
    .band.cards .grid, .band.stats > .inner > ul { animation:none; }
  }
}

/* Moving between pages, without a framework and without a router: the browser
   does it, same-origin, as a crossfade. The masthead and the footer are named,
   so they hold still while the page under them changes — which is the whole
   difference between a site that feels assembled and one that feels like a
   stack of documents. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation:auto; }
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration:.2s; }
}
header.site { view-transition-name:masthead; }
footer.site { view-transition-name:colophon; }

/* Not animation, and found while doing it: the masthead is sticky, so an
   anchor link used to scroll its own heading underneath it. */
h2[id], h3[id], h4[id] { scroll-margin-top:5.5rem; }

/* The site is dark by design. A reader who has asked for light gets a light
   field rather than an inverted one that nobody checked. */
@media (prefers-color-scheme: light) {
  :root {
    --bg:#fbf9f6; --surface:#fff; --card:#fff; --raise:#f3efe9;
    --fg:#1b1b1e; --body:#3a382f; --muted:#56534b; --dim:#67625a;
    --accent:#a2532c; --accent-hi:#8c4524; --accent-soft:rgba(162,83,44,.1);
    --border:rgba(27,27,30,.13); --border-hi:rgba(27,27,30,.22);
    --shadow:0 1px 2px rgba(27,27,30,.05), 0 14px 34px -14px rgba(27,27,30,.22);
  }
  a { border-bottom-color:rgba(162,83,44,.4); }
  header.site { background:rgba(251,249,246,.85); }
  .band.hero {
    background:
      radial-gradient(60rem 28rem at 15% -10%, rgba(162,83,44,.09), transparent 70%),
      radial-gradient(44rem 26rem at 92% 8%, rgba(60,120,140,.07), transparent 70%);
  }
  .card:hover { background:var(--raise); }
  footer.site { background:var(--raise); }
}

@media print {
  body { background:#fff; color:#000; font-size:11pt; }
  header.site, footer.site nav, footer.site .fine, .skip, .actions { display:none; }
  header.site { position:static; }
  .band { padding:1rem 0; }
  figure img { box-shadow:none; }
  /* A printed page has no scroll to drive a reveal with, so nothing may be
     waiting on one. Same argument as reduced motion, different reader. */
  *, *::before, *::after { animation:none !important; transition:none !important; }
}
