/* GitHub Manager documentation site
   ---------------------------------------------------------------------------
   A rebuild in the register Vercel uses: pure black, hairline geometry, a
   display face set tight enough to read as one shape, and colour held back
   until it has something to say.

   The rules that hold it together:

   1. Black is #000 and white is #fff. No tinted greys. A neutral ramp reads as
      paper; a true ramp reads as screen, and every hairline lands on the same
      value on every device.
   2. Structure is drawn with 1px borders, not with fills or shadows. Borders
      scale to any density and never muddy the type sitting on them.
   3. The grid is the brand. A masked line grid behind the hero, and bento cells
      that share edges instead of floating apart on a gap.
   4. One accent, used for focus and links and nothing else. A colour that means
      something everywhere stops meaning anything.
   5. Display type is tight (-0.045em). Body type is not. The contrast between the
      two is what makes a headline feel set rather than typed.
   6. Motion explains. It shows where something came from and where it went, and
      it never hides content that is already on screen. Everything here is off
      under prefers-reduced-motion, and the reveal never gates readability - see
      the "reveal" section for why that matters.
*/

/* ------------------------------------------------------------------- tokens */
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;

  --bg: #000000;
  /* One step up, used for panels that sit on the page. Never two. */
  --bg-raised: #0a0a0a;
  --bg-sunken: #050505;

  --line: #1f1f1f;
  --line-strong: #2e2e2e;
  --line-faint: #131313;

  --text: #ededed;
  --text-dim: #a1a1a1;
  --text-faint: #737373;

  --accent: #3291fd;
  --accent-ink: #ffffff;
  --accent-soft: rgba(50, 145, 253, 0.14);

  --ok: #3ecf8e;
  --ok-soft: rgba(62, 207, 142, 0.12);
  --warn: #f5a524;
  --warn-soft: rgba(245, 165, 36, 0.12);
  --danger: #f2555a;
  --danger-soft: rgba(242, 85, 90, 0.12);

  --sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica,
    Arial, sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, "Liberation Mono", monospace;

  --header-h: 60px;
  --rail-w: 248px;
  --toc-w: 216px;
  --measure: 68ch;
  --edge: 24px;

  --r: 6px;
  --r-btn: 8px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* A second curve, slower and with no overshoot. Used for anything that moves
     a long way: reveals and panels. */
  --ease-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
  /* The sheen that sweeps a filled button on hover. Dark on a light surface,
     light on a dark one - set per theme below. */
  --sheen: rgba(0, 0, 0, 0.16);
}

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

  --bg: #ffffff;
  --bg-raised: #fafafa;
  --bg-sunken: #f2f2f2;

  --line: #e6e6e6;
  --line-strong: #d0d0d0;
  --line-faint: #f0f0f0;

  --text: #0a0a0a;
  --text-dim: #626262;
  --text-faint: #8f8f8f;

  --accent: #0068d6;
  --accent-ink: #ffffff;
  --accent-soft: rgba(0, 104, 214, 0.08);

  --ok: #0f7b4f;
  --ok-soft: rgba(15, 123, 79, 0.09);
  --warn: #8a5a00;
  --warn-soft: rgba(138, 90, 0, 0.09);
  --danger: #c11c1c;
  --danger-soft: rgba(193, 28, 28, 0.08);

  --sheen: rgba(255, 255, 255, 0.24);
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 24px);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
  font-feature-settings: "cv01", "ss01";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

