/* ============================================================================
   frz.codes — site stylesheet
   ----------------------------------------------------------------------------
   Consumes tokens.css and defines no raw colour, size or duration of its own.
   Organised as: reset → atmosphere → layout → typography → chrome →
   components → prose → utilities → motion preference.
   ============================================================================ */

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

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
/* A neutral inset edge keeps a photo from dissolving into the ground. Kept
   hueless on purpose: tinting an image edge with the brand reads as a frame. */
img { outline: 1px solid var(--img-edge); outline-offset: -1px; }
hr { border: 0; border-top: var(--hairline) solid var(--line); margin: 0; }

/* Anything a reader must be able to reach with a keyboard says so clearly. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ------------------------------------------------------------- atmosphere */
/* The halo: the site's one ambient element. A slow drifting mesh, warm at the
   top where the hero sits and cooling down the page. Fixed, blurred, and behind
   everything — it never affects layout and never intercepts a pointer. */

.halo {
  position: fixed;
  inset: -25vmax;
  z-index: 0;
  pointer-events: none;
  opacity: var(--halo-opacity);
}
.halo span {
  position: absolute;
  display: block;
  border-radius: 50%;
  mix-blend-mode: screen;
  mix-blend-mode: var(--halo-blend);
  will-change: transform;
}
.halo span:nth-child(1) {           /* warm lobe, top right, behind the hero */
  width: 95vmax; height: 95vmax; right: -22%; top: -34%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--accent) 90%, transparent) 0%,
    color-mix(in oklab, var(--accent) 68%, transparent) 18%,
    color-mix(in oklab, var(--accent) 42%, transparent) 36%,
    color-mix(in oklab, var(--accent) 20%, transparent) 54%,
    color-mix(in oklab, var(--accent) 7%, transparent) 72%,
    transparent 88%);
  opacity: .3;
  animation: halo-a var(--dur-halo-a) ease-in-out infinite alternate;
}
.halo span:nth-child(2) {           /* cool spill */
  width: 105vmax; height: 105vmax; left: -34%; top: -18%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--weather) 90%, transparent) 0%,
    color-mix(in oklab, var(--weather) 68%, transparent) 18%,
    color-mix(in oklab, var(--weather) 42%, transparent) 36%,
    color-mix(in oklab, var(--weather) 20%, transparent) 54%,
    color-mix(in oklab, var(--weather) 7%, transparent) 72%,
    transparent 88%);
  opacity: .26;
  animation: halo-b var(--dur-halo-b) ease-in-out infinite alternate;
}
.halo span:nth-child(3) {           /* low wash further down the page */
  width: 88vmax; height: 88vmax; left: 6%; top: 34%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--accent-2) 90%, transparent) 0%,
    color-mix(in oklab, var(--accent-2) 68%, transparent) 18%,
    color-mix(in oklab, var(--accent-2) 42%, transparent) 36%,
    color-mix(in oklab, var(--accent-2) 20%, transparent) 54%,
    color-mix(in oklab, var(--accent-2) 7%, transparent) 72%,
    transparent 88%);
  opacity: .22;
  animation: halo-c var(--dur-halo-c) ease-in-out infinite alternate;
}
@keyframes halo-a { to { transform: translate3d(-9vmax,  8vmax, 0); } }
@keyframes halo-b { to { transform: translate3d(11vmax,  6vmax, 0); } }
@keyframes halo-c { to { transform: translate3d(-8vmax, -10vmax, 0); } }

/* A faint node grid over the wash, tying the atmosphere to something ordered. */
/* A very fine grain over the whole page. Large soft gradients on a dark ground
   land on 8-bit colour steps and show as concentric rings; a little noise
   dithers those steps away. It is generated, not a file, so it costs no
   request and nothing to embed. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)'/%3E%3C/svg%3E");
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at 1px 1px,
    color-mix(in oklab, var(--muted) 30%, transparent) 1px, transparent 0);
  background-size: 46px 46px;
  opacity: var(--grid-opacity);
  mask-image: radial-gradient(ellipse 150% 120% at 50% 0%, #000 35%, transparent 95%);
}

/* ----------------------------------------------------------------- layout */

.page { position: relative; z-index: 1; }

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

.section { padding-block: var(--section-y); }
section[id] { scroll-margin-top: 6rem; }   /* clear the sticky nav on anchor jumps */

