/*
 * VIA Systems Lab — site stylesheet
 * ---------------------------------
 * Layered on top of Pure CSS 3.0.0, which provides the reset, buttons, forms,
 * tables and — since the layout rework — the grid. This file defines no grid
 * of its own; see docs/DESIGN-GUIDE.md.
 *
 * Contents
 *   1. Tokens                     8. About: prose, glossary, pull quote
 *   2. Base                       9. Projects
 *   3. Layout shell              10. People
 *   4. Common components         11. Publications
 *   5. Masthead and navigation   12. Open practice
 *   6. Hero                      13. Footer
 *   7. Sections and themes       14. Motion, print
 *                                15. Sub-pages and highlights
 *
 * Layout is Pure's grid: .pure-g rows and .pure-u-* units, at Pure's own
 * breakpoints, so widths live in the markup where Pure puts them:
 *   base    below 48em — one column
 *   md      48em and up — units take effect
 *   lg      64em and up — the widest measure
 *
 * Blocks that sit side by side share a top edge. Nothing is staggered.
 *
 * The palette is sampled from the lab emblem: travertine, oxblood brick and
 * ink, with the node-graph's teal raised to an instrument cyan and used only
 * as a signal. Every text pair below meets WCAG AA; body text is 14.5:1.
 *
 * There is one scheme. The site does not follow prefers-color-scheme: the
 * palette was designed and reviewed on a travertine page, and a dark variant
 * that nobody has looked at is worse than none. See docs/DESIGN-GUIDE.md.
 */

/* ========================================================================
   1. Tokens
   ===================================================================== */

:root {
  /* Colour ------------------------------------------------------------ */
  /* --page-paper is the page background as a fixed value. --paper is the
     *current* surface and is re-pointed inside the dark bands, so anything
     that needs the real page colour regardless of context — the text on a
     filled button, for instance — must use --page-paper. */
  --page-paper:     #f6f1e8;
  --paper:          var(--page-paper);  /* warm near-white reading surface  */
  --field:          #ebe1d2;  /* travertine band for secondary sections */
  --ink:            #2a1d19;  /* body text                    14.5:1    */
  --ink-soft:       #4a3831;  /* metadata and secondary text   9.8:1    */
  --petrol:         #4a2118;  /* brand oxblood, used as a surface       */
  --petrol-deep:    #33170f;
  --accent-2:       #085059;  /* instrument cyan, used as ink  8.1:1    */
  --signal:         #8c3a2b;  /* brick: links, emphasis        6.8:1    */
  --signal-strong:  #6e2c20;  /* hover                         9.1:1    */
  --rule:           #d2c3ae;  /* hairlines and dividers (decorative)    */

  /* Project tags are filled, so they name their own foreground rather than
     inheriting a surface token. The names say what the pair is for; the
     values come from the palette above, so a colour is edited in one place. */
  --tag-armada-bg:   var(--signal);
  --tag-armada-fg:   var(--page-paper);
  --tag-datagems-bg: var(--accent-2);
  --tag-datagems-fg: var(--page-paper);

  /* A note is a panel laid on a page surface, so its own background is the
     other surface: travertine on paper, paper on the travertine sections.
     A translucent tint cannot do that job. It lands on both surfaces as the
     same small darkening, which reads as a box on neither. */
  --note-bg: var(--field);

  /* Inverse context (hero, footer) ------------------------------------ */
  --inverse-bg:     var(--petrol);
  --inverse-text:   #f4eadf;  /* on --inverse-bg              11.6:1    */
  --inverse-soft:   #d7b9ab;  /* on --inverse-bg               7.5:1    */
  --inverse-signal: #5fd4e3;  /* on --inverse-bg               7.9:1    */
  --inverse-rule:   rgb(244 234 223 / 0.28);
  /* The glow in the band's top corner: the brick, thinned, so it composites
     to a lighter oxblood over --inverse-bg. Mixed from --signal rather than
     picked, so the band stays inside the page's palette. */
  --inverse-bloom:  color-mix(in srgb, var(--signal) 42%, transparent);
  /* The node figure on a band, decorative and never text. Derived rather
     than picked, so the page has one cool hue: the signal cyan, halved into
     the soft cream. That lands on #9bc6c7, 7.4:1 on the band, which is where
     the figure was tuned to sit. */
  --graph-ink:      color-mix(in srgb, var(--inverse-signal) 50%, var(--inverse-soft));

  /* Typography -------------------------------------------------------- */
  --font-text:  'Source Sans 3', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-mono:  ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --step--2: clamp(0.72rem, 0.70rem + 0.10vw, 0.78rem);
  --step--1: clamp(0.82rem, 0.79rem + 0.15vw, 0.90rem);
  --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.10rem);
  --step-1:  clamp(1.16rem, 1.09rem + 0.35vw, 1.34rem);
  --step-2:  clamp(1.38rem, 1.24rem + 0.70vw, 1.78rem);
  --step-3:  clamp(1.65rem, 1.40rem + 1.20vw, 2.40rem);
  --step-4:  clamp(2.00rem, 1.60rem + 2.00vw, 3.20rem);
  --step-5:  clamp(2.35rem, 1.70rem + 3.10vw, 4.20rem);

  /* Space and measure -------------------------------------------------- */
  --shell-max:   82rem;
  --gutter:      clamp(1.15rem, 4vw, 3rem);
  --col-gap:     clamp(1rem, 2vw, 2rem);
  /* Each Pure unit carries half a gutter of padding on both sides, so the row
     itself is one --col-gap wider than the content measure. These two express
     that, and keep the width calculation to a single subtraction. */
  --shell-edge:  calc(var(--gutter) - (var(--col-gap) / 2));
  --shell-outer: calc(var(--shell-max) + var(--col-gap));
  --row-gap:     clamp(2rem, 4vw, 3.5rem);
  --section-pad: clamp(3rem, 7vw, 6.5rem);
  --measure:     38rem;
  /* The standfirst under a section heading. Wider than body copy because it
     is set larger and read in one pass, but still capped: the column it sits
     in is wider than this, and filling it would give a ~95-character line. */
  --measure-lead: 68ch;

  /* How far each dark band overlaps the section beneath it. These live here,
     not on .hero and .pagehead, because the section that follows reads them
     too — and a sibling inherits nothing from the element before it. */
  --hero-skew:     clamp(4.5rem, 14vw, 12rem);
  --pagehead-skew: clamp(2rem, 5vw, 4rem);

  /* Tilt and overlap are two different things and now have two names. The
     tilt is how much lower one end of the diagonal is than the other; the
     overlap is how far the band is pulled over the section below. They are
     equal by default, which is what makes that section show through the
     diagonal, but a band that has been lifted clear of the join has nothing
     to show through and should not overlap at all. */
  --hero-overlap:   var(--hero-skew);
  --hero-band-lift: 0px;

  /* How far the emblem hangs below the hero, and how much room the section
     underneath has to add for it. The overhang is zero until the large
     breakpoint, so neither value touches a layout that does not use it.

     The reserve is smaller than the overhang on purpose. The emblem hangs
     over the right of the row beneath it, and that corner is empty: the
     section head is a left-hand unit and its title ends columns before the
     emblem starts. Reserving the whole overhang was correct arithmetic and
     the wrong layout, an extra 64px above every section head at 1920px,
     which read as a hole over the title.

     What the reserve does have to do is keep the emblem off the rows that
     follow the head. If anything is ever placed in the top right of the
     section after the hero, this is the number that has to grow with it. */
  --hero-overhang: 0rem;
  --hero-reserve:  0rem;

  /* Texture ------------------------------------------------------------ */

  /* Grain for the dark bands: fractal noise, desaturated, laid on at 16%.
     Two numbers tune it: baseFrequency is the coarseness, lower being larger
     flecks, and the rect's opacity is the strength.
     It gives the band a tooth instead of a flat field, and being grey it
     carries no colour of its own, so it survives a palette change. The tile
     stitches, so nothing seams where it repeats. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)' opacity='0.16'/%3E%3C/svg%3E");

  /* Icons -------------------------------------------------------------- */
  --icon-ext: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 2h8v8h-2V5.4L4.4 13 3 11.6 10.6 4H6z'/%3E%3C/svg%3E");

  /* Focus -------------------------------------------------------------- */
  --focus: var(--signal);

  color-scheme: light;
}

