/* FujoOS — design system
   Concept: "The Envelope". One canvas: near-black, everywhere. The light
   surface is not a second theme, it is an object on that canvas — a #eeeeee
   card landing on #101010, which is the whole figure/ground move.

   Two functional accents, used only where they name a trust state or plot
   data — never as chrome:
     --amber  the model / a proposal / untrusted   (the spec's "signal")
     --steel  the kernel / verified / mechanism    (the spec's "metric")
   The names are historical. The spec calls these signal and metric; renaming
   them here would touch 56 colour-class references across three hand-written
   pages for no visual gain, so the mapping is recorded instead of applied.

   One sanctioned extension to the spec's palette: --alert and --warn, the
   announcement severities. Severity is a status, and status is the one thing
   the spec allows colour for. Nothing else may join them.
*/

/* ---------------------------------------------------------------- tokens */

:root {
  --sans: "Geist", "Noto Sans SC", ui-sans-serif, system-ui, -apple-system,
    "PingFang SC", "Microsoft YaHei", sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, "IBM Plex Mono", Menlo,
    Consolas, "Courier New", monospace;

  /* The spec's six steps, plus two. It was written for a marketing page; this
     site carries tables, an API reference and long-form documentation, and
     the spec's jump from 16 straight to 36 would leave every mid-level
     heading sitting at body size. */
  --t-xs: 12px;
  --t-sm: 14px;
  --t-base: 16px;
  --t-lg: 20px;
  --t-xl: 28px;
  --t-2xl: 36px;
  --t-3xl: 44px;
  --t-4xl: 72px;

  --measure: 68ch;
  --page: 1200px;
  --gutter: clamp(16px, 4vw, 48px);

  /* 8px base unit, per the spec */
  --s1: 8px;
  --s2: 16px;
  --s3: 24px;
  --s4: 32px;
  --s5: 40px;
  --s6: 56px;
  --s7: 80px;
  --s8: 96px;
  --s9: 120px;

  /* The spec has no single radius: corners are sized by what they are. */
  --radius: 3px; /* controls, nav, tags */
  --radius-card: 10px; /* cards */
  --radius-panel: 20px; /* the largest surfaces */

  /* Two curves, because there are two kinds of movement here and the two
     supplied specs each describe one of them.

     --ease is for state: a hover, an open, a focus. Its spec asks for "the
     feel of a CLI tool" — quick out, quick in, a switch flipping.

     --ease-out is for arrivals: the hero sequence and the diagram drawing
     itself. Nothing here is being switched; something is arriving, and an
     arrival that decelerates into place reads as weight rather than as a snap.
     That is the second spec's curve, kept for the one job it describes. */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.625, 0.05, 0, 1);
}

/* One palette, two class names. Pages still carry theme-instrument or
   theme-datasheet from when there were two registers; the light reading
   surface is the card below, not a theme. */
.theme-instrument,
.theme-datasheet {
  --bg: #101010; /* the canvas */
  --bg-2: #1d1a18; /* raised: nav well, button fills, inline fills */
  --bg-3: #1d1a18; /* the site had a third step; the spec has one lift */
  --panel: #1d1a18;
  --rule: #3d3a39; /* hairlines */
  --rule-soft: #262322; /* a derived step down from --rule, for row separators */
  --fg: #eeeeee; /* bone — primary text, and the light card's own surface */
  --fg-2: #b8b3b0; /* pale stone — secondary. 9.2:1 on canvas */
  --fg-3: #8a8380; /* warm granite — tertiary. 5.1:1 on canvas */
  /* The active marker in the docs rail: a brightness step, not a colour. It
     used to point at the accent, which made "you are here" the loudest thing
     in a navigation column. */
  --marker: var(--fg-2);
  --amber: #ee6018; /* the spec's signal orange — model / untrusted */
  --amber-soft: #ee601826;
  --steel: #a0ca92; /* the spec's metric green — kernel / verified */
  --steel-soft: #a0ca921f;
  /* Announcement severities. The only colours here the spec does not have;
     see the header comment for why they are allowed to exist. */
  --alert: #c23a34;
  --alert-fg: #fff4f3;
  --warn: #d9a326;
  --warn-fg: #1a1206;
  /* Ink is text that lands on the bone card. The spec's warm granite is only
     3.2:1 there, so the second and third steps are darkened until they clear
     4.5:1 on #eeeeee. */
  --ink: #101010;
  --ink-2: #4d4947; /* 7.8:1 on bone */
  --ink-3: #6b6663; /* 4.9:1 on bone */
  --bone: #eeeeee;
  --chalk: #fafafa;
  /* The mark is chrome, and chrome does not carry accent colour. Two neutral
     tones keep the mountain readable without competing with the envelope. */
  --mark-rock: #b8b3b0;
  --mark-snow: #eeeeee;
  --grid: #eeeeee0a;
  color-scheme: dark;
}

/* --------------------------------------------------------- the light card */
/* One object is allowed to be bright: a #eeeeee card on the #101010 canvas.
   Anything inside it re-resolves the same token names, so tables, code, tags,
   callouts and the pager all adapt without a single component needing to know
   it has landed on light ground. Set the tokens, not the components.

   The two lower ink steps are darker than the spec's warm granite, which only
   reaches 3.2:1 on bone. For a light reading surface, text has to clear 4.5. */
.card,
.docs-body,
.modal {
  --bg: #eeeeee;
  --bg-2: #fafafa;
  --bg-3: #e4e0dd;
  --panel: #eeeeee;
  --rule: #d6d1ce;
  --rule-soft: #e4e0dd;
  --fg: #101010;
  --fg-2: #4d4947;
  --fg-3: #625d5a;
  /* The two accents are tuned to read on #101010. On bone they fail badly —
     a syntax string in orange measured 3.2:1 and a keyword in green 1.8:1.
     Darkened here so the same semantic pair survives the light card. */
  --amber: #b8440c;
  --amber-soft: #b8440c1a;
  --steel: #3f6b34;
  --steel-soft: #3f6b341a;
  /* The primary button is the one element filled with the ground's opposite:
     a bright fill labelled in the ground's own colour. On this ground both
     halves flip, so the fill token has to flip with them. Left at the canvas
     chalk, the button kept a #fafafa fill while its label took the card's ink
     #eeeeee — the label landed on its own colour, at 1.05:1. */
  --chalk: #1d1a18;
  color: var(--fg);
}

