/* ==========================================================================
   Yonnia.com

   One stylesheet, no framework, no JS.

   The design is ported from the digital-garden-jekyll-template
   (github.com/maximevaillancourt/digital-garden-jekyll-template), whose look is
   deliberately plain: the reader's own system sans, a pure greyscale ramp, and
   flat 4%-grey panels instead of borders and shadows. Nothing here asks to be
   noticed, which is the point.

   Its SCSS variables are resolved to literals below, because `mix()` has no CSS
   equivalent and a hard-coded hex is honest about what actually ships:

     $color-primary        hsl(0,0%,10%)              -> #1a1a1a
     $color-text           hsl(0,0%,20%)              -> #333333
     $color-subtext        hsl(0,0%,30%)              -> #4d4d4d
     $color-border         hsl(0,0%,85%)              -> #d9d9d9
     $color-box-background mix(primary, white, 4%)    -> #f6f6f6
     footer colour         mix(text, white, 80%)      -> #5c5c5c

   Two properties are left to the browser on purpose, because the template
   leaves them alone and they are what make it read as a document rather than as
   a designed page:

     - heading font-size and font-weight (h1 2em bold, h2 1.5em bold, ...)
     - the monospace family for code

   Layer order: tokens -> reset -> layout -> components -> prose -> pages -> print.
   ========================================================================== */

/* --------------------------------------------------------------- 1. tokens */

:root {
  --bg:          #ffffff;
  /* The one raised surface in the system: the template's $color-box-background.
     Every panel — cards, callouts, the TOC, code — is this grey, with no border
     and no shadow. Depth is communicated by tone alone. */
  --bg-raised:   #f6f6f6;
  --bg-code:     #f8f8f8;
  --bg-inline-code: #f5f5f5;

  --text:        #333333;
  --text-muted:  #4d4d4d;
  --text-faint:  #5c5c5c;
  /* Near-black, used for link text and anything that must sit above body copy.
     The template has no accent hue at all, so this stands in for one. */
  --primary:     #1a1a1a;

  --border:      #d9d9d9;

  /* Warm tints, the only colour the template permits. Link hover is a hair of
     cream; focus is a stronger amber so it cannot be mistaken for hover. */
  --link-hover-bg: #fffaf1;
  --focus-bg:      #ffe8bc;
  --marker:        #aaaaaa;

  --radius: 4px;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;

  /* The template's own geometry: a 63em column inside 6vw side margins and 4vh
     of breathing room top and bottom. */
  --wrap:   63em;
  --gutter: 6vw;

  /* What sits above the home page's hero, subtracted from one screen so the hero
     ends exactly at the fold: the body's 4vh top padding, plus the header's 1em
     top margin + one 1.7em line + 3em bottom margin. The footer is not in the sum
     — it is a screen further down, below the sections.

     The header term assumes the nav fits on one line, which holds down to 462px
     of viewport — measured by probing the header's natural width, not guessed.
     The fifth nav item moved that threshold up from ~360px, so 375px now wraps
     the language switch onto its own row and the first screen ran 37px long until
     the 29rem query below corrected it. Verified at 1280x900, 1440x760,
     1920x1080 and 375x812, where the hero's bottom edge lands on the fold to the
     pixel. */
  --home-chrome: calc(4vh + 5.7em);

  /* The measure, capped so a line stays in the comfortable range. Chosen by
     measuring rendered width, not by converting the template's 46em: at the
     63em column this lands 72 Latin characters and 36 CJK characters per line,
     inside the 45-75 / <=40 targets. Every rule that caps text width reads from
     this one value, so widening the measure is a one-line change (38em gives 76
     characters, which is the safe alternative if this reads too narrow). */
  --measure: 36em;

  /* Vertical rhythm, three levels. The template has only one — the browser's
     `margin: 1em 0` — which is enough for a single-column note but leaves a
     heading equidistant from the paragraph above and the one below, so a long
     page reads as equidistant floating blocks instead of bounded sections.
     Both values are em, and em on a heading resolves against the heading's own
     font-size: 1.8em on an h2 at 28.8px is 52px, not 52px's worth of body
     text. They were tuned by measuring gaps, not by arithmetic. */
  --flow-tight:   0.42em;  /* heading -> its own first line of content */
  --flow-section: 1.8em;   /* section -> section */

  /* The reader's system UI face, verbatim from the template. No web font, so no
     network request and no FOIT/FOUT. Emoji families are last so they only ever
     resolve glyphs the text faces do not carry. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
               sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
               "Segoe UI Symbol";

  --leading-body:  1.7;
  --leading-tight: 1.3;

  /* Light only. The template has no dark theme, and a half-ported one would be
     worse than none. */
  color-scheme: light;
}