/*
 * Below Pure's md breakpoint the hero stacks and the emblem is the last thing
 * in it. Cutting the band across the emblem's middle leaves its lower half on
 * the page surface, which gives it an edge to sit against instead of ending
 * quietly inside a dark field. The emblem is sized as a fraction of the
 * viewport, so the lift tracks the viewport too.
 */
@media screen and (max-width: 47.99em) {
  :root {
    --hero-overlap:   0px;
    --hero-band-lift: clamp(7rem, 38vw, 11rem);
  }

  /* The lift leaves a strip of hero below the diagonal. It was showing the
     page surface, a lighter tone than the travertine section that starts
     immediately under it, so the emblem's lower half sat on a pale band
     between two darker ones. Travertine here runs the two together.

     This is a background on .hero, which paints before its own ::before:
     inside a stacking context the element's background comes first and
     negative z-index children after it, so the band still covers this. It
     stays below the md breakpoint because from there the overlap returns,
     and what shows through the diagonal is meant to be the section itself. */
  .hero { background: var(--field); }
}

/* The emblem overhangs only once there is room for it beside the text. */
@media screen and (min-width: 64em) {
  :root {
    --hero-overhang: clamp(3.5rem, 11vw, 12rem);
    --hero-reserve:  clamp(0rem, 1.5vw, 2rem);
  }
}

/* ========================================================================
   2. Base
   ===================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 1rem;
}

/* The masthead only becomes sticky at 48rem, so only then does an anchor
   target need to clear it. */
@media screen and (min-width: 48em) {
  html { scroll-padding-top: 5.25rem; }
}

body {
  margin: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--step-0);
  line-height: 1.62;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.15;
  text-wrap: balance;
  margin: 0;
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }

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

em { font-style: italic; }
strong { font-weight: 600; }

a {
  color: var(--signal);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  transition: color 120ms ease, text-decoration-color 120ms ease;
}

a:hover,
a:focus-visible {
  color: var(--signal-strong);
  text-decoration-color: currentColor;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 1px;
}

/* Pure sets its own font stack on form controls; keep them in family. */
input, select, textarea, button { font-family: inherit; }

::selection {
  background: color-mix(in srgb, var(--signal) 24%, transparent);
}

