:root {
  color-scheme: light dark;
  --fg: #16181d;
  --bg: #ffffff;
  --muted: #5b6169;
  --border: #e6e8eb;
  --code-bg: #f4f5f6;
  --accent: hsl(210 45% 42%);
  --accent-soft: hsl(210 45% 42% / 0.1);
  --code-type: hsl(160 45% 30%);
  --code-string: hsl(25 65% 40%);
  --callout-bg: #f5a623;
  --callout-fg: #1a1200;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #e6e8eb;
    --bg: #12141a;
    --muted: #9298a3;
    --border: #2a2d35;
    --code-bg: #1a1d24;
    --accent: hsl(210 70% 72%);
    --accent-soft: hsl(210 70% 72% / 0.12);
    --code-type: hsl(160 55% 62%);
    --code-string: hsl(30 70% 68%);
    --callout-bg: #b8860b;
    --callout-fg: #0d0800;
  }
}

* {
  box-sizing: border-box;
}

html {
  -webkit-font-smoothing: antialiased;
}

body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.65;
  color: var(--fg);
  background: var(--bg);
}

.page {
  max-width: 42rem;
  margin: 0 auto;
  padding: 2rem 1.25rem 4rem;
}

code, pre, .mono {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

code {
  background: var(--code-bg);
  padding: 0.15em 0.4em;
  border-radius: 4px;
  font-size: 0.88em;
}

pre {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem 1.1rem;
  overflow-x: auto;
  font-size: 12px;
}

pre code {
  background: none;
  padding: 0;
  font-size: 1em;
}

/* Every fenced code block is wrapped in this (see the markdown-it fence
   override in .eleventy.js) — the box styling lives here instead of on
   <pre> directly, so the toolbar and the code share one continuous
   border/background rather than reading as two stacked boxes. */
.code-block {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.code-block pre {
  background: none;
  border-top: none;
  /* .code-block's own border is what normally frames this box, but a
     horizontally-scrollable pre (overflow-x: auto with actual overflow)
     gets promoted to its own compositing layer, and that layer's edge can
     paint over the ancestor's 1px border along pre's height, leaving the
     side/bottom border looking like it's missing while the toolbar above
     (not scrollable, not composited) still shows its border fine. Drawing
     the same border directly on pre too — redundant with .code-block's,
     but the same color so the overlap is invisible — makes the frame
     survive regardless of that compositing quirk. */
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  border-bottom-left-radius: 8px;
  border-bottom-right-radius: 8px;
  margin: 0;
}

/* The vertical rhythm lives on the wrapper, not on the pre above, whose own
   margin has to stay 0 so it sits flush under the toolbar. Without it a code
   block has no margin of its own at all, and two consecutive blocks stack
   edge to edge — a block next to a paragraph only looks spaced because the
   paragraph's margin is doing the work. Matching .post p's 1.1rem means these
   margins collapse to the same gap paragraphs already get, so nothing else
   shifts. */
.code-block-hover-zone {
  margin: 1.1rem 0;
}

.code-block-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4rem 0.75rem;
  border-bottom: 1px solid var(--border);
}

.code-block-lang {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}

.code-block-copy {
  /* Not `inherit` — a code block nested inside .aside would otherwise pick
     up its serif font, making this one button look inconsistent with
     every other copy button on the site depending on where it happens to
     be nested. */
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 0.7rem;
  color: var(--muted);
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.15rem 0.55rem;
  cursor: pointer;
  transition: color 0.1s ease, border-color 0.1s ease;
}

.code-block-copy:hover {
  color: var(--fg);
  border-color: var(--accent);
}

.code-block-copy[data-copied="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

/* Prism (via @11ty/eleventy-plugin-syntaxhighlight) token colors — a
   restrained 4-color set rather than a full rainbow theme, matching the
   site's existing minimal palette. */
.token.comment {
  color: var(--muted);
  font-style: italic;
}

.token.string,
.token.number,
.token.macro,
.token.boolean {
  color: var(--code-string);
}

.token.keyword {
  color: var(--accent);
}

.token.builtin,
.token.class-name,
.token.type-definition,
.token.function,
.token.function-definition,
.token.namespace,
.token.module-declaration,
.token.table {
  color: var(--code-type);
}

.token.punctuation,
.token.closure-punctuation,
.token.operator {
  color: var(--muted);
}

/* Line highlighting, via the syntax-highlight plugin's info-string ranges
   (```rust/2-4). Note those ranges are 0-indexed. The plugin wraps every line
   in .highlight-line and marks the selected ones with <mark>, which would
   otherwise render with the browser's default yellow — unreadable against the
   dark theme's code background. */
.highlight-line {
  display: inline-block;
  width: 100%;
}

mark.highlight-line-active {
  position: relative;
  background: var(--accent-soft);
  /* The tint bleeds out over the block's own padding on both sides, so a
     highlighted line reads as a full-width band without the code itself
     shifting relative to the unhighlighted lines around it. */
  box-shadow: -1.1rem 0 0 var(--accent-soft), 1.1rem 0 0 var(--accent-soft);
  color: inherit;
}

/* The accent bar belongs out in that left padding, as a gutter rule. Drawn as
   an inset border on the <mark> instead, it lands hard against the line's
   first character and reads as a rendering artifact rather than a marker. */
mark.highlight-line-active::before {
  content: "";
  position: absolute;
  left: -1.1rem;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--accent);
}

/* Collapsible regions, from the // <fold label> markers handled in
   .eleventy.js. The toggle is a <button> (phrasing content) rather than
   <details>/<summary> (flow content) so the markup stays valid inside <pre>.

   The dashed border sits on the wrapper rather than the toggle, so it encloses
   whatever is currently visible: just the label while collapsed, label plus
   revealed code once expanded. inline-block (not block) is load-bearing — the
   wrapper is emitted between the newlines that separate the surrounding source
   lines, and inside a <pre> those newlines are real line breaks, so a block box
   here would leave a blank line above and below the region. */
.code-fold {
  display: inline-block;
  vertical-align: top;
  background: var(--code-bg);
  border: 1px dashed var(--border);
  border-radius: 4px;
  padding: 0 0.5em;
}

/* Expanded, the box has actual lines in it and wants breathing room under the
   last one; collapsed, it should stay tight around the label. */
.code-fold:has(.code-fold-toggle[aria-expanded="true"]) {
  padding-bottom: 0.3em;
}

.code-fold:has(.code-fold-toggle:hover) {
  border-color: var(--accent);
}

.code-fold-toggle {
  display: inline-block;
  font-family: inherit;
  font-size: 0.92em;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.code-fold-toggle::before {
  content: "▸ ";
}

.code-fold-toggle[aria-expanded="true"]::before {
  content: "▾ ";
}

.code-fold-toggle:hover {
  color: var(--fg);
}

.code-fold-toggle[aria-expanded="false"] + .code-fold-body {
  display: none;
}

/* Numbered callout badges, from the // <1> markers handled in .eleventy.js,
   and the paragraph-side target they pair with via the {% callout %}
   shortcode. base.njk wires up the hover/tap highlighting between the two;
   this just styles their resting and active states. */

/* The "sticky last column" pattern: pinned to the visible right edge of
   pre's own horizontal scroll (its nearest scrolling ancestor), the same
   0 inset a scrollport-edge sticky element normally uses, relying on pre's
   existing 1.1rem padding for the gap from the border rather than adding a
   second one here. Without this, a badge on a line long enough to need
   scrolling either scrolls out of view with the rest of the line or (worse)
   escapes the block's own clipping — sticky guarantees neither, since it's
   always rendered within its scrolling ancestor's viewport by definition.
   The whole group of badges on one line stick together as a single unit;
   see calloutBadges in .eleventy.js for why. */
.callout-markers {
  display: inline-flex;
  gap: 0.25em;
  position: sticky;
  right: 0;
  margin-left: 0.4em;
  vertical-align: middle;
}

.callout-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6em;
  height: 1.6em;
  border-radius: 50%;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
  background: var(--callout-bg);
  color: var(--callout-fg);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

/* Active state swaps the badge's own yellow for the site's existing accent
   (already how highlighted lines and copy-button feedback read as "active")
   rather than ringing it, so "there's a note here" and "this note is active
   right now" are the same badge in two colors, not two overlapping shapes.
   --bg is the highest-contrast text color against --accent in both themes
   (accent is mid-dark in light mode, light in dark mode). */
.callout-marker.callout-active {
  background: var(--accent);
  color: var(--bg);
}

/* Vertical margin matches .post p's own (1.1rem) so a callout-target
   collapses with its neighbors exactly like a plain paragraph would — no
   vertical padding to reserve room for the ring, since box-shadow paints
   outside the box without needing layout space, and 1.1rem of gap is
   already far more than a few px of ring needs. Horizontal padding plus a
   matching negative margin is still needed there, though: paragraph text
   runs flush to the column's edges with no gap of its own for the ring to
   sit in. */
.callout-target {
  margin: 1.1rem -0.6rem;
  padding: 0 0.6rem;
  border-radius: 6px;
  transition: box-shadow 0.15s ease;
}

/* Without this, the wrapped paragraph's own margin doesn't collapse with
   .callout-target's (blocked by the padding above), so it stacks on top of
   the div's own spacing instead of being absorbed by it — same fix .aside
   needs for the same reason. */
.callout-target > *:first-child {
  margin-top: 0;
}

.callout-target > *:last-child {
  margin-bottom: 0;
}

.callout-target.callout-active {
  box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 1px var(--accent);
}

/* On a wide-enough desktop viewport, a code block wider than the reading
   column has to scroll sideways even though there's plenty of empty page
   margin doing nothing — hovering temporarily widens it into that space.
   .overflows is added by base.njk's script only to blocks that actually
   need it, and this is gated to real mouse pointers so it doesn't trigger
   from a tap-and-hold on touch.

   The hover trigger is .code-block-hover-zone, an invisible wrapper sized
   to the SAME footprint the expanded block will occupy — not the visible
   .code-block itself — so moving the pointer into the empty margin where
   the block is *about* to expand into already counts as hovering it,
   rather than requiring the pointer to already be on the still-narrow box. */
.post .code-block {
  width: 100%;
  transition: width 0.15s ease, box-shadow 0.15s ease;
}

/* The width transition above exists for the hover expansion. It must not run
   when base.njk's script classifies blocks, because applying .overflows makes
   the hover zone 98vw and the block inside it is briefly laid out against that
   full width before 100cqw resolves — so the transition animates it down from
   ~98vw and the reader sees a code block flash open and shrink on load. The
   script sets this class around each programmatic measurement pass. */
html.code-measuring .post .code-block {
  transition: none;
}

@media (hover: hover) and (pointer: fine) and (min-width: 900px) {
  /* Deliberately much wider than the block ever visually expands to (98vw,
     not the ~100ch the block itself caps at below) — this is the invisible
     hover *trigger* area, not the visible result, so it needs generous
     slack. On a merely-wide (not ultra-wide) window the gap between a
     precisely-sized zone and the visible box can be thin enough that
     aiming for the toolbar's copy button overshoots past it and collapses
     the block back down before the click lands. */
  .post .code-block-hover-zone.overflows {
    width: 98vw;
    margin-left: calc((100% - 98vw) / 2);
    margin-right: calc((100% - 98vw) / 2);
  }

  .post .code-block-hover-zone.overflows .code-block {
    /* 100cqw = 100% of the nearest ancestor with container-type set below
       (.post, .aside, or blockquote) — not the hover zone immediately
       wrapping this, which is deliberately much wider. This is what makes
       the block sit at its normal size by default regardless of nesting:
       a code block directly in .post gets .post's own content width, one
       nested inside an .aside or blockquote gets THAT box's narrower
       inner width instead, accounting for its own padding correctly. A
       single hardcoded width couldn't do both. */
    width: 100cqw;
    margin: 0 auto;
  }

  .post .code-block-hover-zone.overflows:hover .code-block {
    position: relative;
    z-index: 2;
    /* ch = the width of "0" in the current (monospace) font, so this is a
       genuine ~100-character cap on the visible block itself — independent
       of the much wider hover zone above, so it stays readable even on an
       ultra-wide viewport. */
    width: min(90vw, 100ch);
    margin: 0 auto;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
  }
}

blockquote {
  margin: 1.25rem 0;
  padding: 0.15rem 1.1rem;
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 8px 8px 0;
  font-size: 15px;
  container-type: inline-size;
}

.aside {
  margin: 1.25rem 0;
  padding: 0.9rem 1.1rem;
  font-family: Georgia, Cambria, "Times New Roman", Times, serif;
  container-type: inline-size;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--code-bg);
}

.aside > *:first-child {
  margin-top: 0;
}

.aside > *:last-child {
  margin-bottom: 0;
}

.aside-label {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-transform: uppercase;
  margin: 0 0 0.6rem;
}

/* GitHub-style [!NOTE] / [!TIP] / [!IMPORTANT] / [!WARNING] / [!CAUTION]
   blockquote alerts, plus [!HINT] (not one of GitHub's own) — see the
   markdown-it core rule in .eleventy.js that detects the marker and adds
   these classes/attributes. A plain blockquote otherwise gets none of this
   and renders as before. */
.alert {
  border-left-color: var(--alert-color, var(--accent));
  /* blockquote's own top padding (0.15rem) is basically nothing — a plain
     blockquote doesn't need more, since its first paragraph's own margin
     provides real spacing, but the label sits flush against that padding
     with no margin of its own above it, unlike the visible gap below the
     last paragraph. Matches that bottom gap instead of blockquote's base. */
  padding-top: 0.9rem;
}

.alert::before {
  content: attr(data-alert-kind);
  display: block;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--alert-color, var(--accent));
  margin-bottom: 0.5rem;
}

