/* =============================================================================
   site.css — the SHELL. The masthead, the strata, the register bars, the
   publication list and the colophon: everything the two site pages
   (/ · /research) are made of. The contact band went with /contact, 2026-08-26.

   WHERE THIS CAME FROM. It is the inline <style> of
   mockups/direction-b-masthead.html — the approved design — lifted out of that
   file and made into a real stylesheet, with the authored comments kept because
   they are the record of why the non-obvious parts are the way they are. Three
   things were dropped in the move and each is noted where it used to be:

     · the FIVE-PAIRING TYPE AUDIT.   RECORD is ratified (research.css §0b), so
                                      one [data-type] block survives, below.
     · the REVIEW SWITCHER CSS.       Review chrome does not ship — the general
                                      rule is [data-review], see template.mjs.
     · the VIEW-ENTER ANIMATION.      There are no views. The mockup routed
                                      three panes on #hash inside one document;
                                      the build emits three real documents, so
                                      there is nothing to animate between and
                                      nothing to hide from a crawler.

   Two files sit under this one and this file declares neither of the things
   they own:

     identity/tokens.css   every colour, the ground palettes, the type scale,
                           --shell / --gutter / --radius / --hairline, and the
                           .is-dark accent re-point.
     research.css          the paper pattern library (.rp-*). NOT loaded by a
                           shell page — a shell page is not an article.

   Same law as research.css and register.css: NO colour and NO font-family is
   declared literally here. Every value is a var() into the token substrate.
   ============================================================================= */

/* ============================================================================
   DIRECTION B — the masthead and real pages
   ----------------------------------------------------------------------------
   THE SHEET IS GONE. The page is a vertical stack of STRATA. Every stratum
   spans the full viewport and is opened by a full-bleed datum rule; inside it,
   content is held to --shell by either

       .hold   — content sitting directly on the GROUND (masthead, running
                 head, the colophon), or
       .plate  — content on a raised SURFACE.

   The ground is therefore no longer a margin. It runs the full width of the
   page, above and below every plate, and it is what you see between strata.

   Three grounds, three theories of how a page separates its parts. The same
   DOM resolves to three different constructions because the aliases below
   resolve differently:

     cool  — MEASURED.  A faint plate registered against ruled datums, with a
                        narrow ground terrace between strata. Laboratory sheet.

     white — DRAWN.     No surface exists at all (plate == ground). Gaps close
                        to zero. Every boundary is therefore a line, and the
                        masthead and colophon datums go near-black. The page is
                        a line drawing. Monograph plate / wall text.
                        ▓▓▓ SHIPPED — data-ground="white" on every page. ▓▓▓

     grey  — BUILT.     Real surfaces. Plates float clear of each other on a
                        grey field, take a 2px edge, and the datums step back
                        to nothing — an object's own edge is its boundary.

   No new colours: every alias resolves to a token in identity/tokens.css.
   ========================================================================= */

:root {
  --meta:        var(--dim);
  --rule:        var(--hair);
  --rule-strong: var(--hair-strong);
  --well:        var(--sunk);
  --spine:       clamp(28px, 4vw, 72px);
  --col-head:    340px;

  /* cool — MEASURED */
  --plate:        var(--raised);
  --plate-edge:   var(--hair);
  --plate-radius: 0px;
  --datum:        var(--hair);
  --datum-strong: var(--hair-strong);
  --gap:          clamp(8px, 1.1vw, 18px);
}

/* white — DRAWN */
[data-ground="white"] {
  --plate:        transparent;
  --plate-edge:   var(--hair-strong);
  --plate-radius: 0px;
  --datum:        var(--hair-strong);
  --datum-strong: var(--ink-soft);
  --gap:          0px;
}

/* grey — BUILT */
[data-ground="grey"] {
  --plate:        var(--raised);
  --plate-edge:   transparent;
  --plate-radius: var(--radius);
  --datum:        transparent;
  --datum-strong: var(--hair-strong);
  --gap:          clamp(18px, 2.5vw, 38px);
}