.skip-link {
  position: absolute;
  left: 0.75rem;
  top: -100%;
  z-index: 100;
  padding: 0.7rem 1.1rem;
  background: var(--petrol);
  color: var(--inverse-text);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus {
  top: 0.75rem;
  color: var(--inverse-text);
}

/* ========================================================================
   3. Layout shell — Pure's grid, with gutters
   ===================================================================== */

/*
 * Composition uses Pure's own grid: `.pure-g` for the row and `.pure-u-*`
 * unit classes for the widths, at Pure's `md` (48em) and `lg` (64em)
 * breakpoints. Nothing here re-implements a grid; this layer only does the
 * two things Pure deliberately leaves to the page:
 *
 *   1. centres the row and caps its measure (.shell)
 *   2. adds gutters, which Pure omits so that units stay exact percentages
 *
 * Gutters are half-gutter padding on each unit. On its own that would inset
 * the first and last columns from the page edge, so the shell is made exactly
 * one gutter wider to compensate: content lands on --gutter at both edges and
 * the widest content measure is still --shell-max.
 *
 * Deliberately no negative margins. `.shell` is itself a `.pure-g`, so a
 * negative `margin-inline` on the row would override the `auto` that centres
 * it — equal specificity, and the row rule is read last. Widening is the
 * version that cannot silently un-centre the page.
 *
 * --col-gap never exceeds 2 * --gutter, so the shell cannot outgrow the
 * viewport and nothing scrolls sideways.
 */

.shell {
  width: min(100% - (2 * var(--shell-edge)), var(--shell-outer));
  margin-inline: auto;
}

.pure-g { row-gap: var(--row-gap); }

.pure-g > [class*="pure-u"] {
  padding-inline: calc(var(--col-gap) / 2);
}

/* A list that is itself a row of units still has to fill its parent unit. */
.themes,
.highlights,
.openlist,
.pubs,
.project {
  width: 100%;
}

/* ========================================================================
   4. Common components
   ===================================================================== */

/* Eyebrow: section number and label ----------------------------------- */

.eyebrow {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  margin: 0 0 0.9rem;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.eyebrow__num {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--signal);
}

.eyebrow__num::after {
  content: "";
  display: inline-block;
  width: 2.2rem;
  margin-left: 0.7rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: 0.3em;
}

/* External links ------------------------------------------------------ */

/* Drawn as a masked shape rather than a text glyph: an empty `content` is
   not announced by screen readers, and it inherits the link colour. */
.link-ext::after {
  content: "";
  display: inline-block;
  width: 0.6em;
  height: 0.6em;
  margin-left: 0.32em;
}

@supports ((-webkit-mask: url("")) or (mask: url(""))) {
  .link-ext::after {
    background-color: currentColor;
    opacity: 0.75;
    -webkit-mask: var(--icon-ext) center / contain no-repeat;
    mask: var(--icon-ext) center / contain no-repeat;
  }
}

/* Tags: output kind, project, status ----------------------------------- */

.tag {
  display: inline-block;
  padding: 0.2em 0.62em 0.24em;
  border: 1px solid var(--rule);
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Where a paper appeared. One family, weighted by how substantial the venue
   is, and told apart by border style as well as by colour, so the distinction
   survives greyscale and colour-vision deficiency. What the paper argues is
   said by the group it is filed under, not by a tag. */
.tag--conference { color: var(--accent-2); border-color: var(--accent-2); border-style: solid; border-width: 1px 1px 3px; }
.tag--journal    { color: var(--accent-2); border-color: var(--accent-2); border-style: solid; border-width: 2px; }
.tag--workshop   { color: var(--accent-2); border-color: var(--accent-2); border-style: solid; }
.tag--dataset    { color: var(--ink-soft); border-style: double; border-width: 3px; }
.tag--software   { color: var(--ink-soft); border-style: double; border-width: 3px; font-family: var(--font-mono); }

/* Funding project: filled, so a publication's project reads at a glance. */
.tag--armada   { background: var(--tag-armada-bg);   border-color: var(--tag-armada-bg);   color: var(--tag-armada-fg); }
.tag--datagems { background: var(--tag-datagems-bg); border-color: var(--tag-datagems-bg); color: var(--tag-datagems-fg); }
.tag--active   { background: transparent; border-color: var(--accent-2); color: var(--accent-2); }

.tag--venue {
  border-color: transparent;
  padding-inline: 0;
  color: var(--ink-soft);
}

/* Buttons -------------------------------------------------------------- */

.button {
  border-radius: 1px;
  padding: 0.72em 1.5em;
  font-family: var(--font-text);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  box-shadow: none;
  transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
}

/* The same fill as .button--solid on a light page, so one callout reads as
   one thing across the site. On the band that fill is only 1.5:1 against the
   ground, so the shape is carried by the hairline the ghost button uses, and
   the label by --page-paper at 8.1:1 on the fill. */
.button--primary {
  background: var(--accent-2);
  border: 1px solid var(--inverse-rule);
  color: var(--page-paper);
}

.button--primary:hover,
.button--primary:focus-visible {
  background: var(--inverse-text);
  border-color: var(--inverse-text);
  color: var(--petrol-deep);
}

.button--ghost {
  background: transparent;
  border: 1px solid var(--inverse-rule);
  color: var(--inverse-text);
}

.button--ghost:hover,
.button--ghost:focus-visible {
  background: color-mix(in srgb, var(--inverse-text) 14%, transparent);
  border-color: var(--inverse-text);
  color: var(--inverse-text);
}

/* Notes ---------------------------------------------------------------- */

/* A block set aside from the argument of its section: a caveat under a list,
   a panel inviting people to work with the lab, a disclosure. One rule for all
   of them, because a reader who learns to recognise the frame once should not
   have to learn it again on the next page. The heading and the row of links
   are optional; a note is often a single paragraph. */
.note {
  padding: 1.5rem;
  background: var(--note-bg);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent-2);
  line-height: 1.6;
  color: var(--ink-soft);
}

.note strong { color: var(--ink); }

.note__title {
  font-size: var(--step-1);
  margin-bottom: 0.7rem;
  color: var(--ink);
}

.note__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.2rem;
  font-weight: 600;
}

/* A note hugs its content rather than stretching to the height of whatever
   Pure has put beside it in the row. */
@media screen and (min-width: 48em) {
  .note { align-self: flex-start; }
}

/* ========================================================================
   5. Masthead and navigation
   ===================================================================== */

.masthead {
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

@media screen and (min-width: 48em) {
  .masthead {
    position: sticky;
    top: 0;
    z-index: 20;
    background: color-mix(in srgb, var(--paper) 92%, transparent);
    backdrop-filter: saturate(140%) blur(6px);
  }
}

.masthead__inner {
  width: min(100% - (2 * var(--gutter)), var(--shell-max));
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1.5rem;
  padding-block: 0.6rem;
}

.wordmark {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--ink);
  text-decoration: none;
}

.wordmark__seal {
  width: 2.3rem;
  height: 2.3rem;
  flex: none;
}

.wordmark__text { display: block; }

.wordmark__name {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.2;
}

.wordmark__sub {
  display: none;
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}

@media screen and (min-width: 48em) {
  .wordmark__sub { display: block; }
}

.wordmark:hover .wordmark__name,
.wordmark:focus-visible .wordmark__name { color: var(--signal); }

/*
 * The navigation is a plain wrapping list at every width: no disclosure, no
 * script, nothing to open. Six short labels wrap onto two lines at 320px and
 * onto one from 48rem.
 */
