:root {
  --ink: #090b0f;
  --ink-soft: #10141a;
  --panel: #141922;
  --panel-soft: #191f28;
  --paper: #f1f3f5;
  --paper-dim: #b8bec8;
  --muted: #7e8794;
  --line: rgba(241, 243, 245, 0.16);
  --line-strong: rgba(241, 243, 245, 0.34);
  --white: #ffffff;
  --cyan: #8fd6df;
  --yellow: #e1c46f;
  --coral: #e4897f;
  --surface-rgb: 255, 255, 255;
  --surface-soft: rgba(255, 255, 255, 0.035);
  --surface: rgba(255, 255, 255, 0.045);
  --surface-strong: rgba(255, 255, 255, 0.06);
  --background-grid: rgba(255, 255, 255, 0.018);
  --header-transparent: rgba(9, 11, 15, 0);
  --header-solid: rgba(9, 11, 15, 0.88);
  --mobile-nav-bg: rgba(9, 11, 15, 0.98);
  --prose-ink: #d6dae0;
  --code-bg: #0b0f14;
  --code-gutter: #111720;
  --code-text: #e8ebef;
  --code-muted: #697381;
  --code-border: rgba(241, 243, 245, 0.15);
  --code-copy-bg: rgba(9, 11, 15, 0.88);
  --code-copy-text: #b8bec8;
  --code-shadow: 0 18px 48px rgba(0, 0, 0, 0.24);
  --code-keyword: #f09a94;
  --code-string: #9fd7ad;
  --code-number: #e1c46f;
  --code-comment: #74808e;
  --code-function: #8fd6df;
  --code-variable: #c9b1f2;
  --code-operator: #b8bec8;
  /* Route transitions take their colours from the active theme: a dark screen
     with a luminous seam in dark mode, a pale screen with an ink seam in light
     mode. Nothing here is meant to be theme-independent. */
  --route-veil: #080a0d;
  --route-line: #ffffff;
  --route-glow: rgba(255, 255, 255, 0.88);
  /* The seam keeps only this soft halo once it settles as a divider. */
  --route-glow-soft: rgba(255, 255, 255, 0.26);
  --theme-veil: #080a0d;
  --scene-acrylic: rgba(9, 11, 15, 0.64);
  --scene-grid: rgba(196, 224, 236, 0.145);
  --scene-sweep: rgba(214, 240, 255, 0.5);
  /* The wallpaper carries all scene blur itself. A backdrop-filter on the
     acrylic layer would re-blur the full screen on every parallax frame. */
  --scene-wallpaper-blur: 46px;
  --article-surface: #0f141c;
  --article-edge: rgba(180, 222, 236, 0.42);
  --article-glow: rgba(143, 214, 223, 0.16);
  --header-height: 76px;
  --content: 1160px;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-sharp: cubic-bezier(0.77, 0, 0.18, 1);
  --ease-curtain: cubic-bezier(0.74, 0, 0.18, 1);
  /* Post entry choreography. Everything is driven by CSS delays so the JS
     only has to toggle classes; tune these to re-time the sequence.

     Three phases: the seam plays alone on a flat plate in the panel colour,
     then the bar drops in just before the line lands, then the article
     blocks fly in — and only after the last one has landed does the scene
     (wallpaper, grid, sweep) fade up. */
  --ease-travel: cubic-bezier(0.22, 1, 0.28, 1);
  --post-veil-duration: 1200ms;
  --post-line-extend-delay: 500ms;
  --post-line-extend-duration: 900ms;
  --post-line-travel-duration: 1400ms;
  /* Anchor: the moment the seam touches the header's lower edge. */
  --post-line-landing: calc(
    var(--post-line-extend-delay)
    + var(--post-line-extend-duration)
    + var(--post-line-travel-duration)
  );
  /* The bar is allowed to start before the line is done, so the last stretch
     of the travel is no longer dead time. */
  --post-bar-lead: 400ms;
  --post-bar-delay: calc(var(--post-line-landing) - var(--post-bar-lead));
  --post-bar-duration: 600ms;
  --post-bar-distance: 64px;
  --post-header-delay: 2900ms;
  --post-header-duration: 800ms;
  /* Leading layer: the body column. */
  --post-fly-delay: 2950ms;
  --post-fly-duration: 1200ms;
  --post-fly-stagger: 140ms;
  --post-fly-distance: 48px;
  --post-fly-blur: 6px;
  /* Second layer: cover and contents panel — shorter travel, softer blur. */
  --post-fly-distance-soft: 30px;
  --post-fly-blur-soft: 3px;
  --post-fly-duration-soft: 1000ms;
  /* Trailing layer: footer and prev/next — a plain lift, no blur. */
  --post-fly-distance-tail: 18px;
  --post-fly-duration-tail: 850ms;
  --font-sans: "Microsoft YaHei", "PingFang SC", "Noto Sans SC", "Helvetica Neue", sans-serif;
  --font-mono: "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono", "Courier New", monospace;
}

html[data-theme="light"] {
  --ink: #f4f5f2;
  --ink-soft: #eceee9;
  --panel: #e6e9e2;
  --panel-soft: #dfe3db;
  --paper: #15181c;
  --paper-dim: #4e555d;
  --muted: #747b83;
  --line: rgba(21, 24, 28, 0.14);
  --line-strong: rgba(21, 24, 28, 0.3);
  --white: #080a0d;
  --cyan: #08798a;
  --yellow: #946b0f;
  --coral: #ad493f;
  --surface-rgb: 21, 24, 28;
  --surface-soft: rgba(21, 24, 28, 0.035);
  --surface: rgba(21, 24, 28, 0.055);
  --surface-strong: rgba(21, 24, 28, 0.08);
  --background-grid: rgba(21, 24, 28, 0.035);
  --header-transparent: rgba(244, 245, 242, 0);
  --header-solid: rgba(244, 245, 242, 0.9);
  --mobile-nav-bg: rgba(244, 245, 242, 0.98);
  --prose-ink: #30363d;
  --code-bg: #fbfcfa;
  --code-gutter: #edf0ea;
  --code-text: #23282e;
  --code-muted: #88909a;
  --code-border: rgba(21, 24, 28, 0.14);
  --code-copy-bg: rgba(236, 239, 233, 0.94);
  --code-copy-text: #4e555d;
  --code-shadow: 0 18px 48px rgba(21, 24, 28, 0.09);
  --code-keyword: #a23b37;
  --code-string: #39754c;
  --code-number: #8b650d;
  --code-comment: #8a9199;
  --code-function: #086d7d;
  --code-variable: #7853a6;
  --code-operator: #58616b;
  --theme-veil: #f2f3ef;
  --scene-acrylic: rgba(244, 245, 242, 0.76);
  --scene-grid: rgba(22, 26, 31, 0.13);
  --scene-sweep: rgba(0, 0, 0, 0.42);
  --article-surface: #eef0ea;
  --article-edge: rgba(8, 121, 138, 0.42);
  --article-glow: rgba(8, 121, 138, 0.12);
  /* Light mode flips the transition screen too: pale plate, ink seam. */
  --route-veil: #eef1ee;
  --route-line: #0b0d10;
  --route-glow: rgba(11, 14, 18, 0.45);
  --route-glow-soft: rgba(11, 14, 18, 0.24);
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  background: var(--ink);
  color-scheme: dark;
}

html[data-theme="light"] {
  color-scheme: light;
}

body {
  margin: 0;
  min-width: 320px;
  color: var(--paper);
  background:
    linear-gradient(var(--background-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--background-grid) 1px, transparent 1px),
    var(--ink);
  background-size: 42px 42px;
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.intro-running,
body.nav-open {
  overflow: hidden;
}

::selection {
  color: var(--ink);
  background: var(--paper);
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
textarea {
  font: inherit;
}

button {
  color: inherit;
}

img {
  display: block;
  max-width: 100%;
}

.icon {
  display: block;
  flex: 0 0 auto;
}

.app-shell {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  overflow: clip;
}

.scene-background {
  position: fixed;
  z-index: 0;
  inset: 0;
  overflow: hidden;
  background: var(--ink);
  pointer-events: none;
}

html:not(.js) .scene-background {
  display: none;
}

/* Reveal is the plate dissolving, never the scene fading in.

   A wallpaper that animates its own opacity gets its blur() rasterised late:
   the compositor skips the filter while the layer is transparent and only
   computes it once the fade starts, so the first frames land sharp and then
   snap to full blur. Keeping the filtered layer permanently opaque means the
   blur is computed up front, and the fade happens on a flat colour above it —
   which is also far cheaper to composite. */
.scene-background__plate {
  position: absolute;
  inset: 0;
  /* Same tiled backdrop as body, so the page looks untouched before reveal. */
  background:
    linear-gradient(var(--background-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--background-grid) 1px, transparent 1px),
    var(--ink);
  background-size: 42px 42px;
  opacity: 1;
  /* Hiding is quick; revealing is the slow, visible direction. */
  transition: opacity 260ms var(--ease-out);
  will-change: opacity;
}

.scene-background.is-visible .scene-background__plate {
  opacity: 0;
  transition: opacity var(--scene-reveal-duration, 900ms) var(--ease-out);
}

/* Post pages hold the scene back until the article has landed, and until then
   the plate is a flat panel tone rather than the tiled body backdrop. */
body.is-post .scene-background__plate {
  background: var(--article-surface);
}

.scene-background__wallpaper {
  position: absolute;
  top: -6%;
  left: -6%;
  width: 112%;
  /* The global img rule caps width at 100%, which would shrink the overscan
     and leave a gap on the right once the layer is offset. */
  max-width: none;
  height: 112%;
  object-fit: cover;
  object-position: center;
  /* Permanently opaque: the plate above it does the revealing. Fading this
     layer itself is what made the compositor rasterise blur() mid-animation
     and flash one sharp frame before the filter landed. */
  filter: blur(var(--scene-wallpaper-blur)) saturate(1.08) brightness(0.86);
  transform: translate3d(0, 0, 0) scale(1.02);
  will-change: transform;
}

.scene-background__acrylic {
  position: absolute;
  inset: 0;
  background: var(--scene-acrylic);
}

.scene-background__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* The grid trails the wallpaper, so the scene arrives in two beats instead
     of as one flat block. Hiding stays quick to match the plate. */
  opacity: 0;
  transition: opacity 260ms var(--ease-out);
  will-change: transform;
}

.scene-background.is-visible .scene-background__canvas {
  opacity: 1;
  transition: opacity var(--scene-grid-duration, 700ms) var(--ease-out)
    var(--scene-grid-delay, 250ms);
}

.theme-transition {
  position: fixed;
  z-index: 265;
  inset: 0;
  display: none;
  background: var(--theme-veil);
  pointer-events: none;
  transform: translateY(100%);
}

html.js .theme-transition.is-running,
html.js .theme-transition.is-leaving {
  display: block;
}

.theme-transition.is-running {
  animation: theme-veil-cover 360ms var(--ease-curtain) both;
}

.theme-transition.is-leaving {
  animation: theme-veil-reveal 520ms var(--ease-curtain) both;
}

@keyframes theme-veil-cover {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

@keyframes theme-veil-reveal {
  from { transform: translateY(0); }
  to { transform: translateY(-100%); }
}

.main-content {
  min-height: 70vh;
}

.micro-label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  line-height: 1.4;
  text-transform: uppercase;
}

.micro-label::before {
  width: 24px;
  height: 1px;
  background: currentColor;
  content: "";
}

.button {
  position: relative;
  display: inline-flex;
  min-height: 48px;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 0 20px;
  border: 1px solid var(--line-strong);
  background: transparent;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1;
  cursor: pointer;
  transition:
    color 220ms ease,
    background 220ms ease,
    border-color 220ms ease,
    transform 220ms var(--ease-out);
}

.button::before {
  position: absolute;
  top: -1px;
  right: -1px;
  width: 8px;
  height: 8px;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  content: "";
}

.button:hover {
  transform: translateY(-2px);
  border-color: var(--paper);
}

.button:focus-visible,
.site-nav__icon:focus-visible,
.theme-toggle:focus-visible,
.nav-toggle:focus-visible,
.post-card__open:focus-visible,
.archive-row:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 4px;
}

