@charset "utf-8";

/* ==========================================================================
   Digital Smarties — site stylesheet

   The visual identity comes from the CV (steve.html / steve.pdf): a white
   page, warm grey ink, a pale halftone dot field in the top-left corner, and
   a humanist sans. No colour blocks, no shadows, no heavy chrome.

     #4a4b4a   the ink — taken verbatim from the old cv.css `body { color }`
     dots.png  the corner halftone, rebuilt here as a CSS radial-gradient
     Geneva / Verdana   the typeface stack cv.css asked for

   The one place colour survives is the five smarties from the 1998 banner
   artwork, used as small accents. The halftone corner is the same motif in
   monochrome, which is what ties the two together.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Smartie accents — sampled from the original heading.gif ---------------- */
  --dot-red: #e8121f;
  --dot-orange: #f69c09;
  --dot-yellow: #ffd00b;
  --dot-green: #009247;
  --dot-blue: #0d7ab2;

  /* Ink & surfaces (light) ------------------------------------------------ */
  --bg: #ffffff;
  --bg-sunken: #f6f7f6;
  --border: #e3e5e2;
  --border-strong: #c9ccc7;

  --text: #4a4b4a;
  --text-strong: #2b2c2b;
  --text-muted: #6a6d69;
  --text-faint: #71756f;
  --link: var(--text);
  --link-hover: #0d7ab2;

  --accent: var(--text-strong);
  --on-accent: #ffffff;
  --halftone: rgb(74 75 74 / 30%);

  /* Type ------------------------------------------------------------------ */
  /* cv.css asked for Geneva, Verdana, Arial, Helvetica; steve.pdf is set in
     Calibri. Both are humanist sans faces, so the stack keeps that order and
     falls back to the platform UI face. */
  --font-sans: Geneva, Verdana, Calibri, "Segoe UI", system-ui, -apple-system,
    "Helvetica Neue", Arial, sans-serif;

  /* Geneva and Verdana both run wide, so the scale sits a step below a
     typical grotesk scale. */
  --step--1: clamp(0.78rem, 0.755rem + 0.12vw, 0.84rem);
  --step-0: clamp(0.94rem, 0.91rem + 0.16vw, 1.02rem);
  --step-1: clamp(1.09rem, 1.03rem + 0.28vw, 1.26rem);
  --step-2: clamp(1.26rem, 1.16rem + 0.46vw, 1.55rem);
  --step-3: clamp(1.46rem, 1.32rem + 0.68vw, 1.91rem);
  --step-4: clamp(1.69rem, 1.47rem + 1.05vw, 2.36rem);
  /* One step above the scale, for the home-page h1 only. Verdana carries a
     display size well; what it will not take is tight tracking. Kept modest —
     at much past 3rem its bold weight turns from confident to shouty. */
  --step-5: clamp(1.95rem, 1.5rem + 1.9vw, 2.9rem);

  /* Space ----------------------------------------------------------------- */
  --gutter: clamp(1.25rem, 0.9rem + 1.75vw, 2.5rem);
  --shell: 68rem;
  --measure: 40rem;
  --radius: 3px;
}

/* Dark values are declared twice — once for the OS preference and once for the
   explicit toggle — because a rule inside a media query cannot share a body
   with one outside it. The OS block is guarded by :not([data-theme="light"])
   so that an explicit light choice wins over a dark OS setting; that guard is
   what lets the light values live in :root alone, declared once. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #191a19;
    --bg-sunken: #202220;
    --border: #33352f;
    --border-strong: #474a44;

    --text: #d8dad6;
    --text-strong: #eceee9;
    --text-muted: #a9ada6;
    --text-faint: #9aa096;
    --link: var(--text);
    --link-hover: #7ec3e8;

    --accent: var(--text-strong);
    --on-accent: #191a19;
    --halftone: rgb(216 218 214 / 24%);
  }
}

:root[data-theme="dark"] {
  --bg: #191a19;
  --bg-sunken: #202220;
  --border: #33352f;
  --border-strong: #474a44;
  --text: #d8dad6;
  --text-strong: #eceee9;
  --text-muted: #a9ada6;
  --text-faint: #9aa096;
  --link: var(--text);
  --link-hover: #7ec3e8;
  --accent: var(--text-strong);
  --on-accent: #191a19;
  --halftone: rgb(216 218 214 / 24%);
}

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

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

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

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

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

/* The CV sets headings in the body face at plain bold — no display sizes, no
   tight tracking. Verdana in particular does not want negative letter-spacing. */