svg {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

::selection { background: var(--accent-soft); color: var(--text); }

.skip {
  position: fixed; top: 8px; inset-inline-start: 8px;
  z-index: 200; transform: translateY(-200%);
  background: var(--text); color: var(--bg);
  padding: 10px 16px; border-radius: var(--r); font-weight: 600;
}
.skip:focus { transform: none; text-decoration: none; }

/* ------------------------------------------------------------------ motion */
/* The whole animation system hangs off this one class.

   Without JavaScript - or with it blocked, or slow - the class is never added,
   and nothing is hidden. That is the reason the reveal states are scoped to
   `html.motion` rather than written on the elements themselves: a reveal that
   can hide content is a bug waiting for a reader who never scrolls far enough. */
@media (prefers-reduced-motion: no-preference) {
  html.motion [data-reveal] {
    opacity: 0;
    transform: translateY(18px);
    transition:
      opacity .7s var(--ease-soft),
      transform .7s var(--ease-soft);
    transition-delay: var(--reveal-delay, 0ms);
  }
  html.motion [data-reveal="left"] { transform: translateX(-18px); }
  html.motion [data-reveal="scale"] { transform: translateY(26px) scale(.965); }
  html.motion [data-reveal="fade"] { transform: none; }

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

/* ------------------------------------------------------------------- header */
.site-header {
  position: sticky; top: 0; z-index: 60;
  height: var(--header-h);
  /* Translucent so content scrolling underneath reads as motion rather than as
     a second, sharper copy of itself. */
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--line);
}

.bar {
  height: 100%;
  display: flex; align-items: center; gap: 16px;
  max-width: 1440px; margin: 0 auto; padding: 0 var(--edge);
}

/* Reading progress. A two pixel accent line under the header that grows with the
   page. Scaled rather than resized: transform stays on the compositor, so it
   does not cost a layout pass per frame. */
.progress {
  position: absolute; inset-block-end: -1px; inset-inline-start: 0;
  height: 2px; width: 100%;
  background: var(--accent);
  transform: scaleX(0); transform-origin: 0 50%;
  opacity: .9;
}
@media (prefers-reduced-motion: reduce) {
  .progress { display: none; }
}

.brand {
  display: flex; align-items: center; gap: 9px;
  color: var(--text); font-weight: 560; font-size: 0.94rem;
  letter-spacing: -0.012em; white-space: nowrap;
}
.brand:hover { color: var(--text); text-decoration: none; }
.brand-mark { width: 20px; height: 20px; transition: transform .4s var(--ease); }
.brand:hover .brand-mark { transform: rotate(-12deg); }

.top-nav {
  display: flex; align-items: center; gap: 4px;
  margin-inline-start: 8px;
}
.top-nav a {
  position: relative;
  color: var(--text-dim); font-size: 0.875rem; font-weight: 460;
  padding: 5px 10px; border-radius: var(--r);
  transition: color .12s var(--ease);
}
.top-nav a:hover { color: var(--text); text-decoration: none; background: var(--bg-raised); }
.top-nav a.is-active { color: var(--text); }

.tools { display: flex; align-items: center; gap: 8px; margin-inline-start: auto; }

.icon-btn {
  display: grid; place-items: center;
  width: 34px; height: 34px; flex: none;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-raised); color: var(--text-dim); cursor: pointer;
  transition: color .12s var(--ease), border-color .12s var(--ease),
    background .12s var(--ease), transform .12s var(--ease);
}
.icon-btn:hover {
  color: var(--text); border-color: var(--line-strong);
  background: var(--bg-sunken); transform: translateY(-1px);
}
.icon-btn:active { transform: translateY(0) scale(.94); }
html[data-theme="light"] .i-moon,
html:not([data-theme="light"]) .i-sun { display: none; }
.nav-toggle { display: none; }

/* -------------------------------------------------------------------- search */
/* On a narrow screen the input collapses to its icon and expands over the bar
   when tapped. A visible field plus a theme button plus a download button does
   not fit in 380px, and shrinking all three produces something unusable. */
.search { position: relative; width: 264px; flex: none; }
.search-icon-button {
  display: none; place-items: center;
  width: 34px; height: 34px; flex: none;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-raised); color: var(--text-dim); cursor: pointer;
}
.search-icon-button:hover { color: var(--text); border-color: var(--line-strong); }

.search-icon {
  position: absolute; inset-inline-start: 11px; top: 50%;
  transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--text-faint);
  pointer-events: none;
}

.search input {
  width: 100%; height: 34px;
  padding: 0 14px 0 33px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg-raised); color: var(--text);
  font: inherit; font-size: 0.8125rem; outline: none;
  transition: border-color .12s var(--ease), background .12s var(--ease);
}
.search input::placeholder { color: var(--text-faint); }
.search input:hover { border-color: var(--line-strong); }
.search input:focus { border-color: var(--accent); background: var(--bg); }
.search input::-webkit-search-cancel-button { display: none; }

.results {
  position: absolute; top: calc(100% + 8px); inset-inline: 0;
  background: var(--bg-raised);
  border: 1px solid var(--line-strong); border-radius: var(--r);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  max-height: min(70vh, 520px); overflow-y: auto; overscroll-behavior: contain;
  padding: 4px; z-index: 70;
}

.result {
  display: block; padding: 9px 11px; border-radius: 5px;
  color: var(--text); line-height: 1.5;
  transition: background .1s var(--ease);
}
.result:hover, .result.is-active { background: var(--bg); text-decoration: none; }
.result-title { display: block; font-weight: 560; font-size: 0.8125rem; }
.result-page {
  display: inline-block; font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.04em; color: var(--text-faint);
  margin-inline-end: 7px; font-weight: 400;
}
.result-text {
  display: block; font-size: 0.75rem; color: var(--text-dim); margin-top: 3px;
}
.result-text mark, .result-title mark {
  background: var(--accent-soft); color: var(--accent);
  border-radius: 2px; padding: 0 1px;
}
.result-empty {
  padding: 20px; text-align: center;
  color: var(--text-faint); font-size: 0.8125rem;
}