.button--solid {
  color: var(--ink);
  border-color: var(--paper);
  background: var(--paper);
}

.button--solid:hover {
  color: var(--ink);
  background: var(--white);
}

.button--ghost:hover {
  background: var(--surface-strong);
}

/* Header */
.site-header {
  position: fixed;
  /* Frozen above every route overlay (route-exit 260, home-return 245,
     route-transition 240) so the bar stays put instead of being veiled,
     blurred or scaled with the page shell. */
  z-index: 261;
  top: 0;
  right: 0;
  left: 0;
  display: flex;
  height: var(--header-height);
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(20px, 4vw, 64px);
  border-bottom: 1px solid transparent;
  background: var(--header-transparent);
  transition:
    height 260ms ease,
    background 260ms ease,
    border-color 260ms ease,
    /* backdrop-filter is deliberately not transitioned: interpolating a blur
       radius re-samples the whole backdrop every frame, and the bar is a
       full-width fixed element, so that cost lands on every scroll frame.
       The blur now switches instantly while the background still eases,
       which reads the same because the two land together. */
    opacity 500ms var(--ease-out),
    filter 500ms var(--ease-out),
    transform 500ms var(--ease-out);
}

/* Post pages keep the seam line as a permanent divider under the bar. It
   lives on the header so it tracks the bar's height (76px -> 64px) instead
   of being pinned to a fixed offset. */
body.is-post .site-header::after {
  position: absolute;
  right: 0;
  bottom: -1px;
  left: 0;
  height: 1px;
  background: var(--line);
  box-shadow: 0 0 8px var(--route-glow-soft);
  content: "";
  pointer-events: none;
}

/* Hand over from the travelling seam: the divider only appears once the line
   has landed (extend + travel), so there is never a second line on screen.
   Scoped to route-ready — not route-animating — so the animation survives
   the scroll lock being lifted early (see the enter handler in the JS). */
html.js body.is-post.route-ready .site-header::after {
  animation: post-divider-in 260ms var(--ease-out) var(--post-line-landing) both;
}

@keyframes post-divider-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* One line only: the divider replaces the header's own scrolled border. */
body.is-post .site-header.is-scrolled {
  border-color: transparent;
}

/* Blackout: while the seam plays, nothing else may be on screen — the frozen
   bar included. The JS drops this class shortly before the line lands. */
html.js body.route-blackout .site-header {
  opacity: 0;
  pointer-events: none;
}

/* The bar now outranks the opener too, so it has to step aside while the
   intro plays — otherwise it floats over the full-screen animation. */
html.js body.intro-running .site-header {
  opacity: 0;
  pointer-events: none;
}

/* The bar is frozen, not inert: it drops in from above and fades up a beat
   before the seam lands, so the tail of the travel is spent on the bar
   arriving rather than on an empty screen. It settles almost fully by the
   time the line touches down, which keeps the divider hand-off aligned. */
html.js body.is-post.route-ready:not(.route-leaving) .site-header {
  animation: post-bar-in var(--post-bar-duration) var(--ease-out)
    var(--post-bar-delay) both;
}

