:root {
  --bg: #fafafa;
  --fg: #1a1a1a;
  --muted: #666;
  --card: #fff;
  --border: #e2e2e2;
  --accent: #b7410e; /* rust */
  /* Sites' lines on /status, in order: muted (about a third less chroma than
     full-strength hues), with lightness varied between neighbours so each
     adjacent pair stays apart under protan/deutan simulation (ΔE ≥ 8, OKLab)
     and normal vision (ΔE ≥ 15). Light: the yellow is under 3:1 against --bg,
     so the numbers sit beside the colours in the tooltips. */
  --series-1: #4a7ab9; --series-2: #ad5939; --series-3: #439d76; --series-4: #d6a45a;
  --series-5: #c87794; --series-6: #3c7d38; --series-7: #635db5; --series-8: #c9645e;
  /* The sites past the 8th, together: a grey darker than every colour (in
     the dark themes, lighter), so it stays apart from each of them, ΔE ≥ 13
     in protan/deutan simulation and ≥ 18 in normal vision. */
  --series-other: #454545;
  color-scheme: light;
}

/* Dark theme: the system setting, unless the toggle chose light; or the
   toggle chose dark (theme.js sets data-theme). The two blocks must match. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #121212;
    --fg: #eaeaea;
    --muted: #a0a0a0;
    --card: #1c1c1c;
    --border: #2e2e2e;
    --accent: #e0703a;
    --series-1: #5789c7; --series-2: #b86345; --series-3: #4ba680; --series-4: #b2803a;
    --series-5: #aa5470; --series-6: #33742f; --series-7: #8e88d0; --series-8: #cc7674;
    --series-other: #c4c4c4;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --bg: #121212;
  --fg: #eaeaea;
  --muted: #a0a0a0;
  --card: #1c1c1c;
  --border: #2e2e2e;
  --accent: #e0703a;
  --series-1: #5789c7; --series-2: #b86345; --series-3: #4ba680; --series-4: #b2803a;
  --series-5: #aa5470; --series-6: #33742f; --series-7: #8e88d0; --series-8: #cc7674;
  --series-other: #c4c4c4;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* Only regular (400) and semibold (600) are self-hosted; 600 for headings
   too, so the browser never fakes a bold. */
h1, h2, h3, strong, b { font-weight: 600; }

/* Read by screen readers, not shown on screen. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

body {
  margin: 0;
  padding: 0 1rem;
  font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.6;
  background: var(--bg);
  color: var(--fg);
}

/* Page width: wide for the project grid (four cards per row), narrower
   for the resume's reading columns, and the whole window for /status's
   charts. The header keeps the project grid's width on every page, so the
   name and the links stay put from page to page. The footer follows the
   page; its text is centred, so its width doesn't show. */
body { --site-width: 1200px; --page-width: var(--site-width); }
body:has(> .resume) { --page-width: 960px; }
body:has(> main.status) { --page-width: none; }

.site-header, .hero, main, footer, .resume {
  width: 100%;
  max-width: var(--page-width);
  margin: 0 auto;
}
.site-header { max-width: var(--site-width); }

/* The page fills at least the window, so on a short page the footer sits
   at the bottom of it rather than right under the content. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
footer { margin-top: auto; }

/* Top of every page: the name on the left; the pages and the theme toggle
   on the right, aligned with the content. Wraps on a narrow screen. */
.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  padding-top: 1rem;
}
.site-name {
  margin: 0 auto 0 0;
  font-size: 1.05rem;
  font-weight: 600; /* also where it's a p, see site_header */
  line-height: 1.3;
}
.site-name a { color: var(--fg); text-decoration: none; }
.site-name a:hover { color: var(--accent); }
.site-nav ul {
  display: flex;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.site-nav a { color: var(--muted); text-decoration: none; }
.site-nav a:hover { color: var(--accent); }
.site-nav [aria-current="page"] { color: var(--fg); font-weight: 600; }
/* Each link as wide as its label in bold (an invisible bold copy under
   it), so the links don't shift as the bold one changes from page to page. */
.site-nav [data-label] { display: inline-flex; flex-direction: column; }
.site-nav [data-label]::after {
  content: attr(data-label);
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 600;
}
.theme-toggle {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--fg);
  cursor: pointer;
}
.theme-toggle:hover { color: var(--accent); border-color: currentColor; }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle svg { width: 1.25rem; height: 1.25rem; }
/* Show the theme you'd switch to: a moon in light mode, a sun in dark. */
.theme-toggle[data-current="light"] .sun,
.theme-toggle[data-current="dark"] .moon { display: none; }

.hero { padding: 1.5rem 0 2rem; text-align: center; }
.hero p { margin: 0.5rem 0 0; color: var(--muted); }
.hero .tagline { font-size: 1.15rem; color: var(--fg); }
.hero .intro { margin-top: 1rem; }
/* Centered text reads best in a narrow column: about 65 characters a line. */
.hero .tagline, .hero .intro { max-width: 65ch; margin-inline: auto; }
.hero .tagline { text-wrap: balance; }
.hero .more { margin-top: 1rem; }
.hero .more a, .resume a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.hero .more a:hover, .resume a:hover { text-decoration: underline; }

