/* dehpeh.dev — design tokens.
   Default is the light theme, which is what stripe.dev serves when the system
   does not ask for dark. It is close to monochrome: one ink colour on a faintly
   tinted off-white. Other themes override the same tokens below. */
:root {
  --red: #7A1F1F;
  --term-bg: #1E1E1E;
  --term-bar: #D7D9DB;
  --term-fg: #E8E8E8;
  --term-dim: #8A8A8A;
  --term-accent: #7FD1C1;
  --bg: #E8E8E8;
  --bg-image: linear-gradient(20deg, #F1F9F3, #F3F1F9, #F9F3F1);
  --panel: rgba(30, 30, 30, 0.06);
  --line: rgba(30, 30, 30, 0.2);
  --dim: rgba(30, 30, 30, 0.66);
  --bright: #1E1E1E;
  --teal: #1E1E1E;
  --purple: #1E1E1E;
  --gold: #1E1E1E;
  --blue: #1E1E1E;
  --scrim: rgba(232, 232, 232, 0.88);
  --sans: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", "SF Mono", Menlo, monospace;
}

/* Night Owl. The dark palette stripe.dev uses when the system prefers dark. */
[data-theme="night-owl"] {
  --red: #EF5350;
  --term-bg: #04121F;
  --term-bar: #C0C7D1;
  --term-fg: #C0C7D1;
  --term-dim: #5F7D97;
  --term-accent: #75D1C4;
  --bg: #011627;
  --bg-image: none;
  --panel: #0A2534;
  --line: #14344B;
  --dim: #5F7D97;
  --bright: #C0C7D1;
  --teal: #75D1C4;
  --purple: #9372C9;
  --gold: #F0C078;
  --blue: #6E9CF1;
  --scrim: rgba(1, 22, 39, 0.85);
}

/* Amber CRT. Phosphor on black, for the full terminal fantasy. */
[data-theme="amber"] {
  --red: #FF6B00;
  --term-bg: #0B0700;
  --term-bar: #FFB000;
  --term-fg: #FFB000;
  --term-dim: #8A6A1F;
  --term-accent: #FFC547;
  --bg: #0B0700;
  --bg-image: none;
  --panel: #1A1200;
  --line: #3D2B00;
  --dim: #8A6A1F;
  --bright: #FFB000;
  --teal: #FFC547;
  --purple: #D98A00;
  --gold: #FFB000;
  --blue: #FFC547;
  --scrim: rgba(11, 7, 0, 0.88);
  --sans: "IBM Plex Mono", "SF Mono", Menlo, monospace;
}

/* Paper. High contrast, no tint, for reading long posts. */
[data-theme="paper"] {
  --red: #8B2E1E;
  --term-bg: #141412;
  --term-bar: #DAD8D2;
  --term-fg: #FBFAF7;
  --term-dim: #9A968C;
  --term-accent: #D9A066;
  --bg: #FBFAF7;
  --bg-image: none;
  --panel: rgba(20, 20, 18, 0.05);
  --line: rgba(20, 20, 18, 0.18);
  --dim: rgba(20, 20, 18, 0.6);
  --bright: #141412;
  --teal: #7A4A1E;
  --purple: #7A4A1E;
  --gold: #7A4A1E;
  --blue: #7A4A1E;
  --scrim: rgba(251, 250, 247, 0.9);
}

/* 90s vibes. A nod to the joke theme on stripe.dev. */
[data-theme="90s"] {
  --red: #CC0000;
  --term-bg: #000080;
  --term-bar: #C0C0C0;
  --term-fg: #FFFFFF;
  --term-dim: #A8A8C8;
  --term-accent: #FFFF55;
  --bg: #F5F1FF;
  --bg-image: none;
  --panel: #E6DEFF;
  --line: #9A8FC4;
  --dim: #333333;
  --bright: #000000;
  --teal: #0000EE;
  --purple: #551A8B;
  --gold: #CC0000;
  --blue: #0000EE;
  --scrim: rgba(245, 241, 255, 0.9);
  --sans: "Times New Roman", Times, serif;
  --mono: "Courier New", Courier, monospace;
}

[data-theme="90s"] .prose a,
[data-theme="90s"] .row:hover .row__name { text-decoration: underline; }

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background-color: var(--bg);
  background-image: var(--bg-image);
  background-attachment: fixed;
  color: var(--dim);
  font-family: var(--sans);
  font-weight: 300;
  letter-spacing: -0.03em;
  -webkit-font-smoothing: antialiased;
}

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

