/* 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); }

/* ---- 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(--media-fit-bg); }
.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);
}

/* the kef-remote drawing (home card and About card) */
.kef-pic .panel { fill: var(--surface); stroke: var(--control); stroke-width: 2; }
.kef-pic .glyph { fill: var(--ink); }
.kef-pic .ln.thin { stroke-width: 1.5; }
.kef-pic .kt { fill: var(--ink); font: 500 13px/1 var(--mono); }
.kef-pic .kt.big { font: 500 17px/1 var(--sans); }
.kef-pic .kt.plus { fill: var(--muted); font-size: 16px; }
.kef-pic .cap { fill: var(--muted); font: 400 12px/1 var(--mono); }

/* ---- 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; }
}
/* 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-3); }
.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); }