/* Below 462px of viewport the header's three parts no longer fit on two rows and
   the language switch takes a third, adding a measured 38px. Without this the
   hero's min-height still budgets for the shorter header and the first screen
   overshoots the fold by 37px at 375px — the most common phone width. 29rem
   (464px) is the round value just above the measured threshold.

   This is a token override rather than a change to the base value because the
   wrap is real only at these widths; adding 38px unconditionally would leave a
   gap under the hero at every width above it. */
@media (max-width: 29rem) {
  :root { --home-chrome: calc(4vh + 5.7em + 38px); }
}

/* ---------------------------------------------------------------- 2. reset */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  /* content-box, per the template: the 63em is the text column itself, with the
     6vw padding sitting outside it rather than eating into it. */
  box-sizing: content-box;
  margin: 0 auto;
  max-width: var(--wrap);
  padding: 4vh var(--gutter);
  overflow-x: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: var(--leading-body);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

@media (min-width: 820px) {
  body { font-size: 1.2rem; }
}

/* Headings keep the browser's own size and weight — the template sets only
   these three properties, and the resulting scale is what gives it its plain,
   documentary feel. */
h1, h2, h3, h4, h5, h6 {
  line-height: var(--leading-tight);
  margin-bottom: 0;
  padding-bottom: 0;
  text-wrap: balance;
}

img, svg, video { max-width: 100%; height: auto; }

img {
  display: block;
  margin: 0 auto;
  max-height: 75vh;
  border-radius: var(--radius);
}

/* Links are underlined by a border rather than by text-decoration, so the rule
   sits clear of descenders. The 300ms background transition is the template's
   one piece of motion. */
a {
  transition: background 300ms;
  padding: 0 0.1em;
  text-decoration: none;
  border-bottom: 1px solid var(--border);
  color: var(--primary);
}
a:hover {
  color: black;
  background: var(--link-hover-bg);
}

/* An amber wash rather than a ring. `!important` is the template's own choice
   and it is load-bearing here: it has to beat the per-component link and panel
   colours that would otherwise leave the focused element unmarked. */
*:focus {
  background: var(--focus-bg) !important;
  color: black !important;
}

/* Inline code and code blocks use the browser's monospace default, matching the
   template, which never declares a family for them. */
code {
  background: var(--bg-inline-code);
  padding: 0.1em 0.2em;
  border-radius: var(--radius);
}

pre { font-size: 1em; }

button { font: inherit; color: inherit; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* --------------------------------------------------------------- 3. layout */

/* body already carries the column and the gutters, so .wrap only needs to not
   fight it. Kept as a hook because every template in src/ emits it. */
.wrap {
  width: 100%;
}

main.wrap { flex: 1; }

/* Only the home page: main becomes a flex column so the hero's own `min-height`
   sits in a predictable formatting context and `.hero-more` can be pinned to the
   hero's bottom edge with `margin-top: auto`. */
.page-home main.wrap {
  display: flex;
  flex-direction: column;
}

/* Present in the DOM, off-screen until focused. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-raised);
  color: var(--primary);
  border: 0;
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* header */

/* Flat and static: no sticky positioning, no backdrop filter, no bottom rule.
   The template's entire header is one bold site name with 3em of air under it. */
.site-header {
  margin: 1em 0 3em;
}

.header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3) var(--space-4);
}

.brand {
  font-weight: bold;
  margin-inline-end: auto;
  border-bottom: 0;
  padding: 0;
}
.brand:hover { background: var(--link-hover-bg); }

.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: 0.9em;
}
.site-nav a[aria-current="page"] { font-weight: bold; }

.lang-switch {
  font-size: 0.9em;
  white-space: nowrap;
}

/* footer */

.site-footer {
  margin: 2em 0 0;
  padding-top: 1em;
  font-size: 0.8em;
  color: var(--text-faint);
}
/* Three columns on one line: nav left, copyright centred, feed links right. The
   grid is 1fr each so the centre column is centred on the page, not on whatever
   width the nav happens to take — which is what a flex row with
   space-between would give. */