.nav__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.15rem;
}

.nav__list a {
  display: inline-block;
  padding: 0.3rem 0;
  color: var(--ink-soft);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.nav__list a:hover,
.nav__list a:focus-visible {
  color: var(--signal);
  border-bottom-color: var(--signal);
}

/* ========================================================================
   6. Hero
   ===================================================================== */

.hero {
  /* Inverse context: every component token flips, so nothing below needs to
     know it is sitting on a dark band. */
  --paper: var(--inverse-bg);
  --ink: var(--inverse-text);
  --ink-soft: var(--inverse-soft);
  --signal: var(--inverse-signal);
  --signal-strong: var(--inverse-text);
  --rule: var(--inverse-rule);
  --focus: var(--inverse-signal);

  --skew: var(--hero-skew);
  --band-lift: var(--hero-band-lift);

  position: relative;
  z-index: 1;
  color: var(--inverse-text);
  padding-block: clamp(2rem, 5vw, 4rem) clamp(2.5rem, 6vw, 5rem);

  /* Pulled over the section below by --hero-overlap, so what shows through
     the diagonal is that section itself. See .hero::before. */
  margin-bottom: calc(-1 * var(--hero-overlap));
}

/*
 * The diagonal, done honestly.
 *
 * Nothing is imitated. The dark band is its own layer and is *clipped* on the
 * diagonal; the band's element is pulled down over the following section by
 * the same amount. What shows through the cut is that section's real
 * background, whatever colour it has been given, with no coupling to maintain
 * and nothing to keep in step.
 *
 * The alternative, a triangle painted over the band in var(--page-paper) to
 * imitate the page showing through, only looks right while the section below
 * happens to be paper-coloured. Reorder the sections, or tint one with
 * --field, and a mismatched wedge sits on it with nothing in the CSS saying
 * the two have to agree.
 *
 * The band is deepest on the left and rises to the right.
 */
.hero::before,
.pagehead::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--inverse-bg);
  background-image:
    radial-gradient(120% 90% at 78% 10%, var(--inverse-bloom), transparent 62%),
    var(--grain);
  /* Two knobs, doing two different things. --skew tilts the bottom edge: it
     is how much higher the right end finishes than the left. --band-lift
     raises both ends together, and defaults to 0, which leaves the left end
     on the band's own bottom edge. Neither says anything about how far the
     band is pulled over the section below; that is the host's margin. */
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--skew) - var(--band-lift, 0px)),
    0 calc(100% - var(--band-lift, 0px)));
}



/*
 * The section underneath gets its padding back, so its content still clears
 * the band it is now tucked beneath. These read the root tokens rather than
 * --skew: a sibling inherits nothing from the element before it, so var(--skew)
 * here would resolve to nothing and the padding would collapse to zero.
 */
.hero + .section {
  padding-top: calc(var(--section-pad) + var(--hero-overlap) + var(--hero-reserve));
}

.pagehead + .section {
  padding-top: calc(var(--section-pad) + var(--pagehead-skew));
}

/*
 * A node-and-link figure for a dark band. Everything adjustable is a custom
 * property, so a second band could reuse it by overriding a few values rather
 * than by copying the rule.
 *
 * A 62% box means the drawing renders at roughly natural size, so about 70% of
 * it is in view rather than a magnified sliver: widening the box would zoom in,
 * not widen. The fade masks the element box, so first ink stays at 57% of the
 * band whatever the viewport; the text column ends at 54%, which is the margin
 * that keeps ink off words.
 */
.graph {
  --graph-fade: linear-gradient(to right, transparent 0,
    transparent var(--graph-fade-start, 30.7%),
    #000 var(--graph-fade-end, 55%), #000 100%);
  /* Top to bottom shading. A second mask layer rather than an SVG gradient:
     a gradient would need a url(#id) paint reference, which does not resolve
     reliably from an external stylesheet. */
  --graph-shade: linear-gradient(to bottom, #000, rgb(0 0 0 / 0.55));

  /* Hidden by default. On a narrow screen there is no empty half to put it
     in, so it would sit under the text as noise rather than as decoration; it
     appears from Pure's md breakpoint up, where the band has room for it. */
  display: none;
  position: absolute;
  inset: 0 0 0 auto;
  width: var(--graph-width, 62%);
  z-index: 0;
  pointer-events: none;
  color: var(--graph-ink);
  -webkit-mask-image: var(--graph-fade);
  mask-image: var(--graph-fade);
}

/* Where the two layers can be combined, the shading applies as well. Where
   they cannot, the horizontal fade still does, which is the part that keeps
   the figure off the text. */
@supports (mask-composite: intersect) {
  .graph {
    mask-image: var(--graph-fade), var(--graph-shade);
    mask-composite: intersect;
  }
}

@media screen and (min-width: 48em) {
  .graph { display: block; }
}

.graph svg {
  display: block;
  width: 100%;
  height: 100%;
}

.graph__edges {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--graph-stroke, 1.72);
  stroke-opacity: var(--graph-edge-alpha, 0.26);
}

.graph__nodes {
  fill: currentColor;
  fill-opacity: var(--graph-node-alpha, 0.62);
}

/* The disc under each node stops its edges at the dot instead of letting them
   show through, so it is the band colour, not a transparency. */
.graph__halo { fill: var(--graph-ground, var(--inverse-bg)); }

.hero__grid { position: relative; z-index: 1; align-items: center; }

/* Width comes from the pure-u-* classes on the element. Below md the unit is
   full width, so the emblem is capped and centred here instead. */
.hero__figure {
  margin: 0 auto 1.25rem;
  max-width: min(19rem, 78%);
}

@media screen and (min-width: 48em) {
  .hero__figure {
    max-width: none;
    margin: 0;
    /* --hero-overhang is 0 until 64em, so this is inert at this width. */
    margin-bottom: calc(-1 * var(--hero-overhang));
  }
}

.hero__figure img {
  width: 100%;
  /* Light by default. Below the md breakpoint the emblem straddles the band's
     diagonal and finishes on the page surface, where a shadow tuned for the
     dark ground reads as grime. */
  filter: drop-shadow(0 6px 16px rgb(51 23 15 / 0.18));
}

@media screen and (min-width: 48em) {
  .hero__figure img { filter: drop-shadow(0 12px 28px rgb(51 23 15 / 0.45)); }
}

.hero__h1 { margin: 0 0 1rem; }

.hero__name {
  display: block;
  font-size: var(--step-5);
  font-weight: 600;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  line-height: 1.02;
}

.hero__expansion {
  display: block;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 2px solid var(--inverse-signal);
  max-width: 22ch;
  font-family: var(--font-text);
  font-size: var(--step-0);
  font-weight: 400;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--inverse-soft);
}