/* Sibling groups are spaced with gap, never with per-child margins. */
.row       { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }
.split     { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
@media (min-width: 840px) { .split { grid-template-columns: 1fr 1fr; } }

/* Glass is depth, used only where something genuinely floats above the ground. */
.glass {
  background: var(--glass);
  border: var(--hairline) solid var(--glass-edge);
  border-radius: var(--radius);
  backdrop-filter: blur(18px) saturate(1.35);
  -webkit-backdrop-filter: blur(18px) saturate(1.35);
}

/* ------------------------------------------------------------- typography */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  letter-spacing: var(--ls-head);
  line-height: var(--lh-head);
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: var(--t-h1); font-weight: 800; letter-spacing: var(--ls-display); line-height: var(--lh-tight); }
h2 { font-size: var(--t-h2); font-weight: 600; }
h3 { font-size: var(--t-h3); font-weight: 600; }
h4 { font-size: var(--t-body); font-weight: 600; }

p { margin: 0; }

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

/* The label is the site's structural voice: mono, uppercase, with a status dot
   whose colour says leader (accent) or follower (accent-2). */
.label {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
}
.label::before {
  content: "";
  flex: none;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 16%, transparent);
}
.label--follower::before {
  background: var(--accent-2);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent-2) 16%, transparent);
}
.label--plain::before { display: none; }

.lead  { font-size: var(--t-lead); line-height: var(--lh-lead); color: var(--muted); text-wrap: pretty; }
.lead b, .lead strong { color: var(--text); font-weight: 600; }
.muted { color: var(--muted); }
.measure { max-width: var(--measure); }

.mono {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------------- chrome */

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: calc(var(--z-nav) + 1);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus-visible { transform: none; }

.nav {
  position: sticky;
  top: var(--sp-4);
  z-index: var(--z-nav);
  margin-top: var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
}
.nav__brand {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--t-body);
  letter-spacing: var(--ls-head);
  color: var(--text);
  text-decoration: none;
}
.nav__brand span { color: var(--accent); }
.nav__list {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  list-style: none;
  margin: 0 0 0 auto;
  padding: 0;
  align-items: center;
}
/* On a phone the brand and the links cannot share a row without wrapping into
   a ragged block, so give each its own. */
@media (max-width: 559px) {
  .nav { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .nav__list { margin-left: 0; }
}
.nav__link {
  font-size: var(--t-small);
  color: var(--muted);
  text-decoration: none;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-pill);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.nav__link { position: relative; }
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 40px;
  translate: 0 -50%;
}
.nav__link:hover { color: var(--text); background: var(--glass-strong); }
.nav__link[aria-current] { color: var(--text); background: var(--glass-strong); }

.theme-toggle {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  background: transparent;
  border: var(--hairline) solid var(--line);
  color: var(--muted);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.theme-toggle { position: relative; }
.theme-toggle::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 40px;
  translate: 0 -50%;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }

.site-footer {
  margin-top: var(--sp-8);
  padding-block: var(--sp-12) var(--sp-16);
  border-top: var(--hairline) solid var(--line-soft);
  font-size: var(--t-small);
  color: var(--muted);
  display: grid;
  gap: var(--sp-4);
}
.site-footer a { color: var(--accent); }

/* -------------------------------------------------------------- reveal ---- */
/* Scroll-triggered entrance, native. This is what replaces AOS, jQuery,
   Waypoints and Stellar. Browsers without support simply show the content. */
