/* Storyteller documentation theme.
   Overrides for MkDocs Material 9.7.x, shipped by the
   mkdocs-storyteller-docs-theme plugin and loaded after tokens.css on every
   Storyteller docs site. Values come from tokens.css; this file only places
   them.

   UNITS: Material sets html{font-size:125%}, so 1rem is 20px at the base
   breakpoint and scales to 22px at 100em and 24px at 125em. Every rem below is
   annotated in px at 20px/rem. Media queries use em, which stays 16px, and
   they reuse Material's exact breakpoint values so no width falls between two
   of its rules.

   LAYOUT MODEL: one shell (--st-shell) owns the header and main grid, so the
   logo, navigation rail, article and table of contents use the same vertical
   edges. The columns add up to the shell exactly:

     gutter | rail | article (measure + padding) | toc | gutter
      2rem  | 14rem|            36rem            |12rem|  2rem   = 66rem

   The rail/article divider is drawn on .md-content, which is in normal flow
   and stretches over the whole main area. The previous/next navigation is the
   final element inside .md-content__inner and follows the article width.

   ORDER: dark-scheme wordmark, base, header, search, shell, rail, contents,
   article, typography, code, blocks, AI control, home, article navigation,
   motion, responsive, user preferences. */

/* Dark scheme wordmark ---------------------------------------------------- */

/* The header is page-coloured, so the light scheme uses the dark-ink wordmark
   the plugin sets as theme.logo. Dark mode swaps in the white one, which ships
   next to this file. */
[data-md-color-scheme="slate"] .md-header__button.md-logo img,
[data-md-color-scheme="slate"]
  .md-nav--primary
  .md-nav__button.md-logo
  img {
  content: url("storyteller-logo.svg");
}

/* Base ------------------------------------------------------------------- */

html {
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  /* Anchored headings must clear the sticky header. */
  scroll-padding-top: calc(var(--st-header-h) + 1rem);
}

body {
  background: var(--st-bg);
  color: var(--st-ink);
  font-family: var(--md-text-font);
}

::selection {
  background: var(--st-selection);
}

:where(a, button, input, label, summary, [tabindex]):focus-visible {
  outline: 0.1rem solid var(--st-focus);
  outline-offset: 0.15rem;
}

/* Material still ships :not(.focus-visible){outline:none} rules from its
   polyfill era on these controls. They set outline-style, not just a colour,
   so they erase the native ring: restating outline-color is not enough. Each
   selector below matches Material's at the same depth and wins on order. */
.md-header__button:focus-visible,
.md-search__options > .md-icon:focus-visible,
.md-clipboard:focus-visible,
.md-nav__link:focus-visible,
.md-footer__link:focus-visible,
.md-typeset .md-code__button:focus-visible,
.md-typeset code:focus-visible,
.md-typeset summary:focus-visible {
  outline: 0.1rem solid var(--st-focus);
  outline-offset: 0.15rem;
}

:where(a, button, label, summary) {
  touch-action: manipulation;
}

.md-skip {
  background: var(--st-ink);
  border-radius: var(--st-radius-sm);
  color: var(--st-bg);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-medium);
}

/* Material scopes the anchor scroll offset to :target through this variable. */
.md-typeset :target {
  --md-scroll-margin: calc(var(--st-header-h) + 1rem);
}

/* Header ----------------------------------------------------------------- */

/* Material swaps in a drop shadow once the page is scrolled. One hairline
   already separates the bar from the page, and it never moves. */
.md-header,
.md-header--shadow {
  background: var(--st-bg);
  border-bottom: 1px solid var(--st-line);
  box-shadow: none;
  color: var(--st-ink);
}

.md-header__inner {
  gap: 0.5rem;
  margin-inline: auto;
  max-width: var(--st-shell);
  min-height: var(--st-header-h);
  padding-inline: var(--st-gutter);
}

.md-header__button {
  color: var(--st-ink-2);
  opacity: 1;
}

.md-header__button:hover {
  color: var(--st-ink);
  opacity: 1;
}

/* The logo sits on the shell's leading edge, the same edge the rail's
   navigation pills and the article's padding box start from. */
.md-header__button.md-logo {
  align-items: center;
  display: flex;
  margin-inline-start: 0;
  padding-inline: 0 0.5rem;
}

.md-header__button.md-logo :is(img, svg) {
  height: 1.3rem; /* 26px */
  width: auto;
}

/* The wordmark states the product, so Material's duplicate site-name topic and
   its scroll-driven swap to the page title are both dropped. The page title is
   already the first thing in the article, and a title that changes under the
   reader while scrolling is motion without a purpose. The element itself stays
   as the flexible gap between the logo and search. */
.md-header__title .md-header__topic {
  display: none;
}

/* Site label. The hub serves nine documentation sets behind one wordmark, so
   each header names its own set beside the mark. The text comes from the
   plugin's site_label option and defaults to the site name without its
   leading "Storyteller". It sits inside the logo link, so it shares the
   link's target and accessible name and adds no tab stop. */
.st-site-label {
  border-inline-start: 1px solid var(--st-line);
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-medium);
  line-height: 1.2;
  margin-inline-start: 0.6rem;
  padding-inline-start: 0.6rem;
  white-space: nowrap;
}

/* Search ----------------------------------------------------------------- */

/* Colours apply at every width. The field's own box is only shaped where the
   search is an inline header field; below 60em Material turns it into a
   full-screen sheet, which needs a flat full-width field instead. */

.md-search__input {
  color: var(--st-ink);
}

/* Keep search focus visible without the default blue input rectangle. The
   containing field already supplies the focus boundary. */
.md-search__input:focus-visible {
  outline: 0;
}

.md-search__form:focus-within {
  border-color: var(--st-line-strong);
}

.md-search__input::placeholder {
  color: var(--st-ink-3);
}

.md-search__input + .md-search__icon,
.md-search__options .md-icon {
  color: var(--st-ink-3);
}

.md-search-result__meta {
  background: var(--st-bg-subtle);
  color: var(--st-ink-2);
  font-size: var(--st-type-small);
}

/* Keep the final search result clear of the panel edge at every width. */
.md-search__scrollwrap {
  padding-block-end: 0.4rem; /* 8px */
}

.md-search-result__link:is(:focus, :hover) {
  background: var(--st-accent-wash);
}

.md-search-result__article {
  color: var(--st-ink);
}

.md-search-result__teaser {
  color: var(--st-ink-2);
}

/* Shell ------------------------------------------------------------------ */

.md-grid {
  max-width: var(--st-shell);
}

.md-main,
.md-container {
  background: var(--st-bg);
}

.md-main__inner {
  margin-top: 0;
  padding-inline: var(--st-gutter);
}

.md-content {
  min-width: 0;
}

/* Navigation rail -------------------------------------------------------- */

.md-nav {
  font-size: var(--st-type-ui);
  line-height: 1.45;
}

.md-nav__link {
  align-items: center;
  border-radius: var(--st-radius-sm);
  color: var(--st-ink-2);
  gap: 0.4rem;
  margin-block: 1px;
  min-height: 1.6rem; /* 32px */
  padding: 0.25rem 0.5rem;
}

/* Material turns a hovered link accent-blue through .md-nav__link[href]:hover,
   which outranks a single-class rule. Hover is ink on a wash here, so the
   attribute is matched too. */
.md-nav__link[href]:is(:focus, :focus-visible, :hover),
.md-nav__link[for]:is(:focus, :focus-visible, :hover) {
  background: var(--st-bg-hover);
  color: var(--st-ink);
}

/* Active page. Material scores .md-nav__item .md-nav__link--active above a
   single class, so the state is matched at the same depth. Weight, ink and a
   surface carry the state together; hue alone never does. */
.md-nav--primary .md-nav__item .md-nav__link--active,
.md-nav--primary .md-nav__item .md-nav__link--active:hover,
.md-nav--primary .md-nav__item .md-nav__link--active code {
  background: var(--st-bg-hover);
  color: var(--st-ink);
  font-weight: var(--st-weight-heading);
}