/* ------------------------------------------------------------------ buttons */
/* Filled, not outlined.

   An outline reads as "this is available". A fill reads as "this is the thing
   to press", which is what a call to action actually is. On a black page a
   light fill is also the brightest object on the screen, so it is found without
   needing colour to point at it.

   Three weights, all filled:
     primary  - light surface, dark label. One per view.
     solid    - dark surface, light label. For a second action.
     ghost    - no fill until hover. For a tertiary action in a dense row. */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 40px; padding: 0 18px;
  border: 1px solid var(--line); border-radius: var(--r-btn);
  background: var(--bg-raised); color: var(--text);
  font: inherit; font-size: 0.875rem; font-weight: 520; letter-spacing: -0.008em;
  cursor: pointer; white-space: nowrap;
  transition: background .14s var(--ease), border-color .14s var(--ease),
    color .14s var(--ease), transform .14s var(--ease),
    box-shadow .14s var(--ease);
}
.btn:hover {
  text-decoration: none; background: var(--bg-sunken);
  border-color: var(--line-strong);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px -6px rgba(0, 0, 0, .7);
}
.btn:active { transform: translateY(0) scale(.98); box-shadow: none; }
html[data-theme="light"] .btn:hover { box-shadow: 0 6px 18px -8px rgba(0, 0, 0, .3); }

/* Each variant repeats the base class in its own selector on purpose.
   `.btn:hover` is two selectors deep, so it beats a bare `.btn-primary` - and
   the primary button inverted to dark on hover, which is exactly the moment it
   must not, since that is when someone is deciding whether to press it. */
.btn.btn-primary {
  background: var(--text); color: var(--bg); border-color: var(--text);
  font-weight: 560;
}
.btn.btn-primary:hover {
  background: var(--text); color: var(--bg);
  border-color: var(--text);
  box-shadow: 0 8px 26px -8px color-mix(in srgb, var(--text) 55%, transparent);
}

.btn.btn-solid {
  background: var(--bg-sunken); color: var(--text); border-color: var(--line-strong);
}
.btn.btn-solid:hover { background: var(--bg-raised); border-color: var(--text-faint); }

.btn.btn-ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
.btn.btn-ghost:hover { background: var(--bg-raised); border-color: var(--line); color: var(--text); }

/* The sheen. One pass of light across the surface on hover, which is what makes
   a filled button feel like an object rather than a rectangle of colour. */
.btn::after {
  content: "";
  position: absolute; inset-block: 0; inset-inline-start: -60%;
  width: 55%;
  background: linear-gradient(100deg, transparent, var(--sheen), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .btn::after { transform: translateX(0) skewX(-18deg); opacity: 0; }
  .btn:hover::after { animation: sheen .75s var(--ease) forwards; }
}
@keyframes sheen {
  from { transform: translateX(-40%) skewX(-18deg); opacity: 0; }
  35% { opacity: 1; }
  to { transform: translateX(320%) skewX(-18deg); opacity: 0; }
}

/* An arrow that steps forward on hover, so the button points at its destination.

   Drawn with two borders rather than written as an <svg>. The body sanitiser
   drops svg outright, and rightly so - it can carry script, event handlers and
   external references. A shape made of borders cannot, costs nothing, and
   scales with the button. */
.btn-arrow {
  display: inline-block; flex: none;
  width: 8px; height: 8px;
  margin-inline-start: 2px;
  border-top: 1.6px solid currentColor;
  border-right: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-1px, 1px);
  transition: transform .22s var(--ease);
}
.btn:hover .btn-arrow { transform: rotate(45deg) translate(2px, -2px); }

.cta-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 30px; }
.cta-row .btn { height: 46px; padding: 0 22px; font-size: 0.9375rem; border-radius: 10px; }

/* ------------------------------------------------------------ marketing shell
   The landing page drops the docs rails: one centred column, full bleed, with
   the grid doing the structural work.
   ------------------------------------------------------------------------- */
.marketing { max-width: 1080px; margin: 0 auto; padding: 0 var(--edge); }

/* A masked line grid. The mask is what makes it read as light falling off a
   surface rather than as graph paper laid over the text. */