.hero__lead {
  max-width: 34rem;
  margin-bottom: 1.6rem;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--inverse-text);
}

.hero__lead em {
  font-style: italic;
  color: var(--inverse-signal);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1.6rem;
}

.hero__fact {
  letter-spacing: 0.04em;
  color: var(--inverse-text);
}


@media screen and (min-width: 64em) {
  .hero__grid { align-items: flex-end; }
  .hero__text {
    padding-bottom: clamp(0.5rem, 2vw, 2rem);
  }
}

/* ========================================================================
   7. Sections and research themes
   ===================================================================== */

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

/*
 * Sections alternate between the page surface and the travertine band.
 *
 * The rhythm is positional on purpose. Keying it to named sections
 * (.section--about, .section--people) would tie the tint to the *content*
 * rather than to its place on the page, and reordering the sections would make
 * the pattern arbitrary: two tinted bands adjacent, the band under the hero
 * changing colour with nothing saying it had. Positionally, nothing has to be
 * kept in step; a section is tinted because of where it sits, and moving it
 * re-tints it automatically.
 *
 * The hero and the page header are <section> elements too, so they count as
 * the first item and the alternation starts beneath them.
 */
.section:nth-of-type(even) {
  background: var(--field);
  --note-bg: var(--page-paper);
}

.section__head { margin-bottom: clamp(1.5rem, 3vw, 2.5rem); }

.section__title {
  font-size: var(--step-4);
  letter-spacing: -0.01em;
  /* The one cool colour the page has, on the one heading level that marks
     its structure. 8.1:1 on paper and 7.1:1 on travertine. */
  color: var(--accent-2);
  margin-bottom: 0.6rem;
}

.section__standfirst {
  max-width: var(--measure-lead);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink-soft);
}



/*
 * Research themes.
 *
 * A plain grid: three across, then two. The widths come from pure-u-md-1-2
 * and pure-u-lg-1-3; the only thing added here is centring, which pushes the
 * short second row to the middle instead of leaving it hanging on the left.
 * A full row has no spare space, so centring does nothing to it.
 */

.themes {
  row-gap: clamp(1.75rem, 3.5vw, 3rem);
  justify-content: center;
}


.theme {
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}

.theme__name {
  font-size: var(--step-2);
  margin-bottom: 0.45rem;
}

.theme__desc {
  max-width: 46ch;
  margin-bottom: 0.7rem;
  color: var(--ink);
}

.theme__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1.1rem;
  margin: 0;
  font-size: var(--step--1);
}

.theme__methods {
  color: var(--ink-soft);
  letter-spacing: 0.02em;
}

.theme__link {
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: var(--step--2);
}

/* ========================================================================
   8. About: prose, glossary, pull quote
   ===================================================================== */

/* The measure cap is on .main-prose, the body of a page that is mostly text.
   A unit is a column width and a measure is a line length, and past about 80
   characters the eye loses the start of the next line. Prose that shares its
   row with other blocks is not capped: it is already as wide as its unit lets
   it be, and capping it too leaves it sitting in half of its column. */
.main-prose { max-width: var(--measure); }

/* The indent belongs to the narrow unit this element takes at the large
   breakpoint. Below that it is a full-width or near-full-width unit, and a
   margin on top of that width overflows the row. */
@media screen and (min-width: 64em) {
  .main-prose { margin-left: var(--step-5); }
}

.prose__lead {
  font-size: var(--step-1);
  line-height: 1.5;
}

.prose p + p { margin-top: 0; }

/*
 * The opening mark hangs in its own column, level with the quotation rather
 * than stacked above it, and the quotation is indented past it.
 *
 * The mark was absolutely positioned once, with a padding-left meant to clear
 * it. That could not hold: the glyph's advance width scales with its font-size
 * on one curve while the indent scaled on another, so the gap between them
 * narrowed to a few pixels at some widths, and vanished entirely with a
 * fallback font whose quotation mark is a different width again. It then spent
 * a while as a block on its own line, which could not collide but put the mark
 * a long way above the words.
 *
 * An auto-width grid column removes the coupling instead of retuning it: the
 * column is exactly as wide as the glyph, whatever size or font it ends up at,
 * so the indent and the mark are one measurement rather than two that have to
 * agree. There is no width here to keep in step.
 */
.pullquote {
  /* The one optical adjustment: how far the mark drops below the first line.
     In em, so it scales with the glyph and not on a second curve. */
  --quote-drop: 0.18em;
  --quote-gap:  0.4rem;
  --quote-size: clamp(3.5rem, 7vw, 5.5rem);
  /* How far the closing mark sits below the text's baseline, so a glyph this
     large ends level with the last line instead of standing above it. */
  --quote-sink: -0.45em;

  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--quote-gap);
}

.pullquote::before {
  content: "\201C";
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  margin-top: var(--quote-drop);
  font-family: var(--font-display);
  font-size: var(--quote-size);
  line-height: 0.75;
  color: var(--rule);
}

/* The closing mark runs on after the last word, so it belongs to the verse
   rather than to the grid. Line height 0 keeps it from opening up the line. */
.pullquote blockquote p::after {
  content: "\201D";
  margin-left: 0.05em;
  vertical-align: var(--quote-sink);
  font-family: var(--font-display);
  font-size: var(--quote-size);
  font-style: normal;
  line-height: 0;
  color: var(--rule);
}