@keyframes post-bar-in {
  from {
    opacity: 0;
    transform: translateY(calc(-1 * var(--post-bar-distance)));
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.site-header.is-scrolled {
  height: 64px;
  border-color: var(--line);
  background: var(--header-solid);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 13px;
}

.brand__mark {
  position: relative;
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  color: var(--paper);
  background:
    repeating-linear-gradient(0deg, transparent 0 3px, rgba(var(--surface-rgb), 0.1) 3px 4px),
    var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 21px;
  font-weight: 900;
  line-height: 1;
  text-shadow: 2px 0 0 rgba(143, 214, 223, 0.7);
}

.brand__mark::before,
.brand__mark::after {
  position: absolute;
  content: "";
}

.brand__mark::before {
  right: 4px;
  bottom: 4px;
  width: 4px;
  height: 4px;
  background: var(--paper);
}

.brand__mark::after {
  right: -9px;
  bottom: 7px;
  width: 18px;
  height: 1px;
  background: var(--paper);
  transform: rotate(-45deg);
}

.brand__text {
  display: grid;
  line-height: 1;
}

.brand__text strong {
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.08em;
}

.brand__text small {
  margin-top: 7px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.12em;
}

.site-nav {
  display: flex;
  height: 100%;
  align-items: center;
  gap: 6px;
}

.site-nav__link {
  position: relative;
  display: flex;
  height: 100%;
  align-items: center;
  gap: 8px;
  padding: 0 16px;
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  transition: color 180ms ease;
}

.site-nav__link::after {
  position: absolute;
  right: 16px;
  bottom: 19px;
  left: 16px;
  height: 1px;
  background: var(--paper);
  content: "";
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 260ms var(--ease-out);
}

.site-nav__link small {
  color: var(--muted);
  font-size: 8px;
}

.site-nav__link:hover,
.site-nav__link.is-active {
  color: var(--white);
}

.site-nav__link:hover::after,
.site-nav__link.is-active::after {
  transform: scaleX(1);
  transform-origin: left;
}

.site-nav__icon {
  display: grid;
  width: 42px;
  height: 42px;
  margin-left: 6px;
  place-items: center;
  border: 1px solid var(--line);
  transition:
    border-color 180ms ease,
    background 180ms ease,
    transform 180ms var(--ease-out);
}

.site-nav__icon:hover {
  border-color: var(--line-strong);
  background: var(--surface);
  transform: rotate(-4deg);
}

.theme-toggle {
  display: grid;
  width: 42px;
  height: 42px;
  margin-left: 6px;
  place-items: center;
  border: 1px solid var(--line);
  color: var(--paper);
  background: transparent;
  cursor: pointer;
  transition:
    border-color 180ms ease,
    background 180ms ease,
    transform 180ms var(--ease-out);
}

.theme-toggle:hover {
  border-color: var(--line-strong);
  background: var(--surface);
  transform: rotate(4deg);
}

.theme-toggle__icon {
  grid-area: 1 / 1;
  transition:
    opacity 180ms ease,
    transform 260ms var(--ease-out);
}

html[data-theme="dark"] .theme-toggle [data-theme-icon="moon"],
html[data-theme="light"] .theme-toggle [data-theme-icon="sun"] {
  opacity: 0;
  transform: rotate(-45deg) scale(0.72);
}

.nav-toggle {
  display: none;
  width: 42px;
  height: 42px;
  place-items: center;
  border: 1px solid var(--line);
  background: var(--header-solid);
  cursor: pointer;
}

.nav-toggle span {
  grid-area: 1 / 1;
  transition:
    opacity 180ms ease,
    transform 180ms ease;
}

.nav-toggle [data-nav-icon="close"] {
  opacity: 0;
  transform: rotate(-45deg);
}

.nav-toggle[aria-expanded="true"] [data-nav-icon="open"] {
  opacity: 0;
  transform: rotate(45deg);
}

.nav-toggle[aria-expanded="true"] [data-nav-icon="close"] {
  opacity: 1;
  transform: rotate(0);
}

/* Intro */
.intro {
  position: fixed;
  z-index: 200;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  color: var(--paper);
  background: var(--ink);
  cursor: pointer;
  transition:
    opacity 620ms var(--ease-sharp),
    visibility 620ms;
}

.intro.is-hidden {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

.intro__grid {
  position: absolute;
  inset: 0;
  opacity: 0.32;
  background:
    linear-gradient(rgba(var(--surface-rgb), 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--surface-rgb), 0.055) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: radial-gradient(circle at 50% 48%, #000 0, transparent 65%);
}

.intro__grid::before {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(0deg, transparent 0 3px, rgba(var(--surface-rgb), 0.03) 3px 4px);
  content: "";
}

.intro__scan {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: min(72vw, 900px);
  height: 1px;
  background: var(--paper);
  box-shadow: 0 0 16px rgba(var(--surface-rgb), 0.52);
  transform: translate(-50%, -50%) scaleX(0);
}

.intro.is-active .intro__scan {
  animation: intro-scan 950ms var(--ease-sharp) 120ms forwards;
}

.intro__shards {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.intro__shards span {
  position: absolute;
  width: 12px;
  height: 12px;
  border: 1px solid rgba(var(--surface-rgb), 0.42);
  opacity: 0;
  transform: rotate(45deg);
}

.intro__shards span:nth-child(1) {
  top: 22%;
  left: 18%;
}

.intro__shards span:nth-child(2) {
  top: 29%;
  right: 21%;
  width: 20px;
  height: 20px;
}

.intro__shards span:nth-child(3) {
  bottom: 24%;
  left: 25%;
  width: 7px;
  height: 7px;
}

.intro__shards span:nth-child(4) {
  right: 17%;
  bottom: 18%;
  width: 16px;
  height: 16px;
}

.intro.is-active .intro__shards span {
  animation: intro-shard 950ms var(--ease-out) forwards;
}

.intro.is-active .intro__shards span:nth-child(2) { animation-delay: 120ms; }
.intro.is-active .intro__shards span:nth-child(3) { animation-delay: 210ms; }
.intro.is-active .intro__shards span:nth-child(4) { animation-delay: 310ms; }

.intro__skip {
  position: absolute;
  z-index: 8;
  top: 30px;
  right: 34px;
  display: grid;
  gap: 5px;
  padding: 8px 0 8px 20px;
  border: 0;
  color: var(--paper);
  background: transparent;
  text-align: right;
  cursor: pointer;
}

.intro__skip::before {
  position: absolute;
  top: 50%;
  left: 0;
  width: 13px;
  height: 1px;
  background: var(--paper);
  content: "";
}

.intro__skip span {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
}

.intro__skip small {
  color: var(--muted);
  font-size: 9px;
}

.intro__stage {
  position: relative;
  z-index: 3;
  display: grid;
  width: min(880px, calc(100vw - 48px));
  justify-items: center;
  text-align: center;
}

.intro__frame {
  position: relative;
  width: clamp(130px, 18vw, 190px);
  height: clamp(130px, 18vw, 190px);
  margin-bottom: 38px;
  opacity: 0;
  transform: scale(0.72) rotate(8deg);
}

.intro.is-active .intro__frame {
  animation: intro-frame 1150ms var(--ease-out) 520ms forwards;
}

.intro__corner {
  position: absolute;
  width: 26px;
  height: 26px;
  border-color: var(--paper);
  border-style: solid;
}

.intro__corner--tl {
  top: 0;
  left: 0;
  border-width: 2px 0 0 2px;
}

.intro__corner--tr {
  top: 0;
  right: 0;
  border-width: 2px 2px 0 0;
}

.intro__corner--bl {
  bottom: 0;
  left: 0;
  border-width: 0 0 2px 2px;
}

.intro__corner--br {
  right: 0;
  bottom: 0;
  border-width: 0 2px 2px 0;
}

.intro__diamond {
  position: absolute;
  inset: 19%;
  border: 1px solid var(--paper);
  background: var(--surface-soft);
  transform: rotate(45deg);
}

.intro__diamond::before,
.intro__diamond::after {
  position: absolute;
  content: "";
}

.intro__diamond::before {
  inset: 11%;
  border: 1px solid rgba(var(--surface-rgb), 0.38);
}

.intro__diamond::after {
  top: 50%;
  left: -35%;
  width: 170%;
  height: 1px;
  background: var(--paper);
  transform: rotate(-45deg);
}

.intro__core {
  position: absolute;
  z-index: 2;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: clamp(42px, 7vw, 74px);
  font-weight: 900;
  line-height: 1;
  text-shadow:
    3px 0 0 rgba(143, 214, 223, 0.72),
    -2px 0 0 rgba(228, 137, 127, 0.42);
}

.intro__core::after {
  position: absolute;
  width: 56%;
  height: 1px;
  background: var(--paper);
  box-shadow: 0 5px 0 rgba(241, 243, 245, 0.5), 0 -5px 0 rgba(241, 243, 245, 0.3);
  content: "";
  animation: intro-core-line 1.2s steps(2, end) infinite;
}

.intro__eyebrow,
.intro__subtitle,
.intro__progress,
.intro__actions {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 520ms ease,
    transform 760ms var(--ease-out);
}

.intro.phase-copy .intro__eyebrow,
.intro.phase-copy .intro__subtitle {
  opacity: 1;
  transform: translateY(0);
}

.intro.phase-progress .intro__progress {
  opacity: 1;
  transform: translateY(0);
}

.intro.phase-actions .intro__actions {
  opacity: 1;
  transform: translateY(0);
}

.intro__eyebrow {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
}

.intro__title {
  display: flex;
  margin: 17px 0 11px;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: clamp(48px, 10vw, 118px);
  font-weight: 900;
  letter-spacing: 0.01em;
  line-height: 0.9;
}

.intro__title span {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.7em) skewX(-10deg);
}

.intro.phase-copy .intro__title span {
  animation: intro-letter 620ms var(--ease-out) forwards;
}

.intro.phase-copy .intro__title span:nth-child(2) { animation-delay: 45ms; }
.intro.phase-copy .intro__title span:nth-child(3) { animation-delay: 90ms; }
.intro.phase-copy .intro__title span:nth-child(4) { animation-delay: 135ms; }
.intro.phase-copy .intro__title span:nth-child(5) { animation-delay: 180ms; }
.intro.phase-copy .intro__title span:nth-child(6) { animation-delay: 225ms; }
.intro.phase-copy .intro__title span:nth-child(7) { animation-delay: 270ms; }
.intro.phase-copy .intro__title span:nth-child(8) { animation-delay: 315ms; }

.intro__subtitle {
  margin: 0;
  color: var(--paper-dim);
  font-size: 13px;
}

.intro__progress {
  width: min(460px, 76vw);
  margin-top: 44px;
  font-family: var(--font-mono);
}

.intro__progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  color: var(--muted);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.intro__progress-head strong {
  color: var(--paper);
  font-size: 12px;
}

.intro__track {
  height: 5px;
  margin-top: 10px;
  padding: 1px;
  border: 1px solid var(--line-strong);
  background: var(--surface-soft);
}

.intro__track span {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--paper);
  box-shadow: 0 0 12px rgba(var(--surface-rgb), 0.25);
  transform: scaleX(0);
  transform-origin: left;
}

.intro__ticks {
  display: grid;
  margin-top: 7px;
  grid-template-columns: repeat(19, 1fr);
  gap: 4px;
}

.intro__ticks i {
  height: 3px;
  background: var(--line);
}

.intro__ticks i:nth-child(4n + 1) {
  background: var(--line-strong);
}

.intro__actions {
  display: flex;
  margin-top: 29px;
  align-items: center;
  gap: 18px;
}

.intro__hint {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.13em;
}

.intro__footer {
  position: absolute;
  z-index: 3;
  right: clamp(20px, 4vw, 64px);
  bottom: 25px;
  left: clamp(20px, 4vw, 64px);
  display: flex;
  justify-content: space-between;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.13em;
}

.intro.phase-exit .intro__stage {
  opacity: 0;
  transform: scale(1.06);
  transition:
    opacity 300ms ease,
    transform 480ms var(--ease-sharp);
}

.intro.phase-exit::after {
  position: absolute;
  z-index: 6;
  inset: 0;
  background:
    linear-gradient(118deg, transparent 0 43%, #fff 43% 49%, transparent 49%),
    linear-gradient(118deg, transparent 0 53%, #fff 53% 56%, transparent 56%);
  content: "";
  animation: intro-shutter 580ms var(--ease-sharp) forwards;
}

.intro.is-static .intro__frame,
.intro.is-static .intro__eyebrow,
.intro.is-static .intro__subtitle,
.intro.is-static .intro__progress {
  opacity: 1;
  transform: none;
}

.intro.is-static .intro__title span,
.intro.is-static .intro__actions {
  opacity: 1;
  transform: none;
}

.intro.is-static .intro__core::after {
  animation: none;
}

@keyframes intro-scan {
  0% { transform: translate(-50%, -50%) scaleX(0); }
  42% { transform: translate(-50%, -50%) scaleX(1); }
  72% { transform: translate(-50%, -50%) scaleX(1); }
  100% { transform: translate(-50%, -50%) scaleX(0); }
}

@keyframes intro-shard {
  0% { opacity: 0; transform: translateY(18px) rotate(0) scale(0.4); }
  55% { opacity: 1; }
  100% { opacity: 0.48; transform: translateY(0) rotate(180deg) scale(1); }
}

@keyframes intro-frame {
  0% { opacity: 0; transform: scale(0.72) rotate(8deg); }
  64% { opacity: 1; transform: scale(1.04) rotate(-2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

@keyframes intro-core-line {
  0%, 100% { transform: translateY(-1px) scaleX(0.78); opacity: 0.55; }
  50% { transform: translateY(2px) scaleX(1); opacity: 1; }
}

@keyframes intro-letter {
  to { opacity: 1; transform: translateY(0) skewX(0); }
}

@keyframes intro-shutter {
  0% { opacity: 0; transform: translateX(-110%); }
  38% { opacity: 1; }
  100% { opacity: 1; transform: translateX(110%); }
}

/* Home hero */
.home-hero {
  position: relative;
  display: grid;
  min-height: min(860px, 100svh);
  grid-template-columns: minmax(0, 1fr) 160px;
  align-content: center;
  padding: calc(var(--header-height) + 64px) clamp(20px, 6vw, 96px) 76px;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.home-hero__noise {
  position: absolute;
  inset: 0;
  opacity: 0.2;
  background-image:
    linear-gradient(90deg, transparent 49.5%, rgba(var(--surface-rgb), 0.06) 49.5% 50.5%, transparent 50.5%),
    repeating-linear-gradient(0deg, transparent 0 5px, rgba(var(--surface-rgb), 0.025) 5px 6px);
  pointer-events: none;
}

.home-hero__content {
  position: relative;
  z-index: 2;
  max-width: 870px;
}

.home-hero__eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.home-hero__eyebrow i {
  width: 24px;
  height: 1px;
  background: var(--line-strong);
}

.status-dot {
  width: 6px;
  height: 6px;
  background: #8ce2b0;
  box-shadow: 0 0 10px rgba(140, 226, 176, 0.6);
}

.home-hero h1 {
  display: grid;
  margin: clamp(29px, 5vh, 54px) 0 24px;
  line-height: 0.9;
}

.home-hero__name {
  position: relative;
  width: fit-content;
  font-family: var(--font-mono);
  font-size: clamp(56px, 10.4vw, 142px);
  font-weight: 900;
  letter-spacing: -0.06em;
  text-shadow:
    4px 0 0 rgba(143, 214, 223, 0.38),
    -2px 0 0 rgba(228, 137, 127, 0.25);
}

.home-hero__name::after {
  position: absolute;
  right: -22px;
  bottom: 7px;
  width: 12px;
  height: 12px;
  background: var(--paper);
  content: "";
  animation: blink-mark 1.5s steps(1, end) infinite;
}

.home-hero__cn {
  margin-top: 18px;
  color: var(--paper-dim);
  font-size: clamp(24px, 3vw, 42px);
  font-weight: 500;
  letter-spacing: 0.18em;
}

@keyframes blink-mark {
  0%, 45% { opacity: 1; }
  46%, 100% { opacity: 0; }
}

.home-hero__tagline {
  margin: 0 0 14px;
  color: var(--paper);
  font-size: clamp(15px, 1.5vw, 19px);
  font-weight: 500;
}

.home-hero__description {
  max-width: 600px;
  margin: 0;
  color: var(--paper-dim);
  font-size: 14px;
  line-height: 1.9;
}

.home-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 36px;
}

.home-hero__side {
  position: relative;
  z-index: 2;
  display: grid;
  align-content: center;
  justify-self: end;
  gap: 28px;
  border-left: 1px solid var(--line);
  padding-left: 26px;
}

.home-hero__metric {
  display: grid;
  gap: 5px;
}

.home-hero__metric span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.15em;
}

.home-hero__metric strong {
  font-family: var(--font-mono);
  font-size: 15px;
  line-height: 1.3;
}

.home-hero__scroll {
  position: absolute;
  bottom: 30px;
  left: clamp(20px, 6vw, 96px);
  display: flex;
  align-items: center;
  gap: 15px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.14em;
}

.home-hero__scroll i {
  position: relative;
  width: 54px;
  height: 1px;
  overflow: hidden;
  background: var(--line);
}

.home-hero__scroll i::after {
  position: absolute;
  inset: 0;
  background: var(--paper);
  content: "";
  transform: translateX(-100%);
  animation: scroll-line 2.2s var(--ease-out) infinite;
}

@keyframes scroll-line {
  55%, 100% { transform: translateX(100%); }
}

/* Stream groups */
.stream-section,
.listing,
.standalone,
.article,
.article-nav,
.pagination {
  width: min(var(--content), calc(100% - 40px));
  margin-right: auto;
  margin-left: auto;
}

.stream-section {
  padding: clamp(82px, 12vw, 150px) 0 clamp(100px, 13vw, 170px);
}

.section-heading {
  display: grid;
  margin-bottom: 45px;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 410px);
  align-items: end;
  gap: 40px;
}

.section-heading h2,
.listing__header h1 {
  margin: 12px 0 0;
  font-family: var(--font-mono);
  font-size: clamp(34px, 5.2vw, 70px);
  font-weight: 900;
  letter-spacing: -0.05em;
  line-height: 0.95;
}

.section-heading p {
  margin: 0;
  color: var(--paper-dim);
  font-size: 13px;
  line-height: 1.9;
}

.stream-groups {
  border-top: 1px solid var(--line-strong);
}

.stream-group {
  --tail: 34px;
  position: relative;
  border-bottom: 1px solid var(--line-strong);
}

.stream-group summary {
  position: relative;
  display: grid;
  min-height: 128px;
  grid-template-columns: 88px minmax(0, 1fr) 92px 74px;
  align-items: center;
  padding: 0 8px;
  list-style: none;
  cursor: pointer;
  transition: background 240ms ease;
}

.stream-group summary::-webkit-details-marker {
  display: none;
}

.stream-group summary::before {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(var(--surface-rgb), 0.055), transparent 66%);
  content: "";
  opacity: 0;
  transform: translateX(-12%);
  transition:
    opacity 260ms ease,
    transform 520ms var(--ease-out);
}

.stream-group summary:hover::before,
.stream-group[open] summary::before {
  opacity: 1;
  transform: translateX(0);
}

.stream-group__number,
.stream-group__count {
  position: relative;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.stream-group__number::after {
  position: absolute;
  top: 50%;
  left: 29px;
  width: 34px;
  height: 1px;
  background: var(--line);
  content: "";
}

.stream-group__title {
  position: relative;
  display: grid;
  gap: 8px;
}

.stream-group__title small {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.stream-group__title strong {
  /* Solid plate instead of bare type on the grid: the group name was
     illegible over the sweep. Same -7deg skew as the route slash. */
  display: inline-block;
  padding: 11px 36px;
  background: var(--paper);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
  color: var(--ink);
  font-size: clamp(24px, 3.4vw, 42px);
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.25;
  transform: skewX(-7deg);
}

.stream-group__title strong > span {
  display: inline-block;
  transform: skewX(7deg);
}

.stream-group__count {
  justify-self: end;
}

/* Bare arrow — no plate, no frame. Hover warms it up, open flips it. */
.stream-group__toggle {
  position: relative;
  display: grid;
  width: 44px;
  height: 44px;
  justify-self: end;
  place-items: center;
  color: var(--muted);
  transition:
    color 220ms ease,
    transform 420ms var(--ease-out);
}

.stream-group summary:hover .stream-group__toggle {
  color: var(--paper);
}

.stream-group[open] .stream-group__toggle {
  color: var(--paper);
  transform: rotate(180deg);
}

/* Collapse = two interpolated tracks: the content row (0fr <-> 1fr) plus a
   tail row carrying the old bottom padding (0px <-> var(--tail)). Padding on
   the grid item would floor the 0fr track at its own size and leave a dead
   34px strip under closed groups; as a track it interpolates to zero. */
.stream-group__body,
.archive-year__body {
  display: grid;
  grid-template-rows: 0fr 0px;
  transition: grid-template-rows 520ms var(--ease-out);
  will-change: grid-template-rows;
}

.stream-group[open] .stream-group__body,
.archive-year[open] .archive-year__body {
  grid-template-rows: 1fr var(--tail, 0px);
}

/* This browser never hides closed details content — clamp it ourselves once
   the collapse has landed (visibility also drops the links from tab order). */
.stream-group:not([open]) .stream-group__body,
.archive-year:not([open]) .archive-year__body {
  visibility: hidden;
}

.stream-group__inner,
.archive-year__inner {
  min-height: 0;
  overflow: hidden;
}

.stream-group__inner {
  display: grid;
  gap: 12px;
  /* The picker's skewed bounding box pokes ~27px past this column on the
     right; overflow hidden (needed for the collapse) would slice that tip
     off. Bleed the clip window out instead of cutting the preview. */
  margin-right: -34px;
  padding: 0 34px 0 88px;
}

.stream-group__inner,
.archive-year__inner {
  opacity: 0;
  transform: translateY(-14px);
  transition:
    opacity 300ms ease,
    transform 480ms var(--ease-out);
}

.stream-group[open] .stream-group__inner,
.archive-year[open] .archive-year__inner {
  opacity: 1;
  transform: none;
  /* Content starts fading almost with the height: a long delay used to leave
     a growing empty hole that read as a flash, worst on touch GPUs. */
  transition:
    opacity 380ms ease 40ms,
    transform 520ms var(--ease-out);
}

.stream-group.is-collapsing .stream-group__inner,
.details.is-collapsing .archive-year__inner {
  opacity: 0;
  transform: translateY(-10px);
  transition:
    opacity 260ms ease,
    transform 420ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .stream-group__inner,
  .archive-year__inner {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.post-card {
  position: relative;
  display: grid;
  min-height: 220px;
  grid-template-columns: 58px minmax(0, 1fr);
  border: 1px solid var(--line);
  background:
    linear-gradient(125deg, var(--surface-soft), transparent 46%),
    var(--ink-soft);
  transition:
    border-color 260ms ease,
    transform 360ms var(--ease-out),
    background 260ms ease;
}

.post-card:hover {
  border-color: var(--line-strong);
  background:
    linear-gradient(125deg, rgba(143, 214, 223, 0.07), transparent 46%),
    var(--panel);
  transform: translateX(7px);
}

.post-card__rail {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  border-right: 1px solid var(--line);
  padding: 18px 0;
}

.post-card__rail span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
}

.post-card__content {
  display: grid;
  padding: 28px 30px 24px;
}

.post-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.post-card__meta time {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.post-card__cat {
  color: var(--cyan);
}

.post-card h3 {
  margin: 25px 0 12px;
  font-size: clamp(20px, 2.4vw, 30px);
  font-weight: 600;
  line-height: 1.35;
}

.post-card h3 a {
  background-image: linear-gradient(var(--paper), var(--paper));
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 0 1px;
  transition: background-size 420ms var(--ease-out);
}

.post-card h3 a:hover {
  background-size: 100% 1px;
}

.post-card__content > p {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  color: var(--paper-dim);
  font-size: 13px;
  line-height: 1.85;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.post-card__bottom {
  display: flex;
  margin-top: 25px;
  align-items: end;
  justify-content: space-between;
  gap: 18px;
}

.post-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
}

.post-card__open {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.12em;
}

.post-card__open .icon {
  transition: transform 220ms var(--ease-out);
}

.post-card:hover .post-card__open .icon {
  transform: translate(3px, -3px);
}

.empty-state {
  display: grid;
  min-height: 340px;
  place-content: center;
  border: 1px solid var(--line);
  background:
    linear-gradient(135deg, var(--surface-soft), transparent 52%),
    var(--ink-soft);
  text-align: center;
}

.empty-state span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
}

.empty-state h3 {
  margin: 16px 0 8px;
  font-size: 25px;
}

.empty-state p {
  margin: 0;
  color: var(--paper-dim);
}

.empty-state code {
  color: var(--cyan);
  font-family: var(--font-mono);
  font-size: 12px;
}

/* Footer */
.site-footer {
  width: min(var(--content), calc(100% - 40px));
  margin: 0 auto;
  padding: 0 0 55px;
}

.site-footer__line {
  height: 1px;
  background: var(--line-strong);
}

.site-footer__main {
  display: flex;
  padding-top: 32px;
  align-items: end;
  justify-content: space-between;
  gap: 30px;
}

.site-footer p {
  margin: 14px 0 0;
  color: var(--paper-dim);
  font-size: 13px;
}

.site-footer__meta {
  display: grid;
  gap: 6px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-align: right;
}

/* Article and pages */
.article,
.standalone {
  padding-top: calc(var(--header-height) + 72px);
}

.article__crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.article__crumbs a:hover {
  color: var(--paper);
}

.article__heading {
  margin-top: 60px;
}

.article__heading h1,
.standalone__header h1,
.listing__header h1 {
  max-width: 980px;
  margin: 16px 0 0;
  font-size: clamp(38px, 6.5vw, 82px);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 1.12;
}

.article__heading > p,
.standalone__header > p,
.listing__header > p {
  max-width: 700px;
  margin: 22px 0 0;
  color: var(--paper-dim);
  font-size: 14px;
}

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  margin-top: 32px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.article__meta span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.article__cover {
  position: relative;
  margin: 66px 0 0;
  border: 1px solid var(--line);
}

.article__cover img {
  width: 100%;
  max-height: 720px;
  object-fit: cover;
}

.article__cover figcaption {
  position: absolute;
  right: 14px;
  bottom: 12px;
  padding: 6px 8px;
  color: #ffffff;
  background: rgba(9, 11, 15, 0.78);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.12em;
}

.article__layout {
  display: grid;
  margin-top: 80px;
  grid-template-columns: 210px minmax(0, 760px);
  justify-content: center;
  gap: clamp(40px, 7vw, 100px);
}

/* No outline: the body takes the reading column on its own. */
.article__layout--no-toc {
  grid-template-columns: minmax(0, 760px);
}

.article__toc {
  position: sticky;
  top: 105px;
  align-self: start;
  max-height: calc(100vh - 130px);
  overflow-y: auto;
  padding-right: 12px;
}

.article__toc ol,
.article__toc ul {
  display: grid;
  margin: 19px 0 0;
  padding: 0;
  gap: 10px;
  list-style: none;
}

.article__toc ol ol,
.article__toc ul ul {
  margin: 9px 0 0 14px;
}

.article__toc a {
  color: var(--muted);
  font-size: 11px;
  line-height: 1.5;
  transition: color 180ms ease;
}

.article__toc a:hover,
.article__toc a.is-active {
  color: var(--paper);
}

.article__toc a.is-active::before {
  display: inline-block;
  width: 12px;
  height: 1px;
  margin-right: 7px;
  background: currentColor;
  vertical-align: middle;
  content: "";
}

.article__footer {
  display: flex;
  width: min(760px, 100%);
  margin: 80px auto 0;
  padding-top: 24px;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  border-top: 1px solid var(--line);
}

.article__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 11px;
}

.article__tags a {
  color: var(--paper-dim);
  font-family: var(--font-mono);
  font-size: 10px;
}

.article__back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.1em;
}

.article-nav {
  display: grid;
  margin-top: 80px;
  margin-bottom: 110px;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--line);
}

.article-nav a {
  display: grid;
  min-height: 160px;
  align-content: center;
  padding: 30px;
  gap: 12px;
  transition: background 220ms ease;
}

.article-nav a:hover {
  background: var(--surface);
}

.article-nav a + a {
  border-left: 1px solid var(--line);
  text-align: right;
}

.article-nav strong {
  font-size: 18px;
  font-weight: 500;
  line-height: 1.45;
}

.standalone {
  padding-bottom: 120px;
}

.standalone__header {
  max-width: 900px;
}

.standalone__rule {
  height: 1px;
  margin: 62px 0;
  background: var(--line-strong);
}

.standalone__body {
  max-width: 780px;
  margin: 0 auto;
}

/* --- About page: friend links -------------------------------------------
   Rendered inside the article panel, as the last block of the body. The
   section heading intentionally inherits the prose h2 divider so it reads
   like any other section of the article. */
.friends {
  margin: 3em 0 0;
}

/* Keep the friends section's own heading language inside the prose flow:
   the generic .prose h2 divider (full rule + accent bar + 3.1em gap) would
   blow up the rhythm between the micro-label and the heading. Two classes
   so the override out-specifies .prose h2 regardless of file order. */
.prose .article__friends h2 {
  margin: 10px 0 26px;
  padding-top: 0;
  border-top: none;
  font-size: 22px;
}

.prose .article__friends h2::before {
  display: none;
}

/* --- Comments (Waline) ---------------------------------------------------
   Mounted inside the article panel like the friends section. Waline's own
   palette is fully re-mapped onto the theme's custom properties so the
   widget follows the site's dark/light flip without its JS dark mode
   (which would inject inline colours we cannot tune). */
.comments {
  margin: 3em 0 0;
  --waline-theme-color: var(--paper);
  --waline-active-color: var(--paper);
  --waline-color: var(--prose-ink);
  --waline-bg-color: var(--surface-soft);
  --waline-bg-color-light: var(--surface-soft);
  --waline-bg-color-hover: var(--surface);
  --waline-border-color: var(--line);
  --waline-disable-bg-color: var(--surface-soft);
  --waline-disable-color: var(--muted);
  --waline-info-bg-color: var(--surface-soft);
  --waline-info-color: var(--muted);
  --waline-light-grey: var(--muted);
  --waline-dark-grey: var(--paper-dim);
  /* Waline paints primary buttons with `theme-color` and their label with
     `white`; since the theme colour is a paper-on-ink flip, the label has to
     be the page ink or the submit button renders as a blank rectangle. */
  --waline-white: var(--ink);
  --waline-bq-color: var(--surface-soft);
  --waline-code-bg-color: var(--code-bg);
  --waline-badge-color: var(--cyan);
  --waline-avatar-radius: 2px;
  --waline-box-shadow: none;
  font-family: inherit;
}

.prose .comments h2 {
  margin: 10px 0 26px;
  padding-top: 0;
  border-top: none;
  font-size: 22px;
}

.prose .comments h2::before {
  display: none;
}

.prose .comments .wl-app {
  font-family: inherit;
}

/* Keep the prose link treatment out of the widget chrome; comment bodies
   keep the cyan affordance so links stay recognisable. */
.prose .comments a {
  color: inherit;
  text-decoration: none;
}

.prose .comments .wl-content a {
  color: var(--cyan);
  text-decoration: underline;
  text-decoration-color: rgba(143, 214, 223, 0.36);
  text-underline-offset: 4px;
}

/* Waline's RSS icon bakes orange fills into individual paths; flatten it
   into a quiet monochrome badge (square = faint surface, arcs = muted). */
.prose .comments .wl-rss svg path {
  fill: var(--surface);
}

.prose .comments .wl-rss svg path:last-child {
  fill: var(--muted);
}

.friends__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Layered card: faint vertical sheen + inset top highlight + drop shadow
   lift the tile off the panel ground without moving it (a hover transform
   would push the label text onto the compositor path and soften it — the
   exact regression removed in track-defuse). Depth on hover comes from the
   shadow growing, not from translation. */
.friends__card a {
  display: flex;
  align-items: center;
  gap: 14px;
  height: 100%;
  padding: 16px 18px;
  border: 1px solid var(--line-strong);
  background: linear-gradient(180deg, rgba(241, 243, 245, 0.055), rgba(241, 243, 245, 0.015));
  box-shadow:
    inset 0 1px 0 rgba(241, 243, 245, 0.06),
    0 10px 26px rgba(0, 0, 0, 0.24);
  color: inherit;
  text-decoration: none;
  transition: background 240ms var(--ease-out), border-color 240ms var(--ease-out),
    box-shadow 240ms var(--ease-out);
}

.friends__card a:hover {
  border-color: var(--paper);
  background: linear-gradient(180deg, rgba(241, 243, 245, 0.09), rgba(241, 243, 245, 0.03));
  box-shadow:
    inset 0 1px 0 rgba(241, 243, 245, 0.08),
    0 18px 38px rgba(0, 0, 0, 0.36);
}

.friends__card a:hover .icon {
  transform: translate(2px, -2px);
}

.friends__card .icon {
  margin-left: auto;
  flex: 0 0 auto;
  opacity: 0.55;
  transition: transform 240ms var(--ease-out), opacity 240ms var(--ease-out);
}

.friends__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-strong);
  background: rgba(241, 243, 245, 0.06);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 700;
  overflow: hidden;
}

.friends__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.friends__meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.friends__meta strong {
  font-size: 14px;
  font-weight: 500;
}

.friends__meta small {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

/* Listings and archive */
.listing {
  padding-top: calc(var(--header-height) + 80px);
  padding-bottom: 120px;
}

.listing__header {
  margin-bottom: 66px;
}

.archive-years {
  border-top: 1px solid var(--line-strong);
}

.archive-year {
  border-bottom: 1px solid var(--line-strong);
}

.archive-year summary {
  display: grid;
  min-height: 92px;
  grid-template-columns: 1fr auto 32px;
  align-items: center;
  gap: 30px;
  list-style: none;
  cursor: pointer;
}

.archive-year summary::-webkit-details-marker {
  display: none;
}

.archive-year summary strong {
  font-family: var(--font-mono);
  font-size: 34px;
}

.archive-year summary span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.13em;
}

.archive-year[open] summary .icon {
  transform: rotate(180deg);
}

.archive-year summary .icon {
  transition: transform 260ms var(--ease-out);
}

.archive-row {
  display: grid;
  min-height: 84px;
  grid-template-columns: 90px minmax(0, 1fr) 150px 24px;
  align-items: center;
  gap: 22px;
  border-top: 1px solid var(--line);
  transition:
    padding 260ms var(--ease-out),
    background 200ms ease;
}

a.archive-row:hover {
  padding-right: 14px;
  padding-left: 14px;
  background: var(--surface-soft);
}

.archive-row time,
.archive-row > span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.archive-row strong {
  font-size: 16px;
  font-weight: 500;
}

.archive-row > span {
  text-align: right;
}

/* Stream picker: ONE -7deg acrylic parallelogram wraps both columns — every
   slant in this region comes from that single transform, so the slopes can't
   disagree. The inner grid counter-skews (which cancels the wrapper exactly),
   so all content stays upright. The acrylic blur lives on an overscanned
   pseudo instead of the wrapper: backdrop-filter on the transformed element
   itself pushes it into a compositor path that clips edges unantialiased. */
.stream-picker {
  position: relative;
  height: 434px;
  overflow: hidden;
  transform: skewX(-7deg);
  outline: none;
}

/* The frame lives on an empty pseudo instead of the wrapper border: the
   skewed hairline rasterises into visible stair steps at fractional display
   scales, and a sub-pixel blur feathers them away. The pseudo never carries
   text, so the compositor path this filter creates cannot blur any type. */
.stream-picker::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid var(--line-strong);
  pointer-events: none;
  filter: blur(0.6px);
}

.stream-picker:focus-within::after {
  border-color: var(--paper);
}

.stream-picker::before {
  content: '';
  position: absolute;
  inset: -90px;
  background: rgba(7, 9, 12, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  pointer-events: none;
}

.stream-picker:focus-within::after {
  border-color: var(--paper);
}

/* Selection bar, pinned at the vertical centre. It lives in the wrapper's
   skewed space, so the -7deg slant is free; it spans the left column only. */
.stream-picker__plate {
  position: absolute;
  z-index: 0;
  top: 50%;
  left: -6%;
  width: 57%;
  height: 44px;
  transform: translateY(-50%);
  background: var(--paper);
  box-shadow: 0 12px 38px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

/* Upright content plane. skewX(7deg) cancels the wrapper exactly; the 116%/-8%
   overscan just keeps the parallelogram's clipped corners covered. */
.stream-picker__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
  height: 100%;
  transform: skewX(7deg);
  width: 116%;
  margin-left: -8%;
}

/* Column divider: a bare bar in the wrapper's skewed space, so it leans
   -7deg like every other line here for free. */
.stream-picker__divider {
  position: absolute;
  z-index: 0;
  top: -8%;
  bottom: -8%;
  left: 54.5%;
  width: 1px;
  background: var(--line-strong);
  pointer-events: none;
}

/* --- left: transparent rows gliding through native momentum scroll --- */
.stream-viewport {
  position: relative;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
  outline: none;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 46px, #000 calc(100% - 46px), transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 46px, #000 calc(100% - 46px), transparent 100%);
}

.stream-viewport::-webkit-scrollbar {
  display: none;
}

.stream-viewport__list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* half viewport minus half row, so the first/last row can reach the bar */
  padding: 195px 0;
}

.stream-row {
  flex: 0 0 auto;
  height: 44px;
  /* left padding clears the inner plane's -8% overscan plus the wrapper's
     slanted corner inset, or the slant eats the first letters */
  padding: 0 56px 0 128px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  color: var(--paper);
  opacity: 0.45;
  transition: opacity 240ms var(--ease-out), color 240ms var(--ease-out);
  cursor: pointer;
}

.stream-row__title {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.stream-row__date {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.stream-row.is-active {
  opacity: 1;
  color: var(--ink);
}

/* Bottom-right of the parallelogram is clipped by the slant, so the counter
   sits at the safe bottom-left; skewX(7deg) unskews the type. */
.stream-viewport__count {
  position: absolute;
  z-index: 2;
  left: 30px;
  bottom: 12px;
  transform: skewX(7deg);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.14em;
  pointer-events: none;
}

/* --- right: upright detail panels, the whole block rolls with the scroll ---
   The track's translate is driven continuously from the viewport's scrollTop
   in JS (see updateTrack), so the cover + text slide together with the finger
   and drag the left list along. No per-notch transition of its own — the inner
   plane is already upright. The panel's darker ground is clipped to the same
   -7deg lean (the left edge of the polygon), so the "divider" between the
   columns leans with all the other edges; right/bottom padding clears the
   parallelogram's clipped corners.

   Overscan 27px to the left so both ends of the slanted left edge stay inside
   this element. Without it the lower end falls outside the grid box and
   overflow clipping turns the last stretch into a vertical edge. */
.stream-detail {
  position: relative;
  margin-left: -27px;
  overflow: hidden;
  /* Drag-stepping is handled in JS; never let the page pan along. */
  touch-action: none;
  /* Full -7deg parallelogram: left edge leans like the outer frame,
     right edge leans the same way (inset at the bottom), so the panel
     reads as the same slanted plate as the left column.
     Chromium aliases clip-path polygons; JS paints the identical shape as
     a feathered gradient mask (see paintDetailMask), which lands on top
     of this and gives the slanted edges their anti-aliasing. */
  -webkit-clip-path: polygon(54px 0, 140% 0, calc(140% - 54px) 100%, 0 100%);
  clip-path: polygon(54px 0, 140% 0, calc(140% - 54px) 100%, 0 100%);
}

.stream-detail__track {
  height: 100%;
  /* No translate3d / will-change here on purpose: both would permanently
     promote the track to a compositor layer, and Chromium disables ClearType
     subpixel AA for composited text — every word in the panel would render
     grayscale and read soft (worst on the big title). The track is moved by
     JS with a 2D translate instead, which paints in the main frame with full
     subpixel AA; measured cost on a continuous animated scroll: 0 frames
     over 17ms (cost-track.js). */
  counter-reset: detail-panel;
}

.stream-detail__panel {
  position: relative;
  height: 100%;
  padding: 42px 118px 40px 91px;
  overflow: hidden;
  counter-increment: detail-panel;
}

/* Ghost index fills the quiet top-right corner */
.stream-detail__panel::before {
  content: counter(detail-panel, decimal-leading-zero);
  position: absolute;
  z-index: 1;
  top: 30px;
  right: 118px;
  color: transparent;
  font-family: var(--font-mono);
  font-size: 54px;
  font-weight: 700;
  letter-spacing: 0.04em;
  -webkit-text-stroke: 1px rgba(241, 243, 245, 0.16);
  pointer-events: none;
}

.stream-detail__cover {
  position: absolute;
  inset: -24px;
  background-size: cover;
  background-position: center;
  opacity: 0.4;
  filter: saturate(0.85);
}

.stream-detail__panel::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(7, 9, 12, 0.5) 0%, rgba(7, 9, 12, 0.88) 76%);
  pointer-events: none;
}

.stream-detail__body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
  height: 100%;
}

.stream-detail__body small {
  /* Trapezoid typesetting: the blocks' left edges descend along the same
     -7deg lean as the wrapper (56 - 42 - 28 mirrors the left list's slant
     across the vertical gaps between the blocks). */
  margin-left: 56px;
  color: rgba(241, 243, 245, 0.55);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
}

.stream-detail__body h4 {
  position: relative;
  margin: 0 0 0 42px;
  /* right inset keeps the title's first line clear of the ghost index */
  padding-right: 64px;
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(241, 243, 245, 0.16);
  color: #f1f3f5;
  font-size: clamp(20px, 1.9vw, 27px);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.42;
}

/* short accent sitting on the hairline, echoing the solid plates */
.stream-detail__body h4::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 44px;
  height: 2px;
  background: #f1f3f5;
}

