/*
 * The site's one stylesheet, in cascade order: the design tokens; the base, the elements' defaults; the layout of
 * the page, its sections and its card grid; the components every section uses; then each section's own rules, in the
 * page's order, under the banners Header, Hero, Overview donuts, Traffic Explorer, Insights, Cite and Team, and
 * Footer; and last the reduced-motion rule. A later rule wins over an earlier one of the same specificity, so a
 * component's rules follow the base it changes, and each media rule follows directly the rule it changes.
 *
 * Colours are tokens on :root, redefined for dark mode; how a chart takes the series slots is the site spec's
 * "Chart colours" (openspec/specs/site/spec.md). Components are named after what they are, not where they sit.
 */

@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Crimson+Text:wght@600;700&display=swap");

/* ---------- Tokens ---------- */

/*
 * The light scheme is Open Data's: a warm paper, white cards, a warm ink and the Harvard crimson. The dark scheme below
 * turns the same inks over. tests/palette_contrast.test.js reads both blocks and checks every colour's contrast.
 */
:root {
  color-scheme: light;

  --font-sans: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-serif: "Crimson Text", Georgia, Cambria, "Times New Roman", serif;
  --font-mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
  --font-size-smallest: 0.8125rem; /* 14.6 px at the 18 px base: no caption, label, cell or tick is set smaller */

  --color-accent: #a51c30;
  --color-accent-strong: #7f1425; /* the accent under the pointer: darker in light, lighter in dark */
  --color-accent-wash: #fbf0f2;
  --color-on-accent: #ffffff; /* text on a field of the accent, such as the primary button */

  --color-text: #1c1917;
  --color-text-muted: #49423e;
  --color-text-faint: #716965;
  --color-background: #f8f7f5;
  --color-surface: #ffffff;
  --color-surface-raised: #f1efec;
  --color-border: #e4dfdb;

  --shadow-card: 0 1px 2px rgb(28 25 23 / 0.04), 0 12px 32px -20px rgb(28 25 23 / 0.22);
  --radius: 0.9rem;
  --radius-small: 0.375rem;
  --radius-button: 0.65rem;
  --page-width: 76rem; /* Open Data's width: the header's links fit one row in it, and the header, page and footer align */
  --header-height: 4.5rem; /* the sticky header's height on one row; where its links wrap below, the page measures it */
  --loading-card-height: 26rem; /* about a drawn figure card's height, so a loading card holds the place of one */
  --gutter: clamp(1rem, 4vw, 3.5rem); /* Open Data's gutter, with the phone's 1rem kept so 360 px still fits */

  /* Chart ink: recessive grid and axes, muted labels. */
  --chart-grid: #e4dfdb;
  --chart-axis: #c4bcb7;
  --chart-label: var(--color-text-faint);

  /*
   * Categorical series, in the fixed order charts assign them. A ninth series folds into "Other".
   * Eight OKLCH hues, the same in both schemes: blue 255°, orange 50°, green 143°, amber 81°, magenta 340°, teal 192°,
   * violet 293° and red 18°, no two closer than 30°. In light most slots are the lightest colour of their hue that
   * keeps 3.3:1 on the white card, at the most chroma sRGB holds there (capped at 0.19, short of neon). Five are set
   * apart in lightness so that a reader with red-green colour blindness can still tell every pair apart: blue, teal,
   * red and amber deeper (3.8 to 4:1), and green deeper still (4.4:1), where it would otherwise merge with amber and
   * red. Amber leans towards orange (81°) so that it reads golden rather than olive.
   *
   * The ninth and tenth are the families' alone, as there are ten named families and the Traffic Explorer stacks them
   * all: olive 112° and petrol 224°, in the two gaps of the wheel wide enough to keep 30° from every slot. Both are much
   * deeper than the eight (7:1), so that a red-green colour-blind reader tells them from amber, green, teal and blue.
   */
  --series-1: #1a82f2;
  --series-2: #df6d19;
  --series-3: #0a8d12;
  --series-4: #a67700;
  --series-5: #dd5db9;
  --series-6: #19918e;
  --series-7: #9a76fa;
  --series-8: #ed3d57;
  --series-9: #55581e;
  --series-10: #0d6179;
  --series-other: #938a86;
}