h1,
h2,
h3,
h4 {
  margin: 0 0 0.5em;
  line-height: 1.25;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--text-strong);
  text-wrap: balance;
}

h1 {
  font-size: var(--step-4);
}

h2 {
  font-size: var(--step-3);
}

h3 {
  font-size: var(--step-1);
}

h4 {
  font-size: var(--step-0);
}

p,
ul,
ol {
  margin: 0 0 1.1em;
}

li + li {
  margin-top: 0.4em;
}

a {
  color: var(--link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color 0.15s ease;
}

a:hover {
  color: var(--link-hover);
}

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

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

hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 2.5rem 0;
}

strong,
b {
  font-weight: 700;
  color: var(--text-strong);
}

/* Skip link ---------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -100%;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus {
  top: 0.5rem;
}

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

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

.section {
  padding-block: clamp(2.75rem, 2rem + 3.5vw, 5rem);
}

/* Consecutive sections are told apart by a hairline rule rather than by a
   tinted band — the idiom the CV already used, and the reason the page stays
   white all the way down. */
.section + .section {
  position: relative;
  border-top: 1px solid var(--border);
}

/* A short smartie segment sits on the hairline, cycling the five colours down
   the page the way the banner dots ran across it. It starts at the shell's
   left edge so it aligns with the heading below, not with the viewport. */
.section + .section::before {
  content: "";
  position: absolute;
  top: -1px;
  left: max(var(--gutter), (100% - var(--shell)) / 2);
  inline-size: 3.5rem;
  block-size: 2px;
  background: var(--dot, var(--dot-red));
}

.section:nth-of-type(5n + 1) {
  --dot: var(--dot-red);
}

.section:nth-of-type(5n + 2) {
  --dot: var(--dot-orange);
}

.section:nth-of-type(5n + 3) {
  --dot: var(--dot-yellow);
}

.section:nth-of-type(5n + 4) {
  --dot: var(--dot-green);
}

.section:nth-of-type(5n + 5) {
  --dot: var(--dot-blue);
}

.prose {
  max-width: var(--measure);
}

.lede {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 34ch;
}

.section-head {
  margin-bottom: clamp(1.5rem, 1rem + 2vw, 2.5rem);
}

/* These intros are one or two sentences, not body copy, so they run the full
   shell width rather than being held to --measure — at 40rem a single
   sentence wrapped onto three lines for no reason. Long-form prose still uses
   .prose, which keeps the measure. */
.section-head p {
  margin-bottom: 0;
  color: var(--text-muted);
  max-width: none;
}

/* Eyebrow: small label with a smartie dot in front ------------------------- */

.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 0.9rem;
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.eyebrow::before {
  content: "";
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  background: var(--dot, var(--dot-orange));
  flex: none;
}

/* --------------------------------------------------------------------------
   4. Header, navigation, footer
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.site-header__bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4rem;
}

/* Wordmark ----------------------------------------------------------------- */

/* The CV carries no wordmark at all, so this follows its typography instead:
   the body face, plain, with weight rather than colour doing the work. */
.wordmark {
  display: inline-flex;
  align-items: baseline;
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.25rem);
  letter-spacing: 0.005em;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  margin-right: auto;
  padding-block: 0.35rem;
}

.wordmark__digital {
  color: var(--text-muted);
  font-weight: 400;
}

.wordmark__smarties {
  color: var(--text-strong);
  font-weight: 700;
}

.wordmark__reg {
  font-size: 0.45em;
  color: var(--text-faint);
  transform: translateY(-0.8em);
  display: inline-block;
  margin-left: 0.12em;
}

.wordmark:hover .wordmark__digital,
.wordmark:hover .wordmark__smarties {
  color: var(--link-hover);
}

/* Nav ---------------------------------------------------------------------- */

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