.stream-detail__body p {
  margin: 4px 0 0 28px;
  max-width: 34em;
  color: rgba(241, 243, 245, 0.78);
  font-size: 14px;
  letter-spacing: 0.01em;
  line-height: 2.05;
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
}

/* READ as a slanted solid plate — the same -7deg block language as the
   group titles; the label counter-skews so the type sits upright. */
.stream-detail__read {
  position: absolute;
  z-index: 1;
  right: 118px;
  bottom: 34px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 24px;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.16em;
  transform: skewX(-7deg);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
  transition:
    background 220ms ease,
    color 220ms ease,
    box-shadow 220ms ease;
}

.stream-detail__read span,
.stream-detail__read svg {
  transform: skewX(7deg);
}

.stream-detail__read:hover {
  background: transparent;
  color: #f1f3f5;
  box-shadow: inset 0 0 0 1px rgba(241, 243, 245, 0.85);
}

/* --- mobile portrait: the original card list, not the picker ---
   Both markups render; portrait swaps the parallelogram out entirely. */
.stream-cards {
  display: none;
}

@media (max-width: 960px) and (orientation: portrait) {
  .stream-picker {
    display: none;
  }

  .stream-cards {
    display: grid;
    gap: 12px;
  }
}

@media (max-width: 960px) {
  .stream-picker {
    height: auto;
    transform: none;
  }

  /* Unskewed frame is rasterised straight — no stair steps to feather. */
  .stream-picker::after {
    filter: none;
  }

  .stream-picker::before {
    inset: 0;
  }

  .stream-picker__inner {
    transform: none;
    width: 100%;
    margin-left: 0;
    grid-template-columns: 1fr;
  }

  .stream-picker__plate {
    display: none;
  }

  .stream-picker__divider {
    display: none;
  }

  .stream-viewport {
    height: 340px;
  }

  .stream-viewport__list {
    padding: 148px 0;
  }

  .stream-row {
    padding: 0 28px;
  }

  .stream-detail {
    height: 320px;
    margin-left: 0;
    border-top: 1px solid var(--line-strong);
    -webkit-clip-path: none;
    clip-path: none;
  }

  .stream-detail__panel {
    padding: 30px 30px;
  }

  .stream-detail__body small,
  .stream-detail__body h4,
  .stream-detail__body p {
    margin-left: 0;
  }

  .stream-detail__panel::before {
    right: 30px;
    top: 22px;
    font-size: 40px;
  }

  .stream-detail__read {
    right: 30px;
    bottom: 26px;
    padding: 9px 18px;
  }
}