/* ============ MOTION ============
   Vocabulary borrowed from stripe.dev: an expo-out curve for anything that
   moves, fast linear fades for anything that only changes colour, and a
   step-end blink for the terminal caret. */
:root { --ease: cubic-bezier(0.19, 1, 0.22, 1); }

@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}

@keyframes px-in {
  from { opacity: 0; transform: scale(0.3); }
  to { opacity: 1; transform: none; }
}

@keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }

@keyframes drift {
  from { transform: translateY(0); }
  to { transform: translateY(4px); }
}

/* Scroll reveal. Content only starts hidden when JS is present to reveal it,
   so the page still reads fine with scripts off. */
.js [data-reveal] { opacity: 0; transform: translateY(18px); }

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s linear, transform 0.65s var(--ease);
  transition-delay: var(--d, 0ms);
}

/* Hero builds itself on load. */
.hero__title { animation: rise 0.85s var(--ease) both; }
.hero__sub { animation: rise 0.85s var(--ease) 0.1s both; }
.hero__stamps { animation: fadein 0.8s ease 0.5s both; }

/* Each module of the wordmark pops in, left to right. */
.pixel-word rect {
  transform-box: fill-box;
  transform-origin: center;
  animation: px-in 0.34s var(--ease) both;
  animation-delay: var(--d, 0ms);
}

.plus { animation: drift 3.5s ease-in-out infinite alternate; animation-delay: var(--d, 0s); }

/* Colour crossfade while a theme swaps, then removed so hovers stay snappy. */
.theming, .theming *, .theming *::before, .theming *::after {
  transition: background-color 0.35s linear, color 0.35s linear,
              border-color 0.35s linear, fill 0.35s linear !important;
}

::selection { background: var(--gold); color: var(--bg); }

/* ============ TOP BAR ============ */
/* No rule under the bar and no fill behind it: the chips sit straight on the
   page and content scrolls beneath them, the way stripe.dev has it. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 12px;
  background: transparent;
}

.topbar__flag {
  display: grid;
  place-items: center;
  padding: 5px 8px;
  background: var(--panel);
  border-radius: 2px;
}

.topbar__nav {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
}

.topbar__nav a,
.topbar__console {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 300;
  line-height: 1;
  /* Negative tracking is what makes these read dense at this size. */
  letter-spacing: -0.025em;
  text-transform: uppercase;
  color: var(--teal);
  background: var(--panel);
  border: none;
  border-radius: 2px;
  padding: 5px 8px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.topbar__nav a:hover,
.topbar__console:hover {
  background: var(--teal);
  color: var(--bg);
}

.topbar__console { margin-left: auto; }

/* The bracketed key tracks tighter than its label and shares its colour. */
.key { letter-spacing: -0.08em; }

/* ============ SHARED ============ */
.section-label {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dim);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.5rem;
  margin-bottom: 1.25rem;
}

.section-label--pad { margin: 0 1.5rem 1.25rem; }

.chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  color: var(--purple);
  border: 1px dotted var(--purple);
  padding: 2px 5px 2.5px;
}

.arr {
  font-size: 0.7em;
  vertical-align: 0.15em;
  display: inline-block;
  transition: transform 0.3s var(--ease);
}

a:hover .arr, .featured__card:hover .arr { transform: translate(3px, -3px); }

/* stripe.dev outlines its buttons in both themes rather than filling them. */
.btn {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--bright);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.55rem 1rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.btn:hover {
  background: var(--bright);
  color: var(--bg);
  border-color: var(--bright);
}

/* Full-width pill, the shape stripe.dev uses on its Get help cards.
   Doubled class so it outranks the mono-tracking rule further down, which
   lists .btn and would otherwise reset the weight and family. */
.btn.btn--pill {
  display: block;
  width: 100%;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: -0.03em;
  border-radius: 99px;
  padding: 10px 12px 12px;
}

.count { color: var(--dim); font-weight: 300; }

/* On a section heading the count rides as a superscript. Scoped, because at
   filter-list size the same treatment renders it about four pixels tall. */
.index__title .count {
  font-size: 0.28em;
  letter-spacing: -0.02em;
  vertical-align: super;
  margin-left: 0.15em;
}