.gridfield { position: relative; isolation: isolate; }
.gridfield::before {
  content: "";
  position: absolute; inset: -1px 0 auto; height: 760px; z-index: -1;
  background-image:
    linear-gradient(to right, var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: 68px 68px;
  mask-image: radial-gradient(115% 62% at 50% 0%, #000 8%, transparent 78%);
  pointer-events: none;
}
/* A single soft highlight at the top, the way a screen catches light. */
.gridfield::after {
  content: "";
  position: absolute; inset: -1px 0 auto; height: 620px; z-index: -1;
  background: radial-gradient(58% 44% at 50% 0%,
    color-mix(in srgb, var(--text) 7%, transparent), transparent 72%);
  pointer-events: none;
}

/* The grid drifts, slowly enough that you notice it only if you stop reading.
   24s across one cell: long enough to never resolve into a loop. */
@media (prefers-reduced-motion: no-preference) {
  .gridfield::before { animation: drift 24s linear infinite; }
  .gridfield::after { animation: breathe 9s var(--ease-soft) infinite alternate; }
}
@keyframes drift {
  from { background-position: 0 0, 0 0; }
  to { background-position: 68px 68px, 68px 68px; }
}
@keyframes breathe {
  from { opacity: .7; }
  to { opacity: 1; }
}

.hero { padding: 108px 0 0; text-align: center; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 0.75rem; font-weight: 400;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 12px; margin-bottom: 26px;
  background: var(--bg-raised);
}
.eyebrow b { color: var(--text); font-weight: 500; }
/* A slow pulse on the version number, so the eyebrow reads as live rather than
   as a label someone typed once. */
@media (prefers-reduced-motion: no-preference) {
  .eyebrow b { animation: pulse 3.4s var(--ease-soft) infinite; }
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .5; }
}

.hero h1 {
  font-size: clamp(2.6rem, 7.2vw, 4.75rem);
  line-height: 1.02; letter-spacing: -0.045em; font-weight: 560;
  margin: 0 auto 22px; max-width: 15ch;
  text-wrap: balance;
}

.lede {
  font-size: clamp(1.0625rem, 1.9vw, 1.25rem);
  line-height: 1.55; color: var(--text-dim);
  margin: 0 auto; max-width: 56ch; text-wrap: pretty;
}

.hero .cta-row { justify-content: center; margin-top: 34px; }

.hero-note {
  margin-top: 18px; font-family: var(--mono); font-size: 0.75rem;
  color: var(--text-faint); letter-spacing: 0.01em;
}

/* ------------------------------------------------------------- app mockup
   Drawn rather than screenshotted. A screenshot is a picture of one moment on
   one machine; these divs stay sharp on every display and cost no bytes.
   ------------------------------------------------------------------------- */
.shot {
  position: relative;
  margin: 68px 0 0;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--bg-raised);
  overflow: hidden;
  /* The glow sits behind the window, not around it. */
  box-shadow: 0 0 0 1px var(--bg), 0 40px 120px -40px rgba(0, 0, 0, 0.9);
  text-align: start;
}

