/* Tokens. build.py writes the five theme colors into :root ahead of this file.
   Every other value on the page derives from those five or from the scales
   below. Change a token here and the whole page follows. */

:root {
  /* type: two families plus lining numerals */
  --font-serif:'Iowan Old Style','Palatino Linotype','Book Antiqua',Palatino,Charter,Georgia,'Times New Roman',serif;
  --font-sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
  --font-numeral:'Times New Roman',Times,serif;
  /* The one place a third face is allowed: a command line has to be
     transcribed exactly, so the guide sets its code blocks in a mono. No
     report uses it. */
  --font-code:ui-monospace,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace;

  --t-1:13px;
  --t0:15px;
  --t1:17px;
  --t2:22px;
  --t3:28px;
  --t4:clamp(2.5rem,4.2vw,3.8125rem);

  --lh-numeral:.9;
  --lh-display:1.2;
  --lh-title:1.29;
  --lh-subhead:1.45;
  --lh-label:1.5;
  --lh-body:1.55;
  --track-label:.01em;

  /* space on a 4px base */
  --s1:4px;
  --s2:8px;
  --s3:12px;
  --s4:16px;
  --s5:24px;
  --s6:32px;
  --s7:48px;
  --s9:96px;
  --s10:128px;

  /* grid: one gutter, three role widths, one section rhythm */
  --gutter:clamp(32px,calc(8vw - 24px),64px);
  /* The measure is set in em, so it follows the body size instead of guessing
     at it. The faces in this stack run .40em to .50em a character: 30em holds
     60 characters in Iowan Old Style or Palatino and 73 in the narrowest
     fallback. The fixed 640px it replaced held 93. */
  --reading:30em;
  --reading-print:27em;
  --figure:1092px;
  --wrapper:1400px;
  --pad-wrapper:max(var(--gutter),calc((100% - var(--wrapper)) / 2 + var(--gutter)));
  --section:clamp(40px,5vw,64px);
  --bookend:clamp(var(--s7),9svh,var(--s10));
  --cover-art:48svh;
  --band:clamp(220px,38svh,320px);
  --header:68px;

  /* shape and hit area */
  --radius-card:8px;
  --radius-control:6px;
  --panel-w:min(380px,92vw);
  --progress-h:2px;
  --stage-progress-h:6px;
  --tap:44px;
  --focus-width:2px;
  --focus-offset:3px;

  /* color derived from the theme, with the ratio each pair holds on paper */
  --ink-soft:color-mix(in oklab,var(--ink) 80%,var(--paper));       /* 7.76:1 */
  --ink-strong:color-mix(in oklab,var(--ink) 88%,#000);             /* 14.57:1 */
  --muted:color-mix(in oklab,var(--ink) 62%,var(--paper));          /* 4.55:1 */
  --rule:color-mix(in oklab,var(--ink) 16%,var(--paper));           /* 1.40:1 */
  --rule-strong:color-mix(in oklab,var(--ink) 52%,var(--paper));    /* 3.43:1 */
  --card-border:color-mix(in oklab,var(--ink) 28%,var(--paper));    /* 1.85:1 */
  --line:color-mix(in oklab,var(--secondary) 70%,var(--ink));       /* 7.29:1 */
  --focus:var(--accent);                                            /* 5.74:1 */
  --select:color-mix(in oklab,var(--accent) 22%,var(--paper));
  --scrim:color-mix(in oklab,var(--paper) 70%,transparent);
  --shadow-card:0 4px 24px color-mix(in oklab,var(--ink) 6%,transparent);
  --shadow-header:0 1px 12px color-mix(in oklab,var(--ink) 9%,transparent);
  --shadow-panel:0 1px 2px color-mix(in oklab,var(--ink) 8%,transparent),0 18px 34px -20px color-mix(in oklab,var(--ink) 40%,transparent);
  --grain:180px;
  --grain-strength:.38;

  /* motion: one arrival curve, one drawing curve */
  --ease:cubic-bezier(.32,.72,0,1);
  --ease-draw:cubic-bezier(.33,1,.68,1);
  --dur-hover:140ms;
  --dur-control:160ms;
  --dur-panel:180ms;

  /* stage motion. Travel and draw overlap: the lines start at --draw-start,
     about 70 percent of the way through the travel. */
  --dur-travel:620ms;
  --dur-draw:240ms;
  --dur-erase:140ms;
  --dur-color:340ms;
  --dur-ring:380ms;
  --dur-progress:300ms;
  --dur-caption:320ms;
  --draw-start:434ms;
  --delay-erase:100ms;
  /* Per-element stage timing. report.js reads these four and writes the delay
     each line and each stroke carries, so the stagger is stated once, here. */
  --line-delay-first:14ms;
  --line-delay-last:110ms;
  --intro-mark-step:30ms;
  --intro-stroke-step:12ms;
  --card-pad:20px;
  --card-w:416px;
  --quote-rule:40px;
  /* The band's soft edge on a phone. The step slots clear it as well as the
     band, so a card at rest is never under it. */
  --band-skirt:var(--s7);
}

/* page */

* {
  box-sizing:border-box;
}

[hidden] {
  display:none!important;
}

html {
  /* The palette is paper and ink, so the browser's own chrome, its scrollbars
     and its form controls are told to match it. */
  color-scheme:light;
  /* Jumps are instant on purpose: smooth scrolling over thousands of pixels
     animates the reader through every scrolly state on the way. */
  scroll-padding-top:var(--header);
}

body {
  margin:0;
  /* The grain is a layer on the document, so the body is its containing
     block. */
  position:relative;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--font-serif);
  font-size:var(--t1);
  line-height:var(--lh-body);
}

