/* ==========================================================================
   PRISM — Principled Research In Software Mining
   School of Computing, Queen's University

   Single stylesheet for the whole site. Organised as:
     1. Design tokens
     2. Reset & base elements
     3. Layout primitives
     4. Header / navigation / footer
     5. Generic components (buttons, badges, cards, prose)
     6. Page-specific: publications
     7. Page-specific: members
     8. Page-specific: contact
     9. Print
   ========================================================================== */

/* 1. Design tokens ------------------------------------------------------- */

:root {
  /* Palette. Change --accent to re-skin the site. */
  --bg:            #ffffff;
  --bg-sunk:       #f6f7f9;
  --bg-raised:     #ffffff;
  --text:          #14171c;
  --text-muted:    #5a6371;
  --text-faint:    #838c99;
  --line:          #e2e6ec;
  --line-strong:   #c9cfd9;
  --accent:        #1f4fd8;
  --accent-dark:   #16389a;
  --accent-wash:   #eef2fd;
  --focus:         #1f4fd8;

  /* The "prism" motif: one spectral hairline, used sparingly. */
  --spectrum: linear-gradient(
    90deg,
    #d93b3b 0%, #e5892a 20%, #d9c02c 40%,
    #3f9e63 60%, #2f6fd0 80%, #7048c9 100%
  );

  /* Type. System stack: nothing to download, no third-party requests.
     To use a bespoke typeface, drop .woff2 files in assets/fonts/, add an
     @font-face block here, and prepend the family below. */
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;

  /* Spacing scale */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;

  --radius: 6px;
  --radius-lg: 10px;

  --measure: 68ch;      /* comfortable reading width */
  --page-max: 72rem;
}

/* 2. Reset & base -------------------------------------------------------- */

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

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

h1, h2, h3, h4 {
  line-height: 1.25;
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: 0 0 var(--s-3);
  text-wrap: balance;
}

h1 { font-size: clamp(1.9rem, 1.4rem + 1.8vw, 2.6rem); }
h2 { font-size: clamp(1.35rem, 1.15rem + 0.8vw, 1.7rem); margin-top: var(--s-7); }
h3 { font-size: 1.1rem; }

p, ul, ol, dl { margin: 0 0 var(--s-4); }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
a:hover { color: var(--accent-dark); }

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

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

code, kbd, samp, pre {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

code {
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

pre {
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
  overflow-x: auto;
}
pre code { background: none; border: 0; padding: 0; }

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--s-6) 0;
}

/* Visually hidden but available to screen readers. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -3rem;
  z-index: 100;
  background: var(--accent);
  color: #fff;
  padding: var(--s-2) var(--s-4);
  border-radius: 0 0 var(--radius) var(--radius);
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 0; color: #fff; }

/* 3. Layout primitives --------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--s-5);
}

main { flex: 1 0 auto; }

.section { padding-block: var(--s-7); }
.section + .section { padding-top: 0; }

.page-head {
  border-bottom: 1px solid var(--line);
  padding-block: var(--s-7) var(--s-5);
  margin-bottom: var(--s-6);
}
.page-head h1 { margin-bottom: var(--s-2); }
.page-head .lede {
  color: var(--text-muted);
  font-size: 1.075rem;
  max-width: var(--measure);
  margin: 0;
}

.prose { max-width: var(--measure); }
.prose h2:first-child { margin-top: 0; }
.prose ul { padding-left: 1.25rem; }
.prose li { margin-bottom: var(--s-2); }

.grid {
  display: grid;
  gap: var(--s-5);
}
@media (min-width: 40rem) {
  .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 60rem) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* 4. Header / nav / footer ----------------------------------------------- */

.logo-dropshadow {
  /* Syntax: drop-shadow(X-offset Y-offset Blur-radius Color) */
  filter: drop-shadow(1px -1px 0.5px rgba(0, 0, 0, 0.3));
}

.site-header {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  position: relative;
}
.site-header::before {
  content: "";
  display: block;
  height: 3px;
  background: var(--spectrum);
}

.site-header .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
  padding-block: var(--s-4);
}

.brand {
  display: flex;
  align-items: center;
  /* Wider than the gap between the wordmark and the tagline: the logo's
     spectrum fades out into scattered dots on its right edge, and without
     the extra room those dots crowd the "P". */
  gap: var(--s-4);
  text-decoration: none;
  color: inherit;
  min-width: 0;
}

/* Sized in rem so it scales with the user's text size rather than being
   pinned to a pixel height. width:auto keeps the aspect ratio from the
   width/height attributes. */
