/* ------------------------------------------------------------------
   acxdesign.com — Andy Twomey
   Hand-written CSS. No framework, no build step.
   ------------------------------------------------------------------ */

:root {
  /* The site is dark only — there is no light theme to translate for. These
     were the dark values; they are now the only values. */
  color-scheme: dark;

  --paper:      #131417;
  --paper-sunk: #1a1b1f;
  --ink:        #ececed;
  --ink-soft:   #c9c9ce;
  --muted:      #94949e;
  --rule:       #2b2c31;
  --accent:     #2196f3;   /* 5.90:1 on the dark page */
  --accent-ink: #0d1f1c;
  --shadow:     0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.7);

  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono:  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Blue headings. #2196f3 is 5.9:1 on the dark page but only 3.04:1 on
     the light one — fine for large text, short for the h2 at its smallest
     clamp size (22px, not bold), which counts as body text. Light mode gets
     the same hue darkened to 5.59:1. */
  --head-ink: #2196f3;

  /* Button fill. #FFEB3B carries dark ink at 14.65:1, so the one value
     works in both themes. */
  --btn-bg:  #FFEB3B;
  --btn-ink: #17171a;

  /* Stat text. #FFEB3B is 15:1 on the dark page and 1.1:1 on the light one —
     unreadable, not merely low. Light mode uses the same hue taken down to
     5.25:1; dark mode is the yellow itself. */
  --stat-ink: #FFEB3B;

  --measure: 34rem;
  --gutter: clamp(1.25rem, 4vw, 3rem);
}


*, *::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: .001ms !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* --- focus: visible and unmistakable. This site belongs to the
       accessibility guy; the focus ring is part of the argument. --- */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--btn-bg); color: var(--btn-ink);
  padding: .75rem 1.25rem; z-index: 100; font-weight: 600;
  text-decoration: none; border-radius: 0 0 6px 0;
}
.skip:focus { left: 0; top: 0; }

.wrap { width: min(72rem, 100% - (var(--gutter) * 2)); margin-inline: auto; }
.prose { max-width: var(--measure); }

/* ---------------------------------------------------------- header */

.site-head {
  border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(10px);
}
.site-head .wrap {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1.5rem; padding: 1rem 0; flex-wrap: wrap;
}
.wordmark {
  font-family: var(--sans); font-size: 1.25rem; font-weight: 700; letter-spacing: -.015em;
  letter-spacing: -.01em; color: var(--ink); text-decoration: none;
}
.wordmark span { color: var(--muted); font-weight: 400; }
.site-nav { display: flex; gap: 1.5rem; font-size: .9375rem; flex-wrap: wrap; }
.site-nav a { color: var(--ink-soft); text-decoration: none; padding-bottom: 2px; border-bottom: 1px solid transparent; }
.site-nav a:hover, .site-nav a:focus-visible { color: var(--accent); border-bottom-color: currentColor; }

/* ------------------------------------------------------------ hero */

.hero { padding: clamp(3.5rem, 9vw, 7rem) 0 clamp(2.5rem, 6vw, 4rem); }
.hero h1 {
  font-family: var(--sans); font-weight: 650;
  font-size: clamp(2.25rem, 5.5vw, 3.75rem);
  line-height: 1.08; letter-spacing: -.02em;
  margin: 0 0 1.5rem; max-width: 20ch;
}
.hero h1 em { font-style: normal; color: var(--accent); }
/* Keep the headline left of the planet: the limb starts around 46vw, and
   the text column starts at max(gutter, 50vw - 36rem). */
@media (min-width: 52.01rem) {
  .hero h1 {
    font-size: clamp(2.25rem, 4.6vw, 3.25rem);
    max-width: min(20ch, calc(46vw - max(var(--gutter), 50vw - 36rem)));
    text-wrap: balance;
  }
}
.hero .lede { font-size: clamp(1.0625rem, 1.6vw, 1.1875rem); color: var(--ink-soft); max-width: var(--measure); margin: 0 0 1rem; }
.hero .lede + .lede { margin-top: 1rem; }

.tagline {
  font-family: var(--mono); font-size: .75rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 1.5rem;
}

/* ------------------------------------------------------------ work */