.pagination {
  display: grid;
  margin-top: 50px;
  margin-bottom: 110px;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}

.pagination a {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 9px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.12em;
}

.pagination a:last-child {
  justify-self: end;
}

.pagination__count {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 9px;
}

/* Article prose */
.prose {
  color: var(--prose-ink);
  font-size: 16px;
  line-height: 2;
}

.article__body.prose {
  padding: clamp(26px, 4.2vw, 58px);
  border: 1px solid var(--article-edge);
  background: var(--article-surface);
  box-shadow:
    0 0 26px var(--article-glow),
    0 34px 80px rgba(0, 0, 0, 0.28);
}

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

.prose h2,
.prose h3,
.prose h4 {
  position: relative;
  scroll-margin-top: 105px;
  color: var(--paper);
  font-weight: 600;
  line-height: 1.4;
}

.prose h2 {
  margin: 3.1em 0 1em;
  padding-top: 0.7em;
  border-top: 1px solid var(--line);
  font-size: 28px;
}

.prose h2::before {
  position: absolute;
  top: -1px;
  left: 0;
  width: 54px;
  height: 2px;
  background: var(--paper);
  content: "";
}

.prose h3 {
  margin: 2.3em 0 0.8em;
  font-size: 21px;
}

.prose p {
  margin: 1.35em 0;
}