/* ---------------------------------------------------------------- base */

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

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--t-base);
  line-height: 1.65;
  font-feature-settings: "kern" 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

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

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

:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
  border-radius: var(--radius);
}

/* Every state change moves the same way. One spec asks for "a single switch
   flipping, not a layered animation", and the only way to get that is for
   everything that changes on hover to share one transition — seven elements
   were cutting instantly while six eased, and that mixture is what reads as
   unfinished rather than fast.

   Listed rather than `*`: a transition on every element fires on layout-only
   changes too and spends frames on nothing. Components with their own rule
   keep it and win on specificity, which is correct — they declare exactly the
   properties they move. */
a,
button,
summary,
input,
select,
textarea,
tr,
.card,
.tag,
.pager__link,
.notice__toggle,
.notice__close,
.code__copy {
  transition: color 0.15s var(--ease), background-color 0.15s var(--ease),
    border-color 0.15s var(--ease), opacity 0.15s var(--ease);
}

h1,
h2,
h3,
h4 {
  margin: 0;
  /* Display type earns its presence from size and tight tracking, not weight.
     If a CJK heading reads too soft at 400, the answer is 500 — never 600. */
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.021em;
  text-wrap: balance;
}

p {
  margin: 0;
}

code,
kbd,
samp {
  font-family: var(--mono);
  font-size: 0.9em;
  font-feature-settings: "liga" 0;
}

code {
  color: var(--fg);
  background: var(--bg-3);
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  padding: 0.08em 0.36em;
}

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: var(--s4) 0;
}

/* ---------------------------------------------------------------- layout */

.wrap {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.prose {
  max-width: var(--measure);
}

.prose > * + * {
  margin-top: var(--s3);
}

.section {
  /* Separation is done by space, not by a rule. The spec builds depth from
     figure/ground and 96px gaps; a hairline between every band is the tell of
     a layout that does not trust its own spacing. */
  padding-block: var(--s8);
  border-top: 0;
}

.section--flush {
  border-top: 0;
}

.section--tight {
  padding-block: var(--s5);
}

.grid {
  display: grid;
  gap: var(--s4);
}

@media (min-width: 60rem) {
  .grid--2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .grid--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 66rem) {
  .grid--hero {
    grid-template-columns: minmax(0, 1.04fr) minmax(0, 1fr);
    gap: var(--s5);
    align-items: center;
  }
}

.stack > * + * {
  margin-top: var(--s2);
}

/* ---------------------------------------------------------------- chrome */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--chalk);
  color: var(--bg);
  padding: 0.6rem 1rem;
  font-family: var(--mono);
  font-size: var(--t-sm);
}
.skip:focus {
  left: 0;
}

/* ------------------------------------------------------------ announcements */
/* The only moving thing on the site that is not an entrance, so it is also the
   only thing with a pause control (WCAG 2.2.2) and the only thing that stops
   entirely under reduced-motion. Its inner edges are aligned to the page
   gutter rather than bled to the viewport, so the band lines up with the text
   under it instead of with the window. */
.notice {
  /* percentage, not vw: the band spans the body, which excludes the scrollbar,
     while 50vw does not — the two disagree by the scrollbar's width and the
     tag drifts off the gutter. Both the tag and the toggle resolve this
     against the band's own width. */
  --notice-pad: max(var(--gutter), calc(50% - var(--page) / 2 + var(--gutter)));
  position: relative;
  display: flex;
  align-items: stretch;
  min-height: 2.1rem;
  background: var(--alert);
  color: var(--alert-fg);
  border-bottom: 1px solid #00000033;
  font-family: var(--mono);
  font-size: var(--t-xs);
  line-height: 1.2;
  letter-spacing: 0.02em;
}
/* A caution tape rather than a paler red: at a glance the two severities must
   not be confusable, so the second one inverts to dark text. */
.notice--notice {
  background: var(--warn);
  color: var(--warn-fg);
}
/* pinned: the bar stays put while the page scrolls. At most one may do this —
   two would both claim top: 0 — and the generator honours only the first. */