.footer-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: var(--space-3) var(--space-4);
}
.footer-inner a { color: var(--text-faint); }
.footer-inner a:hover { color: black; }
.footer-nav, .footer-meta, .footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
  margin: 0;
}
.footer-nav  { justify-content: start; }
.footer-meta { justify-content: center; text-align: center; }
.footer-links { justify-content: end; }

/* The parenthetical after the privacy link. Negative start margin cancels the
   row's own gap so it sits against the link it qualifies rather than floating a
   full gap away from it. */
.footer-note {
  margin-inline-start: calc(var(--space-2) * -1 + 0.3em);
  color: var(--marker);
}

/* Too narrow for three columns: stack them and left-align, since centred and
   right-aligned single lines in a stack read as misalignment rather than as
   intent. */
@media (max-width: 40rem) {
  .footer-inner { grid-template-columns: 1fr; }
  .footer-meta, .footer-links { justify-content: start; text-align: start; }
}
.sep { color: var(--marker); }

/* ----------------------------------------------------------- 4. components */

/* The header's own three lines stay tight (12px / 8px) and the whole group is
   pushed off the content below it, so it reads as one unit rather than as three
   rows spaced like body copy. Same idea as the prose rhythm. */
.page-header {
  margin-bottom: var(--space-7);
  max-width: var(--measure);
}
.page-lede {
  margin-top: var(--space-3);
  color: var(--text-muted);
  max-width: var(--measure);
  text-wrap: pretty;
}
/* Dates and counts. The template's own `time` rule: a block of subdued text
   sitting just under the title. */
.page-meta, .post-meta, .post-card-meta, .post-row-date, .like-card-count,
.like-count, .breadcrumbs {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.page-meta { margin: var(--space-2) 0 0; font-size: 0.9em; }

/* No top margin: `.home-section`'s own 48px is the only gap between sections,
   and the head sits 16px off its content. Stacking both left the space between
   sections barely larger than the space inside one. */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin: 0 0 var(--space-4);
}
.section-head h2 { margin: 0; }
.section-more {
  font-size: 0.8em;
  white-space: nowrap;
}

.breadcrumbs {
  margin-bottom: var(--space-5);
  font-size: 0.8em;
}
.breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin: 0;
}
.breadcrumbs li + li::before {
  content: "/";
  margin-inline-end: var(--space-2);
  color: var(--marker);
}

/* Every panel in the system is this: 4% grey, 4px radius, nothing else. */
.empty, .toc, .callout, .project-card, .like-card, .project-facts,
.pager-link, .like-tile a, .run-stat {
  background: var(--bg-raised);
  border-radius: var(--radius);
  border: 0;
  box-shadow: none;
}

.empty {
  padding: var(--space-6);
  text-align: center;
  color: var(--text-muted);
}

/* buttons */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--bg-raised);
  border: 0;
  border-radius: var(--radius);
  color: var(--primary);
  font-size: 0.9em;
}
.btn:hover { background: var(--link-hover-bg); color: black; }
.btn-primary {
  background: var(--primary);
  color: white;
}
.btn-primary:hover { background: black; color: white; }

/* tags */

.tag-list, .tag-index {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
}
.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}
.tag-cloud-related {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 0.8em;
  white-space: nowrap;
}
.tag-count { color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* post cards and rows */

/* 32px, not 24px: the gap between two cards has to beat the gaps inside one,
   or adjacent entries read as one continuous column of text. */
.post-list { display: flex; flex-direction: column; }
.post-card {
  padding-block: var(--space-6);
  border-bottom: 1px solid var(--border);
}
.post-card:first-child { padding-top: 0; }
.post-card-title { margin: 0; }
/* meta belongs to the title (8px); the excerpt is its own block (16px). */
.post-card-meta { margin: var(--space-2) 0 0; }
.post-card-excerpt { margin: var(--space-4) 0 0; }
.post-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: 0.8em;
}
.post-card-excerpt {
  color: var(--text-muted);
  max-width: var(--measure);
  text-wrap: pretty;
}

.post-rows { list-style: none; padding: 0; }
.post-row + .post-row { border-top: 1px solid var(--border); }
.post-row a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-bottom: 0;
}
.post-row-date {
  flex-shrink: 0;
  font-size: 0.8em;
}

@media (max-width: 30rem) {
  .post-row a { flex-direction: column; gap: var(--space-1); }
}

/* The home page's compact project row. Same density as .post-row — a title and
   one line — so all three home sections read at one weight. The full card, with
   its status badge and stack list, stays on /projects/. */