/* Group labels. Sentence case at reading weight: an all-caps tracked eyebrow
   adds noise and reads as decoration rather than structure. Material renders
   these as <label for>, and dims them through .md-nav__link[for], so the
   attribute has to be matched to reach them. */
.md-nav__item--section > .md-nav__link[for],
.md-nav__item--section > .md-nav__link {
  background: none;
  color: var(--st-ink);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-heading);
  letter-spacing: 0;
  margin-block: 0;
  min-height: 0;
  padding-block: 0;
  text-transform: none;
}

/* One hairline guide for genuinely nested groups; the groups themselves are
   separated by space instead. */
.md-nav__item--nested > .md-nav > .md-nav__list {
  border-inline-start: 1px solid var(--st-line);
  margin-inline-start: 0.5rem;
  padding-inline-start: 0.3rem;
}

.md-nav__item--section > .md-nav > .md-nav__list {
  border-inline-start: 0;
  margin-inline-start: 0;
  padding-inline-start: 0;
}

/* Table of contents ------------------------------------------------------ */

.md-nav--secondary .md-nav__title {
  background: var(--st-bg);
  box-shadow: none;
  color: var(--st-ink);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-heading);
  letter-spacing: 0;
  padding-inline: 0.5rem;
  text-transform: none;
}

.md-nav--secondary .md-nav__link {
  border-radius: 0;
  color: var(--st-ink-3);
  font-size: var(--st-type-small);
  min-height: 1.4rem; /* 28px */
  padding-block: 0.2rem;
}

.md-nav--secondary .md-nav__link:is(:focus, :hover) {
  background: none;
  color: var(--st-ink);
}

/* The reader's position in the page. A leading rule marks it in addition to
   ink and weight, so the state survives without colour. */
.md-nav--secondary .md-nav__item .md-nav__link--active {
  box-shadow: inset 0.1rem 0 0 var(--st-accent);
  color: var(--st-ink);
  font-weight: var(--st-weight-medium);
}

.md-nav--secondary .md-nav__list .md-nav__list .md-nav__link {
  padding-inline-start: 1.1rem;
}

/* Article ---------------------------------------------------------------- */

.md-content__inner {
  margin: 0;
  max-width: calc(var(--st-measure) + 2 * var(--st-article-pad));
  padding: 2.2rem var(--st-article-pad) 3.5rem;
}

/* Material inserts a 1.2rem side margin whenever a sidebar is present, through
   a [dir]-qualified five-class rule. Left in place it pushes the prose off the
   article column and away from the rail divider, so it is matched at the same
   depth and removed: the article's inset is its own padding. */
.md-main__inner > .md-sidebar:not([hidden]) ~ .md-content > .md-content__inner {
  margin-inline: 0;
}

.md-content__inner::before {
  display: none;
}

/* Material renders the path before the article, outside its padded box.
   Give it the same text edge and split the old 60px lower gap across the
   top (28px) and the title gap (24px). */
.md-path {
  font-size: var(--st-type-small);
  margin: 0;
  padding: 1.4rem var(--st-article-pad) 0; /* 28px above the path */
}

.md-path + .md-content__inner {
  padding-top: 1.2rem; /* 24px between the path and title */
}

.md-path__link {
  color: var(--st-ink-3);
}

.md-path__link:is(:focus, :hover) {
  color: var(--st-accent);
}

.md-path__item:not(:first-child)::before {
  color: var(--st-line-strong);
}

/* Typography ------------------------------------------------------------- */

.md-typeset {
  color: var(--st-ink);
  font-size: var(--st-type-body);
  line-height: 1.65;
  overflow-wrap: break-word;
}

.md-typeset :is(h1, h2, h3, h4) {
  color: var(--st-ink);
  font-weight: var(--st-weight-heading);
  scroll-margin-top: calc(var(--st-header-h) + 1rem);
  text-wrap: balance;
}

.md-typeset h1 {
  font-size: var(--st-type-h1);
  letter-spacing: -0.022em;
  line-height: 1.12;
  margin: 0 0 0.6rem;
}

/* Section turns are made by space, not by rules. */
.md-typeset h2 {
  font-size: var(--st-type-h2);
  letter-spacing: -0.014em;
  line-height: 1.25;
  margin: 2.4rem 0 0.6rem;
}

.md-typeset h3 {
  font-size: var(--st-type-h3);
  letter-spacing: -0.008em;
  line-height: 1.4;
  margin: 1.6rem 0 0.4rem;
}

.md-typeset h4 {
  font-size: var(--st-type-h4);
  line-height: 1.45;
  margin: 1.2rem 0 0.4rem;
}

/* A heading owns the gap to its own first paragraph. */
.md-typeset :is(h2, h3, h4) + p {
  margin-top: 0;
}

.md-typeset p {
  text-wrap: pretty;
}

/* One orientation passage under the page title. Scoped to the element that
   actually follows the title, so a mid-page paragraph is never promoted. */
.md-typeset > h1 + p,
.md-typeset > .st-ai-title-row + p {
  color: var(--st-ink-2);
  font-size: var(--st-type-lede);
  line-height: 1.6;
}

.md-typeset :is(ul, ol) {
  margin-block: 0.7em;
}

.md-typeset li {
  margin-block: 0.3em;
}

.md-typeset a {
  color: var(--st-accent);
  text-decoration-color: var(--st-link-underline);
  text-decoration-skip-ink: auto;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
}

.md-typeset a:is(:focus, :hover) {
  color: var(--st-accent-strong);
  text-decoration-color: currentcolor;
}

/* Heading anchors stay quiet until the heading is hovered or targeted. */
.md-typeset .headerlink {
  color: var(--st-line-strong);
}

.md-typeset :is(h1, h2, h3, h4):is(:hover, :target) .headerlink,
.md-typeset .headerlink:focus {
  color: var(--st-ink-3);
}

/* Code ------------------------------------------------------------------- */

/* Inline code is a chip one step off the page inside its own hairline. It
   takes the text colour: colour is for syntax, not for mentions. */
.md-typeset code {
  background: var(--st-code-inline-bg);
  border-radius: 0.2rem; /* 4px */
  box-shadow: inset 0 0 0 1px var(--st-code-inline-line);
  color: var(--st-ink);
  font-size: 0.9em;
  padding: 0.05rem 0.25rem; /* 1px 5px */
}

.md-typeset :is(h1, h2, h3, h4) code {
  font-size: 0.9em;
  font-weight: inherit;
}

.md-typeset a code {
  color: inherit;
}

/* The block is one framed object on the page colour: a hairline, one radius,
   and the filename header sharing the frame's top corners. Long lines wrap
   at the frame instead of scrolling sideways, so every line is readable on a
   phone without a second gesture. */
.md-typeset :is(.highlight, .highlighttable) {
  border-radius: var(--st-radius-code);
}

.md-typeset pre > code {
  background: var(--st-code-bg);
  border-radius: var(--st-radius-code);
  box-shadow: inset 0 0 0 1px var(--st-code-line);
  color: var(--st-code-ink);
  font-size: var(--st-type-code);
  line-height: 1.55; /* 20px */
  overflow-wrap: anywhere;
  padding: 0.8rem; /* 16px */
  white-space: pre-wrap;
}

.md-typeset .highlight :is(.hll, .highlighted) {
  background: var(--md-code-hl-color--light);
  box-shadow: inset 0.1rem 0 0 var(--md-code-hl-color);
}

/* The filename is a tab on the frame, set in the text face so it reads as
   a label rather than as code. */
.md-typeset .highlight span.filename {
  background: var(--st-bg-subtle);
  border: 1px solid var(--st-code-line);
  border-bottom: 0;
  border-radius: var(--st-radius-code) var(--st-radius-code) 0 0;
  color: var(--st-ink-2);
  font-family: var(--md-text-font);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-medium);
  padding: 0.45rem 0.8rem; /* 9px 16px */
}

.md-typeset .highlight span.filename + pre > code {
  border-radius: 0 0 var(--st-radius-code) var(--st-radius-code);
}