@supports (animation-timeline: view()) {
  .reveal {
    animation: reveal-rise linear both;
    animation-timeline: view();
    animation-range: entry 8% cover 24%;
  }
  @keyframes reveal-rise {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ------------------------------------------------------------------- hero */
/* A full-height opening that carries a name and a role and nothing else. The
   light behind it is the only thing asked to be interesting. */

.hero {
  position: relative;
  min-height: calc(100svh - 7rem);
  display: grid;
  place-items: center;
  text-align: center;
  padding-block: var(--sp-12) var(--sp-16);
}

/* Local, brighter, and slower than the page halo: this is the moving light the
   opening is built around, not the ambient wash the rest of the page sits on. */
/* The hero's own light is clipped to the hero box, which left a hard
   horizontal seam exactly where the section ended. Let it run past the bottom
   and fade it out with a mask instead, so the opening dissolves into the page
   rather than stopping at a line. */
.hero__light {
  position: absolute;
  inset: -10% -20% -35%;
  z-index: -1;
  pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 0%, #000 46%, transparent 88%);
}
.hero__light span {
  position: absolute;
  display: block;
  border-radius: 50%;
  mix-blend-mode: screen;
  mix-blend-mode: var(--halo-blend);
  will-change: transform;
}
.hero__light span:nth-child(1) {
  width: 68vmax; height: 68vmax; left: -12%; top: -14%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--accent) 90%, transparent) 0%,
    color-mix(in oklab, var(--accent) 68%, transparent) 18%,
    color-mix(in oklab, var(--accent) 42%, transparent) 36%,
    color-mix(in oklab, var(--accent) 20%, transparent) 54%,
    color-mix(in oklab, var(--accent) 7%, transparent) 72%,
    transparent 88%);
  opacity: .26;
  animation: hero-light-a 26s ease-in-out infinite alternate;
}
.hero__light span:nth-child(2) {
  width: 62vmax; height: 62vmax; right: -10%; top: 2%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--weather) 90%, transparent) 0%,
    color-mix(in oklab, var(--weather) 68%, transparent) 18%,
    color-mix(in oklab, var(--weather) 42%, transparent) 36%,
    color-mix(in oklab, var(--weather) 20%, transparent) 54%,
    color-mix(in oklab, var(--weather) 7%, transparent) 72%,
    transparent 88%);
  opacity: .24;
  animation: hero-light-b 31s ease-in-out infinite alternate;
}
.hero__light span:nth-child(3) {
  width: 54vmax; height: 54vmax; left: 28%; bottom: -20%;
  background: radial-gradient(circle closest-side,
    color-mix(in oklab, var(--accent-2) 90%, transparent) 0%,
    color-mix(in oklab, var(--accent-2) 68%, transparent) 18%,
    color-mix(in oklab, var(--accent-2) 42%, transparent) 36%,
    color-mix(in oklab, var(--accent-2) 20%, transparent) 54%,
    color-mix(in oklab, var(--accent-2) 7%, transparent) 72%,
    transparent 88%);
  opacity: .2;
  animation: hero-light-c 37s ease-in-out infinite alternate;
}
@keyframes hero-light-a { to { transform: translate3d(24vmax, 12vmax, 0); } }
@keyframes hero-light-b { to { transform: translate3d(-26vmax, 8vmax, 0); } }
@keyframes hero-light-c { to { transform: translate3d(12vmax, -16vmax, 0); } }

.hero h1 { margin: 0; max-width: 18ch; }

/* A gradient travelling through the glyphs, amber into cyan so it belongs to
   the palette rather than being a generic rainbow. */
.hero__sweep {
  background: linear-gradient(100deg,
    var(--text) 0%, var(--text) 36%,
    var(--accent) 47%, var(--accent-2) 55%,
    var(--text) 66%, var(--text) 100%);
  background-size: 300% 100%;
  background-position: var(--sweep-x) 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: hero-sweep var(--dur-sweep) var(--ease) infinite;
}
@keyframes hero-sweep {
  0%, 14%   { --sweep-x: 120%; }
  62%, 100% { --sweep-x: -45%; }
}

.hero__role {
  margin-top: var(--sp-6);
  font-family: var(--font-mono);
  font-size: var(--t-small);
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--muted);
  padding-left: .38em;      /* optical, not spacing: cancels the trailing letter-space so the words sit centred */
}

/* The way down. Small, quiet, and the only affordance in the opening. */
.hero__next {
  position: absolute;
  bottom: var(--sp-8);
  left: 50%;
  translate: -50% 0;
  display: grid;
  place-items: center;
  width: 2.75rem; height: 2.75rem;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--muted);
  background: var(--glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
  animation: hero-next 2.6s var(--ease) infinite;
}
.hero__next:hover { color: var(--accent); border-color: var(--accent); }
@keyframes hero-next {
  0%, 70%, 100% { transform: translateY(0); }
  35%           { transform: translateY(6px); }
}

/* ------------------------------------------------------------- components */