.alert[data-alert-kind="note"] {
  --alert-color: hsl(210 45% 42%);
}

.alert[data-alert-kind="tip"] {
  --alert-color: hsl(160 45% 30%);
}

.alert[data-alert-kind="important"] {
  --alert-color: hsl(270 45% 45%);
}

.alert[data-alert-kind="warning"] {
  --alert-color: hsl(35 75% 40%);
}

.alert[data-alert-kind="caution"] {
  --alert-color: hsl(0 65% 45%);
}

.alert[data-alert-kind="hint"] {
  --alert-color: hsl(330 50% 45%);
}

@media (prefers-color-scheme: dark) {
  .alert[data-alert-kind="note"] {
    --alert-color: hsl(210 70% 72%);
  }

  .alert[data-alert-kind="tip"] {
    --alert-color: hsl(160 55% 62%);
  }

  .alert[data-alert-kind="important"] {
    --alert-color: hsl(270 60% 75%);
  }

  .alert[data-alert-kind="warning"] {
    --alert-color: hsl(35 80% 65%);
  }

  .alert[data-alert-kind="caution"] {
    --alert-color: hsl(0 70% 70%);
  }

  .alert[data-alert-kind="hint"] {
    --alert-color: hsl(330 65% 72%);
  }
}

/* Tables */