.brand-logo {
  flex: none;
  display: block;
}
.brand-logo img {
  display: block;
  height: 2.25rem;
  width: auto;
}

/* The wordmark and the tagline keep sitting on a shared baseline, and wrap
   onto two lines when the header gets narrow. */
.brand-text {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  flex-wrap: wrap;
  min-width: 0;
}
.brand:hover { color: inherit; }
.brand-mark {
  font-size: 1.4rem;
  font-weight: 750;
  letter-spacing: 0.06em;
  color: var(--text);
}
.brand-name {
  font-size: 0.875rem;
  color: var(--text-muted);
  letter-spacing: 0.01em;
}

.site-nav ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  margin: 0;
  padding: 0;
}
.site-nav a {
  display: block;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text-muted);
  font-weight: 550;
  font-size: 0.95rem;
}
.site-nav a:hover {
  background: var(--bg-sunk);
  color: var(--text);
}
.site-nav a[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-wash);
}

/* --- Collapsing navigation ----------------------------------------------

   The nav becomes a drop-down when the brand and the links can no longer
   share one line, and stays the plain row of links above whenever they fit.
   js/main.js owns every class in this block and builds the button itself.

   None of it exists until that script has run, which is the point: with
   scripting off the visitor keeps the plain nav — every link present,
   wrapping onto a second row, exactly as before this feature. A button
   written into the five HTML files by hand would be a dead control for
   those visitors, and a fifth place to keep the nav in sync.

   The threshold is measured rather than named in a media query because
   nothing here has a fixed width: the tagline, the link text and the
   padding are all in rem, so a visitor reading at 24px overflows a header
   that one at 16px has room to spare in. A hard-coded max-width would be
   right for one of them and wrong for the other. See collapsibleNav() in
   js/main.js. */

.site-nav.is-collapsible { position: relative; }

.nav-toggle {
  /* Hidden until the nav actually collapses. The button is in the DOM the
     whole time so that opening it does not have to build anything. */
  display: none;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 550;
  line-height: 1.2;
  cursor: pointer;
}
.nav-toggle:hover {
  background: var(--bg-sunk);
  color: var(--text);
}

/* Three bars drawn with borders rather than an SVG, an icon font or a
   background image: no extra request, no file to keep in sync with the
   palette, and it follows currentColor along with the label beside it. */
.nav-toggle-bars {
  flex: none;
  width: 1rem;
  height: 0.75rem;
  position: relative;
  border-top: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
}
.nav-toggle-bars::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  margin-top: -1px;
  border-top: 2px solid currentColor;
}

/* The collapsed nav grows into whatever is left of the header row and puts
   the button at its right-hand end. Two things follow. On a header with room
   the button sits at the right edge beside the brand, where the links used
   to be. On a narrow one the nav wraps to a row of its own — as this header
   always has — but that row now spans the full width, so `right: 0` on the
   panel below still means the right edge of the header, rather than the
   right edge of a 90px button stranded at the left with the panel hanging
   off the screen beside it.

   Forcing the whole header onto one line (flex-wrap: nowrap) also fixes the
   panel, and looks right down to about 500px. Below that the brand has to
   give up more room than it has: the wordmark breaks across two lines and
   the tagline stacks a word per line. A second row is the older, plainer
   answer and it degrades better on a phone.

   The state lives on .wrap rather than on .site-nav because it is the wrap's
   layout that changes; one element owns all three states so there is nothing
   to keep in step. */
.site-header .wrap.is-collapsed > .site-nav {
  flex: 1 1 auto;
  display: flex;
  justify-content: flex-end;
}
.site-header .wrap.is-collapsed .nav-toggle { display: inline-flex; }
.site-header .wrap.is-collapsed .site-nav > ul { display: none; }

/* Bordered rather than shadowed, because nothing else on the site casts a
   shadow. z-index keeps it over the page content but under the skip link,
   which has to stay reachable. */