#projects > h2 { text-align: center; }

/* The not-found page: a heading and the way back, centred like the
   front page's header. */
.not-found { padding: 1.5rem 0 2rem; text-align: center; }
.not-found p { margin: 0.5rem auto 0; max-width: 65ch; color: var(--muted); }
.not-found .more { margin-top: 1rem; }
.not-found .more a { color: var(--accent); font-weight: 600; text-decoration: none; }
.not-found .more a:hover { text-decoration: underline; }

/* Flexbox instead of grid so an incomplete last row is centered. Cards
   are at least 17.5rem wide; the breakpoints pick as many columns as fit
   in the page (up to 75rem, plus 2rem of body padding). */
.grid {
  --cols: 1;
  --gap: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--gap);
}
.grid > .card {
  flex: 0 0 calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));
}
@media (min-width: 38.5rem) { .grid { --cols: 2; } }
@media (min-width: 57.5rem) { .grid { --cols: 3; } }
@media (min-width: 76.5rem) { .grid { --cols: 4; } }

.card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.08);
}

.card img {
  display: block;
  width: 100%;
  height: auto; /* the height attribute only reserves space */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-bottom: 1px solid var(--border);
}

.card-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 1.25rem;
}
.card-body h3 { margin: 0 0 0.5rem; }
.card-body p { margin: 0 0 1rem; color: var(--muted); }

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
}
.tags li {
  font-size: 0.8rem;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
}

.links {
  margin-top: auto; /* pin links to the bottom so cards line up */
  display: flex;
  gap: 1rem;
}
.links a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.links a:hover { text-decoration: underline; }

footer {
  padding: 3rem 0 2rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
}
footer a {
  color: inherit;
  text-underline-offset: 0.2em;
}
footer a:hover { color: var(--accent); }
footer p { margin: 0; }
footer .colophon { margin-top: 0.35rem; font-size: 0.8rem; }

/* Server status, without visible headings: the server's numbers in a row,
   the charts (drawn by status.js, as wide as the window) and the sites as
   their legend. */
main.status { padding-top: 1.5rem; }
/* The CPU chart takes the height the page leaves over, down to the window's
   bottom (see the footer), but no less than its own 170px: main, the section
   and the charts are columns that grow, and the chart grows in them. */
main.status, .status .cpu-section, .status .charts:not([hidden]) {
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
}
.status .none { margin-top: 1.5rem; text-align: center; color: var(--muted); }
.spark { height: 1.25rem; min-width: 6rem; }
.spark:empty { display: none; } /* without JavaScript */
.spark svg { display: block; width: 100%; height: 100%; overflow: visible; }
.spark polyline {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* The whole server's numbers as one row on top of the charts: each a small
   label over its value, centred, wrapping on narrow screens. */
.host {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem 2.5rem;
  margin: 0 0 1.25rem;
  font-variant-numeric: tabular-nums;
}
.host > div { text-align: center; }
.host > div[hidden] { display: none; }
.host dt { color: var(--muted); font-size: 0.8rem; }
.host dd { margin: 0; font-size: 1.05rem; font-weight: 600; }
.swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  margin-right: 0.45rem;
  border-radius: 2px;
  background: var(--series);
}

/* The sites' memory: one thin bar split into their colours, drawn by
   status.js. Centred in the
   height of a sparkline, so it lines up with the CPU chart. */
.membar {
  display: flex;
  gap: 2px;
  height: 8px;
  max-width: 16rem;
  margin: calc((1.25rem - 8px) / 2) 0;
  border-radius: 999px;
  overflow: hidden;
}
.membar:empty { display: none; } /* without JavaScript */
.membar > span { min-width: 2px; background: var(--series); }
/* The same charts in the pages' footer, as wide as on /status: a link
   to it, as wide as the charts themselves. */
.footer-charts {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem 2rem;
  max-width: 100%;
  margin-top: 0.35rem;
  padding: 0.25rem 0.5rem;
  border-radius: 6px;
}
.footer-charts:hover { background: var(--card); }
.footer-charts:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.footer-charts[hidden] { display: none; } /* until status.js draws them */
.footer-charts > div { width: 16rem; max-width: 100%; }
/* The CPU chart needs two readings, the memory bar one: keep the chart's
   place while it's empty, so the bar doesn't start centred and then jump. */
.footer-charts > .spark:empty { display: block; }

/* CPU over the last 10 minutes: a line per site across the column, with a
   light grid and a tooltip on hover. */
.cpu-area { position: relative; flex: 1 0 170px; touch-action: pan-y; }
/* Out of the flow, so the drawing follows the area's size and not the
   other way round. */
