/* Portfolio v1.7. Shared by every page. Dark only, high contrast, phone first.

   How this file is laid out (docs/components.md lists every component, its classes and where it's used):
   1. fonts and tokens: colour, type, space, and the shared values components read
   2. base: elements, links, focus, the page width
   3. components: button, go link, chip, tag list, card, post card, section label, meta line, play button,
      video, pager, crumb, notice, form, nav, footer. Change one here and every use changes.
      A variant is a modifier class (.btn.primary, .chip.solid, .tags.dense, .card.project), never an id.
   4. layout: sections and grids
   5. page parts that exist once: hero, ways I can help, why me, how I work, contact, Sumrise, About, article
   6. motion: only inside prefers-reduced-motion: no-preference, only when the reader acts or arrives

   History: one type scale (ratio 1.25, fluid 375px to 1280px) and one spacing scale (8px rhythm) since v1.4;
   the palette, its jobs and contrast are in docs/palette.md. v1.7 (29 Sep 2026): components, no visual change. */

@font-face {
  font-family: "Geist";
  src: url("fonts/geist-latin-wght.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/geist-mono-latin-wght.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}

/* ============================================================ 1. tokens */
:root {
  color-scheme: dark;

  /* ---- colour (docs/palette.md). 60% navy, 30% off-white text and navy surfaces, 10% yellow. */
  --bg: #0b1220;            /* navy, the page */
  --surface: #111a2e;       /* cards and panels */
  --field: #0b1220;         /* form fields sit back into the page */
  --code-bg: #080e19;
  --media-bg: #070c16;
  --media-fit-bg: #0b0d10;  /* behind a picture shown whole (.media.fit): the Vial map's own flat dark edge */
  --line: rgb(255 255 255 / 0.08);        /* hairlines, decorative */
  --line-strong: rgb(255 255 255 / 0.16); /* card hover, chip edge */
  --wash: rgb(255 255 255 / 0.05);        /* the faint fill behind tags and chips */
  --control: #66728c;       /* borders that identify a control: 3.9:1 on the navy, 3.6:1 on cards (WCAG 1.4.11 needs 3:1) */
  --ink: #f4f6fa;           /* off-white text, 17.3:1 */
  --muted: #b4bfd0;         /* secondary text, 10.1:1 */
  --accent: #fcc803;        /* brand yellow: links, the primary button, focus, 11.9:1 */
  --accent-hover: #ffd84a;
  --accent-press: #e3b400;
  --accent-ink: #0b1220;    /* text on yellow: navy, 11.9:1. Never white on yellow (1.6:1) */
  --secondary: #a9bcff;     /* periwinkle, from the navy's own hue family: visited links in posts, status tags, 10.1:1 */
  --secondary-bg: #16223f;
  --success: #5fd9a2;       /* teal-leaning green, 10.6:1 */
  --success-bg: #0f2a24;
  --warning: #ffa15c;       /* orange, kept well away from the brand yellow: "to add" placeholders, 9.4:1 */
  --warning-bg: #33200f;
  --error: #ff8a8a;         /* light red for dark backgrounds, 8.3:1 */
  --error-bg: #2e1419;

  --sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* ---- type: one fluid scale, ratio 1.25 */
  --step--2: 0.8125rem;                                    /* 13px: tags, badges */
  --step--1: clamp(0.875rem, 0.85rem + 0.1vw, 0.9375rem);  /* 14 to 15: labels, meta, nav, captions */
  --step-0: clamp(1.0625rem, 1.02rem + 0.18vw, 1.125rem);  /* 17 to 18: body */
  --step-1: clamp(1.25rem, 1.17rem + 0.35vw, 1.4rem);      /* 20 to 22: lede, h3 */
  --step-2: clamp(1.5rem, 1.35rem + 0.65vw, 1.75rem);      /* 24 to 28: h2 */
  --step-3: clamp(1.875rem, 1.6rem + 1.2vw, 2.5rem);       /* 30 to 40: page h1, contact */
  --step-4: clamp(2.25rem, 1.8rem + 2vw, 3.5rem);          /* 36 to 56: hero h1 */
  --meta-font: 400 var(--step--1)/1.6 var(--mono);         /* meta lines: read time, dates, facts, small notes */

  /* ---- space: 8px rhythm */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
  --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px;

  --measure: 680px;
  --wide: 960px;
  --gutter: var(--sp-4);
  --section: var(--sp-8);
  --radius: 12px;           /* cards, panels, media */
  --radius-sm: 8px;         /* buttons, fields, code */
  --radius-tag: 6px;        /* tags */
  --radius-chip: 18px;      /* chips */
  --radius-pill: 999px;     /* round controls on media, the floating cue */

  /* ---- component values */
  --tag-font: 400 var(--step--2)/1.4 var(--sans);
  --tag-pad: 3px 8px;
  --tag-gap: 6px;
  --card-pad: var(--sp-4) var(--sp-5) var(--sp-5);
  --card-gap: var(--sp-2);
  --lift: 1.03;             /* how much a project card's picture grows on hover */
  --btn-gap: var(--sp-3);   /* between the buttons of a closing call to action: home panel, inner page endings */
}
@media (min-width: 700px) {
  :root { --gutter: var(--sp-5); --section: var(--sp-9); }
}

/* ============================================================ 2. base */
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: var(--sp-4); }
@media (min-width: 560px) { html { scroll-padding-top: 80px; } }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--step-0)/1.6 var(--sans);
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
}
/* page layout: nav, main, footer in a column at least the screen's height, so on a short page (the 404) the footer
   sits at the bottom of the screen, not halfway up. main takes the spare room; width: 100% keeps a centred .wrap main full width */
body { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
main { flex: 1 0 auto; width: 100%; }
img, video { max-width: 100%; height: auto; }
h1, h2, h3, h4 { font-weight: 600; line-height: 1.15; text-wrap: balance; }
p, li, figcaption { text-wrap: pretty; }
h3 { font-size: var(--step-1); margin: 0; letter-spacing: -0.01em; }
a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); text-decoration-thickness: 2px; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
::selection { background: var(--accent); color: var(--accent-ink); } /* selected text in the brand yellow, not the browser's blue */
.skip { position: absolute; left: -9999px; }
.skip:focus { left: var(--sp-4); top: var(--sp-3); background: var(--accent); color: var(--accent-ink); padding: var(--sp-2) var(--sp-3); z-index: 30; font-weight: 600; outline-color: var(--ink); }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.sur { color: var(--accent); } /* his surname in yellow, in the nav and on About */

.wrap { max-width: calc(var(--wide) + 2 * var(--gutter)); margin: 0 auto; padding: 0 var(--gutter); }
.wrap.narrow { max-width: calc(var(--measure) + 2 * var(--gutter)); }

/* ============================================================ 3. components */

/* ---- button: .btn, variants .primary (yellow), .big, .mail (a stroke icon before the label) */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: 48px; padding: var(--sp-3) var(--sp-5); border-radius: var(--radius-sm);
  font: 600 var(--step-0)/1.2 var(--sans); text-decoration: none; cursor: pointer;
  border: 1px solid var(--control); color: var(--ink); background: transparent;
}
.btn:hover { border-color: var(--ink); color: var(--ink); text-decoration: none; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }
.btn.primary:active { background: var(--accent-press); border-color: var(--accent-press); }
.btn.big { min-height: 56px; padding: var(--sp-3) var(--sp-6); }
.btn svg { width: 20px; height: 20px; flex: none; }
.btn.mail svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* a row of buttons and links */
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-5); margin-top: var(--sp-5); }
/* .cta.pair: two buttons on one row, the same width, never wrapping (a Marketplace card's main action and More) */
.cta.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.cta.pair .btn { min-width: 0; padding-left: var(--sp-3); padding-right: var(--sp-3); text-align: center; }
/* a button that can't be used yet ("Coming soon"): a <button disabled aria-disabled="true">, outline dashed, muted words, no hover */
.btn[aria-disabled="true"], .btn[aria-disabled="true"]:hover, .btn[aria-disabled="true"]:active {
  background: transparent; border: 1px dashed var(--control); color: var(--muted); cursor: default;
}

/* ---- go link: a text link with an arrow. .go.down points down (the scroll cues) */
.go { font-weight: 500; display: inline-block; padding: var(--sp-3) 0; }
.go::after { content: "\2192"; margin-left: var(--sp-2); }
.go.down::after { content: "\2193"; display: inline-block; }
.see-all { margin: var(--sp-4) 0 0; }
/* scroll cue: each section ends in a small link to the next. Real links (keyboard, screen readers, no JavaScript);
   the arrow nudges once as the cue arrives (motion block). */
.next-cue { margin: var(--sp-6) 0 0; }
.next-cue .go { color: var(--ink); font: 500 var(--step--1)/1.5 var(--mono); padding: var(--sp-3) 0; }
.next-cue .go::after { color: var(--accent); }
.next-cue .go:hover { color: var(--accent); }

/* ---- chip: a link as a pill on a faint wash, yellow text. In a .chips list.
   .primary marks a group's main chip: inside a card it fills when the card is pointed at or focused.
   .solid fills it at rest (a call to action). */
.chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chips li { display: flex; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 6px 14px 6px 11px;
  border-radius: var(--radius-chip); background: var(--wash); border: 1px solid var(--line-strong);
  color: var(--accent); font: 500 var(--step--1)/1.3 var(--sans); text-decoration: none;
}
.chip svg { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
a.chip:hover, a.card:hover .chip, a.card:focus-visible .chip {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink); text-decoration: none;
}
a.chip:active { background: var(--accent-press); border-color: var(--accent-press); }
a.chip:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-chip); }
.chip.solid { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
a.chip.solid:hover, a.chip.solid:focus-visible { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }
/* on a phone or any touch screen, 44px tall so each is easy to press */
@media (max-width: 559px), (pointer: coarse) { .chip { min-height: 44px; } }
/* in a post: not the post's underlined, periwinkle-when-visited links */
.prose .chips { padding: 0; margin: var(--sp-3) 0 var(--sp-4); }
.prose .chips li { margin: 0; }
.prose a.chip, .prose a.chip:visited { color: var(--accent); text-decoration: none; }
.prose a.chip:hover, .prose a.chip:focus-visible { color: var(--accent-ink); }

/* ---- tag list: small quiet labels (stack, skills, post types). .status is the first tag, in periwinkle.
   .dense packs a little tighter on a phone. Works on <ul><li> or on <span><span> inside a link. */
.tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--tag-gap); }
.tags > * {
  display: inline-block; margin: 0; padding: var(--tag-pad); border-radius: var(--radius-tag);
  font: var(--tag-font); color: var(--muted); background: var(--wash);
}
.tags > .status { font-weight: 500; color: var(--secondary); background: var(--secondary-bg); }
@media (max-width: 559px) {
  .tags.dense { gap: 5px; }
  .tags.dense > * { padding: 3px 7px; }
}

/* ---- card: a box with a picture on top and words below. The only boxes on the site, with the panels.
   Parts: .media (modifiers .fit: shown whole on a dark ground; .top-left: cropped from the top left), .noshot
   (a drawing instead of a picture), .body. Variants: a.card (the whole card is one link), .wide (picture beside
   words from 1024px), .project (home project cards), .compact (a shorter foot). */