.site-header .wrap.is-collapsed.is-open .site-nav > ul {
  display: block;
  position: absolute;
  top: calc(100% + var(--s-2));
  right: 0;
  z-index: 20;
  min-width: 11rem;
  padding: var(--s-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
}
.site-header .wrap.is-collapsed.is-open .site-nav > ul > li + li {
  margin-top: 2px;
}
.site-header .wrap.is-collapsed.is-open .site-nav a { white-space: nowrap; }

/* Measuring state, applied for one synchronous read and removed before the
   browser paints. Both children are frozen at their natural width so the
   comparison is against the width the header wants, not the width flexbox
   has already squeezed it into; the links return to a single row and the
   button leaves the flow, so neither the collapsed state nor the button's
   own width can influence the result. That is what stops the measurement
   from oscillating: it reads the same number whether the nav is currently
   collapsed or not.

   !important throughout. The stylesheet already uses it twice — to stop
   animation under prefers-reduced-motion, and to drop furniture from the
   printed page — but both of those are blunt overrides, and this is the only
   place where it is load-bearing. This state has to beat the two above by
   construction — it is applied for a single synchronous read and removed
   again — and the whole point is that
   the number it produces does not depend on which state was active when it
   ran. Left to ordinary specificity that would come down to which rule sits
   lower in this file, so re-ordering the block would silently change what
   the header measures. */
.site-header .wrap.is-measuring { flex-wrap: nowrap !important; }
.site-header .wrap.is-measuring > .brand,
.site-header .wrap.is-measuring > .site-nav { flex: none !important; }
.site-header .wrap.is-measuring .nav-toggle { display: none !important; }
.site-header .wrap.is-measuring .site-nav > ul {
  display: flex !important;
  flex-wrap: nowrap !important;
  position: static !important;
  padding: 0 !important;
  border: 0 !important;
  min-width: 0 !important;
  background: none !important;
}

.site-footer {
  flex-shrink: 0;
  margin-top: var(--s-8);
  border-top: 1px solid var(--line);
  background: var(--bg-sunk);
  color: var(--text-muted);
  font-size: 0.9rem;
}
.site-footer .wrap {
  padding-block: var(--s-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  justify-content: space-between;
}
.site-footer p { margin: 0 0 var(--s-1); }
.site-footer a { color: var(--text-muted); }
.site-footer a:hover { color: var(--accent); }

/* 5. Components ---------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-raised);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 550;
  line-height: 1.3;
  text-decoration: none;
  cursor: pointer;
}
.btn:hover {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent-dark);
}
.btn:disabled { opacity: 0.5; cursor: default; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
  color: #fff;
}

.btn-quiet {
  border-color: transparent;
  background: none;
  color: var(--text-muted);
  padding-inline: var(--s-2);
}
.btn-quiet:hover { background: var(--bg-sunk); color: var(--text); }

.badge {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 3px;
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid var(--line-strong);
  color: var(--text-muted);
  background: var(--bg-sunk);
  white-space: nowrap;
  vertical-align: 0.08em;
}
.badge-award {
  border-color: #d9a72c;
  background: #fdf6e3;
  color: #8a6410;
  text-transform: none;
  letter-spacing: 0;
}

.card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--s-5);
}
.card h3 { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

/* The vertical margin is symmetric on purpose. A callout sitting flush
   against the paragraph below it reads as part of that paragraph; the box
   needs as much air under it as the heading above leaves over it. The top
   margin collapses with whatever precedes the note, so this sets the gap on
   both sides rather than adding to the one that already exists. */
.note {
  border-left: 3px solid var(--accent);
  background: var(--accent-wash);
  padding: var(--s-4) var(--s-5);
  margin: var(--s-4) 0;
  border-radius: 0 var(--radius) var(--radius) 0;
  max-width: var(--measure);
}
.note > :last-child { margin-bottom: 0; }

.placeholder {
  border: 1px dashed var(--line-strong);
  background: var(--bg-sunk);
  color: var(--text-muted);
  border-radius: var(--radius);
  padding: var(--s-4) var(--s-5);
  max-width: var(--measure);
}
.placeholder > :last-child { margin-bottom: 0; }

/* 6. Publications -------------------------------------------------------- */

.pubs-layout {
  display: grid;
  gap: var(--s-6);
  align-items: start;
}
@media (min-width: 56rem) {
  .pubs-layout { grid-template-columns: 16rem minmax(0, 1fr); }
}

/* --- filter sidebar --- */

.filters {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-sunk);
  padding: var(--s-4);
}
@media (min-width: 56rem) {
  .filters {
    position: sticky;
    top: var(--s-4);
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
  }
}

.filters-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}
.filters-head h2 {
  margin: 0;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.facet {
  border-top: 1px solid var(--line);
  padding-block: var(--s-2);
}
.facet > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  font-weight: 600;
  font-size: 0.9rem;
}
.facet > summary::-webkit-details-marker { display: none; }
.facet > summary::after {
  content: "";
  width: 0.5rem; height: 0.5rem;
  border-right: 2px solid var(--text-faint);
  border-bottom: 2px solid var(--text-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.15s ease;
  flex: none;
}
.facet[open] > summary::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
.facet-count {
  font-weight: 500;
  font-size: 0.75rem;
  color: var(--text-faint);
  margin-left: auto;
  margin-right: var(--s-2);
}

.facet-list {
  list-style: none;
  margin: 0 0 var(--s-2);
  padding: 0;
  max-height: 15rem;
  overflow-y: auto;
}
.facet-list li { margin: 0; }

.facet-option {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  padding: 0.2rem var(--s-1);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1.45;
}
.facet-option:hover { background: var(--bg-raised); }
.facet-option input { margin: 0.35rem 0 0; flex: none; accent-color: var(--accent); }
.facet-option .n {
  margin-left: auto;
  color: var(--text-faint);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  padding-left: var(--s-2);
}
.facet-option.is-empty { opacity: 0.45; }

.facet-filter {
  width: 100%;
  font: inherit;
  font-size: 0.85rem;
  padding: 0.3rem 0.5rem;
  margin-bottom: var(--s-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-raised);
}

/* --- toolbar --- */

.pubs-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}

