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

:root {
  color-scheme: light;

  --ink: #1a1208;
  --ink-muted: #3a3228;
  --ink-faint: #52493d;
  --ink-meta: #6b6258;
  --parchment: #faf7f2;
  --parchment-warm: #f3ede3;
  --parchment-mid: #ede5d8;
  --rule: #c8b89a;
  --rule-light: #e0d8cc;
  --blue: #2d4e7a;
  --blue-mid: #1e3a5f;
  --blue-pale: #dce6f2;
  --blue-accent: #1e3a5f;
  --navy: #1e3a5f;
  --navy-mid: #2d4e7a;

  /* Code blocks stay dark in both themes, so they need their own token
     rather than borrowing --navy (which inverts to a light blue). */
  --code-bg: #1e3a5f;

  /* Portrait framing. --photo-zoom > 1 is what creates room to move in
     BOTH axes: object-fit:cover only ever overflows one axis, so without
     it a vertical nudge would have nothing to travel through.
     Positive x moves the subject RIGHT, positive y moves it DOWN.
     NOTE the sign flipped from the old --photo-nudge; x:2px here renders
     identically to nudge:-2px before. Travel: about +/-14px x, +/-7px y.
     Tune in 1px steps. */
  --photo-x: -2px;
  --photo-y: 0px;
  --photo-zoom: 1.03;

  --control-border: #8d8375;   /* 3.49:1 on --parchment */
  --status: #9a6f24;

  --content-width: 1120px;
  --h-pad: 4rem;

  /* One radius for every rectangular surface and control. Circles
     (avatar, status dot) use 50% and are the deliberate exception. */
  --radius: 2px;
}

/* ── Dark theme ──
   Applied via [data-theme="dark"] on <html>. The inline script in
   index.html sets this before first paint to avoid a flash of light.
   Warm near-black rather than neutral grey, to keep the parchment feel. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ink: #ece4d8;
  --ink-muted: #c6bcac;
  --ink-faint: #a49a89;
  --ink-meta: #8b8173;
  --parchment: #14120f;
  --parchment-warm: #1d1a15;
  --parchment-mid: #262118;
  --rule: #453d31;
  --rule-light: #2e2820;
  --blue: #8fb5e3;
  --blue-mid: #adc9ee;
  --blue-pale: #1e2b3d;
  --blue-accent: #7aa3d4;
  --navy: #9dbce6;
  --navy-mid: #8fb5e3;

  --code-bg: #0f141c;
  --control-border: #6f6557;   /* 3.27:1 on --parchment */
  --status: #d4a45c;
}

/* 112.5% == 18px at the default 16px root, but scales with the
   visitor's own browser font-size setting. A px value ignores it. */
html { font-size: 112.5%; scroll-behavior: smooth; }

body {
  background: var(--parchment);
  color: var(--ink);
  font-family: 'EB Garamond', Georgia, serif;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/* Better paragraph rag: the browser looks ahead a few lines to avoid a
   lone word on the last line. Browsers without support wrap as before.
   Testimonial quotes keep their own `balance` rule. */
p { text-wrap: pretty; }

/* ── Page switcher ── */
#home-view, #article-view { display: none; }
#home-view.active, #article-view.active { display: block; }

/* ── Top rule ── */
.top-rule {
  width: 100%;
  height: 4px;
  background: var(--blue-accent);
}

/* ── Site header ── */
.site-header {
  /* The -min values are the condensed state once the page is scrolled
     (see HEADER CONDENSE); app.js reads them for anchor offsets. */
  --header-pad-top: 1.6rem;
  --header-pad-bottom: 1.4rem;
  --header-pad-top-min: 1rem;
  --header-pad-bottom-min: 0.8rem;
  border-bottom: 1px solid var(--rule);
  padding: var(--header-pad-top) 0 var(--header-pad-bottom);
  background: var(--parchment);
  position: sticky;
  top: 0;
  z-index: 100;
}

.site-header-inner {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 0 var(--h-pad);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.site-name {
  font-family: 'Cormorant Garamond', serif;
  font-size: 1.22rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--navy);
  text-decoration: none;
  cursor: pointer;
  transition: color 0.18s;
}
.site-name:hover { color: var(--blue); }

nav { display: flex; gap: 2.5rem; align-items: baseline; }
nav a {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--ink-muted);
  text-decoration: none;
  transition: color 0.18s;
  cursor: pointer;
}
nav a:hover { color: var(--blue); }

/* ════════════════════════════════
   HOME PAGE
════════════════════════════════ */

/* Personal intro hero */
.personal-hero {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 5rem var(--h-pad) 4.5rem;
  border-bottom: 1px solid var(--rule);
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 4.5rem;
  align-items: start;
}

.photo-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
}

.photo-frame {
  width: 148px;
  height: 182px;
  border: 1px solid var(--blue);
  overflow: hidden;
  background: var(--parchment-mid);
  border-radius: var(--radius);
  flex-shrink: 0;
}

.photo-frame picture { display: contents; }
.photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transform: translate(var(--photo-x), var(--photo-y)) scale(var(--photo-zoom));
  display: block;
  filter: sepia(8%) contrast(1.04);
}

.photo-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.photo-placeholder svg { opacity: 0.2; }

.photo-caption {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-meta);
  text-align: center;
  line-height: 1.6;
}

.intro-col { padding-top: 0; }
.intro-name-row { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.1rem; }

.intro-eyebrow {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 0.85rem;
  display: block;
}

.intro-name {
  font-family: 'Cormorant Garamond', serif;
  font-size: 3rem;
  font-weight: 300;
  line-height: 1.08;
  color: var(--ink);
  letter-spacing: -0.015em;
  margin-bottom: 0;
}

.intro-body {
  font-family: 'EB Garamond', serif;
  font-size: 1.05rem;
  line-height: 1.82;
  color: var(--ink-muted);
  max-width: 620px;
  margin-bottom: 2rem;
}

.social-row {
  display: flex;
  align-items: center;
  border-top: 1px solid var(--rule-light);
  padding-top: 1.3rem;
  flex-wrap: wrap;
  gap: 0;
}

.social-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--blue);
  text-decoration: none;
  border: 1px solid var(--blue-pale);
  padding: 0.35em 0.85em;
  background: var(--blue-pale);
  border-radius: var(--radius);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  line-height: 1;
}
.social-link:hover { border-color: var(--blue); color: var(--blue-mid); }
.social-link svg { width: 13px; height: 13px; flex-shrink: 0; }

.home-hero {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 5.5rem var(--h-pad) 4.5rem;
  border-bottom: 1px solid var(--rule);
  display: grid;
  grid-template-columns: 1fr 360px;
  gap: 6rem;
  align-items: end;
}

.hero-intro-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 1.2rem;
  display: block;
}

.hero-title {
  font-family: 'Cormorant Garamond', serif;
  font-size: 4rem;
  font-weight: 300;
  line-height: 1.08;
  color: var(--ink);
  margin-bottom: 1.6rem;
  letter-spacing: -0.02em;
}

.hero-bio {
  font-family: 'EB Garamond', serif;
  font-size: 1.05rem;
  line-height: 1.78;
  color: var(--ink-muted);
  max-width: 520px;
}

.hero-right {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding-left: 3rem;
  border-left: 1px solid var(--rule-light);
  align-self: stretch;
  /* Centred so the stats' top and bottom hairlines sit an equal distance
     inside the vertical rule. flex-end plus a 4.5px bottom pad left 14px
     above and only 5px below. */
  justify-content: center;
}