.project-rows { list-style: none; padding: 0; }
.project-row + .project-row { border-top: 1px solid var(--border); }
.project-row a {
  display: block;
  padding-block: var(--space-3);
  border-bottom: 0;
}
.project-row-title { display: block; }
.project-row-desc {
  display: block;
  margin-top: var(--space-1);
  font-size: 0.85em;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* projects */

.project-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
}
.project-card {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.project-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.project-card-title { margin: 0; font-size: 1.1em; }
.project-card-desc {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9em;
  text-wrap: pretty;
}

.badge {
  flex-shrink: 0;
  padding: 0.1rem var(--space-2);
  border-radius: var(--radius);
  font-size: 0.7em;
  color: var(--text-muted);
  background: var(--bg);
}
.badge-active { color: var(--primary); font-weight: bold; }
.badge-paused, .badge-archived { color: var(--text-muted); }

.stack-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  list-style: none;
  padding: 0;
  margin-top: auto;
  font-size: 0.75em;
  color: var(--text-muted);
}
.stack-list li:not(:last-child)::after { content: " ·"; }

.project-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.project-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-5);
  margin-block: var(--space-6);
  padding: var(--space-4);
  font-size: 0.9em;
}
.project-facts dt { color: var(--text-muted); }
.project-facts dd { margin: 0; }

.project-hero, .post-hero { margin-block: var(--space-6); }
.post-hero figcaption, .project-hero figcaption {
  margin-top: var(--space-2);
  font-size: 0.8em;
  color: var(--text-muted);
  text-align: center;
}

/* likes */

.like-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
}
.like-card { padding: var(--space-5); }
.like-card-title { margin: 0; font-size: 1.1em; }
.like-card-title a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* Three levels inside one card. `desc` (what this topic is) and `preview` (what
   is in it) used to share a font-size, a colour and a gap, which left them
   indistinguishable; `preview` and `count` now drop a step in both size and tone
   and sit 16px away as secondary matter. --text-faint on --bg-raised is 6.19:1,
   so the quieter tone still passes AA. */
.like-card-desc {
  margin: var(--space-2) 0 0;
  font-size: 0.9em;
  color: var(--text-muted);
}
.like-card-preview {
  margin: var(--space-4) 0 0;
  font-size: 0.82em;
  color: var(--text-faint);
  text-wrap: pretty;
}
.like-card-count {
  margin: var(--space-4) 0 0;
  font-size: 0.75em;
  color: var(--text-faint);
}

.like-icon { line-height: 1; }
.like-icon-lg { font-size: 0.85em; }

.like-tiles {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
  list-style: none;
  padding: 0;
}
.like-tile a {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border-bottom: 0;
}
.like-name { font-size: 0.9em; }
.like-count { font-size: 0.75em; }