/* The copy control is a 32px bordered square 16px inside the frame's top
   corner. Material 9.7 renders it as .md-code__button inside a .md-code__nav
   strip (the strip is what is positioned and what fades); .md-clipboard is
   the older single-button form and is kept in step. The control appears when
   the block is hovered or the control is focused, and stays visible where
   nothing can hover. On success the glyph turns into a check for a moment
   (code-copy.js), which replaces Material's toast. */
.md-typeset .md-code__nav {
  background: none;
  border-radius: 0;
  gap: 0.2rem;
  inset-inline-end: 0.8rem; /* 16px */
  opacity: 0;
  padding: 0;
  top: 0.8rem;
}

.md-typeset pre:hover > .md-code__nav,
.md-typeset .md-code__nav:focus-within,
.md-typeset .md-code__nav:has(.st-copied) {
  background: none;
  opacity: 1;
}

.md-typeset .md-code__button,
.md-typeset .md-clipboard {
  background: var(--st-code-bg);
  border-radius: var(--st-radius-sm);
  box-shadow: 0 0 0 1px var(--st-code-line);
  color: var(--st-code-control-ink);
  display: grid;
  height: 1.6rem; /* 32px */
  place-items: center;
  width: 1.6rem;
}

.md-typeset .md-clipboard {
  inset-inline-end: 0.8rem;
  opacity: 0;
  top: 0.8rem;
}

.md-typeset pre:hover > .md-clipboard,
.md-typeset .md-clipboard:focus-visible,
.md-typeset .md-clipboard.st-copied {
  opacity: 1;
}

.md-typeset pre:hover > * > .md-code__button,
.md-typeset pre:hover > .md-clipboard {
  color: var(--st-code-control-ink);
}

.md-typeset .md-code__button:is(:hover, :focus-visible),
.md-typeset .md-clipboard:is(:hover, :focus-visible) {
  background: var(--st-bg-hover);
  color: var(--st-ink);
}

.md-typeset .md-code__button::after,
.md-typeset .md-clipboard::after {
  height: 0.8rem; /* 16px */
  margin: 0;
  width: 0.8rem;
}

.md-typeset .md-code__button[data-md-type="copy"]::after,
.md-typeset .md-clipboard::after {
  mask-image: var(--st-icon-copy);
  -webkit-mask-image: var(--st-icon-copy);
}

.md-typeset .md-code__button.st-copied::after,
.md-typeset .md-clipboard.st-copied::after {
  mask-image: var(--st-icon-check);
  -webkit-mask-image: var(--st-icon-check);
}

@media (hover: none) {
  .md-typeset .md-code__nav,
  .md-typeset .md-clipboard {
    opacity: 1;
  }
}

/* A block that carries a copy control keeps its first line clear of it,
   whether the control is showing or not: 16px + 32px control + 12px. Lines
   wrap, so text would otherwise run underneath the control. */
.md-typeset pre:has(.md-code__nav, .md-clipboard) > code {
  padding-inline-end: 3rem;
}

/* The check on the control says it; the toast would say it twice. */
.md-dialog {
  display: none;
}

/* Tabbed code blocks read as a quiet segmented control above the frame. */
.md-typeset .tabbed-labels {
  box-shadow: 0 -1px 0 var(--st-line) inset;
}

.md-typeset .tabbed-labels > label {
  color: var(--st-ink-3);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-medium);
  padding: 0.4em 0.9em 0.45em;
}

.md-typeset .tabbed-labels > label:hover {
  color: var(--st-ink);
}

.js .md-typeset .tabbed-labels::before {
  background: var(--st-ink);
}

/* Blocks ----------------------------------------------------------------- */

/* Callouts carry state, so they keep Material's type colour on the border,
   the icon and the label tint. What they lose is the elevation and the small
   type: a hairline box at reading size, with the inner radius stepped down
   from the outer one so the corners stay concentric. */
.md-typeset :is(.admonition, details) {
  border-radius: var(--st-radius);
  border-width: 1px;
  box-shadow: none;
  font-size: var(--st-type-ui);
  margin-block: 1.4em;
}

.md-typeset :is(.admonition, details):focus-within {
  box-shadow: none;
}

.md-typeset :is(.admonition, details) > :is(.admonition-title, summary) {
  border-radius: calc(var(--st-radius) - 1px) calc(var(--st-radius) - 1px) 0 0;
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-heading);
}

.md-typeset blockquote {
  border-inline-start: 2px solid var(--st-line-strong);
  color: var(--st-ink-2);
}

/* Tables are evidence: hairline structure, a header row quieter than the data
   it labels, and every body cell on the row's first text baseline. */
.md-typeset table:not([class]) {
  border: 1px solid var(--st-line);
  border-radius: var(--st-radius);
  box-shadow: none;
  font-size: var(--st-type-small);
}

.md-typeset table:not([class]) th {
  background: var(--st-bg-subtle);
  color: var(--st-ink);
  font-weight: var(--st-weight-heading);
}

.md-typeset table:not([class]) :is(th, td) {
  border-top: 1px solid var(--st-line);
  padding: 0.5rem 0.7rem;
  vertical-align: baseline;
}

.md-typeset table:not([class]) tr:first-child :is(th, td) {
  border-top: 0;
}

/* Material bleeds the table scroller past the article's padding box. The
   table belongs inside the measure, aligned with the prose. */
.md-typeset__scrollwrap {
  margin-inline: 0;
}

.md-typeset__table {
  padding-inline: 0;
}

.md-typeset hr {
  border-bottom-color: var(--st-line);
  margin-block: 2rem;
}

.md-typeset img {
  border-radius: var(--st-radius-sm);
  /* Pure black at low opacity: a tinted hairline picks up the surface under
     it and reads as dirt on the image edge. */
  outline: 1px solid var(--st-image-edge);
  outline-offset: -1px;
}

/* Copy-for-AI control ---------------------------------------------------- */

/* Shipped by the storyteller-ai-buttons plugin, whose stylesheet is injected
   after this file. These rules are matched inside .md-typeset so they win on
   specificity rather than on order. The colours come from the plugin's own
   custom-property API in the bridge above. */

.md-typeset .st-ai-title-row {
  align-items: flex-start;
  gap: 0.6rem 1.2rem;
  justify-content: space-between;
  margin: 0 0 0.6rem;
}

.md-typeset .st-ai-title-row > h1 {
  flex: 1 1 18rem;
  min-width: 0;
}

.md-typeset .st-ai-split {
  border: 1px solid var(--st-line);
  border-radius: var(--st-radius-sm);
  box-shadow: none;
  flex: 0 0 auto;
  /* Optical alignment with the cap height of the 36px page title. */
  margin-block-start: 0.3rem;
  overflow: visible;
}

.md-typeset .st-ai-split:hover {
  border-color: var(--st-line-strong);
}

.md-typeset .st-ai-button {
  border-radius: 0;
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-medium);
  padding: 0.3rem 0.55rem;
}

.md-typeset .st-ai-main {
  border-radius: calc(var(--st-radius-sm) - 1px) 0 0
    calc(var(--st-radius-sm) - 1px);
}

.md-typeset .st-ai-toggle {
  border-inline-start: 1px solid var(--st-line);
  border-radius: 0 calc(var(--st-radius-sm) - 1px)
    calc(var(--st-radius-sm) - 1px) 0;
  width: 1.4rem;
}

.md-typeset .st-ai-caret {
  border-top-width: 5px;
}

/* The plugin ships a translucent black ring, which disappears against a
   neutral control, so the project focus token is restated. */
.md-typeset .st-ai-button:focus-visible {
  outline: 0.1rem solid var(--st-focus);
  outline-offset: 0.15rem;
}

.md-typeset .st-ai-menu {
  border-color: var(--st-line);
  border-radius: var(--st-radius);
  box-shadow: var(--md-shadow-z2);
  padding: 0.2rem;
}

.md-typeset .st-ai-menu-item {
  border-radius: var(--st-radius-sm);
  color: var(--st-ink);
  font-size: var(--st-type-small);
  padding: 0.35rem 0.55rem;
}

.md-typeset .st-ai-menu-item:is(:focus-visible, :hover) {
  background: var(--st-bg-hover);
}