.nav a {
  display: block;
  padding: 0.5rem 0.7rem;
  color: var(--text-muted);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}

.nav a:hover {
  color: var(--text-strong);
  border-bottom-color: var(--border-strong);
}

/* The current page is marked with a smartie, which is the only colour in the
   header. */
.nav a[aria-current="page"] {
  color: var(--text-strong);
  font-weight: 700;
  border-bottom-color: var(--dot-red);
}

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

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.35rem;
  block-size: 2.35rem;
  flex: none;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.icon-btn:hover {
  background: var(--bg-sunken);
  color: var(--text-strong);
}

.icon-btn svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
  display: block;
}

.icon-btn:focus-visible {
  outline-color: var(--accent);
}

/* The theme button shows the icon for the mode it switches *to*.
   These need to outrank `.icon-btn svg`, hence the compound selectors. */
.icon-btn .theme-toggle__moon {
  display: none;
}

:root[data-theme="dark"] .icon-btn .theme-toggle__sun {
  display: none;
}

:root[data-theme="dark"] .icon-btn .theme-toggle__moon {
  display: block;
}

.nav-toggle {
  display: none;
}

/* Mobile navigation -------------------------------------------------------- */

/* Without JS the nav simply wraps and stays visible — the toggle is only
   revealed once the `js` class is on <html>.

   NOTE: 56em is the nav breakpoint. Below it the six primary links plus the
   wordmark no longer fit on one row. site.js matches on the same value —
   keep the two in sync. */
@media (max-width: 55.99em) {
  .js .nav-toggle {
    display: inline-flex;
  }

  .js .nav {
    display: none;
    position: absolute;
    inset-inline: 0;
    top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.6rem var(--gutter) 1rem;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 16px 32px -24px rgb(0 0 0 / 35%);
  }

  .js .nav.is-open {
    display: flex;
  }

  .js .nav a {
    padding: 0.7rem 0.75rem;
    font-size: var(--step-0);
    border-bottom: 1px solid var(--border);
  }

  .js .nav a[aria-current="page"] {
    border-bottom-color: var(--dot-red);
  }

  .nav {
    flex-wrap: wrap;
  }
}

/* Search ------------------------------------------------------------------- */

/* Like the nav toggle, the button is only useful once the script is running. */
.search-toggle {
  display: none;
}

.js .search-toggle {
  display: inline-flex;
}

/* The header is sticky, which makes it the containing block for this panel. */
.search {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  padding-block: 1rem 1.25rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 16px 32px -24px rgb(0 0 0 / 35%);
  max-block-size: calc(100vh - 4rem);
  overflow-y: auto;
}

.search[hidden] {
  display: none;
}

.search__input {
  inline-size: 100%;
  padding: 0.65rem 0.85rem;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}

.search__input::placeholder {
  color: var(--text-faint);
}

.search__status {
  margin: 0.8rem 0 0;
  font-size: var(--step--1);
  color: var(--text-faint);
}

.search__results {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
}

.search__results li + li {
  margin-top: 0;
}

.search__results a {
  display: block;
  padding: 0.7rem 0.5rem;
  border-top: 1px solid var(--border);
  text-decoration: none;
}

.search__results a:hover,
.search__results a:focus-visible {
  background: var(--bg-sunken);
}

.search__title {
  display: block;
  font-weight: 700;
  color: var(--text-strong);
}

.search__where {
  display: block;
  font-size: var(--step--1);
  color: var(--text-faint);
}

.search__snippet {
  display: block;
  margin-top: 0.25rem;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--text-muted);
}

/* The yellow smartie, doing the one job a highlight needs. */
.search mark {
  background: var(--dot-yellow);
  color: #2b2c2b;
  padding: 0 0.1em;
}

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