.pullquote blockquote {
  grid-column: 2;
  grid-row: 1;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.35;
  font-style: italic;
  text-wrap: pretty;
}

/* The English of the verse. It is text rather than a tooltip because a
   translation a reader cannot select, hear or reach by keyboard is not one. */
.pullquote__gloss {
  grid-column: 2;
  grid-row: 2;
  max-width: 34ch;
  margin: 0.7rem 0 0;
  font-size: var(--step--1);
  color: var(--ink-soft);
}

/* Column 2, so the attribution lines up with the quotation it belongs to
   rather than with the mark. */
.pullquote figcaption {
  grid-column: 2;
  grid-row: 3;
  margin-top: 0.9rem;
  font-size: var(--step--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.glossary { border-top: 1px solid var(--rule); padding-top: 1.75rem; }

.glossary__title {
  font-size: var(--step-2);
  margin-bottom: 0.5rem;
}

.glossary__intro {
  max-width: 48ch;
  margin-bottom: 1.5rem;
  color: var(--ink-soft);
}

.glossary__item {
  break-inside: avoid;
  margin-bottom: 1.35rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--rule);
}

.glossary__item:last-child { margin-bottom: 0; }

.glossary__list dt {
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: 600;
  letter-spacing: 0.01em;
  margin-bottom: 0.15rem;
}

.glossary__vi { color: var(--signal); }

.glossary__list dd {
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 46ch;
}

@media screen and (min-width: 48em) {
  .pullquote { align-self: center; }
  .glossary  { column-count: 2; column-gap: clamp(2rem, 4vw, 4rem); }
  .glossary__title,
  .glossary__intro { column-span: all; }
}

/* Seven short definitions read better in three columns once there is room. */
@media screen and (min-width: 64em) {
  .glossary { column-count: 3; }
}


/* ========================================================================
   9. Projects
   ===================================================================== */

.project {
  row-gap: 1.5rem;
  padding-top: 1.5rem;
  border-top: 3px solid var(--accent-2);
}

.project__status {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-bottom: 0.9rem;
}

.project__title {
  font-size: var(--step-3);
  margin-bottom: 0.8rem;
}

.project__title a { color: inherit; text-decoration: none; }
.project__title a:hover,
.project__title a:focus-visible {
  color: var(--signal);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.project__subtitle {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-text);
  font-size: var(--step-0);
  font-weight: 400;
  line-height: 1.4;
  color: var(--ink-soft);
  text-transform: none;
  letter-spacing: 0;
}

.project__desc {
  max-width: 54ch;
  margin-bottom: 1.1rem;
}

/*
 * The rail is the useful part of a project record: where it lives, who
 * recorded it, and the handful of facts worth quoting. It gets a field of its
 * own so it reads as a reference panel rather than as leftover margin, and
 * both records use the same one — a project page is a place to look something
 * up, not a place to be surprised by the layout.
 */
/* Tinted relative to whatever is behind it, rather than filled with a named
   surface colour: the rail has to read on both the page surface and the
   travertine band, and it must not care which one it landed on. */
.project__rail {
  padding-block: 1.25rem;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  align-self: flex-start;
}

.project__logo {
  margin: 0 0 1.25rem;
}

/* The three links are the first thing in the rail and the most likely reason
   anyone opened the page. */
.project__links {
  margin: 0 0 1.25rem;
  font-size: var(--step--1);
}

.project__links li { border-top: 1px solid var(--rule); }
.project__links li:last-child { border-bottom: 1px solid var(--rule); }

.project__links a {
  display: block;
  padding: 0.65rem 0;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
}

.project__links a:hover,
.project__links a:focus-visible {
  color: var(--signal-strong);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.project__meta {
  display: grid;
  gap: 0.85rem;
  align-content: start;
  font-size: var(--step--1);
}

.project__meta > div {
  padding-left: 0.85rem;
  border-left: 1px solid var(--rule);
}

.project__meta dt {
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 0.1rem;
}

.project__meta dd {
  margin: 0;
  line-height: 1.45;
  overflow-wrap: anywhere;
  font-variant-numeric: lining-nums tabular-nums;
}



/* ========================================================================
   10. People
   ===================================================================== */

/*
 * A roster, not a profile page. Each person is a compact card carrying only
 * what a reader needs to identify them and find their work: name, title,
 * affiliation, one line of subject, and their own identifiers. The principal
 * investigator's card is marked, not enlarged, so the list stays even as it
 * grows past the five slots it ships with.
 */

.peoplegroup { margin-bottom: 0.5rem; }

.peoplegroup__title {
  font-size: var(--step--1);
  font-family: var(--font-text);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 0.5rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--rule);
}

.people { row-gap: clamp(1.5rem, 3vw, 2.25rem); width: 100%; }

.person {
  padding-top: 0.9rem;
  border-top: 2px dashed var(--rule);
}

/* The lead card is distinguished by its rule, solid and in the accent, not
   by size. */
.person--lead {
  border-top: 3px solid var(--signal);
}

.person__name {
  font-size: var(--step-1);
  margin-bottom: 0.25rem;
}

.person__name a { color: inherit; text-decoration: none; }
.person__name a:hover,
.person__name a:focus-visible { color: var(--signal); text-decoration: underline; }

.person__title {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.15rem;
}

.person__affil {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin-bottom: 0.6rem;
}

.person__bio {
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 40ch;
  margin-bottom: 0.7rem;
}

.person__ids {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.02em;
  overflow-wrap: anywhere;
}

/* ========================================================================
   11. Publications
   ===================================================================== */

/*
 * One legend, one row per kind of tag, in the order the tags sit on an
 * entry. The rows are children of .legend, not of the shell: as direct
 * children of the shell they would pick up its row-gap, which is sized to
 * separate whole sections and would put most of a section's worth of space
 * inside one legend. The wrapper gives them their own much smaller gap.
 */
.legend {
  display: grid;
  gap: 0.45rem;
  margin: 0;
}

.legend__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem 0.6rem;
  margin: 0;
}