/* The tooth of the paper. It is a layer on the document, not on the window:
   a fixed layer paints the first screen and leaves a seam at the fold in any
   full-page render or print preview. */

body:before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  z-index:20;
  opacity:var(--grain-strength);
  mix-blend-mode:multiply;
  background-size:var(--grain) var(--grain);
  background-repeat:repeat;
  background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Cpath fill='%23808080' filter='url(%23n)' opacity='.4' d='M0 0h180v180H0z'/%3E%3C/svg%3E");
}

::selection {
  background:var(--select);
  color:var(--ink);
}

a {
  color:inherit;
  text-underline-offset:var(--s1);
}

/* Text links carry a quiet underline and take the accent on hover. */

.notice a,.site-footer a,.section-methods li a,.brand-name {
  text-decoration:underline;
  text-decoration-color:var(--rule-strong);
  transition:text-decoration-color var(--dur-hover) var(--ease);
}

.notice a:hover,.site-footer a:hover,.section-methods li a:hover,.brand:hover .brand-name {
  text-decoration-color:var(--accent);
}

button,summary,.button {
  font:inherit;
  cursor:pointer;
}

button {
  color:inherit;
}

button:focus-visible,a:focus-visible,summary:focus-visible {
  outline:var(--focus-width) solid var(--focus);
  outline-offset:var(--focus-offset);
}

/* Two links sit at the size of their own type: the brand in the header and
   the template link in the notice. The pseudo-element carries the hit area
   behind them, so the target reaches --tap at every width while the type and
   the focus ring stay the size the reader sees. The inset is half of what the
   link is missing, top and bottom. */

.brand,.notice a {
  position:relative;
}

.brand:before,.notice a:before {
  content:'';
  position:absolute;
  inset:calc((var(--tap) - 100%) / -2) 0;
}

/* type */

h1,h2,h3,p {
  margin-top:0;
}

h1,h2,h3 {
  font-weight:400;
  text-wrap:balance;
}

h1 {
  font-size:var(--t4);
  line-height:var(--lh-display);
  letter-spacing:normal;
  margin-bottom:var(--s5);
}

h2 {
  font-size:var(--t3);
  line-height:var(--lh-title);
  letter-spacing:normal;
  margin-bottom:var(--s6);
}

h3 {
  font-size:var(--t2);
  line-height:var(--lh-subhead);
  letter-spacing:normal;
  margin-bottom:var(--s3);
}

p {
  margin-bottom:var(--s5);
}

/* Every section heading is a link to its own section. It carries no
   decoration at rest, so a heading still reads as a heading; the underline
   arrives on hover the way it does on every other text link. The pseudo
   element carries the hit area, so a one line heading still reaches --tap
   without a 44px line box. */

.heading-anchor {
  position:relative;
  color:inherit;
  text-decoration:none;
  transition:text-decoration-color var(--dur-hover) var(--ease);
}

.heading-anchor:before {
  content:'';
  position:absolute;
  inset:calc((var(--tap) - 100%) / -2) 0;
}

.heading-anchor:hover {
  text-decoration:underline;
  text-decoration-color:var(--accent);
}

.eyebrow {
  display:block;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  font-variant-numeric:tabular-nums;
  color:var(--muted);
  margin-bottom:var(--s3);
}

/* header */

.site-header {
  position:sticky;
  top:0;
  z-index:10;
  min-height:var(--header);
  background:var(--paper);
  border-bottom:1px solid var(--rule);
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:var(--s5);
  padding-inline:var(--pad-wrapper);
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  transition:box-shadow var(--dur-control) var(--ease);
}

/* The rule alone is too quiet once the page is moving under it. */

.site-header.is-scrolled {
  box-shadow:var(--shadow-header);
}

.brand {
  display:flex;
  gap:var(--s3);
  align-items:center;
  text-decoration:none;
}

.brand-mark {
  font-family:var(--font-serif);
  font-size:var(--t2);
  line-height:1;
  letter-spacing:-.03em;
  color:var(--accent);
}

.brand-name {
  border-left:1px solid var(--rule);
  padding-left:var(--s3);
  text-decoration-color:transparent;
}