/*
 * The dark scheme: Open Data's ink as the paper, each surface one step lighter, the inks turned over with the same
 * warmth, and the crimson lifted to keep its contrast. The series keep their light hues; most are the darkest colour of
 * their hue that keeps 5.5:1 on the dark card, blue and teal a step darker (4.8:1) and amber much lighter (8:1), so
 * every pair stays apart for a red-green colour-blind reader here too; the families' olive is light (7.6:1) and their
 * petrol a step darker than the eight (5.4:1), for the same reason.
 */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --color-accent: #f07a9a; /* crimson turned pink, so that it is never taken for the red series */
    --color-accent-strong: #f6a3bb;
    --color-accent-wash: rgb(240 122 154 / 0.14);
    --color-on-accent: #1c1917;

    --color-text: #f1efec;
    --color-text-muted: #cfc7c2;
    --color-text-faint: #a69d98;
    --color-background: #1c1917;
    --color-surface: #262220;
    --color-surface-raised: #302b28;
    --color-border: #3d3633;

    --shadow-card: 0 1px 2px rgb(0 0 0 / 0.3), 0 12px 32px -20px rgb(0 0 0 / 0.6);

    --chart-grid: #3d3633;
    --chart-axis: #5b534f;

    --series-1: #338ffa;
    --series-2: #f1761c;
    --series-3: #38af38;
    --series-4: #e9ac13;
    --series-5: #eb6ac5;
    --series-6: #1d9e9a;
    --series-7: #a386fa;
    --series-8: #fb6874;
    --series-9: #b3ba61;
    --series-10: #1aa3ca;
    --series-other: #8c8480;
  }
}

/* ---------- Base ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The base type size. Every size in rem is a multiple of it, so the headings keep their ratios to the text. */
html {
  font-family: var(--font-sans);
  font-size: 18px;
  line-height: 1.65;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  color: var(--color-text);
  background: var(--color-background);
}

p {
  margin: 0 0 0.75rem;
}

select {
  font: inherit;
  font-size: 0.875rem;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-small);
  padding: 0.375rem 0.625rem;
}

button {
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text-muted);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-small);
  padding: 0.375rem 0.75rem;
  cursor: pointer;
}

button:hover {
  color: var(--color-text);
  background: var(--color-surface-raised);
}

button:disabled {
  opacity: 0.5;
  cursor: default;
}

button:disabled:hover {
  color: var(--color-text-muted);
  background: var(--color-surface);
}