.card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column; min-width: 0; color: inherit;
}
.card .media { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: var(--media-bg); border-bottom: 1px solid var(--line); }
.card .media img, .card .media video { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.card .noshot { display: grid; place-items: center; color: var(--muted); }
.card .noshot svg { width: 86%; max-width: 440px; height: auto; overflow: visible; }
.card .body { padding: var(--card-pad); display: flex; flex-direction: column; gap: var(--card-gap); flex: 1; }
.card.compact .body { padding-bottom: var(--sp-4); }
.card p { margin: 0; }
.card .kicker { font-size: var(--step--2); }
.card .tags { margin-top: var(--sp-2); }
.card .chips { margin-top: auto; padding-top: var(--sp-3); }
/* a card that is one link (Sumrise on the home page, About's projects, Sumrise's talks) */
a.card { text-decoration: none; }
a.card:hover { border-color: var(--line-strong); color: inherit; }
a.card:hover h3 { color: var(--accent); }
a.card .go { color: var(--accent); }
.card.wide { grid-column: 1 / -1; }
/* side by side only from 1024px: at tablet width the words are taller than the picture's column is wide, and the crop cut "sumrise" in half */
@media (min-width: 1024px) {
  .card.wide { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .card.wide .media { border-bottom: 0; border-right: 1px solid var(--line); aspect-ratio: auto; min-height: 100%; }
  .card.wide .media img { object-position: left center; }
  .card.wide .media video { object-fit: contain; } /* a whole recording, uncropped, from the top */
}
.card .media.fit { background: var(--fit-ground, var(--media-fit-bg)); } /* --fit-ground, set on the media: a picture's own edge colours, so the bands around it don't show */
.card .media.fit img { object-fit: contain; object-position: center; }
.card .media.top-left img { object-position: left top; }

/* project card (home): title, what it is, the human story, how it was built, tags, chips. One hover for every
   project card, the same on keyboard focus: the picture lifts, the title turns yellow, the primary chip fills.
   If another chip is pointed at or focused, the primary one goes back to rest, so only one is ever lit. */
.card.project h3 { font-size: var(--step-2); line-height: 1.15; letter-spacing: -0.015em; margin-bottom: 2px; }
.card.project .what { font-size: var(--step-0); font-weight: 500; line-height: 1.45; color: var(--ink); max-width: 44ch; }
.card.project .story, .card.project .how { font: 400 var(--step--1)/1.55 var(--sans); color: var(--ink); opacity: .86; max-width: 56ch; margin: 0; }
.card.project .how { margin-top: var(--sp-2); }
/* tablet: pictures one step shorter (2:1), so a card fits the screen with its words */
@media (min-width: 768px) and (max-width: 1023px) { .card.project .media { aspect-ratio: 2 / 1; } }
/* phone: a little more width for the words, so the tags pack into fewer rows */
@media (max-width: 559px) { .card.project .body { padding-left: 20px; padding-right: 20px; } }
.card.project:hover, .card.project:focus-within { border-color: var(--line-strong); }
.card.project .media img, .card.project .media svg { transition: filter .2s ease-out; }
.card.project:hover .media img, .card.project:focus-within .media img { filter: brightness(1.08); }
.card.project:hover .media svg, .card.project:focus-within .media svg { filter: brightness(1.12); }
.card.project:hover h3, .card.project:focus-within h3 { color: var(--accent); }
.card.project:hover .chip.primary, .card.project:focus-within .chip.primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.card.project:has(a.chip:not(.primary):is(:hover, :focus-visible)) .chip.primary {
  background: var(--wash); border-color: var(--line-strong); color: var(--accent);
}

/* ---- post card (Blog page): cover, title, what the reader gets, then a foot with type tags and read time.
   The foot sits at the bottom of every card, so a row of cards lines up however long the words are. */
.post-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; min-width: 0; }
.post-card:hover { border-color: var(--line-strong); }
.post-link { display: flex; flex-direction: column; height: 100%; color: inherit; text-decoration: none; }
.post-link:hover { color: inherit; }
.post-link:focus-visible { outline-offset: -3px; border-radius: var(--radius); }
.post-cover-wrap { overflow: hidden; aspect-ratio: 3 / 2; border-bottom: 1px solid var(--line); background: var(--media-bg); }
.post-cover { display: block; width: 100%; height: 100%; object-fit: cover; }
.post-cover.left { object-position: left center; } /* cropped from the right, for a cover with words at its left edge (the Sumrise still) */
.post-text { padding: var(--sp-4) var(--sp-5) var(--sp-5); display: flex; flex-direction: column; flex: 1; }
.post-title { font-size: var(--step-1); line-height: 1.3; margin: 0 0 var(--sp-2); }
.post-link:hover .post-title { color: var(--accent); }
.post-sub { margin: 0; color: var(--ink); opacity: .86; font-size: var(--step--1); line-height: 1.5; }
.post-card-foot { margin-top: auto; padding-top: var(--sp-4); }
.post-card .post-meta { font-size: var(--step--2); margin: var(--sp-2) 0 0; opacity: .8; }

/* ---- section label: the numbered section heading. Mono, h2 size, off-white, number in yellow.
   The statement line under it (.section-title) steps down to lede size so the heading still leads. */
.label { font: 600 var(--step-2)/1.3 var(--mono); color: var(--ink); letter-spacing: 0; margin: 0 0 var(--sp-5); }
.label .num { color: var(--accent); margin-right: var(--sp-2); }
.section-title { font-size: var(--step-1); line-height: 1.4; letter-spacing: -0.02em; margin: 0; max-width: 30ch; text-wrap: balance; } /* one statement, set like a heading: if it wraps, two even lines */
.kicker { font: 500 var(--step--1)/1.5 var(--mono); color: var(--muted); margin: 0; }

/* ---- meta line: read time and other small facts, in mono. One font token (--meta-font) for all of them. */
.post-meta { font: var(--meta-font); color: var(--muted); margin: 0 0 var(--sp-2); }
.role { font: 400 var(--step--1)/1.5 var(--mono); color: var(--muted); margin: 0; }

/* ---- play button: a round control on media (the YouTube stills, the podcast). Bottom left; .right puts it bottom right,
   for a still whose words sit on the left (Sumrise) */
.play {
  position: absolute; left: var(--sp-3); bottom: var(--sp-3); display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 44px; padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-3); border-radius: var(--radius-pill); cursor: pointer;
  background: rgb(11 18 32 / 0.9); color: var(--ink); border: 1px solid var(--control);
  font: 500 var(--step--1)/1 var(--sans);
}
.play svg { width: 20px; height: 20px; flex: none; }
.play:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.play.right { left: auto; right: var(--sp-3); }

/* ---- video: a still and a play button, the player loads on press */
.video { position: relative; aspect-ratio: 16 / 9; background: var(--media-bg); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.video iframe, .video .yt { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.video .yt img { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (min-width: 560px) {
  .video .yt .play { left: var(--sp-4); bottom: var(--sp-4); padding: var(--sp-3) var(--sp-5) var(--sp-3) var(--sp-4); font-size: var(--step-0); }
  .video .yt .play.right { left: auto; right: var(--sp-4); }
} /* on a phone the smaller button, so it covers less of the still */
.video .yt:hover .play { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.video-caption { font-size: var(--step--1); color: var(--muted); margin: var(--sp-3) 0 0; }
.video-caption strong { display: block; color: var(--ink); font-size: var(--step-0); font-weight: 600; }
/* 24px targets (WCAG 2.5.8): the links stay inline, so a long link wraps with its sentence and the padding makes each
   line a 24px target without pushing the caption's lines apart */
.video-caption a, .also a { padding: var(--sp-1) 0; }

/* ---- pager: Previous and Next at the foot of a post */
.pager { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-2) var(--sp-5); margin-top: var(--sp-7); border-top: 1px solid var(--line); padding-top: var(--sp-4); }
@media (min-width: 600px) { .pager { grid-template-columns: 1fr 1fr; } .pager-next { grid-column: 2; text-align: right; } }
.pager a { display: block; padding: var(--sp-2) 0; text-decoration: none; color: var(--ink); font-weight: 500; }
.pager a:hover { color: var(--accent); }
.pager span { display: block; font: var(--meta-font); color: var(--muted); margin-bottom: 2px; }

/* ---- crumb: a back link above a page's title */
.crumb { margin: 0 0 var(--sp-4); font-weight: 500; }
.crumb a { display: inline-flex; align-items: center; min-height: 44px; }
.crumb a::before { content: "\2190"; margin-right: var(--sp-2); }

/* ---- to add: a placeholder. A warning colour, never the brand yellow, dashed so it reads as unfinished */
.todo { display: inline-block; border: 1px dashed var(--warning); color: var(--warning); background: var(--warning-bg); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); font: 400 var(--step--1)/1.5 var(--mono); }

/* ---- facts: short labelled facts in two columns (hidden until Dileepa supplies them) */
.facts { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--sp-5); margin: var(--sp-6) 0 0; }
.facts div { border-top: 1px solid var(--line); padding: var(--sp-4) 0; }
.facts dt { font: var(--meta-font); color: var(--muted); }
.facts dd { margin: var(--sp-1) 0 0; font-weight: 500; }
@media (min-width: 700px) { .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---- line list: items between hairlines. .ticks adds a tick before each */
.also-list { list-style: none; margin: var(--sp-2) 0 var(--sp-4); padding: 0; max-width: var(--measure); }
.also-list li { padding: var(--sp-3) 0; border-top: 1px solid var(--line); }
.also-list li:last-child { border-bottom: 1px solid var(--line); }
.ticks { list-style: none; padding: 0 !important; }
.ticks li { position: relative; padding-left: 28px; }
.ticks li::before { content: ""; position: absolute; left: 2px; top: 0.5em; width: 12px; height: 6px; border-left: 2px solid var(--ink); border-bottom: 2px solid var(--ink); transform: rotate(-45deg); }
.also-list.ticks li { padding-left: 28px; }
.also-list.ticks li::before { top: calc(var(--sp-3) + 0.5em); }

/* ---- post list: About's list of posts, read time then title and line */
.post-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); max-width: var(--measure); }
.post-list li { border-bottom: 1px solid var(--line); }
.post-list a { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: 2px var(--sp-5); padding: var(--sp-4) 0; color: inherit; text-decoration: none; }
.post-list .when { font: var(--meta-font); color: var(--muted); padding-top: 2px; }
.post-list .t { font-weight: 600; line-height: 1.35; }
.post-list .sub { grid-column: 2; color: var(--muted); font-size: var(--step--1); line-height: 1.5; }
.post-list a:hover .t { color: var(--accent); }
/* focus is per post: the ring sits just inside the row, so it never spreads across the list or its neighbours */
.post-list a:focus-visible { outline-offset: 2px; border-radius: var(--radius-sm); }
.post-list a:focus-visible .t { color: var(--accent); }
@media (max-width: 599px) {
  .post-list a { grid-template-columns: minmax(0, 1fr); }
  .post-list .sub { grid-column: 1; }
}

/* ---- notice: after sending the form, success or .is-problem */
.notice { border-radius: var(--radius-sm); padding: var(--sp-5); border-left: 4px solid var(--success); background: var(--success-bg); }
.notice:focus { outline: none; }
.notice:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.notice h3 { font-size: var(--step-1); margin: 0 0 var(--sp-3); }
.notice p { font-size: var(--step-0) !important; margin: 0 0 var(--sp-3); }
.notice.is-problem { border-left-color: var(--error); background: var(--error-bg); }
.notice pre {
  white-space: pre-wrap; font: var(--meta-font); color: var(--ink);
  background: var(--field); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: var(--sp-4); margin: var(--sp-4) 0;
  max-height: 240px; overflow: auto;
}
.notice .cta { margin-top: var(--sp-4); }

/* ---- form: the enquiry form */
.form { margin: 0; }
.field { display: grid; gap: var(--sp-2); margin: 0 0 var(--sp-5); }
.field label { font-weight: 500; font-size: var(--step-0); line-height: 1.3; }
.field .opt { font-weight: 400; color: var(--muted); }
.hint { color: var(--muted); font-size: var(--step--1); line-height: 1.5; margin: 0; }
.form input, .form select, .form textarea {
  width: 100%; min-height: 48px; margin: 0; padding: var(--sp-3) var(--sp-4);
  font: 400 var(--step-0)/1.4 var(--sans); color: var(--ink);
  background: var(--field); border: 1px solid var(--control); border-radius: var(--radius-sm);
}
.form textarea { min-height: 144px; resize: vertical; line-height: 1.5; }
.form ::placeholder { color: var(--muted); opacity: 0.8; } /* a hint, a step quieter than typed text */
.form select {
  appearance: none; -webkit-appearance: none; padding-right: var(--sp-7);
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 22px) 50%, calc(100% - 16px) 50%; background-size: 6px 6px; background-repeat: no-repeat;
}
.form input:hover, .form select:hover, .form textarea:hover { border-color: var(--muted); }
.form input:focus-visible, .form select:focus-visible, .form textarea:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-color: var(--accent); border-radius: var(--radius-sm); }
.form [aria-invalid="true"] { border-color: var(--error); border-width: 2px; }
.error { color: var(--error); font-size: var(--step--1); font-weight: 500; line-height: 1.5; margin: 0; display: flex; gap: var(--sp-2); align-items: baseline; }
.error::before { content: "!"; flex: none; display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--error); color: var(--bg); font: 700 var(--step--2)/1 var(--sans); }
.form .btn { width: 100%; }
@media (min-width: 560px) { .form .btn { width: auto; } }

/* ---- nav: name left, five links grouped right, no button.
   One row from 560px (sticky); below that the links sit on a second row, left-aligned with the name. */
nav.site { border-bottom: 1px solid var(--line); background: rgb(11 18 32 / 0.92); }
@supports ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  nav.site { background: rgb(11 18 32 / 0.8); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
}
nav.site .wrap { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0 var(--sp-5); min-height: 64px; }
nav.site .name {
  display: inline-block; padding: var(--sp-3) 0; /* 44px tall; inline-block keeps the space between the two names */
  font: 600 var(--step-0)/1.2 var(--sans); letter-spacing: -0.01em; color: var(--ink); text-decoration: none;
}
nav.site .name:hover { text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 6px; }
nav.site ul { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--sp-5); margin: 0; padding: 0; }
nav.site ul a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--muted); text-decoration: none; font: 500 var(--step--1)/1.2 var(--sans);
}
nav.site ul a:hover, nav.site ul a[aria-current] { color: var(--ink); text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 6px; }
@media (min-width: 560px) { nav.site { position: sticky; top: 0; z-index: 20; } }
@media (max-width: 559px) {
  nav.site .wrap { padding-top: var(--sp-2); padding-bottom: var(--sp-1); }
  nav.site ul { width: 100%; gap: 0 var(--sp-4); } /* five links fit on one row at 360px with a 16px gap */
}
@media (max-width: 359px) { nav.site ul { gap: 0 var(--sp-3); } } /* and at 320px with a 12px gap, so Contact doesn't sit alone on a third row */

/* ---- footer: the credit line, then three icon links: side by side from 560px, stacked on a phone */
footer.site { margin-top: var(--section); border-top: 1px solid var(--line); padding: var(--sp-4) 0 var(--sp-7); color: var(--muted); font-size: var(--step--1); }
footer.site .wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-2) var(--sp-5); }
footer.site p { margin: 0; }
footer.site .version { font-family: var(--mono); white-space: nowrap; } /* the release number, from VERSION */
/* --social-inset: the space between a 24px icon and its 44px tap area, pulled back so the icon (not the tap area) sits on the text edge */
.social { --social-inset: calc((44px - 24px) / 2); list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0 0 0 calc(-1 * var(--social-inset)); padding: 0; }
@media (min-width: 560px) { .social { margin: 0 calc(-1 * var(--social-inset)) 0 0; } } /* the icons line up with the text edge, not their tap area */
.social a { display: inline-grid; place-items: center; width: 44px; height: 44px; border-radius: var(--radius-sm); color: var(--ink); text-decoration: none; }
.social a svg { width: 24px; height: 24px; display: block; }
.social a:hover { color: var(--accent); }