.stat-item {
  padding: 1.4rem 0;
  border-bottom: 1px solid var(--rule-light);
}
.stat-item:first-child { border-top: 1px solid var(--rule-light); }

.stat-num {
  font-family: 'Cormorant Garamond', serif;
  font-size: 2.8rem;
  font-weight: 300;
  color: var(--navy);
  line-height: 1;
  display: block;
  letter-spacing: -0.02em;
  /* Cormorant defaults to old-style figures, which rise and fall like
     lowercase letters (6 and 7 drop below the line, 0 is x-height). Beside
     a capital M and a +, that reads as uneven. Lining figures sit at cap
     height, so every character in "1.2M+" shares one line. */
  font-variant-numeric: lining-nums;
}
.stat-label {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-meta);
  margin-top: 0.3rem;
  display: block;
}

/* Main body grid */
.home-body {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4rem var(--h-pad) 6rem;
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 5rem;
  align-items: start;
}

.section-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 1.6rem;
  display: block;
}

/* Featured */
.featured-post {
  margin-bottom: 3rem;
  padding-bottom: 3rem;
  border-bottom: 1px solid var(--rule-light);
}

.post-category {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue);
  border: 1px solid var(--blue-pale);
  background: var(--blue-pale);
  border-radius: var(--radius);
  padding: 0.2em 0.65em;
  margin-bottom: 0.7rem;
  display: inline-block;
}

.featured-post h2 {
  font-family: 'Cormorant Garamond', serif;
  font-size: 2.1rem;
  font-weight: 400;
  line-height: 1.18;
  color: var(--ink);
  margin-bottom: 0.7rem;
  cursor: pointer;
  transition: color 0.18s;
  max-width: 640px;
}
.featured-post h2:hover { color: var(--blue); }

.featured-post .excerpt {
  font-family: 'EB Garamond', serif;
  font-size: 1rem;
  color: var(--ink-muted);
  line-height: 1.75;
  margin-bottom: 1.1rem;
  max-width: 580px;
}

.post-meta {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-meta);
  display: flex;
  gap: 0.9rem;
  align-items: center;
}

.post-meta .read-link {
  color: var(--blue);
  text-decoration: none;
  border-bottom: 1px solid var(--blue-pale);
  padding-bottom: 0.05em;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.post-meta .read-link:hover { color: var(--blue-mid); border-color: var(--blue-mid); }

/* Post list */
.post-list { display: flex; flex-direction: column; }

.post-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2rem;
  align-items: baseline;
  padding: 1.2rem 0;
  border-bottom: 1px solid var(--rule-light);
  cursor: pointer;
  transition: background 0.12s;
}
.post-row:first-child { padding-top: 0; }
.post-row:hover .post-row-title { color: var(--blue); }

.post-row-title {
  font-family: 'EB Garamond', serif;
  font-size: 1.08rem;
  color: var(--ink);
  line-height: 1.35;
  transition: color 0.15s;
}
.post-row-sub {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.7rem;
  color: var(--ink-meta);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  display: block;
  margin-top: 0.25rem;
}
.post-row-date {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  color: var(--ink-meta);
  white-space: nowrap;
  letter-spacing: 0.04em;
}

/* Home sidebar */
.home-sidebar { position: sticky; top: calc(1.6rem + 1.4rem + 1px + 18px + 24px); }
.sidebar-block { margin-bottom: 2.8rem; }

.sidebar-rule-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 1.1rem;
  display: block;
}

.about-blurb {
  font-family: 'EB Garamond', serif;
  font-size: 0.93rem;
  color: var(--ink-muted);
  line-height: 1.75;
  margin-bottom: 1rem;
}

.topic-list { display: flex; flex-direction: column; }
.topic-item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule-light);
  cursor: pointer;
}
.topic-item:last-child { border-bottom: none; }
.topic-name {
  font-family: 'EB Garamond', serif;
  font-size: 0.95rem;
  color: var(--ink-muted);
  transition: color 0.15s;
}
.topic-item:hover .topic-name { color: var(--blue); }
.topic-count {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.75rem;
  color: var(--ink-meta);
  letter-spacing: 0.04em;
}

.contact-note {
  font-family: 'EB Garamond', serif;
  font-size: 0.93rem;
  color: var(--ink-muted);
  line-height: 1.7;
}
/* Inline text links — shared by the contact note and the role list. */
.contact-note a,
.role-org a {
  color: var(--blue);
  text-decoration: none;
  border-bottom: 1px solid var(--blue-pale);
  font-style: normal;
}
.contact-note a:hover,
.role-org a:hover { color: var(--blue-mid); border-color: var(--blue-mid); }
.contact-note a:focus-visible,
.role-org a:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* ════════════════════════════════
   ARTICLE PAGE
════════════════════════════════ */

.article-wrap {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4.5rem var(--h-pad) 6rem;
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: 5rem;
  align-items: start;
}

.article-header {
  grid-column: 1 / -1;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 2.5rem;
  margin-bottom: 0.5rem;
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: 5rem;
  align-items: end;
}

.article-header-text {}

.article-header-aside {
  padding-bottom: 0.2rem;
}

.article-category {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue);
  border: 1px solid var(--blue-pale);
  background: var(--blue-pale);
  border-radius: var(--radius);
  padding: 0.2em 0.65em;
  margin-bottom: 1rem;
  display: inline-block;
}

.article-title {
  font-family: 'Cormorant Garamond', serif;
  font-size: 3.2rem;
  font-weight: 300;
  line-height: 1.1;
  color: var(--ink);
  margin-bottom: 1rem;
  letter-spacing: -0.015em;
}

.article-subtitle {
  font-family: 'EB Garamond', serif;
  font-size: 1.1rem;
  font-style: italic;
  color: var(--ink-muted);
  margin-bottom: 2rem;
  line-height: 1.6;
}

.article-meta {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-meta);
}
.article-meta .author { color: var(--ink-muted); font-weight: 500; }
.meta-sep { color: var(--rule); }

/* Abstract / lede in header aside */
.article-header-aside p {
  font-family: 'EB Garamond', serif;
  font-size: 0.9rem;
  color: var(--ink-faint);
  line-height: 1.75;
  border-left: 2px solid var(--rule-light);
  padding-left: 1.2rem;
}
.article-header-aside .aside-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  color: var(--blue-mid);
  display: block;
  margin-bottom: 0.6rem;
  padding-left: 1.2rem;
}

/* Body */
.article-body { min-width: 0; }

.article-body p {
  font-size: 1.05rem;
  line-height: 1.85;
  color: var(--ink);
  margin-bottom: 1.5rem;
}

.article-body p:first-of-type::first-letter {
  font-family: 'Cormorant Garamond', serif;
  font-size: 4.5rem;
  font-weight: 600;
  line-height: 0.78;
  float: left;
  margin-right: 0.08em;
  margin-top: 0.06em;
  color: var(--blue-accent);
}

h2.section-head {
  font-family: 'Cormorant Garamond', serif;
  font-size: 1.65rem;
  font-weight: 600;
  color: var(--blue-accent);
  margin: 3rem 0 1.1rem;
  letter-spacing: 0.01em;
}

h3.subsection-head {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-meta);
  margin: 2.2rem 0 0.9rem;
}

.pull-quote {
  border-left: 3px solid var(--blue);
  margin: 2.5rem 0;
  padding: 0.5rem 0 0.5rem 1.8rem;
}
.pull-quote p {
  font-family: 'Cormorant Garamond', serif;
  font-size: 1.35rem;
  font-style: italic;
  font-weight: 300;
  color: var(--ink-muted);
  line-height: 1.55;
  margin: 0;
}
.pull-quote p::before { content: '\201C'; }
.pull-quote p::after { content: '\201D'; }