a {
  color: var(--color-accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* Monospace runs a little smaller than the text around it, but not under the smallest size. */
code {
  font-family: var(--font-mono);
  font-size: max(0.875em, var(--font-size-smallest));
}

/* A button or a link focused from the keyboard shows a ring of two pixels in the accent. */
a:focus-visible,
button:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* An element the page hides takes no room, whatever display rule its class gives it. */
[hidden] {
  display: none !important;
}

/* Headings in the display serif, set close and balanced over their lines, as Open Data sets them; an Insights card's
   title is an h4, sized by its class. */
h1,
h2,
h3,
h4 {
  font-family: var(--font-serif);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.08;
  text-wrap: balance;
  margin: 0 0 0.5rem;
}

h1 {
  font-size: 2.25rem;
}

@media (max-width: 40rem) {
  h1 {
    font-size: 1.75rem;
  }
}

h2 {
  font-size: 1.5rem;
}

h3 {
  font-size: 1.125rem;
}

/* ---------- Layout ---------- */

/* The page spans the window, its gutters at both sides; its sections set its width (below), the Traffic Explorer's the
   one section that takes the whole of it. */
.page {
  padding: 2rem var(--gutter) 0;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

@media (max-width: 40rem) {
  .page {
    padding-top: 1.25rem;
  }
}

/* A section is as wide as the page's content, the page's width less its two gutters, so the section rules, the hero's
   band and every section's box end where the header's content does. Every box is border-box (Base, above), so the
   header's page width holds its gutters, and a section's edges meet the header's brand. */
.page > section {
  width: 100%; /* auto margins stop a column's item from stretching, so it is given the column's width */
  max-width: calc(var(--page-width) - 2 * var(--gutter));
  margin-inline: auto; /* centred where the window is wider than the page */
}

/* The Traffic Explorer spans the window less the gutters; every other section keeps the page's width. */
#traffic {
  max-width: none;
}

/* A band of the page: a rule above it and generous room around it. */
.section {
  border-top: 1px solid var(--color-border);
  padding: clamp(3rem, 8vw, 5.75rem) 0;
}

/* A part of the page opens as Open Data's sections do: a large serif heading, with room above. */
.section-heading {
  margin-top: clamp(1.5rem, 4vw, 3rem);
}

.section-heading h2,
.cite h2,
.team h2 {
  margin-top: 0;
  font-size: clamp(1.75rem, 1.4rem + 1.2vw, 2.5rem);
  letter-spacing: -0.02em;
}

/* The line under a section's heading that says what a click on it does, in the figure captions' size and ink. */
.section-guide {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

/* Every anchored section lands below the sticky header, with a little room above it: the header's height as the page
   measures it (`--header-measured-height`, from js/shared/layout.js), or its one row before it is measured. */
section[id] {
  scroll-margin-top: calc(var(--header-measured-height, var(--header-height)) + 1rem);
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 1.25rem;
}

@media (max-width: 40rem) {
  .card {
    padding: 1rem;
  }
}

/* Two figures side by side, one under the other on a narrower screen (below). No gap between rows: a card spans a
   row of its grid for each of its parts (see below), and a row gap would open between them; a card's own margin
   parts it from the card below. */
.card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 1.5rem;
}

@media (max-width: 50rem) {
  .card-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
 * A figure card in a grid lays its parts on its row's tracks, so the charts of a row start at one height whatever the
 * length of their insights, captions and legends. It spans a row for each of the six parts `renderFigure` appends to a
 * card with an insight: title, insight, legend, chart, caption and Definition; tests/shared_figure.test.js counts them.
 */
.card-grid > .card {
  display: grid;
  grid-row: span 6;
  grid-template-rows: subgrid;
  grid-template-columns: minmax(0, 1fr); /* the card's one column never grows wider than the card */
  row-gap: 0;
  min-width: 0;
  margin-bottom: 1.5rem;
}

.card-grid > .card > .figure {
  display: contents;
}

/* A card without a legend draws its chart where the legend would be, so it keeps no empty band above the chart; the
   chart sits at the foot of the two rows, so its x axis lines up with that of a card beside it that has a legend. */
.card-grid > .card .figure-legend:empty {
  display: none;
}

.card-grid > .card .figure-legend:empty + .figure-body {
  grid-row: span 2;
  align-self: end;
}

/* The parts are spaced by their own margins, not the rows' gap, so a legend place that is empty in every card of a
   row takes no room at all. */
.card-grid > .card .figure-title,
.card-grid > .card .figure-insight,
.card-grid > .card .figure-caption,
.card-grid > .card .figure-legend:not(:empty),
.card-grid > .card .figure-body {
  margin-bottom: 0.5rem;
}

/* A figure that needs the room, a log axis of many decades or the routing figure's eight panels, takes the whole
   width of its group's grid. */
.card-grid > .card.is-wide {
  grid-column: 1 / -1;
}

/* ---------- Components ---------- */

/* A link drawn as a button: a white card with the rule, lifting a little under the pointer. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-button);
  background: var(--color-surface);
  box-shadow: var(--shadow-card);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--color-text);
  transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}

.button:hover {
  text-decoration: none;
  border-color: var(--color-accent);
  color: var(--color-accent-strong);
  transform: translateY(-1px);
}

.button:active {
  transform: translateY(0);
}

/* The one button that leads: filled with the accent. */
.button--primary {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-on-accent);
}

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

/* A short label above a heading: monospace, upper case and spaced out, in the faint ink. */
.eyebrow {
  margin: 0 0 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  font-weight: 600;
  letter-spacing: 0.13em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

/* The eyebrow's crimson square, as the brand's mark. */
.eyebrow-mark {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.55rem;
  border-radius: 2px;
  background: var(--color-accent);
}

/* A link that leaves the site says so with an arrow after its text. */
a[target="_blank"]::after {
  content: "\2197";
  margin-left: 0.3em; /* a margin, not a space: a flex item such as a button trims a leading space */
  font-size: 0.85em;
}

/* A mark before a link's text: one em square on the baseline, in the link's colour; a margin parts it, as the arrow. */
.link-mark {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  margin-right: 0.3em;
}

.figure {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.figure-title {
  font-size: 1.25rem;
  margin: 0;
}

/* A figure's point, under its title: one sentence in the body's size and ink, a rule in the accent on its left. It
   keeps to its own lines when a neighbour's insight takes more. */
.figure-insight {
  align-self: start;
  margin: 0;
  padding-left: 0.75rem;
  border-left: 3px solid var(--color-accent);
  font-size: 1rem;
  line-height: 1.45;
  color: var(--color-text);
}

/* Evidence for an insight below its chart, or a description below the title of a figure without an insight. */
.figure-caption {
  font-size: 0.875rem;
  color: var(--color-text-muted);
  margin: 0;
}

/* The Definition control of every figure, at the foot of a card: a muted line that opens on the definition, alone on an
   Insights card, followed by the line of rows in the two Overview mixes and the Traffic Explorer's card. */
.figure-definition {
  font-size: var(--font-size-smallest);
  color: var(--color-text-muted);
}

.figure-definition summary {
  width: fit-content;
  cursor: pointer;
  color: var(--color-text-muted);
}

.figure-definition summary:hover {
  color: var(--color-text);
}

.figure-definition p {
  margin: 0.375rem 0 0;
}

.figure-rows {
  font-variant-numeric: tabular-nums;
}

.figure-body {
  min-height: 12rem;
}

/*
 * Plot paints its tooltip and text halos in --plot-background; give it the card's surface in both modes.
 * Plot sets its text at 10 px; the ticks, axis titles and tooltips take the smallest size instead.
 */
.figure-body svg {
  max-width: 100%;
  height: auto;
  font-size: var(--font-size-smallest);
  --plot-background: var(--color-surface);
}

/* A chart drawn with a colour legend is a figure holding the legend and the chart: it takes the body's width, without
   the browser's margins around a figure, so the chart is drawn at the width it was measured for. */
.figure-body figure {
  margin: 0;
}

/* A colour legend Plot draws above a chart (its SVG's class ends in "-ramp"): its tick labels, set larger than Plot
   lays them out for, reach below its box, so room is kept between them and the y axis's title under it. */
.figure-body svg[class$="-ramp"] {
  margin-bottom: 0.75rem;
}

/* A figure card of several panels: a chart per panel under its small title, side by side where the card is wide
   enough for two of at least 16rem, one under the other on a phone. */
.chart-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 0.75rem 1.5rem;
}

/* A wide card's panels, the routing figure's eight: four to a row, one row per model, as the paper's where they fit,
   two by two where they do not, so that no panel stands alone in a row, and one above another on a phone. */
.card.is-wide .chart-panels {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 68rem) {
  .card.is-wide .chart-panels {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 40rem) {
  .card.is-wide .chart-panels {
    grid-template-columns: minmax(0, 1fr);
  }
}

.chart-panel {
  min-width: 0;
}

.chart-panel-title {
  margin: 0 0 0.25rem;
  font-size: var(--font-size-smallest);
  color: var(--color-text-muted);
}

.legend {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 0.25rem 1rem;
  margin: 0;
  font-size: var(--font-size-smallest);
  color: var(--chart-label);
}

/* A legend entry that hides or shows its series: a plain button in the legend's ink, struck through while hidden. */
.legend-button {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: var(--font-size-smallest);
  font-weight: 400;
  color: var(--chart-label);
  background: transparent;
  border: none;
  padding: 0.125rem 0;
  text-align: left; /* a long name that wraps reads from its swatch, not centred under it */
}

.legend-button:hover {
  color: var(--color-text);
  background: transparent;
}

.legend-button[aria-pressed="false"] {
  text-decoration: line-through;
}

.legend-button[aria-pressed="false"] .legend-swatch {
  opacity: 0.3;
}

.legend-swatch {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 2px;
}

/* A line chart's series is a short line in its legend, as it is drawn. */
.legend-swatch.is-line {
  width: 1rem;
  height: 2px;
  border-radius: 0;
}

/* The key of a bands chart, in its legend's place: its entries laid out by the legend as its buttons are, but not
   buttons. */
.band-key {
  display: contents;
}

.band-key-entry {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.125rem 0;
}

/* The Insights' topic tabs: one row of a tab per topic under the Insights heading, drawn as a tab bar: each tab in
   the body's sans at the body's size, the shown topic in the accent, bold, on an accent rule that sits on the row's
   rule. The row scrolls sideways where it does not fit, its right edge faded while more tabs lie beyond it. */
.topic-tabs {
  display: flex;
  gap: 0.25rem;
  min-width: 0;
  /* The row's rule is drawn inside its box, where the tabs' rules lie on it; a border would lie outside the box, and
     the row, which scrolls, clips a tab's rule pushed onto it. */
  box-shadow: inset 0 -2px 0 var(--color-border);
  overflow-x: auto;
  scrollbar-width: none;
}

/* A tab is a button with none of a button's box: a label on a rule, the rule in the accent when its topic is shown,
   lying on the row's rule so the two draw as one line. */
.topic-tab {
  flex-shrink: 0;
  padding: 0.55rem 0.85rem;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: var(--radius-small) var(--radius-small) 0 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color 160ms ease, background-color 160ms ease;
}
.topic-tab:hover {
  color: var(--color-text);
  background: var(--color-surface-raised);
}
.topic-tab[aria-selected="true"],
.topic-tab[aria-selected="true"]:hover {
  color: var(--color-accent);
  font-weight: 600;
  background: none;
  border-bottom-color: var(--color-accent);
}

.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

/* A row of controls: each a field under its name in small monospace capitals, sharing the row equally; on a phone two
   fields a row. */
.control-field {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

@media (max-width: 40rem) {
  .control-field {
    flex-basis: calc(50% - 0.5rem);
  }

  /* A drop-down's text a step smaller and closer to its edge, so "All models" fits half the row. */
  .control-field select {
    padding-inline: 0.375rem;
    font-size: var(--font-size-smallest);
  }
}

.control-field select {
  width: 100%;
}

.control-field-name,
.range-day {
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

/* A drop-down's height, set so that it lines up with the period buttons in the controls row. */
.control-field select {
  box-sizing: border-box;
  height: 2.375rem;
}

.note {
  font-size: 0.875rem;
  color: var(--color-text-muted);
  padding: 0.75rem 1rem;
  border-radius: var(--radius-small);
  background: var(--color-surface-raised);
  /* A note may name a long id or address without a space, which would otherwise run past a phone's width. */
  overflow-wrap: anywhere;
}

.note.is-error {
  color: var(--color-accent-strong);
  background: var(--color-accent-wash);
}

.loading {
  color: var(--color-text-faint);
  font-size: 0.875rem;
}

.loading::after {
  content: "…";
}

/* An Insights card not yet drawn is about as tall as a drawn one, so the cards below it start out of view. */
.topic .card-grid > .card:empty,
.topic .card-grid > .card:has(.loading) {
  min-height: var(--loading-card-height);
}

/* A row with more beyond its right edge, such as the Insights' row of tabs, fades that edge, to say it scrolls
   sideways (`mountScrollingRow` in js/page/scrolling_row.js). */
.has-more-beyond-edge {
  mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
}

/* ---------- Header ---------- */

/* The header stays at the top of the window, over the page as it scrolls: translucent paper, blurred behind. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 84%, transparent);
  background: color-mix(in srgb, var(--color-background) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.4);
}

/* A hairline of the accent riding the header's bottom edge, fading out to the right. */
.site-header::after {
  content: "";
  position: absolute;
  inset: auto 0 -1px;
  height: 1px;
  background: linear-gradient(
    90deg,
    var(--color-accent) 0,
    color-mix(in srgb, var(--color-accent) 45%, transparent) 22%,
    transparent 55%
  );
}

.header-inner {
  max-width: var(--page-width);
  min-height: var(--header-height);
  margin: 0 auto;
  padding-inline: var(--gutter);
  display: flex;
  flex-wrap: wrap; /* were the links ever wider than the row, they wrap rather than widen the page */
  align-items: center;
  gap: 1.2rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--color-text);
  white-space: nowrap;
}

.brand:hover {
  text-decoration: none;
}

.brand em {
  font-style: normal;
  color: var(--color-accent);
}

/* The brand's mark: a crimson square with a pale one offset behind it, which darkens under the pointer. */
.brand-mark {
  flex-shrink: 0;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 2px;
  background: var(--color-accent);
  box-shadow: 0.32rem 0.32rem 0 color-mix(in srgb, var(--color-accent) 22%, transparent);
  transition: box-shadow 220ms ease;
}

.brand:hover .brand-mark {
  box-shadow: 0.32rem 0.32rem 0 color-mix(in srgb, var(--color-accent) 40%, transparent);
}

/* The brand pushes the nav to the right edge. */
.header-inner .brand {
  margin-right: auto;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 0.8rem;
}

.nav-link {
  position: relative;
  padding-block: 0.15rem;
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  letter-spacing: 0.04em;
  line-height: 1.4;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-text-faint);
  transition: color 160ms ease;
}

/* An underline in the accent grows from the left under the pointer. */
.nav-link::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 200ms ease;
}

.nav-link:hover {
  text-decoration: none;
  color: var(--color-accent-strong);
}

.nav-link:hover::after {
  transform: scaleX(1);
}

/* Below this width the header's links take a second row of their own, under the brand: one row needs the brand's
   300 px, the four links' 338 px, the gap between them and the gutters, about 52rem in all. The second row wraps as
   its links need, and the page measures the header's height for the sections that land below it. */
@media (max-width: 52rem) {
  .header-inner {
    min-height: 0;
    padding-top: 0.85rem;
  }

  .site-nav {
    width: 100%;
    flex-wrap: wrap;
    row-gap: 0.55rem;
    margin-top: 0.85rem;
    padding-block: 0.7rem;
    border-top: 1px solid var(--color-border);
  }
}

/* On a phone the header keeps its two rows, closer: the brand, then the four links, wrapping as they need. */
@media (max-width: 46rem) {
  .header-inner {
    padding-top: 0.5rem;
  }

  .site-nav {
    column-gap: 1rem;
    row-gap: 0.3rem;
    margin-top: 0.4rem;
    padding-block: 0.45rem;
  }
}

/* ---------- Hero ---------- */

/* The page's opening: the title, lede and buttons on the left, the stats strip on the right. */
.hero {
  position: relative;
  isolation: isolate;
  padding: clamp(1rem, 4vw, 3rem) 0 clamp(1rem, 3vw, 2rem);
}

/* A soft crimson wash behind the stats strip, so the opening reads warmer than the page. It fades out before the
   hero's sides, so it shows no edge where the page is narrower than the screen. */
.hero::before {
  content: "";
  position: absolute;
  inset: -6rem 0 0;
  z-index: -1;
  background: radial-gradient(30% 75% at 75% 10%, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent 70%);
  pointer-events: none;
}

/* The copy takes seven parts to the stats strip's four, wide enough at 1280 px for the title's first phrase on one line. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(21rem, 4fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 3.5rem);
}

.hero h1 {
  margin-top: 0;
  font-size: clamp(2rem, 1.25rem + 2.7vw, 3.1rem);
  letter-spacing: -0.035em;
}

/* The title's accent phrase never breaks inside: where the name does not fit one line, the phrase takes the second. */
.hero h1 em {
  font-style: normal;
  white-space: nowrap;
  color: var(--color-accent);
}

.hero-lede {
  max-width: 40rem;
  margin: 1.25rem 0 0;
  font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  color: var(--color-text-muted);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.75rem;
}

/* The lab's name, under the hero and in the footer. */
.affiliation {
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--color-text-faint);
  white-space: nowrap;
}

.affiliation:hover {
  text-decoration: none;
  color: var(--color-accent);
}

.hero-affiliation {
  margin: 1.5rem 0 0;
}

/* The stats strip: a white card of seven numbers parted by hairlines, with no frame of their own inside the card's, and
   the line of dates under them. */
.stats-card {
  padding: clamp(1rem, 2vw, 1.25rem);
  border: 1px solid var(--color-border);
  border-radius: 1rem;
  background: var(--color-surface);
  box-shadow: 0 2px 3px rgb(28 25 23 / 0.04), 0 24px 60px -36px color-mix(in srgb, var(--color-accent) 36%, transparent);
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--color-border);
}

.stats-grid li,
.traffic-tiles li {
  min-width: 0;
  padding: 0.75rem 0.6rem;
  background: var(--color-surface);
}

/* The last number, alone in its row, takes the row, so no empty cell shows the hairlines' colour. */
.stats-grid li:last-child:nth-child(3n + 1) {
  grid-column: 1 / -1;
}

.stats-grid strong,
.stats-grid span,
.traffic-tiles strong,
.traffic-tiles span {
  display: block;
}

.stats-grid strong,
.traffic-tiles strong {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.015em;
  line-height: 1.1;
  color: var(--color-accent);
}

.stats-grid span,
.traffic-tiles span {
  margin-top: 0.2rem;
  font-size: var(--font-size-smallest);
  color: var(--color-text-faint);
}

.stats-dates {
  margin: 0.9rem 0 0;
  font-size: var(--font-size-smallest);
  color: var(--color-text-muted);
}

/* The stats strip goes under the copy where the two no longer fit side by side, and keeps two numbers a row on a phone. */
@media (max-width: 62rem) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .stats-card {
    max-width: 40rem;
  }
}