.like-items { list-style: none; padding: 0; }
.like-item {
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--border);
}
.like-item-name { margin: 0; }
.like-item-by { margin: var(--space-1) 0 0; font-size: 0.9em; color: var(--text-muted); }
.like-item-note {
  margin: var(--space-2) 0 0;
  color: var(--text-muted);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* running */

/* This page runs full-width, against the site's `--measure` default: it is a log,
   read by scanning a column of figures rather than by reading sentences, and the
   totals row and the table both want the whole 63em container. Capping the prose
   at 691px next to a 1111px table made the paragraphs look like they broke
   mid-column. The override is scoped to `.page-running` so §2.4's one-token rule
   still holds for every other page.

   The cost is line length: at 1280px the body runs ~115 Latin characters per
   line, above §2.4's 45–75 target. It is two short paragraphs of preamble above a
   table, not a long read, which is why it is acceptable here and would not be on
   a post. */
.page-running .prose,
.page-running .page-header,
.page-running .page-lede { max-width: none; }

/* The totals row. Fixed column counts, not the `auto-fit`/`auto-fill` minmax the
   other grids use, because five is a prime the template fixes: an intrinsic grid
   walks 5 → 4+1 → 3+2 → 2+2+1 as the viewport narrows, and 4+1 strands one panel
   beside three empty slots. Measured with `auto-fit` at 11rem, that shape held
   from 740px to 920px — the widest band of the ladder, and the hole-in-the-row
   defect §3.11 rejects. Stepping the count explicitly skips it. 3+2 leaves one
   empty slot and is the least-bad intermediate: with five items only a 5- or
   1-column grid is holeless, and 1-column at tablet width would be a very long
   stack.

   The breakpoints are where the widest panel stops fitting, not round numbers.
   The widest is English "Average pace" — the label, not the figure — needing
   122px with its 32px of padding. `body` is content-box with 6vw gutters, so the
   container measures 0.863x the viewport (measured, not derived). Five tracks
   need 5x122 + 4x12 = 658px of container, i.e. 763px of viewport; three need
   390px, i.e. 453px. 48rem would clear the five-across case by 5px, which is
   inside the error of a font-metric measurement — 52rem gives each track 133.8px
   against the 122px needed, and 30rem gives 130px for the three-across case. */
.run-stats {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(5, 1fr);
  margin: 0 0 var(--space-6);
}
@media (max-width: 52rem) { .run-stats { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 30rem) { .run-stats { grid-template-columns: repeat(2, 1fr); } }
.run-stat { padding: var(--space-4); }
/* Label above figure, and the label is the quiet one: the reader scans the
   numbers and reads the label only when a number needs naming. Same two-step
   pattern as a card's title/meta, inverted in order. */
.run-stat-label {
  font-size: 0.75em;
  color: var(--text-faint);
}
.run-stat-value {
  margin: var(--space-1) 0 0;
  font-size: 1.2em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The log. Same construction as `.prose table` — collapsed borders, a grey
   header row, no outer frame — because it is the same kind of object; it just
   lives outside `.prose`, so the rules cannot be inherited. */
.run-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
}
.run-table-caption {
  margin-bottom: var(--space-3);
  text-align: start;
  font-size: 0.8em;
  color: var(--text-faint);
}
.run-table th, .run-table td {
  padding: var(--space-2) var(--space-3);
  text-align: start;
  border-bottom: 1px solid var(--border);
}
.run-table thead th {
  background: var(--bg-raised);
  white-space: nowrap;
  font-size: 0.9em;
  color: var(--text-muted);
}
.run-table tbody tr:last-child td,
.run-table tbody tr:last-child th { border-bottom: 0; }
/* The date is a row header, so it is a <th>; without this it would inherit the
   browser's bold and outweigh the figures it labels. Addressed by its class
   rather than by `tbody th` so the class in the markup has a rule behind it —
   §6.5's coverage check treats an unused class as a defect. */
.run-table th.run-cell-date {
  font-weight: normal;
  white-space: nowrap;
  color: var(--text-muted);
}
/* Figures right-aligned so the decimal points and colons stack into a column
   the eye can compare down. Tabular numerals do the rest. Written as two
   element-qualified selectors rather than a bare `.run-cell-num`: the shared
   `.run-table th, .run-table td` rule above is (0,2,1), so a (0,1,0) class
   would lose to it. Matching its specificity and coming later is what carries
   this — no `!important`, which in this stylesheet belongs to focus alone. */
.run-table th.run-cell-num, .run-table td.run-cell-num { text-align: end; }
.run-unit { color: var(--text-faint); font-weight: normal; }

/* Narrow screens. With four columns the binding constraint is the date: "August
   30, 2026" is 138px and `nowrap`, and the four columns then want 360px inside a
   330px container at 375px. Tightening the inline padding to `--space-2` recovers
   16px, and releasing `nowrap` on the date lets the remaining 14px come out of a
   wrap ("August 30," / "2026") rather than out of a scrollbar. The figures keep
   their `nowrap` — a pace broken across two lines is unreadable — but they are
   short enough not to need the room. `.table-wrap` still scrolls as the backstop
   if a future locale's date is longer than either measure allows. */
@media (max-width: 34rem) {
  .run-table th, .run-table td { padding-inline: var(--space-2); }
  .run-table th.run-cell-date { white-space: normal; }
}

/* domains */

/* The domain ledger reuses the running log's table and totals row wholesale —
   `.page-domains` sits alongside `.page-running` on <body> for the full-width
   override, and the table carries both `.run-table` and `.domain-table`. Same
   object, one more column; duplicating 40 lines of border and alignment rules to
   rename them would be two tables to keep in step instead of one.

   Five columns fit 63em without a scroll. Measured at 0.9em: the domain
   (`elephantfridge.com`) 154px with padding, two dates (`September 30, 2026`)
   154px each, and the day count 82px — 544px, leaving 464px for the registrar
   against the 285px that "Hosting Concepts B.V. d/b/a Registrar.eu" needs.
   Below that width `.table-wrap` scrolls, which is the backstop the running
   table already relies on. */

/* The domain name is a row header, and it is what the reader scans down, so it
   keeps full-strength ink — but not the browser's bold. Weight is spent on the
   expiry alert below, and two bold things in one row is neither of them. */
.domain-table th.domain-cell-name {
  font-weight: normal;
  white-space: nowrap;
}
/* The one ragged-text column in an otherwise numeric table. It is the column
   the reader consults rather than scans, so it is quiet, and it is the only one
   allowed to wrap — a 40-character registrar name is what would otherwise push
   the table into a horizontal scroll at full width. */
.domain-table td.domain-cell-registrar {
  color: var(--text-muted);
  font-size: 0.95em;
}
.domain-table td.domain-cell-date {
  white-space: nowrap;
  color: var(--text-muted);
}
/* A missing date is a gap in the data, not a value. Rendering it at
   `--text-faint` says "nothing known here" without an em dash pretending to be
   a figure. */
.domain-blank { color: var(--text-faint); }

/* Near or past expiry. Weight plus full-strength ink against `--text-muted`
   neighbours, and no colour: §2.1 fixes the palette as greyscale with no accent
   hue, so an alert red would be the first colour in the system — introduced for
   a state that is already sorted to the top of the table and already spells out
   its own day count. The signal is carried three ways over, which is also why
   it does not need a fourth. */
.domain-table tbody tr.is-alert th.domain-cell-name,
.domain-table tbody tr.is-alert td { color: var(--text); }
.domain-table tbody tr.is-alert strong { font-weight: 600; }

/* When the figures came from WHOIS. Sits under the table because it qualifies
   the whole table, and is the quietest thing on the page. */
.domain-asof {
  margin: var(--space-3) 0 0;
  font-size: 0.8em;
  color: var(--text-faint);
}

/* The private watchlist (served by a Pages Function, never built into `dist/`).
   Three rules, in the public stylesheet, for a page one person can open — worth
   it because the alternative is an inline <style>, and inline styles would cost
   the site the `style-src 'self'` half of its CSP for every page. The class
   names do reveal that a private page exists; so does the public ledger's own
   prose, on purpose. Existence was never the thing under lock. */
.private-badge {
  margin: 0 0 var(--space-2);
  font-size: 0.75em;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.private-note {
  max-width: var(--measure);
  margin: 0 0 var(--space-5);
  font-size: 0.9em;
  color: var(--text-muted);
}
.private-back { margin: var(--space-5) 0 0; }

/* --------------------------------------------------------------- 5. prose */

/* Prose runs at the body size and leans on the browser's own vertical rhythm —
   `margin: 1em 0` on block elements — rather than on a spacing scale. */
.prose {
  max-width: var(--measure);
}

/* Three-level rhythm. Sections are pushed apart; a heading is pulled toward the
   content it introduces, so it reads as belonging to what follows rather than
   floating between two equal gaps. h2/h3/h4 keep `margin-bottom: 0` — the tight
   gap is given to the successor's margin-top, in the `h2 + *` rule at the end of
   this section. */
.prose h2 { margin-top: var(--flow-section); }
.prose h3 { margin-top: calc(var(--flow-section) * 0.7); }
.prose h4 { margin-top: calc(var(--flow-section) * 0.5); }

/* The permalink stays invisible until its heading is hovered or it is focused,
   so it never clutters the reading view but remains keyboard-reachable. */
.heading-anchor {
  margin-inline-start: 0.4em;
  border-bottom: 0;
  color: var(--border);
  font-weight: normal;
  opacity: 0;
  transition: opacity 0.12s ease;
}
h2:hover > .heading-anchor,
h3:hover > .heading-anchor,
h4:hover > .heading-anchor,
.heading-anchor:focus { opacity: 1; }
.heading-anchor:hover { color: var(--primary); }

.prose ul, .prose ol { padding-inline-start: 1.5em; }
.prose ul.task-list { list-style: none; padding-inline-start: 0; }
.prose .task-list input { margin-inline-end: var(--space-2); }

/* The template's blockquote is a filled panel, not a rule in the margin. */
.prose blockquote {
  padding: 1.5em;
  margin: 1em 0;
  font-size: 0.88em;
  background: var(--bg-raised);
  border-radius: var(--radius);
}
.prose blockquote p { margin: 0; }

.callout {
  padding: 1.5em;
  margin: 1em 0;
  font-size: 0.88em;
}
.callout-label {
  margin: 0 0 var(--space-2);
  font-weight: bold;
  font-size: 0.85em;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.callout p:last-child { margin-bottom: 0; }

/* Code blocks bleed 1em into the gutters, which is the template's way of
   letting them read as a distinct band without a border. */
.prose pre {
  background: var(--bg-code);
  padding: 1px 1em;
  border-radius: 3px;
  font-size: 0.9em;
  overflow: auto;
  margin: 1em -1em;
  display: grid;
}
.prose pre code {
  background: none;
  padding: 0;
  font-size: inherit;
}

.prose figure { margin: var(--space-6) 0; }
.prose figcaption {
  margin-top: var(--space-2);
  font-size: 0.8em;
  color: var(--text-muted);
  text-align: center;
}

hr {
  width: 100%;
  border: 0;
  height: 1px;
  margin: 1.5em 0;
  background: var(--border);
}

.table-wrap { overflow-x: auto; }
.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9em;
}
.prose th, .prose td {
  padding: var(--space-2) var(--space-3);
  text-align: start;
  border-bottom: 1px solid var(--border);
}
.prose th { background: var(--bg-raised); white-space: nowrap; }
.prose tbody tr:last-child td { border-bottom: 0; }

/* Outbound links carry the template's ↗. It is generated content, so it stays
   out of the accessible name and out of copied text. Internal links, footnote
   refs and backrefs get nothing — same exclusion list as the template. */
.prose a.external::after {
  position: relative;
  top: -0.5em;
  font-size: 0.7em;
  content: "↗";
  color: var(--marker);
}

.footnotes {
  margin-top: var(--space-7);
  font-size: 0.9em;
  color: var(--text-muted);
}
.footnotes ol { padding-inline-start: 1.4em; }
.footnote-ref a, .footnote-back { border-bottom: 0; }
.footnote-back { margin-inline-start: 0.3em; }

/* The tight half of the rhythm, last in the prose layer on purpose. It competes
   with `.prose blockquote` and `.prose pre`, which set their own `margin: 1em`
   at the same (0,1,1) specificity, so source order is what makes a code block or
   a quote directly under a heading sit as close as a paragraph does. */
.prose h2 + *, .prose h3 + *, .prose h4 + * { margin-top: var(--flow-tight); }

/* ------------------------------------------------------------ 6. post page */

/* The title block shares the prose measure so the h1 wraps on the same column
   the body text does, rather than running 400px wider than the article. */
.post-header {
  margin-bottom: var(--space-6);
  max-width: var(--measure);
}
.post-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  font-size: 0.8em;
}

.toc {
  margin-block: var(--space-6);
  padding: var(--space-4) var(--space-5);
  font-size: 0.9em;
  /* Tied to the measure: a table of contents wider than the prose it indexes
     reads as a second column rather than as part of the article. */
  max-width: var(--measure);
}
.toc-title {
  margin: 0;
  font-size: 0.85em;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.toc > ol {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  counter-reset: toc;
}
.toc ol { list-style: none; padding: 0; }
.toc li { counter-increment: toc; margin-block: var(--space-1); }
.toc > ol > li > a::before {
  content: counter(toc) ".";
  margin-inline-end: var(--space-2);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.toc ol ol { padding-inline-start: var(--space-5); margin-top: var(--space-1); }

.post-pager {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}
.pager-link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-bottom: 0;
}
.pager-link.is-empty { background: none; }
.pager-next { text-align: end; }
.pager-label {
  font-size: 0.7em;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.pager-title { font-size: 0.9em; }

@media (max-width: 34rem) {
  .post-pager { grid-template-columns: 1fr; }
  .pager-next { text-align: start; }
  .pager-link.is-empty { display: none; }
}

/* Related posts, as the template's note pages do it: one grey panel per entry
   ("Notes mentioning this note" -> .backlink-box), not a run of rules. The
   template's sits in a 3fr 1fr side column; Yonnia has no backlink sidebar, so
   the same panels run full width under the article and reflow into a grid when
   there is room. */
.related {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}
.related h2 { margin-top: 0; font-size: 1.1em; }
.related .post-rows {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  margin-top: var(--space-4);
}
.related .post-row {
  background: var(--bg-raised);
  border-radius: var(--radius);
  border-top: 0;
}
.related .post-row a {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-4);
}
.related .post-row-date { font-size: 0.75em; color: var(--text-faint); }

/* ------------------------------------------------------------ 7. home page */

/* The masthead owns the first screen. On a personal site the subject of the home
   page is "who is this", so the name and one line of bio get the whole viewport
   and the index starts one scroll down.

   The height is `min-height` against one screen less the chrome above and below,
   not `flex-grow`: growing needs definite free space, and there is none here —
   the sections below already make `main` taller than the viewport, so a flex item
   has nothing left to claim and collapses to its content.

   Three things this has to get right, or an empty first screen reads as a broken
   page rather than as a deliberate one:

   - min-height, never height: on a landscape phone (~400px tall) one screen is
     shorter than the hero's own content, and min-height overflows downward
     instead of clipping the bio.
   - dvh, with vh as the fallback: mobile Safari counts the retractable address
     bar in vh, so a 100vh hero jumps as the bar hides. dvh tracks the live
     viewport, so it does not.
   - a visible cue at the bottom, since a screen with nothing below the fold gives
     the reader no reason to scroll. `.hero-more` is that cue: a plain in-page
     anchor, because the site ships no JavaScript. */
.hero {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--home-chrome));
  min-height: calc(100dvh - var(--home-chrome));
  padding-bottom: var(--space-7);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-7);
}
.hero-greeting {
  margin: 0;
  font-size: 0.8em;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The masthead. The template has no display step above h1, so this is just an
   h1 at the browser's own 2em. Headings keep margin-bottom: 0, so the space
   below comes from the next block's own top margin. */
.hero-title { margin: 0.2em 0 0; }
/* The alternate spelling, deliberately small and quiet. It has to be prose under
   the h1 rather than part of it: the alias is only worth anything while exactly
   one spelling is the indexed brand, so `Vackie` must stay out of the <h1>, the
   <title> and og:site_name (see the branding note in README). */
.hero-aka {
  margin-top: var(--space-2);
  font-size: 0.8em;
  color: var(--text-faint);
}
.hero-bio {
  margin-top: var(--space-4);
  color: var(--text-muted);
  max-width: var(--measure);
  text-wrap: pretty;
}
.hero-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-6);
  font-size: 0.9em;
}