/* Writing index: rows, not a card wall. This is a reading site. */
.index { border-top: var(--hairline) solid var(--line); }
.entry {
  position: relative;
  display: grid;
  gap: var(--sp-2) var(--sp-6);
  grid-template-columns: 1fr;
  padding: var(--sp-5) var(--sp-4);
  border-bottom: var(--hairline) solid var(--line-soft);
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  transition: background var(--dur) var(--ease);
}
@media (min-width: 660px) {
  .entry { grid-template-columns: 6.5rem 1fr auto; align-items: baseline; }
}
.entry::before {
  content: "";
  position: absolute;
  left: 0; top: 20%; bottom: 20%;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleY(0);
  transition: transform var(--dur) var(--ease);
}
.entry:hover, .entry:focus-visible { background: var(--glass); }
.entry:hover::before, .entry:focus-visible::before { transform: scaleY(1); }
.entry:hover h3 { color: var(--accent); }
.entry__when {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.entry__desc { margin-top: var(--sp-2); color: var(--muted); font-size: var(--t-small); max-width: 58ch; text-wrap: pretty; }
.entry__len  { font-family: var(--font-mono); font-size: var(--t-mono); color: var(--muted); }

/* About: a portrait on the left, the prose and the facts stacked beside it —
   the arrangement the reference uses, and the one that does not leave a hole
   under a short paragraph. */
.about { display: grid; gap: var(--sp-12); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) { .about { grid-template-columns: .78fr 1.22fr; } }
.about__body { display: grid; gap: var(--sp-10); }
.about__portrait { margin: 0; display: grid; gap: var(--sp-3); }
.about__portrait img {
  width: 100%;
  border-radius: var(--radius);
}

.facts { display: grid; gap: var(--sp-8); grid-template-columns: 1fr; }
@media (min-width: 560px) { .facts { grid-template-columns: 1fr 1fr; } }
.facts h3 {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 400;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: var(--sp-3);
  border-bottom: var(--hairline) solid var(--line);
  margin-bottom: var(--sp-4);
}
.facts dl { margin: 0; display: grid; gap: var(--sp-4); }
.facts dt {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .1em;
  color: var(--muted);
}
.facts dd { margin: var(--sp-1) 0 0; font-weight: 600; }
.facts dd span { display: block; font-weight: 400; font-size: var(--t-small); color: var(--muted); }
.facts ul { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--sp-3); }
.facts li b { display: block; font-family: var(--font-mono); font-size: var(--t-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); font-weight: 400; }
.facts li span { font-size: var(--t-small); }

/* An entry that opens in place: résumé roles and talks share this. The summary
   is the row you see collapsed; the body is the detail behind it.

   The expand is animated through ::details-content, which needs
   interpolate-size on the root to move between 0 and auto. Browsers without
   either simply snap open — the same behaviour as an unstyled <details>. */
.record::details-content {
  block-size: 0;
  overflow: hidden;
  transition:
    block-size var(--dur-slow) var(--ease),
    content-visibility var(--dur-slow) allow-discrete;
}
.record[open]::details-content { block-size: auto; }

.record {
  border-bottom: var(--hairline) solid var(--line-soft);
  border-radius: var(--radius);
  transition: background var(--dur) var(--ease);
}
/* One tint, never two: the row highlights on hover only while it is closed,
   and an open record carries the tint for the whole thing. */
.record:not([open]) > summary:hover { background: var(--glass); }
.record[open] { background: var(--glass); }
.record > summary {
  list-style: none;
  cursor: pointer;
  position: relative;
  display: grid;
  gap: var(--sp-1);
  padding: var(--sp-5) var(--sp-10) var(--sp-5) var(--sp-8);
  border-radius: var(--radius-sm);
  transition: background var(--dur) var(--ease);
}
.record > summary::-webkit-details-marker { display: none; }
.record > summary:hover .record__co { color: var(--accent); }

/* the timeline dot: the open record is the leader, the rest are followers */
.record > summary::before {
  content: "";
  position: absolute;
  left: 3px; top: calc(var(--sp-5) + .45rem);
  width: 9px; height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--accent-2);
  background: var(--bg);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.record[open] > summary::before,
.record:first-of-type > summary::before {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 16%, transparent);
}
/* the connector, drawn behind the dot and stopped on the last record */
.record::before {
  content: "";
  position: absolute;
  left: 7px; top: 0; bottom: 0;
  width: 1px;
  background: var(--line);
}
.record { position: relative; }
.record:last-of-type::before { bottom: 50%; }