@media (max-width: 30rem) {
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .stats-grid li:last-child:nth-child(3n + 1) {
    grid-column: auto;
  }

  .stats-grid li:last-child:nth-child(2n + 1) {
    grid-column: 1 / -1;
  }

  /* The five buttons in even rows rather than ragged ones: the primary one across both columns, the others two by two,
     each as wide as its column. */
  .hero-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .hero-actions .button--primary {
    grid-column: 1 / -1;
  }
}

/* ---------- Overview donuts ---------- */

/* The Overview: the two donuts side by side. */
.overview {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.donut-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem;
}

@media (max-width: 50rem) {
  .donut-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A donut card: the title and caption across the top, the donut with its legend beside it, the Definition below. */
.donut-card .figure {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "title title"
    "caption caption"
    "body legend"
    "definition definition";
  align-items: center;
  gap: 0.375rem 1.5rem;
}

.donut-card .figure-title {
  grid-area: title;
}

.donut-card .figure-caption {
  grid-area: caption;
}

.donut-card .figure-body {
  grid-area: body;
  min-height: 0;
  margin: 0.75rem 0;
}

/* The legend beside the donut lists its slices one under another, in rows close enough that eleven, the family mix's,
   end level with the donut, and in two columns where it goes under the donut. */
.donut-card .figure-legend {
  grid-area: legend;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0 1rem;
}

.donut-card .legend-button {
  padding-block: 0.0625rem;
  line-height: 1.35;
}

.donut-card .figure-definition {
  grid-area: definition;
}

/* A fixed size, so the centre's text keeps its size on every screen. */
.donut {
  display: block;
  width: 13rem;
  height: 13rem;
}

/* The even-odd rule makes the inner circle of a whole ring, a donut of one slice, a hole. */
.donut-slice {
  fill-rule: evenodd;
  stroke: var(--color-surface);
  stroke-width: 1.5;
}

.donut-total,
.donut-unit {
  text-anchor: middle;
  dominant-baseline: middle;
}

.donut-total {
  font-family: var(--font-serif);
  font-size: 28px; /* in the donut's own units, 200 across: about 32 px on the page */
  font-weight: 700;
  fill: var(--color-text);
}

.donut-unit {
  font-size: 13px; /* about 15 px on the page */
  fill: var(--color-text-faint);
}

/* Where two cards share a row too narrow for a donut and its legend side by side, the legend goes under the donut;
   one card a row (below 50rem) keeps it so too, for one layout on every narrow screen. */
@media (max-width: 68rem) {
  .donut-card .figure {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "title"
      "caption"
      "body"
      "legend"
      "definition";
  }

  .donut-card .figure-body {
    justify-self: center;
  }

  .donut-card .figure-legend {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-bottom: 0.75rem;
  }
}

/* On a phone a donut's legend is one column, so a long name and its share keep one line. */
@media (max-width: 30rem) {
  .donut-card .figure-legend {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Traffic Explorer ---------- */

.traffic,
.topic {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* A figure's card may shrink below its content, so a wide chart scrolls inside the card, not the page. */
.traffic .card,
.topic .card {
  min-width: 0;
}

/* The explorer: its row of controls above its one wide card. */
.traffic-explorer {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

/* The period field is as wide as its buttons, and the drop-downs share the rest of the row. */
.control-field.period-field {
  flex: 0 0 auto;
}

/* The period buttons in one row, the chosen one in the accent. */
.period-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

/* As tall as the drop-downs beside them, so the row of controls lines up. */
.period-button {
  box-sizing: border-box;
  height: 2.375rem;
}

.period-button[aria-pressed="true"] {
  color: var(--color-accent);
  background: var(--color-accent-wash);
  border-color: var(--color-accent);
}

/* The four tiles above the chart, parted by hairlines as the stats strip's numbers are: two a row on a narrower screen,
   one under the other on a phone (below). */
.traffic-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-small);
}

/* A family read that fails as the page opens leaves the list empty: no bordered line above the error. */
.traffic-tiles:empty {
  display: none;
}

@media (max-width: 50rem) {
  .traffic-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* On a phone the period field takes a row of its own, its buttons sharing it, closer to their text so all five fit. */
@media (max-width: 40rem) {
  .control-field.period-field {
    flex: 1 1 100%;
  }

  .period-button {
    flex: 1 1 auto;
    padding-inline: 0.375rem;
  }
}

@media (max-width: 30rem) {
  .traffic-tiles {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The day range under the bars: one track as wide as the card, with the From and To handles on one drawn bar, the
   bar between them in the accent, and under it the From day at the left end and the To day at the right. */
.traffic-range {
  margin-top: 0.75rem;
}

.range-track {
  position: relative;
  height: 1.5rem;
}

/* The bar the handles run along, centred in the track. */
.range-track::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  left: 0;
  height: 4px;
  transform: translateY(-50%);
  border-radius: 2px;
  background: var(--color-border);
}

/* The bar between the handles; the script sets its left and right ends. */
.range-span {
  position: absolute;
  top: 50%;
  height: 4px;
  transform: translateY(-50%);
  border-radius: 2px;
  background: var(--color-accent);
}

/* Both inputs cover the track, their own tracks hidden, so that only their thumbs, the handles, are drawn and take
   the pointer: a press on the bar between them reaches neither. */
.range-track input {
  position: absolute;
  inset: 0;
  width: 100%;
  margin: 0;
  background: none;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}

.range-track input::-webkit-slider-runnable-track {
  height: 100%;
  border: none;
  background: transparent;
}

.range-track input::-moz-range-track {
  border: none;
  background: transparent;
}

.range-track input::-webkit-slider-thumb {
  -webkit-appearance: none;
  box-sizing: border-box;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: calc((1.5rem - 1.125rem) / 2); /* WebKit sets the thumb at the track's top; this centres it */
  border: 2px solid var(--color-surface);
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-border);
  cursor: pointer;
  pointer-events: auto;
}

.range-track input::-moz-range-thumb {
  box-sizing: border-box;
  width: 1.125rem;
  height: 1.125rem;
  border: 2px solid var(--color-surface);
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-border);
  cursor: pointer;
  pointer-events: auto;
}

/* A handle focused from the keyboard shows the ring a focused button shows, on the handle rather than the track. */
.range-track input:focus-visible {
  outline: none;
}

.range-track input:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.range-track input:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* The To input, the track's second, is drawn above the From, so that handles standing together part when dragged
   right; on the last day both, the script marks the track and the From is drawn on top, the one that can move. */
.range-track input:first-of-type {
  z-index: 1;
}

.range-track input:last-of-type {
  z-index: 2;
}

.range-track.from-on-top input:first-of-type {
  z-index: 2;
}

.range-track.from-on-top input:last-of-type {
  z-index: 1;
}

.range-days {
  display: flex;
  justify-content: space-between;
}

/* The row of three distribution charts under the day range: three abreast, parted as the card grid's figures are, a
   figure's spacing above it; one under the other on a narrow screen (below). */
.traffic-distributions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}

/* A chart never widens its column: it is drawn at its place's width. */
.distribution-chart {
  min-width: 0;
}

/* A chart's title stands as far above its chart as a figure's title does above its body. */
.distribution-chart .figure-title {
  margin-bottom: 0.5rem;
}

/* One chart a row where three would each be too narrow for their axes' labels. */
@media (max-width: 50rem) {
  .traffic-distributions {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Insights ---------- */

/* The Insights' heading, tabs and topics, one after the other, spaced as the page spaces its parts. */
.insights {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* A topic's number before its heading, in the eyebrow's monospace and the accent: "01 The workload at a glance". */
.topic-number {
  margin-right: 0.25rem; /* with the space after it, the gap the title keeps from the number */
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  font-weight: 600;
  letter-spacing: 0.13em;
  vertical-align: 0.3em;
  color: var(--color-accent);
}

/* A takeaway, a topic's under its heading and a group's under its own: in the muted ink at the lede's size. */
.topic-takeaway,
.figure-group-takeaway {
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--color-text-muted);
}

/* A topic's takeaway sits close to the heading it belongs to. */
.topic-takeaway {
  margin: -0.75rem 0 0;
}

/* A group of a topic's cards: its heading in the display serif, between the topic's heading and a card's title in
   size, with room above it that parts it from the group before; its takeaway close under it. */
.figure-group-title {
  margin: 1rem 0 0;
  font-size: 1.375rem;
}

.figure-group-takeaway {
  margin: -0.5rem 0 0;
}

/* On a phone the takeaways step down to the body's size, so a takeaway takes fewer lines. */
@media (max-width: 40rem) {
  .topic-takeaway,
  .figure-group-takeaway {
    font-size: 1rem;
  }
}

/* ---------- Cite and Team ---------- */

/* The Cite section: its heading on the left, the sentence and the BibTeX on the right, wide enough for the BibTeX's
   longest line at 1280 px; one column on a narrow screen. */
.cite {
  display: grid;
  grid-template-columns: minmax(0, 0.6fr) minmax(20rem, 1.4fr);
  gap: clamp(1.5rem, 6vw, 5rem);
}

.cite-copy-column p {
  color: var(--color-text-muted);
}

.cite-block {
  overflow: hidden;
  margin: 1.25rem 0 0;
  border: 1px solid var(--color-border);
  border-radius: 0.7rem;
  background: var(--color-surface);
}

/* The block's caption bar: what the block holds, and the copy button, which the page shows only where it can copy. */
.cite-block figcaption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 1rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-raised);
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.cite-copy {
  padding: 0.2rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}

.cite-copy:hover {
  border-color: var(--color-accent);
  color: var(--color-accent-strong);
}

/* The BibTeX's lines wrap, a long address anywhere, rather than scroll the block sideways. */
.cite-block pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0;
  padding: 1rem;
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  line-height: 1.6;
  color: var(--color-text);
}

/* The Team section: its heading with the lab beside it on the heading's baseline, then the authors in a row between
   two rules. The section lies on a band of the accent's wash, the page's one tinted rest before the footer; the band
   reaches the window's edges while the content keeps the page's width. */
.team {
  --team-band: color-mix(in srgb, var(--color-accent) 6%, var(--color-background));
  background: var(--team-band);
  box-shadow: 0 0 0 100vmax var(--team-band);
  clip-path: inset(0 -100vmax);
}

/* The Team's heading sits under its eyebrow. */
.team h2 {
  margin-top: 0.5rem;
}

.team-heading {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: last baseline;
  gap: 1rem 3rem;
}

.team-heading > p {
  margin: 0;
  color: var(--color-text-muted);
}

/* The authors on a grid of five columns, each name over its affiliation, so they share one row and one left edge. */
.team-list {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0 1.2rem;
  margin: 1.5rem 0 0;
  padding: 0.85rem 0;
  list-style: none;
  border-block: 1px solid var(--color-border);
}

.team-list li {
  display: flex;
  flex-direction: column;
}

.team-list span {
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  letter-spacing: 0.03em;
  color: var(--color-text-faint);
}

/* On a narrow screen the heading goes above the copy, and each author takes a line of their own. */
@media (max-width: 58rem) {
  .cite,
  .team-heading {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 64rem) {
  .team-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    row-gap: 0.85rem;
  }
}

@media (max-width: 46rem) {
  .team-list {
    grid-template-columns: minmax(0, 1fr);
    padding: 0;
  }

  .team-list li {
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--color-border);
  }

  .team-list li:last-child {
    border-bottom: 0;
  }
}

/* ---------- Footer ---------- */

.site-footer {
  margin-top: 4rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}

/* The brand and its line, then two lists of links. */
.footer-inner {
  max-width: var(--page-width);
  margin: 0 auto;
  padding: clamp(2.25rem, 5vw, 3rem) var(--gutter);
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(9rem, 0.6fr));
  align-items: start;
  gap: 2rem clamp(1.5rem, 4vw, 3.5rem);
}

.footer-brand p {
  max-width: 24rem;
  margin: 0.55rem 0 0;
  font-size: 0.875rem;
  color: var(--color-text-faint);
}

.footer-nav-title {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  font-weight: 700;
  letter-spacing: 0.13em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.footer-links {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin-top: 0.9rem;
}

.footer-links .footer-link {
  width: fit-content;
  font-family: var(--font-mono);
  font-size: var(--font-size-smallest);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  transition: color 160ms ease;
}

.footer-links .footer-link:hover {
  text-decoration: none;
  color: var(--color-accent-strong);
}

/* On a phone the footer's link lists sit side by side under the brand. */
@media (max-width: 46rem) {
  .footer-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer-brand {
    grid-column: 1 / -1;
  }
}

/* ---------- Reduced motion: last, so it wins over every transition above ---------- */

@media (prefers-reduced-motion: reduce) {
  .brand-mark,
  .nav-link,
  .nav-link::after,
  .topic-tab,
  .footer-link {
    transition: none;
  }
}