.header-actions {
  display:flex;
  align-items:center;
  gap:var(--s3);
}

.contents summary {
  list-style:none;
  display:flex;
  align-items:center;
  gap:var(--s2);
  min-height:var(--tap);
  padding-inline:var(--s3);
  border-radius:var(--radius-control);
  transition:background var(--dur-hover) var(--ease);
}

.contents summary:hover {
  background:var(--wash);
}

/* The scrim starts below the header so the control stays reachable.
   A click on it reports the <details> as the event target. */

.contents[open]:before {
  content:'';
  position:fixed;
  inset:var(--header) 0 0;
  background:var(--scrim);
}

.contents summary:after {
  content:'+';
}

.contents[open] summary:after {
  content:'\2212';
}

.contents nav {
  position:absolute;
  top:100%;
  right:var(--pad-wrapper);
  margin-top:-1px;
  padding:var(--s2);
  background:var(--paper);
  border:1px solid var(--card-border);
  border-radius:var(--radius-card);
  width:var(--panel-w);
  box-shadow:var(--shadow-panel);
}

.contents[open] nav {
  animation:panel var(--dur-panel) var(--ease);
}

@keyframes panel {

  from {
    opacity:0;
    transform:translateY(calc(var(--s2) * -1));
  }

}

.contents nav a {
  display:flex;
  align-items:center;
  min-height:var(--tap);
  padding-inline:var(--s3);
  font-size:var(--t0);
  text-decoration:none;
  border-bottom:1px solid var(--rule);
  border-radius:var(--radius-control);
  transition:background var(--dur-hover) var(--ease),color var(--dur-hover) var(--ease),box-shadow var(--dur-hover) var(--ease);
}

.contents nav a:last-child {
  border-bottom:0;
}

.contents nav a:hover {
  background:var(--wash);
  box-shadow:inset 2px 0 0 var(--accent);
}

/* The reader's current section keeps full ink; the rest step back. */

.contents nav:has(a[aria-current]) a {
  color:var(--muted);
}

.contents nav a[aria-current] {
  color:var(--ink);
  box-shadow:inset 2px 0 0 var(--accent);
}

.progress {
  position:absolute;
  bottom:-1px;
  left:0;
  height:var(--progress-h);
  width:100%;
  background:var(--rule-strong);
  transform:scaleX(0);
  transform-origin:left;
}

.skip-link {
  position:fixed;
  top:calc(var(--s10) * -1);
  left:var(--s4);
  background:var(--ink);
  color:var(--paper);
  padding:var(--s3);
  border-radius:var(--radius-control);
  z-index:30;
}

.skip-link:focus {
  top:var(--s2);
}

/* cover */

/* Three horizontal bands: masthead, headline beside the art, footnotes. */

.hero {
  min-height:calc(100svh - var(--header));
  display:grid;
  grid-template-rows:auto 1fr auto;
  gap:var(--s7);
  padding-block:var(--bookend);
  padding-inline:var(--pad-wrapper);
  position:relative;
}

/* The cover's two rules are band edges, like the header's and the notice's,
   so they run to the page edge instead of stopping inside the wrapper.
   --pad-wrapper holds a 100% that resolves against whatever element uses it,
   so a child already inset by --hero's own padding gets back a narrower
   number than --hero itself did. --hero-pad states the same formula in
   viewport width instead, which every descendant reads the same way, so the
   rule and the header above it land on the same edge at every width. */

.hero {
  --hero-pad:max(var(--gutter),calc((100vw - var(--wrapper)) / 2 + var(--gutter)));
}

.hero-top,.hero-bottom {
  margin-inline:calc(var(--hero-pad) * -1);
  padding-inline:var(--hero-pad);
}

.hero-top {
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:var(--s5);
  border-bottom:1px solid var(--rule);
  padding-bottom:var(--s4);
}

.hero-top .eyebrow {
  margin-bottom:0;
}

.hero-main {
  display:grid;
  grid-template-columns:1.15fr 1fr;
  align-items:center;
  gap:var(--s7);
}

.hero-art {
  position:relative;
  min-width:0;
  margin:0;
}

/* The drawing is capped by the viewport so the whole cover stays in frame. */

.hero-art .ink-art {
  width:auto;
  max-width:100%;
  max-height:var(--cover-art);
  margin-inline:auto;
}

.ink-art {
  display:block;
  width:100%;
  overflow:visible;
}

.paper-grid {
  fill:none;
  stroke:var(--ink);
  stroke-width:.5;
  opacity:.16;
}

/* Marks are placed in view-box coordinates, so the CSS transform report.js
   writes and the transform attribute build.py writes mean the same thing.
   Each mark keeps its own stroke-width from build.py. */

.mark {
  fill:none;
  stroke:var(--ink);
  stroke-linecap:round;
  transform-box:view-box;
  transform-origin:0 0;
}

