/* Documentation theme.
 *
 * Every colour, family, size and radius here is a var() onto the token layer
 * generated into tokens.css. This file defines no palette of its own: that is
 * what keeps the documentation and the marketing page the same object rather
 * than two designs that agree for a while.
 *
 * All lengths are relative. The units gate scans this directory, so a pixel
 * value here fails the build; a hairline is 0.0625rem at a 16px root.
 */

/* ---- shell ------------------------------------------------------------- */

/* The documentation reads on a wider column than the landing page: a reference
   page carries a navigation rail beside its prose, which the landing never
   does. The width is declared once, here, rather than restated by the header,
   the shell and the footer - and because the mat derives its grid origin from
   these two values, declaring them is also what makes a printed line register
   with this surface's content edge rather than the other one's. */

:root {
  --container-width: 84rem;
  --container-pad: var(--space-4);
  /* Documentation is built from cut paper and ruled plates. Controls inherit
     that same corner language instead of importing the rounded treatment used
     by the application surface. */
  --interaction-radius: var(--radius-cut);
}

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

html {
  background: var(--color-mat);
}

/* Clears the sticky header plus a little breathing room, so a rail or
   headerlink jump - or keyboard focus - lands with the target visible instead
   of tucked behind the bar.

   The clearance sits on the article's own content rather than as
   scroll-padding on the page. Padding the page also applied to the header's
   own controls: stuck to the top, the menu button sat inside the region the
   padding marks as hidden, so every focus of it - the drawer returning focus
   on Escape - scrolled the page up to half a screen to "reveal" a control that
   was already in view, and the drawer then opened on a section the reader had
   not been reading. */

.vs-docs-main,
.vs-docs-main * {
  scroll-margin-top: calc(var(--chrome-header-height) + var(--space-4));
}

/* The mat is masked to a length rather than to a percentage. The shipped
   default fades over 12% of the element, which on a section band is a sensible
   fraction and on a reference page eleven thousand pixels tall is a fade
   thirteen hundred pixels deep - the grid would spend the whole first screen
   arriving. A fixed run means the ground is established once, near the top,
   and behaves the same on every page whatever its length. */

body.vs-mat {
  --grid-mask: linear-gradient(
    to bottom,
    transparent 0,
    #000 var(--space-16),
    #000 calc(100% - var(--space-16)),
    transparent 100%
  );
}

/* On a phone the fine grid stops being a ground and becomes wallpaper: an 8px
   cell under 16px text at 1.6 leading puts three rules through every line. The
   landing page's hero meets the same problem at the same width and answers it
   the same way - drop to the coarse scale, because what makes a grid quiet on
   a small screen is fewer lines, not fainter ones. Alpha was the wrong lever;
   a grid faint enough to be safe everywhere is invisible. */

@media (max-width: 47.9375rem) {
  body.vs-mat.vs-mat--fine::before {
    background-image:
      linear-gradient(to right, var(--grid-c-ink) var(--border-hairline), transparent var(--border-hairline)),
      linear-gradient(to bottom, var(--grid-c-ink) var(--border-hairline), transparent var(--border-hairline));
    background-size: var(--grid-coarse) var(--grid-coarse), var(--grid-coarse) var(--grid-coarse);
  }
}

body {
  background: var(--color-mat);
  font-size: var(--text-body);
  line-height: var(--text-body-line-height);
  font-weight: var(--text-body-weight);
  -webkit-text-size-adjust: 100%;
}

/* Text for assistive technology only. Not `display: none`, which removes it
   from the accessibility tree along with everything else - the point here is a
   name that is read but not seen. The one-pixel box is written in rem so the
   units gate stays green; at any root size it is far too small to paint. */

.vs-a11y-only {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  margin: -0.0625rem;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.vs-skip {
  position: absolute;
  left: -100vw;
  top: 0;
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  background: var(--color-paper-raised);
  color: var(--color-ink);
  border: var(--border-hairline) solid var(--color-rule-strong);
  border-radius: var(--radius-cut);
}

.vs-skip:focus-visible {
  left: var(--space-2);
  top: var(--space-2);
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* ---- header ------------------------------------------------------------ */

.vs-docs-head {
  position: sticky;
  top: 0;
  z-index: 5;
  background: color-mix(in srgb, var(--color-mat) 88%, transparent);
  backdrop-filter: blur(0.625rem) saturate(1.08);
  border-bottom: var(--border-hairline) solid var(--color-rule);
}

.vs-docs-head-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--chrome-header-height);
  max-width: var(--container-width);
  margin: 0 auto;
  padding: var(--space-2) var(--space-4);
}