.site-footer {
  position: relative;
  overflow: hidden;
  margin-top: auto;
  padding-block: 2.5rem;
  border-top: 1px solid var(--border);
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* The halftone returns in the opposite corner to close the page, mirrored so
   the mask fades towards the middle. Same tile as .hero::before, at a lower
   opacity because the footer type sits closer to it. */
.site-footer::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  inline-size: min(17rem, 40vw);
  block-size: min(17rem, 40vw);
  background-image: radial-gradient(var(--halftone) 2px, transparent 2.1px);
  background-size: 11px 11px;
  -webkit-mask-image: radial-gradient(
    118% 78% at 100% 100%,
    #000 0%,
    rgb(0 0 0 / 40%) 38%,
    transparent 68%
  );
  mask-image: radial-gradient(
    118% 78% at 100% 100%,
    #000 0%,
    rgb(0 0 0 / 40%) 38%,
    transparent 68%
  );
  opacity: 0.55;
  pointer-events: none;
}

.site-footer > .shell {
  position: relative;
}

.site-footer__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.5rem;
  align-items: baseline;
  justify-content: space-between;
}

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
}

.site-footer a {
  color: var(--text-muted);
}

.site-footer a:hover {
  color: var(--link);
}

.site-footer__legal {
  margin: 1.75rem 0 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: 0.94em;
}

.site-footer__legal p {
  margin: 0;
}

/* Smartie dot rule — the five colours from the original banner ------------- */

.dots {
  display: flex;
  gap: 0.4rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.dots span {
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
}

.dots span:nth-child(1) {
  background: var(--dot-red);
}

.dots span:nth-child(2) {
  background: var(--dot-orange);
}

.dots span:nth-child(3) {
  background: var(--dot-yellow);
}

.dots span:nth-child(4) {
  background: var(--dot-green);
}

.dots span:nth-child(5) {
  background: var(--dot-blue);
}

/* --------------------------------------------------------------------------
   5. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  background: var(--bg);
  /* Top padding clears the halftone: the CV set its content clear of the
     corner graphic too, just by indenting instead. */
  padding-block: clamp(5.5rem, 3.5rem + 7vw, 9rem)
    clamp(2.75rem, 2rem + 4vw, 5rem);
}

/* Halftone corner ---------------------------------------------------------- */

/* The brand mark: a rebuild of images/dots.png, the grey halftone that sits
   in the top-left corner of the CV. dots.png fades by shrinking its dots; a
   single gradient tile can only fade opacity, which reads the same at size.
   It is applied to the top of every page, which is what makes the motif the
   site's identity rather than a home-page flourish. */
.hero::before,
.page-head::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  inline-size: min(22rem, 48vw);
  block-size: min(22rem, 48vw);
  z-index: 0;
  background-image: radial-gradient(var(--halftone) 2px, transparent 2.1px);
  background-size: 11px 11px;
  -webkit-mask-image: radial-gradient(
    118% 78% at 0% 0%,
    #000 0%,
    rgb(0 0 0 / 45%) 38%,
    transparent 68%
  );
  mask-image: radial-gradient(
    118% 78% at 0% 0%,
    #000 0%,
    rgb(0 0 0 / 45%) 38%,
    transparent 68%
  );
  pointer-events: none;
}

.hero > *,
.page-head > * {
  position: relative;
  z-index: 1;
}

/* On the home page the halftone is the page's largest element: a coarser tile
   covering a third of the viewport, running behind the heading rather than
   stopping above it. Interior pages keep the small corner version. */
.hero::before {
  inline-size: min(38rem, 80vw);
  block-size: min(34rem, 72vw);
  background-size: 13px 13px;
  -webkit-mask-image: radial-gradient(
    105% 85% at 0% 0%,
    #000 0%,
    rgb(0 0 0 / 38%) 44%,
    transparent 74%
  );
  mask-image: radial-gradient(
    105% 85% at 0% 0%,
    #000 0%,
    rgb(0 0 0 / 38%) 44%,
    transparent 74%
  );
}

/* The only heading on the site not set in bold. Verdana's bold is very heavy,
   and at display size it reads as shouting; regular weight at the same size
   is softer without losing any presence. The colour does the emphasis
   instead. */
.hero h1 {
  max-width: 18ch;
  font-size: var(--step-5);
  font-weight: 400;
  line-height: 1.18;
}

.hero p {
  max-width: 46ch;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--text-muted);
}

.hero .dots {
  margin-bottom: 1.5rem;
}