.code-block {
  background: var(--code-bg);
  border-radius: var(--radius);
  padding: 1.5rem 1.8rem;
  margin: 2rem 0;
  overflow-x: auto;
  position: relative;
}
.code-block::before {
  content: attr(data-lang);
  position: absolute;
  top: 0.7rem;
  right: 1.2rem;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.25);
}
.code-block code {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.82rem;
  line-height: 1.72;
  color: #d4c5a9;
  display: block;
  white-space: pre;
}
.code-block .kw { color: #b48ead; }
.code-block .fn { color: #88c0d0; }
.code-block .cm { color: #616e88; font-style: italic; }
.code-block .nm { color: #ebcb8b; }

code.inline {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.8rem;
  background: var(--parchment-warm);
  border: 1px solid var(--rule-light);
  border-radius: var(--radius);
  padding: 0.1em 0.45em;
  color: var(--blue-accent);
}

.math-display {
  text-align: center;
  margin: 2.2rem 0;
  padding: 1.5rem 3rem;
  background: var(--parchment-warm);
  border: 1px solid var(--rule-light);
  border-radius: var(--radius);
  font-family: 'EB Garamond', serif;
  font-style: italic;
  font-size: 1.2rem;
  color: var(--blue-accent);
  letter-spacing: 0.03em;
  position: relative;
}
.math-display .eq-num {
  position: absolute;
  right: 1.4rem;
  top: 50%;
  transform: translateY(-50%);
  font-family: 'DM Sans', sans-serif;
  font-style: normal;
  font-size: 0.66rem;
  color: var(--ink-faint);
  letter-spacing: 0.08em;
}

sup.fn-ref {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.58rem;
  color: var(--blue);
  cursor: default;
  line-height: 0;
  vertical-align: super;
}

/* Article sidebar */
.article-sidebar {
  position: sticky;
  top: calc(1.6rem + 1.4rem + 1px + 18px + 24px + 1rem);
}

.a-sidebar-section { margin-bottom: 2.8rem; }

.a-sidebar-label {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 0.9rem;
  display: block;
  border-bottom: 1px solid var(--rule-light);
  padding-bottom: 0.55rem;
}

.toc-list { list-style: none; display: flex; flex-direction: column; gap: 0; }
.toc-list li { display: flex; }
/* Real buttons so the contents are reachable by keyboard; styled to keep
   the original look. */
.toc-link {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  color: var(--ink-muted);
  padding: 0.45rem 0 0.45rem 1rem;
  border: none;
  border-left: 2px solid transparent;
  background: none;
  text-align: left;
  width: 100%;
  line-height: 1.4;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  letter-spacing: 0.01em;
}
.toc-link:hover { border-left-color: var(--rule); color: var(--ink); }
.toc-link.active { border-left-color: var(--blue); color: var(--blue-accent); }
.toc-link:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: -2px;
}

.tag-list { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.tag {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  background: var(--parchment-warm);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.3em 0.8em;
}

.related-item { margin-bottom: 1.1rem; padding-bottom: 1.1rem; border-bottom: 1px solid var(--rule-light); }
.related-item:last-child { border-bottom: none; margin-bottom: 0; }
.related-item a {
  font-family: 'EB Garamond', serif;
  font-size: 0.93rem;
  color: var(--blue-accent);
  text-decoration: none;
  line-height: 1.4;
  display: block;
  margin-bottom: 0.3rem;
  transition: color 0.15s;
}
.related-item a:hover { color: var(--blue); }
.related-item span {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.75rem;
  color: var(--ink-meta);
  letter-spacing: 0.04em;
}

/* Footnotes */
.footnotes {
  grid-column: 1;
  border-top: 1px solid var(--rule);
  margin-top: 2.5rem;
  padding-top: 1.8rem;
}
.footnotes-label {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.72rem;
  color: var(--blue-mid);
  margin-bottom: 1.2rem;
  display: block;
}
.footnote-entry {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  color: var(--ink-muted);
  line-height: 1.7;
  margin-bottom: 0.6rem;
  display: flex;
  gap: 0.7rem;
}
.footnote-entry .fn-num { color: var(--blue); flex-shrink: 0; }
.footnote-entry em { font-style: italic; font-family: 'EB Garamond', serif; font-size: 0.85rem; }

/* Footer */
footer {
  border-top: 1px solid var(--rule);
  padding: 0.95rem 0;
  background: var(--parchment-warm);
}
.footer-inner {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 0 var(--h-pad);
  text-align: center;
}
.footer-copy {
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 0.85rem;
  letter-spacing: 0.01em;
  color: var(--ink-meta);
  line-height: 1.5;
}

/* ── Responsive: tablet ── */
@media (max-width: 900px) {
  :root { --h-pad: 2rem; }

  /* Header: keep on one line, tighten */
  .site-name { letter-spacing: 0.14em; font-size: 1.06rem; }
  nav { gap: 1.6rem; }

  /* Personal hero: single column, photo moves inline with name */
  .personal-hero {
    grid-template-columns: 1fr;
    gap: 0;
    padding: 3rem var(--h-pad) 3rem;
  }
  /* The round crop is framed differently from the 4:5 one, so it carries
     its own values. Zoom is higher here than on desktop because the circle
     is small: at 1.08 the vertical travel was only about +/-2.5px, which
     is too little to be worth tuning. */
  :root {
    --photo-x: 0px;
    --photo-y: -3px;
    --photo-zoom: 1.08;
  }

  /* Circular portrait paired with the eyebrow + name. .intro-col becomes
     display:contents so .intro-head lifts into this grid beside the photo;
     everything else in the intro spans full width underneath. */
  .personal-hero {
    grid-template-columns: auto 1fr;
    column-gap: 1.15rem;
    row-gap: 0;
  }
  .intro-col { display: contents; }
  .personal-hero > *,
  .intro-col > * { grid-column: 1 / -1; }

  .photo-col {
    grid-column: 1;
    grid-row: 1;
    display: block;
    margin-bottom: 1.9rem;
  }
  .intro-head {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    margin-bottom: 1.9rem;
  }
  /* Trailing margin inside the wrapper offsets the visible text from the
     box centre, so align-self:center lands the ink high. Zero it here and
     let .intro-head own the spacing below. */
  .intro-head .intro-name-row { margin-bottom: 0; }
  .intro-head .intro-name { margin-bottom: 0; }
  .intro-head .intro-eyebrow { margin-top: 0; margin-bottom: 0.5rem; }

  .photo-frame {
    width: 104px;
    height: 104px;
    border-radius: 50%;
  }
  .photo-caption { display: none; }

  .intro-name-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 0.9rem;
  }
  .intro-name { font-size: 2.4rem; margin-bottom: 0; }
  .intro-body { font-size: 1rem; margin-bottom: 1.5rem; }
  .social-row {
    flex-wrap: wrap;
    gap: 0.9rem;
    padding-top: 1.1rem;
  }
  .social-link {
    border: 1px solid var(--rule-light);
    padding: 0.45em 0.9em;
    border-radius: var(--radius);
  }

  /* Writing hero section */
  .home-hero {
    grid-template-columns: 1fr;
    gap: 2.5rem;
    padding: 3rem var(--h-pad) 3rem;
  }
  .hero-title { font-size: 2.6rem; }
  .hero-right {
    border-left: none;
    padding-left: 0;
    border-top: 1px solid var(--rule-light);
    padding-top: 2rem;
    flex-direction: row;
    gap: 2.5rem;
    /* Desktop uses flex-end to sit the stacked stats at the bottom of their
       column. In a row it pushed them all right instead, so spare width
       piled up on the left. space-between pins the first stat left and the
       last right with equal gaps between, at any width. */
    justify-content: space-between;
  }
  .stat-item {
    border: none !important;
    padding: 0;
    /* Flexbox sizes each stat from its label on ONE line, then shrinks it,
       so a wrapped label left invisible space inside the box (38px / 5px /
       30px) and the visible gaps came out 74px vs 41px. min-content makes
       each box hug its number and wrapped label, so space-between spaces
       what you actually see. */
    width: min-content;
  }
  .stat-num { font-size: 2.2rem; }

  /* Home body */
  .home-body { grid-template-columns: 1fr; gap: 3rem; padding-top: 3rem; }
  .home-sidebar { position: static; }

  /* Article */
  .article-wrap { grid-template-columns: 1fr; gap: 2rem; padding-top: 3rem; }
  .article-header { grid-template-columns: 1fr; padding-bottom: 2rem; }
  .article-header-aside { display: none; }
  .article-title { font-size: 2.4rem; }
  .article-sidebar { position: static; margin-top: 1rem; }
}

/* ── Responsive: mobile ── */
@media (max-width: 600px) {
  :root { --h-pad: 1.25rem; }

  /* Header: site name left, nav right, all on one line */

  /* Personal hero */
  .personal-hero { padding: 2.5rem var(--h-pad) 2.5rem; }
  .intro-name { font-size: 2rem; margin-bottom: 0.8rem; }
  .intro-body { font-size: 0.97rem; line-height: 1.78; }
  .social-row { gap: 0.6rem; }
  .social-link { font-size: 0.7rem; padding: 0.4em 0.75em; }   /* 12.6px; 0.6rem (10.8px) read too small */

  /* Writing hero */
  .home-hero { padding: 2.5rem var(--h-pad) 2.5rem; }
  .hero-title { font-size: 2rem; }
  .hero-bio { font-size: 0.97rem; }
  .hero-right { gap: 2rem; }
  .stat-num { font-size: 2rem; }
  .stat-label { font-size: 0.6rem; }

  /* Home body */
  .home-body { padding: 2.5rem var(--h-pad) 4rem; }
  .featured-post h2 { font-size: 1.5rem; }
  .post-row-title { font-size: 0.97rem; }

  /* Article */
  .article-wrap { padding: 2.5rem var(--h-pad) 4rem; }
  .article-title { font-size: 2rem; }
  .article-subtitle { font-size: 1rem; }
  .article-body p { font-size: 0.97rem; }
  .article-body p:first-of-type::first-letter { font-size: 3.6rem; }
  h2.section-head { font-size: 1.4rem; }
  .pull-quote p { font-size: 1.1rem; }
  .code-block { padding: 1.1rem 1.1rem; }
  .code-block code { font-size: 0.72rem; }

  /* Footer */
  /* Horizontal gutter only — vertical padding lives on <footer>. The old
     1.5rem here stacked on top of it and nearly doubled mobile height. */
  .footer-inner { padding: 0 var(--h-pad); }
}



/* ── a-sidebar-label // mono style ── */
.a-sidebar-label {
  font-family: 'JetBrains Mono', monospace !important;
  font-size: 0.72rem !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--blue-mid) !important;
}

/* ── Footnotes label // style ── */
.footnotes-label {
  font-family: 'JetBrains Mono', monospace !important;
  font-size: 0.72rem !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--blue-mid) !important;
}


/* ════════════════════════════════
   HEADER RIGHT — nav + theme toggle
════════════════════════════════ */

.site-header-inner { align-items: center; }

.header-right {
  display: flex;
  align-items: center;
  gap: 2.2rem;
}

nav { align-items: center; }

/* Icon buttons — the theme toggle and the testimonial arrows share one
   style so every square control on the site looks and behaves the same. */
.theme-toggle,
.tst-btn,
.clients-toggle {
  background: none;
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.18s, border-color 0.18s, background-color 0.18s;
}
.theme-toggle:hover,
.tst-btn:hover,
.clients-toggle:hover {
  color: var(--blue);
  border-color: var(--blue);
  background: color-mix(in srgb, var(--blue-pale) 45%, transparent);
}
.theme-toggle svg,
.tst-btn svg,
.clients-toggle svg { width: 15px; height: 15px; display: block; }

/* One button, two icons — CSS picks which is visible per theme. */
.theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

/* Keyboard focus — these are real controls now, so they must show focus. */
nav a:focus-visible,
.site-name:focus-visible,
.theme-toggle:focus-visible,
.tst-btn:focus-visible,
.clients-toggle:focus-visible,
.social-link:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: var(--radius);
}