/* ---- floating cue: the "next section" link on the home page (assets/site.js 1d). Quiet: muted mono text on a navy
   pill, fixed near the bottom centre, one at a time. Hidden until the script says it's on; gone on short screens
   (a phone on its side), where it would sit over too much of the page. It fades; it only rises when motion is welcome. */
.float-cue {
  position: fixed; left: 50%; bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px)); z-index: 15;
  transform: translateX(-50%); max-width: calc(100vw - 2 * var(--sp-4));
  display: inline-flex; align-items: center; gap: var(--sp-2); min-height: 44px; padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-pill); background: rgb(11 18 32 / 0.92); border: 1px solid var(--line-strong);
  color: var(--muted); font: 500 var(--step--1)/1.2 var(--mono); text-decoration: none; white-space: nowrap;
  opacity: 0; visibility: hidden; transition: opacity .2s ease-out, visibility 0s linear .2s;
}
.float-cue.is-on { opacity: 1; visibility: visible; transition: opacity .2s ease-out; }
.float-cue .fc-text { overflow: hidden; text-overflow: ellipsis; }
.float-cue:hover { color: var(--ink); border-color: var(--control); }
.float-cue:focus-visible { border-radius: var(--radius-pill); color: var(--ink); } /* keep the pill shape inside the yellow ring */
.float-cue:hover .fc-arrow { color: var(--accent); }
@media (max-height: 479px) { .float-cue { display: none; } }
h2[tabindex="-1"]:focus { outline: none; } /* a section heading the floating cue moved focus to: not a control, so no ring */

/* ============================================================ 4. layout */
/* sections: space and a hairline. The hairline sits in the content box, so it lines up with the text rather than the gutters */
.section, .contact { margin-top: var(--section); }
.section::before { content: ""; display: block; border-top: 1px solid var(--line); margin-bottom: var(--sp-5); }
.contact:not(#contact)::before { content: ""; display: block; border-top: 1px solid var(--line); margin-bottom: var(--sp-7); } /* inner pages; the contact page's is a panel */

.page-head { padding-top: var(--sp-7); }
.page-head h1 { font-size: var(--step-3); letter-spacing: -0.02em; margin: 0 0 var(--sp-3); }
.page-head .lede { margin-bottom: 0; }
.lede { font-size: var(--step-1); line-height: 1.45; margin: 0 0 var(--sp-4); max-width: var(--measure); }
.lede.wide { max-width: none; } /* a one-line lede: runs the full width, wraps only when the screen is narrower */

/* card grids */
.grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
@media (min-width: 768px) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* home projects: every card as tall as the tallest, only once the picture sits beside the words (.card.wide).
   Stacked, equal rows left a gap of up to 150px between a short card's tags and its chips. */
@media (min-width: 1024px) { .grid { grid-auto-rows: 1fr; } }
.mini-projects { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
@media (min-width: 600px) { .mini-projects { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .mini-projects { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 600px) { .narrow .mini-projects { grid-template-columns: repeat(2, minmax(0, 1fr)); } } /* About: two by two in the narrow column */
.post-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
@media (min-width: 600px) { .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 900px) { .post-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.page-head + .post-grid { margin-top: var(--sp-6); }
.sum-talks { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
@media (min-width: 700px) { .sum-talks { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 960px) { .sum-talks { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--sp-7); }
@media (min-width: 768px) { .cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ============================================================ 5. page parts that exist once */

/* ---- hero: the photo and the heading are one unit, then the offers as chips and a quiet meta row */
.hero { padding-top: var(--sp-8); }
.hero-id { display: flex; align-items: center; gap: var(--sp-5); margin: 0 0 var(--sp-5); }
.avatar {
  display: block; flex: none; width: 112px; height: 112px; border-radius: 50%; object-fit: cover;
  box-shadow: 0 0 0 1px var(--line-strong); background: var(--surface);
}
.hero h1 { font-size: var(--step-4); line-height: 1.05; letter-spacing: -0.03em; margin: 0; }
.hero p { margin: 0 0 var(--sp-3); max-width: var(--measure); }
.hero .lede { margin-bottom: var(--sp-3); }
@media (max-width: 559px) {
  .hero { padding-top: var(--sp-6); }
  .hero-id { flex-direction: column; align-items: flex-start; gap: var(--sp-4); margin-bottom: var(--sp-4); }
  .avatar { width: 80px; height: 80px; }
}
/* phone: Home hero and About intro centred (Home C, About A, 22 Oct 2026).
   Home lines balanced so no centred line ends ragged, one full-width button with the projects link under it.
   Scoped to .hero and .me: .hero-meta and .offer-chips also sit on Contact and lower on About. Wider screens stay left */
@media (max-width: 559px) {
  .hero { text-align: center; }
  .hero-id { align-items: center; }
  .hero p { margin-inline: auto; text-wrap: balance; }
  .hero .offer-chips, .hero .hero-meta { justify-content: center; }
  .hero .cta { flex-direction: column; align-items: stretch; gap: var(--sp-1); }
  .hero .cta .go { align-self: center; }
  .me .intro-text { text-align: center; }
}
/* the offers: plain list items, not links, so not .chip */
.offer-chips { list-style: none; margin: 0 0 var(--sp-4); padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); max-width: var(--measure); }
.offer-chips li {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 6px 14px 6px 10px; border-radius: var(--radius-pill); background: var(--surface);
  font: 500 var(--step-0)/1.3 var(--sans); color: var(--ink);
}
.offer-chips svg { width: 20px; height: 20px; flex: none; color: var(--accent); }
/* meta row (UK-based, live example); the contact page uses it too */
.hero-meta { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); max-width: var(--measure); }
.hero-meta li { display: inline-flex; align-items: center; gap: 6px; font: 400 var(--step--1)/1.5 var(--sans); color: var(--muted); }
.hero-meta svg { width: 16px; height: 16px; flex: none; color: var(--muted); }
.hero-meta a { display: inline-block; padding: 2px 0; }

/* ---- the drawings (ways I can help, Why me): lines are the control grey, the one thing that matters is yellow */
.ln { fill: none; stroke: var(--control); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.ln.accent { stroke: var(--accent); stroke-width: 3; }
.dot { fill: var(--muted); }
.hi { fill: var(--accent); }
.dash { fill: none; stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 3; }
.x { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; }
.tick { fill: none; stroke: var(--accent-ink); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.tick.spark { fill: var(--accent-ink); stroke-width: 1; }

/* ---- ways I can help (home, and Services' offers): five offers, a small drawing each, no boxes.
   Phone: drawing left, words right. From 700px: the same rows, every row the same height, the drawing 144px;
   from 1000px two columns. Every title and line wraps at the same measure (--way-h, --way-p), so the five look even.
   --way-h is wide enough for a three-word title on one line at every width, so none leaves one word on a line of its own;
   the longer titles still wrap into two even lines (text-wrap: balance).
   Each offer lifts like a project card: the grey lines lighten (the yellow stays), the title turns yellow. */
.ways {
  --way-h: 13.5em; --way-p: 16em; --pic: 144px; --pic-gap: var(--sp-5);
  list-style: none; margin: var(--sp-5) 0 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6);
}
.ways li { min-width: 0; display: grid; grid-template-columns: 112px minmax(0, 1fr); gap: 0 var(--sp-4); align-items: start; }
.way-pic { display: block; width: 100%; max-width: 200px; height: auto; overflow: visible; grid-row: span 2; }
.ways h3, .ways .way-h { font-size: var(--step-1); line-height: 1.3; letter-spacing: -0.01em; margin: 0 0 var(--sp-2); max-width: var(--way-h); text-wrap: balance; }
.ways p { margin: 0; color: var(--muted); max-width: var(--way-p); text-wrap: pretty; }
@media (max-width: 699px) {
  .ways h3, .ways .way-h { font-size: var(--step-0); }
  .ways p { font-size: var(--step--1); }
}
@media (min-width: 700px) {
  .ways { grid-auto-rows: 1fr; }
  .ways li { grid-template-columns: var(--pic) minmax(0, 1fr); gap: 0 var(--pic-gap); align-content: start; }
}
@media (min-width: 1000px) { .ways { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-7) var(--sp-6); } }
.offer h3, .offer .way-h { transition: color .2s ease-out; }
.offer .way-pic .ln:not(.accent) { transition: stroke .2s ease-out; }
.offer:hover :is(h3, .way-h), .offer:focus-within :is(h3, .way-h) { color: var(--accent); }
.offer:hover .way-pic .ln:not(.accent), .offer:focus-within .way-pic .ln:not(.accent) { stroke: var(--muted); }
/* the ask under the offers: a line, then chips (Get in touch is .solid) */
.ways-cta { margin: var(--sp-7) 0 0; font: 500 var(--step-0)/1.4 var(--sans); letter-spacing: 0; color: var(--ink); }
@media (max-width: 699px) { .ways-cta { font-weight: 400; } } /* titles are this size here too: the ask stays a step below them */
.ways-ask .chips { margin-top: var(--sp-4); }
.ways-ask .chips + .ways-cta.sub { margin-top: var(--sp-3); } /* the free call sits under the buttons, a quieter second way in */
.page-head + .svc-offers .ways { margin-top: var(--sp-7); }

/* ---- why me: the picture and three short points. Point to a point and its part of the picture lights up */
.why-title { margin-bottom: var(--sp-6); max-width: none; }
.why { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); align-items: center; }
@media (min-width: 860px) { .why { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--sp-7); } }
.venn { display: block; width: 100%; max-width: 380px; height: auto; margin: 0 auto; overflow: visible; }
.venn .lens { fill: rgb(252 200 3 / 0.08); } /* the overlap, where he works: a yellow edge and the faintest yellow fill */
.venn .vt { fill: var(--ink); font: 500 13px/1 var(--mono); }
.why-points { list-style: none; margin: 0; padding: 0; }
.why-points > li { border-top: 1px solid var(--line); padding: var(--sp-4) 0; transition: background-color .2s ease-out; }
.why-points > li:last-child { border-bottom: 1px solid var(--line); }
.why-points > li:hover { background: rgb(255 255 255 / 0.03); }
.why-points h3 { font-size: var(--step-0); line-height: 1.3; margin: 0 0 var(--sp-1); }
.why-points p { margin: 0; color: var(--muted); }
.why-points .tags { margin-top: var(--sp-2); }
.why .venn .ln { transition: stroke .2s ease-out, stroke-width .2s ease-out; }
.why:has(#why-eng:hover, #why-eng:focus-within) .venn .c-l,
.why:has(#why-prod:hover, #why-prod:focus-within) .venn .c-r { stroke: var(--ink); stroke-width: 3; }
.why:has(#why-prod:hover, #why-eng:hover) .venn .lens { fill: rgb(252 200 3 / 0.10); }
.why:has(#why-meet:hover, #why-meet:focus-within) .venn .lens { fill: rgb(252 200 3 / 0.22); stroke-width: 4; }
.why:has(#why-meet:hover) .venn .hi { r: 9; }

/* ---- how I work: the diagram. Plain ordered list; the connectors are CSS and draw once on arrival */
.flow { margin-top: var(--sp-6); }
.flow-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); }
.flow-step { position: relative; padding: 0 0 var(--sp-5) var(--sp-7); min-width: 0; }
.flow-step:last-child { padding-bottom: 0; }
.flow-dot {
  position: absolute; left: 0; top: 0; z-index: 1; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; font: 500 var(--step--2)/1 var(--mono);
  color: var(--ink); background: var(--bg); border: 1px solid var(--control);
}
.flow-step:not(:last-child)::after {
  content: ""; position: absolute; left: 15px; top: 32px; bottom: 0; width: 2px;
  background: var(--accent); transform-origin: top;
}
.flow-name { font-size: var(--step-0); line-height: 1.3; margin: 0; min-height: 32px; display: flex; align-items: center; }
.flow-detail { margin: var(--sp-2) 0 0; color: var(--muted); font-size: var(--step--1); line-height: 1.55; max-width: 44ch; }
.flow-toggle {
  font: inherit; color: inherit; background: none; border: 0; padding: var(--sp-3) 0; margin: calc(-1 * var(--sp-3)) 0; cursor: pointer;
  display: inline-block; text-align: left;
}
.flow-toggle::after {
  content: ""; display: inline-block; vertical-align: middle; margin-left: var(--sp-3); width: 8px; height: 8px; margin-top: -4px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: rotate(45deg);
}
.flow-toggle[aria-expanded="true"]::after { transform: rotate(-135deg); margin-top: 4px; }
.flow-toggle:hover { color: var(--accent); }
.flow-step.is-open .flow-dot { border-color: var(--accent); color: var(--accent); }
/* steps 2 to 4 repeat: a dashed loop from dot 4 back to dot 2, with an arrowhead at dot 2. It sits on step 2.
   On a phone it runs down the left of the dots, its height set by site.js (--loop-h, dot 2 to dot 4); from 700px
   it arcs above dots 2 to 4, labelled. The dots sit on top, so the line shows only between them. */
.flow-loop {
  display: block; position: absolute; left: -14px; width: 30px; top: 16px; height: var(--loop-h, 200%);
  border: 2px dashed var(--muted); border-right: 0; border-radius: 12px 0 0 12px;
}
.flow-loop::before {
  content: ""; position: absolute; left: 3px; top: -9px; width: 0; height: 0; /* points right, into dot 2 */
  border-left: 11px solid var(--ink); border-top: 8px solid transparent; border-bottom: 8px solid transparent;
}
.flow-loop span { display: none; }
.flow-note { font: var(--meta-font); color: var(--muted); margin: var(--sp-5) 0 0; }
@media (min-width: 700px) {
  .flow { margin-top: var(--sp-8); }
  .flow-steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-5); }
  .flow-step { padding: var(--sp-7) 0 0; }
  .flow-step:not(:last-child)::after { left: 32px; top: 15px; bottom: auto; width: calc(100% + var(--sp-5) - 32px); height: 2px; transform-origin: left; }
  .flow-name { min-height: 0; align-self: start; }
  /* the steps share two rows (subgrid): when one name wraps onto two lines, every step's detail still starts at the same height */
  .flow-step { display: grid; grid-row: span 2; grid-template-rows: subgrid; row-gap: 0; }
  /* above the dots: from dot 4's centre up, across and down into dot 2, arrowhead pointing down at dot 2 */
  .flow-loop {
    top: -16px; bottom: auto; left: 16px; width: calc(200% + 2 * var(--sp-5)); height: 32px;
    border: 2px dashed var(--muted); border-bottom: 0; border-radius: 16px 16px 0 0;
  }
  .flow-loop::before {
    left: -9px; top: 5px; /* the tip touches the top of dot 2 */
    border-top: 11px solid var(--ink); border-left: 8px solid transparent; border-right: 8px solid transparent; border-bottom: 0;
  }
  .flow-loop span { display: block; position: absolute; left: 50%; top: -12px; transform: translateX(-50%); background: var(--bg); padding: 0 var(--sp-2); font: 400 var(--step--2)/1.6 var(--mono); color: var(--muted); white-space: nowrap; }
  .flow-loop span::before { content: "\2190\00a0"; color: var(--ink); } /* a left arrow in the label: the loop runs from 04 back to 02 */
}

/* ---- Services: numbered steps, what you get, what I work with */
.steps { list-style: none; counter-reset: step; margin: var(--sp-5) 0 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--sp-5); }
.steps li { counter-increment: step; border-top: 1px solid var(--line); padding: var(--sp-4) 0 var(--sp-5); }
.steps li::before { content: "0" counter(step); display: block; font: 400 var(--step--1)/1.5 var(--mono); color: var(--muted); margin-bottom: var(--sp-2); }
.steps strong { display: block; font-size: var(--step-0); font-weight: 600; margin-bottom: var(--sp-2); }
.steps p { margin: 0; color: var(--muted); }
/* side by side, the steps in a row share three rows (subgrid): number, name, detail. A name on two lines keeps every detail level */
@media (min-width: 700px) {
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps li { display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: 0; }
}
@media (min-width: 960px) { .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.svc-get { list-style: none; margin: var(--sp-5) 0 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--sp-6); }
.svc-get li { border-top: 1px solid var(--line); padding: var(--sp-4) 0 var(--sp-5); }
.svc-get h3 { font-size: var(--step-0); line-height: 1.35; font-weight: 600; margin: 0 0 var(--sp-2); letter-spacing: 0; }
.svc-get p { margin: 0; color: var(--muted); max-width: 46ch; text-wrap: pretty; }
@media (min-width: 700px) { .svc-get { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.svc-with-intro { margin: 0 0 var(--sp-4); max-width: var(--measure); }
.svc-with { margin: 0; max-width: var(--wide); }
.svc-with > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-2); padding: var(--sp-3) 0; border-top: 1px solid var(--line); }
.svc-with > div:last-child { border-bottom: 1px solid var(--line); }
.svc-with dt { font: 600 var(--step-0)/1.4 var(--sans); color: var(--ink); }
.svc-with dd { margin: 0; }
@media (min-width: 700px) {
  .svc-with > div { grid-template-columns: 11em minmax(0, 1fr); gap: 0 var(--sp-5); align-items: baseline; }
}
.svc-with-note { margin: var(--sp-4) 0 0; color: var(--muted); max-width: var(--measure); }

/* ---- contact: the closing call to action on inner pages, and the contact page */
.contact h2 { font-size: var(--step-3); letter-spacing: -0.02em; margin: 0 0 var(--sp-4); }
.contact > p, .contact .intro p { margin: 0 0 var(--sp-2); max-width: var(--measure); font-size: var(--step-1); line-height: 1.45; }
.contact .address { font-size: var(--step--1); color: var(--muted); margin-top: var(--sp-5); line-height: 1.6; }
.contact .address a { color: var(--ink); }
.contact .cta { gap: var(--btn-gap); }
@media (max-width: 699px) { .contact .btn.big { width: 100%; } }
.connect { text-align: center; }
.connect p { margin-left: auto; margin-right: auto; }
.connect .cta { justify-content: center; }

/* the home page's closing call to action: one panel, one button, the page's destination */
.closing { margin-top: var(--section); }
.closing-inner {
  background: var(--surface); border: 1px solid var(--line); border-top: 3px solid var(--accent); border-radius: var(--radius);
  padding: var(--sp-6) var(--sp-4);
}
@media (min-width: 700px) { .closing-inner { padding: var(--sp-7); } }
.closing h2 { font-size: var(--step-3); letter-spacing: -0.02em; margin: 0 0 var(--sp-4); }
.closing p { margin: 0; max-width: var(--measure); font-size: var(--step-1); line-height: 1.45; }
.closing .cta { margin-top: var(--sp-6); gap: var(--btn-gap); }
@media (max-width: 699px) { .closing .btn.big { width: 100%; } }

/* the contact page: a panel with the intro beside the form */
.contact-page { margin-top: var(--sp-7); }
.contact h1 { font-size: var(--step-3); letter-spacing: -0.02em; margin: 0 0 var(--sp-4); }
.contact-panel {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-6) var(--sp-4); display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6);
}
@media (min-width: 700px) { .contact-panel { padding: var(--sp-7); } }
@media (min-width: 900px) { .contact-panel { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--sp-7); } }
.contact .intro .address { font-size: var(--step--1); margin-top: var(--sp-5); }
.contact-meta { margin: var(--sp-4) 0 0; }
.next-step { border-top: 1px solid var(--line); margin-top: var(--sp-5); padding-top: var(--sp-4); }
.contact .intro .next-step p { font-size: var(--step-0); line-height: 1.55; }
.contact .intro .next-step .kicker { font-size: var(--step--1); margin-bottom: var(--sp-2); }
/* three short numbered lines, one idea each */
.next-steps { list-style: none; counter-reset: next; margin: 0; padding: 0; display: grid; gap: var(--sp-2); line-height: 1.45; }
.next-steps li { counter-increment: next; display: grid; grid-template-columns: 1.125rem minmax(0, 1fr); gap: var(--sp-2); }
.next-steps li::before { content: counter(next); font: 500 var(--step--1)/1.9 var(--mono); color: var(--accent); }

/* ---- Sumrise page */
.story-top { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); align-items: start; margin-top: var(--sp-6); }
@media (min-width: 900px) { .story-top { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); } }
.story-top p { margin: 0 0 var(--sp-4); }
.block { border-top: 1px solid var(--line); padding: var(--sp-5) 0 var(--sp-2); }
.block h3, .block h2 { font-size: var(--step-1); margin: 0 0 var(--sp-3); }
.block p { margin: 0 0 var(--sp-3); }
.block ul { margin: 0; padding-left: var(--sp-5); }
.block li { margin-bottom: var(--sp-2); }
.points { margin: 0; padding-left: var(--sp-5); }
.points li { margin-bottom: var(--sp-2); }
.shots { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6) var(--sp-5); }
@media (min-width: 700px) { .shots { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.shot { margin: 0; }
.shot img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--media-bg); }
.shot figcaption { font-size: var(--step--1); color: var(--muted); margin-top: var(--sp-3); }
/* Talking about it: the two posts and the podcast as one set of compact cards */
.sum-talks .card { margin: 0; }
.sum-talks .card .media { aspect-ratio: 16 / 9; }
.sum-talks .card .media img { object-position: center; }
.sum-talk:first-child .media img { object-position: center 30%; } /* keeps his slide in view */
.sum-talks h3 { font-size: var(--step-0); line-height: 1.35; }
.sum-talks .line { font-size: var(--step--1); line-height: 1.55; color: var(--muted); }
.sum-talks .go { margin-top: auto; padding: var(--sp-2) 0 0; }
.sum-talks a.card:hover .media img { filter: brightness(1.08); }
/* the podcast: the player sits where the others have their picture. Until pressed, Spotify's player is a play
   button (it sets cookies as it loads), with a plain link beneath */