/* ============================================================================
   THE TYPE DIAL — roles here, values in the [data-type] block below.

   The mockup carried five pairings so they could be compared live, and the note
   there said "Production keeps exactly one of these blocks." This is that file,
   and RECORD is the block: ratified by Joel, 2026-08-25, and carried by
   research.css §0b under the same banner. The other four are gone, along with
   the switcher that chose between them.

   These are NOT font-family swaps. Every face is drawn on a different body —
   different x-height, fitting, optical weight at the same nominal size — so the
   page's typography is expressed as ROLES and each pairing sets its own display
   size, weight, tracking and leading.

   Two laws hold whatever the face, because they belong to the institution and
   not to the drawing: the display register is SENTENCE CASE, and the wordmark
   and micro-labels are CAPS.

   The defaults in this block are ARCHIVO's dial — the family's incumbent, and
   still the correct dial for the MICRO-REGISTER, because RECORD's label face is
   Archivo Narrow. So the [data-type] block below overrides only the display and
   prose halves; the label register genuinely inherits from here rather than
   being re-stated.
   ========================================================================= */

:root {
  --font-mark:   var(--font-display);

  --w-display:   700;
  --tr-display:  -0.028em;
  --lh-display:  1.04;

  --w-section:   700;
  --tr-section:  -0.02em;
  --lh-section:  1.16;

  --tr-sub:      -0.01em;
  --tr-lede:     -0.014em;
  --lh-prose:    1.58;
  --lh-body:     1.62;

  --w-semi:      600;
  --w-bold:      700;

  --w-label:     600;
  --track-link:  0.06em;

  /* the drawn arrows' stroke, in viewBox units of 16. It is a TYPE token, not
     a geometry one: the arrow has to hold the same stem weight as the label it
     sits beside. 1.75 renders ~1.2px at an 11px label — the stem of a
     600-weight grotesque at that size. */
  --arrow-w:     1.75;

  --w-mark:      700;
  --tr-mark:     0.185em;
}

/* ------------------------------------------------------------------ RECORD
   Libre Franklin heads · Newsreader sets the text · Archivo Narrow labels.
   Newspaper of record — and it resurrects the family's retired serif in the one
   place a serif earns its keep: running prose. Franklin Gothic headlines are
   heavy, so display goes to 800 and the leading closes to 1.0. Newsreader has a
   generous x-height, so its nominal size comes up slightly, but its bold is
   dense — strong drops to 600 so emphasis does not blacken the paragraph.

   ONE DIFFERENCE FROM THE MOCKUP, and it is deliberate. The mockup's closest
   pairing was `franklin`, which set Libre Franklin on the labels too. RECORD
   does not: the house's condensed narrow is retained for the micro-register so
   every Inganci surface signs itself the same way — which is exactly what
   research.css §0b's RECORD block says, and this is the same pairing. So the
   label face changes and its dial (size, tracking, weight) stays at the :root
   defaults above, which are Archivo Narrow's.

   The three families here are the same fact as fonts.mjs PAIRINGS.record, which
   derives the <link> to Google Fonts. build/test.mjs asserts the two agree, so
   a family added in one place cannot silently go missing from the other.       */
[data-type="record"] {
  --font-display: "Libre Franklin", system-ui, -apple-system, sans-serif;
  --font-prose:   "Newsreader", Georgia, "Times New Roman", serif;
  --font-label:   "Archivo Narrow", "Archivo", system-ui, sans-serif;
  --font-mark:    "Libre Franklin", system-ui, sans-serif;

  --t-display:    clamp(34px, 5.2vw, 61px);
  --w-display:    800;
  --tr-display:   -0.024em;
  --lh-display:   1.0;

  --t-section:    clamp(24px, 2.6vw, 32px);
  --w-section:    700;
  --tr-section:   -0.018em;
  --lh-section:   1.14;

  --t-sub:        clamp(17.5px, 1.55vw, 20.5px);
  --tr-sub:       -0.004em;
  --t-lede:       clamp(17.5px, 1.6vw, 21px);
  --tr-lede:      -0.008em;
  --lh-prose:     1.56;
  --t-body:       16.5px;
  --lh-body:      1.6;

  --w-semi:       600;
  --w-bold:       600;   /* Newsreader 700 blackens a paragraph */

  --w-mark:       700;
  --tr-mark:      0.175em;
}

/* -------------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-prose);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: inherit; }

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

.skip {
  position: absolute; left: 12px; top: -80px; z-index: 60;
  background: var(--ink); color: var(--raised);
  font-family: var(--font-label); font-size: var(--t-label);
  font-weight: var(--w-label); letter-spacing: var(--track-label); text-transform: uppercase;
  padding: 10px 14px; text-decoration: none; border-radius: var(--radius);
  transition: top 120ms linear;
}
.skip:focus { top: 12px; }

/* ------------------------------------------------------------------ strata */