/* Mono runs looser than the sans: Stripe tracks it at -0.012em, not -0.03em. */
.section-label, .chip, .btn, .more-link,
.stat__label, .table__head, .row__date, .row__type, .filter__clear,
.router__head, .router-row__topics a, .help-card__example,
.footer__link, .footer__legal, .console__bar, .console__bar button,
.console__out, .console__prompt, .console__line input {
  font-weight: 300;
  letter-spacing: -0.012em;
}

button, input { font-weight: 300; }

.plus {
  position: absolute;
  font-family: var(--mono);
  color: var(--dim);
  opacity: 0.5;
  font-size: 0.9rem;
  pointer-events: none;
}

/* ============ HERO ============ */
.hero {
  position: relative;
  padding: 4.5rem 1.5rem 4rem;
  border-bottom: 1px solid var(--line);
}

.hero__title {
  font-size: clamp(3rem, 10.5vw, 9rem);
  font-weight: 300;
  line-height: 0.84;
  letter-spacing: -0.07em;
  color: var(--bright);
  max-width: 12ch;
}

.pixel-word {
  display: inline-block;
  vertical-align: baseline;
  line-height: 0;
  margin-left: 0.12em;
}

.pixel-word svg { display: block; height: 0.72em; width: auto; }

.hero__sub {
  margin-top: 2.5rem;
  font-size: clamp(1.3rem, 2.76vw, 2.4rem);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -0.045em;
  max-width: 38ch;
}

.hero__stamps {
  position: absolute;
  top: 3.5rem;
  right: 2rem;
  display: flex;
  gap: 0.75rem;
}

.stamp {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border: 1px solid var(--gold);
  border-radius: 50%;
  color: var(--gold);
  font-family: var(--mono);
  font-size: 0.8rem;
  animation: spin-hold 14s linear infinite;
}

.stamp--globe svg { animation: spin 14s linear infinite; }

@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ============ FEATURED ============ */
.featured {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--line);
}

.featured__col {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
}

.featured__col + .featured__col { border-left: 1px dotted var(--line); }

.featured__card { display: block; flex: 1; }

.featured__card h2 {
  font-size: clamp(1.5rem, 2.8vw, 2.4rem);
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1.2;
  color: var(--bright);
  margin-bottom: 0.9rem;
  transition: color 0.15s;
}

.featured__card:hover h2 { color: var(--teal); }

.featured__card p {
  font-size: 1.16rem;
  font-weight: 300;
  letter-spacing: -0.03em;
  line-height: 1.15;
  margin-bottom: 1rem;
  max-width: 55ch;
}

/* Sits after .btn--pill so these win: the card action is the accent-coloured
   one, and it spans the text column rather than trailing off as a text link. */
.more-link {
  color: var(--teal);
  border-color: var(--teal);
  margin-top: 1.5rem;
}

.more-link:hover {
  background: var(--teal);
  border-color: var(--teal);
  color: var(--bg);
  text-decoration: none;
}

/* ============ STATS TICKER ============ */
.stats {
  padding: 1.5rem 0 2rem;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.ticker { overflow: hidden; white-space: nowrap; }

/* Wrapping is the static fallback. The rolling and measuring states both
   force a single unwrapped row, so a group is always measured at its natural
   width rather than wrapped to the container. */
.ticker__track {
  display: flex;
  flex-wrap: wrap;
  row-gap: 0.75rem;
}

.ticker__track.is-rolling,
.ticker__track.is-measuring {
  flex-wrap: nowrap;
  width: max-content;
}

/* Rolls only once JS has measured a group and cloned enough to cover the
   viewport. --shift is exactly one group wide, so the loop is seamless at
   any width, and the duration is derived from that width to hold a constant
   speed rather than a constant lap time. */
.ticker__track.is-rolling {
  animation: ticker var(--dur, 40s) linear infinite;
}

.ticker:hover .ticker__track.is-rolling { animation-play-state: paused; }

.ticker__group {
  display: flex;
  flex-wrap: wrap;
  gap: 3.5rem;
  padding-right: 3.5rem;
}

.ticker__track.is-rolling .ticker__group,
.ticker__track.is-measuring .ticker__group { flex-wrap: nowrap; }

@keyframes ticker {
  from { transform: translateX(0); }
  to { transform: translateX(calc(-1 * var(--shift))); }
}

/* Nothing to clip when the row is not rolling, so let it show in full. */
.ticker:has(.ticker__track:not(.is-rolling)) { overflow: visible; }

/* A stat is a mono label with the value in a dotted tag, divided from its
   neighbour by a rule, rather than the oversized numerals I had before. */
.stat {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.85rem;
  border-left: 1px solid var(--line);
  white-space: nowrap;
}

.stat__label {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: -0.012em;
  color: var(--dim);
}

.stat__value {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: -0.012em;
  color: var(--purple);
  border: 1px dotted var(--purple);
  border-radius: 3px;
  padding: 2px 5px 2.5px;
}

/* ============ INDEX ============ */
.index { border-bottom: 1px solid var(--line); }

.index__head { padding: 2.5rem 1.5rem 1.5rem; }

.index__title {
  font-size: clamp(2rem, 4.5vw, 3.2rem);
  font-weight: 300;
  letter-spacing: -0.05em;
  line-height: 1;
  color: var(--bright);
}

.index__body {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 2rem;
  padding: 0 1.5rem 3rem;
  align-items: start;
}

.filter__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.filter__head .section-label { flex: 1; margin-bottom: 0; }

.filter__clear {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--dim);
  background: none;
  border: 1px dotted var(--dim);
  padding: 2px 6px;
  cursor: pointer;
}