/* Heading and lede on the left, the smartie scatter on the right; the
   taglines run full width beneath both. Collapses to one column below the nav
   breakpoint, where the scatter sits above the text. */
.hero__grid {
  display: grid;
  gap: clamp(1.5rem, 1rem + 3vw, 3.5rem);
  align-items: center;
}

@media (min-width: 56em) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  }
}

.hero__art {
  inline-size: 100%;
  max-inline-size: 24rem;
  block-size: auto;
  justify-self: center;
}

@media (max-width: 55.99em) {
  .hero__art {
    max-inline-size: 15rem;
    order: -1;
    justify-self: start;
  }
}

/* The taglines sit under a hairline that spans the full measure, which is what
   separates the statement above from the three claims below. They stay in one
   column, stacked, and run the full shell width so that each claim sits on a
   single line instead of wrapping. */
.hero .taglines {
  border-top: 1px solid var(--border);
  padding-top: clamp(1.25rem, 1rem + 1vw, 1.75rem);
  margin-top: clamp(1.75rem, 1.25rem + 2vw, 3rem);
  max-width: none;
}

.hero__eyebrow {
  color: var(--text-faint);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  margin: 0 0 0.75rem;
}

/* Page header (interior pages) --------------------------------------------- */

.page-head {
  position: relative;
  overflow: hidden;
  padding-block: clamp(5rem, 3.5rem + 5vw, 7rem)
    clamp(2.25rem, 1.5rem + 3vw, 3.5rem);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

/* Interior headers are shorter than the hero, so the mark is a wide, shallow
   band rather than a square — otherwise it reaches down into the heading. */
.page-head::before {
  block-size: min(14rem, 32vw);
}

/* Regular weight, matching the home page: Verdana's bold at heading size is
   heavy enough to read as shouting. Interior h1s stay at --step-4, so the
   home page is still the loudest thing on the site. */
.page-head h1 {
  margin-bottom: 0.35rem;
  font-weight: 400;
}

.page-head p {
  margin: 0;
  max-width: 56ch;
  color: var(--text-muted);
  font-size: var(--step-1);
  line-height: 1.55;
}

.page-head p + p {
  margin-top: 0.8rem;
}

/* --------------------------------------------------------------------------
   6. Taglines, cards, grids
   -------------------------------------------------------------------------- */

/* The CV states these as a plain indented bullet list, not as panels. */
.taglines {
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  max-width: 46rem;
}

.taglines li {
  position: relative;
  margin: 0 0 0.5rem;
  padding-left: 1.6rem;
  line-height: 1.55;
}

.taglines li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  background: var(--dot, var(--dot-red));
}

.taglines li:nth-child(1) {
  --dot: var(--dot-orange);
}

.taglines li:nth-child(2) {
  --dot: var(--dot-green);
}

.taglines li:nth-child(3) {
  --dot: var(--dot-blue);
}

.taglines .hilite {
  color: var(--text-strong);
  font-weight: 700;
}

/* Generic auto-fitting grid ------------------------------------------------ */

.grid {
  display: grid;
  gap: clamp(1rem, 0.7rem + 1.2vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
}

.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

/* Nothing in the CV is boxed, so grouped content is separated by a rule
   rather than by a panel. */
.card {
  padding: 1.1rem 0 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--border-strong);
}

.card > :last-child {
  margin-bottom: 0;
}

.card h3 {
  margin-bottom: 0.4rem;
}

.card p {
  color: var(--text-muted);
  margin-bottom: 0;
}

.card--flush {
  border-top: 0;
  padding-top: 0;
}

/* Skills / capability lists ------------------------------------------------

   These were boxed pills. Nothing in the CV is boxed, so they are now a plain
   bullet list.

   One column, full width: several of these run long enough that a second
   column would wrap them mid-phrase. */

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

.chips li {
  position: relative;
  margin: 0 0 0.5rem;
  padding-left: 1.5rem;
}

.chips li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  inline-size: 0.45rem;
  block-size: 0.45rem;
  border-radius: 50%;
  background: var(--dot, var(--text-faint));
}

/* Ticked list -------------------------------------------------------------- */

.ticks {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 52rem;
}

.ticks li {
  position: relative;
  padding-left: 1.6rem;
  margin-top: 0.55rem;
}