.legend__label {
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.pubs { display: grid; gap: 1.4rem; }

.pub {
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}

.pub__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  margin-bottom: 0.55rem;
}

.pub__title {
  font-size: var(--step-1);
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.pub__title a { color: inherit; }
.pub__title a:hover,
.pub__title a:focus-visible { color: var(--signal); }

.pub__detail {
  color: var(--ink-soft);
  max-width: 62ch;
}

/*
 * Optional pointers to the data and the code behind a paper.
 *
 * Its own row, not .pub__tags. Those tags answer what the entry *is*: a
 * conference paper, at EDBT, funded by ARMADA. "Carries data" is a different
 * question, and a DATA tag sitting next to CONFERENCE would read as an answer
 * to the first one, which is the axis confusion the README warns about for
 * groups and tags.
 *
 * Tag-shaped links rather than plain ones, because the thing a reader wants is
 * to scan a column of entries for the ones with code. A tag can be scanned; a
 * sentence has to be read.
 */
.pub__artifacts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.6rem;
  margin-top: 0.55rem;
}

/* A tag that is also a link keeps the tag's own colour: the modifier class
   outranks the `a` element selector, so only the underline has to go. The
   border already marks it as a target. */
a.tag { text-decoration: none; }

a.tag:hover,
a.tag:focus-visible {
  color: var(--ink);
  border-color: currentColor;
}



/*
 * A statement: a term and its rule, one per row, hairline separated. Wider
 * measure than the glossary because each entry is a paragraph rather than a
 * definition, so multi-column would break the reading.
 */
.statement-list { display: grid; }

.statement-list__item {
  padding-top: 1rem;
  margin-bottom: 1.1rem;
  border-top: 1px solid var(--rule);
}

.statement-list__item:last-child { margin-bottom: 0; }

.statement-list dt {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.statement-list dd {
  margin: 0;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink);
}

/* Marks a sentence carried over unchanged from the statement this one adapts.
   Small, but not hidden: the page explains it, so it has to be findable. */
.cmark {
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--signal);
  white-space: nowrap;
}

/* Its own row at the end of the section, so the grid's row-gap already
   separates it from the note above. */
.statement-date {
  font-size: var(--step--1);
  color: var(--ink-soft);
}

/* ========================================================================
   12. Open practice
   ===================================================================== */

.openlist { row-gap: clamp(1.5rem, 3vw, 2.25rem); }

.openitem {
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.openitem__title {
  font-size: var(--step-1);
  margin-bottom: 0.4rem;
}

/* No measure cap. These sit one to a column, and the column is already a
   comfortable 33ch to 58ch across the range. A 34ch cap left the text filling
   barely half its column at the widest sizes, while the rule above it spanned
   the whole width, so the blocks read as mis-sized. */
.openitem p {
  color: var(--ink-soft);
  line-height: 1.6;
}

/* ========================================================================
   13. Footer
   ===================================================================== */

.footer {
  --paper: var(--inverse-bg);
  --ink: var(--inverse-text);
  --ink-soft: var(--inverse-soft);
  --signal: var(--inverse-signal);
  --signal-strong: var(--inverse-text);
  --rule: var(--inverse-rule);
  --focus: var(--inverse-signal);

  background: var(--inverse-bg);
  color: var(--inverse-text);
  padding-block: clamp(2rem, 4vw, 3.25rem);
}

/*
 * Three columns of the same shape — a rule, a label, a short list — then the
 * marks, then the fine print. Nothing here is offset, staggered or sized by
 * eye: a footer is the part of a page a reader scans for a fact, so it is
 * built to be predictable rather than interesting.
 */

.footer .shell { row-gap: clamp(1.4rem, 2.5vw, 2rem); }

.footer__title {
  font-size: var(--step--2);
  font-family: var(--font-text);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--inverse-soft);
  margin: 0 0 0.85rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--inverse-rule);
}

.footer p,
.footer address,
.footer li {
  font-size: var(--step--1);
  line-height: 1.6;
  font-style: normal;
}

.footer__list {
  display: grid;
  gap: 0.35rem;
}

/* The EU emblem keeps its own blue field and its 3:2 proportions, and is never
   inverted or tinted. The wording beside it is live text, not part of an
   image, so it stays selectable, translatable and legible when zoomed. */
.footer__euflag {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 0.8rem;
}

.footer__euflag picture {
  flex: none;
  width: 3.5rem;
}

.footer__euflag img {
  width: 100%;
  height: auto;
}

.footer__euflag span {
  font-size: var(--step--2);
  font-weight: 600;
  line-height: 1.3;
  color: var(--inverse-text);
}

/*
 * Both institutional marks on one baseline, at one size. The university lockup
 * is black line art on transparency, so it is inverted to read on the dark
 * footer — the one adjustment a monochrome mark allows. The lab plaque has a
 * white cut of its own and needs no filter.
 */
.footer__marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 2.5rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--inverse-rule);
}

/* Both marks are <picture> elements sized by a class, and the image inside
   simply fills whatever the picture is. Nothing depends on intrinsic widths
   or on how a flex item measures its contents. */
.footer__marks picture {
  display: block;
  flex: none;
}

.footer__marks img {
  display: block;
  width: 100%;
  height: auto;
}

.footer__plaque { width: min(11rem, 100%); }
.footer__univr { width: min(13rem, 100%); filter: invert(1); }

/*
 * The fine print: provenance, the funding disclaimer, the colophon and the
 * accessibility contact. All four are required or promised somewhere else in
 * the site, so none of them is dropped — they are set small, in two columns,
 * and got out of the way.
 */
.footer__fine {
  font-size: var(--step--2);
  line-height: 1.55;
  color: var(--inverse-soft);
}

.footer__fine p {
  font-size: inherit;
  margin-bottom: 0.7rem;
  break-inside: avoid;
}

.footer__fine p:last-child { margin-bottom: 0; }
.footer__fine strong { color: var(--inverse-text); font-weight: 600; }

@media screen and (min-width: 48em) {
  .footer__fine {
    column-count: 2;
    column-gap: clamp(2rem, 4vw, 3.5rem);
  }
}