.sum-pod .player { display: grid; place-items: center; align-content: center; gap: var(--sp-3); padding: var(--sp-4); aspect-ratio: 16 / 9; background: var(--media-bg); border-bottom: 1px solid var(--line); }
.sum-pod iframe { display: block; width: 100%; height: 102px; border: 0; border-radius: var(--radius-sm); }
.sum-pod .pod-play { position: static; text-decoration: none; }
.sum-pod .pod-note { margin: 0; font-size: var(--step--1); line-height: 1.5; color: var(--muted); text-align: center; }
.sum-pod .pod-note a { display: inline-block; padding: var(--sp-1) 0; } /* 24px target (WCAG 2.5.8) */
@media (min-width: 700px) and (max-width: 959px) {
  .sum-pod { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 0; }
  .sum-pod .player { aspect-ratio: auto; border-bottom: 0; border-right: 1px solid var(--line); }
}

/* ---- About page */
.me { text-align: center; padding-top: var(--sp-7); }
.me .portrait { width: 240px; height: 240px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 1px var(--line-strong); display: block; margin: 0 auto var(--sp-5); }
.me h1 { font-size: var(--step-3); letter-spacing: -0.02em; margin: 0 0 var(--sp-2); }
.me .tagline { font: var(--meta-font); color: var(--muted); margin: 0; }
@media (max-width: 699px) { .me .portrait { width: 200px; height: 200px; } }
.intro-text { text-align: left; margin-top: var(--sp-6); }
.intro-text p { margin: 0 0 var(--sp-4); }
.title { font-size: var(--step-2); letter-spacing: -0.02em; margin: 0 0 var(--sp-4); }
.principles { list-style: none; margin: var(--sp-5) 0 0; padding: 0; }
.principles li { border-top: 1px solid var(--line); padding: var(--sp-5) 0; }
.principles h3 { font-size: var(--step-1); margin-bottom: var(--sp-3); }
.principles blockquote { margin: 0 0 var(--sp-3); padding-left: var(--sp-4); border-left: 2px solid var(--accent); font-size: var(--step-1); line-height: 1.45; }
.principles blockquote p { margin: 0; }
/* on a phone a quote wraps into even lines, so none ends on one word ("understanding."); wider, the quotes keep their natural wrap */
@media (max-width: 559px) { .principles blockquote p { text-wrap: balance; } }
.principles p { margin: 0 0 var(--sp-2); }
.principles .from { margin: 0; font-size: var(--step--1); color: var(--muted); }
.mini p { color: var(--muted); font-size: var(--step--1); }
.also { margin: 0; padding-left: var(--sp-5); }
.also li { margin-bottom: var(--sp-2); }
.talk { margin: var(--sp-5) 0 0; }

/* ---- article */
.post-head { padding-top: var(--sp-7); }
.post-head h1 { font-size: var(--step-3); line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 var(--sp-4); }
.post-subtitle { font-size: var(--step-1); color: var(--muted); margin: 0 0 var(--sp-4); line-height: 1.45; }
.post-head .post-meta { margin-bottom: var(--sp-6); }
.post-hero { display: block; width: 100%; height: auto; border-radius: var(--radius); border: 1px solid var(--line); margin: 0 0 var(--sp-7); } /* the whole cover, uncropped: the covers carry words at the top or the bottom */
.prose { font-size: var(--step-0); line-height: 1.7; }
.prose > * { margin: 0 0 1.1em; }
.prose h2 { font-size: var(--step-2); line-height: 1.25; letter-spacing: -0.01em; margin: 2em 0 0.6em; }
.prose h3 { font-size: var(--step-1); margin: 1.6em 0 0.5em; }
.prose h4 { font-size: var(--step-0); margin: 1.4em 0 0.4em; }
.prose h2 strong, .prose h3 strong, .prose h4 strong { font-weight: inherit; } /* Hashnode wraps headings in <strong>; keep them at 600 */
.prose p { margin: 0 0 1.1em; }
.prose a { text-decoration: underline; }
.prose a:visited { color: var(--secondary); } /* a post is long and link-heavy: visited links step back to periwinkle */
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li { margin: 0 0 0.45em; }
.prose li > p { margin: 0; }
.prose li ul { margin-top: 0.4em; }
.prose blockquote { margin: 1.4em 0; padding: 2px 0 2px var(--sp-5); border-left: 2px solid var(--accent); color: var(--ink); }
.prose blockquote p:last-child, .prose blockquote ol:last-child { margin-bottom: 0; }
.prose code { font: 0.9em/1.4 var(--mono); background: rgb(255 255 255 / 0.06); border-radius: 5px; padding: 1px 5px; }
.prose pre { background: var(--code-bg); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: var(--sp-4); overflow-x: auto; line-height: 1.5; }
.prose pre code { background: none; padding: 0; font-size: var(--step--1); }
.prose img { display: block; margin: 1.6em auto; border-radius: var(--radius-sm); background: #fff; }
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 2em 0; }
/* a wide table scrolls sideways in its own box. A fade on the side with more to see says it scrolls
   (site.js sets .more-left / .more-right on .table-box as the reader scrolls; without the script, no fade) */