.prose a {
  color: var(--cyan);
  text-decoration: underline;
  text-decoration-color: rgba(143, 214, 223, 0.36);
  text-underline-offset: 4px;
}

.prose strong {
  color: var(--paper);
}

.prose blockquote {
  margin: 2em 0;
  padding: 22px 25px;
  border-left: 2px solid var(--paper);
  color: var(--paper-dim);
  background: var(--surface-soft);
}

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

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

.prose ul,
.prose ol {
  padding-left: 1.35em;
}

.prose li {
  margin: 0.55em 0;
}

.prose li::marker {
  color: var(--muted);
  font-family: var(--font-mono);
}

.prose img {
  margin: 2.5em auto;
  border: 1px solid var(--line);
}

.prose hr {
  height: 1px;
  margin: 3em 0;
  border: 0;
  background: var(--line);
}

.prose code {
  padding: 0.14em 0.38em;
  border-radius: 5px;
  border: 1px solid var(--line);
  color: var(--cyan);
  background: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 0.88em;
}

.prose pre {
  position: relative;
  margin: 2em 0;
  padding: 24px;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: auto;
  color: var(--code-text);
  background: var(--code-bg);
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.75;
}

.prose pre code {
  padding: 0;
  border: 0;
  color: inherit;
  background: transparent;
  font-size: inherit;
}

.code-copy {
  position: absolute;
  z-index: 2;
  top: 9px;
  right: 9px;
  padding: 4px 7px;
  border: 1px solid var(--code-border);
  border-radius: 999px;
  color: var(--code-copy-text);
  background: var(--code-copy-bg);
  font-family: var(--font-mono);
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: 1.2;
  cursor: pointer;
  backdrop-filter: blur(8px);
  transition:
    color 180ms ease,
    border-color 180ms ease,
    background 180ms ease,
    transform 180ms var(--ease-out);
}

.code-copy:hover {
  color: var(--code-text);
  border-color: var(--line-strong);
  transform: translateY(-1px);
}

.code-copy:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}

.prose table {
  display: block;
  width: 100%;
  margin: 2em 0;
  overflow-x: auto;
  border-collapse: collapse;
}

.prose th,
.prose td {
  padding: 12px 14px;
  border: 1px solid var(--line);
  text-align: left;
}

.prose th {
  color: var(--paper);
  background: var(--surface);
}

.prose .highlight {
  position: relative;
  margin: 2em 0;
  border: 1px solid var(--code-border);
  border-radius: 11px;
  overflow: hidden;
  background: var(--code-bg);
  box-shadow: var(--code-shadow);
}