/* Soften the theme switch so it reads as a transition, not a flash. */
body,
.site-header,
footer,
.theme-toggle,
.tst-btn,
.clients-toggle {
  transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  body, .site-header, footer, .theme-toggle, .tst-btn, .clients-toggle { transition: none; }
}

/* Anchor targets must clear the sticky header. app.js keeps --header-h
   equal to the header's measured height, so a section lands flush under
   it and the rule above the section stays hidden. 6rem is the no-JS
   fallback. */
html { scroll-padding-top: var(--header-h, 6rem); }

/* ════════════════════════════════
   AFFILIATIONS
════════════════════════════════ */


/* ════════════════════════════════
   WRITING — placeholder state
   Mirrors .featured-post so the real post slots in without a reflow.
════════════════════════════════ */

.coming-soon {
  margin-bottom: 1rem;
}


@media (max-width: 900px) {
  .header-right { gap: 1.4rem; }
}
@media (max-width: 600px) {
  /* A single nav item leaves plenty of room, so mobile runs slightly
     LARGER than desktop rather than smaller. */
  .site-header-inner { gap: 1rem; }
  .site-name { white-space: nowrap; font-size: 1.06rem; letter-spacing: 0.13em; }
  nav { gap: 1rem; }
  nav a { font-size: 0.72rem; letter-spacing: 0.14em; white-space: nowrap; }
  .header-right { gap: 1.1rem; }
  .theme-toggle, .tst-btn, .clients-toggle { width: 36px; height: 36px; }
  .theme-toggle svg, .tst-btn svg, .clients-toggle svg { width: 17px; height: 17px; }
}

/* Below ~360px there genuinely isn't room for one line; wrap the nav
   under the name rather than letting anything overflow. */
@media (max-width: 360px) {
  .site-name { font-size: 0.97rem; letter-spacing: 0.1em; }
  .header-right { gap: 0.9rem; }
}

/* ════════════════════════════════
   MARKDOWN CODE
   marked emits plain <pre><code>, not the hand-authored .code-block
   markup, so those elements had no styling at all. These rules mirror
   .code-block and use --code-bg so they hold up in both themes.
════════════════════════════════ */

.article-body pre {
  background: var(--code-bg);
  border-radius: var(--radius);
  padding: 1.5rem 1.8rem;
  margin: 2rem 0;
  overflow-x: auto;
}
.article-body pre code {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.82rem;
  line-height: 1.72;
  color: #d4c5a9;
  background: none;
  border: none;
  padding: 0;
  display: block;
  white-space: pre;
}

/* Inline code — anything not inside a <pre>. */
.article-body :not(pre) > code {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.8rem;
  background: var(--parchment-warm);
  border: 1px solid var(--rule-light);
  border-radius: var(--radius);
  padding: 0.1em 0.45em;
  color: var(--blue-accent);
}