.filter__clear:hover { color: var(--teal); border-color: var(--teal); }

/* Type and Topic sit side by side inside the sidebar, each a collapsible
   group headed by a chevron, a folder mark and its name. */
.filter__groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 7rem), 1fr));
  gap: 1.25rem 1.5rem;
  margin-top: 1.5rem;
}

.filter__toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--teal);
}

.filter__toggle svg { flex: none; }

.filter__chevron { transition: transform 0.2s var(--ease); }
.filter__toggle[aria-expanded="false"] .filter__chevron { transform: rotate(-90deg); }

.filter__group-name {
  font-size: 0.875rem;
  font-weight: 300;
  letter-spacing: -0.03em;
  color: var(--teal);
}

/* Indented to sit under the folder mark rather than the chevron, so the
   options read as belonging to the group above them. */
.filter__list {
  list-style: none;
  margin-top: 0.4rem;
  padding-left: 1.05rem;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.filter__toggle[aria-expanded="false"] + .filter__list { display: none; }

.filter__opt {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 300;
  line-height: 1.6;
  letter-spacing: -0.03em;
  color: var(--dim);
  transition: color 0.12s linear;
}

.filter__opt svg { flex: none; }
.filter__opt:hover { color: var(--bright); }
.filter__opt.active { color: var(--bright); }
.filter__opt .count { color: var(--dim); }

.table__head {
  display: grid;
  grid-template-columns: 118px 1fr 84px 1rem;
  gap: 1rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  color: var(--dim);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.5rem;
}

.row {
  display: grid;
  grid-template-columns: 118px 1fr 84px 1rem;
  gap: 1rem;
  align-items: baseline;
  padding: 0.8rem 0.6rem;
  margin-inline: -0.6rem;
  border-bottom: 1px dotted var(--line);
  cursor: pointer;
  transition: background 0.12s linear;
}

.row:hover { background: var(--panel); }

.row .row__name { transition: color 0.12s linear, transform 0.3s var(--ease); }
.row:hover .row__name { color: var(--teal); transform: translateX(6px); }

.row .row__date { transition: color 0.12s linear; }
.row:hover .row__date { color: var(--bright); }

.row__date {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.8rem;
  letter-spacing: -0.012em;
  color: var(--blue);
}

.row__name {
  font-size: 1.5rem;
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1.2;
  color: var(--bright);
  transition: color 0.15s;
}

.row__date {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.row__bullet {
  width: 7px;
  height: 7px;
  flex: none;
  background: currentColor;
}

.row__type {
  justify-self: start;
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.75rem;
  letter-spacing: -0.012em;
  text-transform: uppercase;
  color: var(--dim);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 2px 5px 2.5px;
}

.row__plus {
  justify-self: end;
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--dim);
  transition: transform 0.3s var(--ease), color 0.12s linear;
}

.row:hover .row__plus { color: var(--bright); transform: rotate(90deg); }

/* ============ HELP CARDS ============ */
.help { padding: 2.5rem 1.5rem 3rem; border-bottom: 1px solid var(--line); }

/* Two up, no card chrome. The section rule and the dotted example line do the
   dividing, so the cards read as columns of a sheet rather than boxes. */
.help__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2.75rem 3rem;
  margin-top: 1.5rem;
}

.help-card {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  min-width: 0;
}

.help-card__text {
  font-size: clamp(1.2rem, 1.9vw, 1.7rem);
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1.2;
  color: var(--bright);
  flex: 1;
}