.prose .highlight pre {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.prose .highlight table {
  display: table;
  width: 100%;
  margin: 0;
  overflow: visible;
  border-collapse: collapse;
  background: transparent;
}

.prose .highlight tbody,
.prose .highlight tr {
  background: transparent;
}

.prose .highlight td {
  padding: 0;
  border: 0;
  vertical-align: top;
}

.prose .highlight .gutter {
  width: 1%;
  border-right: 1px solid var(--code-border);
  background: var(--code-gutter);
}

.prose .highlight .gutter pre {
  padding: 19px 13px 19px 16px;
  color: var(--code-muted);
  text-align: right;
  user-select: none;
}

.prose .highlight .code {
  width: 100%;
  background: var(--code-bg);
}

.prose .highlight .code pre {
  padding: 19px 64px 19px 19px;
  overflow-x: auto;
}

.prose .highlight .line {
  min-height: 1.75em;
}

.prose .highlight .comment,
.prose .highlight .quote {
  color: var(--code-comment);
  font-style: italic;
}

.prose .highlight .keyword,
.prose .highlight .selector-tag,
.prose .highlight .doctag,
.prose .highlight .meta-keyword {
  color: var(--code-keyword);
  font-weight: 600;
}

.prose .highlight .string,
.prose .highlight .regexp,
.prose .highlight .addition,
.prose .highlight .attribute {
  color: var(--code-string);
}

.prose .highlight .number,
.prose .highlight .literal,
.prose .highlight .symbol,
.prose .highlight .bullet {
  color: var(--code-number);
}

.prose .highlight .title,
.prose .highlight .title.function_,
.prose .highlight .function .title,
.prose .highlight .built_in,
.prose .highlight .type {
  color: var(--code-function);
}

.prose .highlight .variable,
.prose .highlight .template-variable,
.prose .highlight .attr,
.prose .highlight .property {
  color: var(--code-variable);
}

.prose .highlight .operator,
.prose .highlight .punctuation,
.prose .highlight .params {
  color: var(--code-operator);
}

.prose .highlight .deletion {
  color: var(--coral);
}

.prose .highlight .meta,
.prose .highlight .section {
  color: var(--yellow);
}

/* Motion */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 700ms ease var(--reveal-delay, 0ms),
    transform 850ms var(--ease-out) var(--reveal-delay, 0ms);
}

.reveal.is-visible {
  opacity: 1;
  /* Ending on `translateY(0)` would leave a transform in the computed style
     and keep the card on its own rasterised layer forever. `none` is the same
     visual position but hands the text back to the normal render path, and
     it does not block :hover transforms such as the post-card lift. */
  transform: none;
}

.page-enter {
  animation: page-enter 700ms var(--ease-out) both;
}

/* Posts are choreographed block by block, so their wrapper must not fade too. */
.is-post .page-enter {
  animation: none;
}

/* Slash pages are uncovered by the sweep, so the wrapper's own entrance waits
   for the slash instead of playing behind an opaque layer: it starts as the
   veil begins to dissolve (60% of 1.71s) and lands with it. */
html.js body.is-inner:not(.is-post) .page-enter {
  animation-delay: 900ms;
}

@keyframes page-enter {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}

html.js body.is-inner:not(.route-ready),
html.js body.route-animating {
  overflow: hidden;
}

/* Posts hold the flat plate until the seam lands; the slash pages are covered
   by the home-return layer instead of by a repainted body. */
html.js body.is-post:not(.route-ready) {
  background: var(--route-veil);
}

/* The header is frozen, so the hold-back and the reveal apply to the content
   columns only — never to .app-shell, whose filter/transform would also drag
   the fixed header into a containing block. */
html.js body.is-post:not(.route-ready) .main-content,
html.js body.is-post:not(.route-ready) .site-footer {
  opacity: 0;
}

html.js body.is-post.route-ready:not(.route-leaving) .main-content,
html.js body.is-post.route-ready:not(.route-leaving) .site-footer {
  animation: route-content-reveal var(--post-veil-duration) var(--ease-out)
    var(--post-line-landing) both;
}

@keyframes route-content-reveal {
  from { opacity: 0; filter: blur(8px); transform: scale(1.006); }
  to { opacity: 1; filter: none; transform: scale(1); }
}

/* Inner-page entry choreography, shared by every non-home page: the title
   block fades, the main column leads with the full travel and blur, the
   supporting panels settle behind it with a shorter, softer move, and the
   footer / prev-next only lift. Post pages fill every tier; archive, taxonomy
   and standalone pages fill whichever tiers they actually have, so they land
   on the same rhythm without needing their own timings. */
@keyframes post-header-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes post-fly-in {
  from {
    opacity: 0;
    filter: blur(var(--post-fly-blur));
    transform: translateY(var(--post-fly-distance));
  }
  to {
    opacity: 1;
    filter: none;
    transform: translateY(0);
  }
}

@keyframes post-fly-in-soft {
  from {
    opacity: 0;
    filter: blur(var(--post-fly-blur-soft));
    transform: translateY(var(--post-fly-distance-soft));
  }
  to {
    opacity: 1;
    filter: none;
    transform: translateY(0);
  }
}

@keyframes post-fly-in-tail {
  from { opacity: 0; transform: translateY(var(--post-fly-distance-tail)); }
  to { opacity: 1; transform: translateY(0); }
}

/* Tier 0 — titles fade in place rather than fly. */
html.js body.is-post.route-ready:not(.route-leaving) .article__header,
html.js body.is-post.route-ready:not(.route-leaving) .listing__header,
html.js body.is-post.route-ready:not(.route-leaving) .standalone__header {
  animation: post-header-in var(--post-header-duration) var(--ease-out)
    var(--post-header-delay) both;
}

/* Tier 1 — the main column leads the cascade. */
html.js body.is-post.route-ready:not(.route-leaving) .article__body,
html.js body.is-post.route-ready:not(.route-leaving) .archive-years,
html.js body.is-post.route-ready:not(.route-leaving) .standalone__body {
  animation: post-fly-in var(--post-fly-duration) var(--ease-out)
    var(--post-fly-delay) both;
}

/* Tier 2 — supporting panels: the cover and the standalone divider rule. */
html.js body.is-post.route-ready:not(.route-leaving) .article__cover,
html.js body.is-post.route-ready:not(.route-leaving) .standalone__rule {
  animation: post-fly-in-soft var(--post-fly-duration-soft) var(--ease-out)
    calc(var(--post-fly-delay) + var(--post-fly-stagger)) both;
}

/* Tier 3 — the contents panel. */
html.js body.is-post.route-ready:not(.route-leaving) .article__toc {
  animation: post-fly-in-soft var(--post-fly-duration-soft) var(--ease-out)
    calc(var(--post-fly-delay) + var(--post-fly-stagger) * 2) both;
}

/* Tier 4 — the footer band, and pagination on the listing pages. */
html.js body.is-post.route-ready:not(.route-leaving) .article__footer,
html.js body.is-post.route-ready:not(.route-leaving) .pagination {
  animation: post-fly-in-tail var(--post-fly-duration-tail) var(--ease-out)
    calc(var(--post-fly-delay) + var(--post-fly-stagger) * 3) both;
}

/* Tier 5 — prev / next closes the cascade. */
html.js body.is-post.route-ready:not(.route-leaving) .article-nav {
  animation: post-fly-in-tail var(--post-fly-duration-tail) var(--ease-out)
    calc(var(--post-fly-delay) + var(--post-fly-stagger) * 4) both;
}

/* Rendering clean-up. Every entrance animation ends on the element's own
   natural state (opacity 1 / transform none / filter none), so once it has
   played it can be dropped entirely — JS tags the element .is-settled on
   animationend. Keeping the animation alive leaves `filter` and `transform`
   in the computed style even at their final values, and a non-none filter or
   transform makes the browser rasterise the block into a texture: the text is
   then capped at that texture's resolution and looks soft after a zoom or a
   display-scaling change. Dropping the animation costs nothing visually and
   hands the text back to the normal, device-resolution text path. */
html.js body.is-post.route-ready
  :is(
    .main-content,
    .site-footer,
    .article__header,
    .listing__header,
    .standalone__header,
    .article__body,
    .archive-years,
    .standalone__body,
    .article__cover,
    .standalone__rule,
    .article__toc,
    .article__footer,
    .pagination,
    .article-nav
  ).is-settled,
html.js body.is-inner .page-enter.is-settled {
  animation: none;
}

.route-transition {
  position: fixed;
  z-index: 240;
  inset: 0;
  display: none;
  overflow: hidden;
  pointer-events: none;
}

html.js .route-transition {
  display: block;
}

.route-transition__veil {
  position: absolute;
  right: 0;
  left: 0;
  height: calc(50% + 1px);
  background: var(--route-veil);
  opacity: 1;
  will-change: opacity, transform;
}

.route-transition__veil--top {
  top: 0;
  transform-origin: bottom;
}

.route-transition__veil--bottom {
  bottom: 0;
  transform-origin: top;
}

.route-transition__seam {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 1px;
  transform: translate(-50%, -0.5px);
}

.route-transition__line {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 1px;
  background: var(--route-line);
  box-shadow:
    0 0 7px var(--route-line),
    0 0 22px var(--route-glow),
    0 0 58px var(--route-glow);
  opacity: 0;
  transform: translate(-50%, -50%) scaleX(0);
  transform-origin: center;
}

/* Post entry: no curtain pull. The veils simply dissolve (that is the
   full-screen fade) while the seam draws over them. */

html.js .route-transition.is-complete {
  display: none;
}

.route-transition.is-line.is-running .route-transition__veil--top,
.route-transition.is-line.is-running .route-transition__veil--bottom {
  /* Hold the black screen for the whole seam sequence; it only lifts once
     the line has landed. */
  animation: route-veil-fade var(--post-veil-duration) var(--ease-out)
    var(--post-line-landing) both;
}

@keyframes route-veil-fade {
  from { opacity: 1; }
  to { opacity: 0; }
}

.route-transition.is-line.is-running .route-transition__line {
  animation: route-line-seam
    calc(var(--post-line-extend-duration) + var(--post-line-travel-duration))
    var(--ease-travel) var(--post-line-extend-delay) both;
}

.route-transition.is-line.is-running .route-transition__seam {
  animation:
    route-seam-travel var(--post-line-travel-duration) var(--ease-travel)
      calc(var(--post-line-extend-delay) + var(--post-line-extend-duration)) both,
    route-seam-handoff 260ms var(--ease-out)
      calc(var(--post-line-extend-delay) + var(--post-line-extend-duration) + var(--post-line-travel-duration))
      both;
}