.stratum {
  margin-top: var(--gap);
  border-top: var(--hairline) solid var(--datum);
}

.hold, .plate {
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.plate {
  background: var(--plate);
  border-inline: var(--hairline) solid var(--plate-edge);
  border-radius: var(--plate-radius);
}

/* the three major horizons: masthead / first stratum of a document / colophon */
.masthead-stratum { margin-top: 0; border-top: 0; }
.view > .stratum:first-child,
.colophon-stratum { border-top-color: var(--datum-strong); }

/* THE ONE DARK STRATUM. Nothing on the site wears it today — the SCOUTSWING
   band that did was deleted with 02 (COPY.md) — and the rule is kept because
   re-deriving it later means re-deriving the --core-band reasoning with it:
   blues are dark hues, 8.73 on the white ground and ~2.2 on near-black, where
   a saturated one simply vanishes. identity/tokens.css carries the re-point
   (`.is-dark { --core: var(--core-band) }`) and the trap it creates — the
   MARK's core is locked to a literal and must never say var(--core). Read that
   note before putting a mark inside one of these. */
.stratum.is-dark {
  background: var(--band);
  border-top-color: transparent;
  color: var(--on-band);
}

/* ------------------------------------------------------------ drawn arrows

   Not a typographic arrow anywhere on this site. U+2197 and U+2192 were probed
   against all ten faces in the type audit — Libre Franklin, Newsreader,
   Archivo, Archivo Narrow, IBM Plex Sans, IBM Plex Mono, Public Sans, Source
   Serif 4, Instrument Sans, Instrument Serif — and NOT ONE of them carries
   either glyph. Every "↗" and "→" was falling through to whatever the system
   offered: a symbol font on macOS, Apple Color Emoji on iPhone. A variation
   selector (U+FE0E) cannot fix this, because there is no glyph in the family
   to select the presentation of.

   So they are drawn, from one definition — the sprite in template.mjs, emitted
   into every page — in the seal's own construction language: miter joins, butt
   caps, no round anything. `stroke-width` is set HERE and not as a presentation
   attribute on the path: an attribute would beat inheritance and freeze the
   weight; declared in CSS it inherits through the <use> shadow tree and stays a
   type token.                                                                */

.a {
  width: 1em;
  height: 1em;
  display: inline-block;
  vertical-align: -0.15em;
  flex: none;
  stroke-width: var(--arrow-w, 1.75);
}

/* ------------------------------------------------------------- the masthead */

.masthead {
  padding-block: clamp(40px, 6.2vw, 88px) clamp(24px, 3vw, 40px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px 40px;
  flex-wrap: wrap;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.62em;
  text-decoration: none;
  color: var(--ink);
  font-size: clamp(22px, 2.7vw, 34px);
}
/* The Inganci mark's page CSS is gone with the mark. Its whole reduction
   problem — canon stroke 4 resolving to 0.44px at colophon size — went with
   it too: nothing here now asks the seal to survive a size that destroys it.
   The seal's home is the favicon and the avatars; see mark-standard.html. */
.wordmark-text {
  font-family: var(--font-mark);
  font-weight: var(--w-mark);
  font-size: 1em;
  letter-spacing: var(--tr-mark);
  margin-right: calc(-1 * var(--tr-mark));
  line-height: 1;
}

/* nav — two short uppercase words, no boxes, no dropdowns; then SCOUTSWING,
   which is not a section of this site but a place you leave for, and is set
   apart accordingly. */
.masthead-nav {
  display: flex;
  align-items: center;
  gap: clamp(22px, 3vw, 44px);
}
.masthead-nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px clamp(20px, 2.6vw, 40px);
}
/* The current-page diamond is an ANNOTATION on the label, not a second line
   stacked under it. It used to be a flow child of a column flexbox, which made
   each link a 33px box with its label pinned to the top — so `align-items:
   center` dutifully centred a box whose text sat 7px above its own centre, and
   the nav rode 7px high of both SCOUTSWING and the wordmark. Out of flow, the
   link box collapses to the label box and all three sit on one axis. */
.masthead-nav ul a {
  position: relative;
  display: inline-block;
  text-decoration: none;
  color: var(--meta);
  font-family: var(--font-label);
  font-size: var(--t-label);
  font-weight: var(--w-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
  padding-block: 4px;
  transition: color 120ms linear;
}
.masthead-nav ul a::after {
  content: "";
  position: absolute;
  left: 50%; top: 100%;
  width: 5px; height: 5px;
  margin-top: 5px;
  background: transparent;
  transform: translateX(-50%) rotate(45deg);
}
.masthead-nav ul a:hover { color: var(--ink); }
/* `.is-current` is written by the BUILD, not by a script. The mockup set
   aria-current from its router on every hash change; there is no router now
   and there are three real documents, so which item is current is known when
   the file is written. The built page also carries aria-current="page" for
   assistive technology — the class is the paint, the attribute is the meaning,
   and both are static. */
.masthead-nav ul a.is-current { color: var(--ink); }
.masthead-nav ul a.is-current::after { background: var(--core); }

/* SCOUTSWING is a locked all-caps product name — the same species as the
   wordmark, so it takes the MARK face, not the label face. Solid ink is the
   page's one button idiom, already set by .send; the CTA does not invent a
   second one. It is the only edged object in the masthead: the live product is
   the only thing here you can actually go and use. */
.nav-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  flex: none;
  text-decoration: none;
  font-family: var(--font-mark);
  font-size: var(--t-label);
  font-weight: var(--w-mark);
  line-height: 1;
  color: var(--raised);
  background: var(--ink);
  border: var(--hairline) solid var(--ink);
  border-radius: var(--radius);
  padding: 12px 16px;
  transition: opacity 120ms linear;
}
.nav-cta:hover { opacity: 0.86; }

/* ---------------------------------------------------------- the nav toggle */

/* A pill, because the masthead already has exactly one edged object and this is
   the second — so it takes the softer edge: a hairline ring rather than the
   CTA's filled block. It never competes with SCOUTSWING for the same reading.

   Hidden at every width by default. Only [data-menu] + the narrow media query
   bring it in, which is what keeps the no-JavaScript path honest. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;                 /* the 44px touch target, not a coincidence */
  height: 44px;
  margin-left: auto;
  padding: 0;
  background: none;
  border: var(--hairline) solid var(--rule-strong);
  border-radius: 999px;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-toggle:focus-visible { outline: 2px solid var(--core); outline-offset: 3px; }

/* Two bars that become a cross. One element and its ::before/::after, animated
   on the button's OWN aria-expanded — so the DOM state and the drawn state
   cannot disagree, which they can when an icon is swapped by script. */
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 17px;
  height: 1.5px;
  background: currentColor;
  transition: transform 180ms ease, opacity 120ms ease;
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before,
.nav-toggle__bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle__bars::before { top: -5px; }
.nav-toggle__bars::after  { top:  5px; }

.nav-toggle[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after  { transform: translateY(-5px) rotate(-45deg); }

.nav-cta-text {
  letter-spacing: var(--track-label);
  margin-right: calc(-1 * var(--track-label));
}

/* -------------------------------------------------------------------- main */

main { flex: 1 0 auto; }

/* `.view` is no longer a routed pane — the mockup's three hidden <div>s are
   three files now. It survives as the name for a document's own stack of
   strata, because `.view > .stratum:first-child` is a real design rule: the
   first horizon of a document takes the strong datum, the way the masthead and
   the colophon do. */
.view { display: block; }

/* register strip — the running head. number · rule · section name.
   It belongs to the binding, not the document: it sits on bare ground. */
.reg {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-block: 14px;
  font-family: var(--font-label);
  font-size: var(--t-label);
  font-weight: var(--w-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1;
}
.reg-n { color: var(--ink); }
.reg-line { flex: 1 1 auto; height: var(--hairline); background: var(--rule); }
.reg-l { color: var(--meta); }

/* two-column register: heading left, prose right, spine rule between */
.register {
  display: grid;
  grid-template-columns: minmax(0, var(--col-head)) minmax(0, 1fr);
  padding-block: clamp(44px, 6.5vw, 96px) clamp(32px, 4vw, 60px);
}
.register-head { padding-right: var(--spine); }
.register-body {
  border-left: var(--hairline) solid var(--rule);
  padding-left: var(--spine);
}
/* Research has no body prose — the list is the content — so its title plate
   drops the second track. A spine rule with nothing hanging off it is a rule
   about nothing. */
.register.is-solo { grid-template-columns: minmax(0, 1fr); }
.register.is-solo .register-head { padding-right: 0; }

/* the section's actual register of record (the publications, the channels) */
.band { padding-block: clamp(32px, 4vw, 56px) clamp(48px, 6vw, 88px); }

h1.display {
  font-family: var(--font-display);
  font-weight: var(--w-display);
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  color: var(--ink);
}
h1.display.thesis { max-width: 21ch; }

/* The section blurb is abolished, so a section is heading + content and the
   heading does all the introducing there is. It is therefore set at full
   section weight, never as a micro-label eyebrow. */
.section-head {
  font-family: var(--font-display);
  font-weight: var(--w-section);
  font-size: var(--t-section);
  line-height: var(--lh-section);
  letter-spacing: var(--tr-section);
  color: var(--ink);
}

.prose p {
  font-size: var(--t-lede);
  line-height: var(--lh-prose);
  color: var(--ink-soft);
  max-width: var(--measure);
}
.prose p + p { margin-top: 1.35em; }
.prose strong { font-weight: var(--w-bold); color: var(--ink); }
.prose a {
  text-decoration: none;
  border-bottom: var(--hairline) solid var(--hair-strong);
  padding-bottom: 1px;
  transition: border-color 120ms linear;
}
.prose a:hover { border-bottom-color: var(--ink); }

.mlabel {
  font-family: var(--font-label);
  font-size: var(--t-label);
  font-weight: var(--w-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--meta);
  line-height: 1;
}

/* ------------------------------------------------------------------- home */

.opening {
  padding-block: clamp(44px, 6.5vw, 96px) clamp(40px, 5.5vw, 80px);
}
.opening-grid {
  display: grid;
  grid-template-columns: minmax(0, var(--col-head)) minmax(0, 1fr);
  margin-top: clamp(32px, 4.5vw, 64px);
}
/* The lede column is gone — its one sentence is now the opening clause of
   paragraph one, where the deck puts it. The head track stays, because it is
   the page's spine: the paragraphs hang off the same rule every section hangs
   off, so 01 is set in the register, not above it. */
.opening-grid .opening-col {
  grid-column: 2;
  border-left: var(--hairline) solid var(--rule);
  padding-left: var(--spine);
}

/* 02 · INSTRUMENTS is gone (COPY.md, 2026-08-25 — all four instrument
   demonstrations rejected, the section removed outright). Its CSS went with it:
   .instruments, .instrument-lockup, .instrument-name, .instrument-owner and
   .instrument-body had exactly one consumer between them. `.linkout` and its
   dark-ground variant survive — the research pane and the colophon use them.

   The stance band that sat beside it is GONE too, and this note deliberately
   does not restate the line: it is internal constitutional language, a source
   comment is a public surface, and quoting it here to explain the deletion
   would be the same leak in a quieter font. Deletion, not relocation — do not
   reinstate it in markup, meta, og: tags or JSON-LD. */

.linkout {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  align-self: flex-start;
  margin-top: 22px;
  font-family: var(--font-label);
  font-size: var(--t-small);
  font-weight: var(--w-label);
  letter-spacing: var(--track-link);
  text-decoration: none;
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--hair-strong);
  padding-bottom: 2px;
  transition: border-color 120ms linear;
}
.linkout:hover { border-bottom-color: var(--ink); }

/* kept for any future inverted stratum — costs one rule, and re-deriving it
   later means re-deriving the --core-band reasoning with it */
.is-dark .linkout {
  color: var(--on-band);
  border-bottom-color: var(--on-band-hair);
}
.is-dark .linkout:hover { border-bottom-color: var(--on-band); }

/* ---------------------------------------------- the honest-blank register: RETIRED

   It was a real device and it worked: a filled value printed in its type, an
   unfilled one printed as a ruled blank of the same measure, and every slot
   carried Live or Pending. It is gone from this site, and the rule that
   replaces it is broader than a layout decision — Joel:

     "we should not have a pending surface on the website … it is not
      institutional"
     "never put pending sentences/copy like that on any of our surfaces"

   So: no ruled blanks, no Pending chip, no Live chip either (the pair goes
   together — a lone status badge with nothing to contrast is decoration), and
   no forward-looking sentence. Nothing on an Inganci surface is drawn as
   absent, forthcoming, or in build. We name things when they ship.

   The register survives for INTERNAL instruments, where an unfilled field is
   information the operator needs. It does not survive contact with the public.
   ------------------------------------------------------------------------ */

/* ------------------------------------------------------------------ research

   The same six rows in two densities. On the homepage's 02 they are
   date · ID · title — the register entry. On the /research route each carries
   one line of dek, taken verbatim from the paper's own lede. Nothing else
   separates the two: the route is the same record, printed fuller.

   Both are generated from the papers' front matter by build/lib/register.mjs —
   renderHomeList() and renderFullList() — so the list is never authored twice.
   This block absorbs build/assets/register.css, which existed only to restate
   this geometry for a /research route the shell did not yet own.
   -------------------------------------------------------------------------- */

.pubs { margin-top: 18px; border-top: var(--hairline) solid var(--rule-strong); }
/* under a full section heading rather than a micro-label, the list needs the
   heading's own leading back */
.section-head + .pubs { margin-top: clamp(22px, 2.6vw, 32px); }
/* on the route the list opens the band with no heading above it — the band's
   own padding is the leading, and the 18px is a gap under nothing */
.band > .pubs:first-child { margin-top: 0; }
.pubs li { border-bottom: var(--hairline) solid var(--rule); }
.pub {
  display: grid;
  grid-template-columns: 92px 158px minmax(0, 1fr) 28px;
  align-items: baseline;
  gap: 0 20px;
  padding-block: 20px;
  text-decoration: none;
  transition: background-color 120ms linear;
}
.pub:hover { background: var(--well); }
.pub time, .pub-id {
  font-family: var(--font-label);
  font-size: var(--t-label);
  font-weight: var(--w-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--meta);
  white-space: nowrap;
}
.pub-main { display: block; min-width: 0; }
.pub-title {
  display: block;
  font-size: var(--t-body);
  font-weight: var(--w-semi);
  line-height: 1.42;
  letter-spacing: var(--tr-sub);
  color: var(--ink);
  max-width: 66ch;
}
/* a dek is one sentence, not a paragraph, so it runs to the full title track
   rather than to the prose measure — a 68ch cap here only widows short deks */
.pub-dek {
  display: block;
  margin-top: 7px;
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--meta);
}
/* the papers are ours — /research/<slug>, not an outbound link — so the row's
   arrow is level and its nudge is level with it */
.pub-go {
  justify-self: end;
  color: var(--meta);
  font-size: 15px;              /* the row affordance is sized, not inherited */
  align-self: center;
  transition: transform 140ms cubic-bezier(.2,.6,.2,1);
}
.pub:hover .pub-go { transform: translateX(3px); }
/* the fuller row runs to two blocks, so the whole entry breathes wider */
.pub.is-full { padding-block: 24px; }

.pubs-foot { margin-top: 22px; }

/* -------------------------------------------------------------------- footer */

.colophon { padding-block: clamp(40px, 5vw, 64px) 0; }
.colophon-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 40px;
  align-items: start;
}
.colophon .wordmark { font-size: 17px; }
/* three columns, so they read as a structure rather than as a block and a
   right-aligned afterthought — all left-aligned, the grid does the spacing */