.search-field {
  position: relative;
  flex: 1 1 16rem;
  min-width: 0;
}
.search-field input {
  width: 100%;
  font: inherit;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-raised);
}
.search-field input:focus-visible { border-color: var(--accent); }

.sort-field {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.9rem;
  color: var(--text-muted);
}
.sort-field select {
  font: inherit;
  font-size: 0.9rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-raised);
  color: var(--text);
}

.pubs-status {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin-bottom: var(--s-4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
}

.active-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  background: var(--accent-wash);
  border: 1px solid #cddafa;
  color: var(--accent-dark);
  border-radius: 999px;
  padding: 0.1rem 0.25rem 0.1rem 0.6rem;
  font-size: 0.8rem;
  font-weight: 550;
}
.chip button {
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  padding: 0.15rem 0.3rem;
  border-radius: 999px;
}
.chip button:hover { background: #dbe4fb; }

/* --- results --- */

.year-group { margin-bottom: var(--s-6); }
.year-heading {
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--s-2);
  margin: 0 0 var(--s-4);
}

.pub-list { list-style: none; margin: 0; padding: 0; }

.pub {
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--line);
}
.pub:last-child { border-bottom: 0; }

.pub-title {
  font-size: 1.02rem;
  font-weight: 620;
  margin: 0 0 var(--s-1);
  line-height: 1.4;
}
.pub-authors {
  margin: 0 0 var(--s-1);
  font-size: 0.93rem;
  color: var(--text-muted);
}

.pub-meta {
  margin: 0 0 var(--s-3);
  font-size: 0.9rem;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
}
.pub-venue { font-style: italic; }

.pub-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
}

.pub-abstract {
  margin: var(--s-3) 0 0;
  padding: var(--s-3) var(--s-4);
  background: var(--bg-sunk);
  border-radius: var(--radius);
  font-size: 0.93rem;
  color: var(--text-muted);
  max-width: var(--measure);
}
.pub-abstract[hidden] { display: none; }

mark {
  background: #fff2a8;
  color: inherit;
  padding: 0 0.1em;
  border-radius: 2px;
}

.empty-state {
  text-align: center;
  padding: var(--s-8) var(--s-4);
  color: var(--text-muted);
}
.empty-state p { margin-bottom: var(--s-4); }

/* 7. Members ------------------------------------------------------------- */

.member-group { margin-bottom: var(--s-7); }
.member-group > h2 {
  margin-top: 0;
  font-size: 1.15rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--s-2);
}

.member-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-5);
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
}

