/* THIS SITE'S OWN FILE. Not generated, and named in gen-docs-lib.js under OWN.
 *
 * The documentation shell. 16d: GitBook's structure and interaction patterns,
 * with GFTV's own palette. **The layout conventions, never the branding** --
 * no GitBook logo, name, or assets, and nothing implying any affiliation.
 *
 * Every colour here is a token from theme.css, which is generated from the
 * portal's copy and measured against AA in all four combinations of theme and
 * mode. **That measurement does not carry over to this file.** Phase 12 part 3
 * is the standing warning: a ratio cannot tell you a state is drawn only in
 * hue, and every component below is new. What the shared tokens buy is that no
 * component here invents a colour; what they do not buy is that the components
 * are right.
 *
 * **This covers the whole site and not only the public half.** The sign in form
 * and the account settings page render in this shell, with the content column
 * carrying a form where the prose would be. It is documentation that happens to
 * need a session, not an admin panel with pages in it, and it must not start
 * looking like one. The portal's dashboard look does not come here.
 *
 * Three columns on desktop, and what happens as the width goes:
 *
 *   >= 1024px   sidebar, content, on-page contents
 *   >= 640px    sidebar, content, and the contents as a collapsible block above
 *   <  640px    content, with the sidebar behind the hamburger as a panel
 *
 * Search and the account control keep their place in the header at every width
 * and never go inside the hamburger. 16d gives the reason for both: search is
 * how people navigate documentation on a phone, and a reader who cannot find
 * how to sign out assumes they have not.
 */

:root {
  /* The three callout tints this site needs that theme.css already has, and the
   * fourth it does not.
   *
   * A note is the flavour with no status behind it, so there is no --note to
   * mix from. Derived against --text the way --border-control is, for the same
   * reason: it composites over whatever surface is behind it and stays correct
   * in all four combinations without four literals somebody has to re-measure.
   *
   * Defined here and not in theme.css because theme.css is generated from the
   * portal's copy, and the portal has no callouts. A token added there for this
   * site alone would be a token nothing on that site ever references. */
  --callout-note-bg: color-mix(in srgb, var(--text) 6%, transparent);
  --callout-note-line: color-mix(in srgb, var(--text) 40%, transparent);

  --docs-header-height: 3.5rem;
  --docs-sidebar-width: 17rem;
  --docs-toc-width: 14rem;
  --docs-content-width: 48rem;
  --docs-gutter: 2rem;
}

/* ---------------------------------------------------------------------------
 * The frame
 * ------------------------------------------------------------------------ */

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  line-height: 1.65;
}

.docs-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
}

.docs-skip:focus {
  left: 0.5rem;
  top: 0.5rem;
}

/* Every focusable thing, one ring. --focus-ring is --brand-dark, which is over
 * 3:1 in all four combinations; --border is not and is why this does not use
 * it. */
.docs-shell :focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

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

.docs-header {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: var(--docs-header-height);
  padding: 0 1rem;
  border-bottom: 1px solid var(--border);

  /* Glass, and the blur is not decoration. --surface is 60% white in classic
   * light, so a sticky bar painted with it and nothing else has the page's own
   * text sliding legibly through it. theme.css pairs the token with this blur
   * in .glass-card for exactly that reason, and anything sticky needs the pair
   * and not the half. */
  background: var(--surface);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
}

.docs-brand {
  font-weight: 600;
  white-space: nowrap;
  color: var(--text);
}

.docs-brand:hover {
  color: var(--brand-text);
}

/* The search field, and the flexible space in the header. It grows and the two
 * controls to its right do not, which is what keeps them in the same place as
 * the window changes size. */
.docs-search {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 26rem;
  margin: 0 auto;
}

.docs-search input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.4rem 0.75rem;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
}

/* The results, hung under the field. Fixed width and its own scroll, because it
 * holds up to eight results and the header it hangs from is 3.5rem tall. */
.docs-search {
  position: relative;
}