/* Transitions wait for the first state, so nothing travels in from the
   corner on load. */

.is-live .mark {
  transition:transform var(--dur-travel) var(--ease) var(--md,0ms),stroke var(--dur-color) var(--ease);
}

.mark.is-highlight {
  stroke:var(--accent);
}

.connections {
  stroke:var(--line);
  stroke-width:1.1;
  fill:none;
  opacity:.85;
}

/* Lines are drawn, not revealed, and they start while the marks are still
   travelling. Old lines clear out under them. */

.connections path {
  stroke-dasharray:var(--len,600);
  animation:draw-line var(--dur-draw) var(--ease-draw) calc(var(--draw-start) + var(--d,0ms)) both;
  transition:opacity var(--dur-erase) var(--ease) var(--delay-erase);
}

.connections path.is-leaving {
  animation:none;
  opacity:0;
}

@keyframes draw-line {

  from {
    stroke-dashoffset:var(--len,600);
  }

  to {
    stroke-dashoffset:0;
  }

}

/* The cover draws its own strokes in, once per load. Every stroke carries
   pathLength from build.py, so --stroke-len is one dash for all of them and
   no stroke is drawn short. */

.is-intro .mark path {
  stroke-dasharray:var(--stroke-len);
  animation:draw-stroke var(--dur-draw) var(--ease-draw) calc(var(--i,0) * var(--intro-mark-step) + var(--j,0) * var(--intro-stroke-step)) both;
}

@keyframes draw-stroke {

  from {
    stroke-dashoffset:var(--stroke-len);
  }

  to {
    stroke-dashoffset:0;
  }

}

/* --i is the mark's index and --j the stroke's, both written by build.py, so
   a mark of any stroke count staggers from one rule. */

/* The ring and its label arrive after the lines that point at them. */

.center-ring {
  fill:none;
  stroke:var(--accent);
  stroke-width:.8;
  opacity:0;
  transition:opacity var(--dur-ring) var(--ease) var(--ring-delay,0ms);
}

/* The size lives on the element, in frame units, because the label has to
   hold its own against the drawing at every scale the drawing is used at. */

.center-label {
  font-family:var(--font-sans);
  fill:var(--ink);
  opacity:0;
  transition:opacity var(--dur-ring) var(--ease) var(--ring-delay,0ms);
}

.hero-art figcaption {
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  text-align:center;
  color:var(--muted);
  margin-top:var(--s5);
}

.hero-copy {
  min-width:0;
}

/* The deck is prose, so it takes the prose measure. The cover column is wider
   than the measure at desktop widths and would otherwise run past 70
   characters a line. */

.standfirst {
  max-width:var(--reading);
  color:var(--ink-soft);
  margin-bottom:var(--s6);
}

.byline {
  display:flex;
  flex-wrap:wrap;
  justify-content:flex-end;
  gap:var(--s1) var(--s3);
  margin:0;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  text-align:right;
}

.byline-date {
  color:var(--muted);
}

.hero-actions {
  display:flex;
  gap:var(--s3);
  flex-wrap:wrap;
}

.hero-bottom {
  display:flex;
  justify-content:space-between;
  gap:var(--s5);
  border-top:1px solid var(--rule);
  padding-top:var(--s4);
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
}

/* A lone tagline already sits left under space-between. A lone cue needs to
   sit right instead, so push it there with an auto margin. */

.hero-bottom .scroll-cue:only-child {
  margin-inline-start:auto;
}

/* controls */

.button,.control {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:var(--s2);
  min-height:var(--tap);
  padding:var(--s2) var(--s4);
  border:1px solid var(--rule-strong);
  border-radius:var(--radius-control);
  background:none;
  color:var(--ink);
  text-decoration:none;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  transition:background var(--dur-control) var(--ease),border-color var(--dur-control) var(--ease),color var(--dur-control) var(--ease),transform var(--dur-control) var(--ease);
}

.button:hover,.control:hover {
  background:var(--wash);
  transform:translateY(-1px);
}

.button:active,.control:active {
  transform:none;
}

.button.primary {
  background:var(--ink);
  border-color:var(--ink);
  color:var(--paper);
}

.button.primary:hover {
  background:var(--ink-strong);
  border-color:var(--ink-strong);
  color:var(--paper);
}

.motion-icon {
  flex:none;
}

.motion-button .icon-play,.motion-button[aria-pressed="true"] .icon-pause {
  display:none;
}

.motion-button[aria-pressed="true"] .icon-play {
  display:inline;
}

/* notice */

.notice {
  display:flex;
  justify-content:space-between;
  flex-wrap:wrap;
  gap:var(--s3) var(--s5);
  padding-block:var(--s3);
  padding-inline:var(--pad-wrapper);
  border-block:1px solid var(--rule);
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
}

/* sections: one flat rhythm, three role widths */

.section {
  padding-block:var(--section);
  padding-inline:var(--gutter);
}