.colophon-col { text-align: left; }
.foot-echo {
  margin-top: clamp(24px, 3vw, 38px);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 12px;
}
.foot-echo .sep { color: var(--mid); }
.foot-list { margin-top: 12px; }
.foot-list li { line-height: 1.9; }
.foot-echo a, .foot-list a {
  text-decoration: none;
  color: var(--ink-soft);
  font-size: var(--t-small);
  border-bottom: var(--hairline) solid transparent;
  transition: border-color 120ms linear, color 120ms linear;
}
/* the outbound rows carry a drawn arrow, so the anchor has to be a box that can
   hold one on the text's own axis */
.foot-list a { display: inline-flex; align-items: center; gap: 0.45em; }
.foot-echo a:hover, .foot-list a:hover { color: var(--ink); border-bottom-color: var(--hair-strong); }
.foot-bar {
  margin-top: clamp(36px, 4.5vw, 56px);
  border-top: var(--hairline) solid var(--rule);
  padding-block: 20px clamp(28px, 4vw, 44px);
}
.foot-bar p {
  font-family: var(--font-label);
  font-size: var(--t-label);
  font-weight: var(--w-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--meta);
}

/* ---------------------------------------------------------------- responsive */

@media (max-width: 900px) {
  :root { --col-head: 240px; }
  .pub { grid-template-columns: 84px 140px minmax(0, 1fr) 24px; }
}