.notice--pinned {
  position: sticky;
  top: 0;
  z-index: 45;
}
.notice__tag {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0;
  padding: 0 var(--s2) 0 var(--notice-pad);
  background: #0000002e;
  font-weight: 500;
  letter-spacing: 0.14em;
}
.notice__pip {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.notice__view {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
}
/* --- the crawling kind ---------------------------------------------------- */
.notice--scroll .notice__view {
  overflow: hidden;
  /* The crawl enters and leaves under a fade. That is the look, but it also
     covers the seam where the last duplicated copy meets the first. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 2.2rem, #000 calc(100% - 2.2rem), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 2.2rem, #000 calc(100% - 2.2rem), transparent);
}
.notice--scroll .notice__track {
  display: flex;
  width: max-content;
  will-change: transform;
  /* --notice-dur is measured in site.js from the real track width, so the
     notice travels at one speed on every viewport instead of one per screen. */
  animation: notice-crawl var(--notice-dur, 26s) linear infinite;
}
.notice--scroll .notice__item {
  white-space: nowrap;
}
/* A dot rather than a character: the repeats are a crawl artifact, and text
   separators get read aloud by screen readers as punctuation. */
.notice--scroll .notice__item::after {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  /* wide enough that the repeats read as separate notices — the message is
     short, so a tight gap shows three near-identical copies at once and the
     bar looks like a wall of text rather than a crawl */
  margin-inline: 3.6rem;
  vertical-align: middle;
  border-radius: 50%;
  background: #ffffff7a;
}
.notice--notice.notice--scroll .notice__item::after {
  background: #0000004d;
}
/* The separator gap is sized against the marquee window, which collapses to
   ~220px on a phone while the notice itself stays ~206px wide — so the wide
   desktop gap would leave the bar briefly all gap and no text. */
@media (max-width: 40rem) {
  .notice--scroll .notice__item::after {
    margin-inline: 1.7rem;
  }
}
@keyframes notice-crawl {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}
.notice[data-paused="true"] .notice__track {
  animation-play-state: paused;
}
/* --- the still kind ------------------------------------------------------- */
.notice--static .notice__view {
  padding: 0.5rem var(--notice-pad) 0.5rem var(--s3);
}
/* when a close button follows, it owns the right-hand gutter instead — two
   elements both padding to the page edge would push the text off it */
.notice--static:has(.notice__close) .notice__view {
  padding-right: var(--s3);
}
/* --- shared controls ------------------------------------------------------ */
.notice__toggle,
.notice__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  border: 0;
  border-left: 1px solid #ffffff33;
  background: #0000002e;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.notice--notice .notice__toggle,
.notice--notice .notice__close {
  border-left-color: #00000033;
}
.notice__toggle {
  gap: 0.5rem;
  padding: 0 var(--notice-pad) 0 var(--s3);
}
.notice__close {
  justify-content: center;
  padding: 0 var(--notice-pad) 0 var(--s3);
  font-size: var(--t-base);
  line-height: 1;
}
.notice__toggle:hover,
.notice__close:hover {
  background: #0000004a;
}
.notice__toggle:focus-visible,
.notice__close:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -3px;
}
.notice__ico {
  position: relative;
  flex: none;
  width: 8px;
  height: 9px;
}
.notice__ico::before,
.notice__ico::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: currentColor;
}
.notice__ico::before {
  left: 0;
}
.notice__ico::after {
  right: 0;
}
/* pressed = paused, so the glyph shows what a click gives you back */
.notice[data-paused="true"] .notice__ico {
  width: 0;
  height: 0;
  border-left: 8px solid currentColor;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
}
.notice[data-paused="true"] .notice__ico::before,
.notice[data-paused="true"] .notice__ico::after {
  display: none;
}
.notice__lbl--play {
  display: none;
}
.notice[data-paused="true"] .notice__lbl--pause {
  display: none;
}
.notice[data-paused="true"] .notice__lbl--play {
  display: inline;
}
.notice__item a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .notice--scroll .notice__view {
    -webkit-mask-image: none;
    mask-image: none;
    justify-content: center;
  }
  .notice--scroll .notice__track {
    animation: none;
  }
  /* site.js does not clone under reduced-motion; this drops the copy that the
     markup ships so the notice reads as one static line */
  .notice--scroll .notice__item[aria-hidden="true"],
  .notice__toggle {
    display: none;
  }
}