.shot-bar {
  display: flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.shot-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--line-strong);
}
@media (prefers-reduced-motion: no-preference) {
  .shot-dot:nth-child(1) { animation: blink 4s var(--ease-soft) infinite; }
  .shot-dot:nth-child(2) { animation: blink 4s var(--ease-soft) .5s infinite; }
  .shot-dot:nth-child(3) { animation: blink 4s var(--ease-soft) 1s infinite; }
}
@keyframes blink {
  0%, 70%, 100% { background: var(--line-strong); }
  35% { background: var(--text-faint); }
}
.shot-title {
  margin-inline-start: 10px; font-family: var(--mono); font-size: 0.6875rem;
  color: var(--text-faint); letter-spacing: 0.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.shot-body { display: grid; grid-template-columns: 172px minmax(0, 1fr); min-height: 268px; }

.shot-nav {
  border-inline-end: 1px solid var(--line);
  padding: 14px 10px;
  display: flex; flex-direction: column; gap: 2px;
}
.shot-nav i {
  display: block; min-height: 24px; border-radius: 5px;
  font-style: normal; font-size: 0.75rem; color: var(--text-faint);
  padding: 4px 9px;
}
.shot-nav i.on {
  background: var(--bg-sunken); color: var(--text);
  box-shadow: inset 2px 0 0 var(--text);
}

.shot-main { padding: 18px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.shot-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.shot-h {
  font-size: 0.9375rem; font-weight: 560; letter-spacing: -0.015em; color: var(--text);
}
.shot-p { font-family: var(--mono); font-size: 0.6875rem; color: var(--text-faint); }

.shot-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.shot-chip {
  font-family: var(--mono); font-size: 0.6875rem; color: var(--text-dim);
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
.shot-chip.on { color: var(--bg); background: var(--text); border-color: var(--text); }
.shot:hover .shot-chip { color: var(--text-faint); }
.shot:hover .shot-chip.on { color: var(--bg); background: var(--text); }

/* The panes are equal height, which is what a real split view looks like, but
   they hug their contents rather than filling the window - an empty lower half
   reads as a screenshot that failed to load. */
.shot-split { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

@media (max-width: 720px) {
  .shot-body { grid-template-columns: 1fr; min-height: 0; }
  .shot-nav { display: none; }
  .shot-split { grid-template-columns: 1fr; }
}

/* ----------------------------------------------------------------- sections */
/* Padded on both sides on purpose. The rule belongs to the top edge of its own
   section, so without space below the previous one the divider lands directly on
   the block above it and reads as that block's bottom border. */
.section { padding: 96px 0; }
.section-rule { border-top: 1px solid var(--line); }

.section-head { max-width: 60ch; margin-bottom: 40px; }
.section-head h2 {
  font-size: clamp(1.75rem, 3.6vw, 2.5rem);
  line-height: 1.1; letter-spacing: -0.035em; font-weight: 560;
  margin: 0 0 14px;
}
.section-head p {
  font-size: 1.0625rem; color: var(--text-dim); margin: 0;
  line-height: 1.6; text-wrap: pretty;
}
.section-head .eyebrow { margin-bottom: 18px; }

/* Bento: cells share edges, so the whole block reads as one table rather than as
   a pile of separate boxes.

   The dividers are the cells' own borders, pulled one pixel under the frame by a
   negative margin. The obvious alternative - a one pixel gap over a coloured
   container - draws that colour into whatever the grid leaves empty, so a row
   that does not divide evenly ends up with a grey block in the corner. */
.bento {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(296px, 1fr));
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.bento > * {
  position: relative;
  padding: 26px 24px 28px;
  border-inline-end: 1px solid var(--line);
  border-block-end: 1px solid var(--line);
  margin-inline-end: -1px;
  margin-block-end: -1px;
  transition: background .2s var(--ease);
}
/* A wash that rises from the bottom edge on hover. Contained by the cell's own
   padding box, so it cannot bleed into its neighbour through the shared border. */
.bento > *::after {
  content: "";
  position: absolute; inset-inline: 0; inset-block-end: 0; height: 0;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--text) 5%, transparent), transparent);
  pointer-events: none;
  transition: height .3s var(--ease);
}
.bento > *:hover { background: var(--bg-raised); }
.bento > *:hover::after { height: 100%; }
.bento h3 {
  margin: 0 0 8px; font-size: 1rem; font-weight: 560;
  letter-spacing: -0.018em;
  transition: transform .2s var(--ease);
}
.bento p { margin: 0; color: var(--text-dim); font-size: 0.875rem; line-height: 1.6; }
.bento > *:hover h3 { transform: translateX(3px); }
.bento-num {
  font-family: var(--mono); font-size: 0.6875rem; color: var(--text-faint);
  letter-spacing: 0.06em; margin-bottom: 14px; display: block;
}
.bento svg { width: 18px; height: 18px; color: var(--text); margin-bottom: 14px; }

/* Numbered steps. The counter is CSS, so the markdown stays prose. */
.steps { counter-reset: step; display: grid; gap: 0; border-top: 1px solid var(--line); }
.step {
  counter-increment: step;
  display: grid; grid-template-columns: 64px minmax(0, 1fr);
  gap: 8px; align-items: start;
  padding: 26px 0; border-bottom: 1px solid var(--line);
  transition: background .2s var(--ease), padding-inline-start .2s var(--ease);
}
.step:hover { background: var(--bg-raised); padding-inline-start: 10px; }
.step::before {
  content: "0" counter(step);
  font-family: var(--mono); font-size: 0.8125rem; color: var(--text-faint);
  letter-spacing: 0.02em; padding-top: 3px;
  transition: color .2s var(--ease);
}
.step:hover::before { color: var(--text); }
.step h3 {
  margin: 0 0 6px; font-size: 1.0625rem; font-weight: 560; letter-spacing: -0.018em;
}
.step p { margin: 0; color: var(--text-dim); font-size: 0.9375rem; }

/* A fact strip. Four numbers, hairline separated. The count-up is driven by
   assets/motion.js reading data-count; the final value is written in the markup
   too, so a reader without JavaScript sees the right number immediately. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.fact {
  padding: 28px 24px; border-inline-end: 1px solid var(--line);
  transition: background .2s var(--ease);
}
.fact:last-child { border-inline-end: none; }
.fact:hover { background: var(--bg-raised); }
.fact b {
  display: block; font-size: 1.75rem; font-weight: 560;
  letter-spacing: -0.035em; line-height: 1.1; margin-bottom: 6px;
}
.fact span { font-size: 0.8125rem; color: var(--text-dim); }

/* A note that carries weight without a coloured background shouting it. */
.callout {
  border: 1px solid var(--line); border-radius: var(--r);
  border-inline-start: 2px solid var(--text);
  background: var(--bg-raised);
  padding: 18px 20px; margin: 24px 0;
}
.callout p { margin: 0; color: var(--text-dim); font-size: 0.9375rem; }
.callout p + p { margin-top: 10px; }

/* ------------------------------------------------------------------- footer */
.site-foot {
  border-top: 1px solid var(--line);
  margin-top: 96px;
  padding: 56px 0 48px;
}
.foot-grid {
  max-width: 1080px; margin: 0 auto; padding: 0 var(--edge);
  display: grid; grid-template-columns: minmax(0, 1.6fr) repeat(auto-fit, minmax(130px, 1fr));
  gap: 40px;
}
.foot-brand p {
  color: var(--text-faint); font-size: 0.8125rem;
  margin: 14px 0 0; max-width: 34ch; line-height: 1.6;
}
.foot-col h4 {
  font-family: var(--mono); font-size: 0.6875rem; font-weight: 400;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin: 0 0 14px;
}
.foot-col a {
  display: flex; align-items: center;
  min-height: 32px; color: var(--text-dim); font-size: 0.875rem;
  transition: color .12s var(--ease), transform .18s var(--ease);
}
.foot-col a:hover { color: var(--text); transform: translateX(3px); }
.foot-base {
  max-width: 1080px; margin: 44px auto 0; padding: 22px var(--edge) 0;
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  color: var(--text-faint); font-size: 0.75rem;
}

/* ------------------------------------------------------------- docs shell */
.shell {
  max-width: 1440px; margin: 0 auto; padding: 0 var(--edge) 96px;
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--toc-w);
  gap: 48px;
  align-items: start;
}