@media (max-width: 600px) {
  .article-body pre { padding: 1.1rem 1.2rem; }
  .article-body pre code { font-size: 0.74rem; }
}

/* ════════════════════════════════
   ACCESSIBILITY
════════════════════════════════ */

/* Skip link — visually hidden until focused, then anchored top-left. */
.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -100px;
  z-index: 200;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  background: var(--parchment);
  color: var(--blue-mid);
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  padding: 0.7rem 1.1rem;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 0.5rem; }

/* Expand pointer/touch targets to 44x44 without shifting any layout.
   WCAG 2.5.8 requires 24px; Apple's HIG asks for 44pt, so we take 44. */
.theme-toggle, .tst-btn, .clients-toggle, nav a { position: relative; }
.theme-toggle::after,
.tst-btn::after,
.clients-toggle::after,
nav a::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
}

/* Social pills: comfortably past the 24px floor, with gaps wide enough
   that 2.5.8's spacing allowance applies cleanly between neighbours. */
.social-link { padding: 0.5em 0.9em; }
.social-row { gap: 0.7rem; }

/* Honour a stated preference for more contrast. */
@media (prefers-contrast: more) {
  :root {
    --ink-meta: #57503f;
    --rule: #8d8375;
    --rule-light: #b3a894;
  }
  :root[data-theme="dark"] {
    --ink-meta: #a89e8e;
    --rule: #6f6557;
    --rule-light: #574e40;
  }
  .social-link, .theme-toggle, .tst-btn, .clients-toggle { border-color: currentColor; }
}

/* Focus rings must clear the sticky header when tabbing down the page. */
:target, [tabindex="-1"]:focus { outline: none; }
#intro-name, #art-title, #article-body h2 { scroll-margin-top: 1.5rem; }

/* ════════════════════════════════
   ROLES
   A single rule down the left groups the roles as one affiliation
   block, rather than reading as two unrelated bullets.
════════════════════════════════ */

.role-list {
  list-style: none;
  margin: 0 0 1.7rem;
  padding: 0.12rem 0 0.12rem 0.95rem;
  border-left: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
}
.role-list li {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  line-height: 1.5;
}
.role-title { color: var(--ink-muted); font-weight: 500; }

/* Shown only below 900px — see the note in index.html. Deliberately
   outside the role-list's rule so it reads as subordinate detail
   rather than a third job title. */
.intro-location {
  display: none;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-meta);
  margin: -1rem 0 1.7rem 0.96rem;
}
@media (max-width: 900px) {
  .intro-location { display: block; }
}
.role-org   { color: var(--ink-meta); }

/* ════════════════════════════════
   AVAILABILITY
════════════════════════════════ */

.availability {
  margin-top: 1.15rem;
  max-width: 560px;
  font-family: 'EB Garamond', serif;
  font-size: 0.88rem;
  font-style: italic;
  line-height: 1.6;
  color: var(--ink-meta);
}

@media (max-width: 600px) {
  .role-list { font-size: 0.72rem; margin-bottom: 1.4rem; }
  .availability { font-size: 0.84rem; }
}

/* ════════════════════════════════
   SEMANTIC TAG ADJUSTMENTS
   These labels are now real headings for screen-reader navigation, so
   they need the browser's default heading weight neutralised. The global
   reset already zeroes their margins.
════════════════════════════════ */

.section-label,
.sidebar-rule-label,
.a-sidebar-label { font-weight: 400; }

.coming-soon h3 {
  font-family: 'Cormorant Garamond', serif;
  font-size: 2.1rem;
  font-weight: 400;
  line-height: 1.18;
  color: var(--ink);
  margin-bottom: 0.7rem;
  max-width: 640px;
}


@media (max-width: 600px) {
  .coming-soon h3 { font-size: 1.5rem; }
}

/* On touch devices, trade a little density for comfortable targets.
   Pointer input keeps the tighter original proportions. */
@media (pointer: coarse) {
  .social-link { padding: 0.75em 1em; }
  .social-row { gap: 0.85rem; }
  .toc-link { padding-top: 0.7rem; padding-bottom: 0.7rem; }
}

/* ════════════════════════════════════════════════════════════════
   EXPERTISE
   Filterable term grid. Items keep their slots; the matched set
   lifts and sharpens in a staggered wave while the rest recede.
════════════════════════════════════════════════════════════════ */

.home-expertise {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4.5rem var(--h-pad);
  border-bottom: 1px solid var(--rule);
}