.help-card__text strong { font-weight: 500; }

.help-card__example {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: -0.012em;
  color: var(--dim);
  border-bottom: 1px dotted var(--line);
  padding-bottom: 0.9rem;
  min-height: 1.9rem;
}

.help-card__bullet {
  width: 7px;
  height: 7px;
  flex: none;
  background: currentColor;
}

/* Caret sits after whatever has been typed so far. */
.help-card__typed::after {
  content: "_";
  margin-left: 0.1em;
  animation: blink 1.2s step-end infinite;
}

/* ============ ROUTER ============ */
.router { padding: 2.5rem 1.5rem 3rem; border-bottom: 1px solid var(--line); }

.router__head {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.5rem;
  margin-top: 1.5rem;
}

.router-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  padding: 1.75rem 0;
  border-bottom: 1px dotted var(--line);
}

.router-row__info h3 {
  font-size: 1.5rem;
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--bright);
  margin-bottom: 0.6rem;
}

.router-row__info p { margin-bottom: 1rem; max-width: 45ch; line-height: 1.15; }

.router-row__topics {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  justify-content: center;
}

.router-row__topics a {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--teal);
}

.router-row__topics a:hover { text-decoration: underline; }

/* ============ FOOTER ============ */
.footer { padding: 2.5rem 1.5rem 1.5rem; }

.footer__cols {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 2rem;
}

.footer__blurb { margin-bottom: 1rem; max-width: 40ch; }

.footer__link {
  display: block;
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--teal);
  padding: 0.2rem 0;
}

.footer__link:hover { text-decoration: underline; }

.footer__legal {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  margin-top: 3rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}

.footer__legal a { margin-left: 1rem; }
.footer__legal a:hover { color: var(--teal); }

/* ============ ARTICLE ============ */
.article { border-bottom: 1px solid var(--line); }

.article__body {
  max-width: 46rem;
  margin: 0 auto;
  padding: 3rem 1.5rem 4rem;
}

.article__back {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: -0.012em;
  color: var(--teal);
}

.article__back:hover { text-decoration: underline; }

.article__meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 2rem;
}

.article__read {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: -0.012em;
  color: var(--dim);
}

.article__title {
  font-size: clamp(2.2rem, 5.5vw, 3.6rem);
  font-weight: 300;
  letter-spacing: -0.05em;
  line-height: 1.02;
  color: var(--bright);
  margin-top: 1.25rem;
}

.article__dek {
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  font-weight: 300;
  letter-spacing: -0.03em;
  line-height: 1.25;
  margin-top: 1.25rem;
}

.article__rule {
  border-top: 1px solid var(--line);
  margin: 2.5rem 0;
}

.article__notfound { max-width: 46rem; margin: 0 auto; padding: 5rem 1.5rem; }
.article__notfound p { margin: 1rem 0 2rem; }

/* ---- Prose ---- */
.prose { font-size: 1.14rem; line-height: 1.6; letter-spacing: -0.02em; }

.prose p { margin-bottom: 1.5rem; }

.prose h2 {
  font-size: 1.6rem;
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1.15;
  color: var(--bright);
  margin: 2.75rem 0 1rem;
}

.prose ul { margin: 0 0 1.5rem 1.1rem; }
.prose li { margin-bottom: 0.6rem; }
.prose li::marker { color: var(--teal); }

.prose blockquote {
  border-left: 2px solid var(--gold);
  padding-left: 1.25rem;
  margin: 2rem 0;
  font-size: 1.35rem;
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--bright);
}

.prose pre {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 1rem 1.1rem;
  margin-bottom: 1.5rem;
  overflow-x: auto;
}

.prose code {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 0.86rem;
  letter-spacing: -0.012em;
  line-height: 1.55;
  color: var(--gold);
  white-space: pre;
}

/* ---- Prev / next ---- */
.postnav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
}

.postnav__item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--bg);
  padding: 1.75rem 1.5rem;
}

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

.postnav__dir {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: -0.012em;
  color: var(--dim);
}

.postnav__title {
  font-size: 1.25rem;
  font-weight: 300;
  letter-spacing: -0.04em;
  line-height: 1.2;
  color: var(--bright);
  transition: color 0.15s;
}

a.postnav__item:hover .postnav__title { color: var(--teal); }

.row-empty { padding: 2rem 0; }