.rail {
  position: sticky; top: var(--header-h);
  max-height: calc(100vh - var(--header-h));
  overflow-y: auto; overscroll-behavior: contain;
  padding: 32px 0 28px;
}
.rail-nav { display: flex; flex-direction: column; gap: 1px; }
.rail-caption {
  font-family: var(--mono); font-size: 0.6875rem; font-weight: 400;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint);
  padding: 22px 10px 8px;
}
.rail-caption:first-child { padding-top: 0; }
.rail-link {
  position: relative;
  display: flex; align-items: center;
  padding: 6px 10px; border-radius: 5px;
  min-height: 36px;
  color: var(--text-dim); font-size: 0.875rem; font-weight: 460;
  transition: color .12s var(--ease), background .12s var(--ease),
    transform .18s var(--ease);
}
.rail-link:hover {
  background: var(--bg-raised); color: var(--text);
  text-decoration: none; transform: translateX(2px);
}
.rail-link.is-active { color: var(--text); background: var(--bg-raised); font-weight: 540; }
/* The active entry gets a bar rather than only a background tint, so it stays
   findable in a long list. */
.rail-link.is-active::before {
  content: "";
  position: absolute; inset-block: 7px; inset-inline-start: -1px;
  width: 2px; border-radius: 1px; background: var(--text);
}
@media (prefers-reduced-motion: no-preference) {
  html.motion .rail-link.is-active::before { animation: grow .45s var(--ease) both; }
}
@keyframes grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }

.rail-foot { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--line); }
.rail-foot .btn { width: 100%; }
.rail-foot p {
  margin: 10px 0 0; font-family: var(--mono); font-size: 0.6875rem;
  color: var(--text-faint); text-align: center;
}

.content { padding: 40px 0 0; min-width: 0; }
.content > * { min-width: 0; }

/* Prose is held to a measure. Code and tables opt out below. */
.content > h2, .content > h3, .content > h4,
.content > p, .content > ul, .content > ol,
.content > blockquote, .content > .callout,
.content > .steps, .content > .bento, .content > .facts,
.content > .scroll-x { max-width: var(--measure); }

.doc-head { max-width: var(--measure); margin-bottom: 40px; padding-bottom: 28px; border-bottom: 1px solid var(--line); }
.doc-head h1 {
  font-size: clamp(2rem, 4.4vw, 2.75rem);
  line-height: 1.08; letter-spacing: -0.038em; font-weight: 560;
  margin: 0 0 12px;
}
.doc-head p { font-size: 1.0625rem; color: var(--text-dim); margin: 0; max-width: 60ch; }
/* The first h2 sits under the head's own rule, so it does not need to push
   away from a border it would collide with. */
.doc-head + h2 { margin-top: 36px; }

h2 {
  font-size: 1.5rem; letter-spacing: -0.028em; font-weight: 560;
  margin: 56px 0 16px; padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
  line-height: 1.25;
  scroll-margin-top: calc(var(--header-h) + 24px);
}
h3 {
  font-size: 1.0625rem; letter-spacing: -0.018em; font-weight: 560;
  margin: 36px 0 10px; line-height: 1.4;
  scroll-margin-top: calc(var(--header-h) + 24px);
}
h4 {
  font-size: 0.9375rem; font-weight: 560;
  margin: 26px 0 8px;
  scroll-margin-top: calc(var(--header-h) + 24px);
}
p { margin: 16px 0; }
.content > h2:first-child, .doc-head + h2 { margin-top: 40px; }

ul, ol { padding-inline-start: 22px; margin: 16px 0; }
li { margin: 6px 0; padding-inline-start: 3px; }
li::marker { color: var(--text-faint); }
li > ul, li > ol { margin: 6px 0; }
strong { font-weight: 560; }

/* Prose links grow an underline from the left rather than snapping it on. */
.content p a, .content li a, td a, .step p a {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size .22s var(--ease), color .12s var(--ease);
}
.content p a:hover, .content li a:hover, td a:hover, .step p a:hover {
  background-size: 100% 1px;
  text-decoration: none;
}

code {
  font-family: var(--mono); font-size: 0.855em;
  background: var(--bg-raised);
  border: 1px solid var(--line-faint); border-radius: 4px;
  padding: 0.14em 0.38em;
  direction: ltr; unicode-bidi: embed;
  overflow-wrap: anywhere;
}

/* A path that wraps is a path you cannot read, so code and tables may exceed
   the prose measure. The scroll lives on a wrapper around the table rather than
   on the table: display:block on a table stops the rows from filling its width,
   and the header row's background ends mid-table. */
.scroll-x { max-width: min(100%, 900px); overflow-x: auto; }
pre { max-width: min(100%, 900px); }

pre {
  position: relative;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--r); padding: 15px 17px;
  overflow-x: auto; direction: ltr; text-align: left;
  margin: 18px 0; line-height: 1.6;
  font-size: 0.8125rem;
  transition: border-color .2s var(--ease);
}
pre:hover { border-color: var(--line-strong); }
pre code {
  background: none; border: none; padding: 0; font-size: inherit;
  overflow-wrap: normal;
}