.banner {
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.01em;
  color: var(--fg-3);
  border-bottom: 1px solid var(--rule-soft);
  background: var(--bg);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  white-space: nowrap;
}
.banner::-webkit-scrollbar {
  display: none;
}
.banner__inner {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 2.1rem;
  padding-inline: var(--gutter);
  max-width: var(--page);
  margin-inline: auto;
}
.banner b {
  color: var(--fg-2);
  font-weight: 500;
}
.banner .sep {
  color: var(--rule);
}
.banner .mode {
  color: var(--fg-3);
}
.banner .cadence {
  margin-left: auto;
  display: flex;
  gap: var(--s2);
  align-items: center;
}
.banner .dot {
  position: relative;
  flex: none;
  width: 6px;
  height: 6px;
  background: var(--amber);
  border-radius: 50%;
}
/* The dot used to emit an infinite ping ring. It was the only thing on the
   site that moved without being asked to, and it moved forever: motion that
   never stops stops meaning anything. The spec's own Status Pulse says "no
   animation required". A filled circle is enough. */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--rule);
}
.nav__inner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 3.6rem;
}
.nav__brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--t-sm);
  letter-spacing: 0.02em;
  color: var(--fg);
}
.nav__brand svg {
  width: 26px;
  height: 26px;
  flex: none;
}
.nav__links {
  display: none;
  gap: var(--s3);
  margin-left: auto;
  align-items: center;
}
.nav__link {
  font-size: var(--t-sm);
  color: var(--fg-2);
  padding-block: 0.3rem;
  border-bottom: 1px solid transparent;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.nav__link:hover {
  color: var(--fg);
  border-bottom-color: var(--fg);
}
.nav__link[aria-current="page"] {
  color: var(--fg);
  border-bottom-color: var(--fg);
}

/* A nav item that opens a panel. The panel is a flat surface with a hairline
   border — no shadow, because the system has none; the lift is --bg-2 against
   the canvas. It only exists on the wide layout: below 62rem the same links
   live in the drawer, which is a plain list and does not need nesting. */
.nav__drop {
  position: relative;
}
/* The button stands in for a top-level link, so on a page under it the button
   takes the same current marker the link would have. */
.nav__drop-btn[aria-current] {
  color: var(--fg);
  border-bottom-color: var(--fg);
}
.nav__drop-btn {
  appearance: none;
  border: 0;
  background: transparent;
  /* Not `font: inherit` — that would reset the size .nav__link just set and
     leave this button a different size from the links beside it. */
  font-family: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
/* The caret is drawn, not a glyph: a rotated square with two borders. No font
   is asked to have a triangle in it. */
.nav__drop-btn::after {
  content: "";
  width: 0.3rem;
  height: 0.3rem;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-0.12rem) rotate(45deg);
  transition: transform 0.15s var(--ease);
}
.nav__drop[data-open="true"] > .nav__drop-btn::after {
  transform: translateY(0.1rem) rotate(225deg);
}
.nav__drop-menu {
  position: absolute;
  top: calc(100% + 0.55rem);
  left: 0;
  min-width: 13rem;
  display: none;
  flex-direction: column;
  padding: 0.3rem;
  background: var(--bg-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.nav__drop[data-open="true"] > .nav__drop-menu {
  display: flex;
}
.nav__drop-menu a {
  padding: 0.42rem 0.55rem;
  border-radius: var(--radius);
  font-size: var(--t-sm);
  color: var(--fg-2);
  white-space: nowrap;
}
.nav__drop-menu a:hover {
  color: var(--fg);
  background: var(--bg-3);
}
.nav__drop-menu a[aria-current="page"] {
  color: var(--fg);
}
.nav__drop-menu .nav__drop-k {
  padding: 0.4rem 0.55rem 0.25rem;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  color: var(--fg-3);
}
@media (min-width: 62rem) {
  .nav__links {
    display: flex;
  }
  .nav__toggle {
    display: none;
  }
}

.lang {
  display: flex;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  font-family: var(--sans);
  font-size: var(--t-xs);
  font-weight: 500;
}
.lang button {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--fg-3);
  padding: 0.28rem 0.5rem;
  cursor: pointer;
  font: inherit;
  letter-spacing: 0.06em;
}
.lang button[aria-pressed="true"] {
  background: var(--fg);
  color: var(--bg);
}
.lang button:hover:not([aria-pressed="true"]) {
  color: var(--fg);
}

.nav__toggle {
  margin-left: auto;
  appearance: none;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  color: var(--fg);
  font: inherit;
  font-size: var(--t-xs);
  font-weight: 500;
  padding: 0.32rem 0.55rem;
  cursor: pointer;
}
.nav__drawer {
  display: none;
  border-top: 1px solid var(--rule);
  padding-block: var(--s2);
}
.nav__drawer[data-open="true"] {
  display: block;
  animation: drawerin 0.18s var(--ease-out);
}
@keyframes drawerin {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}
.nav__drawer a {
  display: block;
  padding: 0.55rem 0;
  color: var(--fg-2);
  font-size: var(--t-sm);
  border-bottom: 1px solid var(--rule-soft);
}
.nav__drawer a:last-child {
  border-bottom: 0;
}
@media (min-width: 62rem) {
  .nav__drawer {
    display: none !important;
  }
}

/* ---------------------------------------------------------------- type */

.display {
  font-size: clamp(2.35rem, 4.4vw, 3.45rem);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: -0.03em;
}
.display em {
  font-style: normal;
}
.display .propose {
  color: var(--amber);
}
.display .decide {
  color: var(--steel);
}

.h2 {
  font-size: clamp(1.6rem, 3.4vw, var(--t-2xl));
  letter-spacing: -0.028em;
}
.h3 {
  font-size: var(--t-lg);
  letter-spacing: -0.018em;
}

.lede {
  font-size: clamp(1.05rem, 1.7vw, var(--t-lg));
  line-height: 1.55;
  color: var(--fg-2);
  max-width: 46ch;
}

.mono-label {
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  color: var(--fg-3);
}
.mono-label--amber {
  color: var(--amber);
}
.mono-label--steel {
  color: var(--steel);
}

.muted {
  color: var(--fg-2);
}
.dim {
  color: var(--fg-3);
}
.small {
  font-size: var(--t-sm);
}
.tnum {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- hero */

.hero {
  position: relative;
  /* More air above the fold than below it: the first screen now carries the
     ticker and the nav and nothing else, so the headline gets to sit alone. */
  padding-block: clamp(3.4rem, 9vw, 7.6rem) var(--s6);
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 44px 44px;
  mask-image: radial-gradient(120% 90% at 72% 40%, #000 20%, transparent 78%);
  pointer-events: none;
}
.hero__inner {
  position: relative;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* Buttons, footer headings and the language switch are product UI, not
     notation. Mono there is what makes a page read as a terminal; it stays on
     code, IDs, numbers and status tags, where it means something. */
  font-family: var(--sans);
  font-weight: 500;
  font-size: var(--t-sm);
  letter-spacing: 0.01em;
  padding: 0.62rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  color: var(--fg);
  background: var(--panel);
  /* colour, background and border move together, so a hover reads as one
     switch flipping rather than as a layered little animation */
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.btn:hover {
  /* A border that brightens, rather than a button that rises. The spec builds
     depth from contrast; a 1px lift is a shadow's ghost. */
  border-color: var(--fg-3);
  background: var(--bg-2);
}
.btn--primary {
  background: var(--chalk);
  color: var(--bg);
  border-color: var(--chalk);
}
.btn--primary:hover {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}
/* Tertiary entry points. These were three more buttons, which put five
   equal-weight actions in the first screen and left the hero with no focus. */
.hero__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.55rem;
  margin-top: var(--s3);
  font-size: var(--t-sm);
  color: var(--fg-3);
}
.hero__links a {
  color: var(--fg-2);
  padding-bottom: 1px;
  border-bottom: 1px solid transparent;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.hero__links a:hover {
  color: var(--fg);
  border-bottom-color: var(--fg);
}
.btn--ghost {
  background: transparent;
}
button.btn {
  appearance: none;
  cursor: pointer;
  font-weight: 400;
}

/* ------------------------------------------------------- envelope figure */

.figure {
  border: 1px solid var(--rule);
  background: var(--panel);
  border-radius: var(--radius-panel);
}
.figure img {
  display: block;
  width: 100%;
  height: auto;
}
.figure__cap {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  padding: 0.7rem 0.9rem;
  border-top: 1px solid var(--rule-soft);
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
}
.figure__cap--note {
  display: block;
  line-height: 1.55;
}
.figure__cap--note b {
  color: var(--fg-2);
  font-weight: 500;
}
.envelope {
  display: block;
  width: 100%;
  height: auto;
  padding: clamp(0.6rem, 2vw, 1.4rem);
}

@media (max-width: 42rem) {
  .figure {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }
  .figure .envelope,
  .figure .figure__cap {
    min-width: 29rem;
  }
}
.envelope text {
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.01em;
  fill: var(--fg-2);
}
.envelope .t-amber {
  fill: var(--amber);
}
.envelope .t-steel {
  fill: var(--steel);
}
.envelope .t-dim {
  fill: var(--fg-3);
  font-size: 13px;
}
.envelope .stroke {
  fill: none;
  stroke-width: 1;
}
.envelope .s-rule {
  stroke: var(--rule);
}
.envelope .s-amber {
  stroke: var(--amber);
}
.envelope .s-steel {
  stroke: var(--steel);
}
.envelope .s-dash {
  stroke-dasharray: 4 4;
}
.envelope .fill-amber {
  fill: var(--amber-soft);
}
.envelope .fill-steel {
  fill: var(--steel-soft);
}
.envelope .gate {
  fill: var(--bg);
  stroke: var(--steel);
}
.mk path {
  fill: var(--mark-rock);
}
.mk--amber path {
  fill: var(--amber);
}
.mk--dim path {
  fill: var(--fg-3);
}

/* Motion is scoped: only elements inside a [data-motion] container are
   hidden up front, so a stray .fade-step can never be stranded invisible. */
.js [data-motion] .draw {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
}
.js [data-motion] .fade-step {
  opacity: 0;
}
.js [data-motion].is-loaded .draw {
  animation: draw var(--draw-dur, 0.5s) var(--ease-out) forwards;
  /* Every delay on the page is scaled here rather than in two dozen inline
     attributes. The hero carries about that many staggered elements, and at
     their authored spacing the last one lands 1.1s in — which is a wait, not
     a moment. Halving keeps the cascade legible and ends it inside 600ms. */
  animation-delay: calc(var(--delay, 0s) * 0.5);
}
.js [data-motion].is-loaded .fade-step {
  animation: stepin 0.28s var(--ease-out) forwards;
  animation-delay: calc(var(--delay, 0s) * 0.5);
}
@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}
@keyframes stepin {
  from {
    opacity: 0;
    transform: translateY(7px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Safety net: if site.js never runs, the head timeout adds .no-motion and
   everything is simply visible. Content is never lost to a failed script. */
.no-motion .draw,
.no-motion .fade-step {
  opacity: 1 !important;
  stroke-dashoffset: 0 !important;
  transform: none !important;
  animation: none !important;
}
/* Below the two-column breakpoint the figure sits under the fold, so a
   per-element stagger would play unseen. Collapse it into one beat. */
@media (max-width: 65.99rem) {
  .js [data-motion].is-loaded .draw,
  .js [data-motion].is-loaded .fade-step {
    animation-delay: 0s !important;
  }
}
@media (prefers-reduced-motion: reduce) {
  .js [data-motion] .draw,
  .js [data-motion] .fade-step,
  .no-motion .draw,
  .no-motion .fade-step {
    opacity: 1 !important;
    stroke-dashoffset: 0 !important;
    transform: none !important;
    animation: none !important;
  }
  .banner .dot::after,
  .modal[open],
  .nav__drawer[data-open="true"] {
    animation: none !important;
  }
  /* the card lift is a transition, not an animation, so it needs its own guard */
  .card--link:hover {
    transform: none;
  }
}

/* ----------------------------------------------------------- built with */
/* The spec's logo strip, used for something it can be honest about: not
   "trusted by", just the tooling this was made with. Monochrome wordmarks in
   one row, no cards, no dividers, floating in negative space.

   The spec renders these as type rather than as images, and that is also the
   right call here for two reasons of its own: six brand marks would put six
   more colours into a system that allows two, and it would mean shipping
   third-party binaries in a repository whose whole claim is that it carries
   nothing it did not build. */
.built {
  padding-block: var(--s6);
}
/* The band drifts. It is the one long-duration motion on the site besides the
   notice — slow enough to read as a ribbon rather than as a message, and it
   stops the moment a pointer or a keyboard rests on it. */
.built__band {
  position: relative;
  overflow: hidden;
  margin-top: var(--s3);
  /* names enter and leave under a fade rather than being cut off, which also
     hides the seam where the last copy meets the first */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 3rem, #000 calc(100% - 3rem), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 3rem, #000 calc(100% - 3rem), transparent);
}
.built__track {
  display: flex;
  width: max-content;
  /* --built-dur is measured in site.js from the real track width, so the band
     drifts at one speed on every viewport instead of one per screen */
  animation: built-scroll var(--built-dur, 90s) linear infinite;
}
.built__band:hover .built__track,
.built__band:focus-within .built__track {
  animation-play-state: paused;
}
@keyframes built-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}
.built__list {
  display: flex;
  align-items: baseline;
  gap: var(--s6);
  margin: 0;
  /* the trailing space is part of the copy, so every repeat is the same width
     and the -50% translation lands exactly on the next one */
  padding: 0 var(--s6) 0 0;
  list-style: none;
}
@media (prefers-reduced-motion: reduce) {
  /* no crawl: the band becomes a plain row that wraps */
  .built__band {
    -webkit-mask-image: none;
    mask-image: none;
  }
  .built__track {
    animation: none;
  }
  .built__list {
    flex-wrap: wrap;
    padding-right: 0;
  }
  .built__track > .built__list[aria-hidden="true"] {
    display: none;
  }
}
.built__list li {
  display: flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--mono);
  font-size: var(--t-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
}
/* The mark says which kind of thing this is — a command prompt for a coding
   agent, a node graph for a model. It is not a brand logo, because four of
   these six have no logo available to use, and because a row where two entries
   carry a mark and four do not draws attention to the gap rather than the
   names. Drawn from currentColor, so it cannot be anything but monochrome. */
.built__g {
  flex: none;
  width: 13px;
  height: 13px;
  opacity: 0.75;
}
.built__note {
  margin-top: var(--s3);
  font-size: var(--t-xs);
  color: var(--fg-3);
  max-width: 62ch;
}

/* ---------------------------------------------------------------- stats */

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--rule-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  overflow: hidden;
}
@media (min-width: 52rem) {
  .stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.stat {
  background: var(--panel);
  padding: var(--s3);
}
.stat__v {
  font-family: var(--mono);
  font-size: clamp(1.5rem, 3vw, var(--t-xl));
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--fg);
  display: block;
  font-variant-numeric: tabular-nums;
}
.stat__v em {
  font-style: normal;
  color: var(--steel);
}
.stat__k {
  font-size: var(--t-sm);
  color: var(--fg-3);
  margin-top: 0.35rem;
  display: block;
  line-height: 1.4;
}

/* ---------------------------------------------------------------- table */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  background: var(--panel);
}
table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--t-sm);
}
caption {
  text-align: left;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--rule-soft);
}
th,
td {
  text-align: left;
  padding: 0.62rem 0.9rem;
  border-bottom: 1px solid var(--rule-soft);
  vertical-align: top;
}
thead th {
  font-family: var(--mono);
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--fg-3);
  letter-spacing: 0.03em;
  background: var(--bg-3);
  white-space: nowrap;
}
tbody tr:last-child td {
  border-bottom: 0;
}
tbody tr:hover {
  background: var(--bg-3);
}
td.num,
th.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
td.docs {
  font-family: var(--mono);
  font-size: 0.86em;
  overflow-wrap: anywhere;
}
td strong {
  font-weight: 500;
}