.record__when { font-family: var(--font-mono); font-size: var(--t-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.record__co   { font-family: var(--font-display); font-weight: 600; font-size: var(--t-title); letter-spacing: var(--ls-head); transition: color var(--dur) var(--ease); }
.record__role { color: var(--muted); font-size: var(--t-small); }
.record__more {
  position: absolute;
  right: var(--sp-4); top: 50%;
  translate: 0 -50%;
  display: grid; place-items: center;
  width: 1.75rem; height: 1.75rem;
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--muted);
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.record[open] .record__more { transform: rotate(180deg); color: var(--accent); border-color: var(--accent); }

.record__body {
  margin-inline: var(--sp-8) var(--sp-6);
  margin-bottom: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: var(--hairline) solid var(--line-soft);
  color: var(--muted);
  font-size: var(--t-small);
}
.record__body > p { margin: 0 0 var(--sp-4); }
.record__body ul { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--sp-4); }
.record__body li b { display: block; color: var(--text); font-weight: 600; margin-bottom: var(--sp-1); }
.record__body li, .record__role, .facts li span { text-wrap: pretty; }
.record__body a { color: var(--accent); }

/* The résumé's contact details are four short facts. As a labelled table they
   cost 245px of the first screen and pushed the actual work history off it, so
   they run as one wrapped line instead. */
.resume-contact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  align-items: baseline;
  margin-top: var(--sp-6);
  font-size: var(--t-small);
  color: var(--muted);
}
/* Separator on the leading item, not the trailing one: when the line wraps on a
   phone a dangling "·" at the end of a row reads as "continues below", where the
   same dot at the start of the next row reads as a bullet. */
.resume-contact > span:not(:last-child)::after {
  content: "·";
  margin-left: var(--sp-4);
  color: var(--line);
}

/* The header's own bottom padding plus the next section's top padding put 144px
   of empty ground right where the fold lands. The jump row fills it with the
   page's own contents instead. */
.resume-head { padding-bottom: var(--sp-10); }

/* Table of contents and scroll cue in one row. Styled off .tag rather than
   .btn: these are navigation, not the page's action, and two accent buttons
   under the name read as a landing page rather than a résumé. */
.resume-jump {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-2);
  align-items: center;
  margin-top: var(--sp-8);
}
.resume-jump a {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .05em;
  color: var(--muted);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  padding: var(--sp-2) var(--sp-4);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.resume-jump a:hover { color: var(--accent); border-color: var(--accent); }
/* The pill is ~31px tall. Expand the hit area to 41 without moving the pixels;
   the 12px row gap keeps expanded areas from touching when the row wraps. */
.resume-jump a::after { content: ""; position: absolute; inset: -5px 0; }
/* The one link that leaves this page, pushed to the far end and given the
   full-contrast colour so it reads as a different kind of thing. */
.resume-jump .resume-jump__alt { color: var(--text); }
@media (min-width: 660px) { .resume-jump .resume-jump__alt { margin-left: auto; } }

/* Skills, as a grouped list rather than a badge wall. No logos, no proficiency
   bars, no year counters — those are the tells of a template portfolio, and the
   grouping is what carries the information anyway. */
.skills { margin: 0; display: grid; gap: 0; border-top: var(--hairline) solid var(--line); }
.skills > div {
  display: grid;
  gap: var(--sp-2) var(--sp-6);
  grid-template-columns: 1fr;
  padding-block: var(--sp-4);
  border-bottom: var(--hairline) solid var(--line-soft);
}
@media (min-width: 660px) { .skills > div { grid-template-columns: 12rem 1fr; align-items: baseline; } }
.skills dt {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}
.skills dd { margin: 0; }
.skills dd b { font-weight: 600; }

/* The section heading row: an eyebrow on the left, a way out on the right. */
.section-head {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-6);
}