.member {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.member-photo {
  aspect-ratio: 1 / 1;
  width: 100%;
  border-radius: var(--radius-lg);
  object-fit: cover;
  background: var(--bg-sunk);
  border: 1px solid var(--line);
}
.member-photo-fallback {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background: var(--bg-sunk);
  color: var(--text-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  font-weight: 650;
  letter-spacing: 0.05em;
}
.member-name { margin: 0; font-size: 1rem; font-weight: 620; }
.member-role { margin: 0; font-size: 0.9rem; color: var(--text-muted); }

/* Where the person is based, and who else supervises them. Both sit a step
   quieter than the role: they qualify it rather than compete with it, and a
   card can carry either, both, or neither. */
.member-affiliation,
.member-supervision {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.member-affiliation { font-style: italic; }
.member-supervision-label { color: var(--text-faint); }
.member-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: var(--s-1) 0 0;
  font-size: 0.85rem;
}

/* 8. Contact ------------------------------------------------------------- */

.contact-layout {
  display: grid;
  gap: var(--s-6);
  align-items: start;
}
@media (min-width: 56rem) {
  .contact-layout {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    /* stretch, not start: the map takes its height from the column beside it
       instead of standing at a fixed one. Its top lands on "Find us" and its
       bottom on the last line of the address, so the block reads as one
       rectangle however long the address grows. */
    align-items: stretch;
  }
}

.map-frame {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-sunk);
  /* A column, so the iframe can be told to take whatever height is left once
     the caption underneath it has taken its own. */
  display: flex;
  flex-direction: column;
}
.map-frame iframe {
  display: block;
  width: 100%;
  height: 380px;
  border: 0;
}
@media (min-width: 56rem) {
  .map-frame iframe {
    height: auto;
    flex: 1;
    /* The floor matters. The address beside it is short, and if it were ever
       shortened further the map would be what sets the row height rather than
       the other way round — at which point a map with no floor collapses to a
       strip of tiles. Below this, the map drives the height instead. */
    min-height: 16rem;
  }
}
.map-caption {
  font-size: 0.85rem;
  color: var(--text-muted);
  padding: var(--s-2) var(--s-3);
  border-top: 1px solid var(--line);
  margin: 0;
}

.address {
  font-style: normal;
  line-height: 1.7;
}

.dl-contact {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--s-2) var(--s-4);
  margin: 0;
}
.dl-contact dt {
  font-weight: 620;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.dl-contact dd { margin: 0; }

/* A <dd> that continues the one above it rather than answering a new <dt>.
   Auto-flow would drop it into the label column, so it is pinned to the value
   column instead. The alternative — an empty <dt> to fill the gap — is a
   labelled row with no label, which is what a screen reader would announce. */
.dl-contact .dd-continued { grid-column: 2; }

.faq { border-top: 1px solid var(--line); }
.faq details {
  border-bottom: 1px solid var(--line);
  padding: var(--s-3) 0;
}
.faq summary {
  cursor: pointer;
  font-weight: 600;
  padding: var(--s-1) 0;
}
.faq details > :last-child { margin-bottom: var(--s-2); }

/* 9. Print --------------------------------------------------------------- */

@media print {
  .site-header, .site-footer, .filters, .pubs-toolbar,
  .pub-actions, .skip-link, .active-filters { display: none !important; }
  body { font-size: 11pt; }
  .pubs-layout { display: block; }
  a { color: inherit; text-decoration: none; }
  .pub { break-inside: avoid; }
}

/* 10. Small utilities ---------------------------------------------------- */
/* These exist so that no element needs an inline style attribute. That is
   not tidiness for its own sake: it lets the Content-Security-Policy forbid
   inline styles outright, which closes off a whole class of injection. */

.lede-lg      { font-size: 1.1rem; color: var(--text-muted); }
.flush-top    { margin-top: 0; }
.flush        { margin: 0; }
.spaced-top   { margin-top: var(--s-5); }

/* A DOI is shown for reference and is not a link; it must not look like one. */
.btn-static {
  border-style: dashed;
  cursor: text;
  color: var(--text-muted);
}
.btn-static:hover {
  background: var(--bg-raised);
  border-color: var(--line-strong);
  color: var(--text-muted);
}

/* 11. Home page hero ----------------------------------------------------- */

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-5);
}

/* Introduction on the left, logo on the right, once there is room for both.
   The first column is capped at the reading measure the prose already uses,
   so the logo takes the width the text was never going to use rather than
   competing with it for space.

   Below the breakpoint the grid is a single column and the logo simply falls
   under the text. It is not hidden: it is the only place the wordmark
   appears, and a phone is where a visitor is most likely to meet the lab for
   the first time. */
.hero {
  display: grid;
  gap: var(--s-6);
  align-items: center;
}
@media (min-width: 56rem) {
  .hero { grid-template-columns: minmax(0, var(--measure)) minmax(0, 1fr); }
}

/* The logo is a photographic prism on a transparent background: it carries no
   border or panel of its own, so it needs a little breathing room on small
   screens where it sits directly under the text. max-width keeps it from
   growing into a banner on a wide monitor; height:auto holds the ratio the
   width and height attributes declare, so nothing shifts while it loads. */
.hero-logo {
  display: block;
  justify-self: center;
  padding-block: var(--s-4);
}
/* EXPERIMENT: the logo top-aligned with the first line of the introduction
   rather than centred against the whole block. The padding is dropped with
   it, so what lines up is the image's own top edge and not a padded box
   around it.

   This rule must stay after the .hero-logo block above: same specificity, so
   source order is what decides which padding-block wins.

   To drop the experiment, delete this whole rule. Nothing else depends on
   it, and the centred layout comes back. */
@media (min-width: 56rem) {
  .hero-logo {
    align-self: start;
    padding-block: 0;
  }
}

.hero-logo img {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
}