/* Confirmation for the copy action. Kept clear of the safe area so it never
   lands under a phone's home indicator. */
.st-ai-toast {
  border-radius: var(--st-radius-sm);
  bottom: max(0.8rem, env(safe-area-inset-bottom));
  box-shadow: var(--md-shadow-z2);
  font-size: var(--st-type-small);
  padding: 0.4rem 0.6rem;
  right: max(0.8rem, env(safe-area-inset-right));
}

/* Home ------------------------------------------------------------------- */

/* The landing page selects a route; it is not prose. Its own headings would
   only restate the page, so it drops the table of contents, and the route grid
   and task columns take the width that frees up. */
body:has(.st-home) .md-sidebar--secondary {
  display: none;
}

body:has(.st-home) .md-content__inner {
  max-width: 100%;
}

.st-home {
  margin-top: 2.2rem;
}

/* One owner for every gap between the landing page's blocks. */
.st-home > * + * {
  margin-top: 2.6rem;
}

.md-typeset .st-home h2 {
  font-size: var(--st-type-h3);
  margin: 0 0 0.9rem;
}

.md-typeset .st-icon {
  color: var(--st-ink-3);
  flex: 0 0 auto;
  height: 1rem;
  width: 1rem;
}

.md-typeset .st-home-note {
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
  margin: 0.9rem 0 0;
}

/* The three installation routes are true peers and the only framed objects on
   the page: choosing between them is the reader's first job. */
.st-routes {
  display: grid;
  gap: 0.6rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.md-typeset .st-route {
  background: var(--st-bg);
  border: 1px solid var(--st-line);
  border-radius: var(--st-radius);
  color: var(--st-ink);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.8rem;
  text-decoration: none;
}

.st-route__icon {
  height: 1.1rem;
  margin-bottom: 0.35rem;
  width: 1.1rem;
}

.md-typeset .st-route:is(:focus-visible, :hover) {
  background: var(--st-bg-subtle);
  border-color: var(--st-line-strong);
}

.st-route__name {
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-heading);
  line-height: 1.35;
  text-wrap: pretty;
}

.md-typeset .st-route:is(:focus-visible, :hover) .st-route__name {
  color: var(--st-accent);
}

.st-route__hint {
  color: var(--st-ink-2);
  font-size: var(--st-type-small);
  line-height: 1.5;
  text-wrap: pretty;
}

/* The step after installing outranks the grouped task lists below it, so it
   gets its own full-width row rather than an equal cell. */
.md-typeset .st-next {
  align-items: center;
  border-top: 1px solid var(--st-line);
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.6rem;
  /* The block gap above is owned by .st-home; only the end margin is reset. */
  margin-block-end: 0;
  margin-inline: 0;
  padding-top: 0.9rem;
}

.md-typeset .st-next a {
  font-size: var(--st-type-lede);
  font-weight: var(--st-weight-medium);
}

.st-next span {
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
}

/* Everything below is a plain task link plus one line of context. No cards:
   these links are not peers of the install routes. */
.st-groups {
  display: grid;
  gap: 2.2rem 2.4rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.st-groups .st-home-group + .st-home-group {
  margin-top: 0;
}

.md-typeset .st-tasks {
  list-style: none;
  margin: 0;
  padding: 0;
}

.md-typeset .st-tasks > li {
  column-gap: 0.55rem;
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr);
  margin: 0 0 0.75rem;
}

.md-typeset .st-task__icon {
  color: var(--st-ink-3);
  grid-column: 1;
  grid-row: 1 / span 2;
  height: 0.9rem;
  margin-top: 0.15rem;
  width: 0.9rem;
}

.md-typeset .st-tasks > li:last-child {
  margin-bottom: 0;
}

.md-typeset .st-tasks a {
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-medium);
  grid-column: 2;
}

.st-tasks span {
  color: var(--st-ink-2);
  display: block;
  font-size: var(--st-type-small);
  grid-column: 2;
  line-height: 1.5;
  text-wrap: pretty;
}

/* Reference closes the page and reads across its full width. */
.md-typeset .st-tasks--inline {
  display: grid;
  gap: 0.75rem 2.4rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.md-typeset .st-tasks--inline > li {
  margin: 0;
}

/* Content-format guides -------------------------------------------------- */

/* The Web home page shows the formats before the supporting configuration
   links. These selectors apply only to the new lower section. The older
   home classes stay available while source sites adopt the new markup. */
.st-discover__heading {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  justify-content: space-between;
  margin-bottom: 1.2rem;
}

.md-typeset :is(.st-discover, .st-guide-shelf) h2 {
  font-size: var(--st-type-h2);
  margin: 0;
}

.st-discover__heading > a {
  font-size: var(--st-type-ui);
}

.st-discover__formats {
  display: grid;
  gap: 1.6rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Static format examples link to their guides. Photography is source content
   and comes from the consuming docs page; the package owns the geometry. */
.md-typeset .st-format__preview {
  align-items: center;
  background: var(--st-bg-subtle);
  border-radius: var(--st-radius);
  display: flex;
  height: 10.8rem;
  justify-content: center;
  overflow: hidden;
  padding: 1rem;
  text-decoration: none;
  transition: background-color var(--st-duration) var(--st-ease);
}

.md-typeset .st-format__preview:is(:hover, :focus-visible) {
  background: var(--st-bg-hover);
}

.st-format__tiles {
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: 100%;
}

.md-typeset .st-format__tiles img {
  aspect-ratio: 1;
  border-radius: var(--st-radius-round);
  height: auto;
  object-fit: cover;
  padding: 0.2rem;
  border: 1px solid var(--st-accent);
  width: 100%;
}

.st-format__clips {
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  height: 100%;
  max-width: 100%;
  width: 14rem;
}

.md-typeset .st-format__clips img {
  aspect-ratio: 3 / 4;
  border-radius: var(--st-radius-sm);
  height: 100%;
  min-height: 0;
  object-fit: cover;
  width: 100%;
}

.md-typeset .st-format h3 {
  font-size: var(--st-type-h2);
  margin: 1rem 0 0.3rem;
}

.md-typeset .st-format p {
  color: var(--st-ink-2);
  font-size: var(--st-type-body);
  line-height: 1.5;
  margin: 0;
  max-width: 38ch;
  text-wrap: pretty;
}

.st-format__links {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.1rem;
  margin-top: 0.55rem;
}

.md-typeset .st-format__links a {
  align-items: center;
  display: inline-flex;
  font-size: var(--st-type-ui);
  min-height: 2.2rem;
  text-underline-offset: 0.2em;
}

.st-format__links a:first-child {
  font-weight: var(--st-weight-heading);
}

/* Native disclosures keep setup choices available while the reader focuses
   on adding content. The whole summary is a keyboard and touch target. */
.md-typeset .st-guide-shelf > h2 {
  margin-bottom: 1rem;
}

.md-typeset .st-guide-shelf > details {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--st-line);
  border-radius: 0;
  color: var(--st-ink);
  margin: 0;
  padding: 0;
}

.md-typeset .st-guide-shelf > details:last-child {
  border-bottom: 1px solid var(--st-line);
}

.md-typeset .st-guide-shelf > details > summary {
  align-items: baseline;
  background: transparent;
  border-radius: 0;
  color: var(--st-ink);
  display: flex;
  flex-wrap: wrap;
  font-size: var(--st-type-body);
  font-weight: var(--st-weight-medium);
  gap: 0.15rem 0.9rem;
  margin: 0;
  min-height: 3.3rem;
  padding: 1rem 1.5rem 1rem 0;
}

.md-typeset .st-guide-shelf summary::before {
  display: none;
}

.md-typeset .st-guide-shelf summary::after {
  background-color: var(--st-ink-2);
  inset-inline-end: 0.2rem;
  top: 1.1rem;
}

.md-typeset .st-guide-shelf summary:hover {
  color: var(--st-accent);
}

.st-guide-shelf summary > span {
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-regular);
  margin-inline-start: auto;
}

.md-typeset .st-guide-shelf ul {
  display: grid;
  gap: 0.3rem 1.5rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0 0 1rem;
}