.section > :last-child {
  margin-bottom:0;
}

.reading,.section-heading {
  width:min(var(--reading),100%);
  margin-inline:auto;
}

/* A heading opens the block under it, so it takes that block's role width.
   The title itself still sets on the reading measure. */

.section-findings .section-heading,.section-scrolly .section-heading {
  width:min(var(--figure),100%);
}

.section-heading {
  margin-bottom:var(--s7);
}

.section-heading h2 {
  max-width:var(--reading);
}

.section-heading h2 {
  margin-bottom:0;
}

/* scrolly. The frame pins for the whole section; the cards run past it, one
   card to a screen, and each card's state fires as the card appears. */

/* The card column is the card's own width, so the card's right edge lands on
   the figure's right edge instead of eight pixels inside it. */

.scrolly {
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,1fr) var(--card-w);
  gap:var(--s6);
  width:min(var(--figure),100%);
  margin-inline:auto;
}

.stage {
  --art-width:min(100%,calc((var(--stage-h-safe,700px) - var(--s9)) * 4 / 3));
  margin:0;
  position:sticky;
  top:0;
  height:100vh;
  height:100svh;
  padding-top:calc(var(--header) + var(--s1));
  display:flex;
  flex-direction:column;
  justify-content:center;
  align-self:start;
  min-width:0;
}

/* report.js writes --stage-h-safe as the viewport minus the header, so the
   drawing is always inside the pinned frame. */

.stage .ink-art {
  width:var(--art-width);
  margin-inline:auto;
}

.stage figcaption {
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
  text-align:center;
  margin:var(--s4) auto 0;
  animation:caption-in var(--dur-caption) var(--ease) both;
}

@keyframes caption-in {

  from {
    opacity:0;
  }

}

.stage-progress {
  width:var(--art-width);
  height:var(--stage-progress-h);
  margin:var(--s5) auto 0;
  border-radius:calc(var(--stage-progress-h) / 2);
  background:var(--wash);
  overflow:hidden;
}

/* --rule-strong holds 3:1 on paper but only 2.99:1 on the wash the track is
   filled with, so the fill takes --muted, which holds 3.96:1 on it. */

.stage-progress i {
  display:block;
  height:100%;
  background:var(--muted);
  transform:scaleX(0);
  transform-origin:left;
  transition:transform var(--dur-progress) var(--ease);
}

/* The column is led in by one stage height, then pulled back by half a step,
   so the first card meets the frame it explains. */

.steps {
  padding-top:100vh;
  padding-top:100svh;
  margin-top:-56vh;
  margin-top:-56svh;
  pointer-events:none;
}

.steps:after {
  content:'';
  display:block;
  height:80vh;
  height:80svh;
}

.step {
  min-height:112vh;
  min-height:112svh;
  display:flex;
  align-items:center;
}

.step-card {
  pointer-events:auto;
  width:min(var(--card-w),100%);
  padding:var(--card-pad);
  background:var(--paper);
  border:1px solid var(--card-border);
  border-radius:var(--radius-card);
  box-shadow:var(--shadow-card);
}

.step-card .eyebrow {
  color:var(--accent);
}

.step-card h3 {
  font-family:var(--font-sans);
  font-weight:600;
  font-size:var(--t1);
  line-height:var(--lh-body);
}

.step-card p {
  margin-bottom:0;
}

/* This is the accessible description of the drawing. The stage figcaption is
   the sighted copy and is hidden from assistive technology, so this one is
   hidden from the eye instead of from the page: it stays in the accessibility
   tree, in reading order, inside the card it belongs to. */

.step-card .static-caption {
  position:absolute;
  width:1px;
  height:1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  color:var(--muted);
  margin-top:var(--s3);
}

/* report.js fills this with one static state per step, for print only. */

.print-figure {
  display:none;
}

/* findings */

.section-findings {
  border-top:1px solid var(--rule);
  border-bottom:1px solid var(--rule);
}

/* Three across, or two when a pair or a set of four would leave a hole. */

.findings {
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:var(--s7);
  width:min(var(--figure),100%);
  margin-inline:auto;
}

.findings[data-count="1"] {
  grid-template-columns:1fr;
}

.findings[data-count="2"],.findings[data-count="4"] {
  grid-template-columns:repeat(2,1fr);
}

.findings article {
  border-top:1px solid var(--rule);
  padding-top:var(--s4);
}

/* Every numeral is one line in a box of one height, so a row of statistics
   sits on a single baseline. */

.finding-value {
  display:block;
  font-family:var(--font-numeral);
  font-variant-numeric:lining-nums tabular-nums;
  font-size:var(--t4);
  line-height:var(--lh-numeral);
  letter-spacing:-.02em;
  color:var(--accent);
}

.finding-unit {
  display:block;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
  margin-top:var(--s2);
}