table {
  margin: 1.75rem auto;
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  font-size: 0.92em;
}

th,
td {
  padding: 0.5rem 1.1rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

th {
  background: var(--code-bg);
  font-weight: 600;
}

tr:last-child td {
  border-bottom: none;
}

.preview-banner {
  position: sticky;
  top: 0;
  z-index: 100;
  padding: 0.5rem 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  background: #f5a623;
  color: #1a1200;
}

.preview-banner a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 0.2em;
}

.preview-banner-live-link {
  text-transform: none;
  font-weight: 500;
  white-space: nowrap;
}

@media (prefers-color-scheme: dark) {
  .preview-banner {
    background: #b8860b;
    color: #0d0800;
  }
}

a {
  color: var(--accent);
  text-decoration-color: var(--accent-soft);
  text-underline-offset: 0.2em;
}

a:hover {
  text-decoration-color: currentColor;
}

.external-link::after {
  /* The icon is a background image on a pseudo-element, not an inline <svg>.
     An svg element is an atomic inline and CSS Text allows a line break at the
     boundary of one, which stranded a sentence's closing period on its own
     line after the link; a word joiner cannot prevent that, because the
     boundary is governed by white-space rather than by adjacent characters.
     This element's content is a single U+2060 WORD JOINER, so it is a
     non-replaced inline carrying zero-width text: no box boundary to break at,
     and no break opportunity in the text either.

     The stroke color is baked into the data URI per theme rather than taken
     from currentColor, so if this rule ever stops matching the link color,
     these two values are what to update. */
  content: "\2060";
  padding-left: 0.9em;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%233B6B9B'%20stroke-width='2.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M18%2013v6a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V8a2%202%200%200%201%202-2h6'/%3E%3Cpolyline%20points='15%203%2021%203%2021%209'/%3E%3Cline%20x1='10'%20y1='14'%20x2='21'%20y2='3'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 0.7em 0.7em;
}