.table-box { position: relative; margin: 1.4em 0; }
.table-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.table-box::before, .table-box::after {
  content: ""; position: absolute; top: 1px; bottom: 1px; width: var(--sp-7); pointer-events: none; opacity: 0;
  transition: opacity .15s ease-out;
}
.table-box::before { left: 1px; border-radius: var(--radius-sm) 0 0 var(--radius-sm); background: linear-gradient(to right, var(--bg), transparent); }
.table-box::after { right: 1px; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: linear-gradient(to left, var(--bg), transparent); }
.table-box.more-left::before, .table-box.more-right::after { opacity: 1; }
.prose table { border-collapse: collapse; width: 100%; font-size: var(--step--1); line-height: 1.5; min-width: 520px; }
.prose td, .prose th { border-bottom: 1px solid var(--line); padding: var(--sp-3); text-align: left; vertical-align: top; }
.prose thead td, .prose thead th { font-weight: 600; background: var(--surface); }
.prose tr:last-child td { border-bottom: 0; }
/* the post's foot: the nudge, then the pager */
.post-foot { padding-top: var(--sp-4); }
.nudge { border-top: 1px solid var(--line); padding-top: var(--sp-5); margin: var(--sp-5) 0 0; }
.nudge p { margin: 0 0 var(--sp-4); font-size: var(--step-1); line-height: 1.45; }
@media (max-width: 699px) { .nudge .btn { width: 100%; } } /* full width on a phone, like the other closing buttons */

/* ============================================================ 6. motion */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  @view-transition { navigation: auto; }
  a, .btn, .card, .post-card, .chip, .flow-toggle, .form input, .form select, .form textarea { transition: color .15s ease-out, background-color .15s ease-out, border-color .15s ease-out; }
  .card .media img, .post-cover { transition: transform .2s ease-out; }
  a.card:hover .media img, .post-card:hover .post-cover { transform: scale(1.02); }

  /* project cards: the picture lifts, the title fades to yellow */
  .card.project .media img, .card.project .media svg { transition: transform .25s ease-out, filter .2s ease-out; }
  .card.project:hover .media img, .card.project:focus-within .media img,
  .card.project:hover .media svg, .card.project:focus-within .media svg { transform: scale(var(--lift)); }
  .card.project h3 { transition: color .15s ease-out; }

  /* an offer's drawing rises a little on hover */
  .offer .way-pic { transition: transform .25s ease-out; }
  .offer:hover .way-pic, .offer:focus-within .way-pic { transform: translateY(-4px) scale(1.03); }

  /* the diagram draws once when a third of it is in view; html.js is set in <head>, so without JS nothing is hidden */
  .js .flow .flow-step { opacity: 0; translate: 0 6px; transition: opacity .3s ease-out, translate .3s ease-out; transition-delay: calc(var(--i) * 150ms); }
  .js .flow .flow-step:not(:last-child)::after { scale: 1 0; transition: scale .3s ease-out; transition-delay: calc(var(--i) * 150ms + 150ms); }
  .js .flow .flow-loop { opacity: 0; transition: opacity .4s ease-out .75s; }
  .js .flow.is-in .flow-step { opacity: 1; translate: 0 0; }
  .js .flow.is-in .flow-step:not(:last-child)::after { scale: 1 1; }
  .js .flow.is-in .flow-loop { opacity: 1; }
  @media (min-width: 700px) {
    .js .flow .flow-step:not(:last-child)::after { scale: 0 1; }
    .js .flow.is-in .flow-step:not(:last-child)::after { scale: 1 1; }
  }

  /* the drawings: lines draw, then the yellow part arrives. Once, when a third is in view.
     Only paths draw (pathLength="1"); rects and circles fade, which every browser handles the same way.
     Opacity too, or a round line cap shows as a dot before the line draws. */
  .js .draw path.ln { stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; transition: stroke-dashoffset .6s ease-out, opacity .1s linear; transition-delay: calc(var(--d, 0) * 1ms); }
  .js .draw.is-in path.ln { opacity: 1; }
  .js .draw :is(rect.ln, .dot, .dash, .x, .vt) { opacity: 0; transition: opacity .4s ease-out; transition-delay: calc(var(--d, 0) * 1ms + 150ms); }
  .js .draw :is(.hi, .tick) { opacity: 0; transition: opacity .3s ease-out, translate .4s ease-out; transition-delay: calc(var(--d, 0) * 1ms + 650ms); }
  .js .draw .slot { translate: 10px -14px; }
  .js .draw .venn .lens { --d: 600; }  /* the overlap draws after the two circles */
  .js .draw .venn .hi { --d: 500; }
  .js .draw li:nth-child(2) { --d: 200; }
  .js .draw li:nth-child(3) { --d: 400; }
  .js .draw li:nth-child(4) { --d: 600; }
  .js .draw li:nth-child(5) { --d: 800; }
  .js .draw.is-in path.ln { stroke-dashoffset: 0; }
  .js .draw.is-in :is(rect.ln, .dot, .dash, .x, .vt, .hi, .tick) { opacity: 1; }
  .js .draw.is-in .slot { translate: 0 0; }

  /* a section-end cue's arrow nudges down twice as the reader scrolls it into view, then never again. */
  @keyframes nudge { 0%, 100% { translate: 0 0; } 40% { translate: 0 5px; } }
  .js .next-cue.is-in .go::after { animation: nudge .7s ease-in-out .2s 2; }

  /* the hero's "See my projects" arrow. One small bounce (4px down, back, a 1.5px settle) each time the
     script adds is-nudging: only while the visitor sits still at the top, three times at most (site.js 1c). */
  @keyframes hint { 0%, 100% { translate: 0 0; } 30% { translate: 0 4px; } 55% { translate: 0 0; } 75% { translate: 0 1.5px; } }
  .js .hero .go.down.is-nudging::after { animation: hint .9s ease-in-out 1; }

  /* the floating cue rises 6px as it fades in; under reduced motion it only fades */
  .float-cue { translate: 0 6px; transition: opacity .2s ease-out, translate .2s ease-out, visibility 0s linear .2s; }
  .float-cue.is-on { translate: 0 0; transition: opacity .2s ease-out, translate .2s ease-out; }
}

/* 404: the 4 0 4 keycap drawing above the heading */
.nf-pic { display: block; width: 196px; height: auto; overflow: visible; margin: 0 0 var(--sp-5) -4px; }
.nf-pic .key { fill: var(--bg); }
.nf-pic .kt { fill: var(--ink); font: 500 22px/1 var(--sans); }
.nf-pic .kt.on { fill: var(--accent-ink); }

/* #38: a checkbox field (the contact form's free call), box then label on one line */
.field.check { grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--sp-3); }
.form .field.check input { width: 22px; height: 22px; min-height: 0; padding: 0; accent-color: var(--accent); }
.field.check label { font-weight: 400; }
/* #38: a kicker between two offer lists ("Ways to start", then "What I can build") */
.ways + .kicker { margin-top: var(--sp-7); }
.label + .kicker, .page-head + .svc-offers > .kicker:first-child { margin-top: var(--sp-5); }

/* the ask's second line: smaller and quieter, under the question (#38) */
.ways-cta.sub { margin-top: var(--sp-1); font: 400 var(--step--1)/1.5 var(--sans); color: var(--muted); }

/* about-d5: a centred row of chips (modifier), and its gap under the About intro */
.chips.center { justify-content: center; }
.me .chips { margin-top: var(--sp-6); }

/* about ring */
.me .portrait { box-shadow: 0 0 0 2px rgb(255 255 255 / 0.9); }