.expertise-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.8rem;
}
.expertise-filter {
  font-family: 'DM Sans', sans-serif;
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-meta);
  background: none;
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  padding: 0.5em 1em;
  cursor: pointer;
  transition: color 0.2s, background-color 0.2s, border-color 0.2s;
}
.expertise-filter:hover { color: var(--blue); border-color: var(--blue); }
.expertise-filter.is-on {
  color: var(--parchment);
  background: var(--blue-mid);
  border-color: var(--blue-mid);
}
.expertise-filter:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.expertise-grid {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.expertise-item {
  font-family: 'EB Garamond', serif;
  font-size: 0.92rem;
  color: var(--ink-muted);
  border: 1px solid var(--rule-light);
  border-radius: var(--radius);
  background: var(--parchment-warm);
  padding: 0.42em 0.9em;
  white-space: nowrap;

  transition:
    opacity 0.45s cubic-bezier(.2,.8,.2,1),
    transform 0.45s cubic-bezier(.2,.8,.2,1),
    color 0.3s, border-color 0.3s, background-color 0.3s;
  transition-delay: calc(var(--i) * 32ms);
}
/* Entrance state, applied only when scripting is available so the terms
   are never hidden behind an animation that cannot run. */
/* :where() contributes zero specificity, so the JS guard doesn't lift these
   above the .is-mute / .is-hit filter states below. Written as :root.js
   directly, this rule scored (0,5,0), beat the filter rules at (0,4,0), and
   silently disabled the dimming and lift. */
:where(:root.js) .expertise-item { opacity: 0; transform: translateY(10px); }
:where(:root.js) .home-expertise.is-in .expertise-item { opacity: 1; transform: none; }

.home-expertise.is-in .expertise-item.is-mute {
  opacity: 0.22;
  transform: translateY(3px) scale(0.985);
}
.home-expertise.is-in .expertise-item.is-hit {
  color: var(--blue);
  border-color: var(--blue-pale);
  background: var(--blue-pale);
  transform: translateY(-3px);
}

@media (prefers-reduced-motion: reduce) {
  .expertise-item {
    transition: opacity 0.01ms, color 0.2s, border-color 0.2s, background-color 0.2s;
    transition-delay: 0ms;
    transform: none;
  }
  .home-expertise.is-in .expertise-item.is-mute,
  .home-expertise.is-in .expertise-item.is-hit { transform: none; }
}

@media (max-width: 600px) {
  .home-expertise { padding: 3rem var(--h-pad); }
  .expertise-item { font-size: 0.86rem; }
}

/* Below ~360px the name would wrap beside a 104px circle. Shrink both
   just enough to keep "Gary Simcox" on one line. Appended at the end so
   it outranks the earlier breakpoint blocks. */
@media (max-width: 360px) {
  .photo-frame { width: 88px; height: 88px; }
  .intro-name { font-size: 1.75rem; }
  .personal-hero { column-gap: 1rem; }
}

/* Desktop: pull the eyebrow up by its own half-leading + ascender gap so
   the cap height of the first line sits exactly on the portrait's top
   edge, rather than the invisible top of its line box. In em so it holds
   if the font size changes. Mobile resets this — its vertical centring is
   measured from the box, not the glyphs. */
.intro-eyebrow { margin-top: -0.49em; }

/* Two-column home body (desktop): the left heading's first line started
   9.9px lower than the sidebar paragraph's — 9px from .section-label's
   larger bottom margin (1.6rem vs the sidebar's 1.1rem), 0.9px from the
   difference in leading. Lift it so both columns' cap heights share a
   line. Scoped here because .section-label also spaces // expertise,
   and below 900px the sidebar stacks so there is nothing to align to. */
@media (min-width: 901px) {
  .coming-soon h3 { margin-top: -0.55rem; }
}

/* Screen-reader-only text: present in the accessibility tree, invisible. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ════════════════════════════════════════════════════════════════
   TESTIMONIALS — stacked deck
════════════════════════════════════════════════════════════════ */

.home-testimonials {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4.5rem var(--h-pad);
  border-bottom: 1px solid var(--rule);
  min-width: 0;
}

/* ── Quote ─────────────────────────────────────────────────── */
/* 48rem holds the quote to ~75 characters per line, the upper end of a
   comfortable measure. The card itself spans the full container width;
   at the widest desktop the text stops only ~19px short on each side. */
.tst-quote { margin: 0 auto; max-width: 48rem; text-align: center; }
.tst-quote blockquote { margin: 0; }
.tst-quote p {
  /* Even out line lengths so wide cards don't strand a lone word on the
     last line. Ignored harmlessly by browsers without support. */
  text-wrap: balance;
  font-family: 'Cormorant Garamond', serif;
  font-style: italic;
  font-weight: 400;
  font-size: 1.65rem;
  line-height: 1.38;
  color: var(--ink);
}
/* Typographic quote marks are presentation; <blockquote> carries the meaning. */
.tst-quote p::before { content: '\201C'; }
.tst-quote p::after  { content: '\201D'; }
.tst-quote figcaption {
  margin-top: 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  font-family: 'DM Sans', sans-serif;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.tst-name { color: var(--ink-muted); font-weight: 500; }
/* ink-faint, not ink-meta: on the blue-tinted card ink-meta drops to
   4.42:1 in dark mode, under the 4.5:1 minimum. */
.tst-role { color: var(--ink-faint); }

/* ── Deck ───────────────────────────────────────────────────────
   Cards share one grid cell, so the stage is as tall as the longest
   quote. data-pos places each card in the pile; positions wrap, so the
   pile is always full and the order loops in both directions. */
.tst-deck .tst-stage {
  display: grid;                 /* full container width */
  padding-bottom: 30px;          /* room for the cards peeking below */
}
.tst-card {
  grid-area: 1 / 1;
  /* Cards in the pile are beige; only the front card takes the blue wash
     (see [data-pos="0"]). Colours transition so a card turns blue as it
     comes forward. */
  background: var(--parchment-warm);
  border: 1px solid var(--rule-light);
  border-radius: var(--radius);
  padding: 2.4rem 2rem 2.1rem;
  /* Every card stretches to the tallest quote; centre shorter ones so
     they don't sit high with a gap underneath. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  transform-origin: 50% 100%;
  transition: transform 0.45s cubic-bezier(.2,.8,.2,1), opacity 0.45s ease,
              background-color 0.45s ease, border-color 0.45s ease;
  opacity: 0;
  transform: translateY(30px) scale(0.9);
  z-index: 0;
}
.tst-card[data-pos="0"] {
  opacity: 1; transform: none; z-index: 3;
  /* Fallbacks first for browsers without color-mix(). */
  background: var(--blue-pale);
  background: color-mix(in srgb, var(--blue-pale) 55%, var(--parchment));
  border-color: var(--blue-pale);
  border-color: color-mix(in srgb, var(--blue) 22%, var(--parchment));
}
.tst-card[data-pos="1"] { opacity: 0.75; transform: translateY(15px) scale(0.955);  z-index: 2; }
.tst-card[data-pos="2"] { opacity: 0.45; transform: translateY(30px) scale(0.91);   z-index: 1; }
.tst-card.is-flick      { opacity: 0;    transform: translateX(-80px) rotate(-4deg); z-index: 4; }

/* ── Controls ──────────────────────────────────────────────────
   Arrow buttons take their look from the shared icon-button style above. */
.tst-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  margin-top: 1.9rem;
}

/* Progress indicator: a row of dots where the current one stretches into
   a blue bar. Each button keeps a 24px target (WCAG 2.5.8). */
.tst-dots { display: flex; align-items: center; }
.tst-dot {
  min-width: 24px; height: 24px;
  padding: 0; border: 0; background: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.tst-dot::before {
  content: '';
  width: 6px; height: 6px; border-radius: 3px;
  background: var(--control-border);
  transition: width 0.3s cubic-bezier(.2,.8,.2,1), background-color 0.2s;
}
.tst-dot:hover::before { background: var(--blue); }
.tst-dot[aria-current="true"]::before { width: 18px; background: var(--blue); }
.tst-dot:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; border-radius: var(--radius); }

@media (prefers-reduced-motion: reduce) {
  .tst-card, .tst-dot::before { transition: none; }
}
@media (pointer: coarse) {
  .tst-dot { min-width: 32px; height: 32px; }
}
@media (max-width: 600px) {
  .home-testimonials { padding: 3rem var(--h-pad); }
  .tst-quote p { font-size: 1.3rem; }
  .tst-card { padding: 1.8rem 1.3rem 1.6rem; }

  /* One dot per testimonial doesn't fit beside the arrows on a phone, so
     the dots take their own centred row above them, wrapping if the list
     grows. 28px targets stay above the WCAG 2.5.8 minimum. */
  .tst-controls { flex-wrap: wrap; gap: 0.9rem 1.1rem; }
  .tst-dots { order: -1; flex-basis: 100%; justify-content: center; flex-wrap: wrap; }
  .tst-dot { min-width: 28px; height: 28px; }
}

/* ════════════════════════════════
   FOOTER — icon-only social links
   Same colours and hover as the pill links in the intro, at the square
   icon-button size used by the theme toggle and carousel arrows.
════════════════════════════════ */

.footer-social {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: 0.75rem;                 /* keeps neighbouring 44px hit areas apart */
  margin: 0.2rem 0 0.75rem;
}
.footer-social-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--blue);
  background: var(--blue-pale);
  border: 1px solid var(--blue-pale);
  border-radius: var(--radius);
  transition: border-color 0.18s, color 0.18s;
}
.footer-social-link svg { width: 15px; height: 15px; display: block; }
.footer-social-link::after {    /* 44px target without changing layout */
  content: ''; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  min-width: 44px; min-height: 44px; width: 100%; height: 100%;
}
.footer-social-link:hover { border-color: var(--blue); color: var(--blue-mid); }
.footer-social-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

@media (prefers-contrast: more) {
  .footer-social-link { border-color: currentColor; }
}
@media (prefers-reduced-motion: reduce) {
  .footer-social-link { transition: none; }
}
@media (max-width: 600px) {
  .footer-social-link { width: 36px; height: 36px; }
  .footer-social-link svg { width: 17px; height: 17px; }
}

/* ════════════════════════════════
   CLIENTS
   Two slow logo marquees, running in opposite directions, inside a lightly
   tinted band with double rules. Without JS, or with reduced motion, the
   source list shows as a static centered row. app.js adds .is-running and
   builds the rows (see initMarquee); the source list then stays in the
   accessibility tree, visually hidden, as the one list screen readers get.
════════════════════════════════ */