.tag {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.72rem;
  padding: 0.08rem 0.4rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  color: var(--fg-2);
  white-space: nowrap;
}
.tag--ok {
  color: var(--steel);
  border-color: var(--steel-soft);
  background: var(--steel-soft);
}
.tag--part {
  color: var(--amber);
  border-color: var(--amber-soft);
  background: var(--amber-soft);
}
.tag--no {
  color: var(--fg-3);
}

/* ------------------------------------------------------------- bug board */
/* The table is the site's ordinary one; what is new is the filter bar above it
   and the state marks in it.

   The state marks carry no accent colour, deliberately. A bug's state is a
   workflow, not a trust state, and the site's two accents are only allowed to
   name the latter. They are told apart by weight instead — the same device the
   docs rail uses for "you are here". */

.board__bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  align-items: center;
  margin-bottom: var(--s3);
}
.board__chip {
  font: inherit;
  font-size: var(--t-sm);
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--fg-2);
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease),
    background 0.15s var(--ease);
}
.board__chip[aria-pressed="true"] {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}
.board__chip .board__n {
  font-family: var(--mono);
  font-size: 0.72rem;
  opacity: 0.65;
  margin-left: 0.2rem;
}
.board__search {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-left: auto;
  font-size: var(--t-sm);
  color: var(--fg-3);
}
.board__search input {
  font: inherit;
  font-size: var(--t-sm);
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--fg);
  min-width: 11rem;
}
.board__id {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  white-space: nowrap;
}
.board__t {
  color: var(--fg);
}
.board__meta {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--t-xs);
  color: var(--fg-3);
}
.board__blank {
  color: var(--fg-3);
  font-size: var(--t-sm);
}
/* The evidence, folded away until asked for. A title alone is a claim with
   the working taken out, so the working is here — but open by default it
   would bury the table. */