.cpu-area .plot { position: absolute; inset: 0; }
.cpu-area svg { display: block; width: 100%; height: auto; overflow: visible; }
.cpu-area .grid { stroke: var(--border); stroke-width: 1; }
.cpu-area .axis { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.cpu-area .series { fill: none; stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
/* Hovering: the site nearest the pointer stands out, the others fade. */
.cpu-area .focus .series { opacity: 0.3; }
.cpu-area .focus .series.on { opacity: 1; stroke-width: 2.5; }
.cpu-area .cursor { stroke: var(--fg); stroke-width: 1; stroke-dasharray: 3 3; }
.cpu-area .hit { fill: transparent; cursor: crosshair; }
.cpu-area .tip, .area-memory .tip {
  position: absolute;
  top: 0;
  z-index: 1;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);
  font-size: 0.8rem;
  line-height: 1.5;
  white-space: nowrap;
  pointer-events: none;
}
.tip .when { color: var(--muted); }
.tip b { font-weight: 600; font-variant-numeric: tabular-nums; }
/* Both tooltips dim the sites other than the one pointed at. */
.tip:has(> .on) > :not(.when, .on):nth-child(n + 3) { color: var(--muted); }
/* The sites' memory under the CPU chart: one bar as wide as the plot, with
   its label in the axis gutter. */
.area-memory {
  display: grid;
  grid-template-columns: 44px 1fr;
  align-items: center;
  position: relative;
  margin-top: 0.4rem;
  padding-right: 8px;
}
/* Above the bar, over the CPU chart: the chart takes the window's height, so
   below the bar it could reach past the bottom. */
.area-memory .tip { top: auto; bottom: calc(100% + 6px); }
.area-memory .label { padding-right: 6px; color: var(--muted); font-size: 11px; text-align: right; }
.area-memory .membar { max-width: none; margin: 0; cursor: crosshair; touch-action: pan-y; }

/* The sites under the charts, as their legend: colour and name, and the
   uptime under the name rather than in a tooltip, which a phone can't show.
   Centred and wrapping. */
.sites-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem 1.25rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.sites-legend .name { font-weight: 600; }
.sites-legend .about {
  display: block;
  padding-left: 1.05rem; /* under the name, past the swatch and its margin */
  color: var(--muted);
  font-size: 0.75rem;
}
.sites-legend .down { color: var(--muted); }

/* Resume: sidebar (location, languages, skills, …) and main column. The
   sidebar is wide enough for "Functional programming" on one line, with
   room for a wider fallback font. */
.resume {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: 2.5rem;
  padding-block: 1.5rem 0;
}
.resume h2 {
  margin: 2rem 0 0.75rem;
  padding-top: 0.75rem;
  border-top: 2px solid var(--border);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.resume ul, .resume ol { list-style: none; margin: 0; padding: 0; }

.resume aside {
  text-align: right;
  padding-right: 1.5rem;
  border-right: 2px solid var(--border);
}
.resume aside li { margin-bottom: 0.35rem; }
.resume aside .location { margin: 0; font-weight: 600; }
.resume aside .email { margin: 0.25rem 0 0; font-size: 0.9rem; }
.resume aside .email a { font-weight: 400; }

.resume-main .headline { margin: 0; font-size: 1.15rem; }
.resume-main .roles { display: flex; flex-wrap: wrap; gap: 0 0.5rem; color: var(--muted); }
.resume-main .roles li + li::before { content: "·"; margin-right: 0.5rem; }
.resume-main section > p { color: var(--muted); max-width: 65ch; }

.timeline > li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 1rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--border);
}
.timeline > li:last-child { border-bottom: none; }
.timeline .years { color: var(--muted); font-size: 0.9rem; padding-top: 0.15rem; }
.timeline h3 { margin: 0; font-size: 1.05rem; }
.timeline .title { margin: 0.1rem 0 0.5rem; }
.timeline .tasks { margin: 0 0 0.5rem; color: var(--muted); font-size: 0.9rem; }
.timeline .tags { margin: 0; }

@media (max-width: 40rem) {
  .resume { grid-template-columns: 1fr; gap: 0; padding-top: 1rem; }
  .resume aside {
    order: 2; /* name and overview first on phones */
    text-align: left;
    padding-right: 0;
    border-right: none;
  }
  .timeline > li { grid-template-columns: 1fr; gap: 0.25rem; }
}

@media print {
  /* Always light on paper. Both selectors are as specific as the theme
     selectors above, so they win whichever theme is on screen. */
  :root:not([data-theme]), :root[data-theme] {
    --bg: #fff; --fg: #000; --muted: #333; --border: #ccc; color-scheme: light;
  }
  body { padding: 0; font-size: 10.5pt; }
  /* The name on top, as the resume's heading; not the links. */
  .site-nav, .theme-toggle, footer, .hero .more { display: none; }
  .site-header { padding: 0 0 0.75rem; }
  .site-name { font-size: 1.4rem; }
  .resume { padding-top: 0; gap: 1.5rem; grid-template-columns: 13rem 1fr; }
  .resume h2 { margin: 1rem 0 0.4rem; padding-top: 0.4rem; break-after: avoid; }
  .resume-main section > p { margin: 0 0 0.5rem; }
  .timeline > li { padding: 0.4rem 0; break-inside: avoid; }
  .timeline .title, .timeline .tasks { margin-bottom: 0.25rem; }
  .tags li { padding: 0 0.5rem; }
}