/* The brand is set in the body face, as the landing header's brand name is:
   it names the site rather than titling the page, so it is not in the title
   voice. */
.vs-docs-brand {
  display: inline-flex;
  flex-wrap: wrap;
  min-width: 0;
  align-items: center;
  gap: var(--space-2);
  row-gap: 0;
  color: var(--color-ink);
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: var(--text-body-strong);
  font-weight: var(--text-body-strong-weight);
}

/* Neither carries `.vs-control`, so both fell to the user-agent ring - near
   black in both themes, on three of the first four Tab stops on every page. */

.vs-docs-brand:focus-visible,
.vs-docs-head-link:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.vs-docs-brand-sep,
.vs-docs-brand-section {
  color: var(--color-ink-muted);
  font-weight: var(--text-body-weight);
}

/* `.vs-docs-menu` is `display: contents` on desktop (below), so its empty
   navigation child becomes a third flex item here and `space-between` parks
   this between it and the brand instead of at the trailing edge. The auto
   margin claims the free space ahead of these controls; `space-between` no
   longer has any to distribute once it does. */

.vs-docs-head-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-inline-start: auto;
}

/* The search trigger sits between the brand and these actions in the
   markup already; flow layout gives it room on its own, so nothing here
   has to reserve any. */

@media (min-width: 60.0625rem) {
  .vs-docs-menu-navigation {
    display: none;
  }
}

/* The toggle is the shared glyph control; the only thing this surface says
   about it is that it exists at one width and not at the other. */
.vs-docs-menu-toggle {
  display: none;
}

.vs-docs-menu-icon {
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: square;
}

/* A rotated hamburger reads as a vertical bar, not as a close control - the
   three lines are still three lines, turned. The open glyph shows until the
   drawer is open, then the close mark takes over; only one is ever in the
   flow, so the button never grows to fit both. */

.vs-docs-menu-toggle[aria-expanded="true"] .vs-docs-menu-icon[data-vs-menu-icon="open"],
.vs-docs-menu-toggle:not([aria-expanded="true"]) .vs-docs-menu-icon[data-vs-menu-icon="close"] {
  display: none;
}

.vs-docs-menu {
  display: contents;
}

.vs-docs-menu[hidden] {
  display: contents;
}

.vs-docs-head-link {
  color: var(--color-ink-muted);
  text-decoration: none;
  font-size: var(--text-meta);
}

.vs-docs-head-link:hover {
  color: var(--color-ink);
}

.vs-docs-theme {
  --control-gap: var(--space-1);
  --control-pad-y: var(--space-1);
  --control-pad-x: var(--space-2);
  /* No `--control-font`: this sits beside "Site" and "Source" in the same
     row and read as a stray monospace label set against them, the one
     control in the header not carrying the header's own voice. */
  /* The label cycles through three words of different lengths, and a control
     that changes width when you press it moves the header around it. */
  min-width: 7.5rem;
}

/* The button holds all three glyphs and shows the one matching its state. The
   alternative - swapping innerHTML on click - would rebuild the icon on every
   press and leave the markup lying about which theme is active whenever the
   script has not run. */

.vs-docs-theme-icon {
  width: 0.875rem;
  height: 0.875rem;
  fill: currentColor;
  flex: none;
  display: none;
}

.vs-docs-theme[data-choice="light"] .vs-docs-theme-icon[data-vs-theme-icon="light"],
.vs-docs-theme[data-choice="dark"] .vs-docs-theme-icon[data-vs-theme-icon="dark"],
.vs-docs-theme[data-choice="system"] .vs-docs-theme-icon[data-vs-theme-icon="system"] {
  display: block;
}