.md-typeset .st-guide-shelf li {
  margin: 0;
}

.md-typeset .st-guide-shelf li a {
  display: inline-flex;
  align-items: center;
  font-size: var(--st-type-ui);
  min-height: 2.2rem;
}

.st-guide-shelf li > span {
  color: var(--st-ink-2);
  display: block;
  font-size: var(--st-type-small);
  margin-bottom: 0.4rem;
}

@media (max-width: 44.984375em) {
  .st-discover__formats {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
  }

  .md-typeset .st-guide-shelf ul {
    grid-template-columns: minmax(0, 1fr);
  }

  .st-guide-shelf summary > span {
    flex-basis: 100%;
    margin-inline-start: 0;
  }
}

/* Documentation hub ----------------------------------------------------- */

/* The root hub is a platform index. The two rails are removed so every route
   stays visible at the first decision point. */
body:has(.st-hub-home) .md-sidebar--secondary {
  display: none;
}

/* The hub uses the full desktop width. Its phone menu still uses the primary
   sidebar, so that sidebar must remain available below Material's breakpoint. */
@media screen and (min-width: 76.25em) {
  body:has(.st-hub-home) .md-sidebar--primary {
    display: none;
  }
}

body:has(.st-hub-home) .md-content {
  border-inline-start: 0;
}

body:has(.st-hub-home) .md-content__inner {
  max-width: 100%;
  padding: 2.8rem 0 3.4rem;
}

body:has(.st-hub-home) .md-article-nav {
  display: none;
}

.st-hub-home {
  width: 100%;
}

.md-typeset .st-hub-intro h1 {
  font-size: var(--st-type-hub-title);
  letter-spacing: -0.032em;
  margin: 0 0 0.75rem;
}

.md-typeset .st-hub-intro p {
  color: var(--st-ink-2);
  font-size: var(--st-type-lede);
  line-height: 1.55;
  margin: 0;
}

.st-hub-grid {
  display: grid;
  gap: 3.4rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 2.35rem;
}

.md-typeset .st-hub-section h2,
.md-typeset .st-hub-start h2 {
  font-size: var(--st-type-h3);
  letter-spacing: -0.012em;
  margin: 0 0 0.65rem;
}

.md-typeset .st-hub-route {
  align-items: center;
  color: var(--st-ink);
  display: grid;
  gap: 1rem;
  grid-template-columns: 2.4rem minmax(0, 1fr) 1rem;
  min-height: 4.7rem;
  padding: 0.7rem 0.9rem 0.7rem 0.7rem;
  position: relative;
  text-decoration: none;
  transition:
    background-color var(--st-duration) var(--st-ease),
    color var(--st-duration) var(--st-ease);
}

.md-typeset .st-hub-route:is(:focus-visible, :hover) {
  background: var(--st-bg-hover);
  color: var(--st-ink);
  outline: 0;
}

.md-typeset .st-hub-route:focus-visible {
  box-shadow: inset 0 0 0 2px var(--st-focus);
}

.md-typeset .st-hub-route__icon {
  color: var(--st-accent);
  height: 1.8rem;
  justify-self: center;
  width: 1.8rem;
}

.md-typeset .st-hub-route__icon--apple {
  color: var(--st-platform-apple);
}

.md-typeset .st-hub-route__icon--android {
  color: var(--st-platform-android);
}

.md-typeset .st-hub-route__icon--web {
  color: var(--st-platform-web);
}

.md-typeset .st-hub-route__icon--react {
  color: var(--st-platform-react);
}

.md-typeset .st-hub-route__icon--flutter {
  color: var(--st-platform-flutter);
}

.st-hub-route__copy {
  min-width: 0;
}

.st-hub-route__name,
.st-hub-route__description {
  display: block;
}

.st-hub-route__name {
  font-size: var(--st-type-body);
  font-weight: var(--st-weight-heading);
  line-height: 1.35;
  transition: color var(--st-duration) var(--st-ease);
}

.md-typeset .st-hub-route:is(:focus-visible, :hover) .st-hub-route__name {
  color: var(--st-accent);
}

.st-hub-route__description {
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
  line-height: 1.45;
  margin-top: 0.15rem;
  text-wrap: pretty;
}

.md-typeset .st-hub-route__arrow,
.md-typeset .st-hub-start__arrow {
  color: var(--st-ink-3);
  height: 1rem;
  transition: transform var(--st-duration) var(--st-ease);
  width: 1rem;
}

.md-typeset .st-hub-route:is(:focus-visible, :hover) .st-hub-route__arrow,
.md-typeset .st-hub-start__link:is(:focus-visible, :hover) .st-hub-start__arrow {
  color: var(--st-accent);
  transform: translateX(0.15rem);
}

.st-hub-start {
  margin-top: 2.8rem;
  width: calc((100% - 3.4rem) / 2);
}

.md-typeset .st-hub-start__link {
  align-items: center;
  display: inline-flex;
  font-size: var(--st-type-body);
  font-weight: var(--st-weight-medium);
  gap: 0.55rem;
  min-height: 2.2rem;
  text-decoration: none;
}

.st-hub-start p {
  color: var(--st-ink-2);
  font-size: var(--st-type-ui);
  line-height: 1.5;
  margin: 0.35rem 0 0;
}

@media screen and (max-width: 59.984375em) {
  body:has(.st-hub-home) .md-content__inner {
    max-width: 100%;
    padding-block: 2.4rem 3rem;
  }

  .st-hub-grid {
    gap: 2.6rem;
    grid-template-columns: 1fr;
    margin-top: 2rem;
  }

  .st-hub-start {
    margin-top: 2.3rem;
    width: 100%;
  }
}

@media screen and (max-width: 44.984375em) {
  body:has(.st-hub-home) .md-content__inner {
    padding-block: 1.8rem 2.6rem;
    padding-inline: var(--st-gutter);
  }

  .md-typeset .st-hub-route {
    gap: 0.75rem;
    grid-template-columns: 1.8rem minmax(0, 1fr) 0.9rem;
    min-height: 4.1rem;
    padding: 0.65rem 0.55rem 0.65rem 0.7rem;
  }

  .md-typeset .st-hub-route__icon {
    height: 1.35rem;
    width: 1.35rem;
  }

  .md-typeset .st-hub-route__arrow,
  .md-typeset .st-hub-start__arrow {
    height: 0.9rem;
    width: 0.9rem;
  }
}

/* Article navigation ----------------------------------------------------- */

/* The template emits previous and next links as the final article element.
   There is no outer footer wrapper, so this component cannot enter the
   navigation or table-of-contents columns. */
.md-article-nav:not([hidden]) {
  align-items: stretch;
  border-top: 1px solid var(--st-line);
  display: flex;
  gap: 0.8rem;
  margin-top: 3rem;
  overflow: visible;
  padding: 1.2rem 0 1.4rem;
  width: 100%;
}

/* No inline padding: the link's own content edge is the article's text edge,
   so the bar lines up with the prose it closes. */
.md-footer__link {
  align-items: center;
  color: var(--st-ink);
  /* Equal basis, so one long title cannot squeeze the other link away. */
  flex: 1 1 0;
  margin: 0;
  min-width: 0;
  opacity: 1;
  overflow: visible;
  padding: 0.5rem 0;
}

/* Material fades the whole link to opacity .7 on hover, which reads as a
   disabled state. Colour carries the hover instead. */
.md-footer__link:is(:focus, :hover) {
  opacity: 1;
}

.md-footer__link--next {
  justify-content: flex-end;
}

.md-footer__button {
  color: var(--st-ink-3);
  margin: 0;
  padding: 0;
}

.md-footer__link:is(:focus, :hover) .md-footer__button {
  color: var(--st-ink);
}

.md-footer__title {
  background: none;
  font-size: var(--st-type-ui);
  font-weight: var(--st-weight-medium);
  line-height: 1.4;
  margin: 0;
  max-width: none;
  min-width: 0;
  padding-inline: 0.6rem;
  white-space: normal;
}