.board__proof {
  margin-top: 0.4rem;
}
.board__proof summary {
  cursor: pointer;
  font-size: var(--t-xs);
  color: var(--fg-3);
}
.board__proof summary:hover {
  color: var(--fg);
}
.board__proof pre {
  margin-top: 0.4rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  background: var(--bg-2);
  font-size: var(--t-xs);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.board__st {
  color: var(--fg-3);
}
.board__st--open,
.board__st--confirmed {
  color: var(--fg-2);
}
.board__st--fixed {
  color: var(--fg);
}
/* Provenance, on the same terms: weight, plus a dashed edge for "this is a
   claim we have not checked". No accent — an unverified report is not a trust
   state either, and marking it green or orange would say it is. */
.board__org {
  color: var(--fg-3);
}
.board__org--official {
  color: var(--fg);
  border-color: var(--rule);
}
.board__org--unofficial {
  border-style: dashed;
}
/* The filter sets `hidden` on rows; a table has its own display rules, so say
   it here rather than hoping. */
.board [data-board-row][hidden],
.board [data-board-blank][hidden],
.board [data-board-table][hidden] {
  display: none;
}

/* ------------------------------------------------------- bug report form */
.bugform__field {
  margin-top: var(--s3);
}
.bugform__field label {
  display: block;
  font-size: var(--t-sm);
  color: var(--fg);
}
.bugform__hint {
  margin-top: 0.2rem;
  font-size: var(--t-xs);
  color: var(--fg-3);
}
.bugform textarea,
.bugform input[type="text"],
.bugform input[type="email"] {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  font-family: var(--mono);
  font-size: var(--t-sm);
  line-height: 1.55;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--fg);
  resize: vertical;
}
.bugform__row {
  display: grid;
  gap: var(--s3);
}
@media (min-width: 48rem) {
  .bugform__row {
    grid-template-columns: 1fr 1fr;
  }
}
/* Not an accent. An incomplete report is a state of the form, not a trust
   state, so it is said with weight like everything else on this board. */
.bugform__missing {
  margin-top: var(--s3);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--fg);
}
.bugform__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
}
.bugform[hidden] {
  display: none;
}

/* ---------------------------------------------------------------- cards */

.card {
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background: var(--panel);
  padding: var(--s3);
  transition: border-color 0.15s var(--ease);
}
.card:hover {
  border-color: var(--fg-3);
}
.card__k {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  letter-spacing: 0.04em;
}
.card--link {
  display: block;
}
/* A card that is a link should look like one under the cursor; the border
   alone reads as a static panel. */
.card--link:hover {
  border-color: var(--fg-3);
}
.card h3 {
  margin-bottom: 0.5rem;
}
.card p {
  color: var(--fg-2);
  font-size: var(--t-sm);
}

/* ---------------------------------------------------------------- boot */

.boot {
  counter-reset: stage;
  display: grid;
  gap: 0;
}
.stage {
  display: grid;
  gap: 0.4rem var(--s3);
  padding-block: var(--s3);
  border-top: 1px solid var(--rule-soft);
}
.stage:first-child {
  border-top: 0;
}
@media (min-width: 48rem) {
  .stage {
    grid-template-columns: 5.5rem 1fr;
    align-items: baseline;
  }
  .stage__n {
    grid-row: 1 / span 2;
  }
}
.stage__n {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  letter-spacing: 0.04em;
}
.stage__t {
  font-size: var(--t-lg);
  font-weight: 500;
  letter-spacing: -0.018em;
}
.stage__d {
  color: var(--fg-2);
  font-size: var(--t-sm);
  max-width: 62ch;
}