.findings h3 {
  font-family:var(--font-sans);
  font-weight:600;
  font-size:var(--t1);
  line-height:var(--lh-body);
  margin-top:var(--s5);
}

.findings p {
  color:var(--muted);
  margin-bottom:0;
}

.finding-note {
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  margin-top:var(--s3);
}

/* quote */

/* A short accent rule opens the quote. No glyph, no fill, no centering. */

blockquote {
  width:min(var(--reading),100%);
  margin-inline:auto;
}

blockquote:before {
  content:'';
  display:block;
  width:var(--quote-rule);
  border-top:2px solid var(--accent);
  margin-bottom:var(--s5);
}

/* The quote takes the reading column and nothing narrower: at --t3 that is
   under 40 characters a line, which is already display measure. */

blockquote p {
  font-size:var(--t3);
  line-height:var(--lh-title);
  text-wrap:balance;
  margin-bottom:var(--s5);
}

.quote-attribution {
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
  margin-bottom:0;
}

/* The design carries no italics, and a citation is not an aside. */

.quote-attribution cite {
  font-style:normal;
}

/* methods */

.section-methods {
  background:var(--wash);
  --muted:color-mix(in oklab,var(--ink) 66%,var(--wash));
}

/* Two columns on the band: the note on the reading measure and the citations
   beside it, both inside the figure. One column of prose in the middle of a
   full-bleed tint leaves the band empty on both sides. */

.methods {
  display:grid;
  grid-template-columns:minmax(0,var(--reading)) minmax(0,1fr);
  gap:var(--s7);
  width:min(var(--figure),100%);
  margin-inline:auto;
}

/* Fewer than two sources leaves the citation column mostly empty tint beside
   the note, so stack the citations under it in one column instead. */

.section-methods[data-sources="0"] .methods,
.section-methods[data-sources="1"] .methods {
  grid-template-columns:minmax(0,1fr);
}

.methods .reading {
  width:auto;
  margin-inline:0;
}

/* The sources are a citation list, not a browser default: a leading-zero
   numeral in the accent, a hairline row, and the host under the label so a
   reader can see where a citation points before following it. */

.section-methods ol {
  list-style:none;
  margin:0;
  padding:0;
  counter-reset:source;
}

.section-methods li {
  display:grid;
  grid-template-columns:var(--s6) 1fr;
  align-items:start;
  gap:0 var(--s3);
  padding-bottom:var(--s3);
  border-top:1px solid var(--rule);
}

.section-methods li:before {
  counter-increment:source;
  content:counter(source,decimal-leading-zero);
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--tap);
  letter-spacing:var(--track-label);
  font-variant-numeric:tabular-nums;
  color:var(--accent);
}

.section-methods li a {
  display:inline-flex;
  align-items:center;
  justify-self:start;
  min-height:var(--tap);
}

.source-host {
  grid-column:2;
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
  overflow-wrap:anywhere;
}

/* footer */

/* A colophon: who published it and when on the left, where it lives and what
   the reader can do with it on the right. */

.site-footer {
  display:grid;
  grid-template-columns:1fr auto;
  gap:var(--s5) var(--s7);
  align-items:start;
  border-top:1px solid var(--rule);
  padding-block:var(--bookend);
  padding-inline:var(--pad-wrapper);
  font-family:var(--font-sans);
  font-size:var(--t-1);
  line-height:var(--lh-label);
  letter-spacing:var(--track-label);
  color:var(--muted);
}

.colophon,.colophon-links {
  display:flex;
  flex-direction:column;
  gap:var(--s2);
  min-width:0;
}

/* Each link carries a 44px hit box, so the column starts high enough to put
   its first line on the colophon's first line. */

.colophon-links {
  align-items:end;
  text-align:right;
  margin-top:calc(var(--s3) * -1);
}

.colophon-org {
  color:var(--ink);
}

.colophon-links a {
  display:inline-flex;
  align-items:center;
  min-height:var(--tap);
  max-width:100%;
  overflow-wrap:anywhere;
}

/* paused and reduced motion share one path: states change, nothing animates */

.no-motion *,.no-motion *:before,.no-motion *:after {
  transition-duration:.01ms!important;
  transition-delay:0s!important;
  animation-duration:.01ms!important;
  animation-delay:0s!important;
}

/* Scripts off. All text is already in the document, but the stage has no
   state to show and nothing to pin, so the scrolly stops being a staged
   column: the drawing sits once at --figure, then the steps run down the
   reading column in the order they are written. */

html:not(.enhanced) .scrolly {
  display:block;
}

html:not(.enhanced) .stage {
  position:relative;
  top:auto;
  z-index:auto;
  width:min(var(--figure),100%);
  height:auto;
  margin-inline:auto;
  padding:0;
  background:none;
  box-shadow:none;
}

/* The drawing carries its own paint from build.py, so it reads without a
   state. It is capped like the cover's, so it opens the section instead of
   filling a screen. */