.md-footer__direction {
  color: var(--st-ink-3);
  display: block;
  font-size: var(--st-type-micro);
  font-weight: var(--st-weight-regular);
  margin-bottom: 0.1rem;
  opacity: 1;
}

.md-footer__link:is(:focus, :hover) .md-footer__title {
  color: var(--st-accent);
}

/* Back to top.

   Material fixes this control at top:3.2rem with z-index 2, sized for its own
   2.4rem header. The header here is taller and paints at z-index 4, so the
   offset is derived from the header token instead of Material's constant.
   Elevation comes from a layered shadow rather than a border, because the
   control floats over the article. */
.md-top {
  background: var(--st-bg);
  border: 0;
  border-radius: var(--st-radius-pill);
  box-shadow: var(--md-shadow-z2);
  color: var(--st-ink-2);
  font-size: var(--st-type-small);
  font-weight: var(--st-weight-medium);
  padding: 0.35rem 0.7rem;
  top: calc(var(--st-header-h) + 0.6rem);
}

.md-top:is(:focus, :hover) {
  background: var(--st-bg);
  color: var(--st-accent);
}

/* Motion ----------------------------------------------------------------- */

/* Stillness by default. The only transitions are the state changes a reader
   needs confirmed, and each one also has a static cue. */
@media (prefers-reduced-motion: no-preference) {
  .md-nav__link,
  .md-search__form,
  .md-clipboard,
  .md-typeset .md-code__nav,
  .md-typeset .md-code__button,
  .md-typeset .st-route,
  .md-typeset .st-ai-split,
  .md-footer__link,
  .md-footer__title,
  .md-top {
    transition:
      background-color var(--st-duration) var(--st-ease),
      border-color var(--st-duration) var(--st-ease),
      color var(--st-duration) var(--st-ease),
      opacity var(--st-duration) var(--st-ease);
  }

  .md-nav--primary .md-nav__link .md-nav__icon::after {
    transition: transform var(--st-duration) var(--st-ease);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

/* Responsive ------------------------------------------------------------- */

/* Table of contents in flow, search as an inline header field. Material shows
   both from this width. */
@media screen and (min-width: 60em) {
  /* Scoped to the table of contents on purpose. Between 60em and 76.25em the
     primary sidebar is still the fixed drawer, and a sticky offset applied to
     it there would push the drawer's bottom past the viewport. */
  .md-sidebar--secondary {
    /* Symmetric block padding is required, not cosmetic: Material sizes
       .md-sidebar__scrollwrap from JavaScript as (available height - 2 x the
       scrollwrap's top offset). An asymmetric value makes the rail stop short
       of the footer by that difference at every scroll position. */
    padding-block: var(--st-rail-pad);
    padding-inline: 0;
    top: var(--st-header-h);
    width: var(--st-toc);
  }

  .md-sidebar--secondary .md-sidebar__scrollwrap {
    margin: 0;
    padding-inline: 1rem 0;
    scrollbar-width: thin;
  }

  /* Material reserves scrollbar space with padding-right:calc(100% - 11.5rem),
     which is tied to its own 12.1rem sidebar. Both rails here are different
     widths, so the reserve is dropped and the inline padding is set above. */
  .md-sidebar--secondary .md-sidebar__inner {
    padding-inline: 0;
  }

  /* Material indents the list by .6rem through a [dir]-qualified rule that
     outranks a two-class override. Indentation is owned by the scrollwrap and
     the link padding instead, so every label lands on one text edge. */
  .md-sidebar .md-nav--secondary .md-nav__list {
    padding-inline: 0;
  }

  /* The table of contents label stays with the list it introduces. */
  .md-nav--secondary .md-nav__title {
    padding-block: 0 0.4rem;
  }

  /* Material rests the field at 11.7rem. A wider resting field reads as a real
     search affordance and balances the logo across the shell. The expanded
     width stays Material's, and it matches the results panel. */
  .md-search__inner {
    width: 15rem; /* 300px */
  }

  .md-search__form {
    background: var(--st-bg-subtle);
    border: 1px solid var(--st-line);
    border-radius: var(--st-radius);
    box-shadow: none;
    color: var(--st-ink);
    height: 1.9rem; /* 38px */
  }

  .md-search__form:hover {
    background: var(--st-bg-hover);
    border-color: var(--st-line-strong);
  }

  [data-md-toggle="search"]:checked ~ .md-header .md-search__form {
    background: var(--st-bg);
    border-color: var(--st-line-strong);
    border-radius: var(--st-radius) var(--st-radius) 0 0;
    box-shadow: none;
  }

  /* The input keeps Material's 0.8rem (16px): a smaller size makes iOS Safari
     zoom the page on focus, and 60em is still a tablet width.

     Material anchors the results panel below its 1.8rem form. The form above
     is 1.9rem, offset by the 0.1rem inner padding. */
  .md-search__output {
    border-radius: 0 0 var(--st-radius) var(--st-radius);
    top: 2rem;
  }

  [data-md-toggle="search"]:checked ~ .md-header .md-search__output {
    box-shadow: var(--md-shadow-z3);
  }

  .md-search__scrollwrap {
    border: 1px solid var(--st-line-strong);
    border-top: 0;
    border-radius: 0 0 var(--st-radius) var(--st-radius);
  }

}

/* Desktop: the navigation rail joins the grid. */
@media screen and (min-width: 76.25em) {
  /* Now that the rail is in flow it takes the same sticky geometry as the
     table of contents. See the note there about symmetric block padding. */
  .md-sidebar--primary {
    padding-block: var(--st-rail-pad);
    padding-inline: 0;
    top: var(--st-header-h);
    width: var(--st-rail);
  }

  .md-sidebar--primary .md-sidebar__scrollwrap {
    margin: 0;
    padding-inline: 0 1rem;
    scrollbar-width: thin;
  }

  .md-sidebar--primary .md-sidebar__inner {
    padding-inline: 0;
  }

  /* Same pair as the table of contents: Material's .6rem list indent and the
     matching -.6rem pull on a section's child nav cancel each other out. Both
     are zeroed so the rail has a single text edge that this file owns. */
  .md-sidebar .md-nav--primary .md-nav__list {
    padding-inline: 0;
  }

  .md-sidebar .md-nav__item--section > .md-nav {
    margin-inline: 0;
  }

  /* The rail/article divider belongs to the in-flow content column, so it runs
     the full height of the main area and stops with the article. */
  .md-content {
    border-inline-start: 1px solid var(--st-line);
  }

  /* The header wordmark already names the product, so the rail starts with its
     first group label on the shared leading edge. Material makes this label a
     sticky drawer control, which has no job at this width. */
  .md-nav--primary > .md-nav__title {
    display: none;
  }

  .md-nav__item--section {
    margin-block: 1.5rem 0;
  }

  .md-nav__item--section:first-child {
    margin-block-start: 0;
  }

  .md-nav__item--section > .md-nav {
    margin-block: 0.4rem 0;
  }

  /* Material centres this control on the whole shell with margin-left:50%.
     Recentre it on the article column. The descendant selector matches the
     specificity of Material's [dir=ltr] rule. */
  .md-main .md-top {
    margin-inline-start: calc(50% + (var(--st-rail) - var(--st-toc)) / 2);
  }
}

/* Below the desktop breakpoint the rail is a drawer with its own chrome. */
@media screen and (max-width: 76.234375em) {
  /* Wherever the rail is a drawer, the control that opens it leads the header:
     it is the page's navigation, so it takes the leading edge and the logo
     follows. Material emits the logo first, so only the paint order is
     swapped. Tab order still reaches the logo first; the two are adjacent
     controls in one landmark, so the sequence carries the same meaning either
     way, and both keep their own visible focus ring. */
  .md-header__button[for="__drawer"] {
    order: -1;
  }

  /* Header icons are touch targets at these widths. Material ships a 1.2rem
     glyph inside .4rem of padding (40px) plus a .2rem margin; 2.2rem is the
     44px touch size, and dropping the margin lets the glyph itself, not its
     hit box, be aligned with the column below. Padding does the centring, not
     a display change: Material hides the search icon from 60em up through a
     rule of this same weight, and restating display here would bring it back
     across the overlap with that breakpoint. */
  .md-header__button.md-icon {
    border-radius: var(--st-radius-sm);
    height: 2.2rem; /* 44px */
    margin: 0;
    padding: 0.5rem;
    width: 2.2rem;
  }

  /* The glyph is centred in that box, so it sits 0.5rem inside its leading
     edge. Pulling the header's own inset back by the same 0.5rem lands the
     menu icon on the shell's leading edge, where the logo used to start. */
  .md-header__inner {
    padding-inline-start: calc(var(--st-gutter) - 0.5rem);
  }

  /* The logo follows the menu button now, so it is inset on both sides: the
     inset is what separates the two controls, and it turns a hit box the size
     of the artwork into a touch target the same height as its neighbour. */
  .md-header__button.md-logo {
    align-items: center;
    margin: 0;
    min-height: 2.2rem; /* 44px */
    padding-inline: 0.4rem;
  }

  /* The drawer is a sheet. It fills the viewport under the header, fades in
     and holds one flat tree, the way a phone menu reads. The header stays
     where it is, so the control that opened the sheet is the one that closes
     it. Material sizes the drawer as a 12.1rem panel sliding in from the
     leading edge through [dir=ltr] rules; the geometry is restated at that
     depth. */
  [dir] .md-sidebar--primary {
    background: var(--st-bg);
    height: calc(100% - var(--st-header-h));
    left: 0;
    opacity: 0;
    right: 0;
    top: var(--st-header-h);
    transform: none;
    transition: opacity var(--st-duration) var(--st-ease), visibility 0s var(--st-duration);
    visibility: hidden;
    width: 100%;
  }

  [dir] [data-md-toggle="drawer"]:checked ~ .md-container .md-sidebar--primary {
    box-shadow: none;
    opacity: 1;
    transform: none;
    transition: opacity var(--st-duration) var(--st-ease), visibility 0s;
    visibility: visible;
  }

  .md-sidebar--primary .md-sidebar__scrollwrap {
    overflow-y: auto;
  }

  /* Material keeps the tree at the old drawer width with a right padding of
     calc(100% - 12.1rem); the sheet's tree spans the sheet. */
  .md-sidebar--primary .md-sidebar__inner {
    padding: 0;
  }

  /* The sheet covers the page, so there is nothing for the dimmed overlay to
     dim, and the page behind it must not scroll. */
  .md-overlay {
    display: none;
  }

  html:has([data-md-toggle="drawer"]:checked) {
    overflow: hidden;
  }

  /* While the sheet is open the menu glyph becomes a close glyph: the inline
     SVG is hidden and two bars are drawn in its place. */
  .md-header__button[for="__drawer"] {
    position: relative;
  }

  [data-md-toggle="drawer"]:checked ~ .md-header .md-header__button[for="__drawer"] svg {
    visibility: hidden;
  }

  [data-md-toggle="drawer"]:checked ~ .md-header .md-header__button[for="__drawer"]::before,
  [data-md-toggle="drawer"]:checked ~ .md-header .md-header__button[for="__drawer"]::after {
    background: currentcolor;
    border-radius: 1px;
    content: "";
    height: 2px;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%) rotate(45deg);
    width: 1rem; /* 20px */
  }

  [data-md-toggle="drawer"]:checked ~ .md-header .md-header__button[for="__drawer"]::after {
    transform: translate(-50%, -50%) rotate(-45deg);
  }

  /* One flat tree. Material renders every nested group as its own absolute
     pane that slides in over the list with a back header; here a group
     expands in place under its label, and sections are always open. */
  .md-nav--primary,
  .md-nav--primary .md-nav {
    background: none;
    display: block;
    height: auto;
    position: static;
  }

  .md-nav--primary .md-nav__toggle ~ .md-nav {
    display: none;
    opacity: 1;
    transform: none;
    transition: none;
  }

  .md-nav--primary .md-nav__toggle:checked ~ .md-nav,
  .md-nav--primary .md-nav__item--section > .md-nav {
    display: block;
  }

  /* The header already carries the wordmark, and a flat tree needs no back
     headers. */
  .md-nav--primary .md-nav__title,
  .md-nav--primary .md-nav .md-nav__title {
    display: none;
  }

  .md-nav--primary .md-nav__title ~ .md-nav__list {
    box-shadow: none;
    overflow: visible;
    scroll-snap-type: none;
  }

  .md-nav--primary .md-nav__item {
    border-top: 0;
  }

  .md-nav--primary .md-nav__list {
    padding: 0.6rem; /* 12px */
  }

  .md-nav--primary .md-nav .md-nav__list {
    border: 0;
    margin: 0;
    padding: 0 0 0 0.6rem;
  }

  /* Rows are 40px tall and set at 18px: a phone menu is read at arm's length
     and tapped with a thumb. */
  .md-nav--primary .md-nav__item,
  .md-nav--primary .md-nav__link {
    font-size: 0.9rem; /* 18px */
    line-height: 1.55;
  }

  .md-nav--primary .md-nav__link {
    border-radius: var(--st-radius-sm);
    color: var(--st-ink);
    justify-content: space-between;
    margin: 0;
    min-height: 2rem; /* 40px */
    padding: 0.3rem 0.6rem; /* 6px 12px */
  }

  .md-nav--primary .md-nav__link[href]:is(:hover, :focus-visible),
  .md-nav--primary .md-nav__link[for]:is(:hover, :focus-visible) {
    background: var(--st-bg-hover);
    color: var(--st-ink);
  }

  .md-nav--primary .md-nav__item--active > .md-nav__link,
  .md-nav--primary .md-nav__item .md-nav__link--active {
    background: var(--st-bg-hover);
    color: var(--st-ink);
    font-weight: var(--st-weight-heading);
  }

  /* Nested groups keep a chevron that turns when the group is open. */
  .md-nav--primary .md-nav__link .md-nav__icon {
    color: var(--st-ink-3);
    height: 1rem;
    margin: 0;
    width: 1rem;
  }

  .md-nav--primary .md-nav__toggle:checked ~ .md-nav__link .md-nav__icon::after {
    transform: rotate(90deg);
  }

  /* Section labels are headings, not controls: small, grey, no chevron. */
  .md-nav--primary .md-nav__item--section > .md-nav__link[for],
  .md-nav--primary .md-nav__item--section > .md-nav__link {
    background: none;
    color: var(--st-ink-3);
    font-size: var(--st-type-small);
    font-weight: var(--st-weight-heading);
    margin: 1.2rem 0 0.2rem;
    min-height: 0;
    padding: 0 0.6rem;
    pointer-events: none;
  }

  .md-nav--primary .md-nav__item--section:first-child > .md-nav__link {
    margin-top: 0.2rem;
  }

  .md-nav--primary .md-nav__item--section > .md-nav__link .md-nav__icon {
    display: none;
  }

  .md-nav--primary .md-nav__item--section > .md-nav > .md-nav__list {
    padding: 0;
  }

  /* The table of contents rides under the current page as a smaller list
     behind one guide line, closed until the current-page row is tapped.
     Material shows it open at this width through a four-class rule, so the
     toggle's id is what closes it here. */
  .md-nav--primary .md-nav__link[for="__toc"] ~ .md-nav {
    display: none;
  }

  .md-nav--primary #__toc:checked ~ .md-nav {
    display: block;
  }

  .md-nav--primary .md-nav--secondary > .md-nav__list {
    border-inline-start: 1px solid var(--st-line);
    margin: 0.2rem 0 0.4rem 0.6rem;
    padding: 0;
  }

  .md-nav--primary .md-nav--secondary .md-nav .md-nav__list {
    border: 0;
    margin: 0;
    padding: 0;
  }

  .md-nav--primary .md-nav--secondary .md-nav__link {
    color: var(--st-ink-2);
    font-size: var(--st-type-ui);
    font-weight: var(--st-weight-regular);
    min-height: 1.8rem; /* 36px */
  }

  /* Material indents each contents level by another .6rem from 1.4rem; the
     sheet steps .5rem from the guide line instead. Matched at Material's
     [dir]-qualified depth. */
  [dir] .md-nav--primary .md-nav--secondary .md-nav__link {
    padding-inline-start: 0.6rem;
  }

  [dir] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link {
    padding-inline-start: 1.1rem;
  }

  [dir] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link {
    padding-inline-start: 1.6rem;
  }

  [dir] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link {
    padding-inline-start: 2.1rem;
  }
}