/* Before the script runs there is no state to key off, and a button that is all
   text until hydration is the flicker this theme goes out of its way to avoid.
   The head script has already resolved the ground by this point; "system" is
   the stored default the control itself falls back to. */

.vs-docs-theme:not([data-choice]) .vs-docs-theme-icon[data-vs-theme-icon="system"] {
  display: block;
}

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

/* Three rails at full width: the site tree, the reading column, and the local
   contents. The third exists because the second cannot use the space - the
   measure holds prose to 623px, and with only the site tree beside it that
   column is 1008px, so there were some 385px of nothing beside every page
   while the local contents was stacked under a forty-row site tree. The third
   rail spends that slack instead of leaving it blank: the reading column comes
   down to 736px and keeps 113px of margin, which is the same prose in a page
   that answers "where am I in this document" without scrolling.

   The rails are declared once as tokens because three separate places need to
   agree about them: the grid, the collapse below, and anything that later has
   to know how wide the reading column actually is. */

.vs-docs-shell {
  --docs-rail: 17rem;
  --docs-rail-onpage: 15rem;
  display: grid;
  grid-template-columns: var(--docs-rail) minmax(0, 1fr);
  gap: var(--space-8);
  max-width: var(--container-width);
  margin: 0 auto;
  padding: var(--space-6) var(--container-pad);
  align-items: start;
}

/* Only a page that has a local contents reserves a column for one. Keyed off
   the same condition that renders it, so a page without one is not paying a
   rail's width for an empty rail. */

@media (min-width: 78rem) {
  .vs-docs-shell--onpage {
    grid-template-columns: var(--docs-rail) minmax(0, 1fr) var(--docs-rail-onpage);
  }
}

/* Between the three-column width and the single-column one there is no room
   for a third rail but there is still a left one, so the contents returns to
   the column it came from - underneath the tree, as a second row - rather than
   disappearing at the width where a page is still long enough to need it.

   The article spans both rows. With two auto rows the grid shares the article's
   height between them, which pushed the contents thousands of pixels down the
   page and let the sticky tree scroll over it on the way. The tree's row is
   sized to the tree instead and the contents' row takes the rest of the
   article's length. A sticky grid item is held by the whole grid, not by its
   row, so only one of the two can stick in the one column: the contents does,
   because it follows the page being read, and the tree scrolls away with the
   top of the page.

   The tree keeps a scroll box of its own, capped at half the window. Grown to
   its full height it was 1305px on a vaultspec-rag page, which put the
   contents below the first screen on every page at this width and the page's
   own row below the fold on twelve of them; boxed, the contents starts on the
   first screen and the tree can show its current row the way it does wider. */

@media (max-width: 77.9375rem) {
  .vs-docs-shell--onpage {
    grid-template-rows: auto 1fr;
  }

  .vs-docs-shell--onpage .vs-docs-nav {
    grid-column: 1;
    grid-row: 1;
    position: static;
    max-height: min(26rem, 50vh);
    overflow-y: auto;
  }

  .vs-docs-shell--onpage .vs-docs-main {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .vs-docs-shell--onpage .vs-docs-onpage-rail {
    grid-column: 1;
    grid-row: 2;
    align-self: start;
  }

  /* Its rule faces the article, which is now on its other side. */
  .vs-docs-shell--onpage .vs-docs-onpage-rail {
    border-inline-start: 0;
    border-inline-end: var(--border-hairline) solid var(--color-rule);
  }
}

@media (max-width: 60rem) {
  .vs-docs-shell,
  .vs-docs-shell--onpage {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
  }

  .vs-docs-shell--onpage .vs-docs-nav,
  .vs-docs-shell--onpage .vs-docs-main,
  .vs-docs-shell--onpage .vs-docs-onpage-rail {
    grid-column: 1;
    grid-row: auto;
  }

  .vs-docs-shell {
    padding-top: var(--space-4);
  }

  .vs-docs-menu-toggle {
    display: inline-flex;
    flex: none;
  }

  .vs-docs-menu {
    position: absolute;
    inset: 100% 0 auto;
    display: block;
    max-height: calc(100dvh - var(--chrome-header-height));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-3) max(var(--container-pad), env(safe-area-inset-right)) max(var(--space-4), env(safe-area-inset-bottom)) max(var(--container-pad), env(safe-area-inset-left));
    background: var(--color-paper-raised);
    border-bottom: var(--border-hairline) solid var(--color-rule-strong);
    box-shadow: var(--stock-contact-plate);
  }

  .vs-docs-menu[hidden] {
    display: none;
  }

  body.vs-docs-menu-open {
    overflow: hidden;
  }

  /* The drawer opens on navigation: the script puts the tree above these, so
     the rule that divides the two moves to the top of the settings block. */

  .vs-docs-head-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: var(--border-hairline) solid var(--color-rule);
  }

  .vs-docs-head-link,
  .vs-docs-theme {
    min-height: 2.75rem;
  }

  .vs-docs-head-link {
    display: inline-flex;
    align-items: center;
    padding: var(--space-2);
  }

  .vs-docs-theme {
    grid-column: 1 / -1;
    justify-content: flex-start;
    width: 100%;
  }

  .vs-docs-menu-navigation {
    display: grid;
    gap: var(--space-4);
  }

  /* Inside the drawer a rail has nothing to be separated from, so it drops the
     hairline and the inset it draws it against. */

  .vs-docs-menu-navigation .vs-docs-nav,
  .vs-docs-menu-navigation .vs-docs-onpage-rail {
    position: static;
    max-height: none;
    overflow: visible;
    padding: 0;
    border: 0;
  }
}