/* ---------------------------------------------------------------- code */

.code {
  position: relative;
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  background: var(--bg-2);
  overflow: hidden;
}
.code__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.8rem;
  border-bottom: 1px solid var(--rule-soft);
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
}
.code__bar .lang-tag {
  color: var(--fg-3);
}
.code__copy {
  margin-left: auto;
  appearance: none;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--fg-3);
  font: inherit;
  font-size: var(--t-xs);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius);
  cursor: pointer;
}
.code__copy:hover {
  color: var(--fg);
  border-color: var(--fg-3);
}
pre {
  margin: 0;
  padding: 1rem 1.1rem;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: var(--t-sm);
  line-height: 1.62;
  color: var(--fg);
  tab-size: 2;
}
pre .c {
  color: var(--fg-3);
}
pre .k {
  color: var(--steel);
}
pre .s {
  color: var(--amber);
}

/* ---------------------------------------------------------------- callout */

.note {
  border-left: 2px solid var(--rule);
  padding: 0.2rem 0 0.2rem var(--s3);
  color: var(--fg-2);
  font-size: var(--t-sm);
}
.note--amber {
  border-color: var(--amber);
}
.note--steel {
  border-color: var(--steel);
}
.note strong {
  color: var(--fg);
  font-weight: 500;
}

.rule-note {
  display: flex;
  gap: var(--s2);
  align-items: baseline;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  letter-spacing: 0.02em;
}

/* ---------------------------------------------------------------- lists */

.list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.list li {
  position: relative;
  padding: 0.55rem 0 0.55rem 1.5rem;
  border-top: 1px solid var(--rule-soft);
  font-size: var(--t-sm);
  color: var(--fg-2);
}
.list li:first-child {
  border-top: 0;
}
.list li::before {
  content: "";
  position: absolute;
  left: 0.1rem;
  top: 1.02rem;
  width: 6px;
  height: 6px;
  border: 1px solid var(--fg-3);
}
.list li b {
  color: var(--fg);
  font-weight: 500;
}
.list--check li::before {
  background: var(--fg-3);
}

/* ---------------------------------------------------------------- docs */

.docs-shell {
  display: grid;
  gap: var(--s5);
}
@media (min-width: 62rem) {
  .docs-shell {
    grid-template-columns: 15rem minmax(0, 1fr);
    align-items: start;
  }
}
.docs-side {
  font-size: var(--t-sm);
}
.docs-side__group + .docs-side__group {
  margin-top: 1.1rem;
}
.docs-side__h {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.05em;
  color: var(--fg-3);
  padding: 0 0 0.35rem 0;
  margin-bottom: 0.35rem;
  border-bottom: 1px solid var(--rule-soft);
}
.docs-side__group--active .docs-side__h {
  color: var(--marker);
  border-bottom-color: var(--marker);
}
.docs-side__links a {
  display: block;
  padding: 0.28rem 0 0.28rem 0.7rem;
  color: var(--fg-2);
  border-left: 2px solid transparent;
  margin-left: -0.72rem;
  line-height: 1.45;
}
.docs-side__links a:hover {
  color: var(--fg);
}
.docs-side__links a[aria-current="page"] {
  color: var(--fg);
  border-left-color: var(--marker);
  font-weight: 500;
}

@media (min-width: 62rem) {
  .docs-side {
    position: sticky;
    top: 5rem;
    /* the rail is taller than the viewport, so it scrolls on its own —
       a sticky element taller than the screen has an unreachable bottom */
    max-height: calc(100vh - 6.5rem);
    overflow-y: auto;
    overscroll-behavior-y: contain;
    /* a default OS scrollbar reads as an unstyled seam down the rail; thinning
       it and tinting the thumb to --rule makes it part of the page */
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
    padding-right: 0.5rem;
  }
}

/* On narrow screens a 48-item rail is useless, so it collapses to the
   page links of the current section only, as a swipeable row. */
@media (max-width: 61.99rem) {
  .docs-side {
    position: static;
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 0.7rem;
  }
  .docs-side::-webkit-scrollbar {
    display: none;
  }
  .docs-side__group {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin: 0;
  }
  .docs-side__group:not(.docs-side__group--active) {
    display: none;
  }
  .docs-side__links {
    display: flex;
    gap: 0.5rem;
  }
  .docs-side__h,
  .docs-side__links a {
    flex: none;
    margin: 0;
    padding: 0.3rem 0.72rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    white-space: nowrap;
  }
  .docs-side__group--active .docs-side__h {
    border-color: var(--marker);
  }
  .docs-side__links a[aria-current="page"] {
    border-color: var(--marker);
    background: var(--bg-3);
  }
}

/* ---------------------------------------------------------------- pager */

.pager {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  margin-top: var(--s5);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
}
.pager__link {
  display: block;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  transition: border-color 0.15s var(--ease);
}
.pager__link:hover {
  border-color: var(--fg-3);
}
.pager__link--next {
  text-align: right;
  grid-column: 2;
}
.pager__dir {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  margin-bottom: 0.2rem;
}
.pager__t {
  color: var(--fg);
  font-weight: 500;
}
.docs-body {
  min-width: 0;
  /* The sheet of paper on the war-room floor. The documentation used to be a
     whole light theme of its own; it is now an object sitting on the canvas,
     which is the spec's figure/ground move applied to something rather than
     used as decoration. */
  background: var(--bg);
  border-radius: var(--radius-panel);
  padding: clamp(24px, 4vw, 56px);
}
.docs-body .prose {
  max-width: 72ch;
}
.docs-body h2 {
  font-size: var(--t-xl);
  margin-top: var(--s5);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
}
.docs-body h2:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.docs-body h3 {
  font-size: var(--t-lg);
  margin-top: var(--s4);
}
.docs-body p,
.docs-body li {
  color: var(--fg-2);
}
.docs-body p strong,
.docs-body li strong {
  color: var(--fg);
}
.docs-body .table-wrap,
.docs-body .code,
.docs-body .note {
  margin-top: var(--s3);
}