/* Tablet and below: one reading column, no rails in flow. */
@media screen and (max-width: 59.984375em) {
  :root {
    --st-gutter: 1.2rem; /* 24px */
  }

  .md-content__inner {
    max-width: calc(var(--st-measure) + 2rem);
    padding-inline: 0;
  }

  .md-path {
    padding-inline: 0;
  }

  /* Every grid on the landing page is one column here, so it takes the same
     comfortable width as a reading page. */
  body:has(.st-home) .md-content__inner {
    max-width: calc(var(--st-measure) + 2rem);
  }

  .st-routes,
  .st-groups,
  .md-typeset .st-tasks--inline {
    grid-template-columns: 1fr;
  }

  .st-groups {
    gap: 2.2rem;
  }

  /* The sheet's field spans the top of the screen, so it is flat and full
     width with one hairline under it. */
  .md-search__form {
    box-shadow: 0 1px 0 var(--st-line);
  }

  /* Search collapses to an icon button from here down, so the trailing edge of
     the header takes the same 0.5rem pull-back as the leading one and both
     glyphs land on the column's edges. */
  .md-header__inner {
    padding-inline-end: calc(var(--st-gutter) - 0.5rem);
  }
}

/* Phone: keep the title, the lede and code at readable sizes, keep the article
   clear of the safe area, and stack the previous/next bar so both destinations
   keep a visible label. */