.tags { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.tag {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: .05em;
  color: var(--muted);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  padding: var(--sp-1) var(--sp-3);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.tag:hover { color: var(--accent); border-color: var(--accent); }
.tag__count { color: var(--accent-2); }

/* Buttons read as a set: same pill, same weight, differing only in how much
   accent they carry. A solid amber fill next to an outlined pill made the pair
   look like two unrelated controls. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--t-small);
  font-weight: 600;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-pill);
  text-decoration: none;
  color: var(--text);
  background: var(--glass);
  border: var(--hairline) solid var(--line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition:
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}
.btn svg { flex: none; opacity: .75; }
.btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 9%, var(--glass));
}
.btn:hover svg { opacity: 1; }

/* the one that carries the accent at rest */
.btn--accent {
  color: var(--accent);
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  background: color-mix(in oklab, var(--accent) 7%, var(--glass));
}

.btn--ghost { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }

/* Tactile press. Small enough to feel like a button rather than a toy, and
   removed entirely when the reader has asked for less motion. */
.btn:active,
.tag:active,
.theme-toggle:active,
.record > summary:active { transform: scale(.97); }

/* ------------------------------------------------------------------ prose */
/* Everything the markdown pipeline emits lands inside .prose. */

.prose { max-width: var(--measure); }
/* Headings inside a post are subordinate to the page title, which is already an
   h1 in the header. Without this a markdown "# Heading" renders at hero size
   and outshouts the title above it. */
.prose :where(h1, h2) { font-size: var(--t-h2); font-weight: 600; letter-spacing: var(--ls-head); }
.prose :where(h3, h4) { font-size: var(--t-h3); font-weight: 600; }
.prose > * + * { margin-top: var(--sp-4); }
.prose h2 { margin-top: var(--sp-10); }
.prose h3 { margin-top: var(--sp-8); }
.prose a  { color: var(--accent); text-decoration: underline; }
.prose ul, .prose ol { padding-left: var(--sp-5); }
.prose li + li { margin-top: var(--sp-2); }
.prose img { border-radius: var(--radius-sm); }

.prose blockquote {
  margin-inline: 0;
  padding: var(--sp-4) var(--sp-6);
  border-left: 2px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--glass);
  font-size: var(--t-lead);
  line-height: var(--lh-lead);
}

/* Wide content scrolls inside its own box so the page body never does. */
.prose pre { overflow-x: auto; }
/* The markdown pipeline emits a bare <table> with nothing to wrap it, so the
   table is its own scroll container. Without this a wide table would push the
   whole page sideways. */
.prose table {
  display: block;
  width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: var(--t-small);
}
.prose th, .prose td { text-align: left; padding: var(--sp-2) var(--sp-3); border-bottom: var(--hairline) solid var(--line-soft); }
.prose th { font-family: var(--font-mono); font-size: var(--t-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); font-weight: 400; }

pre {
  background: var(--bg-deep);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-4) var(--sp-6);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  line-height: 1.7;
}
/* Syntax highlighting. Chroma emits these class names server-side; the colours
   come from the token layer, so code follows the theme like everything else.
   Only the token types Go, SQL, JSON and shell actually produce are mapped —
   an exhaustive chroma stylesheet would be mostly dead rules. */
.chroma               { color: var(--text); background: transparent; }
.chroma .k,                        /* keyword          */
.chroma .kd, .chroma .kn,
.chroma .kp, .chroma .kr,
.chroma .kc            { color: var(--code-keyword); }
.chroma .kt            { color: var(--code-type); }     /* built-in type      */
.chroma .s,                        /* string           */
.chroma .s1, .chroma .s2,
.chroma .sb, .chroma .sc,
.chroma .sa, .chroma .se,
.chroma .si, .chroma .sx,
.chroma .sr, .chroma .ss,
.chroma .sd            { color: var(--code-string); }
.chroma .c,                        /* comment          */
.chroma .c1, .chroma .cm,
.chroma .cs, .chroma .cp,
.chroma .cpf, .chroma .ch { color: var(--code-comment); font-style: italic; }
.chroma .m,                        /* number           */
.chroma .mi, .chroma .mf,
.chroma .mh, .chroma .mo,
.chroma .mb, .chroma .il { color: var(--code-number); }
.chroma .nf, .chroma .fm { color: var(--code-func); }   /* function name      */
.chroma .nb            { color: var(--code-type); }     /* builtin            */
.chroma .nc, .chroma .nn,
.chroma .no, .chroma .nd { color: var(--code-type); }
.chroma .o, .chroma .ow { color: var(--muted); }        /* operator           */
.chroma .p             { color: var(--muted); }         /* punctuation        */
.chroma .err           { color: var(--text); }          /* never a red flash  */
.chroma .ln, .chroma .lnt {
  color: var(--muted);
  opacity: .6;
  user-select: none;                                    /* copying code should not take the gutter */
}

code { font-family: var(--font-mono); }
:not(pre) > code {
  font-size: var(--t-code-inline);
  background: var(--glass);
  border: var(--hairline) solid var(--line-soft);
  border-radius: 5px;
  padding: .1em .35em;   /* em-relative on purpose: the chip grows with its code */
}

/* -------------------------------------------------------------- utilities */

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

/* ------------------------------------------------------- motion preference */
/* Everything ambient stops: the halo freezes into a static wash, the name
   renders in solid text colour, and revealed sections are simply present.
   Nothing here changes what the page says. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .halo span,
  .hero__light span { animation: none; }
  .hero__next { animation: none; }
  /* Wires become steady lines and the core stops flashing. The diagram still
     reads left to right: inputs, evaluator, amount. */
  .hero__sweep {
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    color: var(--text);
  }
  .reveal { opacity: 1 !important; transform: none !important; }
  .btn:active,
  .tag:active,
  .theme-toggle:active,
  .record > summary:active { transform: none; }
}