/* Copy button, added by motion.js only when it can work. */
.copy {
  position: absolute; top: 8px; inset-inline-end: 8px;
  height: 28px; padding: 0 10px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--bg-sunken); color: var(--text-dim);
  font: inherit; font-size: 0.6875rem; font-family: var(--mono);
  cursor: pointer;
  opacity: 0; transform: translateY(-4px);
  transition: opacity .18s var(--ease), transform .18s var(--ease),
    color .12s var(--ease), border-color .12s var(--ease);
}
pre:hover .copy, .copy:focus-visible { opacity: 1; transform: none; }
.copy:hover { color: var(--text); border-color: var(--line-strong); }
.copy.is-done { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.copy svg { width: 13px; height: 13px; }

blockquote {
  margin: 20px 0; padding: 12px 18px;
  border-inline-start: 2px solid var(--line-strong);
  color: var(--text-dim);
  transition: border-inline-start-color .2s var(--ease);
}
blockquote:hover { border-inline-start-color: var(--text); }
blockquote p { margin: 6px 0; }
blockquote > :first-child { margin-top: 0; }
blockquote > :last-child { margin-bottom: 0; }

table {
  border-collapse: collapse; width: 100%; margin: 20px 0;
  font-size: 0.875rem;
}
th, td {
  border-bottom: 1px solid var(--line-faint);
  padding: 10px 14px; text-align: start; vertical-align: top;
  transition: background .14s var(--ease);
}
th {
  background: var(--bg-raised); font-weight: 560;
  font-family: var(--mono); font-size: 0.75rem;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-dim);
  white-space: nowrap;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--bg-raised); }
/* The frame belongs to the wrapper so it stays put while the table inside
   scrolls under it. */
.scroll-x table { border: 1px solid var(--line); border-radius: var(--r); }

hr { border: none; border-top: 1px solid var(--line); margin: 44px 0; }

details {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 16px; margin: 16px 0;
  background: var(--bg-raised);
  transition: border-color .18s var(--ease);
}
details[open] { border-color: var(--line-strong); }
summary { cursor: pointer; font-weight: 540; font-size: 0.9375rem; }
summary:hover { color: var(--accent); }
details > *:not(summary) { margin-top: 12px; }

/* A pill states something, so it carries a dot to be read at a glance. */
.pill {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 0.75rem;
  padding: 4px 11px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg-raised);
  color: var(--text-dim);
  margin-inline-end: 7px; margin-block: 4px;
  transition: border-color .16s var(--ease), color .16s var(--ease);
}
.pill::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex: none;
}
.pill-ok { color: var(--ok); }
.pill-warn { color: var(--warn); }
.pill:hover { border-color: var(--line-strong); color: var(--text); }

/* ---------------------------------------------------------------------- toc */
.toc {
  position: sticky; top: var(--header-h);
  max-height: calc(100vh - var(--header-h));
  overflow-y: auto; overscroll-behavior: contain;
  padding: 40px 0 28px;
}
.toc-title {
  font-family: var(--mono); font-size: 0.6875rem; font-weight: 400;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); margin: 0 0 12px;
}
.toc ul { list-style: none; padding: 0; margin: 0; }
.toc-item { margin: 0; }
/* Sections and their subsections are told apart by weight and colour, not by
   size: a list of forty entries set at two sizes stops being readable. */