.crumbs {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.crumbs a:hover {
  color: var(--fg);
}
.crumbs .sep {
  color: var(--rule);
}

/* ---------------------------------------------------------------- footer */

.foot {
  border-top: 1px solid var(--rule);
  padding-block: var(--s5) var(--s4);
  margin-top: var(--s6);
}
.foot__grid {
  display: grid;
  gap: var(--s4);
}
@media (min-width: 60rem) {
  .foot__grid {
    grid-template-columns: 1.4fr repeat(3, minmax(0, 1fr));
  }
}
.foot h4 {
  font-family: var(--sans);
  font-size: var(--t-sm);
  color: var(--fg-2);
  letter-spacing: 0.01em;
  margin-bottom: 0.7rem;
  font-weight: 500;
}
.foot a {
  display: block;
  padding: 0.24rem 0;
  color: var(--fg-2);
  font-size: var(--t-sm);
}
.foot a:hover {
  color: var(--fg);
}
.foot__end {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  justify-content: space-between;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
}

/* ---------------------------------------------------------------- misc */

.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 1.2rem;
  font-size: var(--t-sm);
}
.kv dt {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  padding-top: 0.15rem;
}
.kv dd {
  margin: 0;
  color: var(--fg-2);
}

.matrix {
  display: grid;
  gap: 2px;
  background: var(--rule-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  overflow: hidden;
}
.matrix__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s3);
  background: var(--panel);
  padding: 0.6rem var(--s3);
  font-size: var(--t-sm);
  align-items: center;
}
.matrix__row b {
  font-weight: 500;
}
.matrix__row span {
  color: var(--fg-3);
  font-family: var(--mono);
  font-size: var(--t-xs);
}

.bar {
  height: 6px;
  background: var(--rule-soft);
  border-radius: 3px;
  overflow: hidden;
  min-width: 90px;
}
.bar i {
  display: block;
  height: 100%;
  background: var(--steel);
}

.anchor {
  scroll-margin-top: 5.5rem;
}

/* ---------------------------------------------------------------- news */

.news {
  padding-block: var(--s6);
}
.news__title {
  margin-top: var(--s3);
  max-width: 22ch;
  /* CJK breaks anywhere by default, which split "开发访谈" across two lines.
     keep-all confines breaks to spaces so the title breaks where it reads. */
  word-break: keep-all;
  text-wrap: balance;
}
.news__body {
  margin-top: var(--s5);
  max-width: 62ch;
}
.news__body p {
  color: var(--fg-2);
  line-height: 1.78;
}
.news__body p + p {
  margin-top: var(--s3);
}
.news__body strong {
  color: var(--fg);
  font-weight: 500;
}
/* the opening line carries the greeting, so it reads as a lede */
.news__body > p:first-child {
  font-size: var(--t-lg);
  line-height: 1.6;
  color: var(--fg);
}
/* The docs scope sizes its own h2; a post needs the same or a heading drops
   back to body size. */
.news__body h2 {
  margin-top: var(--s5);
  font-size: var(--t-xl);
  letter-spacing: -0.022em;
  color: var(--fg);
}
.news__body h2:first-child {
  margin-top: 0;
}
.news__body code {
  font-size: 0.92em;
  padding: 0.1em 0.35em;
  border-radius: var(--radius);
  background: var(--bg-3);
}
/* interview layout: a question, then its answer */
.qa + .qa {
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
}
/* Qualified by .qa on purpose: .news__body p is (0,1,1) and the bare .qa__q was
   (0,1,0), so the body rule silently won and the questions came out in the
   answers' colour. Raising the selector keeps the question the brightest line
   without loosening the body rule. */
.qa .qa__q {
  font-size: var(--t-lg);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.015em;
  color: var(--fg);
  text-wrap: balance;
}
.qa__a {
  margin-top: var(--s2);
}
.qa__a p + p {
  margin-top: var(--s2);
}

.news__pager {
  max-width: 62ch;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.news__pager .pager__link--prev {
  grid-column: 1;
}

.news-index {
  padding-top: var(--s6);
  padding-bottom: var(--s6);
}
.news-list {
  list-style: none;
  margin: var(--s5) 0 0;
  padding: 0;
  max-width: 68ch;
}
.news-list li {
  border-top: 1px solid var(--rule);
  padding: var(--s3) 0;
}
.news-list li:first-child {
  border-top: 0;
}
.news-list time {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
  margin-bottom: 0.4rem;
}
.news-list a {
  font-size: var(--t-xl);
  font-weight: 500;
  letter-spacing: -0.02em;
  transition: color 0.15s var(--ease);
}
.news-list a:hover {
  color: var(--fg);
}
.news-list p {
  margin-top: 0.5rem;
  color: var(--fg-2);
  font-size: var(--t-sm);
  max-width: 60ch;
}

/* ---------------------------------------------------------------- modal */

.modal {
  margin: auto;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius-panel);
  background: var(--panel);
  color: var(--fg);
  width: min(31rem, calc(100vw - 2rem));
}
.modal::backdrop {
  background: #101010d9;
}
.modal[open] {
  animation: modalin 0.18s var(--ease-out);
}
@keyframes modalin {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .modal[open] {
    animation: none;
  }
}
.modal__inner {
  padding: var(--s4);
}
.modal__k {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  color: var(--marker);
}
.modal h2 {
  margin-top: 0.65rem;
  font-size: var(--t-xl);
  letter-spacing: -0.022em;
}
.modal p {
  margin-top: var(--s2);
  color: var(--fg-2);
  font-size: var(--t-sm);
}
.modal__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
}
.modal__rule {
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule-soft);
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--fg-3);
}