.docs-search-results {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 45;
  max-height: min(70vh, 30rem);
  overflow-y: auto;
  padding: 0.35rem;
  /* Opaque, for the reason the account menu is: it is read over whatever the
   * page happens to have under it. */
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.docs-result {
  display: block;
  padding: 0.5rem 0.6rem;
  color: var(--text);
  border-radius: var(--radius-sm);
}

.docs-result:hover,
.docs-result[aria-selected='true'] {
  background: var(--surface-active);
}

.docs-result-title {
  display: block;
  font-weight: 600;
  font-size: 0.9rem;
}

/* The heading the match sits under, and the result jumps to its anchor. */
.docs-result-heading {
  display: block;
  font-size: 0.8rem;
  color: var(--brand-text);
}

.docs-result-snippet {
  display: block;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* A matched word. **Not colour alone**: the weight steps up with the tint, so
 * the mark survives a reader who cannot separate the two. */
.docs-result mark {
  padding: 0 0.1em;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-active);
  border-radius: 2px;
}

/* Nothing matched, or a half of the index that did not load. A sentence in the
 * panel and never an empty box, which reads as a broken control. */
.docs-result-note {
  margin: 0;
  padding: 0.5rem 0.6rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.docs-header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

.docs-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.7rem;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--transition) var(--ease);
}

.docs-btn:hover {
  background: var(--surface-hover);
}

.docs-btn-quiet {
  border-color: transparent;
  background: transparent;
}

.docs-btn-quiet:hover {
  background: var(--surface-hover);
}

/* The hamburger. Present only below 640px, where it is the sidebar's only
 * opener. */
.docs-menu-btn {
  display: none;
}

.docs-account {
  position: relative;
}

.docs-account-role {
  color: var(--text-muted);
  font-size: 0.8rem;
}