/* From the vertical middle to just under the bar. */
@keyframes route-seam-travel {
  0% { transform: translate(-50%, -0.5px); }
  100% { transform: translate(-50%, calc(var(--header-height) - 50vh - 0.5px)); }
}

/* The travelling line dissolves exactly as the divider fades in. */
@keyframes route-seam-handoff {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* Extend first (0-39%), then ride along and settle into a soft halo. */
@keyframes route-line-seam {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scaleX(0);
    box-shadow:
      0 0 7px var(--route-line),
      0 0 22px var(--route-glow),
      0 0 58px var(--route-glow);
  }
  39% {
    opacity: 1;
    transform: translate(-50%, -50%) scaleX(1);
    box-shadow:
      0 0 7px var(--route-line),
      0 0 22px var(--route-glow),
      0 0 58px var(--route-glow);
  }
  100% {
    opacity: 1;
    transform: translate(-50%, -50%) scaleX(1);
    box-shadow:
      0 0 6px var(--route-line),
      0 0 12px var(--route-glow-soft),
      0 0 24px rgba(255, 255, 255, 0);
  }
}

html.js .route-transition.is-held {
  display: block;
}

.home-return {
  position: fixed;
  z-index: 245;
  inset: 0;
  display: none;
  overflow: hidden;
  background: var(--route-veil);
  opacity: 1;
  pointer-events: none;
}

html.js .home-return.is-held,
html.js .home-return.is-running {
  display: block;
}

.home-return::before,
.home-return::after {
  position: absolute;
  top: -50%;
  bottom: -50%;
  left: 0;
  width: 76vw;
  background: linear-gradient(118deg, transparent 0 43%, #fff 43% 49%, transparent 49%);
  content: "";
  opacity: 0;
  filter: drop-shadow(0 0 18px rgba(255, 255, 255, 0.42));
  transform: translateX(-135%) skewX(-7deg);
  will-change: opacity, transform;
}

.home-return::after {
  width: 54vw;
  background: linear-gradient(118deg, transparent 0 53%, #fff 53% 56%, transparent 56%);
}

.home-return.is-held {
  opacity: 1;
}

html.js .home-return.is-complete {
  display: none;
}

.home-return.is-running {
  animation: home-return-veil 1.71s var(--ease-sharp) both;
}

.home-return.is-running::before {
  animation: home-return-slash 1.08s var(--ease-sharp) 70ms both;
}

.home-return.is-running::after {
  animation: home-return-slash 1.08s var(--ease-sharp) 180ms both;
}

@keyframes home-return-veil {
  0%, 60% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes home-return-slash {
  0% { opacity: 0; transform: translateX(-135%) skewX(-7deg); }
  14% { opacity: 1; }
  76% { opacity: 0.94; }
  100% { opacity: 0; transform: translateX(165%) skewX(-7deg); }
}

.route-exit {
  position: fixed;
  z-index: 260;
  inset: 0;
  display: none;
  background: var(--route-veil);
  opacity: 0;
  pointer-events: none;
}

html.js .route-exit {
  display: block;
}

/* Leaving: the page blurs and fades down to a ghost over a white base, and
   only then does the white plate close over it to hide the swap. Holding the
   veil back is what keeps the blurred remnant readable instead of burying it. */
html.js body.route-leaving {
  background: var(--route-veil);
}

/* Leaving: the plate repaints itself in the transition colour so the blurred
   remnant of the old page floats on that base instead of on the live scene.
   Holding the veil back is what keeps the remnant readable. */
html.js body.route-leaving .scene-background__plate {
  background: var(--route-veil);
  opacity: 1;
  transition: opacity 200ms linear;
}

html.js body.route-leaving .scene-background__canvas {
  opacity: 0;
  transition: opacity 200ms linear;
}

html.js body.route-leaving .route-exit {
  animation: route-exit-veil 680ms var(--ease-sharp) forwards;
}

html.js body.route-leaving .main-content,
html.js body.route-leaving .site-footer {
  animation: route-exit-shell 680ms var(--ease-sharp) forwards;
  will-change: opacity, filter, transform;
}

/* The bar leaves with the page. It outranks every overlay, so a bar that
   stayed razor sharp while the shell blurred and the veil closed read as a
   hard cut — it now rides the same blur a beat behind the content and fades
   out over the closing veil. Written as a transition rather than an animation
   so it also eases back in when the next page takes over, instead of snapping
   to full opacity the moment route-leaving is dropped. */
html.js body.route-leaving .site-header {
  opacity: 0;
  filter: blur(12px);
  transform: scale(1.014);
  transition:
    opacity 620ms var(--ease-sharp) 60ms,
    filter 620ms var(--ease-sharp) 60ms,
    transform 620ms var(--ease-sharp) 60ms;
  pointer-events: none;
}

@keyframes route-exit-veil {
  0% { opacity: 0; }
  58% { opacity: 0; }
  100% { opacity: 1; }
}

@keyframes route-exit-shell {
  0% { opacity: 1; filter: none; transform: scale(1); }
  100% { opacity: 0.2; filter: blur(14px); transform: scale(1.012); }
}

@media (max-width: 960px) {
  .home-hero {
    min-height: 790px;
    grid-template-columns: 1fr;
  }

  .home-hero__side {
    position: absolute;
    right: 28px;
    bottom: 28px;
    left: 28px;
    grid-template-columns: repeat(3, 1fr);
    border-top: 1px solid var(--line);
    border-left: 0;
    padding: 18px 0 0;
  }

  .home-hero__metric {
    border-right: 1px solid var(--line);
  }

  .home-hero__metric:last-child {
    border-right: 0;
  }

  .home-hero__scroll {
    display: none;
  }

  .section-heading {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .article__layout {
    grid-template-columns: minmax(0, 760px);
  }

  .article__toc {
    position: static;
    max-height: none;
    padding: 0 0 22px;
    border-bottom: 1px solid var(--line);
  }
}

@media (max-width: 760px) {
  :root {
    --header-height: 66px;
    --scene-wallpaper-blur: 32px;
  }

  .scene-background__acrylic {
    background: var(--scene-acrylic);
  }

  .article__body.prose {
    padding: 24px 20px;
  }

  .site-header {
    height: var(--header-height);
    padding: 0 18px;
  }

  .site-nav {
    position: fixed;
    top: var(--header-height);
    right: 0;
    left: 0;
    display: grid;
    height: calc(100svh - var(--header-height));
    align-content: center;
    padding: 32px;
    gap: 0;
    background: var(--mobile-nav-bg);
    opacity: 0;
    pointer-events: none;
    transform: translateY(-12px);
    transition:
      opacity 220ms ease,
      transform 350ms var(--ease-out);
  }

  .site-nav.is-open {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
  }

  .site-nav__link {
    height: 84px;
    justify-content: space-between;
    border-bottom: 1px solid var(--line);
    font-size: 18px;
  }

  .site-nav__link::after {
    display: none;
  }

  .site-nav__icon {
    width: 100%;
    height: 68px;
    margin: 22px 0 0;
  }

  .theme-toggle {
    width: 100%;
    height: 68px;
    margin: 12px 0 0;
  }

  .nav-toggle {
    display: grid;
  }

  .intro__skip {
    top: 20px;
    right: 18px;
  }

  .intro__skip small,
  .intro__hint {
    display: none;
  }

  .intro__title {
    font-size: clamp(42px, 14.5vw, 76px);
  }

  .intro__actions {
    margin-top: 23px;
  }

  .intro__footer span:last-child {
    display: none;
  }

  .home-hero {
    min-height: 760px;
    padding: calc(var(--header-height) + 48px) 20px 150px;
  }

  .home-hero__name {
    font-size: clamp(50px, 18.5vw, 80px);
    letter-spacing: -0.08em;
  }

  .home-hero__name::after {
    right: -14px;
    width: 8px;
    height: 8px;
  }

  .home-hero__cn {
    margin-top: 13px;
    font-size: 24px;
  }

  .home-hero__eyebrow i,
  .home-hero__eyebrow span:last-child {
    display: none;
  }

  .home-hero__actions {
    align-items: stretch;
  }

  .home-hero__actions .button {
    flex: 1 1 100%;
  }

  .home-hero__side {
    right: 20px;
    bottom: 20px;
    left: 20px;
  }

  .home-hero__metric strong {
    font-size: 12px;
  }

  .stream-section {
    padding-top: 84px;
  }

  .stream-group summary {
    min-height: 106px;
    grid-template-columns: 44px minmax(0, 1fr) 46px;
    padding: 0 4px;
  }

  .stream-group__number::after,
  .stream-group__count {
    display: none;
  }

  .stream-group__toggle {
    width: 42px;
    height: 42px;
  }

  .stream-group {
    --tail: 20px;
  }

  .stream-group__inner {
    margin-right: 0;
    padding: 0;
  }

  .post-card {
    grid-template-columns: 1fr;
  }

  .post-card__rail {
    display: none;
  }

  .post-card__content {
    padding: 22px 19px 20px;
  }

  .post-card h3 {
    margin-top: 18px;
  }

  .post-card__bottom {
    align-items: start;
    flex-direction: column;
  }

  .site-footer__main {
    align-items: start;
    flex-direction: column;
  }

  .site-footer__meta {
    text-align: left;
  }

  .article,
  .standalone {
    padding-top: calc(var(--header-height) + 48px);
  }

  .article__heading {
    margin-top: 42px;
  }

  .article__heading h1,
  .standalone__header h1,
  .listing__header h1 {
    font-size: clamp(34px, 11vw, 56px);
  }

  .article__meta {
    gap: 12px;
    flex-direction: column;
  }

  .article__cover {
    margin-top: 44px;
  }

  .article__layout {
    margin-top: 52px;
  }

  .article__footer {
    align-items: start;
    flex-direction: column-reverse;
  }

  .article-nav {
    grid-template-columns: 1fr;
    margin-bottom: 80px;
  }

  .article-nav a {
    min-height: 125px;
  }

  .article-nav a + a {
    border-top: 1px solid var(--line);
    border-left: 0;
    text-align: left;
  }

  .archive-row {
    grid-template-columns: 62px minmax(0, 1fr) 22px;
    gap: 12px;
  }

  .archive-row > span {
    display: none;
  }

  .archive-year summary {
    grid-template-columns: 1fr auto 24px;
    gap: 14px;
  }

  .pagination {
    margin-bottom: 80px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    /* Delays matter too: the post entry staggers up to ~1.6s, which would
       otherwise leave reduced-motion users staring at a blank page. */
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
  }

  .route-transition {
    display: none !important;
  }

  .theme-transition {
    display: none !important;
  }

  .scene-background.is-visible .scene-background__plate,
  .scene-background.is-visible .scene-background__canvas {
    transition-delay: 0ms !important;
  }
}