@media (prefers-color-scheme: dark) {
  .external-link::after {
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%2386B8EA'%20stroke-width='2.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M18%2013v6a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V8a2%202%200%200%201%202-2h6'/%3E%3Cpolyline%20points='15%203%2021%203%2021%209'/%3E%3Cline%20x1='10'%20y1='14'%20x2='21'%20y2='3'/%3E%3C/svg%3E");
  }
}

/* Screen-reader-only text: visually hidden but still announced, the
   standard clip-based hiding technique (display:none is skipped by most
   screen readers, unlike this). Pairs with .external-link::after, which is
   aria-hidden, so sighted and non-sighted readers each get exactly one
   signal that the link opens in a new window. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

h1, h2, h3 {
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Header */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  padding-bottom: 1.25rem;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--border);
}

.site-title {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: 1.05rem;
  text-decoration: none;
  color: var(--fg);
}

.site-logo {
  display: block;
  flex: none;
  border-radius: 6px;
}

.site-nav a {
  margin-left: 1.25rem;
  text-decoration: none;
  color: var(--muted);
  font-size: 0.95rem;
}

.site-nav a:hover {
  color: var(--fg);
}

/* Post list (homepage) */

.post-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.post-list-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.75rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--border);
}

/* The wrapper takes the full-width flex basis, not the <a> itself. A flex
   item is blockified, so putting flex-basis: 100% on the anchor gave it a
   click target spanning the whole row — an invisible strip sitting one
   0.15rem gap above the title, easy to hit when aiming slightly high. Same
   structure the post page's .post-series-label uses. */