html:not(.enhanced) .stage .ink-art {
  width:auto;
  max-width:100%;
  max-height:var(--cover-art);
}

/* The stage caption names a state the reader cannot reach here, and the card
   carries its own caption instead. */

html:not(.enhanced) .stage:after,html:not(.enhanced) .steps:after,html:not(.enhanced) .stage-progress,html:not(.enhanced) .stage figcaption {
  display:none;
}

html:not(.enhanced) .steps {
  padding-top:0;
  margin-top:var(--s6);
}

html:not(.enhanced) .step {
  display:block;
  width:min(var(--reading),100%);
  min-height:0;
  margin-inline:auto;
  padding:0;
}

/* Nothing here can become active, so a card is a block of text under a
   hairline and carries no other chrome. */

html:not(.enhanced) .step-card {
  width:auto;
  padding:var(--s5) 0;
  background:none;
  border:0;
  border-top:1px solid var(--rule);
  border-radius:0;
  box-shadow:none;
}

/* With scripts off the stage shows one state and no caption, so the card's
   caption is the only one and it is shown. */

html:not(.enhanced) .static-caption {
  position:static;
  width:auto;
  height:auto;
  overflow:visible;
  clip-path:none;
  white-space:normal;
}

/* responsive. The stage gives up its column first, at 1000px, because below
   that the drawing and a readable card cannot share a row. The rest of the
   page stacks at 760px. */

@media(max-width:1000px) {

  .scrolly {
    display:block;
  }

  .stage {
    top:var(--header);
    height:var(--band);
    padding-top:0;
    background:var(--paper);
    z-index:2;
    box-shadow:0 1px 0 var(--rule);
    margin-inline:calc(var(--gutter) * -1);
    padding-inline:var(--gutter);
  }

  /* Card text passes under the band instead of being sliced by it. The edge
     starts as solid paper so a heading crossing it dims for less of its own
     height. */

  .stage:after {
    content:'';
    position:absolute;
    top:100%;
    left:0;
    right:0;
    height:var(--band-skirt);
    background:linear-gradient(var(--paper) 25%,transparent);
    pointer-events:none;
  }

  .stage .ink-art,.stage-progress {
    width:100%;
  }

  .stage .ink-art {
    height:calc(100% - var(--s9));
  }

  .stage figcaption,.stage-progress {
    margin-top:var(--s3);
  }

  /* The band sits at the top of the section, so the column needs no lead-in
     and no tail. One slot is one screen: the card is alone in the frame and
     the trigger still fires as it appears. */

  .steps {
    padding-top:0;
    margin-top:0;
  }

  /* The card rests in the middle of what the band leaves, not in the middle
     of the slot, so it is never half under the band. It clears the soft edge
     as well as the band, so a card at rest is never dimmed by it. */

  .step {
    min-height:100vh;
    min-height:100svh;
    padding-top:calc(var(--header) + var(--band) + var(--band-skirt));
  }

  /* The card keeps its own width and centers under the band, so a card is one
     width everywhere and the band above it always overhangs it. */

  .step-card {
    margin-inline:auto;
  }

  .methods {
    grid-template-columns:minmax(0,1fr);
  }

}

@media(max-width:760px) {

  .hero-top {
    flex-direction:column;
    align-items:flex-start;
    gap:var(--s2);
  }

  .byline {
    justify-content:flex-start;
    text-align:left;
  }

  .hero-main {
    grid-template-columns:1fr;
    gap:var(--s6);
  }

  .hero-copy {
    grid-row:1;
  }

  .step-card {
    --card-pad:var(--s4);
  }

  .findings {
    grid-template-columns:1fr;
    gap:var(--s6);
  }

  .site-footer {
    grid-template-columns:1fr;
  }

  .colophon-links {
    align-items:start;
    text-align:left;
    margin-top:0;
  }

}

@media(max-width:420px) {

  .brand-name {
    display:none;
  }

  .motion-button .motion-label {
    display:none;
  }

  .contents nav {
    left:var(--gutter);
    right:var(--gutter);
    width:auto;
  }

}

/* The reader's own setting is the default, and the motion control is the
   reader overruling it. Without the .motion-on gate the control would report
   a state the page does not have. */

@media(prefers-reduced-motion:reduce) {

  :root:not(.motion-on) *,:root:not(.motion-on) *:before,:root:not(.motion-on) *:after {
    transition-duration:.01ms!important;
    transition-delay:0s!important;
    animation-duration:.01ms!important;
    animation-delay:0s!important;
  }

}

/* print. Paper is white, ink is black, and everything that only makes sense on
   a screen goes away: the grain, the sticky frames, the shadows, the card
   fills and the controls. The scrolly cannot scroll on paper, so it prints as
   small multiples with the captions under the states. */