.docs-account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  min-width: 12rem;
  padding: 0.35rem;
  /* Opaque, and not the glass surface. A menu is read over whatever it happens
   * to be covering, and two overlapping words are worse than a plain box. */
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.docs-account-menu button,
.docs-account-menu a {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 0.45rem 0.6rem;
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.docs-account-menu button:hover,
.docs-account-menu a:hover {
  background: var(--surface-hover);
}

/* ---------------------------------------------------------------------------
 * The three columns
 * ------------------------------------------------------------------------ */

.docs-layout {
  display: grid;
  grid-template-columns: 1fr;
  max-width: 96rem;
  margin: 0 auto;
  padding: 0 1rem;
  gap: var(--docs-gutter);
}

.docs-sidebar {
  display: none;
}

.docs-main {
  min-width: 0;
  padding: 1.5rem 0 4rem;
}

.docs-article {
  max-width: var(--docs-content-width);
}

/* **A public page's article is English until its translation arrives**, since
   phase 14 part 9, and this is the portal's own rule applied to the one element
   that arrives late here.

   The build writes one static file per public page and it is the English one,
   per 16f: a language is a stored preference and not an address. So a 华文
   reader loads the right page in the wrong language and the shell fetches the
   translation. Without this rule they would read a paragraph of English and
   watch it be replaced.

   The pre-paint script in shell.html does exactly this for the whole document
   while the dictionary loads, and the comment in theme.css beside it carries
   the reasoning: visibility over display, so layout is already computed and
   nothing jumps when the page appears. This is the same technique, scoped to
   the article, for the same reason.

   **shell.js releases it on arrival, on failure, and on a timeout**, which is
   the release valve the pre-paint script has and for the identical reason: a
   translation that never arrives must not leave somebody looking at nothing.
   Never set for an English reader, and never set without JavaScript. */
.docs-article[data-awaiting-translation] {
  visibility: hidden;
}

.docs-toc {
  display: none;
}

@media (min-width: 640px) {
  .docs-layout {
    grid-template-columns: var(--docs-sidebar-width) minmax(0, 1fr);
    padding: 0 1.5rem;
  }

  .docs-sidebar {
    display: block;
  }
}

@media (min-width: 1024px) {
  .docs-layout {
    grid-template-columns: var(--docs-sidebar-width) minmax(0, 1fr) var(--docs-toc-width);
  }

  .docs-toc {
    display: block;
  }

  /* The collapsible copy above the content is the same list, and only one of
     the two is ever on screen. */
  .docs-toc-inline {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
 * Sidebar
 * ------------------------------------------------------------------------ */

.docs-sidebar {
  position: sticky;
  top: var(--docs-header-height);
  align-self: start;
  max-height: calc(100vh - var(--docs-header-height));
  overflow-y: auto;
  padding: 1.5rem 0.5rem 3rem 0;
  font-size: 0.925rem;
}

.docs-sidebar-section + .docs-sidebar-section {
  margin-top: 1.25rem;
}

.docs-sidebar-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0.3rem 0.5rem;
  font: inherit;
  font-weight: 600;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
}

.docs-sidebar-heading:hover {
  background: var(--surface-hover);
}

.docs-sidebar-heading svg {
  flex: none;
  transition: transform var(--transition) var(--ease);
}

.docs-sidebar-heading[aria-expanded='false'] svg {
  transform: rotate(-90deg);
}

.docs-sidebar ul {
  list-style: none;
  margin: 0.15rem 0 0;
  padding: 0 0 0 0.5rem;
}

.docs-sidebar li a {
  display: block;
  padding: 0.3rem 0.5rem;
  color: var(--text-muted);
  border-left: 2px solid var(--border);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.docs-sidebar li a:hover {
  color: var(--text);
  background: var(--surface-hover);
}

/* The current page. **Not colour alone**: the left rule thickens and the weight
 * steps up, so the state survives a reader who cannot separate the two hues.
 * Part 3 of phase 12 is the reason that sentence is here. */
.docs-sidebar li a[aria-current='page'] {
  color: var(--text);
  font-weight: 600;
  border-left-color: var(--brand-dark);
  background: var(--surface-active);
}

/* The portal link, which lives in the header above 640px and here below it. One
 * of the two is always hidden, and it is a button either way.
 *
 * **It was a bare anchor here and a quiet button there, and both read as loose
 * text.** It is the one control on this site that leaves it, sitting under a
 * rule at the bottom of the panel with nothing around it, so it needs to look
 * like something you press. The header's copy drops `docs-btn-quiet` for the
 * same reason. Reported 7 September 2026. */
.docs-sidebar-foot {
  margin-top: 1.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.docs-sidebar-foot .docs-btn {
  justify-content: center;
  width: 100%;
}

@media (min-width: 640px) {
  .docs-sidebar-foot {
    display: none;
  }
}

/* Below 640px the sidebar is an off canvas panel, per section 3. */
@media (max-width: 639.98px) {
  .docs-menu-btn {
    display: inline-flex;
  }

  /* **What comes out of the header at this width, and why.** 16d pins two
   * things there at every width and gives the reason for each: search, because
   * it is how people navigate documentation on a phone, and the account
   * control, because a reader who cannot find how to sign out assumes they have
   * not. Everything else in that list is movable, and all of it has to move:
   * measured at 375px, the row wanted 494px and the three fixed width controls
   * were 256px of it, so the search field had nothing left to be.
   *
   * The site name goes, because the page title and the first entry in the panel
   * both carry it. The portal link goes into the panel, where it is a link like
   * any other and is not lost. The role beside the name goes, because the name
   * is what identifies the account and the role is what qualifies it. */
  .docs-brand,
  .docs-portal-link,
  .docs-account-role {
    display: none;
  }

  .docs-search input {
    min-width: 6rem;
  }

  .docs-sidebar {
    display: block;
    position: fixed;
    inset: var(--docs-header-height) auto 0 0;
    z-index: 50;
    width: min(20rem, 84vw);

    /* **The panel is as tall as the screen below the header, and it scrolls
     * inside itself.** Both halves of that were missing and the second was a
     * consequence of the first.
     *
     * `inset` sets top and bottom, which should have been enough to fill the
     * space between them -- and was not, because the base rule above sets
     * `align-self: start`. That is right for the sticky column this becomes at
     * 640px, and for an absolutely positioned box it means "take your content
     * height and sit at the start" in place of the default stretch. So the panel
     * grew to its content, `bottom` was ignored as over-constrained, and a
     * sidebar with every section open stood 1,467px tall on a 720px screen with
     * its lower half simply off the bottom of the window and no way to reach it.
     *
     * The height is stated here instead of unsetting align-self, because a
     * number is what the next person reading this needs: dvh is the viewport a
     * phone actually shows, which changes as the browser's own chrome slides
     * away, and vh is the fallback for anything that does not know dvh.
     *
     * `overscroll-behavior` is the reported half of the defect. Without it, a
     * scroll that reaches either end of the panel carries on into the page
     * underneath, which is what a reader felt as "the sidebar will not scroll":
     * it was not scrolling, the page behind it was. Reported 7 September 2026. */
    height: calc(100vh - var(--docs-header-height));
    height: calc(100dvh - var(--docs-header-height));
    max-height: none;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 1rem;
    /* Opaque, for the same reason the account menu is: this panel covers the
     * page and its whole job is a list somebody has to read. */
    background: var(--bg);
    border-right: 1px solid var(--border);
    transform: translateX(-100%);
    transition: transform var(--transition) var(--ease);
  }

  .docs-sidebar[data-open='true'] {
    transform: translateX(0);
  }

  .docs-scrim {
    position: fixed;
    inset: var(--docs-header-height) 0 0 0;
    z-index: 45;
    background: rgba(0, 0, 0, 0.4);
    /* Dragging the dimmed area is not a way to scroll the page you cannot see.
     * The panel's own overscroll rule stops the chaining from inside it, and
     * this stops it from beside it. */
    overscroll-behavior: contain;
    touch-action: none;
  }
}

/* ---------------------------------------------------------------------------
 * On-page contents
 * ------------------------------------------------------------------------ */

.docs-toc {
  position: sticky;
  top: var(--docs-header-height);
  align-self: start;
  max-height: calc(100vh - var(--docs-header-height));
  overflow-y: auto;
  padding: 1.5rem 0 3rem;
  font-size: 0.875rem;
}

.docs-toc h2,
.docs-toc-inline summary {
  margin: 0 0 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.docs-toc ol,
.docs-toc-inline ol {
  list-style: none;
  margin: 0;
  padding: 0;
}

.docs-toc a,
.docs-toc-inline a {
  display: block;
  padding: 0.2rem 0 0.2rem 0.65rem;
  color: var(--text-muted);
  border-left: 2px solid var(--border);
}

.docs-toc a:hover {
  color: var(--text);
}

.docs-toc li[data-level='3'] a,
.docs-toc-inline li[data-level='3'] a {
  padding-left: 1.4rem;
}

.docs-toc li[data-level='4'] a,
.docs-toc-inline li[data-level='4'] a {
  padding-left: 2.15rem;
}

/* Weight as well as colour, again. */
.docs-toc a[aria-current='true'] {
  color: var(--text);
  font-weight: 600;
  border-left-color: var(--brand-dark);
}

.docs-toc-inline {
  margin: 0 0 1.5rem;
  padding: 0.75rem 1rem;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.docs-toc-inline summary {
  cursor: pointer;
}

.docs-toc:empty,
.docs-toc-inline[hidden] {
  display: none;
}

/* ---------------------------------------------------------------------------
 * Breadcrumbs, and the pager at the foot
 * ------------------------------------------------------------------------ */

.docs-breadcrumbs {
  margin-bottom: 0.75rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.docs-breadcrumbs a {
  color: var(--text-muted);
}

.docs-breadcrumbs a:hover {
  color: var(--text);
}

.docs-breadcrumbs span {
  margin: 0 0.4rem;
  color: var(--text-light);
}

.docs-pager {
  display: flex;
  gap: 1rem;
  max-width: var(--docs-content-width);
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.docs-pager a {
  flex: 1 1 0;
  padding: 0.75rem 1rem;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color var(--transition) var(--ease);
}

.docs-pager a:hover {
  border-color: var(--border-strong);
}

.docs-pager .docs-pager-next {
  text-align: right;
}

.docs-pager small {
  display: block;
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* ---------------------------------------------------------------------------
 * The article itself
 * ------------------------------------------------------------------------ */

.docs-article h1 {
  margin: 0 0 1rem;
  font-size: 2rem;
  line-height: 1.25;
}

.docs-article h2 {
  margin: 2.5rem 0 0.75rem;
  padding-top: 0.5rem;
  font-size: 1.4rem;
  line-height: 1.3;
  border-top: 1px solid var(--border);
}

.docs-article h3 {
  margin: 2rem 0 0.5rem;
  font-size: 1.15rem;
}

.docs-article h4 {
  margin: 1.5rem 0 0.5rem;
  font-size: 1rem;
}

.docs-article p,
.docs-article ul,
.docs-article ol {
  margin: 0 0 1rem;
}

.docs-article li {
  margin-bottom: 0.35rem;
}

/* Anchor links, which appear on hover and on focus. Focus matters more than
 * hover here: a keyboard reader has no pointer to hover with, and the link is
 * otherwise unreachable. */
.docs-anchor {
  margin-left: 0.4rem;
  color: var(--text-light);
  opacity: 0;
  transition: opacity var(--transition) var(--ease);
}

.docs-article h1:hover .docs-anchor,
.docs-article h2:hover .docs-anchor,
.docs-article h3:hover .docs-anchor,
.docs-article h4:hover .docs-anchor,
.docs-anchor:focus {
  opacity: 1;
}

/* Anything wide scrolls inside its own box. The page body never scrolls
 * sideways, at any width. */
.docs-scroller {
  overflow-x: auto;
  max-width: 100%;
}

.docs-article table {
  border-collapse: collapse;
  width: 100%;
  margin: 0 0 1rem;
  font-size: 0.925rem;
}

.docs-article th,
.docs-article td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}

.docs-article th {
  border-bottom-color: var(--border-strong);
  font-weight: 600;
}

.docs-article code {
  padding: 0.1em 0.35em;
  font-size: 0.9em;
  background: var(--surface-active);
  border-radius: var(--radius-sm);
}

.docs-article blockquote {
  margin: 0 0 1rem;
  padding: 0.25rem 0 0.25rem 1rem;
  border-left: 3px solid var(--border-strong);
  color: var(--text-muted);
}

.docs-article blockquote p:last-child {
  margin-bottom: 0;
}

.docs-article hr {
  margin: 2rem 0;
  border: 0;
  border-top: 1px solid var(--border);
}

/* Code blocks */
.docs-code {
  margin: 0 0 1.25rem;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.docs-code-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem 0.35rem 0.85rem;
  border-bottom: 1px solid var(--border);
}

.docs-code-lang {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.docs-copy {
  padding: 0.2rem 0.55rem;
  font: inherit;
  font-size: 0.8rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.docs-copy:hover {
  background: var(--surface-hover);
}

.docs-code pre {
  margin: 0;
  padding: 0.85rem;
  font-size: 0.875rem;
  line-height: 1.55;
}

.docs-code pre code {
  padding: 0;
  background: none;
}

/* Callouts, 16d's four flavours. Used sparingly, which is a rule for whoever
 * writes the pages and not something this file can enforce.
 *
 * **A label as well as a tint.** The four are told apart by a word, not by
 * colour: three of the tints are 14% of a status colour and would be four
 * shades of pale to a reader who cannot separate them. */
.docs-callout {
  margin: 0 0 1.25rem;
  padding: 0.85rem 1rem;
  border-left: 3px solid var(--callout-note-line);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--callout-note-bg);
}

.docs-callout-label {
  margin: 0 0 0.35rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.docs-callout p:last-child {
  margin-bottom: 0;
}

.docs-callout[data-callout='tip'] {
  background: var(--callout-ok-bg);
  border-left-color: var(--ok);
}

.docs-callout[data-callout='warning'] {
  background: var(--callout-warn-bg);
  border-left-color: var(--warn);
}

.docs-callout[data-callout='danger'] {
  background: var(--callout-danger-bg);
  border-left-color: var(--danger);
}

/* Collapsible blocks */
.docs-details {
  margin: 0 0 1.25rem;
  padding: 0.5rem 1rem;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.docs-details summary {
  cursor: pointer;
  font-weight: 600;
  padding: 0.25rem 0;
}

.docs-details-body {
  padding-top: 0.5rem;
}

/* Tabs, for anything that differs between a desktop and a phone. */
/* Wrapping, from phase 14 part 2, which is the first page in either tree to use
   a tabbed block. Four labels in one unwrapped row squeezed every button until
   its text wrapped inside it and pushed the last one 35px off a 320px screen.
   16d puts tabs there for "anything that differs by device", so the width they
   have to hold is a phone's. Rows stack; a label stays on one line. */
.docs-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0 0 -1px;
  border-bottom: 1px solid var(--border);
}

.docs-tabs button {
  padding: 0.4rem 0.85rem;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text-muted);
  background: none;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  cursor: pointer;
}

.docs-tabs button[aria-selected='true'] {
  color: var(--text);
  font-weight: 600;
  background: var(--bg-alt);
  border-color: var(--border);
}

.docs-tabpanel {
  margin: 0 0 1.25rem;
  padding: 1rem;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 0 var(--radius) var(--radius) var(--radius);
}

.docs-tabpanel > :last-child {
  margin-bottom: 0;
}

/* ---------------------------------------------------------------------------
 * Figures, and the slots waiting for a screenshot
 * ------------------------------------------------------------------------ */

.docs-figure {
  margin: 1.5rem 0;
  max-width: 100%;
}

.docs-figure img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.docs-figure figcaption {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* 16g: "until the first capture run, render clearly marked placeholder slots
 * with the intended alt text and caption in place, so a missing image reads as
 * pending rather than broken." A dashed edge and the words, and it is
 * deliberately not styled to look like a picture: it is a note saying one is
 * coming. */
.docs-pending {
  display: block;
  padding: 1.5rem;
  text-align: center;
  border: 1px dashed var(--border-control);
  border-radius: var(--radius);
  background: var(--surface);
}

.docs-pending-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.docs-pending-alt {
  display: block;
  margin-top: 0.35rem;
  color: var(--text);
}

/* ---------------------------------------------------------------------------
 * States the shell itself draws
 * ------------------------------------------------------------------------ */

/* When the page last changed, per 16e. Quiet, and absent when there is no date
 * to show. */
.docs-updated {
  max-width: var(--docs-content-width);
  margin: 2rem 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.docs-state {
  max-width: var(--docs-content-width);
  padding: 2rem 0;
}

.docs-state h1 {
  margin: 0 0 0.75rem;
  font-size: 1.5rem;
}

.docs-state p {
  color: var(--text-muted);
}

/* Nothing for 250ms, then a fade, which is theme.css's own loading rule and the
 * reason a fast page never flashes a spinner at anybody. */
.docs-loading {
  color: var(--text-muted);
  opacity: 0;
  animation: docs-fade-in 0.2s var(--ease) 0.25s both;
}

@keyframes docs-fade-in {
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .docs-sidebar,
  .docs-anchor,
  .docs-btn,
  .docs-pager a,
  .docs-loading {
    transition-duration: 0.01ms;
    animation-duration: 0.01ms;
    animation-delay: 0ms;
  }
}

/* -------------------------------------------------------------------------
 * The account pages, phase 13 part 6
 *
 * **This block is the adapter, and it is the whole of what makes 5f's "one
 * implementation, mounted twice" true.** assets/js/staff-account.js and
 * staff-forgot-password.js are generated copies of the portal's and write the
 * portal's class names; theme.css, which is also generated, already carries
 * .btn, .callout, .glass-card, .modal and the spinner. What is left is
 * everything app.css defines and this site has never had, and it is defined
 * here in this site's own language instead of by copying app.css across.
 *
 * 16d is the brief and it is emphatic: "The staff guides, the developer guide,
 * the login form, and the account settings page in 16c are all GitBook shaped
 * ... The staff half is documentation that happens to need a session, not an
 * admin panel with pages in it, and it must not start looking like one." So
 * these are hairlines and whitespace, and the portal's cards and shadows stay
 * on the portal.
 * ---------------------------------------------------------------------- */

.page-header {
  margin-bottom: 1.5rem;
}

.page-header h1 {
  margin: 0 0 0.35rem;
}

.page-header .lede {
  margin: 0;
  color: var(--text-muted);
}

.lede {
  color: var(--text-muted);
}

.muted {
  color: var(--text-muted);
}

/* A panel. The portal draws a glass card; this draws a hairline and lets the
   whitespace do the work, which is the whole difference between the two sites
   expressed in four declarations. */
.glass-card.card,
section.card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: none;
  backdrop-filter: none;
  padding: 1.5rem;
  margin: 0 0 1.5rem;
}

.stack > * + * {
  margin-top: 1rem;
}

.card h2 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 1.05rem;
}

/* ---- Fields ----------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.field label {
  font-weight: 600;
  font-size: 0.9rem;
}

.field input,
.field textarea {
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem;
  width: 100%;
}

.field input:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

.field-hint {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.field-error {
  margin: 0;
  font-size: 0.85rem;
  color: var(--danger);
}

.checkbox,
.check-row {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: 0.92rem;
}

.checkbox input,
.check-row input {
  margin-top: 0.2rem;
  flex: none;
}

/* ---- Lists of things you can revoke ----------------------------------- */

.device-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}

.device-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.8rem 0;
  border-bottom: 1px solid var(--border);
}

.device-main {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.device-label {
  font-weight: 600;
}

.device-main .muted {
  font-size: 0.85rem;
}

.device-actions {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.account-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}

.account-row-label {
  color: var(--text-muted);
}

.account-row-value {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.badge {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  vertical-align: middle;
}

/* ---- The authenticator app enrolment ---------------------------------- */

.totp-qr {
  /* The white plate is the QR's own, from the SVG, and this frames it so the
     symbol has somewhere to sit in dark mode without the page behind it
     bleeding into the quiet zone. */
  max-width: 220px;
  padding: 0.75rem;
  background: #ffffff;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.totp-secret {
  margin: 0;
}

.totp-secret code {
  display: inline-block;
  font-size: 1rem;
  letter-spacing: 0.08em;
  overflow-wrap: anywhere;
}

/* ---- The danger zone --------------------------------------------------- */

.danger-zone {
  border-color: var(--danger);
}

.danger-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.danger-consequences {
  margin: 0;
  padding-left: 1.15rem;
}

.danger-consequences li + li {
  margin-top: 0.4rem;
}

.danger-step {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.danger-dialog {
  max-width: 32rem;
}

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* ---- The recovery code dialog ------------------------------------------ */

.code-dialog {
  max-width: 30rem;
}

.code-list {
  list-style: none;
  margin: 0;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-alt);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem 1rem;
}

.code-list li {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.04em;
}

.code-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.code-action-note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.tabular {
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------------------
 * The language and theme controls, phase 14 part 1
 *
 * **There is deliberately nothing here.** A `.docs-lang` block styled the bare
 * <select> this header carried from part 6a, and a `.docs-btn` styled the word
 * "Light" beside it. Both are gone: the two controls are `.icon-btn`s opening
 * the modals gftv-theme.md prescribes, and `.icon-btn`, `.modal`,
 * `.modal-backdrop`, `.modal-head`, `.modal-section-label`, `.mode-toggle`,
 * `.mode-btn`, `.mode-note`, `.swatch-grid`, `.swatch`, `.locale-list`,
 * `.locale-btn` and `.locale-note` are all defined in theme.css — which has been
 * generated into this site since part 4.
 *
 * That is the whole finding of the part, seen from the stylesheet's end: this
 * site had been shipping the styles for controls it had never built.
 * ---------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
 * The connection bar, phase 14 part 4
 *
 * The bar assets/js/connection-bar.js draws, above the header and below the
 * skip link. It says one of three things: you are offline, we cannot be
 * reached, or a new version is ready.
 *
 * **Written here in this site's own language and not copied from app.css**,
 * which is the account block's rule two hundred lines above and the same
 * reasoning: the module is shared because its behaviour is the same on both
 * sites, and 16d is emphatic that this site stays hairlines and whitespace
 * where the portal has cards. `.btn`, `.btn-secondary` and `.btn-quiet` come
 * from theme.css, which is generated into both; `.small-btn` does not, so it is
 * here.
 * ---------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * The official site banner, gftv-official.md. Phase 15 part 3.
 *
 * The quietest thing on the page, by that file's instruction: one line on
 * --bg-alt with a hairline under it, the toggle reading as a link, and the
 * panel the same surface growing and not a new one appearing. Every value is a
 * theme token. It sits above the header and above every other bar, and honours
 * the safe area because it is the topmost element on the page.
 *
 * One row at every width down to 320px: the mark and the toggle never shrink,
 * and the line gives way with an ellipsis before the bar is allowed a second
 * row, since a bar tall enough to push the header off a phone is the failure
 * the file names. The panel is two columns above 640px and one below, points
 * stacking in the order written so the domain rule comes first on a phone.
 * ------------------------------------------------------------------------- */

.gov-bar {
  background: var(--bg-alt);
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.8125rem;
}

.gov-bar-inner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 72rem;
  margin: 0 auto;
  padding: 0.375rem 1rem;
  padding-top: max(0.375rem, env(safe-area-inset-top));
  min-height: 2rem;
}

.gov-bar-mark {
  flex: none;
  width: 24px;
  height: 16px;
  border-radius: 2px;
  object-fit: cover;
}

.gov-bar-line {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.gov-bar-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0;
  padding: 0.125rem 0;
  border: 0;
  background: none;
  color: var(--brand-text);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}

.gov-bar-toggle [data-icon] {
  transition: transform 180ms ease;
}

.gov-bar-toggle[aria-expanded='true'] [data-icon] {
  transform: rotate(180deg);
}

/* The grow and shrink, on the grid row, so nothing is measured in script. The
 * panel is `hidden` while collapsed and data-open drives the motion between
 * the two states; official-bar.js sequences the attribute and the flag. */
.gov-bar-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 180ms ease;
}

.gov-bar-panel[data-open='true'] {
  grid-template-rows: 1fr;
}

.gov-bar-panel[hidden] {
  display: none;
}

.gov-bar-panel-inner {
  overflow: hidden;
  max-width: 72rem;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0 2rem;
  padding: 0 1rem;
}

.gov-bar-panel[data-open='true'] .gov-bar-panel-inner {
  padding-bottom: 0.875rem;
}

@media (min-width: 640px) {
  .gov-bar-panel-inner {
    grid-template-columns: 1fr 1fr;
  }
}

.gov-bar-point {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding-top: 0.5rem;
  color: var(--text-muted);
}

.gov-bar-point > [data-icon] {
  flex: none;
  margin-top: 0.125rem;
  color: var(--text);
}

.gov-bar-point h2 {
  margin: 0 0 0.25rem;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--text);
}

.gov-bar-point p {
  margin: 0 0 0.25rem;
  line-height: 1.45;
}

.gov-bar-point a,
.gov-bar-point a:visited {
  color: var(--brand-text);
}

@media (prefers-reduced-motion: reduce) {
  .gov-bar-panel,
  .gov-bar-toggle [data-icon] {
    transition: none;
  }
}

.connection-notice {
  background: var(--bg-alt);
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.875rem;
}

/* The one state carrying an action, tinted so it reads apart from a page that
 * has simply lost its connection. */
.connection-notice[data-state='update'] {
  background: color-mix(in srgb, var(--brand-dark) 12%, transparent);
  border-bottom-color: var(--border-strong);
  color: var(--text);
}

.connection-notice-inner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* The same 96rem .docs-layout uses, so the bar's text starts where the page's
     does instead of at a width of its own. */
  max-width: 96rem;
  margin: 0 auto;
  padding: 0.5rem 1rem;
  padding-top: max(0.5rem, env(safe-area-inset-top));
}

/* Wrapping, because the update state carries two buttons and a sentence, and
 * 320px is a width this build tests at. */
@media (max-width: 640px) {
  .connection-notice-inner {
    flex-wrap: wrap;
    padding-inline: 1rem;
  }
}

.connection-notice-inner > [data-icon] {
  flex: none;
  display: inline-flex;
}

.connection-notice p {
  flex: 1 1 12rem;
  min-width: 0;
}

.connection-notice a,
.connection-notice a:visited {
  color: var(--brand-text);
}

.connection-notice .btn {
  flex: none;
}

.small-btn {
  min-height: 36px;
  padding: 0.25rem 0.625rem;
  font-size: 0.875rem;
}

/* -------------------------------------------------------------------------
 * The test script downloads, phase 14 part 7
 *
 * One table on one page of the developer guide, drawn by
 * assets/js/test-scripts.js from the data file the content route sent inside
 * the page. It reuses the article's own table styles, because it is a table in
 * an article and looking like one is the whole intent: 16d keeps this site to
 * hairlines and whitespace, and a panel with its own frame would read as the
 * dashboard arriving on the documentation.
 * ---------------------------------------------------------------------- */

.docs-scripts {
  margin: 0 0 1.5rem;
}

.docs-scripts td {
  vertical-align: top;
}

.docs-scripts-description {
  margin: 0.25rem 0 0;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.docs-scripts-usage {
  margin: 0.25rem 0 0;
  font-size: 0.875rem;
}

/* The two narrow columns, which hold a measurement and a digest. Neither is
   prose, so neither wraps mid value: the scroller around the table is what
   gives way at a narrow width. */
.docs-scripts-size,
.docs-scripts-digest {
  white-space: nowrap;
}

.docs-scripts-digest {
  font-size: 0.8125rem;
  color: var(--text-muted);
}

.docs-scripts-generated {
  margin: 0.5rem 0 0;
  color: var(--text-muted);
  font-size: 0.875rem;
}