.post-list-series {
  flex-basis: 100%;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.post-list-series a {
  color: var(--accent);
  text-decoration: none;
}

.post-list-series a:hover {
  text-decoration: underline;
}

.post-list-more {
  margin-top: 1.5rem;
}

.post-list-title {
  font-weight: 500;
  text-decoration: none;
  color: var(--fg);
}

.post-list-title:hover {
  color: var(--accent);
}

.post-list-date {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.88rem;
  white-space: nowrap;
}

.empty-state {
  color: var(--muted);
}

/* Post page */

.post {
  container-type: inline-size;
}

.post-header {
  margin-bottom: 2rem;
}

.post-series-label {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 0.6rem;
}

.post-series-label a {
  color: var(--accent);
  text-decoration: none;
}

.post-series-label a:hover {
  text-decoration: underline;
}

.post-header h1 {
  margin: 0 0 0.4rem;
  font-size: 1.75rem;
}

.post-date {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0;
}

.post p {
  margin: 1.1rem 0;
}

.post img,
.post svg {
  max-width: 100%;
  height: auto;
  display: block;
  margin: 1.75rem auto;
}

.post-nav {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.post-nav-item {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  max-width: 48%;
  min-width: 0;
}

.post-nav-next {
  margin-left: auto;
  text-align: right;
}

.post-nav-label {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-transform: uppercase;
}

.post-nav-item a {
  font-size: 0.85rem;
}

/* Archive pagination */

.pagination {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.pagination-item {
  min-width: 0;
  flex: 1;
}

.pagination-next {
  text-align: right;
}

.pagination-status {
  color: var(--muted);
  font-size: 0.85rem;
  white-space: nowrap;
}

/* Series */

.series-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.series-list-item {
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--border);
}

.series-list-title {
  font-weight: 600;
  font-size: 1.1rem;
  text-decoration: none;
  color: var(--fg);
}

.series-list-title:hover {
  color: var(--accent);
}

.series-list-description {
  margin: 0.3rem 0 0;
  color: var(--muted);
}

/* About page */

.social-links {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.social-links a {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  text-decoration: none;
  color: var(--fg);
  font-size: 0.9rem;
}

.social-links a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Footer */

.site-footer {
  margin-top: 3.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.85rem;
}