/* ---- About v2 (#23, 20 Oct 2026): small additions, modifiers where a component exists */
.section-sub { margin: var(--sp-3) 0 0; color: var(--muted); max-width: 52ch; }
.section-title + .steps, .about-page .section-title + .why, .about-page .section-sub + .why, .section-title + .grid,
.section-title + .cols, .section-title + .photo-grid { margin-top: var(--sp-6); }
.steps + .section-sub { margin-top: var(--sp-5); }
.fit-tags { margin-top: var(--sp-4); }
h3.kicker { font-weight: 500; letter-spacing: 0; margin: var(--sp-6) 0 var(--sp-3); }
.sub-label { font-size: var(--step-1); margin: var(--sp-8) 0 var(--sp-4); }
/* story: a timeline of short lines, the last dot yellow (now) */
.timeline { list-style: none; margin: var(--sp-5) 0 0; padding: 0; max-width: var(--measure); }
.timeline li { position: relative; padding: 0 0 var(--sp-5) var(--sp-6); }
.timeline li::before { content: ""; position: absolute; left: 3px; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--bg); border: 2px solid var(--control); z-index: 1; }
.timeline li:not(:last-child)::after { content: ""; position: absolute; left: 8px; top: 12px; bottom: -6px; width: 2px; background: var(--line-strong); }
.timeline li.now::before { background: var(--accent); border-color: var(--accent); }
.timeline .when { display: block; font: var(--meta-font); color: var(--muted); margin-bottom: 2px; }
.timeline p { margin: 0; }
.story-me { margin-top: var(--sp-6); padding-top: var(--sp-5); border-top: 1px solid var(--line); max-width: var(--measure); }
.story-me .lede { margin: 0; font-weight: 500; }
.story-me .offer-chips { margin: 0; }
/* how I think: four angles drawing, and the quotes two by two */
.angles .ln { stroke-width: 1.5; }
@media (min-width: 700px) { .principles.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-6); } }
.principles.pair { margin-top: 0; }
.principles.pair blockquote { font-size: var(--step-0); }
/* who I work with */
.not-fit li { color: var(--muted); }
.fit-cols h3.kicker { margin-top: 0; }
@media (max-width: 767px) { .fit-cols > div + div { margin-top: var(--sp-6); } }
/* How I communicate (About, Who I work with): a small, light part under the fit lists */
.fit-talk { max-width: var(--measure); }
.fit-talk .also-list li { font-size: var(--step--1); line-height: 1.55; }
/* highlights: a card with no picture, a big number line */
.card.highlight .stat { font: 600 var(--step-2)/1.15 var(--sans); letter-spacing: -0.02em; margin: var(--sp-2) 0 var(--sp-1); }
.card.highlight .stat span { display: block; font: 400 var(--step--1)/1.5 var(--sans); color: var(--muted); letter-spacing: 0; }
.card.highlight .stat ~ p { color: var(--ink); opacity: .86; font-size: var(--step--1); line-height: 1.55; }
.card.highlight .go { margin-top: auto; padding: var(--sp-2) 0 0; }
.talks-cols { align-items: start; }
.talks-cols .talk { margin: 0; }
@media (max-width: 767px) { .talks-cols .also-list { margin-top: var(--sp-5); } }
/* outside work: a photo grid, the first photo big */
.photo-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5) var(--sp-3); }
.photo-grid figure { margin: 0; display: flex; flex-direction: column; min-width: 0; }
.photo-grid img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--line); background: var(--media-bg); }
.photo-grid figcaption { font-size: var(--step--1); line-height: 1.45; color: var(--muted); margin-top: var(--sp-2); }
.photo-grid .lead { grid-column: 1 / -1; }
.photo-grid .lead img { aspect-ratio: 3 / 2; }
.photo-grid .lead figcaption { color: var(--ink); font-size: var(--step-0); }
@media (min-width: 760px) {
  .photo-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-5) var(--sp-4); }
  .photo-grid .lead { grid-column: span 2; grid-row: span 2; }
  .photo-grid .lead img { aspect-ratio: auto; flex: 1 1 0; min-height: 0; height: 0; }
}
h3.kicker.more-me { margin-top: var(--sp-6); }
.chips.song { margin-top: var(--sp-3); }
/* things I've built: five cards, three to a row on a wide screen */
@media (min-width: 1024px) { .mini-projects.three { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---- About v2b (20 Oct 2026): a part that carries on the section above (no hairline, no number);
   quotes as cards; the makes as a plain row of the photo grid */
.follow-on { margin-top: 0; }
.follow-on > .sub-label:first-child { margin-top: var(--sp-8); }
@media (min-width: 900px) { .grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: auto; } }
.card.quote { margin: 0; }
.card.quote blockquote { margin: 0; padding-left: var(--sp-4); border-left: 2px solid var(--accent); }
.card.quote blockquote p { font-size: var(--step-0); line-height: 1.55; }
.card.quote figcaption { margin-top: auto; padding-top: var(--sp-3); font-size: var(--step--1); line-height: 1.5; color: var(--muted); }
.card.quote figcaption strong { display: block; color: var(--ink); font-weight: 600; font-size: var(--step-0); }
.photo-grid.makes figcaption a { display: inline-block; padding: 2px 0; }

/* ---- About v2c (20 Oct 2026): quote cards with an initials circle (a photo later); a quote and a note inside a highlight card */
.card.quote figcaption { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 0 var(--sp-3); align-items: center; }
.card.quote .initials {
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  background: var(--secondary-bg); color: var(--secondary); font: 500 var(--step--2)/1 var(--mono); letter-spacing: 0.04em;
}
.card.highlight .card-quote { display: block; margin-top: var(--sp-3); padding-left: var(--sp-4); border-left: 2px solid var(--accent); font-size: var(--step-0); line-height: 1.45; color: var(--ink); }
.card.highlight .card-quote .by { display: block; margin-top: var(--sp-1); font-size: var(--step--1); color: var(--muted); }
.card.highlight .advisors { display: block; font-size: var(--step--1); line-height: 1.55; color: var(--muted); margin-top: var(--sp-2); }
@media (min-width: 1024px) { .grid.loose { grid-auto-rows: auto; } } /* each row as tall as its own cards */

/* ---- About v3 (20 Oct 2026): track record as preview cards; a quote inside any card; experience four across;
   blog posts as mini cards with a quote; the story's turning point with more lines */
.card .card-quote { display: block; padding-left: var(--sp-3); border-left: 2px solid var(--accent); font-size: var(--step--1); line-height: 1.5; color: var(--ink); }
.card .card-quote .by { display: block; margin-top: var(--sp-1); color: var(--muted); }
.card.preview h3 { font-size: var(--step-0); line-height: 1.35; }
.card.preview .line { font-size: var(--step--1); line-height: 1.55; color: var(--muted); }
.card.preview .go { margin-top: auto; padding: var(--sp-2) 0 0; align-self: flex-start; }
.card.preview .card-quote { margin-top: var(--sp-1); }
.grid.talks > .card { margin: 0; }
.card .video { border: 0; border-radius: 0; border-bottom: 1px solid var(--line); }
@media (min-width: 1024px) { .grid.talks, .grid.four { grid-auto-rows: auto; } }
@media (min-width: 700px) and (max-width: 959px) {
  .sum-pod.in-grid { grid-column: auto; display: flex; }
  .sum-pod.in-grid .player { aspect-ratio: 16 / 9; border-right: 0; border-bottom: 1px solid var(--line); }
}
@media (min-width: 1024px) { .grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.talk-tags { margin-top: var(--sp-4); }
h3.kicker.first { margin-top: var(--sp-5); }
.mini-projects.posts .card-quote { margin-top: var(--sp-1); }
.timeline p.more { margin-top: var(--sp-2); color: var(--muted); }
.make-song { margin: var(--sp-5) 0 0; }
.make-song a { display: inline-block; padding: var(--sp-1) 0; }

/* ---- About v4 (20 Oct 2026): the home page's offers and flow inside What I build; the stack with a press-to-open full list;
   one proof section of cards */
.section-title + .ways { margin-top: var(--sp-6); }
h3.kicker.build-kicker { margin-top: var(--sp-8); }
.build-kicker + .flow { margin-top: var(--sp-4); }
@media (min-width: 700px) { .build-kicker + .flow { margin-top: var(--sp-5); } }
.stack-sub { margin-top: 0; }
.stack { margin-top: var(--sp-4); }
.stack-label { margin: 0 0 var(--sp-2); font-weight: 600; }
.stack-more { margin-top: var(--sp-4); }
.stack-more > summary.chip { cursor: pointer; list-style: none; user-select: none; }
.stack-more > summary.chip::-webkit-details-marker { display: none; }
.stack-more > summary.chip::after { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); margin: -3px 0 0 4px; transition: transform .2s ease-out; }
.stack-more[open] > summary.chip::after { transform: rotate(-135deg); margin-top: 3px; }
.stack-more > summary.chip:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.stack-more > summary.chip:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.stack-more .when-open, .stack-more[open] .when-closed { display: none; }
.stack-more[open] .when-open { display: inline; }
.stack-groups { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5) var(--sp-6); margin-top: var(--sp-5); padding: var(--sp-5); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
@media (min-width: 700px) { .stack-groups { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .stack-groups { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.stack-group h4.kicker { margin: 0 0 var(--sp-2); font-weight: 500; }
@media (prefers-reduced-motion: no-preference) {
  .stack-more[open] .stack-groups { animation: stack-in .25s ease-out; }
  @keyframes stack-in { from { opacity: 0; translate: 0 -4px; } to { opacity: 1; translate: 0 0; } }
}
h3.kicker.proof-kicker { margin-top: var(--sp-8); }
@media (min-width: 1024px) { .grid.talks { grid-template-columns: repeat(4, minmax(0, 1fr)); } } /* v4: talks four across, like the projects above */
.grid.talks .card .media { aspect-ratio: 16 / 9; } /* v4: every talk picture the same shape as the video and the player */
.grid.talks .card .media img { object-position: center; }

/* ---- About v5 (20 Oct 2026): the offers at v3's size with v4's drawings; skills as cards that open; experience as short lines
   with an icon; more talks from my decks */
.ways.small { gap: var(--sp-5); }
.ways.small li { grid-template-columns: 72px minmax(0, 1fr); gap: 0 var(--sp-4); }
.ways.small h3 { font-size: var(--step-0); }
.ways.small p { font-size: var(--step--1); }
@media (min-width: 700px) {
  .ways.small { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: auto; gap: var(--sp-6); }
  .ways.small li { grid-template-columns: 96px minmax(0, 1fr); }
}
@media (min-width: 1000px) {
  .ways.small { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-5); }
  .ways.small li { grid-template-columns: minmax(0, 1fr); }
  .ways.small .way-pic { grid-row: auto; max-width: 132px; margin-bottom: var(--sp-3); }
}
.ways.small + .section-sub { margin-top: var(--sp-6); }
.skills { margin-top: var(--sp-6); }
.skills h3.kicker { margin: 0 0 var(--sp-3); }
.skills-core { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-7); }
.skills-core .stack-label { margin: 0 0 var(--sp-2); font: 500 var(--step--1)/1.4 var(--sans); color: var(--muted); }
.skills-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-2) var(--sp-4); margin-top: var(--sp-6); }
.skills-head h3.kicker { margin: 0; }
button.chip { cursor: pointer; }
.chip[hidden] { display: none; }
button.chip:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
button.chip:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.skill-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); margin-top: var(--sp-4); align-items: start; }
@media (min-width: 600px) { .skill-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .skill-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.card.skill { display: block; padding: var(--sp-4); }
.card.skill:hover, .card.skill[open] { border-color: var(--line-strong); }
.card.skill > summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2) var(--sp-3); cursor: pointer; list-style: none; border-radius: var(--radius-sm); }
.card.skill > summary::-webkit-details-marker { display: none; }
.card.skill > summary:focus-visible { outline: 3px solid var(--accent); outline-offset: 6px; }
.skill-h { flex: 1 1 auto; font-weight: 600; transition: color .2s ease-out; }
.card.skill > summary:hover .skill-h { color: var(--accent); }
.skill-more { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; font: var(--meta-font); color: var(--accent); }
.skill-more::after { content: ""; width: 6px; height: 6px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); margin-top: -3px; transition: transform .2s ease-out; }
.card.skill[open] .skill-more::after { transform: rotate(-135deg); margin-top: 3px; }
.card.skill .when-open, .card.skill[open] .when-closed { display: none; }
.card.skill[open] .when-open { display: inline; }
.card.skill > summary .tags { flex: 1 0 100%; margin: 0; }
.card.skill > .tags { margin-top: var(--tag-gap); }
@media (prefers-reduced-motion: no-preference) {
  .card.skill[open] > .tags { animation: stack-in .25s ease-out; }
}
.pasts { margin-top: var(--sp-4); }
@media (min-width: 1024px) { .grid.four.pasts { grid-auto-rows: auto; } }
.card.past .past-icon { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--wash); color: var(--accent); margin-bottom: var(--sp-1); }
.card.past .past-icon svg { width: 22px; height: 22px; }
.card.past h3 { font-size: var(--step-1); line-height: 1.25; margin: 0; }
.card.past p:not(.kicker) { font-size: var(--step--1); line-height: 1.55; color: var(--ink); opacity: .86; }
.card.past .card-quote { margin-top: var(--sp-1); }
.card.past .go { margin-top: auto; padding-top: var(--sp-2); }
.pasts-more { margin: var(--sp-4) 0 0; }
.talk-decks { margin-top: var(--sp-4); }
.talk-decks .media { aspect-ratio: 16 / 9; }
.talk-decks .media img { object-position: center; }
.card .media.deck-cover { display: grid; place-items: center; padding: var(--sp-4); background: var(--deck); color: #fff; text-align: center; font: 600 var(--step-2)/1.1 var(--sans); letter-spacing: -0.02em; text-wrap: balance; }
.talk-decks .card h3 { font-size: var(--step-0); line-height: 1.35; }

/* ---- About v6 (20 Oct 2026, #23): his notes on v5 */
/* 01: skill cards line up. The count and arrow sit top right in every card; with every card open ("Open all"), each card in a
   row is as tall as the tallest, so the grid reads as tidy rows, not a ragged edge. */
.card.skill > summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-2) var(--sp-3); }
.card.skill > summary .tags { grid-column: 1 / -1; }
.skill-cards.is-all-open { align-items: stretch; }
.skills-core { gap: var(--sp-4) var(--sp-6); }
/* 03 and 04: a statement line that may run longer (.long: three even lines on a wide screen) or should sit on one line (.wide) */
.section-title.long { max-width: 46ch; }
.section-title.wide { max-width: none; }
.about-page .why-points .tags { margin-top: var(--sp-3); } /* the home page keeps its own gap */
/* 04: shorter rows, and a reason under a line in a smaller, quieter size */
.also-list.compact li { padding-top: var(--sp-2); padding-bottom: var(--sp-2); }
.also-list.ticks.compact li::before { top: calc(var(--sp-2) + 0.5em); }
.also-list.why-under strong { display: block; font-weight: 500; color: var(--ink); }
.also-list.why-under span { display: block; margin-top: 2px; font-size: var(--step--1); line-height: 1.5; color: var(--muted); }
/* 05: NASA Space Apps, a wide card in Things I've built; a picture centred in its crop */
.card .media.center img { object-position: center; }
@media (min-width: 1024px) { .card.wide .media.center img { object-position: center; } }
.mini-projects .card.wide h3 { font-size: var(--step-1); }
/* 05: talks a little tighter on a wide screen */
@media (min-width: 1024px) {
  .grid.talks { gap: var(--sp-4); }
  .grid.talks .card .body { padding: var(--sp-3) var(--sp-4) var(--sp-4); gap: var(--sp-1); }
  .grid.talks .card h3 { font-size: var(--step--1); line-height: 1.4; font-weight: 600; }
  .grid.talks .card .card-quote, .grid.talks .card .line { font-size: var(--step--2); line-height: 1.5; }
}
/* the quote's name line with a small round photo */
.card .card-quote .by { display: flex; align-items: center; gap: var(--sp-2); }
.by-face { width: 28px; height: 28px; flex: none; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 1px var(--line-strong); }
/* the podcast card: a drawn sound wave behind the play button; the link to Spotify at the card's foot */
.pod-card .player { position: relative; background: linear-gradient(135deg, #1d2a48, #0c1222); }
.pod-wave { width: 78%; max-width: 260px; height: auto; fill: var(--accent); opacity: .55; }
.pod-card .player:has(iframe) .pod-wave { display: none; }
.pod-card .go { margin-top: auto; padding-top: var(--sp-2); align-self: flex-start; }
/* 06: Before this. Sumrise takes the full row with its quote beside it; the other three sit in one row, as tall as each other */
@media (min-width: 768px) { .grid.pasts { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: auto; } }
.card.past.feature { grid-column: 1 / -1; }
.card.past.feature .body { display: grid; gap: var(--sp-4); }
.past-main { display: flex; flex-direction: column; gap: var(--card-gap); }
.card.past.feature .card-quote { font-size: var(--step-0); line-height: 1.5; padding-left: var(--sp-4); }
@media (min-width: 768px) {
  .card.past.feature .body { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: var(--sp-7); align-items: center; }
}
/* 07: the testimonial carousel (component: docs/components.md). A row that scrolls sideways one card at a time (scroll snap);
   site.js adds Previous and Next (looping) and the count. 1 card on a phone, 2 from 700px, 3 from 1000px. */
.carousel { position: relative; }
.carousel-track {
  position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; gap: var(--sp-4);
  overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; border-radius: var(--radius);
}
.js .carousel-track { scrollbar-width: none; }
.js .carousel-track::-webkit-scrollbar { display: none; }
.carousel-track > * { scroll-snap-align: start; }
.carousel-track:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; }
@media (min-width: 700px) { .carousel-track { grid-auto-columns: calc((100% - var(--sp-4)) / 2); } }
@media (min-width: 1000px) { .carousel-track { grid-auto-columns: calc((100% - 2 * var(--sp-4)) / 3); } }
.carousel-nav { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-3); margin-top: var(--sp-4); }
.carousel-btn {
  width: 44px; height: 44px; flex: none; display: grid; place-items: center; cursor: pointer; padding: 0;
  border-radius: var(--radius-pill); border: 1px solid var(--line-strong); background: var(--wash); color: var(--accent);
}
.carousel-btn:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.carousel-btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.carousel-btn svg { width: 20px; height: 20px; }
.carousel-count { margin: 0; min-width: 10ch; text-align: center; font: var(--meta-font); color: var(--muted); }
.carousel .card.quote .body { gap: var(--sp-4); }
.carousel .card.quote blockquote p { margin: 0; }
.card.quote figcaption { grid-template-columns: 48px minmax(0, 1fr); }
.card.quote .face { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 1px var(--line-strong); }
/* 08: every caption one size; the first line short, the rest behind a small More */
.photo-grid .lead figcaption { color: var(--muted); font-size: var(--step--1); }
.cap-more { margin-top: 2px; }
.cap-more > summary {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 0; cursor: pointer; list-style: none;
  font: var(--meta-font); color: var(--accent); border-radius: 4px;
}
.cap-more > summary::-webkit-details-marker { display: none; }
.cap-more > summary::after { content: ""; width: 6px; height: 6px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); margin-top: -3px; transition: transform .2s ease-out; }
.cap-more[open] > summary::after { transform: rotate(-135deg); margin-top: 3px; }
.cap-more .when-open, .cap-more[open] .when-closed { display: none; }
.cap-more[open] .when-open { display: inline; }
.cap-more > summary:hover { color: var(--accent-hover); }
.cap-more p { margin: var(--sp-1) 0 0; }
.cap-more a { display: inline-block; padding: 2px 0; }
/* 08: Things I make, three across on a wider screen, the song a double-width tile */
.make-sub { margin: 0 0 var(--sp-5); }
.photo-grid .song { grid-column: 1 / -1; }
.photo-grid .song .video { border-radius: var(--radius); }
@media (min-width: 760px) {
  .photo-grid.makes { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .photo-grid .song { grid-column: span 2; }
  .photo-grid .song .video { aspect-ratio: auto; flex: 1 1 0; min-height: 0; }
}
/* 09: a short personal line before the ask */
.closing-thanks { margin: 0 0 var(--sp-5); color: var(--muted); font-size: var(--step-0); max-width: var(--measure); }
.closing .closing-thanks { font-size: var(--step-0); }

/* ---- About v7 (20 Oct 2026, #23): his notes on v6 */
/* 01: Show all skills, a toggle: its words say what it does, a chevron turns when open */
.chip.toggle::after { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); margin: -3px 0 0 4px; transition: transform .2s ease-out; }
.chip.toggle[aria-expanded="true"]::after { transform: rotate(-135deg); margin-top: 3px; }
/* 01: core skills, one row each: the label, then its tags */
.skills-core.rows { display: grid; gap: var(--sp-3); }
.skills-core.rows > div { display: grid; gap: var(--sp-2); }
.skills-core.rows .stack-label { margin: 0; }
@media (min-width: 600px) {
  .skills-core.rows > div { grid-template-columns: 11rem minmax(0, 1fr); align-items: center; gap: var(--sp-4); }
}
/* 02: Now, three things at one level */
.timeline .now-list { list-style: none; margin: var(--sp-1) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.timeline .now-list > li { position: relative; padding: 0 0 0 var(--sp-4); }
.timeline .now-list > li::before { content: ""; position: absolute; left: 0; top: .72em; width: 8px; height: 2px; border: 0; border-radius: 1px; background: var(--accent); z-index: auto; }
.timeline .now-list > li::after { content: none; }
.timeline .now-list strong { font-weight: 600; }
/* 03: the lenses as a plain line */
.why-points p.lenses { margin-top: var(--sp-2); color: var(--muted); }
/* 05: Things I've built. Sumrise across the row (its still beside the words on a wider screen); the four projects; NASA slim */
.mini-projects.built .card.feature, .mini-projects.built .card.slim { grid-column: 1 / -1; }
.mini-projects.built .card.feature .card-quote { margin-top: var(--sp-1); }
.mini-projects.built .card .go { margin-top: auto; padding-top: var(--sp-2); }
.mini-projects.built .card.feature h3 { font-size: var(--step-1); }
@media (min-width: 768px) {
  .mini-projects.built .card.feature { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  .mini-projects.built .card.feature .media { aspect-ratio: 16 / 9; width: 100%; height: auto; align-self: stretch; position: relative; border-bottom: 0; border-right: 1px solid var(--line); }
  .mini-projects.built .card.feature .media img { position: absolute; inset: 0; object-position: left center; }
  .mini-projects.built .card.feature .body { justify-content: center; padding: var(--sp-5) var(--sp-6); }
}
.mini-projects.built .card.slim { display: grid; grid-template-columns: 96px minmax(0, 1fr); align-items: start; }
.mini-projects.built .card.slim .media { aspect-ratio: 1; border-bottom: 0; border-right: 1px solid var(--line); height: 100%; }
.mini-projects.built .card.slim .body { padding: var(--sp-3) var(--sp-4); }
.mini-projects.built .card.slim h3 { font-size: var(--step-0); }
.mini-projects.built .card.slim p:not(.kicker) { font-size: var(--step--1); line-height: 1.5; }
@media (min-width: 600px) { .mini-projects.built .card.slim { grid-template-columns: 180px minmax(0, 1fr); } }
@media (min-width: 1024px) {
  .mini-projects.built .card.slim { grid-template-columns: 220px minmax(0, 1fr); }
  .mini-projects.built .card.slim .body { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--sp-6); align-content: center; }
  .mini-projects.built .card.slim .body > * { grid-column: 1; }
  .mini-projects.built .card.slim .body > .tags, .mini-projects.built .card.slim .body > .go { grid-column: 2; justify-self: end; }
  .mini-projects.built .card.slim .body > .tags { grid-row: 1; margin: 0; }
  .mini-projects.built .card.slim .body > .go { grid-row: 3; align-self: end; margin: 0; padding: 0; }
}
/* 05: talks, every card the same rhythm: picture, kicker, title, one line, the link at the foot */
.grid.talks .card .line { margin: 0; }
/* 07: the carousel's arrows either side of the cards (site.js adds .has-nav); the count for screen readers only */
.carousel.has-nav { display: grid; grid-template-columns: 40px minmax(0, 1fr) 40px; align-items: center; gap: var(--sp-2); }
.carousel.has-nav .carousel-track { grid-column: 2; grid-row: 1; }
.carousel.has-nav .carousel-nav { display: contents; }
.carousel.has-nav [data-prev] { grid-column: 1; grid-row: 1; }
.carousel.has-nav [data-next] { grid-column: 3; grid-row: 1; }
.carousel.has-nav .carousel-btn { width: 40px; height: 40px; }
@media (min-width: 700px) {
  .carousel.has-nav { grid-template-columns: 44px minmax(0, 1fr) 44px; gap: var(--sp-4); }
  .carousel.has-nav .carousel-btn { width: 44px; height: 44px; }
}
/* 08: Things I make, five equal squares: the song is one of them. 2 across on a phone, 3 from 760px, 5 from 1100px */
.photo-grid.makes.five .song { grid-column: auto; }
@media (min-width: 1100px) { .photo-grid.makes.five { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.tile-video { position: relative; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius); border: 1px solid var(--line); background: var(--media-bg); }
.tile-video .yt, .tile-video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.photo-grid .tile-video img { width: 100%; height: 100%; aspect-ratio: auto; object-fit: cover; object-position: 28% center; border: 0; border-radius: 0; }
.tile-video .play.icon { left: auto; right: var(--sp-2); bottom: var(--sp-2); width: 44px; height: 44px; min-height: 0; padding: 0; justify-content: center; }
.tile-video .play.icon svg { margin-left: 2px; }
.tile-video .yt:hover .play, .tile-video .yt:focus-visible .play { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.tile-video .yt:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }
/* v7 layout pass */
/* NASA: the picture fills the height of the words, so the card stays slim */
.mini-projects.built .card.slim { align-items: stretch; }
.mini-projects.built .card.slim .media { aspect-ratio: auto; height: auto; min-height: 96px; position: relative; }
.mini-projects.built .card.slim .media img { position: absolute; inset: 0; }
/* talks: on a wide screen two by two, each picture beside its words, so a short card has no gap and the text keeps its size */
@media (min-width: 1024px) {
  .grid.talks { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: auto; gap: var(--sp-4); }
  .grid.talks > .card { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr); }
  .grid.talks > .card > .media, .grid.talks > .card > .video, .grid.talks > .card > .player {
    aspect-ratio: auto; height: auto; min-height: 200px; position: relative; border-bottom: 0; border-right: 1px solid var(--line);
  }
  .grid.talks > .card > .media img { position: absolute; inset: 0; }
  .grid.talks .card .body { padding: var(--sp-4) var(--sp-5); gap: var(--sp-2); }
  .grid.talks .card h3 { font-size: var(--step-0); line-height: 1.35; }
  .grid.talks .card .card-quote, .grid.talks .card .line { font-size: var(--step--1); line-height: 1.5; }
}
/* v7 carousel margin */
@media (min-width: 1180px) { .carousel.has-nav { margin-inline: calc(-44px - var(--sp-4)); } }

/* ---- v8 mobile pass (360 to 414px, 768px, 1440px checked) ---- */
/* jump chips (.chips.jump): even rows, never one chip alone. 2 across on a phone, 3 on a tablet, one row from 860px */
@media (max-width: 859px) {
  .chips.jump { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 560px; margin-inline: auto; }
  .chips.jump > li > .chip { flex: 1; justify-content: center; text-align: center; padding-inline: 10px; }
}
@media (max-width: 479px) { .chips.jump { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 860px) { .chips.jump { flex-wrap: nowrap; margin-inline: -48px; } }
/* tap targets at least 44px on a phone or a touch screen: go links in cards, caption More, nav links, carousel arrows */
@media (max-width: 1023px), (pointer: coarse) {
  .card a.go { display: inline-flex; align-items: center; min-height: 44px; }
  .cap-more > summary { min-height: 44px; margin-top: -8px; }
  .cap-more a, .photo-grid.makes figcaption .cap-more a { display: inline; padding-block: 10px; }
  nav.site ul a { min-width: 44px; justify-content: center; }
}
/* the quotes carousel on a phone: arrows still either side, 44px, reaching into the page margin, so the card has room */
@media (max-width: 699px) {
  .carousel.has-nav { grid-template-columns: 44px minmax(0, 1fr) 44px; gap: var(--sp-1); margin-inline: -12px; }
  .carousel.has-nav .carousel-btn { width: 44px; height: 44px; }
  .carousel .card.quote .body { padding: var(--sp-4); }
  .carousel .card.quote blockquote { padding-left: var(--sp-3); }
  .card.quote figcaption { grid-template-columns: 40px minmax(0, 1fr); }
  .card.quote .face { width: 40px; height: 40px; }
}
/* NASA (.card.slim) on a phone: the picture on top, full width, not a thin strip beside the words */
@media (max-width: 599px) {
  .mini-projects.built .card.slim { grid-template-columns: minmax(0, 1fr); }
  .mini-projects.built .card.slim .media { aspect-ratio: 16 / 9; min-height: 0; border-right: 0; border-bottom: 1px solid var(--line); }
  .mini-projects.built .card.slim .body { padding: var(--card-pad); }
}
/* the song on a phone: once pressed, the player takes the whole row at 16:9 (it is last and alone on its row) */
@media (max-width: 759px) {
  .photo-grid.makes.five .song:has(iframe) { grid-column: 1 / -1; }
  .photo-grid.makes.five .song:has(iframe) .tile-video { aspect-ratio: 16 / 9; }
}
/* More talks on a tablet (700 to 1023px): three across, so no card sits alone */
@media (min-width: 700px) { .mini-projects.three.talk-decks { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---- About v10 (21 Oct 2026, #23) */
/* not a fit: a short muted dash where the ticks sit on the good-fit list */
.also-list.dashes li { position: relative; padding-left: 28px; }
.also-list.dashes li::before { content: ""; position: absolute; left: 3px; top: calc(var(--sp-2) + 0.5em + 3px); width: 10px; height: 2px; border-radius: 1px; background: var(--muted); }
/* core skills: the skill card, not expandable, with a calm accent edge on the left */
.skills-head.first { margin-top: 0; min-height: 36px; }
@media (max-width: 559px), (pointer: coarse) { .skills-head.first { min-height: 44px; } }
.skill-cards.core { align-items: stretch; }
.card.skill.core { display: grid; align-content: start; gap: var(--sp-2); box-shadow: inset 3px 0 0 var(--accent); }
.card.skill.core:hover { border-color: var(--line); }
.card.skill.core > .tags { margin: 0; }

/* v11 Focal point (docs/components.md, Card): a cropped picture whose subject is off-centre names
   where to crop, on the picture itself: <img class="focus" style="--focus: 38% 80%">. It wins over
   every crop rule above (cards, wide cards, talks, the photo grid), at every width. */
img.focus { object-position: var(--focus, center) !important; }


/* ============================================================ Marketplace (products/, 31 Oct 2026, planning #61)
   docs/components.md: Marketplace (listing card, details page, brand band). The hub (v0.6.0) and the details pages are
   normal site pages: the site nav and footer. Find box gone at three tools (back at ~6: commit c66ef03). */

/* listing card (.card.project.listing): a store listing, the minimum. Picture (a link to the details page), name, one line,
   one meta line (where it runs, then "Free" or "Free to try" in ink), then .cta.pair: the main action and More. */
.card.listing .cta.pair { margin-top: auto; padding-top: var(--sp-3); }
/* the hub's cards (.listings) start under the page head as the Blog's cards do: no hairline, the same gap */
.page-head + .listings { margin-top: var(--sp-6); }
.post-meta.price { margin: var(--sp-2) 0 0; }
.post-meta.price strong { color: var(--ink); font-weight: 600; }

/* ---- gallery: a product's video and screenshots, one at a time (details page slot 5; Peapod). The first slide is a
   video still (.yt, the same click-to-load as .video: nothing from YouTube before the press). Under it, ‹ › and a
   strip of thumbnails (a tablist): click one, or arrow keys, or swipe. site.js shows one slide; without it they stack and
   the still links to YouTube. --fit-ground: the pictures' own edge colour. A modifier per tool colours the play button. */
.gallery { margin: var(--sp-5) 0 0; }
.gallery-stage { display: grid; gap: var(--sp-3); }
.gallery-slide { position: relative; aspect-ratio: 16 / 10; overflow: hidden; margin: 0;
  background: var(--fit-ground, var(--media-fit-bg)); border: 1px solid var(--line); border-radius: var(--radius); }
.gallery-slide[hidden] { display: none; }
.gallery-slide > img { display: block; width: 100%; height: 100%; object-fit: contain; }
.gallery-slide .yt, .gallery-slide iframe { position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border: 0; } /* the 16:9 video, centred in the 16:10 frame */
.gallery-slide .yt img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gallery .yt:hover .play, .gallery .yt:focus-visible .play { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.gallery .yt:focus-visible { outline-offset: -3px; } /* the slide clips outside it */
@media (min-width: 560px) {
  .gallery .yt .play { left: var(--sp-4); bottom: var(--sp-4); padding: var(--sp-3) var(--sp-5) var(--sp-3) var(--sp-4); font-size: var(--step-0); }
}
.gallery-nav { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-3); }
.gallery-nav[hidden] { display: none; }
.gallery-count { flex: none; margin: 0; min-width: 3.5ch; text-align: center; font: var(--meta-font); color: var(--muted); font-variant-numeric: tabular-nums; }
.gallery-note { margin: var(--sp-2) 0 0; font-size: var(--step--1); color: var(--muted); }
.gallery-step { flex: none; display: grid; place-items: center; width: 44px; height: 44px; padding: 0; cursor: pointer;
  border-radius: 50%; background: var(--surface); color: var(--ink); border: 1px solid var(--control); }
.gallery-step svg { width: 20px; height: 20px; }
@media (hover: hover) { .gallery-step:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); } } /* a tap leaves no stuck hover on a phone */
.gallery-thumbs { flex: 1; min-width: 0; display: flex; gap: var(--sp-2); overflow-x: auto; padding: 5px; scrollbar-width: none; }
.gallery-thumbs::-webkit-scrollbar { display: none; }
.gallery-thumbs [role="tab"] { position: relative; flex: 1 0 64px; max-width: 120px; aspect-ratio: 16 / 10; padding: 0; cursor: pointer;
  overflow: hidden; border: 2px solid transparent; border-radius: 8px; background: var(--fit-ground, var(--media-fit-bg)); opacity: 0.65; }
@media (hover: hover) { .gallery-thumbs [role="tab"]:hover { opacity: 1; } }
.gallery-thumbs [aria-selected="true"] { opacity: 1; border-color: var(--accent); }
.gallery-thumbs [role="tab"]:focus-visible { outline-offset: 1px; }
.gallery-thumbs img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gallery-thumbs .thumb-play { position: absolute; inset: 0; display: grid; place-items: center; }
.gallery-thumbs .thumb-play svg { width: 22px; height: 22px; padding: 4px; border-radius: 50%; background: rgb(11 18 32 / 0.85); color: #fff; }
@media (prefers-reduced-motion: no-preference) {
  .js .gallery-slide { animation: gallery-in 180ms ease-out; }
}
@keyframes gallery-in { from { opacity: 0.35; } }
/* .play.round: a big round play button in the middle of the still, icon only (its words in .vh), for a still whose
   corners and bottom carry words (Peapod's video poster) */
.play.round { left: 50%; top: 50%; bottom: auto; transform: translate(-50%, -50%); justify-content: center;
  width: 64px; height: 64px; padding: 0; border-radius: 50%; border-width: 2px; box-shadow: 0 4px 18px rgb(0 0 0 / 0.35); }
.play.round svg { width: 30px; height: 30px; margin-left: 4px; } /* the triangle's weight sits left of its box: nudge it to look centred */
@media (min-width: 560px) { .gallery .yt .play.round { left: 50%; bottom: auto; width: 84px; height: 84px; padding: 0; } .gallery .yt .play.round svg { width: 38px; height: 38px; } }
@media (prefers-reduced-motion: no-preference) { .play.round { transition: transform 150ms ease-out; } .yt:hover .play.round { transform: translate(-50%, -50%) scale(1.08); } }
/* the tools: Peapod's play button in bright pea on its pod green */
.gallery.peapod .play, .card .media .play.peapod { background: #b9f27a; color: #1b5e20; border-color: #1b5e20; font-weight: 600; }
.gallery.peapod .yt:hover .play, .gallery.peapod .yt:focus-visible .play, .card .media:hover .play.peapod { background: #ffffff; color: #1b5e20; border-color: #1b5e20; }
.gallery.peapod .thumb-play svg { background: #b9f27a; color: #1b5e20; }

/* details page (main.wrap.narrow.details), signed template tools/wireframe-details.html v0.3. Top to bottom:
   .crumb ← Marketplace · .brand-band (icon, h1 name, tagline) · .lede one line · .pic · .post-meta.price · .cta.pair ·
   .intro · .does (What it does, 3 points) · .more (Read more, if there's a post) · .brand-band.foot · footer.site.
   Hierarchy: h1 (step-3) > h2 (step-2) > lede (step-1) > body > meta and the product footer (step--1). */
main.details { padding-top: var(--sp-5); }
main.details .crumb { margin-bottom: var(--sp-3); }
main.details .lede { margin: var(--sp-5) 0 0; }
main.details .pic { display: block; width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: contain; margin-top: var(--sp-5);
  background: var(--fit-ground, var(--media-fit-bg)); border: 1px solid var(--line); border-radius: var(--radius); }
main.details .post-meta.price { margin-top: var(--sp-4); }
main.details .cta.pair { max-width: 420px; margin-top: var(--sp-4); }
main.details .intro { margin: var(--sp-7) 0 0; }
main.details .does { margin-top: var(--sp-6); }
main.details .does h2 { font-size: var(--step-2); letter-spacing: -0.01em; margin: 0 0 var(--sp-3); }
main.details .does ul { margin: 0; padding-left: var(--sp-5); }
main.details .does li + li { margin-top: var(--sp-2); }
main.details .more { margin: var(--sp-4) 0 0; }
main.details .more .go { display: inline-flex; align-items: center; min-height: 44px; padding: 0; }

/* brand band (.brand-band): a tool's own colours on its details page. In the header: icon, the page h1 (its name; .name on
   a legal page, which keeps its h1 for the page title, the same look) and a
   tagline, with a 4px lip under it. .foot: the same band at the end of main, smaller, the lip on top, for the tool's small
   print (Privacy, Terms, made by). A modifier per tool sets the tokens: --brand-ground (any background), --brand-lip (an
   image: a gradient, stripes, or linear-gradient(c, c) for one colour), --brand-ink (name), --brand-sub (tagline, small print), --brand-link.
   Contrast (worst case: lightest point of the ground): see docs/components.md, Brand band. */
.brand-band { --brand-ground: var(--surface); --brand-lip: linear-gradient(var(--accent), var(--accent)); --brand-ink: var(--ink); --brand-sub: var(--muted); --brand-link: var(--accent);
  display: flex; align-items: center; gap: var(--sp-4); min-width: 0; margin: 0; padding: var(--sp-5) var(--sp-5) calc(var(--sp-5) + 4px);
  border-radius: var(--radius); color: var(--brand-ink);
  background: var(--brand-lip) bottom / 100% 4px no-repeat, var(--brand-ground); }
.brand-band img { width: 56px; height: 56px; flex: none; border-radius: 13px; box-shadow: 0 0 0 1px rgb(255 255 255 / 0.18); }
.brand-band > div { min-width: 0; }
.brand-band h1, .brand-band .name { margin: 0; font-size: var(--step-3); font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: var(--brand-ink); }
.brand-band .tag { margin: var(--sp-1) 0 0; font-size: var(--step-0); line-height: 1.35; color: var(--brand-sub); }
.brand-band a { color: var(--brand-link); }
.brand-band a:hover { color: var(--brand-ink); }
.brand-band :focus-visible { outline-color: var(--brand-link); }
.brand-band.foot { display: block; margin-top: var(--sp-8); padding: calc(var(--sp-4) + 4px) var(--sp-5) var(--sp-4);
  background: var(--brand-lip) top / 100% 4px no-repeat, var(--brand-ground); color: var(--brand-sub); font-size: var(--step--1); line-height: 1.6; }
.brand-band.foot p { margin: 0; }
.brand-band.foot .small { margin-top: var(--sp-1); }
.brand-band.foot a { display: inline-block; padding: var(--sp-2) 0; } /* a bigger tap target, without moving the line */
@media (max-width: 559px) { .brand-band { padding-left: var(--sp-4); padding-right: var(--sp-4); gap: var(--sp-3); } .brand-band img { width: 48px; height: 48px; border-radius: 11px; } }
/* the tools: Peapod (products/peapod/product.css: pod green, bright pea lip), KEF Remote (its icon's navy, a teal-to-blue
   lip from its volume ring), Vial Keyboard Layers (the map's near-black, a lip in its five layer colours) */
.brand-band.peapod { --brand-ground: #1b5e20; --brand-lip: linear-gradient(#b9f27a, #b9f27a); --brand-ink: #ffffff; --brand-sub: #d3ecc6; --brand-link: #ffffff; }
.brand-band.kef { --brand-ground: linear-gradient(#22304f, #0f1628); --brand-lip: linear-gradient(90deg, #3ee0c5, #2fa8ff); --brand-ink: #f4f6fa; --brand-sub: #c3cde0; --brand-link: #3ee0c5; }
.brand-band.vial { --brand-ground: #0b0d10; --brand-lip: linear-gradient(90deg, #4fd6ff 0 20%, #ffd23f 0 40%, #ff79d9 0 60%, #7dff8a 0 80%, #ffa24d 0); --brand-ink: #f4f6fa; --brand-sub: #b4bfd0; --brand-link: #4fd6ff; }

/* legal page (main.wrap.narrow.details.legal.<tool>), signed template tools/wireframe-legal.html v0.1.1: a tool's Privacy,
   Terms and removal pages. Written by tools/make_details_pages.py (legal). Top to bottom: nav.site · .crumb ← Name (the
   details page, the same place and spacing as ← Marketplace) · .brand-band.<tool> (the details page's band, name as
   .name) · .legal-body (h1 title, .meta version line, .short In short, the sections, a form on the removal page) ·
   .brand-band.foot.<tool> · footer.site. Hierarchy: band name (step-3) > h1 (step-2) > h2 (step-1) > body > .meta, .dim
   (step--1, muted). --legal-lip: the In short box's edge, the tool's lip colour. */
main.legal { --legal-lip: var(--accent); }
main.legal.peapod { --legal-lip: #b9f27a; }
main.legal.vial { --legal-lip: #4fd6ff; } /* its first layer colour (the band's link colour) */
main.legal .legal-body { margin-top: var(--sp-6); }
main.legal .legal-body h1 { font-size: var(--step-2); font-weight: 700; letter-spacing: -0.01em; line-height: 1.2; margin: 0 0 var(--sp-2); }
main.legal .legal-body h2 { font-size: var(--step-1); letter-spacing: -0.01em; line-height: 1.3; margin: var(--sp-6) 0 var(--sp-2); }
main.legal .legal-body p, main.legal .legal-body li { margin: 0 0 var(--sp-2); }
main.legal .legal-body ul { padding-left: var(--sp-5); }
main.legal .legal-body code { font: 0.9em/1.4 var(--mono); background: rgb(255 255 255 / 0.06); border-radius: 5px; padding: 1px 5px; }
main.legal .legal-body .meta { font: var(--meta-font); color: var(--muted); margin: 0 0 var(--sp-5); }
main.legal .short { background: var(--surface); border-left: 4px solid var(--legal-lip); border-radius: var(--radius-sm); padding: var(--sp-4) var(--sp-4) var(--sp-2); }
main.legal .short ul { margin: 0; }
main.legal .legal-body .dim { color: var(--muted); font-size: var(--step--1); }
/* the removal page's form: radios in rows, an optional note, Send (.btn.primary), then the text to copy if no mail app opened */
main.legal fieldset { border: 0; padding: 0; margin: 0 0 var(--sp-5); min-width: 0; }
main.legal legend, main.legal label.block { display: block; padding: 0; border: 0; margin: 0 0 var(--sp-2); font-size: var(--step-1); font-weight: 600; line-height: 1.3; }
main.legal .opt { font-weight: 400; color: var(--muted); font-size: var(--step--1); }
main.legal .choice { display: flex; align-items: center; gap: var(--sp-3); min-height: 44px; }
main.legal .choice input { width: 20px; height: 20px; margin: 0; flex: none; accent-color: var(--accent); }
main.legal .inline { display: flex; flex-wrap: wrap; gap: 0 var(--sp-5); }
main.legal textarea { display: block; width: 100%; margin: 0; padding: var(--sp-3) var(--sp-4); font: 400 var(--step-0)/1.5 var(--sans); color: var(--ink);
  background: var(--field); border: 1px solid var(--control); border-radius: var(--radius-sm); resize: vertical; }
main.legal textarea::placeholder { color: var(--muted); opacity: 0.8; }
main.legal textarea:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
main.legal .after { background: var(--surface); border-radius: var(--radius-sm); padding: var(--sp-4); margin-top: var(--sp-4); }
main.legal .after textarea { font-size: var(--step--1); }

/* Privacy page: the "Stop counting my visits" switch (privacy.html#counting) */
.counting { margin-top: var(--sp-6); padding: var(--sp-4); border: 1px solid var(--line); border-radius: 12px; }
.counting h2 { margin-top: 0; }
.counting .field.check { display: grid; min-height: 44px; }
.counting .field.check input { width: 22px; height: 22px; margin: 0; accent-color: var(--accent); }
.counting .field.check label { font-weight: 600; cursor: pointer; }
.counting-status { font-weight: 600; }
.counting p:last-child { margin-bottom: 0; }