/* ============ CONSOLE ============ */
/* Docked to the right rather than centred, and deliberately without a scrim:
   the page stays readable and clickable while the console is open, so it reads
   as a tool alongside the site instead of a modal on top of it. */
.console[hidden] { display: none; }

.console {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  justify-content: flex-end;
  padding: 1.1rem;
  pointer-events: none; /* only the window itself takes the pointer */
}

.console__window {
  pointer-events: auto;
  width: min(600px, 100%);
  display: flex;
  flex-direction: column;
  background: var(--term-bg);
  border: 1px solid var(--line);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.28);
}

.console__bar {
  flex: none;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  background: var(--term-bar);
  color: #1E1E1E;
}

.console__glyphs { display: flex; gap: 5px; }
.console__glyphs svg { display: block; }

.console__title {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-align: center;
}

.console__bar button {
  justify-self: end;
  font-family: var(--mono);
  font-size: 0.85rem;
  line-height: 1;
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  padding: 2px 4px;
}

.console__bar button:hover { opacity: 0.6; }

.console__out {
  flex: 1;
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--term-fg);
  padding: 0.75rem 0.85rem 0;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.console__out .c-dim { color: var(--term-dim); }
.console__out .c-teal { color: var(--term-accent); }
.console__out .c-gold { color: var(--term-accent); }
.console__out .c-err { color: var(--red); }
.console__out .c-line { display: block; animation: fadein 0.18s ease both; }

/* The banner is the pixel wordmark reduced to an outline, so it is the same
   letterforms as the header rather than a second, unrelated piece of type. */
.console__banner {
  color: var(--term-dim);
  font-size: 0.62rem;
  line-height: 1.02;
  display: block;
  margin-bottom: 0.35rem;
}

.console__rule { color: var(--term-dim); opacity: 0.55; display: block; overflow: hidden; height: 1.1em; }

.console__line {
  flex: none;
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.5em;
  padding: 0.35rem 0.85rem 0.85rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  cursor: text;
}

.console__prompt { color: var(--term-accent); }

/* The real input sits transparently on top of a mirror of its own text. That
   is what allows a solid block cursor: the caret is drawn as an element
   between the two halves of the mirrored string, so it lands in the right
   place even when editing mid-line. */
.console__mirror {
  flex: 1;
  min-width: 0;
  color: var(--term-fg);
  white-space: pre;
  overflow: hidden;
}

.console__cursor {
  display: inline-block;
  width: 0.62em;
  height: 1.05em;
  vertical-align: text-bottom;
  background: var(--term-accent);
  animation: blink 1.1s step-end infinite;
}

.console:not(.is-focused) .console__cursor { opacity: 0.45; animation: none; }

.console__line input {
  position: absolute;
  left: 0.85rem;
  right: 0.85rem;
  top: 0.35rem;
  width: auto;
  font: inherit;
  color: transparent;
  caret-color: transparent;
  background: none;
  border: none;
  outline: none;
  padding: 0 0 0 1em;
}

.console__line input::selection { background: var(--term-accent); color: var(--term-bg); }

@media (max-width: 700px) {
  .console { padding: 0.6rem; }
  .console__window { width: 100%; }
}

/* ============ COMPOSER ============ */
/* Opened by an unlisted console command. It writes an entry for posts.js and
   nothing else: there is no network call here, so publishing still means a
   commit. See the header of compose.js. */
.cmp[hidden] { display: none; }

.cmp {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--scrim);
  display: grid;
  place-items: start center;
  padding: 1.25rem;
  overflow-y: auto;
}

.cmp__window {
  width: min(780px, 100%);
  background: var(--bg);
  border: 1px solid var(--line);
}

.cmp__bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 0.6rem;
  background: var(--term-bar);
  color: #1E1E1E;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
}

.cmp__bar button {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
}

.cmp__body { padding: 1rem; }

.cmp__note {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--dim);
  margin-bottom: 0.9rem;
}

.cmp__note code { color: var(--teal); }

.cmp__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}

.cmp label,
.cmp__wide {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
}

.cmp input,
.cmp select,
.cmp textarea {
  display: block;
  width: 100%;
  margin-top: 0.2rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--bright);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.4rem 0.5rem;
  text-transform: none;
  letter-spacing: 0;
}

.cmp textarea { resize: vertical; line-height: 1.45; }
.cmp input:focus, .cmp select:focus, .cmp textarea:focus { outline: 1px solid var(--teal); }

.cmp__blockshead,
.cmp__outhead {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 1.1rem 0 0.5rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
}