@media screen and (max-width: 44.984375em) {
  :root {
    --st-gutter: 1rem; /* 20px */
    --st-header-h: 2.8rem; /* 56px */
    --st-type-h1: 1.5rem; /* 30px */
    --st-type-h2: 1.1rem; /* 22px */
    --st-type-lede: 0.85rem; /* 17px */
  }

  /* The header's controls are icon buttons at this width, and each glyph sits
     0.5rem inside its 2.2rem hit box. Insetting the bar by 0.5rem less than
     the article's own gutter therefore lands the menu glyph and the search
     glyph exactly on the article's text edges. The safe area still wins when
     it is larger, and the max() floor keeps the pull-back from going negative
     on a screen that reports no inset. */
  .md-header__inner {
    gap: 0;
    padding-inline: max(0.3rem, calc(env(safe-area-inset-left) - 0.5rem))
      max(0.3rem, calc(env(safe-area-inset-right) - 0.5rem));
  }

  .md-header__button.md-logo :is(img, svg) {
    height: 1.1rem; /* 22px */
  }

  /* Three icon-sized controls fill the bar at this width; the set's name is
     one tap away in the drawer title. */
  .st-site-label {
    display: none;
  }

  .md-main__inner {
    padding-inline: 0;
  }

  /* 0.8rem (16px) is the article's layout margin on a phone. The header glyphs,
     code blocks and article navigation use this edge. The safe area wins when
     it is larger. */
  .md-content__inner {
    padding-block: 1.6rem 2.6rem;
    padding-inline: max(0.8rem, env(safe-area-inset-left))
      max(0.8rem, env(safe-area-inset-right));
  }

  .md-path {
    padding: 1.2rem max(0.8rem, env(safe-area-inset-right)) 0
      max(0.8rem, env(safe-area-inset-left)); /* 24px above, 16px gutters */
  }

  .md-path + .md-content__inner {
    padding-top: 1rem; /* 20px between the path and title */
  }

  .md-typeset h1 {
    letter-spacing: -0.018em;
  }

  .md-typeset h2 {
    margin-top: 1.9rem;
  }

  .md-typeset pre > code {
    padding: 0.8rem;
  }

  /* Material pulls every top-level code block and the tab strip above it out
     by -0.8rem so they run to the window edges, then drops their corner radius
     to match. A code block is content, not a background: it keeps the
     article's own gutter and its whole frame, exactly like one nested in a tab
     or a callout. Only the inline margins are reset, so Material's
     overflow:auto on pre > code — the horizontal scroll inside a long line —
     is untouched. */
  .md-content__inner > pre,
  .md-content__inner > .highlight {
    margin-inline: 0;
  }

  /* The tab strip is matched at Material's own depth: it bleeds and re-insets
     the strip from [dir]-qualified rules that outrank a three-class override.
     [dir] matches the same element in either direction, so one rule covers
     both. The -0.8rem margin and the 0.8rem lead padding cancel out, which is
     how the first label lands on the text edge; removing one without the other
     would indent it, so both go. */
  [dir] .md-content__inner > .tabbed-set .tabbed-labels {
    margin-inline: 0;
    max-width: 100%;
    padding-inline: 0;
    scroll-padding-inline-start: 0;
  }

  [dir] .md-content__inner > .tabbed-set .tabbed-labels::after {
    padding-inline: 0;
  }

  /* The overflow arrows are pulled out to sit over the bled strip's edges, so
     only their margins are reset. Their own padding places the chevron inside
     the arrow and is left alone. */
  [dir] .md-content__inner > .tabbed-set .tabbed-labels ~ .tabbed-control--prev,
  [dir]
    .md-content__inner
    > .tabbed-set
    .tabbed-labels
    ~ .tabbed-control--next {
    margin-inline: 0;
  }

  /* With the bleed gone the frame is whole again, so the radius Material drops
     for the edge-to-edge treatment comes back. A captioned block needs nothing
     here: the filename rules in the code section are already deeper than
     Material's, so its header and its stepped bottom corners survive. */
  .md-content__inner > pre > code,
  .md-content__inner > .highlight > pre > code {
    border-radius: var(--st-radius-code);
  }

  .md-typeset .st-ai-title-row {
    gap: 0.5rem;
  }

  .md-typeset .st-ai-split {
    margin-block-start: 0;
  }

  .st-home > * + * {
    margin-top: 2.2rem;
  }

  .md-article-nav {
    flex-direction: column;
    gap: 0.2rem;
    margin-top: 2.2rem;
    padding: 0.8rem 0 1rem;
  }

  /* Each destination becomes its own full-width row. The arrow keeps its
     conventional side, so direction still reads without the label. */
  .md-footer__link {
    flex: 0 0 auto;
    min-height: 2.2rem;
  }

  .md-footer__link--next {
    justify-content: flex-start;
  }

  /* Material hides the previous title at this width, which leaves a bare arrow
     with no visible destination. Both rows keep their label. */
  .md-footer__link--prev .md-footer__title {
    display: block;
  }

  .md-footer__title {
    padding-inline: 0.5rem;
  }

}

/* User preferences ------------------------------------------------------- */

/* The token values for this preference live in tokens.css; only the widths
   that carry structure change here. */
@media (prefers-contrast: more) {
  .md-typeset table:not([class]),
  .md-typeset .st-route,
  .md-typeset .st-ai-split {
    border-width: 2px;
  }
}

@media (forced-colors: active) {
  /* Forced colours remove the inset shadow used for these route links. */
  .md-typeset .st-hub-route:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  :where(a, button, input, label, summary, [tabindex]):focus-visible,
  .md-header__button:focus-visible,
  .md-search__options > .md-icon:focus-visible,
  .md-clipboard:focus-visible,
  .md-nav__link:focus-visible,
  .md-footer__link:focus-visible,
  .md-typeset .md-code__button:focus-visible,
  .md-typeset summary:focus-visible {
    outline-color: Highlight;
  }

  .md-header,
  .md-article-nav,
  .md-content,
  .st-site-label,
  .md-typeset :is(.admonition, details),
  .md-typeset table:not([class]),
  .md-typeset .st-route,
  .md-typeset .st-ai-split,
  .md-typeset .st-next {
    border-color: CanvasText;
  }

  .md-nav--primary .md-nav__item .md-nav__link--active,
  .md-nav--secondary .md-nav__item .md-nav__link--active {
    background: Highlight;
    color: HighlightText;
  }
}