.home-clients {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4.5rem var(--h-pad);
  min-width: 0;
  --logo: color-mix(in srgb, var(--navy) 42%, var(--ink-faint));
  --band-rule: color-mix(in srgb, var(--blue) 32%, var(--rule-light));
  --diamond-space: 2.2rem;
}
.home-clients .section-label { margin-bottom: 2rem; }

.clients-band {
  position: relative;
  border-top: 3px double var(--band-rule);
  border-bottom: 3px double var(--band-rule);
  background:
    radial-gradient(ellipse 55% 140% at 50% 50%,
      color-mix(in srgb, var(--blue-pale) 55%, transparent), transparent 75%),
    color-mix(in srgb, var(--blue-pale) 18%, var(--parchment));
  padding: 1.5rem 0;
}

/* Static list: no JS, or reduced motion. */
.clients-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 1.3rem 3.5rem;
  padding: 0.4rem 1.5rem;
}

/* Logos are inline SVGs filled with currentColor, so the one --logo
   color follows the theme. */
.client-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--logo);
  white-space: nowrap;
  line-height: 1;
  transition: color 0.6s ease;
}
/* Fixed height, width from each logo's viewBox, so wordmarks keep their proportions. */
.client-logo svg { height: 1.4rem; width: auto; flex-shrink: 0; }

.clients-rows { display: none; }

@media (prefers-reduced-motion: no-preference) {
  .clients-marquee.is-running .clients-list {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .clients-marquee.is-running .clients-rows {
    display: flex;
    flex-direction: column;
    gap: 1.15rem;
  }
}

.clients-row {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}
.clients-track {
  display: flex;
  width: max-content;
  animation: clients-scroll var(--row-duration, 40s) linear infinite;
}
.clients-row--reverse .clients-track { animation-direction: reverse; }
@keyframes clients-scroll { to { transform: translateX(-50%); } }

/* Every item carries its own trailing diamond and spacing, so the join
   between one set and the next is invisible. */
.clients-set { list-style: none; display: flex; align-items: center; }
/* Equal row heights whatever the logos' own heights. */
.clients-set li { display: flex; align-items: center; min-height: 1.6rem; }
.clients-set li::after {
  content: '';
  width: 5px;
  height: 5px;
  margin: 0 var(--diamond-space);
  transform: rotate(45deg);
  background: color-mix(in srgb, var(--blue) 45%, transparent);
}

/* Paused, by hover or the button: the motion stops and the logos take
   the accent blue, so the state is visible as well as the stillness. */
.clients-band:hover .clients-track,
.clients-marquee.is-paused .clients-track { animation-play-state: paused; }
.clients-band:hover .clients-rows .client-logo,
.clients-marquee.is-paused .client-logo { color: var(--blue); }

.clients-controls {
  display: flex;
  justify-content: center;
  margin-top: 1.4rem;
}
.clients-toggle[hidden] { display: none; }
/* No button (no JS), no row: don't leave its margin above the page edge. */
.clients-controls:not(:has(button:not([hidden]))) { display: none; }

@media (prefers-reduced-motion: reduce) {
  .clients-controls { display: none; }
  .client-logo { transition: none; }
}

@media (max-width: 600px) {
  .home-clients { padding: 3rem var(--h-pad); --diamond-space: 1.6rem; }
  .clients-band { padding: 1.2rem 0; }
  .clients-list { gap: 1rem 2.2rem; }
  .clients-marquee.is-running .clients-rows { gap: 1rem; }
}

/* ════════════════════════════════
   AGENCY
   Sits under the blog in the left column. On desktop both columns share
   two grid rows (subgrid), so "// the agency" starts on the same line as
   "// contact" however long either first block runs.
════════════════════════════════ */

.main-block { margin-bottom: 2.8rem; }
.main-block > :last-child { margin-bottom: 0; }
/* Stacked: the column gap matches the space between blocks. */
@media (max-width: 900px) { .home-body { row-gap: 2.8rem; } }

@media (min-width: 901px) {
  .home-body { grid-template-rows: auto auto; row-gap: 0; }
  .home-main,
  .home-sidebar {
    grid-row: span 2;
    display: grid;
    grid-template-rows: subgrid;
    row-gap: 0;
  }
  /* Share a baseline with the contact note beside it. Same type, so the
     only difference is .section-label's larger bottom margin (1.6rem vs
     the sidebar label's 1.1rem). */
  .agency-blurb { margin-top: -0.5rem; }
}

/* Same type as the about and contact paragraphs beside it. */
.agency-blurb {
  font-family: 'EB Garamond', serif;
  font-size: 0.93rem;
  line-height: 1.7;
  color: var(--ink-muted);
  max-width: 600px;
  margin-bottom: 1.5rem;
}

/* Keeps the link its natural width; .social-link is display:flex. */
.agency-cta-row { display: flex; }

/* The shared pill link, filled with Lazul's logo gradient: #6E28EA,
   #A621E4, #E11FCB. The magenta end is deepened to #C81AB5 because white
   text on the original is 3.99:1; every stop now clears 4.5:1 (6.66, 5.30,
   4.92). The gradient runs there and back across a double-width
   background, so hover slides it the other way. The inset highlight and
   shade use the white 17% and plum 22% facets from the logo mark. */
.social-link.agency-cta {
  font-weight: 600;
  color: #fff;
  border-color: transparent;
  background-image: linear-gradient(100deg, #6E28EA 0%, #A621E4 25%, #C81AB5 50%, #A621E4 75%, #6E28EA 100%);
  background-size: 200% 100%;
  background-position: 0% 50%;
  background-origin: border-box;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.17),
    inset 0 -1px 0 rgb(28 0 42 / 0.22),
    0 6px 16px -10px rgb(166 33 228 / 0.6);
  transition: background-position 0.9s cubic-bezier(.2,.8,.2,1), box-shadow 0.3s ease;
}
.social-link.agency-cta:hover {
  color: #fff;
  border-color: transparent;
  background-position: 100% 50%;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.17),
    inset 0 -1px 0 rgb(28 0 42 / 0.22),
    0 8px 20px -10px rgb(200 26 181 / 0.75);
}

/* Arrow: a 19-unit stem at the same height and head size as the other
   24-unit icons. The viewBox is cropped to the stroke's outer edges
   (round caps included), so the button's right padding is measured to
   the arrow's tip and matches the left; the width keeps the 21.5:24
   ratio. The 2.5 stroke renders about 1.35px, between DM Sans 600's
   horizontal (1.26px) and vertical (1.56px) strokes at this size. It
   stays still on hover, so that padding never shifts. */
.social-link.agency-cta svg { width: 11.65px; }

/* Shimmer: a soft band of light crossing the button, then a rest, on a
   continuous loop. A deliberate choice: without a pause control, looping
   motion falls outside WCAG 2.2.2, so reduced motion and forced colors
   both turn it off. The band sits between the gradient and the label:
   isolation keeps its z-index inside the link. */