@media (max-width: 720px) {
  .masthead {
    padding-block: clamp(32px, 8vw, 52px) 0;
    align-items: flex-start;
    gap: 0;
  }
  /* NARROW: the nav is behind a toggle.
     It used to be a plain stacked list of rows, always open — which on a phone
     meant the wordmark, three full-width rows and the SCOUTSWING block filled
     the first screen and pushed the H1 below the fold. The page opened on its
     own navigation. The rows themselves were right; being always-on was not.

     `[data-menu]` is set by script. Absent — JavaScript off, or before the
     script runs — none of these rules apply, the button stays hidden and the
     nav renders as the old open list. Nothing is ever behind a control that
     cannot be operated. */
  [data-menu] .nav-toggle { display: inline-flex; }
  [data-menu="closed"] .masthead-nav { display: none; }

  .masthead-nav {
    display: block;
    width: 100%;
    margin-top: clamp(24px, 6vw, 36px);
    border-top: var(--hairline) solid var(--rule);
  }
  .masthead-nav ul { display: block; }
  .masthead-nav li + li { border-top: var(--hairline) solid var(--rule); }
  .masthead-nav ul a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-block: 16px;
  }
  /* stacked, the marker returns to flow: on a full-width row it is the right
     hand terminal of the row, not an underline beneath a word */
  .masthead-nav ul a::after {
    position: static;
    margin-top: 0;
    transform: rotate(45deg);
  }
  .nav-cta {
    display: flex;
    justify-content: space-between;
    width: 100%;
    margin-top: 20px;
    margin-bottom: clamp(20px, 5vw, 30px);
    padding: 15px 18px;
  }

  .register, .opening-grid { grid-template-columns: minmax(0, 1fr); }
  .register-head { padding-right: 0; margin-bottom: 26px; }
  .register-body, .opening-grid .opening-col {
    border-left: 0;
    padding-left: 0;
  }
  .opening-grid .opening-col { grid-column: 1; }
  .contact-band { grid-template-columns: minmax(0, 1fr); }

  .pub {
    grid-template-columns: minmax(0, 1fr) 24px;
    row-gap: 6px;
  }
  .pub time { grid-column: 1; grid-row: 1; }
  .pub-id { grid-column: 1; grid-row: 1; justify-self: start; margin-left: 84px; }
  .pub-title, .pub-main { grid-column: 1 / 3; grid-row: 2; }
  .pub-go { grid-column: 2; grid-row: 1; }

  .field-row { grid-template-columns: 1fr; gap: 0; }
  .channel {
    grid-template-columns: minmax(0, 1fr) minmax(24px, auto);
    row-gap: 4px;
    gap: 12px;
  }
  .channel-name { grid-column: 1; grid-row: 1; }
  .channel-dest { grid-column: 1 / 3; grid-row: 2; }
  .channel-go { grid-column: 2; grid-row: 1; }

  .colophon-grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  /* the mockup padded 170px here to clear its fixed review panel. The panel
     does not ship, so neither does the padding. */
}

@media (max-width: 420px) {
  .pub-id { margin-left: 0; grid-row: 1; justify-self: end; margin-right: 34px; }
}

/* --------------------------------------------------------- reduced motion

   Everything that moves on a shell page is an affordance nudge — a row tint, an
   arrow sliding 3px under the cursor, the skip link dropping into view. None of
   it carries meaning that the still state does not, so under `reduce` all of it
   simply stops. (The mockup guarded only its view-enter animation, because that
   was the only motion it had that a reader could not opt out of by not
   hovering; that animation is gone with the router.)                          */

@media (prefers-reduced-motion: reduce) {
  .skip,
  .masthead-nav ul a,
  .nav-cta,
  .prose a,
  .linkout,
  .pub, .pub-go,
  .channel, .channel-go,
  .field input, .field textarea,
  .send,
  .foot-echo a, .foot-list a {
    transition: none;
  }
  .pub:hover .pub-go,
  a.channel:hover .channel-go { transform: none; }
}