/* `margin-top: auto` is what pins this to the bottom of the flex column, so the
   cue sits at the fold however tall the viewport is. */
.hero-more {
  margin-top: auto;
  padding-top: var(--space-7);
  font-size: 0.9em;
}
.hero-more a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border-bottom: 0;
  color: var(--text-muted);
  padding: 0;
}
.hero-more a:hover { color: black; background: none; }
.hero-more-arrow { color: var(--marker); }
.hero-more a:hover .hero-more-arrow { color: black; }

/* A short viewport has no room to give a screen away, so the hero stops growing
   and falls back to its natural height, and the cue — which would now point at
   content already in view — is removed rather than left saying something
   untrue. */
@media (max-height: 34rem) {
  .hero { flex-grow: 0; }
  .hero-more { display: none; }
}

.home-section + .home-section { margin-top: var(--space-7); }

/* -------------------------------------------------------- 8. static pages */

.profile-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  list-style: none;
  padding: 0;
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  font-size: 0.9em;
}

.contact-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-6);
  align-items: baseline;
}
.contact-list dt { color: var(--text-muted); font-size: 0.9em; }
.contact-list dd { margin: 0; }

@media (max-width: 26rem) {
  .contact-list { grid-template-columns: 1fr; gap: var(--space-1); }
  .contact-list dd { margin-bottom: var(--space-3); }
}

.notfound { padding-block: var(--space-7); text-align: center; }
.notfound h1 { font-size: 4em; color: var(--border); line-height: 1; }
.notfound .page-lede { margin-inline: auto; }
.notfound-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  font-size: 0.9em;
}

/* ------------------------------------------------------------- 9. printing */

@page { margin: 18mm 16mm; }

@media print {
  :root {
    --bg: #fff; --bg-raised: #fff; --bg-code: #f6f6f6;
    --bg-inline-code: #f6f6f6;
    --text: #000; --text-muted: #333; --text-faint: #666;
    --primary: #000; --border: #ccc; --marker: #666;
    --link-hover-bg: transparent;
  }
  .site-header, .site-footer, .toc, .post-pager, .related,
  .skip-link, .tag-cloud, .lang-switch, .heading-anchor { display: none; }
  body { font-size: 11pt; max-width: none; padding: 0; }
  .prose { max-width: none; }
  /* Show link targets in print, where a hyperlink cannot be followed. */
  .prose a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #555;
    word-break: break-all;
  }
  .prose h2, .prose h3 { break-after: avoid; }
  .prose pre, .prose figure, .prose table { break-inside: avoid; }
  .project-card, .like-card, .callout { break-inside: avoid; }
}