.toc-level-2 > a { color: var(--text); font-weight: 500; margin-top: 10px; }
.toc-level-2:first-child > a { margin-top: 0; }
.toc-level-3 > a { color: var(--text-faint); }
.toc-item a {
  display: flex; align-items: center;
  padding: 4px 0 4px 12px; margin-inline-start: -1px;
  min-height: 32px; line-height: 1.45;
  font-size: 0.8125rem;
  border-inline-start: 1px solid var(--line);
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.toc-level-3 > a { padding-inline-start: 24px; font-size: 0.78125rem; }
.toc-item a:hover { color: var(--text); border-inline-start-color: var(--text); }

/* The heading the reader is currently at, tracked by motion.js. */
.toc-item a.is-current {
  color: var(--text); border-inline-start-color: var(--text);
  font-weight: 500;
}

/* Below the three-column threshold the right rail drops; the TOC moves into the
   flow as a disclosure so the headings are still reachable. */
.toc-inline { display: none; }
.toc-inline > summary { margin: 48px 0 0; }

.page-foot {
  margin-top: 72px; padding-top: 24px;
  border-top: 1px solid var(--line);
  color: var(--text-faint); font-size: 0.8125rem;
}
.page-foot p { margin: 4px 0; }
.foot-nav { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-bottom: 10px; }
.foot-nav a { color: var(--text-dim); font-weight: 460; }
.foot-nav a:hover { color: var(--text); }

@media (max-width: 1180px) {
  .shell { grid-template-columns: var(--rail-w) minmax(0, 1fr); gap: 40px; }
  .toc-rail { display: none; }
  .toc-inline { display: block; max-width: var(--measure); }
}

@media (max-width: 900px) {
  .top-nav { display: none; }
  .nav-toggle { display: grid; }
  .shell { grid-template-columns: minmax(0, 1fr); gap: 0; }

  /* A tablet is often touched with a stylus or a finger even with a mouse
     attached, so the bar grows here too. This sits above the narrow-screen
     rules below deliberately: they are the ones that reach 44px, and a later
     rule with the same specificity would quietly undo that. */
  .icon-btn, .search-icon-button, .bar .btn { height: 38px; }
  .icon-btn, .search-icon-button { width: 38px; }

  .rail {
    position: fixed; inset-block: var(--header-h) 0; inset-inline-start: 0;
    width: 288px; max-height: none;
    background: var(--bg); border-inline-end: 1px solid var(--line);
    padding: 24px 16px; z-index: 55;
    transform: translateX(-100%); transition: transform .22s var(--ease);
    box-shadow: 24px 0 60px rgba(0, 0, 0, 0.5);
  }
  .rail.is-open { transform: none; }

  .content > h2, .content > h3, .content > h4, .content > p,
  .content > ul, .content > ol, .content > blockquote,
  .content > .callout, .content > .steps,
  .content > .bento, .content > .facts,
  .content > .scroll-x, pre { max-width: 100%; }
  .bento > * { padding: 22px 18px; }
  .fact {
    border-inline-end: none;
    border-bottom: 1px solid var(--line);
    padding: 22px 18px;
  }
  .fact:last-child { border-bottom: none; }
}

@media (max-width: 640px) {
  :root { --edge: 16px; }
  .bar { gap: 8px; }
  .brand span { display: none; }

  /* The search box becomes an overlay so the bar keeps room to breathe. */
  .search-icon-button { display: grid; }
  .search {
    position: absolute; inset-inline: 0;
    display: none; width: auto; padding: 0 var(--edge);
    background: var(--bg); border-bottom: 1px solid var(--line);
  }
  .bar { position: relative; }
  body.searching .search {
    display: block;
    position: fixed; inset-block-start: 0; z-index: 70;
    padding-block: 14px;
  }
  body.searching .search-icon-button { visibility: hidden; }
  .results { inset-inline: var(--edge); }

  /* "Download" alone is enough at this width; the hero carries the rest. */
  .bar .btn { padding-inline: 12px; }

  /* 44px is the smallest target a finger reliably lands on. Everything in the
     bar grows to it rather than being shrunk to fit. */
  .bar .btn, .icon-btn, .search-icon-button { height: 44px; }
  .icon-btn, .search-icon-button { width: 44px; }
  .search input { height: 44px; }

  .hero { padding-top: 72px; }
  .cta-row .btn { width: 100%; height: 48px; }
  .step { grid-template-columns: 44px minmax(0, 1fr); }
  .step:hover { padding-inline-start: 0; }
  .section { padding: 64px 0; }
  .shot-title { display: none; }
  .bento { grid-template-columns: minmax(0, 1fr); }
  .foot-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 28px; }
  .foot-brand { grid-column: 1 / -1; }
  /* A hover wash on a touch screen sticks after the tap and never leaves. */
  .bento > *:hover::after { height: 0; }
  .bento > *:active::after { height: 100%; }
}

@media (min-width: 641px) {
  .search-icon-button { display: none !important; }
}

/* Coarse pointers get no hover states at all: they do not exist, and faking
   them with :active leaves things stuck on. */
@media (hover: none) {
  .bento > *::after { transition: none; }
  .bento > *:hover::after, .bento > *:hover h3 { transform: none; }
  .copy { opacity: 1; transform: none; }
}

/* A finger needs 44px. A mouse pointer does not, and making every link on a
   dense documentation rail 44px tall would push the list past the fold, so the
   sizing follows the input rather than the screen.

   This block comes last on purpose. The width-based rules above already set the
   bar's height, and a media query of the same specificity that follows them
   wins outright - which is how the header ended up 40px on a phone. */
@media (pointer: coarse) {
  .rail-link, .toc-item a, .foot-col a { min-height: 44px; }
  .top-nav a { padding-block: 12px; }
  /* Every button, not only the ones in the bar. The rail's download button is
     the most-pressed control on the whole site and it was 40px. */
  .btn { height: 44px; }
  .icon-btn, .search-icon-button { height: 44px; width: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Not an animation: the reader needs to be able to see it. */
  .progress { display: none; }
}

@media print {
  .site-header, .rail, .toc-rail, .toc-inline, .page-foot, .search, .copy {
    display: none;
  }
  .shell { display: block; padding: 0; }
  .content { padding: 0; }
  body { background: #fff; color: #000; }
  /* Reveal states are scoped to html.motion; print may run with the class set
     and paint hidden content as blank paper. */
  html.motion [data-reveal] { opacity: 1 !important; transform: none !important; }
}