.cmp__status { text-transform: none; letter-spacing: 0; color: var(--teal); }

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

.cmp__block {
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.4rem;
  margin-bottom: 0.5rem;
}

.cmp__blockbar { display: flex; align-items: center; gap: 0.35rem; margin-bottom: 0.35rem; }
.cmp__blockbar select { width: auto; margin: 0; }
.cmp__spacer { flex: 1; }

.cmp__blockbar button {
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1;
  background: none;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--dim);
  cursor: pointer;
  padding: 3px 6px;
}

.cmp__blockbar button:hover { color: var(--bright); border-color: var(--bright); }

.cmp__out {
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--term-fg);
  background: var(--term-bg);
  border: 1px solid var(--line);
  padding: 0.6rem;
  max-height: 240px;
  overflow: auto;
  white-space: pre;
}

.cmp__tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--line);
}

.cmp__tab {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--dim);
  cursor: pointer;
  padding: 0.55rem 0.9rem;
}

.cmp__tab:hover { color: var(--bright); }
.cmp__tab.is-on { color: var(--bright); border-bottom-color: var(--teal); }

.cmp__note--warn { color: var(--red); }

.cmp__list { list-style: none; margin: 0 0 0.8rem; padding: 0; }

/* A table without a table: the date column is fixed so the titles line up,
   and the actions sit hard right whatever the title length. */
.cmp__row {
  display: grid;
  grid-template-columns: 6.5em 1fr auto auto;
  gap: 0.6rem;
  align-items: center;
  padding: 0.4rem 0.2rem;
  border-bottom: 1px dotted var(--line);
  font-size: 0.82rem;
}

.cmp__row.is-editing { background: var(--panel); }

.cmp__rowdate { font-family: var(--mono); font-size: 0.72rem; color: var(--dim); }
.cmp__rowtitle { color: var(--bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cmp__rowtype {
  font-family: var(--mono);
  font-size: 0.65rem;
  text-transform: uppercase;
  color: var(--dim);
}

.cmp__rowacts { display: flex; gap: 0.25rem; }

.cmp__rowacts button {
  font-family: var(--mono);
  font-size: 0.7rem;
  line-height: 1;
  background: none;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--dim);
  cursor: pointer;
  padding: 3px 6px;
}

.cmp__rowacts button:hover { color: var(--bright); border-color: var(--bright); }
.cmp__rowacts button[data-mact="del"]:hover { color: var(--red); border-color: var(--red); }

.cmp__empty {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--red);
  padding: 0.8rem 0.2rem;
}

.cmp__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.8rem; }
.cmp__actions .btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ============ FIGURE ART ============ */
/* Window chrome around a canvas figure. The frame is deliberately plain: the
   rules either side of the caption only appear on hover, so a resting card
   shows the label alone and the drag affordance arrives with the cursor. */
.art {
  --art-chrome: 20px;
  aspect-ratio: 302 / 252;
  border: 1px solid var(--line);
  background: var(--panel);
  display: flex;
  flex-direction: column;
  margin-bottom: 1.2rem;
  user-select: none;
  -webkit-user-select: none;
}

.art__chrome {
  height: var(--art-chrome);
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 6px;
  border-bottom: 1px solid var(--line);
}

.art__rule {
  flex: 1;
  height: 1px;
  background: var(--line);
  opacity: 0;
  transition: opacity 0.15s;
}

.art:hover .art__rule { opacity: 1; }

.art__fig {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}

.art__open {
  flex: none;
  font-size: 0.7rem;
  line-height: 1;
  color: var(--dim);
  opacity: 0;
  transition: opacity 0.15s, color 0.15s;
}

.art:hover .art__open { opacity: 1; }
.art__open:hover { color: var(--teal); }

/* The dot grid sits under the canvas rather than in it, so the renderer never
   has to redraw the backdrop and it stays crisp at any DPR. */
.art__stage {
  flex: 1;
  min-height: 0;
  position: relative;
  margin: 4px;
  border: 1px solid var(--line);
  border-radius: 2px;
  cursor: grab;
  background-image: radial-gradient(var(--line) 1px, transparent 1px);
  background-size: 12px 12px;
  background-position: center;
  overflow: hidden;
}

.art.is-dragging .art__stage { cursor: grabbing; }

/* The renderer reads this colour off the canvas to pick its ink, which is how
   the figure follows the theme without knowing any of the palettes. */
.art__canvas {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--bright);
  touch-action: pan-y;
}