/* Both rails were sunken panels inside a strong border. At full width that put
   three boxed regions and a boxed code block on a page whose article is the
   thing being read, and the tree's own current row was a fourth box inside the
   first. Unboxed, a rail is a column of rows with one hairline between it and
   the article - the rule a printed page uses for the same job - and the only
   drawn state left in the tree is the row a reader is on.

   The rule is logical, not left and right: each rail draws it on the side that
   faces the article, so the two are mirror images of one another. */

.vs-docs-nav,
.vs-docs-onpage-rail {
  position: sticky;
  top: calc(var(--chrome-header-height) + var(--space-1));
  max-height: calc(100vh - var(--chrome-header-height) - var(--space-8) - var(--space-1));
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding: var(--space-2) var(--space-3);
}

.vs-docs-nav {
  border-inline-end: var(--border-hairline) solid var(--color-rule);
}

.vs-docs-onpage-rail {
  border-inline-start: var(--border-hairline) solid var(--color-rule);
}

@media (max-width: 60rem) {
  .vs-docs-nav {
    display: none;
  }

  .vs-docs-menu-navigation .vs-docs-nav {
    display: block;
  }
}

.vs-docs-main {
  min-width: 0;
}

.vs-docs-main:focus {
  outline: none;
}

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

.vs-docs-foot {
  border-top: var(--border-hairline) solid var(--color-rule);
  margin-top: var(--space-8);
  background: var(--color-paper);
}

.vs-docs-foot-inner {
  max-width: var(--container-width);
  margin: 0 auto;
  padding: var(--space-4);
}

/* The provenance line is the page's own statement about itself rather than
   part of its content, so it sits at meta scale and in the muted ink - present
   for anyone who looks, and not competing with the prose above it. */

.vs-docs-foot-source,
.vs-docs-foot-line {
  margin: 0 0 var(--space-2);
  color: var(--color-ink-muted);
  font-size: var(--text-meta);
  line-height: var(--text-meta-line-height);
}

.vs-docs-foot-line {
  margin-bottom: 0;
}

.vs-docs-foot a {
  color: var(--color-ink-muted);
  text-decoration-color: var(--color-rule-strong);
}

.vs-docs-foot a:hover {
  color: var(--color-ink);
}

.vs-docs-foot code {
  font-family: var(--font-mono);
  font-size: 0.95em;
}

/* ---- brand mark -------------------------------------------------------- */

/* The header mark is served from the marketing bundle this tree deploys
   inside, so it is one file on both surfaces. It is sized in ems rather than
   rems so it tracks the brand type beside it if that type is ever rescaled. */

.vs-docs-brand-mark {
  display: block;
  width: var(--space-6);
  height: var(--space-6);
  object-fit: contain;
  flex: none;
}