.ticks li:first-child {
  margin-top: 0;
}

.ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
}

/* --------------------------------------------------------------------------
   7. Project entries
   -------------------------------------------------------------------------- */

.projects {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1rem, 0.7rem + 1.2vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

/* Each entry is headed by a rule in one of the five smartie colours — the
   only colour on the page, cycling the way the banner dots did. */
.projects > li {
  margin: 0;
  padding: 1.1rem 0 0;
  background: none;
  border: 0;
  border-top: 2px solid var(--dot, var(--accent));
}

.projects > li > :last-child {
  margin-bottom: 0;
}

.projects h2 {
  font-size: var(--step-0);
  margin-bottom: 0.4rem;
}

.projects p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.97em;
}

/* Cycle the five smartie colours across the cards. */
.projects > li:nth-child(5n + 1) {
  --dot: var(--dot-red);
}

.projects > li:nth-child(5n + 2) {
  --dot: var(--dot-orange);
}

.projects > li:nth-child(5n + 3) {
  --dot: var(--dot-yellow);
}

.projects > li:nth-child(5n + 4) {
  --dot: var(--dot-green);
}

.projects > li:nth-child(5n + 5) {
  --dot: var(--dot-blue);
}

/* Open-source project list ------------------------------------------------- */

.oss-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr));
}

.oss-list > li {
  margin: 0;
  padding: 1.1rem 0 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--border-strong);
  display: flex;
  flex-direction: column;
}

.oss-list h3 {
  font-size: var(--step-0);
  margin-bottom: 0.4rem;
}

.oss-list p {
  color: var(--text-muted);
  font-size: 0.97em;
  margin-bottom: 1rem;
}

.oss-list .more {
  margin-top: auto;
  font-size: var(--step--1);
  font-weight: 700;
  text-decoration: none;
}

.oss-list .more:hover {
  text-decoration: underline;
}

.more::after {
  content: " \2192";
}

/* The arrow picks up whichever smartie its section is cycling on. */
.more:hover::after {
  color: var(--dot, var(--dot-blue));
}

/* --------------------------------------------------------------------------
   8. Quotes
   -------------------------------------------------------------------------- */

.pullquote {
  margin: 2rem 0;
  max-width: 46rem;
  padding: 0.35rem 0 0.35rem 1.5rem;
  border-left: 2px solid var(--dot, var(--accent));
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--text-strong);
}

.pullquote blockquote {
  margin: 0 0 0.6rem;
}

.pullquote blockquote p {
  margin: 0;
}

.pullquote figcaption {
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-faint);
}

.pullquote figcaption::before {
  content: "\2014\00a0";
}

/* --------------------------------------------------------------------------
   8b. Team

   CURRENTLY UNUSED. /team named its associates individually until the
   individual entries were removed; these rules styled those cards. Kept
   because the markup they belong to is one edit away from returning — the
   original is in the pre-2026 `resources.html` at the repo root. Delete this
   whole block if the people are not coming back.
   -------------------------------------------------------------------------- */

.team {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1rem, 0.7rem + 1.2vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

.team > li {
  margin: 0;
  padding: 1.1rem 0 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--border-strong);
  display: flex;
  flex-direction: column;
}

.team h2 {
  font-size: var(--step-1);
  margin-bottom: 0.15rem;
}

.team p {
  color: var(--text-muted);
  font-size: 0.97em;
}

.team > li > p:last-of-type {
  margin-bottom: 0;
}

.team .more {
  margin-top: 1rem;
  font-size: var(--step--1);
  font-weight: 700;
  text-decoration: none;
}

.team .more:hover {
  text-decoration: underline;
}

/* Initials avatar built from the smartie palette. */
.team__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.6rem;
  block-size: 2.6rem;
  margin-bottom: 0.9rem;
  border-radius: 50%;
  background: var(--dot, var(--accent));
  color: #fff;
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.03em;
}

/* White is unreadable on the two light smarties — use the ink instead. */
.team__mark.dot-yellow,
.team__mark.dot-orange {
  color: #2b2c2b;
}