/* Holding a figure runs its clock ten times faster. Nothing else on screen
   says so, which is what the badge is for: it appears on press and pulses for
   as long as the pointer is down. Under motion-reduced the pulse is flattened
   by the global rule at the end of this file, and the badge just sits there. */
.art__tenx {
  position: absolute;
  top: 6px;
  left: 8px;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  color: var(--dim);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s;
}

.art.is-boosting .art__tenx {
  opacity: 1;
  animation: artPulse 1s ease infinite;
}

@keyframes artPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

.art.is-boosting .art__stage { cursor: grabbing; }

/* The feed and router figures sit in columns far wider than a card, so they
   need a ceiling or the router one alone would fill half the section. */
.filter__art { margin-top: 1.5rem; }
.router-row--art .art { max-width: 340px; margin-bottom: 0; }

/* ============ INSTRUMENTATION PAGE ============ */
.lab__bar {
  display: flex;
  align-items: baseline;
  gap: 1.2rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.8rem;
}

.lab__back { color: var(--teal); }
.lab__back:hover { text-decoration: underline; }
.lab__title { color: var(--dim); letter-spacing: 0.08em; }

.lab {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: calc(100vh - 3.2rem);
}

.lab__panel {
  padding: 1.5rem;
  border-right: 1px dotted var(--line);
}

.lab__params {
  list-style: none;
  margin: 0.8rem 0 1.2rem;
  padding: 0;
}

/* Rows are a two-column grid rather than a flex row with space-between, so
   every value starts at the same x and the column reads as a table. */
.lab__param {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.18rem 0.35rem;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--dim);
  cursor: pointer;
  border-left: 2px solid transparent;
}

.lab__param:hover { color: var(--bright); }

.lab__param.is-armed {
  color: var(--bright);
  background: var(--panel);
  border-left-color: var(--teal);
}

.lab__value { font-variant-numeric: tabular-nums; }
.lab__param.is-armed .lab__value { color: var(--teal); }

.lab__reset {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--dim);
  padding-top: 0.6rem;
  border-top: 1px dotted var(--line);
}

.lab__stage {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

/* The card figures are locked to a card-shaped box; here the figure is the
   point of the page, so it takes whatever room is left. */
.art--lab {
  aspect-ratio: auto;
  flex: 1;
  min-height: 320px;
  margin-bottom: 0;
}

.lab__hint {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--dim);
  margin: 0;
}

.lab__actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }

/* ============ RESPONSIVE ============ */
@media (max-width: 900px) {
  .featured { grid-template-columns: 1fr; }
  .featured__col + .featured__col { border-left: none; border-top: 1px dotted var(--line); }
  .index__body { grid-template-columns: 1fr; }
  .help__grid { grid-template-columns: 1fr 1fr; }
  .router__head { display: none; }
  .router-row { grid-template-columns: 1fr; gap: 1rem; }
  .footer__cols { grid-template-columns: 1fr; }
  .hero__stamps { top: 1.5rem; right: 1rem; }
}

@media (max-width: 560px) {
  .help__grid { grid-template-columns: 1fr; }
  .table__head, .row { grid-template-columns: 92px 1fr 66px; gap: 0.6rem; }
  /* No room for the trailing plus at this width. */
  .table__head span:nth-child(4), .row__plus { display: none; }
  .topbar__nav { overflow-x: auto; flex-wrap: nowrap; }
}

/* Animation is on by default. JS adds .motion-reduced when a visitor turns it
   off via the console, or picks "auto" and their system asks for less motion.
   The ticker gates itself on .is-rolling instead. */
html.motion-reduced *,
html.motion-reduced *::before,
html.motion-reduced *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

html.motion-reduced { scroll-behavior: auto; }

html.motion-reduced .stamp--globe svg,
html.motion-reduced .plus,
html.motion-reduced .console__prompt::after { animation: none !important; }

html.motion-reduced.js [data-reveal] { opacity: 1; transform: none; }

@media (max-width: 800px) {
  .lab { grid-template-columns: 1fr; }
  .lab__panel { border-right: none; border-bottom: 1px dotted var(--line); }
}

/* Publishing is the one control here that changes what other people see, so it
   is the one that looks different from the rest. */
.cmp__publish {
  color: var(--teal);
  border-color: var(--teal);
}

.cmp__publish:hover { background: var(--teal); color: var(--bg); }
.cmp__signin { color: var(--dim); text-decoration: none; }