/*
 * The projects supply flat white cuts of their marks. The page is always light,
 * so they are always inverted, which produces exactly the black cut ARMADA also
 * supplies. Inverting is the one adjustment a monochrome mark allows; a
 * full-colour mark would need its own rule instead.
 */
.project__logo img {
  width: min(11rem, 100%);
  height: auto;
  filter: invert(1);
}

/* ========================================================================
   15. Sub-pages: page header, breadcrumb, highlights, call to action
   ===================================================================== */

/*
 * Projects and publications each have their own page. The index keeps only
 * the highlights, so these components exist to open a sub-page with the same
 * inverse band as the hero, at about a third of its height.
 */

.pagehead {
  --paper: var(--inverse-bg);
  --ink: var(--inverse-text);
  --ink-soft: var(--inverse-soft);
  --signal: var(--inverse-signal);
  --signal-strong: var(--inverse-text);
  --rule: var(--inverse-rule);
  --focus: var(--inverse-signal);

  /* Shallower than the hero's, because there is no emblem crossing it. */
  --skew: var(--pagehead-skew);

  position: relative;
  z-index: 1;
  color: var(--inverse-text);
  padding-block: clamp(1.75rem, 4vw, 3rem) clamp(3rem, 7vw, 5.5rem);
  margin-bottom: calc(-1 * var(--skew));
}

.pagehead::before {
  background-image:
    radial-gradient(120% 120% at 85% 0%, var(--inverse-bloom), transparent 62%),
    var(--grain);
}

.pagehead .shell { position: relative; z-index: 1; }

.pagehead__h1 {
  font-size: var(--step-4);
  letter-spacing: 0.01em;
  margin-bottom: 0.9rem;
}

.pagehead__lead {
  max-width: var(--measure-lead);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--inverse-text);
}




/* Breadcrumb ------------------------------------------------------------ */

.crumbs { margin-bottom: 1rem; }

.crumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--inverse-soft);
}

.crumbs li + li::before {
  content: "/";
  margin-right: 0.5rem;
  opacity: 0.6;
}

.crumbs a { color: var(--inverse-soft); }
.crumbs a:hover,
.crumbs a:focus-visible { color: var(--inverse-signal); }

/* Project highlights on the index --------------------------------------- */

/*
 * Two records, offset from each other rather than set side by side in equal
 * boxes, so the index keeps the same diagonal rhythm as the rest of the page.
 */

.highlights { row-gap: clamp(1.75rem, 4vw, 2.75rem); }

.highlight {
  padding-top: 1.1rem;
  border-top: 3px solid var(--accent-2);
}

.highlight__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-bottom: 0.8rem;
}

.highlight__title {
  font-size: var(--step-2);
  margin-bottom: 0.3rem;
}

.highlight__title a { color: inherit; text-decoration: none; }
.highlight__title a:hover,
.highlight__title a:focus-visible {
  color: var(--signal);
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.highlight__sub {
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--ink-soft);
  margin-bottom: 0.7rem;
}

.highlight__desc {
  max-width: 44ch;
  margin-bottom: 1rem;
}

.highlight__meta {
  display: grid;
  gap: 0.6rem;
  font-size: var(--step--1);
}

.highlight__meta > div {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr);
  gap: 0.75rem;
  align-items: baseline;
  padding-top: 0.4rem;
  border-top: 1px solid var(--rule);
}

.highlight__meta dt {
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.highlight__meta dd { margin: 0; overflow-wrap: anywhere; }

/* Halves come from pure-u-md-1-2. Nothing is offset: two records side by side
   are read as a pair, and a staggered top edge just looks like a mistake. */


/* Publication groups ----------------------------------------------------- */

.pubgroup { display: grid; gap: 1.25rem; }

.pubgroup__title {
  font-size: var(--step-2);
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--accent-2);
}



/* Project cross-references ----------------------------------------------- */

.project__related {
  margin-top: 1rem;
  font-size: var(--step--1);
  color: var(--ink-soft);
}

/* Call to action rows ----------------------------------------------------- */

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
}

/* Inside a section head the row follows the standfirst it acts on, so it
   takes a paragraph's distance rather than the grid's row gap. */
.section__head .cta-row { margin-top: 1.25rem; }

/* On a light surface the hero button colours would be unreadable, so the
   two solid/outline variants below are the light-surface equivalents. */
.button--solid {
  background: var(--accent-2);
  border: 1px solid var(--accent-2);
  color: var(--page-paper);
}

.button--solid:hover,
.button--solid:focus-visible {
  background: var(--signal);
  border-color: var(--signal);
  color: var(--page-paper);
}

.button--outline {
  background: transparent;
  border: 1px solid var(--accent-2);
  color: var(--accent-2);
}

.button--outline:hover,
.button--outline:focus-visible {
  background: var(--accent-2);
  color: var(--page-paper);
}

/* Current page in the navigation ------------------------------------------ */

.nav__list a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--signal);
}

/* ========================================================================
   14. Motion and print
   ===================================================================== */

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }

  /*
   * One reveal, and only where the browser can do it without script. Guarded
   * by @supports, so a browser that cannot animate on scroll never sees the
   * zero-opacity start state and the content is simply there.
   */
  @supports (animation-timeline: view()) {
    .theme,
    .project,
    .person,
    .note,
    .glossary__item,
    .openitem,
    .highlight,
    .pub {
      animation: rise-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
  }
}

@keyframes rise-in {
  from { opacity: 0; transform: translateY(1.25rem); }
  to   { opacity: 1; transform: none; }
}

@media print {
  .masthead, .hero::before, .pagehead::before, .button { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .hero, .pagehead { background: none; color: #000; padding-block: 0 1rem; margin-bottom: 0; }
  .hero__figure { display: none; }
  .section, .footer { background: none; color: #000; padding-block: 1rem; }
  a { color: #000; text-decoration: underline; }
  .link-ext::after { content: " (" attr(href) ")"; font-size: 0.8em; opacity: 1; }
  .theme, .project, .person, .pub, .openitem { break-inside: avoid; }
}