@media print {

  :root {
    --paper:#fff;
    --ink:#000;
    --wash:#fff;
    --rule:color-mix(in oklab,var(--ink) 30%,var(--paper));
    --shadow-card:none;
    --shadow-header:none;
    --shadow-panel:none;
  }

  body {
    background:var(--paper);
    color:var(--ink);
    font-size:11pt;
    line-height:1.5;
  }

  /* The stage goes whole: the print figure carries the drawing and the
     captions, so nothing prints twice. */

  body:before,.skip-link,.header-actions,.progress,.hero-actions,.hero-bottom,
  .steps:after,.back-to-top,
  .site-footer button {
    display:none!important;
  }

  /* report.js marks a scrolly section print-ready once its small multiples
     exist. Scripts off, or a section whose copies could not be built, keeps
     this rule from matching, so the fallback stage and its static captions
     stay on the printed page instead of both being lost. */

  .print-ready .stage,.print-ready .step-card .static-caption {
    display:none!important;
  }

  p {
    orphans:2;
    widows:2;
  }

  .eyebrow,h2,h3 {
    break-after:avoid;
  }

  /* The header keeps the brand and hands the title to the cover under it. */

  .site-header {
    position:static;
    min-height:0;
    padding:0 0 var(--s3);
    border-bottom:1px solid var(--rule);
  }

  /* A label and the heading it belongs to never land on separate sheets. */

  .section-heading {
    break-inside:avoid;
    break-after:avoid;
  }

  .hero {
    min-height:0;
    padding:var(--s5) 0;
    gap:var(--s5);
  }

  .hero h1 {
    font-size:24pt;
  }

  .hero .ink-art {
    max-height:60mm;
  }

  /* A printed page is narrower than the browser window that laid the cover
     out, so the deck needs the same measure the body has. */

  .standfirst {
    max-width:var(--reading-print);
  }

  .section {
    padding:var(--s5) 0;
    break-inside:auto;
  }

  .section h2 {
    font-size:17pt;
  }

  .section h3 {
    font-size:12pt;
  }

  /* Measure on paper. The column is set in em so it follows the printed body
     size, and 27em is what holds this serif under 68 characters a line. */

  .reading,.section-heading {
    max-width:var(--reading-print);
    width:auto;
    margin-inline:0;
  }

  /* On paper every section starts at the page margin. A centered column would
     leave a third of the sheet empty down the left side. */

  blockquote,.step-card {
    margin-inline:0;
  }

  .methods {
    display:block;
  }

  .section-methods ol {
    margin-top:var(--s5);
  }

  .scrolly {
    display:block;
  }

  .steps {
    padding-top:0;
    margin-top:0;
  }

  .step {
    min-height:0;
    padding:0 0 var(--s4);
    break-inside:avoid;
  }

  /* A step opens with a hairline, like a finding does, and starts at the
     page margin. An indent would make these four blocks the only content on
     the sheet with a left edge of their own. */

  .step-card {
    width:auto;
    max-width:var(--reading-print);
    margin:0;
    padding:var(--s3) 0 0;
    background:none;
    border:0;
    border-top:1px solid var(--rule);
    border-radius:0;
    box-shadow:none;
  }

  /* The four states sit in a row above the four step texts. Their animation
     never runs inside a hidden container, so the lines are drawn outright. */

  /* Two by two, not four across: at a quarter of the text block a state is
     too small to read and the word inside the ring disappears. */

  .print-figure {
    display:grid;
    grid-template-columns:repeat(2,1fr);
    gap:var(--s5) var(--s6);
    margin-bottom:var(--s6);
  }

  /* The grid itself no longer avoids a break: a long report's small
     multiples have to split across pages. Each figure still avoids one, so
     a state and its caption are never cut in half. */

  .print-figure figure {
    margin:0;
    break-inside:avoid;
  }

  .print-figure .ink-art {
    width:100%;
  }

  .print-figure .connections path {
    animation:none;
    stroke-dasharray:none;
  }

  .print-figure figcaption {
    font-family:var(--font-sans);
    font-size:8pt;
    line-height:var(--lh-label);
    color:var(--muted);
    margin-top:var(--s2);
  }

  /* Paper is narrower than the screen the grid was written for, so the row
     packs itself from the space it has instead of collapsing to one column. */

  .findings {
    grid-template-columns:repeat(auto-fit,minmax(38mm,1fr));
    gap:var(--s5);
    break-inside:avoid;
  }

  .findings article {
    break-inside:avoid;
  }

  .finding-value {
    font-size:30pt;
  }

  .section-quote {
    break-inside:avoid;
  }

  blockquote p {
    font-size:16pt;
  }

  .notice {
    padding-inline:0;
    background:none;
  }

  .site-footer {
    padding:var(--s5) 0 0;
    grid-template-columns:1fr;
    gap:var(--s2);
  }

  .colophon-links {
    align-items:start;
    text-align:left;
    margin-top:0;
  }

  .colophon-links a {
    min-height:0;
  }

  @page {
    margin:18mm 26mm;
  }

}