.section { padding: clamp(2.5rem, 6vw, 4.5rem) 0; border-top: 1px solid var(--rule); }
.section-head { display: flex; align-items: flex-end; align-items: last baseline; justify-content: space-between; gap: 1rem; margin-bottom: 1.25rem; flex-wrap: wrap; } /* same as About's h2 → paragraph */
.section-head h2 { font-family: var(--sans); font-weight: 650; font-size: clamp(1.5rem, 3vw, 2rem); margin: 0; letter-spacing: -.015em; }
.section-head p { margin: 0; color: var(--muted); font-size: .9375rem; max-width: 32ch; }

.work-grid { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 48rem) { .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.card {
  display: flex; flex-direction: column;
  background: var(--paper-sunk);
  border: 1px solid var(--rule); border-radius: 10px;
  overflow: hidden;
}

.card-media {
  aspect-ratio: 16 / 9; background: var(--rule);
  display: grid; place-items: center; overflow: hidden; margin: 0;
  /* One track, pinned to the box the aspect-ratio defines. Without this the
     auto row grows to whatever the image is tall, height:100% resolves
     against that taller row, and object-fit never crops anything. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  border-bottom: 1px solid var(--rule);
}
/* The thumbnail is the card's one navigational element, so the link has to
   fill the aspect-ratio box the grid defines. */
.card-media > a {
  grid-area: 1 / 1; align-self: stretch; justify-self: stretch;
  display: block; width: 100%; height: 100%;
}

.card-media img,
.card-media video {
  /* The grid above centres its children so the placeholder text can sit in
     the middle. Media has to opt back out of that, or width/height 100%
     resolve to auto, the image keeps its own ratio, and object-position
     never gets a chance to anchor the crop. */
  grid-area: 1 / 1; align-self: stretch; justify-self: stretch;
  width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block;
}
/* The card's title carries a stretched link across the whole card; the looping
   video must not swallow clicks aimed at it. */
.card-media video { pointer-events: none; }

.card-media .placeholder {
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); text-align: center; padding: 1rem;
}

.card-body { padding: 1.5rem 1.5rem 1.75rem; display: flex; flex-direction: column; gap: .875rem; flex: 1; }
.card-kicker { font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 0; }
/* The card is not a link. Its title is plain text and the button inside is
   the only thing that navigates, so nothing promises a click the card will
   not honour. */
.card h3 { font-family: var(--sans); font-weight: 650; font-size: 1.5rem; line-height: 1.2; margin: 0; letter-spacing: -.01em; color: var(--ink); }
.card p { margin: 0; color: var(--ink-soft); font-size: .9688rem; }

/* Same type size as .cs-stats in the case studies, stacked as a bullet list.
   The text matches the description above it; the square marker carries the
   accent so the list reads as a list, not as a second colour of prose. */
.metrics {
  display: grid; gap: .3rem; list-style: square;
  padding: 0 0 0 1.15rem; margin: .35rem 0 0;
}
.metrics li {
  font-family: var(--sans); font-size: 1rem; line-height: 1.35;
  font-weight: 600; color: var(--ink-soft);
}
.metrics li::marker { color: var(--accent); }

/* Institution lockups. The source files are white knock-outs, so they are
   invisible on the light page — the filter flips them to ink there and the
   dark theme uses them as supplied. Heights are set per lockup because
   Bentley's is a vertical stack and the other two are horizontal; matching
   their box heights would shrink its wordmark to nothing. */
.card-org {
  /* .card-body is a flex column, so without align-self the image stretches to
     the full card width and centres itself. */
  align-self: flex-start;
  display: block; height: auto; margin: 1.5rem 0 0;
  /* The source files are white knock-outs, which is what this page wants. */
}
/* Both lockups are horizontal, so a shared width reads more evenly than a
   shared height would. */
.card-org--bentley { width: 160px; }
.card-org--hms     { width: 172px; }

.card-links { margin-top: auto; padding-top: .9rem; display: flex; flex-wrap: wrap; gap: .6rem; }
.card-links a {
  display: inline-flex; align-items: center; gap: .45rem;
  background: var(--btn-bg); color: var(--btn-ink);
  padding: .55rem 1rem; border-radius: 6px;
  text-decoration: none; font-weight: 600; font-size: .875rem; line-height: 1.2;
}
.card-links a::after { content: "\2192"; }
.card-links a:hover { background: color-mix(in srgb, var(--btn-bg) 86%, var(--btn-ink)); }

/* Arrow by destination, not by hand: a link that leaves this page — another
   site, or anything opening in a new tab — gets the diagonal; navigation
   inside the site keeps the horizontal one. */
.card-links a[href^="http"]::after,
.card-links a[target="_blank"]::after,
.tile-cta[href^="http"]::after,
.tile-cta[target="_blank"]::after,
.cta[href^="http"]::after,
.cta[target="_blank"]::after,
.btn[href^="http"]::after,
.btn[target="_blank"]::after { content: "\2197"; }

/* --------------------------------------------------------- listing */

.plain-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 1.25rem; }
.plain-list li { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: 1rem; align-items: baseline; border-bottom: 1px solid var(--rule); padding-bottom: 1.25rem; }
@media (max-width: 34rem) { .plain-list li { grid-template-columns: minmax(0, 1fr); gap: .35rem; } }
.plain-list .yr { font-family: var(--mono); font-size: .75rem; color: var(--muted); }
.plain-list a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule); }
.plain-list a:hover { color: var(--accent); border-bottom-color: currentColor; }
.plain-list .note { color: var(--muted); font-size: .875rem; }