.social-link.agency-cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.social-link.agency-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg, transparent 25%, rgb(255 255 255 / 0.32) 50%, transparent 75%);
  transform: translateX(-100%);
  pointer-events: none;
  animation: cta-shimmer 3.2s cubic-bezier(.45,0,.25,1) infinite;
}
@keyframes cta-shimmer {
  0%        { transform: translateX(-100%); }
  55%, 100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .social-link.agency-cta { transition: none; }
  .social-link.agency-cta::before { animation: none; }
}
@media (prefers-contrast: more) {
  .social-link.agency-cta, .social-link.agency-cta:hover { border-color: #fff; }
}
@media (forced-colors: active) {
  .social-link.agency-cta { border-color: LinkText; }
  .social-link.agency-cta::before { display: none; }
}

/* ════════════════════════════════
   SCROLL EFFECTS
════════════════════════════════ */

/* Section rules draw in, left to right, tied to scroll position: the line
   starts as it enters the bottom of the viewport and is complete by the
   time it's 35% of the way up. The real border goes transparent and a
   1px pseudo-element takes its place, so nothing shifts. The intro's rule
   is left alone: it's often on screen at load, where a scrubbed line
   would rest half-drawn. Browsers without scroll timelines, and reduced
   motion, keep the plain borders. */
@keyframes rule-draw {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .home-hero,
    .home-expertise,
    .home-testimonials {
      position: relative;
      border-bottom-color: transparent;
    }
    .home-hero::after,
    .home-expertise::after,
    .home-testimonials::after {
      content: '';
      position: absolute;
      left: 0;
      right: 0;
      bottom: -1px;
      height: 1px;
      background: var(--rule);
      transform-origin: left center;
      animation: rule-draw linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 35%;
      pointer-events: none;
    }
  }
}
@media (forced-colors: active) {
  .home-hero, .home-expertise, .home-testimonials { border-bottom-color: CanvasText; }
  .home-hero::after, .home-expertise::after, .home-testimonials::after { display: none; }
}

/* Testimonial deck fans out: the back cards start squared up behind the
   front card and slide down to their peeking offsets as the deck scrolls
   in. Uses the individual `translate` property, which composes with the
   `transform` each data-pos sets, so card flips are unaffected; at the
   end of the range it's zero. */
@keyframes deck-fan-1 { from { translate: 0 -15px; } to { translate: 0 0; } }
@keyframes deck-fan-2 { from { translate: 0 -30px; } to { translate: 0 0; } }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .tst-deck .tst-stage { view-timeline: --deck block; }
    .tst-card[data-pos="1"],
    .tst-card[data-pos="2"] {
      animation: deck-fan-1 linear both;
      animation-timeline: --deck;
      animation-range: cover 10% cover 40%;
    }
    .tst-card[data-pos="2"] { animation-name: deck-fan-2; }
  }
}

/* Stats count up once, when they come into view (app.js). This one is
   time-based rather than scrubbed: a scrubbed counter can come to rest
   mid-count and show a wrong figure. The real figure stays in the DOM,
   transparent, so it holds the layout and is what screen readers read;
   the counting copy sits on top and is removed when it finishes. */
.stat-num.is-counting { position: relative; }
.stat-num.is-counting .stat-value { color: transparent; }
.stat-count {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
}

/* ════════════════════════════════
   PAGE EDGES
   One value for the space between the header and the first content, and
   between the last content and the footer, so both ends of the page feel
   the same. Previously the bottom used its own larger padding plus the
   last sidebar block's inter-block margin, leaving 162px vs 90px on
   desktop. Declared last so it wins over the earlier breakpoint rules.
════════════════════════════════ */

:root { --edge-space: 5rem; }
@media (max-width: 900px) { :root { --edge-space: 3rem; } }
@media (max-width: 600px) { :root { --edge-space: 2.5rem; } }

.personal-hero { padding-top: var(--edge-space); }
/* Whichever section is last takes the edge space. While the clients
   section is shown, the writing section above it closes with a rule
   like the others, its bottom padding matching its top per breakpoint.
   Removing `hidden` from #clients is all it takes to switch. */
.home-clients, .home-body { padding-bottom: var(--edge-space); }
.home-body:has(+ .home-clients:not([hidden])) { padding-bottom: 4rem; border-bottom: 1px solid var(--rule); }
@media (max-width: 900px) { .home-body:has(+ .home-clients:not([hidden])) { padding-bottom: 3rem; } }
@media (max-width: 600px) { .home-body:has(+ .home-clients:not([hidden])) { padding-bottom: 2.5rem; } }

/* Spacing between blocks, not after the last one. */
.home-sidebar > .sidebar-block:last-child,
.home-main > :last-child { margin-bottom: 0; }

/* The last paragraph's line-height adds ~3px of empty leading below its
   final line, so the gap to the footer read 3px larger than the gap under
   the header (which is measured to the portrait's hard edge). Pull that
   leading back out, in em so it tracks the font size. */
.home-sidebar > .sidebar-block:last-child > :last-child { margin-bottom: -0.2em; }

/* ════════════════════════════════
   PAGE ENTRANCE
   Hidden only while scripting is alive and the head script hasn't yet
   added .page-ready (fonts + portrait ready, or 800ms, whichever first).
   Keyframe animations rather than transitions, so they never replace
   the colour transitions these elements use for the theme toggle.
   `backwards` fill holds the start state through each delay, then lets
   go completely once the animation ends.
════════════════════════════════ */

@keyframes page-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
@keyframes page-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

:root.js:not(.page-ready) :is(
  .site-header, .photo-col, .intro-head, .role-list, .intro-location,
  .intro-body, .social-row, .availability,
  .home-hero, .home-expertise, .home-testimonials, .home-clients, .home-body, footer
) { opacity: 0; }

:root.js.page-ready .site-header {
  animation: page-fade 0.5s ease backwards;
}
:root.js.page-ready :is(
  .photo-col, .intro-head, .role-list, .intro-location,
  .intro-body, .social-row, .availability,
  .home-hero, .home-expertise, .home-testimonials, .home-clients, .home-body, footer
) {
  animation: page-rise 0.6s cubic-bezier(.2,.8,.2,1) backwards;
  animation-delay: calc(50ms + var(--rv, 0) * 50ms);
}

/* Reading order. Everything below the fold arrives together. */
.photo-col      { --rv: 0; }
.intro-head     { --rv: 1; }
.role-list      { --rv: 2; }
.intro-location { --rv: 3; }
.intro-body     { --rv: 3; }
.social-row     { --rv: 4; }
.availability   { --rv: 5; }
.home-hero, .home-expertise, .home-testimonials, .home-clients, .home-body, footer { --rv: 6; }

@media (prefers-reduced-motion: reduce) {
  :root.js.page-ready :is(
    .site-header, .photo-col, .intro-head, .role-list, .intro-location,
    .intro-body, .social-row, .availability,
    .home-hero, .home-expertise, .home-testimonials, .home-clients, .home-body, footer
  ) {
    animation: page-fade 0.2s ease backwards;
    animation-delay: 0s;
  }
}

/* ════════════════════════════════
   HEADER CONDENSE
   Over the first 100px of scroll the header's padding tightens and its
   rule firms up, tied to scroll position. A growing bottom margin offsets
   the lost padding exactly, so the header's footprint in the page never
   changes and nothing below it moves. Declared after PAGE ENTRANCE so the
   header keeps its fade-in: both animations are listed together.
   app.js sets --header-h to the condensed height, since that's the
   header anchor links land under.
════════════════════════════════ */

@keyframes header-condense {
  to {
    padding-top: var(--header-pad-top-min);
    padding-bottom: var(--header-pad-bottom-min);
    margin-bottom: calc(var(--header-pad-top) + var(--header-pad-bottom)
                      - var(--header-pad-top-min) - var(--header-pad-bottom-min));
    border-bottom-color: color-mix(in srgb, var(--rule) 55%, var(--ink-faint));
  }
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .site-header {
      animation: header-condense linear both;
      animation-timeline: scroll(root block);
      animation-range: 0px 100px;
    }
    :root.js.page-ready .site-header {
      animation: page-fade 0.5s ease backwards, header-condense linear both;
      animation-timeline: auto, scroll(root block);
      animation-range: normal, 0px 100px;
    }
  }
}