.team__where {
  display: block;
  margin-bottom: 0.9rem;
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   8c. CV
   -------------------------------------------------------------------------- */

/* Jump links at the top of the CV. */
.jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The list is laid out with flex `gap`, so the base `li + li` margin has to go
   or every item after the first sits lower than it. */
.jump li + li {
  margin-top: 0;
}

/* Plain links rather than bordered pills; the hairline between them is enough
   to read them as a row. */
.jump {
  gap: 0.35rem 1.1rem;
}

.jump a {
  display: block;
  font-size: var(--step--1);
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
  padding-bottom: 0.1rem;
}

.jump a:hover {
  color: var(--link-hover);
  border-bottom-color: currentColor;
}

/* Publications, articles, patents: title + meta + optional note. */
.entries {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 52rem;
}

.entries > li {
  margin: 0;
  padding: 1rem 0;
  border-top: 1px solid var(--border);
}

.entries > li:last-child {
  border-bottom: 1px solid var(--border);
}

.entries h3 {
  font-size: var(--step-0);
  font-weight: 700;
  margin-bottom: 0.2rem;
}

.entries h3 a {
  text-decoration: none;
}

.entries h3 a:hover {
  text-decoration: underline;
}

.entry__meta {
  margin: 0;
  color: var(--text-faint);
  font-size: var(--step--1);
}

.entry__note {
  margin: 0.5rem 0 0;
  color: var(--text-muted);
  font-size: 0.97em;
  max-width: 46rem;
}

/* Small "external link" arrow after a linked entry title. */
.ext::after {
  content: " \2197";
  font-size: 0.85em;
  color: var(--text-faint);
}

/* Definition-style contact table. */
.details {
  display: grid;
  grid-template-columns: minmax(5rem, max-content) 1fr;
  gap: 0.5rem 1.5rem;
  margin: 0;
  max-width: 34rem;
}

.details dt {
  font-weight: 700;
  color: var(--text-faint);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding-top: 0.15em;
}

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

/* --------------------------------------------------------------------------
   9. Contact / call to action
   -------------------------------------------------------------------------- */

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 2rem;
  padding: clamp(1.5rem, 1rem + 2vw, 2.25rem) 0 0;
  border-top: 2px solid var(--dot-blue);
  background: none;
}

.cta h2 {
  margin: 0 0 0.3rem;
  font-size: var(--step-2);
}

.cta p {
  margin: 0;
  color: var(--text-muted);
  max-width: 52ch;
}

.cta__body {
  flex: 1 1 22rem;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.3rem;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
  text-decoration: none;
  border: 1px solid var(--accent);
  white-space: nowrap;
  max-width: 100%;
}

/* Below ~380px a long label would push the page wider than the viewport. */
@media (max-width: 23.75em) {
  .button {
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

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

.button--ghost {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text);
}

.button--ghost:hover {
  background: var(--bg-sunken);
  color: var(--link-hover);
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* Feather (Tcl) ------------------------------------------------------------ */

.feather {
  inline-size: 4.5rem;
  opacity: 0.9;
  margin-bottom: 1rem;
}

/* --------------------------------------------------------------------------
   10. Utilities
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute !important;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.stack > * + * {
  margin-top: 1.5rem;
}

.text-muted {
  color: var(--text-muted);
}

.text-faint {
  color: var(--text-faint);
  font-size: 0.92em;
  display: block;
  margin-top: 0.35rem;
}

/* Smartie accent selectors — set the --dot channel an element reads from. */
.dot-red {
  --dot: var(--dot-red);
}

.dot-orange {
  --dot: var(--dot-orange);
}

.dot-yellow {
  --dot: var(--dot-yellow);
}

.dot-green {
  --dot: var(--dot-green);
}

.dot-blue {
  --dot: var(--dot-blue);
}

.mt-2 {
  margin-top: 2rem;
}

.site-footer .dots {
  margin-bottom: 0.7rem;
}

.card h2 {
  font-size: var(--step-2);
}

.mb-0 {
  margin-bottom: 0;
}

@media print {
  .site-header,
  .site-footer nav,
  .icon-btn,
  .cta {
    display: none;
  }

  body {
    color: #000;
    background: #fff;
  }
}