/* --------------------------------------------------- also: tiles */

.tile-grid { display: grid; gap: clamp(1.25rem, 2.5vw, 2rem); row-gap: clamp(2.5rem, 5vw, 4rem); grid-template-columns: minmax(0, 1fr); } /* rows further apart than columns, so each button reads as part of its own row */
@media (min-width: 40rem) { .tile-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 62rem) { .tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.tile { display: flex; flex-direction: column; position: relative; }
.tile-media {
  margin: 0 0 .9rem; aspect-ratio: 16/10; overflow: hidden;
  border: 1px solid var(--rule); border-radius: 8px; background: var(--paper-sunk);
  transition: transform .09s ease-out; /* on the base state, so it plays in reverse on mouse-out */
}
.tile-media img, .tile-media video { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
/* The media lifts only when the tile's own link is hovered or focused.
   Lifting on any hover would promise a click the tile no longer accepts —
   and the tile with no link never lifts at all. */
.tile:has(.tile-cta:hover) .tile-media,
.tile:focus-within .tile-media { transform: translateY(-4px); }

.tile-body { display: flex; flex-direction: column; gap: .4rem; flex: 1; }
.tile-meta { margin: 0; display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
.tile-meta .yr { font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; color: var(--muted); }
.tile-meta .award {
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .06em;
  text-transform: uppercase;
  /* Blue label, grey rule — so the border needs its own value rather than
     riding on currentColor the way it did when both were the same grey. */
  color: var(--accent);
  border: 1px solid var(--muted); border-radius: 0;
  padding: .2rem .4rem;
  display: inline-flex; align-items: center; gap: .35rem;
}
/* The medal carries its own three fills, so it does not follow the label
   colour the way the border does. */
.award-ico { flex: none; position: relative; top: -.5px; }
.tile h3 { font-family: var(--sans); font-weight: 650; font-size: 1.125rem; line-height: 1.25; margin: 0; letter-spacing: -.005em; }

/* Each tile carries one explicit link rather than a whole-tile overlay, so
   the thing you can click is the thing that looks clickable — and a tile
   with nothing to open simply has no button. */
/* margin-top:auto pushes every button to the bottom of its tile, so a row's
   buttons line up. Needs the extra specificity to beat `.tile p` below. */
.tile .tile-body > p.tile-links { margin-top: auto; padding-top: .85rem; }
.tile-cta {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .45rem .9rem;
  background: var(--btn-bg); color: var(--btn-ink);
  border-radius: 6px; text-decoration: none;
  font-weight: 600; font-size: .8125rem; line-height: 1.2;
  transition: background-color .15s ease;
}
.tile-cta::after { content: "\2192"; font-size: 1.05em; line-height: 1; }
.tile-cta:hover { background: color-mix(in srgb, var(--btn-bg) 86%, var(--btn-ink)); }
.tile p:not(.tile-meta) { margin: 0; font-size: .875rem; color: var(--ink-soft); line-height: 1.55; }
.tile .note { color: var(--muted); }

.also-foot { margin: 3.5rem 0 0; font-size: .875rem; color: var(--muted); }
/* pull the About divider up 2rem (floor of 1.5rem on small screens) */
#also { padding-bottom: max(1.5rem, clamp(2.5rem, 6vw, 4.5rem) - 2rem); }

/* ------------------------------------------------------ case study */

/* A case study is one column of running text, so the column *is* the page.
   Bring the container down to the measure — a 34rem column parked at the
   left edge of a 72rem wrapper reads as a mistake — and let only the
   figures break out of it. */
.cs { --measure: 38rem; }
.cs main > .wrap { width: min(var(--measure), 100% - (var(--gutter) * 2)); }

.cs-head { padding: clamp(2.5rem, 6vw, 4.5rem) 0 2rem; }
.back { font-family: var(--mono); font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); text-decoration: none; display: inline-block; margin-bottom: 2rem; }
.back::before { content: "\2190  "; }
.back:hover { color: var(--accent); }

.cs-head h1 {
  font-family: var(--sans); font-weight: 650; font-size: clamp(2rem, 5vw, 3.25rem);
  line-height: 1.1; letter-spacing: -.02em; margin: 0 0 1rem; max-width: 18ch;
  color: var(--head-ink);
}
.cs-head .deck { font-size: clamp(1.0625rem, 1.8vw, 1.25rem); color: var(--muted); max-width: var(--measure); margin: 0; font-family: var(--sans); }

.cta {
  display: inline-flex; align-items: center; gap: .6rem;
  margin: 2rem 0 0; padding: .7rem 1.2rem;
  background: var(--btn-bg); color: var(--btn-ink);
  border-radius: 6px; text-decoration: none;
  font-weight: 600; font-size: .9375rem; line-height: 1.2;
}
.cta::after { content: "\2192"; font-size: 1.1em; line-height: 1; }
.cta:hover { background: color-mix(in srgb, var(--btn-bg) 86%, var(--btn-ink)); }

.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.meta { border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); margin: 2.5rem 0 0; padding: 1.5rem 0; display: grid; gap: 1.25rem 2rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.meta dt { font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: .35rem; }
.meta dd { margin: 0; font-size: .9375rem; color: var(--ink-soft); }
.meta a { color: var(--accent); }

.cs-body { padding: clamp(2rem, 5vw, 3.5rem) 0 4rem; }
.cs-body .prose > * { max-width: var(--measure); }
.cs-body h2 {
  font-family: var(--sans); font-weight: 650; font-size: clamp(1.375rem, 2.6vw, 1.75rem);
  letter-spacing: -.012em; margin: 3rem 0 1rem; color: var(--head-ink);
}
.cs-body h2:first-child { margin-top: 0; }
.cs-body h3 { font-size: 1.0625rem; font-weight: 700; margin: 2rem 0 .5rem; letter-spacing: -.005em; }
.cs-body p { margin: 0 0 1.15rem; }
.cs-body ul, .cs-body ol { margin: 0 0 1.25rem; padding-left: 1.25rem; }
.cs-body li { margin-bottom: .5rem; }
.cs-body strong { font-weight: 700; color: var(--ink); }
.cs-body a { color: var(--accent); text-underline-offset: 3px; }
.cs-body code { font-family: var(--mono); font-size: .875em; background: var(--paper-sunk); padding: .15em .4em; border-radius: 4px; border: 1px solid var(--rule); }

.cs-body blockquote {
  margin: 2rem 0; padding: 1.25rem 1.5rem;
  background: var(--paper-sunk); border-left: 3px solid var(--accent);
  border-radius: 0 8px 8px 0; font-family: var(--sans); font-size: 1.0625rem; color: var(--ink-soft);
}
.cs-body blockquote p:last-child { margin-bottom: 0; }

.table-scroll { overflow-x: auto; margin: 0 0 1.75rem; max-width: 100%; position: relative; } /* relative: keeps visually-hidden cell text inside the scroller */
.cs-body table { border-collapse: collapse; width: 100%; font-size: .9375rem; min-width: 30rem; }
.cs-body th, .cs-body td { text-align: left; padding: .6rem .85rem; border-bottom: 1px solid var(--rule); }
.cs-body th { font-family: var(--mono); font-size: .6875rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 400; }
.cs-body td:not(:first-child) { font-variant-numeric: tabular-nums; }

/* Topic matrix: which topics appear in which track of the a11y resource.
   Row labels stay pinned while the columns scroll on a phone. */
.cs-body .topic-matrix { min-width: 38rem; }
.topic-matrix th, .topic-matrix td { border-bottom: 1px solid var(--rule); }
.topic-matrix thead th { text-align: center; vertical-align: bottom; line-height: 1.35; padding: .5rem .6rem; }
.topic-matrix thead th:first-child { text-align: left; }
.topic-matrix .tm-group { color: var(--ink-soft); padding-bottom: .35rem; border-bottom-color: var(--muted); }
.topic-matrix tbody th {
  font-family: var(--sans); font-size: .9375rem; letter-spacing: 0; text-transform: none;
  color: var(--ink); font-weight: 500; padding: .55rem 1rem .55rem 0;
}
.topic-matrix th[scope="row"], .topic-matrix thead tr:first-child th:first-child {
  position: sticky; left: 0; background: var(--paper); z-index: 1;
}
.topic-matrix td { text-align: center; padding: .55rem .6rem; width: 15%; }
.topic-matrix .tm-intro, .topic-matrix td:nth-of-type(1) { border-right: 1px solid var(--rule); }
.tm-dot {
  display: inline-block; width: .7rem; height: .7rem; border-radius: 50%;
  background: var(--accent); vertical-align: middle;
}
.topic-matrix td:nth-of-type(1) .tm-dot { background: var(--ink); }
.table-scroll:focus-visible { outline-offset: 4px; }
@media (max-width: 40rem) {
  .topic-matrix tbody th { width: 9rem; font-size: .875rem; }
  .cs-body .topic-matrix { min-width: 32rem; }
}

figure.shot { margin: 2.5rem 0; max-width: 100%; }
figure.shot.wide { max-width: none; width: min(58rem, 100%); }
/* the one thing allowed wider than the measure */
.cs figure.shot.wide {
  width: min(54rem, 100vw - (var(--gutter) * 2));
  margin-left: 50%;
  transform: translateX(-50%);
}
/* xwide: for a side-by-side comparison, where each panel needs to be
   readable. Nothing else should use it. */
.cs figure.shot.xwide { width: min(68rem, 100vw - (var(--gutter) * 2)); }

/* Several separate images compared inside one figure. They share equal
   columns once there is room; below that the row scrolls sideways rather
   than stacking into a screen and a half of vertical scroll. */
figure.shot .row {
  display: grid; gap: .75rem;
  grid-auto-flow: column;
  grid-auto-columns: minmax(45%, 1fr);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: .25rem;
}
figure.shot .row > * { scroll-snap-align: start; }
/* Each row gets its own threshold: two panels need far less width to stay
   readable than five do. */
@media (min-width: 40rem) {
  figure.shot .row--2 { overflow-x: visible; grid-auto-columns: minmax(0, 1fr); }
}
@media (min-width: 52rem) {
  figure.shot .row--3 { overflow-x: visible; grid-auto-columns: minmax(0, 1fr); }
}
figure.shot .todo {
  aspect-ratio: 16/9; background: var(--paper-sunk); border: 1px dashed var(--rule);
  border-radius: 8px; display: grid; place-items: center; text-align: center; padding: 1.5rem;
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
figure.shot img,
figure.shot video { width: 100%; height: auto; display: block; border: 1px solid var(--rule); border-radius: 8px; background: var(--paper-sunk); }
figure.shot figcaption { font-size: .8125rem; color: var(--muted); margin-top: .65rem; }

.cs-next { border-top: 1px solid var(--rule); padding: 2.5rem 0 4rem; display: flex; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; }
.cs-next a { color: var(--ink); text-decoration: none; }
.cs-next a:hover { color: var(--accent); }
.cs-next .lbl { display: block; font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: .25rem; }
.cs-next .ttl { font-family: var(--sans); font-size: 1.0625rem; font-weight: 600; }

/* ----------------------------------------------------------- about */

.two-col { display: grid; gap: clamp(2rem, 5vw, 4rem); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 52rem) { .two-col { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); } }
.two-col p { max-width: var(--measure); }
.two-col h2 { font-family: var(--sans); font-weight: 650; font-size: clamp(1.5rem, 3vw, 2rem); margin: 0 0 1.25rem; letter-spacing: -.015em; }

.stack { display: grid; gap: 1.5rem; align-content: start; }
.stack h3 { font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 0 0 .5rem; font-weight: 400; }
.stack p, .stack ul { margin: 0; font-size: .9375rem; color: var(--ink-soft); }
.stack ul { list-style: none; padding: 0; display: grid; gap: .35rem; }
.stack a { color: var(--accent); }

.btn {
  display: inline-block; background: var(--btn-bg); color: var(--btn-ink);
  padding: .7rem 1.35rem; border-radius: 6px; text-decoration: none;
  font-weight: 600; font-size: .9375rem;
}
.btn:hover { filter: brightness(1.1); }
.btn.ghost { background: transparent; color: var(--accent); border: 1px solid currentColor; }

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

.site-foot { border-top: 1px solid var(--rule); padding: 2.5rem 0 3.5rem; color: var(--muted); font-size: .875rem; }
.site-foot .wrap { display: flex; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; }
.site-foot a { color: var(--accent); }
.site-foot a:hover { color: color-mix(in srgb, var(--accent) 70%, #fff); }

/* Homepage footer: hills and a telescope aimed at where Jupiter sits.
   Three greys. The sky band is painted --paper so the fixed planet layer
   can't show through it; the near hill is the same grey as the footer, so
   the ground runs straight on into it. */
.sky-fade { padding-bottom: 10rem; }
.site-foot--scene { --hill-h: 160px; --scope: 130px; --ground: #222329;
  border-top: 0; padding-top: 0; background: var(--ground); }
.foot-scene { position: relative; height: var(--hill-h); background: var(--paper); margin-bottom: .75rem; }
.foot-scene .hills { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 1; }
.foot-scene .wrap { position: relative; height: 100%; display: block; }
/* 8rem left of the column edge, but never closer than 1rem to the screen edge */
.foot-scene .scope { position: absolute; left: max(-7.75rem, 1rem - max(var(--gutter), 50vw - 36rem)); z-index: 0; width: var(--scope); height: var(--scope);
  top: calc(var(--hill-h) * .4125 - var(--scope) * .969); overflow: visible; }
.hill-far { fill: #1b1c21; }
.hill-near { fill: var(--ground); }
.scope-ink { fill: #4a4c56; stroke: #4a4c56; }
.scope-ring { fill: var(--ground); }
@media (max-width: 40rem) { .site-foot--scene { --hill-h: 110px; --scope: 100px; } }

/* ------------------------------------------------------- VERSION D
   Two things borrowed from version C:
     1. .tenure      — fifteen years at three named institutions, stated
                       on the homepage so tenure registers before the
                       project cards do.
     2. .cs-tldr     — the thirty-second read at the top of every case
                       study: three numbers and three lines. The long
                       prose below it is unchanged.
   ------------------------------------------------------------------ */

.tenure-wrap { margin-top: 3.5rem; }
.tenure {
  list-style: none; margin: 0; padding: 1.5rem 0;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  display: grid; gap: 1.25rem; grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 46rem) {
  .tenure { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2rem; }
}
/* Centre the row, but let the outer two sit flush with the rules above and
   below so the block reads as one measured span rather than three inset
   columns. */
.tenure li { display: flex; flex-direction: column; gap: .2rem; text-align: center; }
@media (min-width: 46rem) {
  .tenure li:first-child { text-align: left;  align-items: flex-start; }
  .tenure li:last-child  { text-align: right; align-items: flex-end; }
  .tenure li             { align-items: center; }
}
.t-place {
  font-family: var(--sans); font-size: 1.125rem; font-weight: 600;
  letter-spacing: -.012em; line-height: 1.2;
}
.t-role  { font-size: .875rem; color: var(--ink-soft); line-height: 1.4; }
.t-years { font-family: var(--mono); font-size: .6875rem; letter-spacing: .1em;
           text-transform: uppercase; color: var(--muted); margin-top: .1rem; }

/* --- the skim layer on a case study --- */
.cs-tldr {
  /* .cs-body is already the measure-width wrap, so take its full width
     and sit flush with the prose below it. */
  margin: 0 0 2.75rem;
  padding: 1.5rem 1.5rem 1.25rem;
  background: var(--paper-sunk);
  border-radius: 8px;
}
.cs-tldr > h2 {
  font-family: var(--sans); font-size: .75rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  margin: 0 0 1rem;
}
.cs-stats {
  list-style: none; margin: 0 0 1.25rem; padding: 0 0 1.25rem;
  border-bottom: 1px solid var(--rule);
  display: grid; gap: .5rem 1.5rem; grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 34rem) {
  .cs-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.cs-stats li {
  font-family: var(--sans); font-size: 1rem; line-height: 1.35;
  font-weight: 600; color: var(--stat-ink);
}
.tldr-rows { margin: 0; }
.tldr-rows dt {
  font-family: var(--sans); font-size: .6875rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  margin-top: .9rem;
}
.tldr-rows dt:first-child { margin-top: 0; }
.tldr-rows dd {
  font-family: var(--sans);
  margin: .25rem 0 0; font-size: .9688rem; line-height: 1.6;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------------- jupiter
   The JunoCam limb shot, fixed in the upper right.

   A circle fitted to the limb in that photograph puts Jupiter's centre at
   51.9% / 141.9% of the image. Rotating about that point leaves the limb arc
   where it is and sweeps the cloud features along it, which is what a
   turning globe looks like. The second drift runs on a different period so
   the compound motion does not settle into an obvious loop.

   Rate matters more than amplitude here: ease-in-out at a small amplitude
   worked out to about a fifth of a pixel per second near the ends of the
   cycle, which is under the threshold where anyone sees motion at all. This
   runs linear, so the rate is constant.
   ------------------------------------------------------------------ */
.jupiter {
  position: fixed;
  top: 2.5rem; right: -11vw;
  width: 71vw; aspect-ratio: 1600 / 1083;
  z-index: -2; overflow: hidden;
  pointer-events: none;
  opacity: .92;
  -webkit-mask-image: radial-gradient(74% 82% at 66% 32%, #000 28%, rgba(0,0,0,.6) 54%, transparent 78%);
          mask-image: radial-gradient(74% 82% at 66% 32%, #000 28%, rgba(0,0,0,.6) 54%, transparent 78%);
}
.jupiter b {
  position: absolute; inset: -22%;         /* room for the swing */
  display: block;
  background: url("../assets/jupiter-juno.jpg") 50% 26% / 86% auto no-repeat;
  transform-origin: 51.9% 141.9%;          /* the planet's own centre */
  will-change: rotate, translate;
  animation: jup-spin 42s linear infinite alternate,
             jup-drift 31s ease-in-out infinite alternate;
}
@keyframes jup-spin  { from { rotate: -8deg }      to { rotate: 8deg } }
@keyframes jup-drift { from { translate: -2.2% 0 } to { translate: 2.2% 0 } }

/* The planet is fixed, so it would otherwise sit behind every section that
   scrolls past it. Rather than a finite decorative overlay, this wraps every
   section below the hero: ordinary in-flow content always paints above a
   fixed, negative-z-index layer, so the fade is really just this wrapper's
   own background. The stops are in vh, not %, so the fade-in happens near
   the top and the rest of the (arbitrarily long) wrapper simply stays solid
   all the way to the bottom of the page, however tall that turns out to be. */
.sky-fade {
  background: linear-gradient(to bottom,
    transparent 0, rgba(19,20,23,.62) 12vh, rgba(19,20,23,.93) 22vh,
    var(--paper) 30vh, var(--paper) 100%);
}

@media (max-width: 52rem) {
  /* small enough to sit in the corner behind the header, clear of the tagline */
  .jupiter { width: 66vw; right: -26vw; top: 2rem; opacity: .6; }
}
@media (prefers-reduced-motion: reduce) {
  .jupiter b { animation: none; }
}
