/* navbar-geometry:begin — GENERATED, do not edit */
/* The navbar's geometry, from `scripts/lib/navbar-geometry.ts`. That module
 * carries the argument for every value here, including which side's numbers
 * won and why. `navbar.ts` interpolates the same constants into the
 * stylesheet it injects on a mounted page, so the rail and this site's sidebar
 * cannot be different widths.
 *
 * Regenerate with `bun run cat navbar:geometry`; `navbar:geometry:check` fails
 * the build when this region is stale. Edit the MODULE, never these lines. */
:root {
  /* The mark column — the avatar or glyph, and the only thing in the strip. */
  --fa-nav-mark: 2rem;
  /* Gutter either side of it. DERIVED: (collapsed - mark) / 2. */
  --fa-nav-pad: 0.75rem;
  /* At rest. Closed is NOT gone — a width, never a `visibility: hidden`. */
  --fa-nav-collapsed: 3.5rem;
  /* Open, at the theme's mq(md). */
  --fa-nav-open: 15.5rem;
}

/* The theme widens its own sidebar here, with a `min-width` FLOOR in
 * layout.scss that a later `width` cannot cross. That floor is why these are
 * the numbers both surfaces adopt rather than the rail's. */
@media (min-width: 66.5rem) {
  :root { --fa-nav-open: 16.5rem; }
}
/* navbar-geometry:end */

/*
 * Docs-site UI affordances: the header QR toggle, and zoom / full-width
 * controls on the workflow figures.
 *
 * Everything here is scoped to classes this site adds itself, so a
 * just-the-docs upgrade cannot collide with it.
 *
 * A note on the `var(--x, literal)` colours below, because the header of this
 * file used to claim more than was true. It said colours are "taken from the
 * theme's own custom properties where they exist" — just-the-docs is Sass and
 * defines NONE of `--sidebar-color`, `--border-color` or `--link-color`, so
 * every one of those resolves to its literal fallback, always. The var() form
 * is kept because it costs nothing and would start working if the theme ever
 * exposed them; what is not kept is the impression that it already does.
 *
 * Since the fallbacks were all dark-scheme values and the header now offers a
 * light mode, the ones that actually differ between schemes are overridden
 * under `:root[data-fa-scheme="light"]`, which docs-ui.js sets.
 */

/* ── Header QR ─────────────────────────────────────────────────────────── */

/* The sidebar title sits on one row with two icon buttons now, and at the
   theme's default size it crowds them and wraps on a narrow window. Relative
   to the inherited size rather than a fixed rem, so it still tracks the
   theme's own responsive steps. */
.fa-qr-host .site-title {
  font-size: 0.9em;
}

/* THE WRAPPER TAKES OVER A CONTRACT IT INTERRUPTED, and `width: 100%` was the
 * wrong way to do it.
 *
 * just-the-docs lays `.site-header` out as `display: flex` with exactly two
 * children: `.site-title`, which carries `flex-grow: 1`, and `#menu-button`,
 * which sits at the right end. That button is `display: none` at `mq(md)` — it
 * exists ONLY on mobile, and it is the hamburger that reveals `#main-header`,
 * which is where SEARCH and the GitHub aux link live (`display: none` below
 * the breakpoint, `&.nav-open { display: block }`).
 *
 * `mountQr` moves the title INTO this wrapper, so the theme's `flex-grow: 1`
 * stopped applying to a child of `.site-header` and started applying to a
 * child of this. The wrapper was given `width: 100%` instead — which is not
 * the same thing. A flex item also defaults to `min-width: auto`, so it
 * refuses to shrink below its own min-content; with a title, two icon buttons
 * and a 100% width in the row, the overflow goes somewhere, and on a narrow
 * phone that somewhere is the hamburger going off the right edge. On desktop
 * the same rule was harmless because the button is not rendered at all.
 *
 * `flex: 1 1 auto` is the theme's `flex-grow: 1` transplanted to the element
 * that now occupies that slot. `min-width: 0` is what lets it actually shrink,
 * so the title gives way and the button stays on screen. */
/* No `position: relative` here any more. It existed to be the containing block
   for the absolutely-positioned code panel; the panel is in the sidebar column
   now, so the relative positioning anchored nothing and only made the rule
   look like it still governed the code's placement. */
.fa-qr-host {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 1 1 auto;
  min-width: 0;
}

/* Same reason one level down: without this the title is itself a flex item at
   `min-width: auto` and refuses to shrink, so the wrapper's `min-width: 0`
   buys nothing. Ellipsis rather than a wrap, because the header row is a fixed
   height and a wrapped title would be clipped instead. */
.fa-qr-host .site-title {
  min-width: 0;
  overflow: hidden;
}

/* The title row keeps its height while the code is open. Without this the
   host collapses (both children are hidden) and the sidebar nav jumps up. */
.fa-qr-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  line-height: 0;
}

.fa-qr-toggle:hover,
.fa-qr-toggle:focus-visible {
  border-color: var(--link-color, #7253ed);
  outline: none;
}

.fa-qr-toggle svg { width: 1rem; height: 1rem; fill: currentColor; }

/* THE CODE SITS BELOW THE TITLE, IN THE SIDEBAR COLUMN -- it is no longer a
   popover that replaces the title.
   
   Why it could not be one before: the theme caps the header row at
   `height: 3.75rem; max-height: 3.75rem`, so an in-flow code inside the header
   overflows in both directions (a fixed-height flex row with
   `align-items: center` centres the overflow). The previous version worked
   around that by absolutely positioning the code over the title and hiding the
   title and the toggle while it was open.
   
   The fix is placement, not positioning: `mountQr` inserts the panel as a
   SIBLING of `.site-header` inside `.side-bar`, between the header and the
   nav. There it is in normal flow in the sidebar's flex column, it pushes the
   nav down rather than covering it, and the title stays where it is. */
.fa-qr-panel {
  display: none;
  padding: 0.5rem;
  cursor: pointer;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 6px;
  /* Opaque: when pinned it sits over content, and a transparent code on top of
     text is unreadable to a phone camera as well as to a person. */
  background: var(--sidebar-color, #27262b);
}

/* THE SIDEBAR-COLUMN LOOK IS DESKTOP-ONLY.
   
   Below 50rem the panel is a centred floating card (see the narrow-viewport
   section further down) and must keep its full border and its corners. These
   rules are scoped so they cannot reach it there -- an earlier draft applied
   `border-width: 0 0 1px; border-radius: 0` unconditionally and turned the
   phone card into a bordered strip.
   
   The widths are the theme's own: it gives `.site-nav`, `.site-header` and
   `.site-footer` exactly `width: 100%`, and `width: 16.5rem` from 66.5rem up.
   `.side-bar` is `flex-flow: column nowrap; align-items: flex-end` at this
   breakpoint, so a child with no explicit width shrinks to its content and
   hangs off the right edge instead of filling the column.
   
   `flex: 0 0 auto` because `.site-nav` is the `1 1 auto` child that absorbs
   the leftover height -- the code must not compete with it for space. */
@media (min-width: 50rem) {
  .fa-qr-panel.fa-qr-in-sidebar {
    width: 100%;
    flex: 0 0 auto;
    margin: 0;
    border-width: 0 0 1px;
    border-radius: 0;
  }
}

@media (min-width: 66.5rem) {
  .fa-qr-panel.fa-qr-in-sidebar { width: 16.5rem; }
}

/* ── The code survives a full-width figure ─────────────────────────────── */

/* WHEN A FIGURE IS EXPANDED TO THE FULL DISPLAY WIDTH, IT COVERS THE SIDEBAR.
   
   That is painting order, not a z-index someone forgot: the theme sets
   `.side-bar { z-index: 0 }`, which makes it a stacking context painted at 0,
   while `.main` is `position: relative` with `z-index: auto` and comes later
   in tree order -- so `.main`, and the full-bleed figure inside it, paints on
   top. Anything in the sidebar disappears behind the diagram, and no z-index
   ON the code can help while it is inside that stacking context.
   
   So two things, and BOTH are needed:
   
   1. `z-index: auto` on the sidebar removes the stacking context, letting a
      descendant's z-index reach the root. The sidebar itself keeps painting
      exactly where it did (positioned, auto, before `.main` in tree order),
      so nothing else moves.
   2. The code goes `position: fixed` and pins itself to the sidebar column's
      geometry -- 3.75rem is the theme's exact `.site-header` height, and the
      widths are its exact sidebar widths. It leaves the flex column, so the
      nav springs back up underneath it.
   
   The result is the requested one: widescreen a diagram, scroll a particular
   piece of narrative into view, and still have the code on screen to point a
   phone at.
   
   `:root.fa-has-fullwidth` is set by docs-ui.js from the DOM whenever any
   figure is in full-width mode, and recomputed rather than counted so it
   cannot drift.
   
   Note this is driven by the FULL-WIDTH CONTROL, not by a viewport
   breakpoint. If pinning should instead happen at some screen width, the
   change is the media query on these two rules -- the mechanism is the same. */
@media (min-width: 50rem) {
  /* WAS `z-index: auto`, AND THAT BROKE THE NAV ON EVERY PAGE WITH A WIDE
     FIGURE -- bean `vfr8`, owner's screenshot of `/guides/document-ingestion.html`.

     Removing the stacking context also removed the sidebar's own ability to
     paint above `.main`, and it did so IN EVERY STATE rather than only where
     the conflict was. The open-nav rule below already says what that looks
     like, in as many words: "Without this the sidebar opens BEHIND the page
     content and reads as a rendering bug rather than as a nav." It loses the
     cascade to this rule -- `:root.fa-has-fullwidth .side-bar` is (0,3,0)
     against `.side-bar:hover`'s (0,2,0) -- so on such a page the nav opened
     behind the page whether or not anything was actually in full-width mode.

     MEASURED on `/guides/document-ingestion.html`, which auto-expands 4 of its 5
     figures: with the nav open, `elementFromPoint` inside the opened column
     returned page content (`SPAN.fa-qa-glyph`, `P`, `A`) at x=120, and the
     nav only within the original 56px strip.

     AND `auto` DID NOT ACHIEVE ITS OWN PURPOSE. Synthesising the beneficiary
     these rules describe -- a `.fa-qr-panel.fa-qr-in-sidebar[data-open]` on a
     full-width page -- the panel was NOT visible at its own centre under
     `auto`: `DIV.main-content-wrap` painted over it, despite the panel's
     `z-index: 1200`. Under `100` it is visible. So this value is better than
     `auto` on BOTH counts, not a trade.

     100 rather than something larger because it is measured as sufficient:
     100, 1300 and 10001 all give an identical, correct hit-test, and 100 is
     the number the open-nav rule below already uses. A second, larger
     constant would be two answers to one question.

     ONE THING A READER SHOULD KNOW: nothing in this repository sets
     `fa-qr-in-sidebar`. It occurs 4 times, all in this file. The three rules
     keyed on it cannot currently match, so the escape they describe is not
     reachable today -- which is why the only live effect of the old value was
     the defect. The rules are left alone rather than removed: whether that
     class is still wanted is the owner's call, not this fix's. */
  :root.fa-has-fullwidth .side-bar { z-index: 100; }

  :root.fa-has-fullwidth .fa-qr-panel.fa-qr-in-sidebar[data-open="true"] {
    position: fixed;
    top: 3.75rem;
    left: 0;
    width: 15.5rem;
    z-index: 1200;
    /* Back to a real card: it is floating over a drawing now, not sitting in
       a column, so it needs its own edge again. */
    border-width: 1px;
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.45);
  }
}

@media (min-width: 66.5rem) {
  :root.fa-has-fullwidth .fa-qr-panel.fa-qr-in-sidebar[data-open="true"] {
    width: 16.5rem;
  }
}

/* The panel is opaque on purpose — it sits over navigation links, and a
   transparent code on top of text is unreadable to a phone camera as well as
   to a person. Which opaque colour depends on the scheme. */
:root[data-fa-scheme="light"] .fa-qr-panel {
  background: #fff;
  color: #27262b;
}

/* Keyed on the PANEL, not on the host. The panel lives in the sidebar column
   now, so it is not a descendant of the host and a
   `.fa-qr-host[data-open] .fa-qr-panel` selector matches nothing. `mountQr`
   mirrors the attribute onto both.
   
   The rules that hid `.site-title` and `.fa-qr-toggle` while the code was open
   are GONE: they existed only because the code was covering the slot those two
   occupy, and the whole point of the new placement is that it does not. */
.fa-qr-panel[data-open="true"] { display: block; }

.fa-qr-panel svg {
  display: block;
  width: 100%;
  max-width: 13rem;
  height: auto;
  border-radius: 4px;
}

.fa-qr-caption {
  display: block;
  margin-top: 0.35rem;
  max-width: 13rem;
  font-size: 0.65rem;
  line-height: 1.3;
  opacity: 0.75;
  overflow-wrap: anywhere;
  text-align: left;
}

/* ── Figure controls ───────────────────────────────────────────────────── */

.fa-figure-tools {
  display: flex;
  flex-wrap: wrap; /* eight controls since the export group (#1270); a phone wraps them */
  align-items: center;
  gap: 0.3rem;
  margin: 0.6em 0 0.2em;
  font-size: 0.75rem;
}

.fa-figure-tools button {
  white-space: nowrap;
  min-width: 1.9rem;
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
  line-height: 1.4;
}

.fa-figure-tools button:hover,
.fa-figure-tools button:focus-visible {
  border-color: var(--link-color, #7253ed);
  outline: none;
}

.fa-figure-tools button[aria-pressed="true"] {
  border-color: var(--link-color, #7253ed);
  box-shadow: inset 0 0 0 1px var(--link-color, #7253ed);
}

/* The export group (SVG, PNG, Copy) starts a little apart from the view
   controls: one acts on the page, the other on a file. */
.fa-figure-tools .fa-figure-export-start {
  margin-left: 0.6rem;
}

.fa-figure-tools .fa-figure-status {
  opacity: 0.8;
}

.fa-figure-tools .fa-zoom-level {
  min-width: 3.2rem;
  text-align: center;
  opacity: 0.8;
  font-variant-numeric: tabular-nums;
}

/* Zoom multiplies whatever width the figure already had.
 *
 * Two things here are deliberate and were both wrong in the first version.
 *
 * The selector is a DESCENDANT one (` svg`), not a child one (` > svg`).
 * Mermaid renders into a wrapper div, so the figure's direct child is that
 * div and a child selector matched nothing -- the zoom percentage changed
 * and the drawing did not move.
 *
 * And the declarations are !important, which is not laziness. Mermaid writes
 * `style="max-width: NNNpx"` inline onto the <svg> it generates, and an
 * inline style beats any stylesheet rule. Without overriding it the width
 * here is computed and then ignored. `height: auto` with the SVG's own
 * viewBox is what makes it scale rather than stretch. */
.fa-figure-scope img,
.fa-figure-scope svg,
.fa-figure-scope object {
  width: calc(var(--fa-zoom, 1) * 100%) !important;
  min-width: calc(var(--fa-zoom, 1) * 1200px);
  max-width: none !important;
  height: auto !important;
}

/* A non-BPMN figure has no 1200px floor to scale -- it is whatever size it is.
 *
 * And 100% is the drawing's OWN width, capped at the column (bean `n7f8`):
 * `--fa-natural` is set by `mountFigure` from the viewBox, or from an image's
 * natural width. Without it a small diagram was stretched to the full column,
 * text and all. Where no natural width is known the fallback is the column,
 * which is what every plain figure got before. */
.fa-figure-scope.fa-plain img,
.fa-figure-scope.fa-plain svg,
.fa-figure-scope.fa-plain object {
  min-width: 0;
  width: calc(var(--fa-zoom, 1) * min(100%, var(--fa-natural, 100%))) !important;
}

/* The figure takes keyboard focus so it can be panned without a drag
 * (bean `n7f8`, `board-windows` §"The floor"); focus has to be seen. */
.fa-figure-scope:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

/* The wrapper this script puts around a non-BPMN figure.
 *
 * IT IS WHITE IN BOTH COLOUR SCHEMES, deliberately, and this rule has now been
 * wrong in both directions in one session — worth recording which is which.
 *
 * A Mermaid diagram used to render in Mermaid's LIGHT palette against the dark
 * page with no backdrop at all: pale nodes glaring, connector lines close to
 * invisible. The first fix painted a white card behind it and did nothing,
 * because the wrapper was attached via `.mermaid`, a class just-the-docs does
 * not emit. The second fix dropped the card and set Mermaid's DARK theme
 * instead — which worked as designed and was worse: dark-on-dark, the
 * diagram's own near-black backdrop dissolving into the page.
 *
 * The settled answer (owner's call) is that a schematic is a FIGURE and
 * figures are printed on white, whatever the surrounding page is doing. So the
 * card is back, Mermaid is pinned to its light palette
 * (site-chrome/_includes/mermaid_config.js), and these now get the same treatment as
 * the BPMN exports in `.bpmn-figure` — which carry their own white backdrop
 * and could never have followed a scheme anyway. One treatment for both beats
 * two that each look right in only one mode.
 *
 * The padding is not decoration: without it the drawing's outermost strokes
 * sit flush against the card's edge. */
.fa-figure-wrap {
  overflow-x: auto;
  border: 1px solid rgba(128, 128, 128, 0.35);
  border-radius: 6px;
  margin: 1em 0 0.4em;
}

/* THE WHITE IS ON `.fa-figure-scope`, WHICH IS THE ONE CLASS BOTH KINDS GET.
 *
 * `mountFigure` adds it to every figure it takes over -- the `.bpmn-figure`
 * elements authored in the markdown AND the `.fa-figure-wrap` this script
 * builds around a Mermaid diagram or a loose SVG. Putting the rule here means
 * the two cannot drift: previously the BPMN white came from head_custom.html
 * and the Mermaid white (when it existed at all) from a separate rule here,
 * which is how one of them ended up dark while the other stayed light.
 *
 * `!important` is aimed at head_custom.html's own `.bpmn-figure` background,
 * which would otherwise win by source order in the built page depending on
 * which stylesheet the theme emits last. The value it overrides is the same
 * white, so this is about which rule is AUTHORITATIVE, not about changing it.
 *
 * It applies in full width too -- `.is-fullwidth` only changes geometry. A
 * figure that went transparent when widened would show the dark page through
 * a drawing that is dark-on-light, which is the black-on-black failure again
 * with an extra click in front of it. */
.fa-figure-scope {
  background: #fff !important;
  padding: 0.5rem;
}

/* THE SAME WHITE, WITHOUT JAVASCRIPT — belt and braces for the rule above.
 *
 * Owner, 2026-09-21, with a screenshot of a Mermaid graph on the dark site:
 * *"all drawings/figures need white backgroun in dark mode"*. The nodes were
 * legible (Mermaid's light palette, as pinned in `mermaid_config.js`) and the
 * EDGES were not: thin grey connectors and arrowheads on a near-black page, so
 * the boxes read fine and the graph's structure did not. A diagram whose
 * arrows are invisible is worse than one that is hard to read, because it
 * still looks finished.
 *
 * The white card above should have prevented it, and it is applied by
 * `mountFigures()` — which wraps a diagram only after MEASURING it, and skips
 * anything whose `getBoundingClientRect()` is under 240px in both axes. That
 * guard is right for its purpose (it keeps a toolbar off every inline icon)
 * and it is a guard a diagram can fail transiently, before layout settles, on
 * a path that does not retry it.
 *
 * So the same white is stated here too, declaratively. It needs no script, no
 * measurement and no mutation observer, and it cannot be lost to a timing
 * window. The two agree on the value on purpose; the JS rule additionally
 * carries the scroll container and the zoom controls, which CSS cannot.
 *
 * SCOPED TO MERMAID'S OWN CONTAINERS, and that is the whole of the caution. A
 * blanket `img[src$=".svg"] { background: #fff }` would put a white plate
 * behind every inline icon, which is precisely what the 240px guard exists to
 * avoid — CSS cannot measure, so it must not try to make that judgement. A
 * `.language-mermaid` element is always a figure.
 *
 * WHITE IN BOTH SCHEMES, not only in dark, because that is the settled rule
 * recorded above: a schematic is a figure and figures are printed on white,
 * whatever the surrounding page is doing. Dark mode is where the failure is
 * VISIBLE, not where the rule changes.
 *
 * NOT REPRODUCED against the live page — litlfred.github.io is refused at this
 * environment's proxy — so this is a fix for a mechanism that can fail rather
 * than for a failure that was traced. The JS path is left exactly as it is. */
.main-content .language-mermaid,
.main-content pre.mermaid,
.main-content .mermaid {
  background: #fff;
  border-radius: 6px;
  padding: 0.5rem;
}

/* Full-bleed. The width and offset are set by JS from the element's measured
   position, NOT by the usual `margin-left: calc(-1 * (100vw - 100%) / 2)`
   trick. That trick assumes the element is CENTRED in the viewport; here it
   sits in a content column offset to the right by the sidebar, so the
   calculation pulled it too far left and ran it too far right -- overshooting
   by roughly the sidebar width at each edge. Measuring is exact and needs no
   assumption about the theme's geometry at all. */
.fa-figure-scope.is-fullwidth {
  max-width: none;
  border-radius: 0;
  /* The JS sets `width` to the viewport's client width. Under content-box the
   * card's padding and border were added on top, so every full-width figure
   * ran 18px past the right edge of the window and was clipped there (found by
   * the n7f8 e2e spec, which measures it). */
  box-sizing: border-box;
}

/* ── Narrow viewports ──────────────────────────────────────────────────── */

/* 799.98px, NOT 800px, and the two-hundredths matter.
 *
 * just-the-docs' own `md` breakpoint is `@media (min-width: 50rem)` — 800px —
 * so at EXACTLY 800px the theme is already in its desktop layout. A
 * `max-width: 800px` query here overlaps it by one pixel and applies mobile
 * rules on top of a desktop sidebar. Mirroring the theme means stopping just
 * short of where it starts. */
@media (max-width: 799.98px) {
  .fa-figure-tools { flex-wrap: wrap; }

  /* THE QR GOES FIXED ON MOBILE, because below this breakpoint the thing it
   * was anchored to stops being fixed itself.
   *
   * `.side-bar` is `position: fixed` only inside the theme's `mq(md)` block
   * (its _sass/layout.scss). Under 800px it is an ordinary in-flow element at
   * the top of the document, so an absolutely-positioned panel inside it
   * scrolls away with the header — which is exactly the report: on a phone the
   * code does not stay put when you scroll. On desktop the same rule was
   * always correct, because there the sidebar does not move.
   *
   * Fixed positioning is safe here rather than merely hopeful: a transformed,
   * filtered or perspective ancestor would capture `fixed` and turn it back
   * into scroll-away behaviour, and there is no `transform` anywhere in the
   * theme's layout.scss. `.side-bar { z-index: 0 }` does not interfere either
   * — at this width the element is `static`, so `z-index` does not apply and
   * it creates no stacking context for the panel to be trapped inside.
   *
   * Centred rather than pinned to the left edge: the panel is wider here than
   * the header slot it grew out of, and a code held up to be scanned wants to
   * be in the middle of the screen, not tucked into a corner. */
  .fa-qr-panel {
    position: fixed;
    /* Plain value first: a browser without env() treats the max() line as
       invalid and DROPS it, which would otherwise leave `top` at the base
       rule's 0 and tuck the code under a notch. */
    /* Below the fixed top bar (language, ▾ Folio), which sat over the code's top
       rows at 390px (bean 5rmf, measured 2026-10-12). */
    top: 3rem;
    top: calc(3rem + env(safe-area-inset-top, 0px));
    left: 50%;
    transform: translateX(-50%);
    /* Bigger than the desktop 14rem: on a phone this is usually being read by
       someone else's camera, and scan reliability is mostly module size. */
    max-width: min(78vw, 20rem);
    /* Above the theme's own header and nav, which sit at low z-indexes. */
    z-index: 1000;
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.45);
  }

  .fa-qr-panel svg, .fa-qr-caption { max-width: 100%; }
}

/* ── Build stamp ───────────────────────────────────────────────────────── */

/* Quiet by default — it is diagnostic, not content — but selectable and
   copyable, which is the whole point when someone needs to say which build
   they were looking at. */
.fa-build-stamp {
  opacity: 0.6;
  font-size: 0.7rem;
}

.fa-build-stamp code {
  font-size: inherit;
  padding: 0 0.2em;
}

/* ── Drag-to-pan, and the links inside a diagram ───────────────────────── */

/* THE AFFORDANCE IS THE WHOLE FEATURE HERE.
 *
 * Panning a zoomed diagram by dragging is not discoverable — there is no
 * scrollbar thumb to aim at once the drawing is wider than the card, and
 * nothing on screen says the surface is grabbable. The cursor is the only
 * signal, so it is not decoration: without it the behaviour exists and nobody
 * finds it.
 *
 * `grab`/`grabbing` rather than `move`: `move` reads as "this element can be
 * relocated", which is the opposite of what happens — the drawing stays put
 * and the viewport moves over it.
 *
 * Gated on `.is-pannable`, which the JS sets only when the figure actually
 * overflows its card. A grab cursor over a figure that fits promises a drag
 * that does nothing, and then the cursor is the only thing that was wrong. */
.fa-figure-scope.is-pannable {
  cursor: grab;
}

.fa-figure-scope.is-panning {
  cursor: grabbing;
  /* Without this a drag across a label selects the text instead of panning,
     and the selection highlight follows the pointer. The JS also suppresses
     the click that ends a drag; this is the visual half of the same fix. */
  user-select: none;
  -webkit-user-select: none;
}

/* A subprocess box in a BPMN export is a real link now (render-bpmn.ts wraps
 * the call-activity shape in an <a>), but an SVG <a> inherits none of the
 * browser's link chrome — no colour change, no pointer, nothing. Say so. */
.fa-subprocess-link {
  cursor: pointer;
}

.fa-subprocess-link:hover rect,
.fa-subprocess-link:focus-visible rect {
  stroke-width: 2.5px;
}

.fa-subprocess-link:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

/* ── Full-width content on wide screens ────────────────────────────────── */

/* THE THEME CENTRES A FIXED-WIDTH COLUMN; THIS MAKES IT FILL INSTEAD.
 *
 * Measured from the built stylesheet on gh-pages (assets/css/
 * just-the-docs-default.css), not assumed, because the rule that does the
 * centring is not the obvious one:
 *
 *   @media (min-width: 50rem)   .main            { max-width: 50rem }
 *   @media (min-width: 50rem)   .side-bar + .main{ margin-left: 15.5rem }
 *   @media (min-width: 66.5rem) .side-bar        { width: calc((100% - 66.5rem) / 2 + 16.5rem); min-width: 16.5rem }
 *   @media (min-width: 66.5rem) .side-bar + .main{ margin-left: Max(16.5rem, calc((100% - 66.5rem) / 2 + 16.5rem)) }
 *
 * 66.5rem is 16.5 (nav) + 50 (content). Above that width the SIDEBAR grows to
 * absorb half the surplus, which is what pushes the text column into the
 * middle of the screen — so capping `.main` alone would leave the content
 * floating, and widening `.main` alone would leave it overlapping the nav.
 * Both halves are needed.
 *
 * Below 66.5rem only the cap is removed: the theme's 15.5rem sidebar there is
 * already flush left and there is no surplus to absorb, so nothing else should
 * move.
 *
 * Equal specificity to the theme's own rules; docs-ui.css is emitted after
 * just-the-docs-*.css in the built page, so source order decides and no
 * `!important` is needed. If that emission order ever changes these rules go
 * silently dead — the symptom is a narrow column returning on a wide monitor,
 * not a broken layout.
 *
 * Long lines are the accepted cost. The request was explicit: text should fill
 * the screen width with the nav anchored left. On an ultrawide the measure
 * gets long; a cap can be reintroduced as a `max-width` on `.main-content`
 * if that turns out to matter more than the fill. */
@media (min-width: 50rem) {
  .main { max-width: none; }
}

@media (min-width: 66.5rem) {
  .side-bar { width: 16.5rem; }
  .side-bar + .main { margin-left: 16.5rem; }
}

/* ── The sidebar rests as an icon strip, on every page of this site ────── */

/* Owner, 2026-09-21, looking at the round this replaces:
 *
 *   "clicking it away completelt disappeared in who-iris. i expected the same
 *    behaviour as was in who-iris, that it slides to the far left, icon width
 *    thick."
 *
 * CLOSED IS NOT GONE, and reading it that way cost a round. Asked to "have it
 * start hidden", I removed the strip entirely and put a launcher on the glass.
 * `hidden` meant CLOSED -- slid left to an icon strip -- and the strip is both
 * the resting state and the affordance: there is no separate launcher, because
 * the nav is always on screen to be reached.
 *
 * Three ways in, one way back:
 *   - `:hover` opens it while the pointer is on it;
 *   - `:focus-within` opens it for a keyboard;
 *   - the `☰` checks a box and it STAYS open, which is what a tablet needs --
 *     it is above this breakpoint and has no hover at all;
 *   - the `[x]` unchecks it and the sidebar slides back to the strip.
 *
 * IT OVERLAYS WHEN OPEN. `.main`'s margin stays at the strip's width. A
 * sidebar that pushed the text column right on hover would move every line the
 * reader was looking at on the way to a link.
 *
 * `align-items: flex-start` is not cosmetic and is easy to miss: the theme
 * sets `flex-end` on this flex column, so with a narrow parent and wide
 * children the clip would show the RIGHT-HAND END of every label rather than
 * its start.
 *
 * ATTACHED TO THE GLASS. `.side-bar` is `position: fixed` already, so the page
 * scrolls underneath it. What it lacked is an INNER scroll: a nav taller than
 * the window had items nothing could reach and no scrollbar to say so.
 *
 * Scoped to 50rem and up -- below it the theme is not `fixed` and has its own
 * hamburger, and applying this there breaks the phone layout.
 *
 * MEASURED LIMIT: written against the selectors an earlier session read off
 * the BUILT stylesheet, recorded above. The rendered result is not checked
 * here; litlfred.github.io is refused at this environment's proxy. */
/* `--fa-nav-collapsed` and `--fa-nav-open` ARE NOT DEFINED HERE any more.
 *
 * They were, and `navbar.ts` defined them again — 56px against 40px at rest,
 * 248px against 232px open, on two navbars the owner had asked to be
 * "presented same way" (`sjic`). Both sides had a test asserting its own copy,
 * so both were green while the widths disagreed.
 *
 * They now come from `assets/css/navbar-geometry.css`, GENERATED from
 * `scripts/lib/navbar-geometry.ts` by `bun run navbar:geometry` and gated by
 * `navbar:geometry:check`. That module carries the derivation this comment
 * used to — the theme's gutter, `.fa-site-mark` at 2rem, a matching right
 * gutter — plus the reason these numbers are the ones the RAIL adopted rather
 * than the other way round: only this side has a `min-width` floor in the
 * theme's own layout.scss, and a floor is not something a `width` can cross.
 *
 * Every `var(--fa-nav-*)` USE below is still authored here. Only the two
 * definitions moved, which is what keeps this file hand-written.
 */

@media (min-width: 50rem) {
  .side-bar {
    width: var(--fa-nav-collapsed);
    /* MIN-WIDTH BEATS WIDTH, and the theme sets one this never answered.
     *
     * `layout.scss` carries `.side-bar { min-width: 16.5rem }` inside its own
     * `mq(lg)` (66.5rem). Overriding `width` alone therefore collapsed the
     * sidebar at 820-1063px and did NOTHING at 1064px and up, where the
     * cascade is irrelevant because a `min-width` floor is not something a
     * later `width` can cross.
     *
     * MEASURED 2026-09-22, by rendering the built site at six viewports
     * rather than by reading this file:
     *
     *   viewport 1063 -> width 56px,  min-width 0px     collapsed
     *   viewport 1064 -> width 264px, min-width 264px   NOT collapsed
     *
     * And it was worse than a control that did not work, because
     * `.side-bar + .main` still reserves `--fa-nav-collapsed`: the sidebar
     * sat 264px wide over content that began at 56px, so its own
     * `border-right` was painted ACROSS the page. The owner reported that as
     * "there is stray border when collasped" — the border is the theme's and
     * is correct; what was wrong is that the sidebar was over the page.
     */
    min-width: 0;
    align-items: flex-start;
    /* THE THREE REGIONS, and this is where they stop being a convention.
     *
     * `navbar.ts` has had them since it was extracted: `.fa-nav-top` at
     * `0 0 auto`, `.fa-nav-graphs` at `1 1 auto; min-height:0; overflow-y:auto`
     * and `.fa-nav-bottom` at `0 0 auto`. THIS side had `overflow-y: auto` on
     * `.side-bar` itself -- ONE scroll box -- so the harness tabs scrolled off
     * the bottom with everything else. The owner's layout is explicit that they
     * must not: *"KG libraries is a scrollable stacks between fixed top an
     * bottom parts of navbar menu/tab."*
     *
     * `hidden`, not `auto`: the column no longer has anything to scroll, and
     * leaving `auto` here gives a second scrollbar that appears only when a
     * rounding error makes the column one pixel too tall. */
    overflow: hidden;
    /* AT REST NO TEXT. Read by every text region below; see the block that
     * uses it for why this is one flag rather than a list of selectors. It is
     * declared HERE, inside the breakpoint, so that `var(--fa-nav-text, 1)`
     * means "this rule never ran" and resolves to visible — the safe
     * direction, and the only one a silently-invalid custom property leaves
     * open. Below 50rem the theme owns the sidebar and nothing is hidden. */
    --fa-nav-text: 0;
  }

  /* The theme's own children ARE the regions, so nothing is re-ordered and
   * `sidebar.html` is not overridden -- the same refusal this file already
   * makes for the `☰`'s placement.
   *
   * Written as "every child is fixed, then `.site-nav` absorbs" rather than as
   * three selectors, because the theme resolves through `remote_theme` and is
   * not in this checkout: the count of `.side-bar`'s children is a fact about
   * a gem, and a rule that names each one by hand is a rule that breaks on the
   * next theme bump. `0 0 auto` for an unknown fourth child is the safe
   * default -- it keeps its own height, which is what it had. */
  .side-bar > * { flex: 0 0 auto; }
  /* THE MIDDLE, when the folder block is mounted: one scroll over both.
   *
   * `mountInstanceGraphs` moves `.site-nav` inside this wrapper, so the rule
   * below stops matching and this one takes over. Both are kept — if the
   * script does not run, `.site-nav` is still a direct child and still the
   * middle. A single selector could not express that.
   *
   * The owner's layout is one scrollable stack, not two: *"KG libraries is a
   * scrollable stacks between fixed top an bottom parts of navbar
   * menu/tab."* */

  /* FOLDED, THE FOLDERS HEADING STAYS IN SIGHT. It comes after the page list,
   * which is the ruling's order and the rail's, and the page list runs to
   * thousands of pixels -- so in flow the heading would sit at the very end
   * of the one scroller, where nobody finds it. Pinned to the scroller's
   * bottom edge while folded, it is always one row away; opened, it returns
   * to flow and `mountSidebarRail` brings it to the top. `sticky`, so it is
   * still IN the one scroll region rather than a fixed region of its own.
   *
   * `background: inherit` down the chain from `.side-bar`, which the theme
   * paints opaque, so rows scrolling under the heading do not show through. */

  /* ▦ Harnesses is the LAST row of the scroller and the lowest pinned
   * heading (the viewer rail's order: graphs, then harnesses, then home), so
   * a folded Folders heading stands on top of it rather than under it. The
   * height is measured by `mountSidebarRail`, because ▦'s row is one height
   * in the strip and another in the open bar. */
  
  /* AT THE BOTTOM EVEN WHEN THE LIST IS SHORT. `sticky` holds a heading at
   * the bottom edge only when the content above it overflows; on a short
   * page it sat mid-column, and widening the strip re-flows the icon column
   * above it into a row -- so ▦ jumped ~130px up between a click's mousedown
   * and its mouseup, and the click opened nothing (`rail-tips.e2e.ts`).
   * The middle's bottom edge does not move when the strip opens, so a
   * heading pushed against it stays under the pointer, as it did in the
   * footer. */

  /* Inside the wrapper the nav scrolls with the folders rather than apart from
     them, so it must NOT keep its own. */

  .side-bar > .site-nav {
    /* The middle absorbs, and it must be allowed to shrink far below its
     * content: a flex child defaults to `min-height: auto`, which refuses to,
     * so the column grows past the viewport and the fixed bottom is pushed off
     * the screen -- taking the harnesses and home with it. `navbar.ts` carries
     * the same sentence over its own middle region; it is here because the
     * failure is identical, not because it was copied.
     *
     * BUT NOT TO NOTHING, and that floor was measured rather than guessed.
     * The other two regions are capped, and with the document index open the
     * three caps add up: 60px of header, 33% of index and 50% of footer left
     * the middle **93px** in a 900px column -- one and a half rows of a
     * navigation that is the whole point of the navbar. A cap on a sibling is
     * a claim on this region, and `8rem` is the smallest height at which this
     * still reads as a list rather than as a clipped row. Both siblings yield
     * to it below (`flex: 0 1 auto`), which they can afford because each
     * scrolls inside its own cap. */
    flex: 1 1 auto;
    min-height: 8rem;
    overflow-y: auto;
    overflow-x: hidden;
  }

  /* Children keep their OPEN width and are clipped. Letting them inherit
     3.5rem instead wraps every label to about one character per line, and the
     reflow back on open is a visible re-layout of the whole nav. */
  .side-bar > * { width: var(--fa-nav-open); }

  .side-bar:hover,
  .side-bar:focus-within,
  .side-bar:has(.fa-nav-open:checked) {
    width: var(--fa-nav-open);
    /* Above `.main`, which is positioned and later in tree order. Without this
       the sidebar opens BEHIND the page content and reads as a rendering bug
       rather than as a nav. */
    z-index: 100;
  }

  .side-bar + .main { margin-left: var(--fa-nav-collapsed); }

  /* ON DESKTOP, OPENING THE RAIL SHRINKS CONTENT (bean `ei4q`).
   * Owner, 2026-10-06: "on desktop open up the LHS navbar will shrink content width so they dont overlap".
   * Opening the rail pushes the content column beside it whether open via hover,
   * keyboard focus, or pinned checkbox, so nothing is hidden under the rail. */
  .side-bar:hover + .main,
  .side-bar:focus-within + .main,
  .side-bar:has(.fa-nav-open:checked) + .main {
    margin-left: var(--fa-nav-open);
  }

  /* AT REST THE STRIP CARRIES MARKS AND NOTHING ELSE.
   *
   * Owner, 2026-09-23, with a screenshot of the strip: *"looks horrible when
   * collapsed. should only be icons/avatars so compat."* What they were
   * looking at was "ON" — `On this page`, clipped mid-word — over six folder
   * names bleeding out of a 3.5rem column.
   *
   * ## Why it regressed, and why this is a FLAG rather than a longer list
   *
   * The rule this replaces named three things to hide, by hand. The round that
   * added the document index and the folder block added two more text regions
   * and did not extend it, so they were never hidden — the defect was in the
   * enumeration, not in either new region. A list that has to be extended by
   * whoever adds a region is a list that will not be.
   *
   * So the state is a single inherited flag, `--fa-nav-text`, set ONCE on the
   * open selectors and read by every text region. Adding a region is then one
   * line in one place, and the three open conditions stay stated once instead
   * of being multiplied across the list (they were 9 selectors for 3 regions;
   * 8 regions would have been 24).
   *
   * `var(--fa-nav-text, 1)` — the fallback is VISIBLE, and it is deliberate.
   * An undefined custom property inside `var()` is invalid at computed-value
   * time and fails SILENTLY, which has cost this file three debugging sessions
   * already. If the flag ever goes missing the navbar shows too much, which a
   * reader can work with; the other direction is a navbar that renders blank.
   *
   * WHAT STAYS: `.fa-harness-tab__mark`, which is why `.fa-harness-tabs` is no
   * longer hidden wholesale — the owner asked for avatars in the strip, and
   * hiding the group took its avatars with its labels. Only the group's
   * heading, its labels and its per-harness graph lists go.
   *
   * `opacity`, not `display: none` — the labels stay in the accessibility
   * tree, which is the previous rule's reason and is unchanged.
   */
  .side-bar:hover,
  .side-bar:focus-within,
  .side-bar:has(.fa-nav-open:checked) {
    --fa-nav-text: 1;
  }
  .side-bar .fa-site-title,
  .side-bar .site-nav,
  .side-bar .fa-harness-tabs__heading,
  .side-bar .fa-harness-tab__label,
  .side-bar .fa-harness-tab__graphs,
  /* The SAME regions, named as `lib/navbar.ts` renders them (`sjic`). One
   * set of declarations, two vocabularies — not a second rule free to
   * disagree about when the strip is a strip. */
  .side-bar .fa-nav-group > summary,
  .side-bar .fa-nav-label,
  .side-bar .fa-nav-kids,
  .side-bar .fa-nav-home__label {
    opacity: var(--fa-nav-text, 1);
    transition: opacity .12s ease;
  }

  /* INVISIBLE IS NOT ABSENT, and in the bottom stack that difference was
   * visible as a defect rather than as a subtlety.
   *
   * `opacity: 0` keeps a box. With the labels and the per-harness graph lists
   * merely transparent, the five harness rows were 60, 465, 166... pixels tall
   * — measured in a browser — so their AVATARS, which are what the strip is
   * for, sat in a column with 80 to 150px of nothing between them. The owner
   * asked for *"icons/avatars so compat"*; a compact column is the half that
   * opacity alone cannot give.
   *
   * THE SAME ONE FLAG, so the open state is still stated once. `max-height`
   * rather than `height`, because these boxes have no height to restate and
   * `calc()` cannot produce `auto`. `200vh` is HEADROOM, not a measurement: it
   * has to exceed the tallest of these regions when open, and the tallest
   * measured here is 465px in a 900px viewport. A list that ever exceeded it
   * would be clipped rather than mis-laid, and its own parent scrolls.
   *
   * `.fa-site-title` and `.site-nav` are deliberately NOT in this list. The
   * title's box is the theme's header and the nav's is the middle region's
   * flex floor; collapsing either changes a layout this rule has no business
   * changing, and neither leaves a gap between marks.
   */
  /* ▦ HARNESSES IS A MARK AT REST, like every other mark in the strip.
   *
   * Owner's ruling on bean `ob3m` finding 1, 2026-10-01, option 1 of 4:
   * *"Make ▦ Harnesses visible on the landing page too."* Measured before it
   * on #1762's head, 1280x800: the two lists above gave this summary
   * `opacity: 0` AND `max-height: 0` at rest, so the strip showed six icons
   * and no ▦ — reaching a harness took a hover to widen the strip and then a
   * click. The viewer rail (`lib/navbar.ts`) never hid it, so the same control
   * was one tap away on a generated page and two on this one.
   *
   * ONLY THE HARNESSES GROUP, by position: it is the one disclosure in the
   * fixed bottom. Any other `.fa-nav-group` keeps the lists' rest state.
   *
   * WHAT STILL HIDES AT REST: the word (`.fa-nav-label`, in the lists above)
   * and the `▸` disclosure caret, which in a 56px strip beside a glyph is a
   * second mark that names nothing. Both come back from the same one flag, so
   * the open summary is exactly what it was. The gutter is scaled the same
   * way, so at rest the ▦ sits in the mark column — `--fa-nav-pad` from the
   * edge, like ⌂ below it and every icon above — and open it sits where the
   * caret needs it. ONE CLICK on it opens the group, and the click's focus
   * holds the strip open (`:focus-within`), so the harness list is the
   * first thing that click shows.
   *
   * TWO PLACES, ONE GROUP. Without script it is in the footer. With script
   * `mountSidebarRail` (ob3m finding 7, #1808) moves it into the one scroller
   * as `.fa-nav-harness-group`, beside Graphs, and the rule follows it there:
   * the move must not take back the mark this ruling put in the strip. */
  .side-bar .fa-nav-bottom > .fa-nav-group > summary {
    opacity: 1;
    max-height: none;
    min-height: var(--fa-nav-rest-min);
    padding-left: calc(var(--fa-nav-pad) + var(--fa-nav-text, 1) * (0.5rem - var(--fa-nav-pad)));
    gap: calc(var(--fa-nav-text, 1) * 0.375rem);
  }
  .side-bar .fa-nav-bottom > .fa-nav-group > summary::before {
    display: inline-block;
    overflow: hidden;
    max-width: calc(var(--fa-nav-text, 1) * 2em);
    opacity: var(--fa-nav-text, 1);
  }

  /* A CLOSED DISCLOSURE MUST NOT YIELD, because it has nothing to give.
   *
   * Owner, 2026-09-23, with a screenshot: *"on this page is cut off"*. It was
   * — measured at a 500px viewport, the region was **22px** tall around a
   * summary of **25**, so the row read as `ON THIS PAG` with its descenders
   * sliced off. Fine at 600 and above, which is why it had not been seen.
   *
   * `.fa-doc-index` is `flex: 0 1 auto`, and its own rule says why it may
   * shrink: *"it scrolls inside its cap."* That is true while it is OPEN. A
   * CLOSED `<details>` has nothing to scroll, so every pixel the flex layout
   * takes from it is a pixel of the one row it still has — and the reader
   * cannot scroll to recover it or even tell that it happened.
   *
   * So the yield is conditioned on the thing that made it safe. `.fa-nav-folders`
   * needs no rule here: it is already `flex: 0 0 auto` and never shrank, which
   * is why the owner's screenshot shows `FOLDERS 24` intact directly beneath a
   * clipped `ON THIS PAGE 8`. */

  .side-bar .fa-harness-tabs__heading,
  .side-bar .fa-harness-tab__label,
  .side-bar .fa-harness-tab__graphs,
  /* The SAME regions, named as `lib/navbar.ts` renders them (`sjic`). One
   * set of declarations, two vocabularies — not a second rule free to
   * disagree about when the strip is a strip. */
  .side-bar .fa-nav-group > summary,
  .side-bar .fa-nav-label,
  .side-bar .fa-nav-kids,
  .side-bar .fa-nav-home__label {
    max-height: calc(var(--fa-nav-text, 1) * 200vh);
    /* `max-height` caps the CONTENT box, and `min-height` outranks it outright.
     * Measured at rest with only the cap in place: the document index still
     * held 17px of padding and border, and the harnesses' heading held its
     * whole 36px floor plus the group's padding — 70px of nothing in the one
     * column this rule exists to compact.
     *
     * SCALED BY THE SAME FLAG, and by each region's OWN published metric
     * (`--fa-nav-rest-pad`, `--fa-nav-rest-min`), which its base rule declares
     * once and uses itself. So the open state keeps exactly what the base rule
     * gives it and no number is written here at all. A region that publishes
     * neither falls back to `0px` and is unaffected. */
    padding-block: calc(var(--fa-nav-text, 1) * var(--fa-nav-rest-pad, 0px));
    min-height: calc(var(--fa-nav-text, 1) * var(--fa-nav-rest-min, 0px));
    overflow: hidden;
  }

  /* THE MIDDLE DOES NOT GROW WHILE IT IS EMPTY, and this was the measurement
   * that mattered most. At rest every text region above is invisible, yet the
   * middle's `flex-grow: 1` still claimed 422px of a 900px column — so the
   * fixed bottom shrank to 104px and showed ONE of five harness avatars. The
   * avatars are what the owner asked the strip to be.
   *
   * The same flag again, read as a NUMBER rather than as a length: grow 0 at
   * rest, grow 1 open. `min-height` likewise falls to zero, since the 8rem
   * floor is a claim about a list a reader can read, and at rest there is
   * none. Both are restored by the same hover/focus/pin that restores the
   * text, so the open navbar is unchanged.
   *
   * AFTER the two rules that set `flex: 1 1 auto` on these, which is why this
   * block is here rather than beside them. */
  
  .side-bar > .site-nav {
    /* `flex-basis: 0`, and it is the whole fix. Measured at rest: the middle
     * held 444px of a 900px column and the fixed bottom was squeezed to 104,
     * showing ONE of five harness avatars — with every one of the middle's own
     * text regions invisible.
     *
     * `flex: 1 1 auto` means "start from my own content height". This page's
     * `.site-nav` is 1008px of links, so the column overflowed and the browser
     * shrank EVERY region proportionally, the fixed bottom included. Starting
     * from zero makes the middle take the spare space and only the spare
     * space, which is what a region between two fixed ones is for — and it is
     * what keeps the harnesses anchored to the bottom, the owner's word for
     * where they go.
     *
     * NOT flag-scaled, unlike everything else here: this is right in both
     * states. Collapsing the middle to nothing at rest was the first attempt
     * and it floated the bottom region up to meet the icons — anchored to the
     * bottom is a property of the OPEN navbar too.
     *
     * The floor still is: 8rem while open, because a middle squeezed below
     * that stops reading as a list; nothing at rest, because at rest there is
     * no list to read. */
    flex-basis: 0;
    min-height: calc(var(--fa-nav-text, 1) * 8rem);
  }

  /* THE AVATAR AND THE TITLE START AT THE GUTTER.
   *
   * Owner, same message: *"avatar/text at top needs to be left aligned"*.
   * The theme's `.site-title` carries `padding-left: 32px`, so in a 56px strip
   * a 32px mark began at 32 and was CUT IN HALF by the strip's own edge —
   * measured in a browser, not read off this file.
   *
   * `--fa-nav-pad` is the strip's gutter and is derived rather than chosen:
   * `(3.5rem - 2rem) / 2` in `navbar-geometry.ts`, the same 12px the icon row
   * and every rail row already indent by. So the mark lands centred in the
   * strip at rest and flush with the icons when open, from one number. */
  /* LOGICAL: on an RTL page the mark is at the right, by the strip's outer
     edge, and the theme's padding there cut it in half against the strip
     (measured 2026-10-06: mark 1330-1362px, strip from 1344px). */
  .side-bar .site-title { padding-inline-start: var(--fa-nav-pad); }

  /* THE ICON ROW BECOMES AN ICON COLUMN at rest.
   *
   * Six 36px icons are 232px wide: in a 3.5rem strip five of them are simply
   * off the edge, which is the other half of what the owner was looking at.
   * Wrapping at the strip's width stacks them one per line — the shape
   * `navbar.ts` already gives every rail row, and the shape a reader expects
   * of a vertical rail.
   *
   * It is the ONE child that does not keep its open width. The general rule
   * below gives every child `--fa-nav-open` so labels do not reflow to one
   * character per line on open; here the reflow IS the behaviour, and a row
   * clipped at its second icon is not a row. */
  .side-bar > .fa-nav-icons {
    width: var(--fa-nav-collapsed);
    flex-wrap: wrap;
    /* No `☰` to clear any more (#1757): the avatar above is the control. */
    padding-top: 0.25rem;
  }
  .side-bar:hover > .fa-nav-icons,
  .side-bar:focus-within > .fa-nav-icons,
  .side-bar:has(.fa-nav-open:checked) > .fa-nav-icons {
    width: var(--fa-nav-open);
    flex-wrap: nowrap;
  }
  /* The row keeps clearing the ☰ while the bar is open by hover or focus,
     because the ☰ is still shown then; it only moves up once PINNED, when the
     ☰ is hidden. Lifting it on hover put the ☰ on top of the first icon
     (measured 2026-09-27, once the ☰ was sized as a control). */
  .side-bar:has(.fa-nav-open:checked) > .fa-nav-icons {
    padding-top: 0.25rem;
  }

  /* NO ☰ AND NO [x] ON A THEME PAGE (ob3m finding 8, owner 2026-10-01,
   * option 1 of 4). #1762 removed both from the viewer rail: the avatar is a
   * label for the same checkbox, so it already opens AND closes the bar, and
   * the generated include stopped emitting either. Their rules stayed here,
   * every one inside this 50rem block, so a copy that rendered below 800px
   * rendered unstyled ("☰C@T Harness" loose in the phone footer). Markup and
   * rules are gone together; `sidebar-rail.e2e.ts` fails if either control
   * comes back. */

  /* STAY CLOSED — the second state bit, and the owner's choice of three.
   *
   * *"[x] appears whenever the bar is open. Pressing it sets a remembered
   * 'stay closed' preference that stops the bar peeking on hover, on every
   * page in that browser."*
   *
   * ## It re-asserts the rest state rather than guarding every open rule
   *
   * The bar opens through several rules — its width, the `--fa-nav-text` flag,
   * the icon row's width and wrap. Adding a `:not([data-fa-nav="closed"])` to
   * each would be four places to keep in step and one to forget. This block
   * instead re-states the CLOSED geometry at a specificity that wins, in one
   * place, so a new open rule is covered by whichever of these it fights.
   *
   * ## `:focus-within` IS DELIBERATELY NOT SUPPRESSED
   *
   * A keyboard reader tabs into `.site-nav` whether or not the bar looks open.
   * Suppressing focus-opening would leave them moving through links they
   * cannot see — a trap, and a worse defect than the one this fixes. Only the
   * POINTER peek is withheld, which is the thing a reader asked to stop.
   *
   * The pinned checkbox is not suppressed either: the avatar pinning the bar
   * open is how the preference is lifted, and a control that did nothing would be the `l4zi` defect — an
   * action whose inverse is not reachable is not a toggle. */
  :root[data-fa-nav="closed"] .side-bar:hover:not(:focus-within):not(:has(.fa-nav-open:checked)) {
    width: var(--fa-nav-collapsed);
    --fa-nav-text: 0;
  }
  :root[data-fa-nav="closed"] .side-bar:hover:not(:focus-within):not(:has(.fa-nav-open:checked)) > .fa-nav-icons {
    width: var(--fa-nav-collapsed);
    flex-wrap: wrap;
    padding-top: 2rem;
  }
}

/* The control itself is never seen -- the two labels are the interface. A real
   checkbox rather than a styled div so it is focusable and announces its own
   state, and off-screen rather than `display: none`, because a hidden control
   is not focusable and the keyboard would lose the toggle.

   CLIPPED IN PLACE, not parked at `left: -9999px`. On a right-to-left page
   the scrollable overflow runs to the LEFT, so an element 9999px off that
   edge made the Arabic onboarding guide 10,389px wide at a 390px viewport
   (bean `2r2n`, measured 2026-09-24). A clip has no direction to get wrong. */
.fa-nav-open {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The theme's skip link parks itself at `left: -999px` for the same purpose,
   and on a right-to-left page that is the scrollable side. Mirror it, but only
   while it is hidden: on focus the theme brings it on screen. */
[dir="rtl"] a.skip-to-main:not(:focus):not(:active) {
  left: auto;
  right: -999px;
}

@media (min-width: 50rem) and (prefers-reduced-motion: no-preference) {
  .side-bar { transition: width .14s ease; }
  .side-bar + .main { transition: margin-left .14s ease, margin-right .14s ease; }
}

/* BELOW 50rem the phone menu is the theme's own ☰. */
@media not all and (min-width: 50rem) {
  /* FULL WIDTH on a phone. The footer takes `--fa-nav-open`, the laptop
     sidebar's open width, which measured 217px of a 390px menu, so the
     harness rows sat in a narrow column with the page nav above them twice
     as wide. */
  .side-bar .site-footer:has(.fa-nav-in) { width: 100%; }
}

@media print {
  .side-bar { display: none; }
  .side-bar + .main { margin-left: 0; }
}

/* ── Edit-the-source affordance ────────────────────────────────────────── */

/* 130 of the site's 155 pages are GENERATED and say "do not edit here". That
 * sentence tells a reader where not to go and, until now, nothing told them
 * where to go instead -- the correct source path was already in the banner,
 * but as a /blob/ link, which is read-only. These links point at /edit/, which
 * is GitHub's in-browser editor.
 *
 * Styled as an affordance rather than as prose because it is an ACTION sitting
 * inside a sentence that forbids the obvious one. Running "do not edit here"
 * and "edit it there" together as plain text is how a reader ends up editing
 * the generated copy anyway.
 *
 * Deliberately NOT the theme's `.btn`: a button in every one of 130 callouts
 * is louder than the content it sits above. This is a link that looks
 * clickable, which is all it needs to be. */
.fa-edit-source {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  padding: 0.1em 0.45em;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  font-size: 0.85em;
  text-decoration: none;
  white-space: nowrap;
}

.fa-edit-source:hover,
.fa-edit-source:focus-visible {
  border-color: var(--link-color, #7253ed);
  text-decoration: underline;
}

/* Per-node edit link, on a generated page.
 *
 * Quieter than `.fa-edit-source` and floated out of the text flow, because
 * there is one of these per SECTION rather than one per page. An affordance
 * repeated eleven times down a page has to earn its space: it sits to the
 * right of the heading it belongs to and stays out of the reading line.
 *
 * `float` rather than a flex row with the heading: the heading is emitted by
 * kramdown as a plain `<h2>` and the link as a separate `<p>`, so there is no
 * shared parent to make a row out of without generating wrapper markup for
 * every node. Floating costs nothing and degrades to a normal link.
 *
 * Dimmed until hover, on the same reasoning as the build stamp: it is a
 * maintenance affordance, not content, and a reader who is reading should not
 * have to look past eleven pencils. */
.fa-node-edit {
  float: right;
  margin: -2.2rem 0 0;
  padding: 0.05em 0.4em;
  border: 1px solid transparent;
  border-radius: 4px;
  font-size: 0.75rem;
  text-decoration: none;
  opacity: 0.35;
}

/* LIGHT ONLY. `opacity: 0.35` composites the link toward whatever is behind
 * it, so on white it lands at **2.22:1** — measured 2026-09-19, bean `y8cm`,
 * on every `✎ Edit` on the site, not just the one in a sticky. In DARK the
 * same declaration moves the colour AWAY from the background and measures
 * clean, so the base rule above is left exactly as it is: the defect belongs
 * to one scheme, and "fix it in both" would change a rendering that is already
 * correct.
 *
 * Quiet is kept, by a different mechanism. The point of the opacity was that
 * a full-strength link pulled up into a heading's line is noisy — and that is
 * a real concern, so this does not simply delete it. A muted NEUTRAL reads as
 * chrome rather than as body text: it is visibly not one of the page's purple
 * links, which is what an edit affordance should look like, and it measures
 * **6.78:1**.
 *
 * Worth knowing before changing this: the site's own `--link-color` (#7253ed)
 * measures 5.03:1 on white and would also pass. It was rejected because it
 * makes the control read as a content link, not because it was unsafe.
 *
 * The two schemes now differ in MECHANISM — dark mutes by opacity, light by
 * colour. Do not "tidy" that into one: the tidy version is the defect. */
:root[data-fa-scheme="light"] .fa-node-edit {
  opacity: 1;
  color: #5a5a68;
}

@media (prefers-color-scheme: light) {
  :root:not([data-fa-scheme="dark"]) .fa-node-edit {
    opacity: 1;
    color: #5a5a68;
  }
}

.fa-node-edit:hover,
.fa-node-edit:focus-visible {
  opacity: 1;
  border-color: var(--link-color, #7253ed);
}

/* The negative margin pulls it up beside the heading; on a narrow screen the
 * heading wraps and that lands the link on top of the text. Below the theme's
 * md breakpoint it goes back in the flow. */
@media (max-width: 799.98px) {
  .fa-node-edit {
    float: none;
    margin: 0 0 0.5rem;
    opacity: 0.6;
  }
}

/* ── RTL (right-to-left) support ───────────────────────────────────────── */

/* Applied when <html dir="rtl"> or when docs-ui.js detects lang=ar.
   Smooth transition so the navbar slides across when switching LTR↔RTL. */

/* Base transition for all major layout elements */
.side-bar,
.main,
.main-content-wrap,
.main-content,
.site-nav {
  transition: margin 0.4s ease, padding 0.4s ease, left 0.4s ease, right 0.4s ease;
}

/* RTL overrides */
/* `start`, not `right` (bean `giiw`). `text-align` INHERITS, and `right` is a
   physical value, so every English island inside an Arabic page — the
   unverified notice, a badge, anything `docs-ui.js` marks `dir="ltr"` — was
   pushed to the right edge of its own box. `start` resolves against each
   element's own direction: right on the Arabic, left on the English. */
[dir="rtl"] {
  direction: rtl;
  text-align: start;
}

/* Sidebar moves to right in RTL */
[dir="rtl"] .side-bar {
  left: auto;
  right: 0;
  border-left: 1px solid var(--border-color, #44434d);
  border-right: none;
}

/* THE MIRROR OF THE LTR RESERVATION, not a width of its own (bean `giiw`).
   This read `margin-right: 264px` — the theme's OPEN sidebar — long after
   the sidebar came to rest as a `--fa-nav-collapsed` strip. Measured on the
   Arabic home page at 1400px: the strip at x 1344-1400 and the content
   column ending at 1104, a 240px dead band beside the rail that the LTR page
   (column from 88px) does not have. Same breakpoint and the same two
   variables as `.side-bar + .main` above, so the two sides cannot drift. */
@media (min-width: 50rem) {
  [dir="rtl"] .side-bar + .main {
    margin-left: 0;
    margin-right: var(--fa-nav-collapsed);
  }
  [dir="rtl"] .side-bar:hover + .main,
  [dir="rtl"] .side-bar:focus-within + .main,
  [dir="rtl"] .side-bar:has(.fa-nav-open:checked) + .main {
    margin-right: var(--fa-nav-open);
  }
}

@media print {
  [dir="rtl"] .side-bar + .main { margin-right: 0; }
}

@media (max-width: 799.98px) {
  [dir="rtl"] .main {
    margin-right: 0;
  }
  [dir="rtl"] .side-bar {
    left: auto;
    right: -264px;
    border-left: none;
  }
  /* When sidebar is open on mobile in RTL */
  [dir="rtl"] .side-bar.open {
    right: 0;
  }
}

/* Flip navigation elements */
[dir="rtl"] .site-nav .nav-list > li > a {
  padding-left: 0;
  padding-right: 1em;
}

[dir="rtl"] .site-nav .nav-list > li > a::before {
  left: auto;
  right: 0;
}

/* Badge and icon containers: flex direction stays LTR for mixed content */
[dir="rtl"] .fa-lang-bar,
[dir="rtl"] .fa-lang-coverage-badge,
[dir="rtl"] .fa-qa-badge,
[dir="rtl"] .fa-sweep-badge {
  direction: ltr;
}

/* Mermaid diagrams: always LTR for flowchart arrows */
[dir="rtl"] .mermaid,
[dir="rtl"] .language-mermaid {
  direction: ltr;
  text-align: left;
}

/* Edit button flips to left side in RTL */
[dir="rtl"] .fa-node-edit {
  float: left;
  margin: -1.8em 0 0 0;
}

/* ── Reading preferences ───────────────────────────────────────────────────
 *
 * The gear in the sidebar header, its panel, and the four affordances the
 * panel switches on. Each is a WCAG 2.2 AA criterion this site can actually
 * satisfy; site-chrome/assets/js/docs-ui.js names which.
 *
 * The switches are attributes on <html> (`data-fa-large-type` and friends), so
 * everything below is a plain attribute selector and nothing here depends on
 * the just-the-docs class names staying put.
 *
 * `!important` appears only where just-the-docs' own rule is more specific
 * than an attribute selector on the root can be. That is a real constraint of
 * overriding a compiled Sass theme from outside it, not laziness: the theme
 * sets link colour on `.main-content a` and text size on `body`, and a reader
 * who asked for larger text must get it.
 */

.fa-a11y-panel {
  margin: 0.25rem 0.75rem 0.5rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.35));
  border-radius: 6px;
  background: var(--sidebar-color, rgba(128, 128, 128, 0.08));
  font-size: 0.75rem;
}

.fa-a11y-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  /* WCAG 2.5.8 Target Size (Minimum) is 24x24 CSS px. The row is the target,
     not the checkbox, so the whole label is clickable at comfortable size. */
  min-height: 24px;
  padding: 0.3rem 0;
  cursor: pointer;
}

.fa-a11y-row input { margin-top: 0.15rem; width: 16px; height: 16px; flex: none; }
.fa-a11y-text { display: flex; flex-direction: column; line-height: 1.3; }
.fa-a11y-label { font-weight: 600; }
.fa-a11y-hint { opacity: 0.75; font-size: 0.7rem; }

.fa-a11y-note {
  margin: 0.5rem 0 0;
  font-size: 0.68rem;
  opacity: 0.7;
}

/* 1 — Larger text (WCAG 1.4.4 Resize text).
   One step a reader can take on top of browser zoom, not instead of it. */
:root[data-fa-large-type] .main-content,
:root[data-fa-large-type] .site-nav {
  font-size: 1.15em !important;
  line-height: 1.7 !important;
}
:root[data-fa-large-type] .main-content p,
:root[data-fa-large-type] .main-content li {
  max-width: 68ch;
}

/* 2 — Higher contrast (WCAG 1.4.3) and a focus ring that is actually visible
   (2.4.7). The focus rule is deliberately not gated on the toggle: an
   invisible focus indicator is a defect for everybody, not a preference. */
:root[data-fa-contrast] .main-content {
  color: #111;
}
:root[data-fa-contrast][data-fa-scheme="dark"] .main-content,
:root[data-fa-contrast] .main-content code {
  color: #fff;
}
:root[data-fa-contrast] .main-content a { text-decoration-thickness: 2px; }

:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 2px;
}

/* 3 — Underline links (WCAG 1.4.1 Use of Colour). just-the-docs distinguishes
   a link by hue alone, which is invisible to a reader with a colour-vision
   deficiency. */
:root[data-fa-underline] .main-content a {
  text-decoration: underline !important;
  text-underline-offset: 0.15em;
}

/* 4 — Reduce motion (WCAG 2.3.3). Defaults on when the OS already says so;
   the media query below covers the reader who never opens the panel. */
:root[data-fa-reduce-motion] *,
:root[data-fa-reduce-motion] *::before,
:root[data-fa-reduce-motion] *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}

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

/* ── Disclosure panels in the sidebar column ──────────────────────────────
   
   The QR, reading-preferences and language panels all mount as siblings of
   `.site-header` inside `.side-bar` (see `mountPanelInSidebarColumn`). There
   they are in the sidebar's flex column in NORMAL FLOW — they push the nav
   down rather than covering it, and nothing can clip them.
   
   Stated as one rule because all three had the same bug and two of them had
   two different broken workarounds: the reading-preferences panel sat inside
   the header and was cut off by its height cap, and the language bar was
   `position: absolute; bottom: 100%`, opening upward to dodge that cap and
   running off the top of the column instead — under the staging banner, which
   is where a reader reported it invisible.
   
   `position: static` is the load-bearing declaration. Anything positioned
   escapes the flex column and the clipping comes straight back. */
.fa-panel-in-sidebar {
  position: static;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
}

/* ── The sidebar language bar (bean `rptk`, second half) ──────────────
 *
 * `buildLanguageBar()`'s six locale tabs, shown inside the Language action
 * tile. This is the SIBLING of the per-page bar below, and it carried the same
 * two failing pairs for two days longer, because `rptk` was read as being
 * about `mountPageLanguageBar()` alone and the gate that would have said
 * otherwise never opened this view.
 *
 * Measured 2026-09-21, from the inline literals in `docs-ui.js`, against the
 * `.fa-tiles` panel this bar actually renders on (#27262b):
 *
 *   - a not-yet-translated tab: `color:#475569` with `opacity:0.5`, which
 *     composites to #373e4a = **1.39:1** — three tabs in the gate's
 *     fixture, five on a page with nothing translated. The SAME opacity trap
 *     the per-page bar paid for — and the reason `opacity` on text is not a
 *     dimming control, it is a contrast control nobody measured.
 *   - the current tab: #ffffff on #3b82f6 = **3.67:1**, bold at 12.8px, which
 *     is under SC 1.4.3's 18.66px large-text threshold, so 4.5:1 applies.
 *
 * The other two pairs passed (#93c5fd on the panel at 8.33:1, and the JS
 * hover background at 5.74:1) and are restated as tokens anyway: a pair that
 * passes by accident is one edit from a pair that does not.
 *
 * ONE LITERAL, TWO NUMBERS, and that is the argument for an opaque token in a
 * sentence. `#475569` at `opacity:0.5` is 1.34:1 on the per-page bar's old
 * backdrop and 1.39:1 here, because the composite is a function of whatever
 * is painted underneath. Neither number is a property of anything written in
 * the source, which is why the bean's own note named the wrong colour.
 *
 * TWO PALETTES NOW — AND THE FIRST VERSION OF THIS PARAGRAPH WAS WRONG WITHIN
 * A DAY. It read: *"ONE PALETTE, NOT TWO, and that is a measured claim rather
 * than an omission. `.fa-tiles` paints `var(--sidebar-color, #27262b)` … and
 * there is no light-scheme override for it, so this bar's backdrop is the same
 * dark panel in both schemes. A light block here would be six tokens that
 * never apply, which reads as coverage and is not."*
 *
 * The measurement was right; the CONCLUSION was contingent on a defect. Bean
 * `23bc` gave `.fa-tiles` the light override it had always been missing, and
 * the moment that landed every ratio below became a number about a backdrop
 * this bar no longer sits on in light mode.
 *
 * **A claim justified by another rule's absence has that rule's lifetime, not
 * its own.** That is worth more than the six tokens, and it is why this
 * paragraph REPLACES the old one rather than being appended under it — a
 * superseded claim left in place is one the next reader may act on.
 *
 * `--fa-sidelang-bg` is OPAQUE and is painted by the bar itself in every one
 * of its three contexts, so each ratio below is a fact about these tokens
 * rather than about whatever happens to sit behind them. It is set to the
 * tiles panel's own colour, so inside the tile the result is pixel-identical
 * to the `background: transparent` it replaces.
 */
.fa-lang-bar {
  --fa-sidelang-bg: #27262b;
  --fa-sidelang-fg: #e6e9ee;           /* 12.34:1 — any bar text              */
  --fa-sidelang-link: #9ec5fe;         /*  8.49:1 — a locale you can switch to */
  --fa-sidelang-muted: #9aa4b2;        /*  5.95:1 — not yet translated         */
  --fa-sidelang-current-bg: #60a5fa;   /*  5.91:1 as a surface on the bar       */
  --fa-sidelang-on-current: #0f172a;   /*  7.02:1 on --fa-sidelang-current-bg   */
  --fa-sidelang-hover-bg: #35343b;     /*  6.96:1 for --fa-sidelang-link on it  */

  background: var(--fa-sidelang-bg);
  color: var(--fa-sidelang-fg);
}

/* The language bar keeps its own card look; only its POSITIONING changed. */
.fa-lang-bar.fa-panel-in-sidebar {
  border: 1px solid var(--border-color, #475569);
  border-radius: 6px;
  padding: 4px 6px;
  /* Wrap rather than `white-space: nowrap`: six locale tabs do not fit the
     16.5rem sidebar on one line, and an overflowing row is the clipping this
     whole block exists to remove. */
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
}
.fa-lang-bar.fa-panel-in-sidebar[data-open="false"] { display: none; }

/* Fallback — no `.side-bar` of the expected shape. The panel stays anchored
   to the host, so it needs its own card and a stacking position. Opens
   DOWNWARD: upward was the workaround that failed. */
.fa-lang-bar:not(.fa-panel-in-sidebar) {
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 4px;
  border: 1px solid #475569;
  border-radius: 6px;
  padding: 4px 6px;
  z-index: 10000;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  white-space: nowrap;
}

/* LIGHT scheme, against `.fa-tiles`'s `#e8eaed`. The same six values the
   per-page bar uses, and that is not a coincidence — its light backdrop is the
   same colour, so deriving a different set would be two answers to one
   question. Re-measured rather than copied on faith. */
:root[data-fa-scheme="light"] .fa-lang-bar {
  --fa-sidelang-bg: #e8eaed;
  --fa-sidelang-fg: #1f2933;           /* 12.24:1 */
  --fa-sidelang-link: #1d4ed8;         /*  5.56:1 */
  --fa-sidelang-muted: #5a6472;        /*  4.98:1 */
  --fa-sidelang-current-bg: #1d4ed8;   /*  5.56:1 as a surface on the bar */
  --fa-sidelang-on-current: #ffffff;   /*  6.70:1 on --fa-sidelang-current-bg */
  --fa-sidelang-hover-bg: #d6dae0;     /*  4.78:1 for --fa-sidelang-link on it */
}

.fa-lang-tab {
  display: inline-block;
  padding: 4px 8px;
  border-radius: 4px;
  text-decoration: none;
  font-size: 0.8rem;
  margin: 0 1px;
  transition: background 0.15s;
  color: var(--fa-sidelang-link);
  cursor: pointer;
}

/* Not yet translated. Dimmer by its own measured token, NEVER by `opacity`:
   opacity blends the text toward the bar and takes the ratio with it, which is
   exactly how this tab reached 1.37:1. */
.fa-lang-tab.is-unavailable {
  color: var(--fa-sidelang-muted);
  cursor: default;
}

.fa-lang-tab.is-current {
  background: var(--fa-sidelang-current-bg);
  color: var(--fa-sidelang-on-current);
  font-weight: bold;
}

/* The locale this reader saved. A ring rather than a colour swap, so it cannot
   move any of the text pairs above off their measured numbers. */
.fa-lang-tab.is-remembered {
  box-shadow: inset 0 0 0 1px var(--fa-sidelang-link);
}

/* Hover was a pair of JS listeners writing `style.background` — an inline
   colour literal with extra steps, and one nothing could measure, since it
   existed only while a pointer was over the tab. Only a real link hovers, so
   the muted spans keep their background and their ratio. */
a.fa-lang-tab:hover:not(.is-current) {
  background: var(--fa-sidelang-hover-bg);
}

/* ── Per-page language bar (bean `rptk`) ─────────────────────────────────
 *
 * The row of six locale tabs `mountPageLanguageBar()` puts under the first
 * `<h1>` of `.main-content`. It is ALWAYS VISIBLE — no disclosure in front of
 * it — so every pair below is on screen for every reader on every page.
 *
 * Its colours were inline style literals in `docs-ui.js` until 2026-09-19, and
 * two pairs were under the WCAG 1.4.3 floor of 4.5:1. Measured by axe-core in
 * `test/a11y.e2e.ts`:
 *
 *   - a not-yet-translated tab: #cbd1d9 on #f0f0f0 = **1.34:1**, five tabs.
 *   - the current tab: #ffffff on #3b82f6 = **3.67:1**, bold at 12.8px. Bold
 *     does not make this large text — SC 1.4.3's exception starts at 18.66px
 *     bold — so 4.5:1 applies.
 *
 * TWO THINGS ABOUT THAT FIRST PAIR ARE WORTH KEEPING, because neither colour
 * in it was written anywhere.
 *
 * `#cbd1d9` is not a literal in the source: the tab was `color:#94a3b8` with
 * `opacity:0.4`, and 0.4 of #94a3b8 over the bar's background is #cbd1d9. That
 * is the trap `skills/ui/ui-core/ui-accessibility.md` names by name — opacity
 * blends text toward its background and quietly drops the ratio — and it is
 * why the bean's own note misattributed the failure to the available-locale
 * colour. There is no `opacity` on text here any more; a dimmer tab is its own
 * token with its own measured number.
 *
 * `#f0f0f0` was not written either: the bar's background was
 * `rgba(128,128,128,0.12)`, which is #f0f0f0 only because the page behind it
 * happened to be white. A translucent background makes the ratio a property of
 * whatever the theme paints underneath, so the number below would be a claim
 * about one page rather than about this bar. `--fa-langbar-bg` is OPAQUE, and
 * every ratio in this block is therefore a fact about these tokens alone.
 *
 * Dark is the default here, as everywhere else in this file, and light is the
 * `:root[data-fa-scheme="light"]` override that `docs-ui.js` sets. Ratios
 * computed per WCAG relative luminance and confirmed by axe.
 */
.fa-page-lang-bar {
  /* DARK scheme. Each ratio is against --fa-langbar-bg unless stated. */
  --fa-langbar-bg: #232a33;
  --fa-langbar-fg: #e6e9ee;            /* 11.90:1 — the globe and any bar text */
  --fa-langbar-link: #9ec5fe;          /*  8.18:1 — a locale you can switch to */
  --fa-langbar-muted: #9aa4b2;         /*  5.74:1 — not yet translated          */
  --fa-langbar-current-bg: #60a5fa;    /*  5.69:1 as a surface on the bar       */
  --fa-langbar-on-current: #0f172a;    /*  7.02:1 on --fa-langbar-current-bg    */
  --fa-langbar-hover-bg: #333c48;      /*  6.31:1 for --fa-langbar-link on it   */

  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 0.3em 0 0.8em;
  padding: 5px 10px;
  border-radius: 6px;
  font-size: 0.82rem;
  background: var(--fa-langbar-bg);
  color: var(--fa-langbar-fg);
}

:root[data-fa-scheme="light"] .fa-page-lang-bar {
  /* LIGHT scheme. Same six tokens, re-measured — a colour written for one
     scheme and never rechecked in the other is how this bar failed. */
  --fa-langbar-bg: #e8eaed;
  --fa-langbar-fg: #1f2933;            /* 12.24:1 */
  --fa-langbar-link: #1d4ed8;          /*  5.56:1 */
  --fa-langbar-muted: #5a6472;         /*  4.98:1 */
  --fa-langbar-current-bg: #1d4ed8;    /*  5.56:1 as a surface on the bar */
  --fa-langbar-on-current: #ffffff;    /*  6.70:1 on --fa-langbar-current-bg */
  --fa-langbar-hover-bg: #d6dae0;      /*  4.78:1 for --fa-langbar-link on it */
}

.fa-page-lang-globe {
  font-size: 1.1em;
  margin-right: 2px;
}

/* IN THE BAND, A DISCLOSURE — issue #2201. The bar is the band's
   inline-start item: the field's height, no margin of its own (the band
   spaces it), and the six tabs shown only while it is open. The toggle names
   the page's language so a closed bar still says which one you are reading. */
.fa-glass-band .fa-page-lang-bar {
  box-sizing: border-box;
  min-height: 2.25rem;
  margin: 0;
  padding: 0 4px;
  flex-wrap: wrap;
}
/* IN A SLOT THE PAGE OFFERS — owner, 2026-10-15: "move the language picker
   into the IG top bar". A page's own bar marks `data-fa-slot="locale"` beside
   its search box and `docs-ui.js` puts the bar there (`data-fa-slotted`). It
   sits in someone else's row, so it takes the row's line and no margin; its
   tokens above are unchanged, so every ratio still holds on its own chip. An
   empty slot (no translation block, so no picker) takes no room. */
[data-fa-slot="locale"]:empty { display: none; }
[data-fa-slot="locale"] { display: inline-flex; align-items: center; }
.fa-page-lang-bar[data-fa-slotted] {
  box-sizing: border-box;
  min-height: 2rem;
  margin: 0;
  padding: 0 4px;
  flex-wrap: wrap;
  line-height: 1.2;
}
/* The host row's own link rule (an IG bar writes `.ig-topbar a { color:
   #fff }`) outranks `.fa-page-lang-tab`, and white on the light scheme's chip
   is 1.2:1. These put the measured tokens back. */
.fa-page-lang-bar[data-fa-slotted] a.fa-page-lang-tab { color: var(--fa-langbar-link); text-decoration: none; }
.fa-page-lang-bar[data-fa-slotted] a.fa-page-lang-tab.is-current { color: var(--fa-langbar-on-current); }
.fa-page-lang-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: 2rem;
  padding: 0 6px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.fa-page-lang-toggle:hover { background: var(--fa-langbar-hover-bg); }
.fa-page-lang-toggle:focus-visible {
  outline: 2px solid var(--fa-langbar-link);
  outline-offset: 1px;
}
.fa-page-lang-current { font-weight: bold; }
.fa-page-lang-caret { font-size: 0.8em; }
.fa-page-lang-list { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 2px; }

/* CLOSED UNTIL THE GLOBE IS PRESSED, AT EVERY WIDTH — owner, 2026-10-05:
   *"make globe click open and closed the desktop view of the locale
   selector. start closed too."* This replaces #2210's split, where the six
   tabs were always shown above 40rem and the toggle was not drawn there.

   WIDE: the toggle is the globe alone; pressed, the six tabs open inline
   beside it in the band, one click each. NARROW (40rem and below): the
   toggle also names the page's language and carries the caret, the phone
   dropdown #2210 kept. While the tabs are open the Folio handle steps aside
   (`data-fa-band-active`), so they never meet it. */
.fa-page-lang-bar[data-open="false"] .fa-page-lang-list { display: none; }
@media (min-width: 40.0625rem) {
  .fa-page-lang-current,
  .fa-page-lang-caret { display: none; }
}

.fa-page-lang-tab {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 3px;
  text-decoration: none;
  font-size: 0.8rem;
  transition: background 0.15s;
  color: var(--fa-langbar-link);
  cursor: pointer;
}

/* Not yet translated: a span, not a link, and dimmer — but dimmer by its own
   measured token rather than by `opacity`, which would blend it into the bar
   and take the ratio with it. */
.fa-page-lang-tab.is-unavailable {
  color: var(--fa-langbar-muted);
  cursor: default;
}

.fa-page-lang-tab.is-current {
  background: var(--fa-langbar-current-bg);
  color: var(--fa-langbar-on-current);
  font-weight: bold;
}

/* The locale this reader saved. A ring rather than a colour swap: it must not
   move any of the text pairs above off their measured numbers. */
.fa-page-lang-tab.is-remembered {
  box-shadow: inset 0 0 0 1px var(--fa-langbar-link);
}

/* Hover was a JS `style.background` write, which is the same inline-literal
   problem one step removed. Only a real link hovers, so the muted tabs keep
   their background and their ratio. */
a.fa-page-lang-tab:hover:not(.is-current) {
  background: var(--fa-langbar-hover-bg);
}

/* ── Per-block QA badge (bean g6yr) ─────────────────────────────────
 *
 * Rides beside the edit pencil, inside the same floated row, because both are
 * per-block affordances about the same block. It is NOT dimmed the way the
 * pencil is: the pencil is a maintenance affordance a reader should be able to
 * look past, while a failing QA verdict is the one thing on the row a reader
 * might want to notice.
 *
 * Colour is not the only channel. Each state carries its own mark — ✕ fail,
 * ! warn, ✓ pass, and nothing at all for unswept — so the badge survives
 * monochrome, a colour-vision deficiency, and the theme's dark mode, and the
 * `title` carries the counts in words. That is deliberate rather than
 * defensive: a status icon whose only difference is hue is unreadable to a
 * sizable fraction of readers.
 *
 * The marks replace `● ◐ ○ ·`, which shipped in #274 and was reported
 * unreadable: filled-vs-open circles encode a scale without saying which end is
 * good, and at this size `●` and `·` differ only in size.
 *
 * `unswept` is a real state, shown rather than omitted. A missing icon and a
 * clean one look identical, and only one of them is true. */
/* The row stays IN FLOW, directly under the heading, while `.fa-node-edit`
 * floats up beside it.
 *
 * Floating this too was tried and collides: both are floated right with the
 * same -2.2rem pull, but a row of chips is taller than a one-line link, so the
 * boxes overlap and the second family's icon lands under the word "Edit" —
 * visible in the first screenshot of this panel. In flow it cannot collide with
 * anything, it needs no breakpoint override, and it is in the same place on a
 * phone as on a desktop.
 *
 * One container, not a float per chip: floated chips reverse their visual order
 * (the first float lands rightmost), so `QA TR KG` would read backwards against
 * the order the generator emits them in. */
.fa-qa-badges {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  margin: 0.1em 0 0.2em;
}

.fa-qa-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
  /* 2.2em is a deliberate floor, not a look: these are the smallest targets on
   * the page and the reader this was built for types with difficulty. */
  min-height: 2.2em;
  padding: 0.1em 0.45em;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  font-family: inherit;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  color: inherit;
}

/* A span cannot be opened; a button can. `help` on something clickable is a
 * lie about what pressing it does. */
span.fa-qa-badge { cursor: help; }
button.fa-qa-badge { cursor: pointer; }

button.fa-qa-badge:hover,
button.fa-qa-badge:focus-visible {
  border-color: currentcolor;
  background: rgba(128, 128, 128, 0.14);
}

button.fa-qa-badge[aria-expanded="true"] {
  border-color: currentcolor;
  background: rgba(128, 128, 128, 0.22);
}

button.fa-qa-badge[aria-busy="true"] { opacity: 0.6; }

/* The family tag rides INSIDE the icon. Four unlabelled glyphs are a legend
 * nobody has, and an icon distinguished only by hue fails the same readers the
 * state glyphs exist for. */
.fa-qa-tag {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  opacity: 0.85;
}

.fa-qa-glyph { font-size: 0.8rem; }

.fa-qa-fail { color: #b3261e; opacity: 1; }
.fa-qa-warn { color: #8a6100; opacity: 1; }
.fa-qa-pass { color: #1b5e20; opacity: 0.55; }
 /* No mark, just a dulled tag. A glyph here is a claim about a check nobody
  * ran, and the `·` it replaces read as a very small "pass". */
.fa-qa-unswept { color: currentcolor; opacity: 0.38; }
.fa-qa-unswept .fa-qa-tag { font-weight: 600; }

/* ── The three states a badge reaches by FETCHING its verdict ──────────────
 *
 * The generated page carries no verdict any more (bean `d2kp`): it says which
 * subjects exist and which have been swept, and `paintQaBadges` in
 * `docs-ui.js` fetches the page's verdict index at load. So a badge is in one
 * of three states the markup alone cannot be in, and none of them may be
 * mistaken for `pass`.
 *
 * `pending` is the state the server writes and the only one with motion. It is
 * dulled and carries `…`, so a reader who arrives before the fetch lands — or
 * with JavaScript off — sees "not yet known" rather than a verdict. */
.fa-qa-pending { color: currentcolor; opacity: 0.45; }

/* `empty` — swept, and every criterion came back `n/a`. No mark, for the same
 * reason `unswept` has none. It is deliberately NOT `.fa-qa-unswept`: that one
 * means nobody ever checked, this one means somebody did and nothing applied,
 * and they differ where it counts — this badge OPENS and lists what did not
 * apply, the other is an inert `<span>` with nothing behind it. */
.fa-qa-empty { color: currentcolor; opacity: 0.5; }
.fa-qa-empty .fa-qa-tag { font-weight: 600; }

/* `unknown` — the verdict could not be read. Loud, and closer to a warning
 * than to a pass: a badge that does not know must not look like one that
 * knows the answer is fine. The dotted underline survives monochrome and a
 * colour-blind reader, which hue alone does not. */
.fa-qa-unknown {
  color: #8a6100;
  opacity: 1;
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
}

/* Dark mode: the light-mode reds and greens sit too dark on a dark surface to
 * meet contrast. Guarded the same way the rest of this file guards its dark
 * rules, so an explicitly-light reader is unaffected. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .fa-qa-fail { color: #ff8a80; }
  :root:not([data-theme="light"]) .fa-qa-warn { color: #ffcc66; }
  :root:not([data-theme="light"]) .fa-qa-pass { color: #a5d6a7; }
  :root:not([data-theme="light"]) .fa-qa-unknown { color: #ffcc66; }
}

:root[data-theme="dark"] .fa-qa-fail { color: #ff8a80; }
:root[data-theme="dark"] .fa-qa-warn { color: #ffcc66; }
:root[data-theme="dark"] .fa-qa-pass { color: #a5d6a7; }
:root[data-theme="dark"] .fa-qa-unknown { color: #ffcc66; }

/* ── QA witness panel ──────────────────────────────────────────────────────
 *
 * What opens under an icon: the sidecar's criteria worst-first, each expanding
 * to every witness that ruled on it. Inline and in the page flow rather than a
 * drawer or a modal — full-width rows are the largest targets available, and
 * nothing here can be closed by a mis-click outside it.
 *
 * Three things the colours must NOT be the only carrier of, because they are
 * the whole content of the panel: the verdict (carried as a word — `fail`,
 * `no verdict`), the witness kind (`script` / `agent` / `human`), and
 * freshness (`current` / `STALE` / `currency unknown`). Every chip is
 * labelled; hue only reinforces. */
.fa-qa-panel {
  clear: both;
  margin: 0.6em 0 1.4em;
  padding: 0.8em 1em;
  border: 1px solid rgba(128, 128, 128, 0.4);
  border-left: 4px solid rgba(128, 128, 128, 0.55);
  border-radius: 6px;
  background: rgba(128, 128, 128, 0.06);
  font-size: 0.82rem;
}

.fa-qa-panel:focus-visible { outline: 2px solid currentcolor; outline-offset: 2px; }

.fa-qa-panel-fail { border-left-color: #b3261e; }
.fa-qa-panel-warn { border-left-color: #8a6100; }
.fa-qa-panel-pass { border-left-color: #1b5e20; }
.fa-qa-panel-error { border-left-color: #b3261e; }

.fa-qa-panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5em;
  margin-bottom: 0.6em;
  padding-bottom: 0.5em;
  border-bottom: 1px solid rgba(128, 128, 128, 0.3);
}

.fa-qa-subject { font-family: var(--fa-mono, ui-monospace, monospace); font-size: 0.78rem; }
.fa-qa-counts { opacity: 0.75; font-variant-numeric: tabular-nums; }
.fa-qa-sidecar-link { font-size: 0.72rem; font-family: var(--fa-mono, ui-monospace, monospace); }
.fa-qa-close { margin-left: auto; min-height: 2.2em; padding: 0.1em 0.6em; cursor: pointer; }

.fa-qa-crit-list { margin: 0; padding: 0; list-style: none; }
.fa-qa-crit-item { border-bottom: 1px solid rgba(128, 128, 128, 0.2); }

/* The whole row is the target, not the caret. */
.fa-qa-crit,
.fa-qa-more {
  display: flex;
  align-items: center;
  gap: 0.45em;
  width: 100%;
  min-height: 2.6em;
  padding: 0.3em 0.2em;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.fa-qa-crit:hover,
.fa-qa-crit:focus-visible,
.fa-qa-more:hover,
.fa-qa-more:focus-visible { background: rgba(128, 128, 128, 0.14); }

.fa-qa-more { margin-top: 0.4em; opacity: 0.85; }
.fa-qa-caret { width: 1em; flex: none; opacity: 0.7; }
.fa-qa-crit-id { flex: 1 1 auto; font-size: 0.76rem; word-break: break-word; }
.fa-qa-wcount { flex: none; opacity: 0.7; font-size: 0.72rem; }

.fa-qa-chip {
  flex: none;
  padding: 0.1em 0.45em;
  border: 1px solid currentcolor;
  border-radius: 3px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.fa-qa-res-fail { color: #b3261e; }
.fa-qa-res-warn { color: #8a6100; }
.fa-qa-res-pass { color: #1b5e20; opacity: 0.8; }
.fa-qa-res-unknown { color: #6b21a8; }
.fa-qa-res-n\/a { opacity: 0.55; }
.fa-qa-sev-critical { color: #b3261e; }
.fa-qa-sev-major { color: #8a6100; }
.fa-qa-sev-minor { opacity: 0.7; }
.fa-qa-locale { opacity: 0.85; }

/* Which block a rolled-up criterion belongs to. Dimmer than the verdict chip
   and free to wrap, because a block label is prose-length where a locale is two
   characters -- `white-space: nowrap` on the shared chip rule would push a long
   one off the row instead of letting the line break. */
.fa-qa-block {
  opacity: 0.7;
  white-space: normal;
  max-width: 22em;
  overflow-wrap: anywhere;
}

/* ── The translation badge row ─────────────────────────────────────────────
 *
 * Every colour here was an INLINE LITERAL in `mountTranslationBadges` until
 * bean `n7vv`: a dark-green `Swept 8/289` chip and a dark-brown `2/6 languages`
 * chip, sitting on the page's light main content beside a `TR` chip that had
 * been themed properly. Contrast inside each chip was fine, so this was never
 * the `rptk` failure — it was two of the three controls in one row ignoring the
 * colour scheme, which `mountPageLanguageBar` above was converted away from for
 * exactly this reason and these were left behind.
 *
 * Same shape as that conversion: DARK is the default block, light overrides
 * under `:root[data-fa-scheme="light"]`, and every pair carries the ratio it
 * MEASURES at rather than one somebody remembered.
 *
 * **Every pair clears its threshold, and the thresholds are different.** Text
 * on its own fill is held at 4.5:1 (WCAG AA for body-size text); a border is a
 * shape rather than a glyph, so it is held at 3:1. The dark `idle` border was
 * `#64748b` and measured 2.92:1 — under the bar it was written to meet, which
 * is the kind of miss a comment stating the standard will not catch on its own.
 * `translation-badges.e2e.ts` now asserts both thresholds against what the
 * BROWSER resolves, in both schemes, so a token edited without re-measuring
 * fails rather than drifting.
 *
 * **Colour is reinforcement, never the message.** Each badge already says its
 * state in words — `2/6 languages`, `Swept 8/289`, `QA: not run` — so a reader
 * who cannot separate the three fills loses nothing, which is the same rule the
 * QA glyph set follows two blocks down.
 */
.fa-translation-badges {
  /* DARK scheme. `page` below is just-the-docs' dark main background,
     #27262b, and is what the `-on-page` ratios are measured against. */
  --fa-trbadge-ok-bg: #14532d;
  --fa-trbadge-ok-border: #34d399;     /* 4.74:1 on its bg, 7.81:1 on the page */
  --fa-trbadge-ok-fg: #d1fae5;         /* 8.03:1 on its bg                     */
  --fa-trbadge-partial-bg: #78350f;
  --fa-trbadge-partial-border: #fbbf24; /* 5.43:1 on its bg, 9.00:1 on the page */
  --fa-trbadge-partial-fg: #fef3c7;     /* 8.15:1 on its bg                     */
  --fa-trbadge-idle-bg: #252d38;
  --fa-trbadge-idle-border: #7286a0;   /* 3.73:1 on its bg, 4.03:1 on the page */
  --fa-trbadge-idle-fg: #cbd5e1;       /* 9.36:1 on its bg                     */

  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0.3em 0 0.6em;
  flex-wrap: wrap;
}

:root[data-fa-scheme="light"] .fa-translation-badges {
  /* LIGHT scheme, re-measured against #ffffff. A tinted fill rather than the
     dark one recoloured: a saturated chip that reads as a surface on a dark
     page reads as an alert on a light one. */
  --fa-trbadge-ok-bg: #e7f6ec;
  --fa-trbadge-ok-border: #1b5e20;     /* 7.04:1 on its bg, 7.87:1 on the page */
  --fa-trbadge-ok-fg: #14532d;         /* 8.15:1 on its bg                     */
  --fa-trbadge-partial-bg: #fdf3e2;
  --fa-trbadge-partial-border: #8a6100; /* 5.04:1 on its bg, 5.54:1 on the page */
  --fa-trbadge-partial-fg: #6b3d0a;     /* 8.31:1 on its bg                     */
  --fa-trbadge-idle-bg: #eef1f5;
  --fa-trbadge-idle-border: #64748b;   /* 4.20:1 on its bg, 4.76:1 on the page */
  --fa-trbadge-idle-fg: #3d4654;       /* 8.42:1 on its bg                     */
}

/* The box the coverage and sweep badges share. Neither opens anything, so
   both stay `span`s with `cursor: default` — the same distinction
   `span.fa-qa-badge` draws, where `help` on something clickable would be a
   lie about what pressing it does. */
.fa-translation-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid transparent;
  border-radius: 4px;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  cursor: default;
}

.fa-translation-badge.is-ok {
  background: var(--fa-trbadge-ok-bg);
  border-color: var(--fa-trbadge-ok-border);
  color: var(--fa-trbadge-ok-fg);
}
.fa-translation-badge.is-partial {
  background: var(--fa-trbadge-partial-bg);
  border-color: var(--fa-trbadge-partial-border);
  color: var(--fa-trbadge-partial-fg);
}
.fa-translation-badge.is-idle {
  background: var(--fa-trbadge-idle-bg);
  border-color: var(--fa-trbadge-idle-border);
  color: var(--fa-trbadge-idle-fg);
}

/* THE TRANSLATION DRAWER'S CONTROL — bean `cat-tools-bbnd`. A button, so it
   looks pressable where the badges beside it deliberately do not: the idle
   pair's colours (already measured above, in both schemes) with a pointer and
   an underline-free focus ring. */
.fa-tr-drawer-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--fa-trbadge-idle-border);
  border-radius: 4px;
  background: var(--fa-trbadge-idle-bg);
  color: var(--fa-trbadge-idle-fg);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}
.fa-tr-drawer-toggle:focus-visible { outline: 2px solid currentcolor; outline-offset: 2px; }
.fa-tr-drawer-toggle[aria-expanded="true"] { border-style: solid; border-width: 2px; padding: 1px 7px; }

/* The drawer itself is a `fa-qa-panel` (owner ruling: reuse the QA panel), so
   its box, border and focus ring are that panel's. Only its two sections are
   new: a fact list for the page and a table for the site. */
.fa-tr-drawer h3 { font-size: 0.85rem; margin: 0.8em 0 0.3em; }
.fa-tr-drawer__facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.15em 0.8em; margin: 0; }
.fa-tr-drawer__facts dt { font-weight: 600; }
.fa-tr-drawer__facts dd { margin: 0; }
.fa-tr-drawer__table { border-collapse: collapse; font-size: 0.8rem; }
.fa-tr-drawer__table th, .fa-tr-drawer__table td { padding: 0.2em 0.6em; text-align: start; border-bottom: 1px solid rgba(128, 128, 128, 0.3); }
.fa-tr-drawer__table td[data-fa-tr-officiality="disagree"],
.fa-tr-drawer__table td[data-fa-tr-officiality="unreadable"] { font-weight: 600; }
.fa-tr-drawer__more { margin: 0.6em 0 0; }
/* The text's own colour, underlined: the theme's link blue measured 2.6:1 on
   the dark panel in the drawer's first screenshot (2026-10-20). */
.fa-tr-drawer a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* The unverified-translation notice. It is the `partial` pair at panel size,
   not a fourth palette: it says the same thing the amber badge says, at more
   length, and a second set of oranges would be two answers to one question.
   Tokens resolve from `:root` here because this banner is inserted at the top
   of the main content rather than inside the badge row. */
:root {
  --fa-trwarn-bg: #78350f;
  --fa-trwarn-border: #fbbf24;         /* 5.43:1 on its bg */
  --fa-trwarn-fg: #fef3c7;             /* 8.15:1 on its bg */
}
:root[data-fa-scheme="light"] {
  --fa-trwarn-bg: #fdf3e2;
  --fa-trwarn-border: #8a6100;         /* 5.04:1 on its bg */
  --fa-trwarn-fg: #6b3d0a;             /* 8.31:1 on its bg */
}

/* ONE LINE, opening to the detail. Owner, 2026-09-21: *"should be a slim one
   line ... which then can open to the full trnslation QA report."*

   It was a four-line block above the page title: the warning, the source, and
   two tool names a reader cannot run from a browser. On a translated page
   that is the first thing between the reader and the content they came for,
   on every page, permanently.

   The padding drops from 12px to 6px vertical BECAUSE it is now one line —
   the old value was sized for a paragraph block and reads as a slab around a
   single sentence. The tokens are unchanged: `--fa-trwarn-*` already carries
   its measured pair for both schemes a few hundred lines up. */
.fa-translation-warning {
  background: var(--fa-trwarn-bg);
  border: 1px solid var(--fa-trwarn-border);
  border-radius: 6px;
  padding: 0.35rem 0.75rem;
  margin: 0.6em 0;
  color: var(--fa-trwarn-fg);
  font-size: 0.85rem;
}

/* The summary IS the notice, so it carries the whole line and stays on one
   where there is room. `list-style: none` plus the WebKit pseudo-element:
   Safari ignores the former on a summary and keeps drawing its own triangle,
   which beside the ⚠️ reads as two markers. */
.fa-translation-warning__line {
  cursor: pointer;
  /* Target size: this is a disclosure on an instance whose declared
     interaction profile is low-dexterity, so the line is held at the 24px
     SC 2.5.8 floor even though the text alone is shorter. */
  min-height: 1.5rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  list-style: none;
}

.fa-translation-warning__line::-webkit-details-marker { display: none; }

/* The sentence is ONE flex item, so it can truncate rather than overflow
   (bean `giiw`). As loose text and <strong>s each piece was its own flex item
   at min-content width; on an Arabic page the overflow ran off the LEFT edge
   and took the sentence's START with it. `min-width: 0` lets the item shrink
   below its content, and the ellipsis falls at the logical END because the
   notice carries `dir="ltr"` from `docs-ui.js` — the attribute, not a
   `direction` rule here, so bidi is right even if this sheet fails. Opened,
   the line wraps: the reader asked to see all of it. */
.fa-translation-warning__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.fa-translation-warning[open] .fa-translation-warning__text {
  white-space: normal;
}

/* Never left to the user agent: the notice's fill differs per scheme and the
   default ring is invisible on one of them. */
.fa-translation-warning__line:focus-visible {
  outline: 2px solid currentcolor;
  outline-offset: 2px;
}

.fa-translation-warning__body {
  margin-top: 0.4rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--fa-trwarn-border);
}

.fa-translation-warning__body p { margin: 0 0 0.35rem; }

/* The control that opens the EXISTING QA panel rather than restating it.
   `currentcolor` throughout, so it inherits the notice's per-scheme ink and
   cannot be legible in one scheme and invisible in the other — the `23bc`
   failure, which this file has now paid for three times. */
.fa-translation-warning__report,
.fa-translation-warning__details {
  min-height: 1.75rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid currentcolor;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.fa-translation-warning__report:hover,
.fa-translation-warning__report:focus-visible,
.fa-translation-warning__details:hover,
.fa-translation-warning__details:focus-visible {
  background: rgba(128, 128, 128, 0.18);
  outline: none;
}

.fa-translation-warning__report:focus-visible,
.fa-translation-warning__details:focus-visible {
  outline: 2px solid currentcolor;
  outline-offset: 2px;
}

/* The page-level QA row, before `mountTranslationBadges` lifts it into the
   badge row under the title. It is a row of its own until then, so it carries
   the same top margin the badge row has rather than sitting flush against the
   heading -- and it must still look deliberate if that script never runs. */
.fa-page-qa-badges { margin: 0.3em 0 0.6em; }

.fa-qa-crit-body { padding: 0.2em 0 0.8em 1.45em; }

.fa-qa-witness {
  margin: 0.5em 0;
  padding: 0.5em 0.7em;
  border: 1px solid rgba(128, 128, 128, 0.3);
  border-radius: 5px;
  background: rgba(128, 128, 128, 0.05);
}

.fa-qa-witness-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45em;
  margin-bottom: 0.4em;
}

.fa-qa-witness-id { font-size: 0.74rem; word-break: break-all; }
.fa-qa-witness-ver { opacity: 0.7; font-size: 0.72rem; }

/* A human ruling outranks an agent's, which outranks a script's; the border
 * weight says so at a glance while the chip says so in words. */
.fa-qa-kind-human { border-left: 3px solid #1b5e20; }
.fa-qa-kind-agent { border-left: 3px solid #6b21a8; }
.fa-qa-kind-script { border-left: 3px solid rgba(128, 128, 128, 0.6); }

.fa-qa-fresh-fresh { color: #1b5e20; }
/* Verified on every file, with a derived input left unchecked. Neither green
 * nor alarming: it is a true statement about how much was established. */
.fa-qa-fresh-partial { color: #4a5568; }
/* `stale` and `unknown` are BOTH marked. A verdict whose currency could not be
 * established is not reported as current — that is the whole freshness rule. */
.fa-qa-fresh-stale { color: #b3261e; }
.fa-qa-fresh-unknown { color: #8a6100; }

.fa-qa-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 0.15em 1em;
}

.fa-qa-field { display: flex; gap: 0.4em; align-items: baseline; }
.fa-qa-field-label {
  flex: none;
  min-width: 9.5rem;
  opacity: 0.7;
  font-size: 0.7rem;
  text-transform: lowercase;
}
.fa-qa-field-value { word-break: break-word; }
.fa-qa-mono { font-family: var(--fa-mono, ui-monospace, monospace); font-size: 0.74rem; }

/* An absent value is PRINTED as "not recorded". A blank cell reads as a value
 * the reader failed to notice; this reads as a fact about the sidecar. */
.fa-qa-absent { opacity: 0.65; font-style: italic; }

.fa-qa-changed { margin: 0.3em 0; }
.fa-qa-changed ul { margin: 0.1em 0 0 1.2em; padding: 0; }
.fa-qa-changed li { font-size: 0.75rem; }

.fa-qa-evidence { margin: 0.3em 0 0.6em; }
.fa-qa-evidence pre {
  margin: 0.2em 0;
  padding: 0.4em 0.6em;
  overflow-x: auto;
  border-radius: 4px;
  background: rgba(128, 128, 128, 0.12);
  font-size: 0.72rem;
  white-space: pre-wrap;
}

.fa-qa-notes { margin: 0.4em 0 0; font-size: 0.76rem; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .fa-qa-res-fail,
  :root:not([data-theme="light"]) .fa-qa-sev-critical,
  :root:not([data-theme="light"]) .fa-qa-fresh-stale { color: #ff8a80; }
  :root:not([data-theme="light"]) .fa-qa-res-warn,
  :root:not([data-theme="light"]) .fa-qa-sev-major,
  :root:not([data-theme="light"]) .fa-qa-fresh-unknown { color: #ffcc66; }
  :root:not([data-theme="light"]) .fa-qa-res-pass,
  :root:not([data-theme="light"]) .fa-qa-fresh-fresh { color: #a5d6a7; }
  :root:not([data-theme="light"]) .fa-qa-fresh-partial { color: #b8c2cc; }
  :root:not([data-theme="light"]) .fa-qa-res-unknown { color: #d8b4fe; }
  :root:not([data-theme="light"]) .fa-qa-kind-agent { border-left-color: #d8b4fe; }
  :root:not([data-theme="light"]) .fa-qa-kind-human { border-left-color: #a5d6a7; }
}

:root[data-theme="dark"] .fa-qa-res-fail,
:root[data-theme="dark"] .fa-qa-sev-critical,
:root[data-theme="dark"] .fa-qa-fresh-stale { color: #ff8a80; }
:root[data-theme="dark"] .fa-qa-res-warn,
:root[data-theme="dark"] .fa-qa-sev-major,
:root[data-theme="dark"] .fa-qa-fresh-unknown { color: #ffcc66; }
:root[data-theme="dark"] .fa-qa-res-pass,
:root[data-theme="dark"] .fa-qa-fresh-fresh { color: #a5d6a7; }
:root[data-theme="dark"] .fa-qa-fresh-partial { color: #b8c2cc; }
:root[data-theme="dark"] .fa-qa-res-unknown { color: #d8b4fe; }
:root[data-theme="dark"] .fa-qa-kind-agent { border-left-color: #d8b4fe; }
:root[data-theme="dark"] .fa-qa-kind-human { border-left-color: #a5d6a7; }

/* Below the theme's md breakpoint the field grid collapses to one column so a
 * label and its value stay on one line. */
@media (max-width: 43.99rem) {
  .fa-qa-field-grid { grid-template-columns: 1fr; }
  .fa-qa-field-label { min-width: 8rem; }
}

/* ── The sidebar mark and title ───────────────────────────────────
   just-the-docs draws `site.title` at the top left; `_includes/title.html`
   overrides it to read the instance's own declaration instead (see
   scripts/sync-docs-harness.ts).

   THIS COMMENT USED TO CLAIM the mark "uses `currentColor` for its ring and
   inner `a`, so it follows the sidebar's text colour in either scheme; only
   its ears carry a fixed sage." That was never true. Checked 2026-09-19
   against every commit that has ever touched docs/assets/img/icons/
   (e5c171029, 2d1b22905, c8c58f28b): `currentColor` appears zero times in
   either mark, in all three. Both bake `#6f8b66`.

   It could not have been true anyway, and that is the part worth keeping.
   `title.html` renders the mark as an `<img>`, and an `<img>` is opaque to
   CSS colour — `currentColor` inside an externally-referenced SVG resolves
   against that SVG's own root, not against the page. Making the claim true
   would take INLINING the mark, not editing it.

   So the mark is fixed sage in both schemes. On the dark sidebar that is
   dimmer than the text beside it. Left as-is deliberately: sage on near-black
   still reads, and inlining the SVG is a real change to how the mark is
   served rather than a colour tweak. Recorded here rather than silently
   corrected, because a comment asserting behaviour the asset never had is the
   same "declared, unread, unnoticed" failure as the unconsumed
   `role: "browser-icon"` — see beans/defs/…blv9. */
/* 2rem, not 1.5rem. The mark's ears CROSS the @ ring by design, and the only
   thing separating them is a 3-unit white halo against a 120-unit viewBox —
   0.6 px at 1.5rem, which fuses the two sage shapes into a disc (measured
   2026-09-19 from a screenshot of this element). At 2rem it is 0.8 px, and
   the whiskers go 0.48 px -> 0.64 px. Still thin; visibly better, and it
   changes the ARTWORK not at all, which is the point — the alternative was
   redrawing the glyph, and that lost the ear tips. */
.fa-site-mark {
  width: 2rem;
  height: 2rem;
  vertical-align: -0.32em;
  /* LOGICAL, so the gap is between the mark and the title in either
   * direction. `margin-right` put it on the outer side under dir=rtl, and the
   * title ran into the avatar (0 px apart on the Arabic site, measured
   * 2026-10-06; 6 px on the English one). */
  margin-inline-end: 0.4rem;
  flex: none;
}

/* THE TITLE TEXT READS IN ITS OWN LANGUAGE; THE ROW MIRRORS WITH THE BAR.
 *
 * Owner, 2026-10-06, looking at the Arabic site: *"avatar still
 * cutoff/overlapping text. maybe put avatar to left of text and flow LTR
 * unless title is translated to a RTL (hebrew,arabic) then do it RTL"*.
 *
 * The TEXT does exactly that: `title.html` marks the title with its own
 * language, so the English harness name reads left to right on an Arabic page
 * and only a right-to-left title would turn. The ROW (avatar, then title)
 * still mirrors with the page, because on an RTL page the bar is docked RIGHT
 * and at rest shows only its rightmost 3.5rem: an avatar placed to the left
 * of the text is outside that strip and cannot be seen (measured 2026-10-06 at
 * 1400px: mark at 1148-1180px, strip at 1344-1400px). The overlap and the
 * cut-off were physical sides — this margin, the strip gutter and the narrow
 * screen's title margin, all written for the left — and are logical now. */

/* ── The mark as a MASK — one asset, both schemes ─────────────────────
 *
 * Owner, 2026-09-22: *"I want theme like in avaatars"*, pointing at the @
 * logo. This is that mechanism, unchanged from `avatars.css`: the glyph is a
 * `mask-image` and the colour is `background-color`, so a scheme is a value
 * rather than a second file.
 *
 * IT CLOSES A DEFERRAL THIS FILE ALREADY RECORDED, a few lines up: *"the mark
 * is fixed sage in both schemes … left as-is deliberately … recorded here
 * rather than silently corrected."* It also ends a disagreement nothing had
 * recorded — `cat-harness` declares tone **268** and its mark was sage, two
 * colour vocabularies for one instance, which is the drift `schemas/theme.ts`
 * exists to have ended.
 *
 * THE TWO COLOURS ARE THE AVATAR GENERATOR'S OWN TARGETS, not new ones:
 * `hsl(<tone> 46% 34%)` light and `hsl(<tone> 42% 72%)` dark, from
 * `gen-avatars-css.ts`. Using different numbers here would be a third
 * vocabulary while removing the second.
 *
 * WHY THE SIZE RULE ABOVE STILL MATTERS. 2rem was chosen because the ears
 * cross the ring and only a 3-unit halo separates them — 0.6 px at 1.5rem,
 * which fused them into a disc. The mask keeps that halo as a CUT-OUT, so the
 * measurement is unchanged and so is the size. */
.fa-site-mark--masked {
  display: inline-block;
  background-color: hsl(268 42% 72%);
  -webkit-mask-image: var(--fa-site-mark-src);
  mask-image: var(--fa-site-mark-src);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}
:root[data-fa-scheme="light"] .fa-site-mark--masked {
  background-color: hsl(268 46% 34%);
}
.fa-site-title {
  vertical-align: baseline;
  /* SMALLER — owner, 2026-09-22: *"text on top navbar too big"*.
   *
   * The theme renders the site title at its own `fs-5`, which at 264px wrapped
   * "C@T Harness" onto two lines and then clipped the second against the
   * header's height — visible in the before/after screenshots of this change,
   * and present on `main`. `1rem` fits it on one line at every width the strip
   * opens to, measured rather than picked: the longest instance title here is
   * "folio-assistant-core" at 20 characters, which is what the ellipsis below
   * is for when a longer one arrives.
   *
   * `!important` answers the theme's own, in kind, as this file already does
   * for `.site-footer`'s font-size. */
  overflow: hidden;
  font-size: 1rem !important;
  line-height: 1.25;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* THE THEME AVATAR in the sidebar header — the card art clipped to the cat.
 *
 * The frame is the same `--fa-nav-mark` column the rail's marks use, so the
 * two surfaces line their titles up at the same x. The image is positioned
 * inside it and the four percentages come from the declaration via
 * `sync-docs-harness.ts`; nothing here knows a box. */
.fa-site-mark--avatar {
  position: relative;
  display: inline-block;
  flex: none;
  width: var(--fa-nav-mark);
  height: var(--fa-nav-mark);
  margin-inline-end: 0.4rem;
  overflow: hidden;
  border-radius: 0.25rem;
  vertical-align: -0.32em;
}

.fa-site-mark--avatar img {
  position: absolute;
  max-width: none;
}

/* THE INITIAL — the mark of an instance that declares no art (`title.html`).
 * The owner's "[W]": the title's first letter in a square frame, the same
 * `--fa-nav-mark` column as the avatar so the title lines up either way, and
 * `currentColor` so it follows the scheme with no colour of its own. */
.fa-site-mark--initial {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  box-sizing: border-box;
  width: var(--fa-nav-mark, 2rem);
  height: var(--fa-nav-mark, 2rem);
  margin-inline-end: 0.4rem;
  border: 2px solid currentColor;
  border-radius: 0.25rem;
  font-weight: 700;
  font-size: 1rem;
  line-height: 1;
  vertical-align: -0.32em;
}

/* ── Action tiles ─────────────────────────────────────────────────────── */

/* ONE PANEL, THE SIZE OF THE QR CODE, HOLDING EVERY HEADER ACTION.

   The header had four buttons — theme, reading preferences, language, QR — in
   a row just-the-docs caps at 3.75rem and shares with the site title, and two
   more were wanted (the knowledge-graph viewer, the source). "that navbar is
   getting crowded" was the owner's words. One launcher takes the row from four
   icons to one.

   The panel reuses `.fa-panel-in-sidebar` for its placement, so the reason it
   is not clipped is the same reason the QR code is not: it is a SIBLING of
   `.site-header` inside `.side-bar`'s flex column, in normal flow, pushing the
   nav down rather than covering it. `position: static` is load-bearing there —
   anything positioned escapes the column and the clipping returns. */
.fa-tiles {
  display: none;
  box-sizing: border-box;
  padding: 0.5rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 6px;
  /* Opaque for the same reason the QR panel is: when a full-width figure
     pins it, it sits over body text. */
  background: var(--sidebar-color, #27262b);
}

/* LIGHT MODE — bean `23bc`. Owner: *"class=\"fa-qr-toggle fa-tiles-toggle\"
 * does not respect light mode."*
 *
 * THE TOGGLE WAS NEVER THE DEFECT. It is `background: transparent;
 * color: inherit`, so it follows the sidebar in either scheme. The defect is
 * the one literal above — `var(--sidebar-color, #27262b)` with no light
 * override, so the panel stayed dark on a light page. Everything inside it,
 * `.fa-tile` and `.fa-tiles-back`, is transparent-and-inherit, so one value
 * fixes the panel and its contents together.
 *
 * THE INCOHERENCE WAS ALREADY IN THIS FILE, which is why the report is right
 * even though the element it names is innocent: `.fa-tiles .fa-search-holder`
 * has carried a light override since it was written, painting a WHITE field
 * onto a permanently dark panel. Somebody light-themed the contents of
 * something that never went light.
 *
 * AND THE a11y GATE CANNOT SEE IT. `test/a11y.e2e.ts` drives this harness in
 * both schemes and passed throughout, because a dark panel with light text
 * PASSES contrast. This is scheme incoherence, not a WCAG failure — so the
 * gate's silence was never evidence either way, and the spec added with this
 * change asks the question axe structurally cannot. */
:root[data-fa-scheme="light"] .fa-tiles {
  background: #e8eaed;
  border-color: #767d88;               /* 3.44:1 as a boundary, SC 1.4.11 */
  color: #1f2933;                      /* 12.24:1 — every tile caption    */
}

.fa-tiles[data-open="true"] { display: block; }

/* Two columns inside the sidebar's 16.5rem, which puts a tile at roughly
   7.2rem across — comfortably past the 24px SC 2.5.8 floor in both axes, and
   past it by enough to still be comfortable for a reader with limited hand
   function, which is this instance's declared interaction profile.
   `auto-fit` rather than a fixed `repeat(2, …)` so the phone card and the
   no-sidebar fallback, which are wider, use the room instead of leaving a
   column of dead space. */
.fa-tiles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: 0.4rem;
}

/* A `display` of anything but `none` BEATS the user agent's `[hidden]` rule,
   so an author stylesheet that sets `display: grid` un-hides the element the
   script just hid. Caught by the e2e spec, which found the grid still visible
   behind an open view; without these two lines the panel renders both at once.
   Stated for each, rather than as `[hidden] { display: none }` globally, so it
   cannot reach markup this file does not own. */
.fa-tiles-grid[hidden],
.fa-tiles-view[hidden] { display: none; }

.fa-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-height: 4rem;
  padding: 0.4rem 0.25rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font-size: 0.72rem;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

.fa-tile svg { width: 1.25rem; height: 1.25rem; fill: currentColor; flex: 0 0 auto; }

/* ── The two greys ──────────────────────────────────────────────────────
 *
 * A graph with no viewer and a graph that is read-only both read as inert,
 * and they are different facts. The first is not here at all: `docs-ui.js`
 * skips a tile with no `href`, so it never reaches this stylesheet. The
 * second opens perfectly and refuses an EDIT.
 *
 * So the read-only tile is NOT greyed down, and that is the point. Dimming it
 * would spell it the same way as "nothing to open" — the collapse this exists
 * to prevent — and it would also read as "less important", which materialized
 * content emphatically is not. It gets a DIFFERENT mark instead: a dashed
 * border, the established convention for a boundary you may look across and
 * not cross.
 *
 * The state also reaches a screen reader through the tile's `aria-label`
 * (`docs-ui.js`), never through colour alone, and the dashed border survives
 * forced-colours mode where a background tint would not. */
a.fa-tile[data-fa-readonly] {
  border-style: dashed;
}

/* The same state on the harness viewer list, where it actually occurs today.
 *
 * A link cannot carry a dashed BORDER legibly — it has none — so the mark is a
 * dashed underline plus a word. The word is there because this list is dense
 * and a reader scanning it should not have to notice a line style; the dash
 * carries it for anyone who reads the link itself. Neither is colour. */
a[data-fa-readonly] {
  text-decoration-style: dashed;
}

.fa-readonly-note {
  margin-left: 0.4em;
  font-size: 0.78em;
  opacity: 0.75;
  white-space: nowrap;
}

a.fa-tile[data-fa-readonly]:hover,
a.fa-tile[data-fa-readonly]:focus-visible {
  /* Hover keeps the dash: the tile is still read-only while you point at it,
     and a mark that vanishes on interaction is a mark that is absent exactly
     when somebody is about to act. */
  border-style: dashed;
}

/* Hover and focus are given the SAME treatment deliberately: a reader driving
   this from the keyboard should see exactly what a reader with a mouse sees,
   and the border alone is not enough of a change to notice. */
.fa-tile:hover,
.fa-tile:focus-visible {
  border-color: var(--link-color, #7253ed);
  background: rgba(128, 128, 128, 0.16);
}

/* Never left to the user agent: the tiles sit on an opaque sidebar background
   whose colour differs per scheme, and the default ring is invisible on one of
   them. 2px is the minimum the a11y spec holds. */
.fa-tile:focus-visible,
.fa-tiles-back:focus-visible,
.fa-tiles-title:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

.fa-tile-caption { display: block; }
/* The harness a tile belongs to, APPENDED beside its one name rather than
 * folded into it (bean `ob3m` finding 6). Smaller, so the base label still
 * reads first. The colour is inherited and not faded, so it keeps the tile's
 * contrast in both schemes. */
.fa-tile-qualifier {
  display: block;
  font-size: 0.72em;
  line-height: 1.15;
  overflow-wrap: anywhere;
}

/* ── A tile's view ────────────────────────────────────────────────────── */

.fa-tiles-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

/* 24px is the floor and this is a row control, so it gets the row height the
   a11y skill asks for rather than the minimum that would pass. */
.fa-tiles-back {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 0.4rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  background: transparent;
  color: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}

.fa-tiles-back:hover { border-color: var(--link-color, #7253ed); }

.fa-tiles-title {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  /* The heading takes focus when a view opens, so the panel announces what it
     became. It must not look like something to click. */
  outline-offset: 2px;
}

/* The theme control is a tile inside the settings view rather than a row in
   the grid — the owner put it "under settings" because it is the one control
   here a reader sets once and never touches again. Full width there, since it
   has the view to itself. */
.fa-tiles-view .fa-theme-toggle { width: 100%; margin-bottom: 0.5rem; }

/* Views are already inside the panel's padding; they must not add a card of
   their own, which is what the QR and language panels carried when each was
   its own disclosure. */
.fa-tiles-view .fa-tile-content {
  width: 100%;
  box-sizing: border-box;
}

.fa-tiles-view .fa-qr-panel {
  display: block;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: default;
}

/* Flush inside the tile: no padding, no border, no card of its own. The
   background is NOT reset to `transparent` here any more — `--fa-sidelang-bg`
   is this panel's own colour, so the bar paints the same pixels while giving
   every ratio above a backdrop that is stated rather than inherited. */
.fa-tiles-view .fa-lang-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  position: static;
  padding: 0;
  border: 0;
}

.fa-tiles-view .fa-a11y-panel {
  position: static;
  padding: 0;
  border: 0;
  background: transparent;
}

/* ── The search field, inside the launcher ────────────────────────────── */

/* THE SEARCH LEFT THE MAIN PANEL. The owner: "move the search to a icon in
   navbar that expands.... keep main display panel uncluttered."

   `docs-ui.js` MOVES just-the-docs' own `.search` container in here rather
   than rebuilding it, so the theme's input, label, results list and handlers
   all arrive intact. What it does not bring is a layout that fits: the theme
   sizes that container to fill a full-width `.main-header`, with an
   absolutely-positioned input stretched to its parent's height. Dropped into
   a 16.5rem sidebar panel with no height of its own, that collapses to
   nothing. Everything below is the un-stretching.

   The holder is `hidden` while parked. It stays IN THE DOCUMENT the whole
   time — see the note in `docs-ui.js`: the theme looks its input up by id,
   and `getElementById` does not find a detached node. */
.fa-search-holder[hidden] { display: none; }

/* WHICH INDEX THE SEARCH BOX IS SEARCHING — bean `eof6`.
 *
 * Only ever rendered when the build stamped a non-canonical index, so on the
 * published site this rule matches nothing. Styled as a NOTE rather than an
 * error: the state it reports is expected on a preview, and an alarm colour
 * for an expected state is how a reader learns to skip it.
 *
 * Colours come from the `--fa-search-*` family the search field already uses,
 * so the notice themes with the control it belongs to rather than tracking a
 * separate palette. A first draft invented `--fa-accent`, `--fa-surface-2` and
 * `--fa-ink-dim`: NONE of the three exists, and the token tests did not catch
 * it because they forbid a raw hex rather than requiring a defined name — an
 * undefined custom property is not an error, it just renders unstyled. */
.fa-search-notice {
  margin: 0.4rem 0 0;
  padding: 0.35rem 0.5rem;
  font-size: 0.75rem;
  line-height: 1.35;
  border-left: 3px solid var(--fa-search-border);
  background: var(--fa-search-bg);
  color: var(--fa-search-muted);
}

.fa-tiles .fa-search-holder {
  width: 100%;
  box-sizing: border-box;
}

/* Per-scheme, OPAQUE tokens, for the same reason the language bar carries its
   own: a translucent field makes its contrast a property of whatever the
   theme paints underneath, and the numbers below would then be a claim about
   one page rather than a fact about these colours. Ratios are against
   `--fa-search-bg`, computed per WCAG relative luminance on the COMPOSITED
   colour (nothing here is blended by `opacity`) and confirmed by axe.

   Dark is the default, as everywhere else in this file. */
.fa-tiles .fa-search-holder {
  --fa-search-bg: #1c2029;
  --fa-search-fg: #e6e9ee;      /* 13.40:1 — what the reader types            */
  --fa-search-muted: #9aa4b2;   /*  6.47:1 — the placeholder and result blurb */
  --fa-search-border: #6b7684;  /*  3.53:1 — a UI component, SC 1.4.11 floor  */
}

:root[data-fa-scheme="light"] .fa-tiles .fa-search-holder {
  --fa-search-bg: #ffffff;
  --fa-search-fg: #1f2933;      /* 14.76:1 */
  --fa-search-muted: #5a6472;   /*  6.00:1 */
  --fa-search-border: #767d88;  /*  4.15:1 */
}

/* The theme gives `.search` `position: relative; height: 100%; flex-grow: 1`
   and, above 50rem, a width tied to the main column. None of that survives
   the move, and `height: 100%` of a parent with no height is zero. */
.fa-tiles .search,
.fa-tiles .search-input-wrap {
  position: static;
  display: block;
  width: 100%;
  height: auto;
  max-width: none;
  flex: none;
  padding: 0;
  margin: 0;
  box-shadow: none;
  background: transparent;
}

/* The input is `position: absolute` in the theme, stretched to the header's
   height. In normal flow it needs a height of its own. 2.25rem is 36px —
   above the 24px SC 2.5.8 floor by the same margin the tiles take, because
   this instance's declared interaction profile is low-dexterity. */
.fa-tiles .search-input {
  position: static;
  box-sizing: border-box;
  width: 100%;
  min-height: 2.25rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--fa-search-border);
  border-radius: 4px;
  background: var(--fa-search-bg);
  color: var(--fa-search-fg);
  font-size: 0.8rem;
}

/* `::placeholder` is rendered at a reduced opacity by every engine unless it
   is told otherwise, and an opacity-blended foreground fails SC 1.4.3 even
   when the token it was blended from passes. `opacity: 1` is what makes the
   6.47:1 above a fact about what is on screen. */
.fa-tiles .search-input::placeholder {
  color: var(--fa-search-muted);
  opacity: 1;
}

.fa-tiles .search-input:focus-visible,
.fa-tiles .search-input:focus {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

/* THE LABEL IS HIDDEN VISUALLY AND KEPT IN THE ACCESSIBILITY TREE, and the
   difference between those two is the whole point.

   The theme's magnifier label is absolutely positioned over its input, and
   there is already a magnifier on the tile that opened this view, so a second
   one inside the field is noise in a panel whose job is to be uncluttered.

   This rule was `display: none` for exactly one commit. That is not hiding a
   picture — it removes the label from the accessibility tree, **and the
   theme's input carries no `aria-label`**: its name comes from this label and
   the `sr-only` span inside it. So `display: none` silently demoted the
   field's accessible name to its `placeholder`, which vanishes the moment the
   reader types.

   It was found by reading the REAL Jekyll output of this branch, not by the
   gate: the test fixture had been written from memory with an `aria-label`
   the theme does not emit, so axe was measuring a name the site never
   renders. axe does not flag it even with the fixture corrected — its `label`
   rule accepts a non-empty placeholder as a last-resort pass. This is the
   same class of defect as the bare `<circle>` nodes in the neighbourhood
   diagram: real, and outside what an automated pass can see. An explicit
   accessible-name assertion guards it instead.

   So: clipped to a pixel rather than removed. Invisible, still named. */
/* SCOPED TO BOTH CONTAINERS since 2026-09-21. The field moved to the navbar
   (`.fa-search-home`) and this rule stayed pinned to `.fa-tiles`, so the
   magnifier came back at full size over the input — caught by the e2e
   assertion on the label's box, which is exactly what that assertion is for.
   Widened rather than duplicated: two copies of an accessibility rule is two
   places for one of them to be reverted. */
.fa-tiles .search-label,
.fa-search-home .search-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Results drop BELOW the field in normal flow rather than floating over the
   sidebar nav, and the panel scrolls instead of growing past the viewport. */
.fa-tiles .search-results {
  position: static;
  width: 100%;
  max-height: 16rem;
  margin-top: 0.4rem;
  overflow-y: auto;
  border: 1px solid var(--fa-search-border);
  border-radius: 4px;
  background: var(--fa-search-bg);
  color: var(--fa-search-fg);
  box-shadow: none;
}

.fa-tiles .search-results:empty { display: none; }

.fa-tiles .search-result {
  display: block;
  padding: 0.35rem 0.5rem;
  min-height: 2rem;
  color: var(--fa-search-fg);
  font-size: 0.78rem;
  text-decoration: none;
}

.fa-tiles .search-result:hover,
.fa-tiles .search-result:focus-visible,
.fa-tiles .search-result.active {
  background: rgba(128, 128, 128, 0.22);
}

.fa-tiles .search-result-rel-url,
.fa-tiles .search-result-previews,
.fa-tiles .search-no-result {
  color: var(--fa-search-muted);
  font-size: 0.72rem;
}

/* ═══ Sticky todos ═══════════════════════════════════════════════════════
 *
 * Scheme-agnostic BY CONSTRUCTION: every surface here is a neutral rgba
 * overlay plus `currentColor`, so it inherits the page's own foreground in
 * either scheme and there is no second palette to keep in step.
 *
 * That is not stylistic. #319 measured `#fff` on this site's accent at
 * 5.97:1 in light and 2.19:1 in dark — a fixed colour that passes in one
 * scheme and fails in the other, from one declaration. An overlay cannot
 * have that defect, because it never names a colour.
 *
 * Targets are 2.75rem minimum, well past the 24px SC 2.5.8 floor, because
 * this instance's declared interaction profile is low-dexterity and a target
 * that is barely legal is a target that is hard to hit (bean `gjli`).
 */

/* ── The folio visualisation's corner radius — ONE home ───────────────
 *
 * Owner, 2026-09-21: *"drop all the rounded corners… too much dead space."*
 *
 * A TOKEN rather than a sweep, and the difference is what the next change
 * costs. Written as `border-radius: 0` at each of the thirteen surfaces, the
 * decision would have thirteen homes and no name: a reader who wanted the
 * rounding back would have to find every one, and a reader editing
 * `.fa-sticky`'s radius would not learn that the value is a policy rather
 * than a taste. Here it is one line, and each surface below SAYS it is
 * governed by asking for it.
 *
 * SCOPED TO THE FOLIO, not to the stylesheet. The surfaces that opt in are
 * the board, the cards on it, the window, the avatar and the chrome inside a
 * card — the thing the owner was looking at. The QA panel, the search field
 * and the translation badges keep their own literals: none was asked about,
 * and a blanket sweep would have changed them silently.
 *
 * THE PILLS ARE NOT SURFACES AND DO NOT OPT IN. `.fa-sticky-chip`,
 * `.fa-sticky-badge` and `.fa-tile-count` are `999px` — a pill is a SHAPE
 * that carries meaning, and squaring a count badge does not recover any dead
 * space because a pill has none. The stated reason does not reach them, so
 * they are left and NAMED here rather than left and unmentioned. */
:root {
  --fa-folio-radius: 0;
}

/* Themed: every colour here is a ROLE supplied by themes.css, which is
 * generated from the theme nodes in `schemas/themes.ts`. The fallbacks after
 * each `var()` are the pre-theme neutrals, so a board still renders if
 * themes.css has not loaded — an unstyled sticky reads as broken, and "no
 * theme yet" is a state rather than an error. */
.fa-sticky-board {
  margin: 0 0 1.5em;
  /* CONDENSED — *"to much padding between panels"*. Was `1em 1.1em 1.2em`.
     The board is the glass the cards sit on, so its inset is frame rather
     than content: it needs to read as an edge, not as a margin. */
  padding: 0.4rem;
  border: 1px solid var(--fa-sticky-edge, rgba(128, 128, 128, 0.4));
  border-radius: var(--fa-folio-radius, 8px);
  background: rgba(128, 128, 128, 0.06);
}

.fa-sticky-board-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1em;
  margin-bottom: 0.8em;
}

.fa-sticky-board-title {
  margin: 0;
  font-size: 1.1rem;
}

.fa-sticky-board-close,
.fa-sticky-close {
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0;
  border: 1px solid rgba(128, 128, 128, 0.4);
  border-radius: var(--fa-folio-radius, 6px);
  background: transparent;
  color: inherit;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

/* `auto-fill` with a floor, so the board is one readable column on a phone and
 * lines the stickies up on a wide screen — without a media query naming a
 * breakpoint that the next layout change would invalidate.
 *
 * ── The column is CAPPED now, and the cap is a measurement ──────────
 *
 * Owner, 2026-09-21: *"sticky should stilll be ~2.5" in large macbook
 * screen"*, said in the same breath as *"THE STICKIES MUST BE CLOSED AVATRS
 * AND SMALL"* — so the cap is what keeps "small" from becoming "unreadable",
 * not a licence to shrink further.
 *
 * `1fr` WAS THE BUG. The floor was 17rem and the track was `1fr`, so on a
 * wide board every column absorbed the leftover space and the sticky's width
 * was whatever the viewport happened to leave — a size nobody chose and
 * nothing could hold. `--fa-sticky-max-width` makes the intended width
 * statable, and therefore testable.
 *
 * 20rem IS 2.5 INCHES, AND NOT BECAUSE CSS SAYS SO. A CSS `in` is defined as
 * exactly 96 CSS px and is NOT a physical inch — writing `2.5in` here would
 * have rendered about 1.9" of glass and looked like the owner was ignored.
 * The 16" MacBook Pro this was asked about is 3456x2234 over a 16.2" diagonal
 * — 13.6" of width — and presents 1728 CSS px across it, so one physical inch
 * is about 127 CSS px. 2.5 x 127 = 318px; 20rem is 320px. The device is named
 * because the conversion is device-dependent: on a 27" 1440p display the same
 * 20rem is about 3.7 physical inches, and that is correct rather than a
 * defect — the owner sized it for the screen they use.
 *
 * MOBILE IS UNTOUCHED. themes.css sets `--fa-sticky-min-width: 100%` under
 * 40rem. `minmax(100%, 20rem)` has min > max, which CSS resolves by IGNORING
 * the max — so the phone keeps its single full-width column without this rule
 * having to know a breakpoint. */
.fa-sticky-grid {
  display: grid;
  grid-template-columns: repeat(
    auto-fill,
    minmax(var(--fa-sticky-min-width, 17rem), var(--fa-sticky-max-width, 20rem))
  );
  /* CONDENSED — *"to much padding between panels, condense"*. Was `0.9em`,
     which is ~14px of dead board between two cards that are themselves only
     320px wide. `rem` rather than `em` because the gap is board geometry and
     must not track a sticky's font scale. */
  gap: 0.4rem;
  align-items: start;
}

.fa-sticky {
  padding: var(--fa-sticky-padding, 0.7em 0.8em);
  font-size: calc(1em * var(--fa-sticky-font-scale, 1));
  color: var(--fa-sticky-ink, inherit);
  border: 1px solid var(--fa-sticky-edge, rgba(128, 128, 128, 0.45));
  /* The priority stripe is the only per-card differentiator, and it is a
   * WIDTH as well as an opacity — colour alone would carry the whole signal,
   * which SC 1.4.1 forbids and a monochrome reader cannot see at all. */
  border-left-width: 5px;
  border-radius: var(--fa-folio-radius, 6px);
  /* A gradated theme supplies both stops or neither — the schema refuses one,
   * because a gradient with a single stop is a flat fill claiming otherwise.
   * So the linear-gradient falls back to the flat surface as a whole. */
  background: var(
    --fa-sticky-grad-from,
    var(--fa-sticky-surface, rgba(128, 128, 128, 0.08))
  );
  background-image: linear-gradient(
    var(--fa-sticky-grad-from, transparent),
    var(--fa-sticky-grad-to, transparent)
  );
}

/* A STICKY WITH A BACKDROP: art behind, scrim between art and ink.
 *
 * Two layers rather than one background, because the art cannot be a CSS
 * background at all: `<picture>` swaps the FILE at a breakpoint, and a
 * `background-image: url(...)` can only name one crop. Serving the wide crop to
 * a phone is the bug `landing.html` records shipping once, so the art stays
 * markup and this positions it.
 *
 * `isolation: isolate` creates a stacking context, so the two negative
 * z-indexes stay inside the card. Without it they sit behind the PAGE and the
 * art disappears under the site background — which looks exactly like a missing
 * image, so it would be debugged as one.
 *
 * The scrim's fallback is the opaque surface, not `transparent`. A theme with a
 * backdrop always has a scrim — the schema refuses one without — so this
 * fallback is only reached by a sticky marked `--backdrop` whose theme did not
 * load. Falling back to transparent there would show ink directly on the art at
 * whatever contrast the photograph happens to give; falling back to the flat
 * surface hides the art and keeps the words readable. Degrade toward legible. */
.fa-sticky--backdrop {
  position: relative;
  isolation: isolate;
  /* REQUIRED, not tidiness. Without it the art escapes the card's rounded
   * corners; and if any rule above ever fails to position it, an unclipped
   * backdrop lays out at its INTRINSIC size — 1672px wide — and covers the
   * page. That is not hypothetical: it is what this board did on its first
   * render, and it was invisible in the HTML, which was correct throughout. */
  overflow: hidden;
}

/* THE POSITIONED LAYER IS THE `<picture>`, NOT THE `<img>`.
 *
 * This was `.fa-sticky--backdrop > .fa-sticky-art` and matched NOTHING: the
 * `<img>` sits inside `<picture>`, so it is a grandchild and the child
 * combinator failed silently. CSS has no error for a selector that matches
 * nothing, so the page built clean, the markup was right, every request
 * returned 200 — and the art rendered at full size in normal flow, pushing the
 * text down and spilling across the whole viewport. Found by screenshotting the
 * board, not by any gate.
 *
 * `<picture>` is the right element to position anyway: it is the box that
 * represents the art, and the `<img>` inside it is the chosen crop. */
.fa-sticky--backdrop > picture {
  position: absolute;
  inset: 0;
  z-index: -2;
}

/* Negative z-index puts this ABOVE the card's own background and below its
 * content, which is what the painting order gives: an element's background
 * paints first, then negative-z children, then in-flow content. `isolation`
 * above keeps both layers inside the card rather than behind the page. */
.fa-sticky--backdrop > picture > .fa-sticky-art {
  display: block;
  width: 100%;
  height: 100%;
  /* `contain`, so the WHOLE artwork is visible rather than a crop of it.
   *
   * This was `cover`, on the reasoning that the card's aspect will not match the
   * art's and letterboxing reads as a broken image. Both halves were wrong here.
   * `cover` cut the cat's head off on a short card and showed a meaningless
   * middle band on a tall one — the owner, looking at it: "show full sticky
   * image, not cropped." And the letterboxing argument does not apply to a
   * backdrop faded to a tenth of its strength: the bands are the card's own
   * surface colour, which is what the rest of the card already is.
   *
   * `top` because the thought-cloud is at the top of every crop and the text is
   * held to the top of the card. Centring the art vertically would slide the
   * cloud down behind the second paragraph. */
  object-fit: contain;
  object-position: center top;
}

/* A SCRIM, NOT IMAGE OPACITY — and the difference is why this needs one value
 * where the approach it replaced needed two.
 *
 * `opacity` on the `<img>` does not tint toward a fixed colour: it blends the
 * art with whatever renders BEHIND it, which is the page background. The
 * landing panel this supersedes carried `opacity: 0.9` by default and `0.8`
 * under the light override for exactly that reason — its ink is a fixed dark
 * green sized against the cream cloud in the artwork, so fading toward WHITE
 * brightens that cloud and widens the contrast margin while fading toward
 * BLACK by the same amount narrows it. Enough fade in dark mode read as the
 * ink sinking into a dimmed cloud rather than standing out from one, so the
 * two schemes could not share a number.
 *
 * A scrim is a layer of a KNOWN colour between art and ink, so the contrast it
 * buys does not depend on what is behind the card. One value, both schemes,
 * and a theme can set it. The rules that carried the 0.9/0.8 pair were removed
 * from `_includes/head_custom.html` once measured dead — `folio-landing__art`
 * was emitted by nothing, so they shipped inlined on 331 pages and matched
 * none of them (bean `alox`, issue #873). This note is where that reasoning
 * lives now; it is recorded because the blend-direction argument is not
 * obvious and would otherwise be re-derived the next time somebody reaches
 * for `opacity` here. */
.fa-sticky--backdrop::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--fa-sticky-scrim, var(--fa-sticky-surface, rgba(128, 128, 128, 0.08)));
}

/* PRIORITY STAYS LITERAL, DELIBERATELY. Critical is red and high is amber in
 * every theme, because the stripe encodes PRIORITY and a theme that recoloured
 * it would make the same urgency read differently on two boards. A theme
 * supplies `--fa-sticky-accent` for its own chrome, not for this signal. */
.fa-sticky-p-critical { border-left-color: rgba(200, 60, 60, 0.85); }
.fa-sticky-p-high     { border-left-color: rgba(200, 120, 40, 0.8); }
/* The two neutral priorities DO take the theme's accent: they carry no hue of
 * their own to preserve, and grey-on-sage is the case where an unthemed stripe
 * reads as a rendering fault rather than as "medium". */
.fa-sticky-p-medium   { border-left-color: var(--fa-sticky-accent, rgba(128, 128, 128, 0.7)); }
.fa-sticky-p-low      { border-left-color: var(--fa-sticky-edge, rgba(128, 128, 128, 0.35)); }

/* A THEMED CARD WITH NO PRIORITY carries its accent as a thin TOP band.
 * Owner ruling 2026-09-30 (bean `v8n5`): the accent reached a card only
 * through `.fa-sticky-p-medium`, so a landing card, which has no priority,
 * showed its theme as white with grey edges, and the IRIS blue appeared
 * nowhere on the who-iris card. Generic by construction: it keys on the
 * theme ATTRIBUTE, not on any theme's name.
 *
 * The TOP edge, not the left. The left edge is the priority channel, and
 * medium already paints the accent there, so an accent left edge on a card
 * with no priority would read as "medium". Two signals, two edges. */
.fa-sticky[data-fa-sticky-theme]:not([class*="fa-sticky-p-"]) {
  border-top: 3px solid var(--fa-sticky-accent, var(--fa-sticky-edge, rgba(128, 128, 128, 0.45)));
}

.fa-sticky-head {
  display: flex;
  flex-direction: column;
  gap: 0.45em;
}

.fa-sticky-toggle {
  display: block;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.2em 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.fa-sticky-toggle::before {
  content: "\25B8\00a0";
  display: inline-block;
}
.fa-sticky-toggle[aria-expanded="true"]::before { content: "\25BE\00a0"; }

.fa-sticky-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35em;
}

.fa-sticky-chip {
  padding: 0.1em 0.5em;
  border: 1px solid rgba(128, 128, 128, 0.4);
  border-radius: 999px;
  font-size: 0.72rem;
  text-transform: lowercase;
}

.fa-sticky-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4em;
}

.fa-sticky-pin,
.fa-sticky-recall {
  min-height: 2.75rem;
  padding: 0.2em 0.7em;
  border: 1px solid rgba(128, 128, 128, 0.4);
  border-radius: var(--fa-folio-radius, 6px);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

/* `.fa-node-edit` is the right CLASS — same affordance, same href shape, same
 * glyph — but its per-node STYLING does not transfer, and axe caught that
 * rather than review.
 *
 * Beside a heading it floats right, pulls up into the heading's line, and sits
 * at `opacity: 0.35` so it stays quiet until hovered. In a sticky's tool row it
 * is a primary control on its own line, and 0.35 opacity measured **2.22:1**
 * against the floating card — WCAG AA asks 4.5:1.
 *
 * Semantics inherited, presentation overridden. A pattern adopted at the class
 * level still has to be looked at where it lands. */
/* BOTH source controls, not just the pencil. Bean `pb04` added a `⎘ View`
 * beside `✎ Edit`, and the accessibility argument above applies to it
 * unchanged — so it is styled by the SAME rule rather than by a copy that
 * would drift away from the 2.75rem target and the restored opacity.
 *
 * The selector is a list rather than a shared class, because the two controls
 * ARE different: `.fa-sticky-view` and `.fa-sticky-edit` are what a test and a
 * future style can tell apart, and collapsing them into one class to save a
 * selector would throw that away. */
/* The row the landing card's two source controls sit in. `display: flex` with
 * a gap rather than margins on the controls themselves, so the 2.75rem touch
 * target below stays the whole of each control and the spacing between them
 * is not part of either. */
.fa-landing-sticky__source {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.7em 0 0;
}

.fa-sticky-view,
.fa-sticky-edit {
  float: none;
  margin: 0;
  padding: 0.2em 0.7em;
  border: 1px solid rgba(128, 128, 128, 0.4);
  border-radius: var(--fa-folio-radius, 6px);
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
  font-size: 0.78rem;
  opacity: 1;
}

.fa-sticky-body {
  margin-top: 0.6em;
  font-size: 0.85rem;
}
.fa-sticky-body p { margin: 0 0 0.6em; white-space: pre-wrap; }
.fa-sticky-body p:last-child { margin-bottom: 0; }
.fa-sticky-empty { opacity: 0.7; font-style: italic; }

/* ── The sticky's overflow drawer (bean `qefk`) ─────────────────────
 *
 * Owner: *"the todos controls are too clunky / take up too much real
 * estate."* Four buttons sat on a card whose content is one line of summary,
 * five once it floated. The answer chosen was **"3+1"** — board gestures on
 * the face, the two that leave for the forge behind one affordance.
 *
 * A `<details>`, so the disclosure, the keyboard path, Escape and the
 * expanded state are the browser's rather than four things to reimplement.
 * The marker is removed because the summary IS the glyph; the accessible name
 * is on the summary and names the contents rather than the shape. */
.fa-sticky-more { position: relative; display: inline-block; }

.fa-sticky-more-summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 24px square: SC 2.5.8's floor, and this instance's declared interaction
     profile is low-dexterity, so the floor is the point rather than the
     minimum to argue down from. */
  min-width: 24px;
  min-height: 24px;
  padding: 0 0.35em;
  border-radius: var(--fa-folio-radius, 4px);
  cursor: pointer;
  line-height: 1;
  list-style: none;
}
.fa-sticky-more-summary::-webkit-details-marker { display: none; }
.fa-sticky-more-summary::marker { content: ""; }
.fa-sticky-more-summary:hover { background: rgba(128, 128, 128, 0.18); }

/* Opens OVER the card rather than pushing its body down: a drawer that
   reflows the board moves every card below it, which is the thing a reader
   was about to click. `z-index` above the card, not above the float layer. */
.fa-sticky-more[open] .fa-sticky-more-body {
  position: absolute;
  right: 0;
  top: 100%;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 0.15em;
  min-width: max-content;
  padding: 0.3em;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: var(--fa-folio-radius, 5px);
  /* OPAQUE, and for the reason the pinned sticky is: it sits over content.
     Inherits the card's own surface so a themed sticky's drawer is themed
     too — the `ivfw` round-trip lesson, applied before it could bite. */
  background: var(--fa-sticky-surface, var(--sidebar-color, #27262b));
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
:root[data-fa-scheme="light"] .fa-sticky-more[open] .fa-sticky-more-body {
  background: #ffffff;
}

/* THE LAYER IS THE FRAME, NOT THE ARRANGEMENT — bean `ivfw`.
 *
 * It was `inset: auto 1rem 1rem auto` with a width: a small bottom-right box
 * whose children stacked in flow. That is precisely the owner's report, *"you
 * cant move around dispaly"* — the LAYER decided where a sticky sat and the
 * sticky had no say, so there was nowhere for it to go.
 *
 * Now it is the whole viewport and each card carries its own geometry, placed
 * by `placeFloating` so the default reproduces the old bottom-right pile and
 * nothing moves until a reader moves it. `position: fixed` is unchanged and is
 * still the point of pinning: it stays put while the page scrolls.
 *
 * `pointer-events: none` on the frame and `auto` on the cards, because a
 * full-viewport layer that swallowed clicks would make the whole page
 * unusable the moment one sticky was pinned. The frame is a coordinate space,
 * not a surface. */
.fa-sticky-layer {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
}
.fa-sticky-layer:empty { display: none; }

/* ── THE GLASS — R25's pull-down folio ────────────────────────────────
 *
 * Owner: *"the user in visualization should be able to pull down their
 * folio"*, and *"pulling down folio panel = glass/window on which stikcy
 * notes/avatrs of materialized assets … are visualized."*
 *
 * CLOSED, THE LAYER IS WHAT IT ALWAYS WAS: a coordinate frame that passes
 * every pointer through, so a pinned sticky floats over the page and the page
 * underneath stays usable. Only `open` makes it a surface.
 *
 * `pointer-events` IS THE WHOLE MECHANISM and it is why this is not a new
 * element. A glass that captured clicks while closed would make every page it
 * is mounted on — which is now every page — unusable, and it would do so
 * invisibly, since a transparent full-viewport div looks like nothing at all.
 */
/* THE GLASS'S LOOK IS THE READER'S, set from the Settings tile (bean `zrvt`,
 * issue #1006). Three tokens per theme — the tint, the card and the ink —
 * and ONE opacity the reader moves, carried as `--fa-glass-opacity` on the
 * layer. Default, on the owner's words 2026-09-23: *"start with the glass
 * being 20% opaque with a blur effect."*
 *
 * THE CARDS ARE SOLID WHATEVER THE OPACITY. The owner's screenshot of the
 * first glass showed an asset's title printed straight over the library
 * table — *"very hard to read"*. See-through is the glass; the things ON it
 * are paper, so a reader can turn the glass down to nothing and still read. */
.fa-sticky-layer {
  --fa-glass-tint: 20, 20, 24;
  --fa-glass-card: #1f1e24;
  --fa-glass-ink: #e8eaed;
  --fa-glass-muted: #b4b8c0;
  --fa-glass-edge: #5a5670;
  --fa-glass-accent: #8c74f0;
}
:root[data-fa-scheme="light"] .fa-sticky-layer,
.fa-sticky-layer[data-fa-glass-theme="paper"] {
  --fa-glass-tint: 240, 240, 244;
  --fa-glass-card: #ffffff;
  --fa-glass-ink: #1b1b1f;
  --fa-glass-muted: #4a4d55;
  --fa-glass-edge: #8a8699;
  --fa-glass-accent: #4a34b8;
}
.fa-sticky-layer[data-fa-glass-theme="night"] {
  --fa-glass-tint: 12, 12, 16;
  --fa-glass-card: #1a1a1f;
  --fa-glass-ink: #f1f2f4;
  --fa-glass-muted: #c3c6cc;
  --fa-glass-edge: #5a5670;
  --fa-glass-accent: #a592ff;
}
/* HIGH CONTRAST: black and white, 2px white edges and a yellow focus — the
   usability theme, so no tint, no shadow and no picture is load-bearing. */
.fa-sticky-layer[data-fa-glass-theme="contrast"] {
  --fa-glass-tint: 0, 0, 0;
  --fa-glass-card: #000000;
  --fa-glass-ink: #ffffff;
  --fa-glass-muted: #ffffff;
  --fa-glass-edge: #ffffff;
  --fa-glass-accent: #ffe600;
}
.fa-sticky-layer[data-fa-glass="open"] {
  pointer-events: auto;
  /* Translucent rather than opaque: the point of a glass is that you can see
     what you were browsing underneath it. */
  background: rgba(var(--fa-glass-tint), var(--fa-glass-opacity, 0.2));
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
/* THE GLASS IS JEWELLED — owner, 2026-09-24: *"I like purple, but maybe
 * slightly/abstractly jewelled? stained glass? modern … align options to
 * themes"*, then **"Mix, per theme"** from five previewed options.
 *
 * Each glass theme carries a PATTERN as well as a tint, painted on `::before`
 * at the reader's opacity (scaled, so 20% still reads as glass and not as
 * nothing). A sticky theme belongs to ONE sticky while the glass is shared, so
 * the pairing is carried by the glass themes the Settings tile offers:
 *
 *   Glass  → amethyst facets  (default; pairs with Analyst, Operations)
 *   Paper  → iris haze        (Pale sage, Dusty Carolina blue)
 *   Night  → opal night       (Night, Operations)
 *   Rose window → cathedral rose, leaded (Library, Grumpy cat)
 *   Leaded grid → modernist panes, one amber jewel (Architecture, Engineer)
 *
 * HIGH CONTRAST HAS NO PATTERN, deliberately: it is the usability theme, and
 * nothing decorative may sit behind it. The cards stay solid on every theme,
 * so no pattern ever sits behind text. Pure CSS: no image to fetch or drift. */
.fa-sticky-layer,
.fa-glass-handle {
  --fa-glass-pattern:
    conic-gradient(from 210deg at 30% 38%, rgba(255,255,255,.18) 0 12%, transparent 12% 30%, rgba(255,255,255,.08) 30% 41%, transparent 41% 62%, rgba(0,0,0,.18) 62% 78%, transparent 78%),
    conic-gradient(from 40deg at 72% 64%, rgba(0,0,0,.2) 0 14%, transparent 14% 36%, rgba(255,255,255,.12) 36% 48%, transparent 48% 70%, rgba(255,255,255,.06) 70% 84%, transparent 84%),
    linear-gradient(135deg, #3b1d6e 0%, #6a3fc4 45%, #8c74f0 70%, #4a2690 100%);
}
:root[data-fa-scheme="light"] .fa-sticky-layer,
.fa-sticky-layer[data-fa-glass-theme="paper"],
:root[data-fa-scheme="light"] .fa-glass-handle:is(:not([data-fa-glass-theme]), [data-fa-glass-theme="glass"]),
.fa-glass-handle[data-fa-glass-theme="paper"] {
  --fa-glass-pattern:
    linear-gradient(115deg, transparent 0 58%, rgba(255,210,240,.55) 60%, rgba(200,230,255,.5) 63%, rgba(220,255,230,.45) 66%, transparent 69%),
    radial-gradient(120% 90% at 15% 10%, #efe8ff 0%, transparent 60%),
    linear-gradient(160deg, #d9ccfb 0%, #b7a3ef 50%, #9d8ae0 100%);
}
.fa-sticky-layer[data-fa-glass-theme="night"],
.fa-glass-handle[data-fa-glass-theme="night"] {
  --fa-glass-pattern:
    radial-gradient(3px 3px at 18% 24%, rgba(190,160,255,.9), transparent),
    radial-gradient(2px 2px at 64% 18%, rgba(140,220,255,.8), transparent),
    radial-gradient(2px 2px at 82% 66%, rgba(255,170,220,.8), transparent),
    radial-gradient(3px 3px at 36% 78%, rgba(165,146,255,.85), transparent),
    radial-gradient(40% 50% at 70% 70%, rgba(120,70,220,.35), transparent 70%),
    radial-gradient(45% 55% at 25% 30%, rgba(70,110,210,.28), transparent 70%),
    linear-gradient(150deg, #141019 0%, #1f1830 55%, #120e18 100%);
}
.fa-sticky-layer[data-fa-glass-theme="rose"],
.fa-glass-handle[data-fa-glass-theme="rose"] {
  --fa-glass-tint: 28, 20, 38;
  --fa-glass-card: #e8e8e0;
  --fa-glass-ink: #1c2630;
  --fa-glass-muted: #4a5663;
  --fa-glass-edge: #c2ccd4;
  --fa-glass-accent: #5b2fb0;
  --fa-glass-pattern:
    radial-gradient(circle at 50% 50%, transparent 0 17%, #1c1426 17% 18.2%, transparent 18.2% 33%, #1c1426 33% 34.2%, transparent 34.2%),
    repeating-conic-gradient(from 0deg at 50% 50%, #1c1426 0 1.2deg, transparent 1.2deg 30deg),
    conic-gradient(from 0deg at 50% 50%, #5b2fb0 0 30deg, #7a4ad8 30deg 60deg, #9b2d5c 60deg 90deg, #5b2fb0 90deg 120deg, #2f4fa8 120deg 150deg, #7a4ad8 150deg 180deg, #5b2fb0 180deg 210deg, #9b2d5c 210deg 240deg, #7a4ad8 240deg 270deg, #2f4fa8 270deg 300deg, #5b2fb0 300deg 330deg, #7a4ad8 330deg 360deg);
}
.fa-sticky-layer[data-fa-glass-theme="leaded"],
.fa-glass-handle[data-fa-glass-theme="leaded"] {
  --fa-glass-tint: 34, 26, 46;
  --fa-glass-card: #fdfbf0;
  --fa-glass-ink: #1a372d;
  --fa-glass-muted: #45574e;
  --fa-glass-edge: #a6aea8;
  --fa-glass-accent: #4d2e9e;
  --fa-glass-pattern:
    linear-gradient(90deg, transparent 0 22%, #221a2e 22% 22.8%, transparent 22.8% 61%, #221a2e 61% 61.8%, transparent 61.8%),
    linear-gradient(0deg, transparent 0 34%, #221a2e 34% 35.2%, transparent 35.2% 72%, #221a2e 72% 73.2%, transparent 73.2%),
    linear-gradient(90deg, #6b4bc9 0 22%, #8e76e6 22% 61%, #4d2e9e 61%) top / 100% 34% no-repeat,
    linear-gradient(90deg, #b9a8f2 0 22%, #6b4bc9 22% 61%, #c6a54a 61%) 0 55.74% / 100% 39% no-repeat,
    linear-gradient(90deg, #4d2e9e 0 22%, #a38ef0 22% 61%, #6b4bc9 61%) bottom / 100% 27% no-repeat;
}
.fa-sticky-layer[data-fa-glass-theme="contrast"],
.fa-glass-handle[data-fa-glass-theme="contrast"] { --fa-glass-pattern: none; }
.fa-sticky-layer[data-fa-glass="open"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--fa-glass-pattern);
  /* 20% (the default) reads as 36% here: enough to see the facets, still glass. */
  opacity: min(1, calc(var(--fa-glass-opacity, 0.2) * 1.8));
}
.fa-sticky-layer[data-fa-glass="open"][data-fa-glass-blur="off"] {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── What sits ON the glass: solid, and legible at any opacity ─────────── */
.fa-glass-empty,
.fa-glass-notes p,
.fa-glass-asset,
.fa-glass-panel {
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
  border: 1px solid var(--fa-glass-edge);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
.fa-sticky-layer[data-fa-glass-theme="contrast"] .fa-glass-empty,
.fa-sticky-layer[data-fa-glass-theme="contrast"] .fa-glass-notes p,
.fa-sticky-layer[data-fa-glass-theme="contrast"] .fa-glass-asset,
.fa-sticky-layer[data-fa-glass-theme="contrast"] .fa-glass-panel {
  border-width: 2px;
  box-shadow: none;
}
/* THE SHELF IS A SURFACE, not a list: each card carries its own left/top/
   width/height (set by the script from the reader's saved place, or a grid),
   and the shelf is as tall as its lowest card so what follows sits below. */
.fa-glass-shelf {
  position: relative;
  margin: 1rem 0 0;
  /* A SURFACE to grab even with nothing on it, and one a finger pinches and
     pans rather than scrolls (`b8eq`). */
  min-height: 50vh;
  touch-action: none;
  cursor: grab;
  /* ITS OWN STACKING CONTEXT, so the cards' raise order stays INSIDE it.
     Every press raises a card (`z-index` 2, 3, 4…), and at home — where the
     shelf carries no transform — those numbers used to compete with the rest
     of the glass: a card pressed four times climbed over the tile strip
     (z 3), and one dropped near the bottom covered the Todos tile. Owner,
     2026-09-24: *"drag drop avatar and cant … inteact w/ todos"*. Now the
     whole shelf is one layer at 0, and the cards order among themselves. */
  z-index: 0;
}
.fa-glass-shelf[data-fa-pinching] { cursor: grabbing; }
/* What the reader OPENS sits above the cards: a panned or zoomed shelf can
   slide under the notes and the panel, and a Todos panel a card covered would
   be the same defect as the strip above. */
.fa-glass-notes {
  position: relative;
  z-index: 1;
}
/* Fixed (see "The panel a chrome tile opens" below), and above the cards. */
.fa-glass-panel { z-index: 1; }

/* ── Zoom, pan and Home — bean `b8eq` ─────────────────────────────────────
   Sticky to the top of the glass so it stays in reach however far the glass
   is scrolled, and ABOVE the cards so a zoomed-in card never covers it. */
.fa-glass-zoom {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  width: max-content;
  max-width: 100%;
  margin: 0 0 0 auto;
  padding: 0.3rem 0.5rem;
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
  border: 1px solid var(--fa-glass-edge);
  /* BORDER-BOX: with the move bar open the row reaches `max-width: 100%`, and
     a content-box 100% plus its padding and border overflowed the sheet by
     18px — so pressing a move-bar button scrolled the glass sideways and the
     button jumped out from under the pointer (measured, issue #1900). */
  box-sizing: border-box;
}
.fa-glass-zoom-btn {
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 0.6rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: var(--fa-glass-ink);
  font: inherit;
  font-size: 1.1rem;
  font-weight: 700;
  cursor: pointer;
}
.fa-glass-home { font-size: 0.9rem; }
/* THE MOVE BAR — what ✜ is for, said on screen (owner, 2026-09-24:
   *"exploding icon outlines the avatar but appears to do nothing else"*). A
   second row of the sticky zoom bar, so it stays in reach and at full size
   however the glass is scrolled or zoomed. Shown only while a card moves. */
.fa-glass-move-bar {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding-top: 0.3rem;
  border-top: 1px solid var(--fa-glass-edge);
}
.fa-glass-move-bar[hidden] { display: none; }
.fa-glass-move-say {
  flex: 1 1 100%;
  max-width: 34rem;
  margin: 0;
  font-size: 0.85rem;
}
.fa-glass-move-done { font-size: 0.9rem; }
.fa-sticky-layer[data-fa-glass-home="false"] .fa-glass-home {
  border-color: var(--fa-glass-accent);
  box-shadow: inset 0 0 0 2px var(--fa-glass-accent);
}
.fa-glass-zoom-slider { width: 9rem; height: 2.75rem; accent-color: var(--fa-glass-accent); }
/* A FIXED width, not a minimum: the bar is right-aligned, so any change in the
   readout's width ("100%" → "90%") would slide − and the slider sideways. */
.fa-glass-zoom-value { flex: 0 0 auto; width: 3.6rem; white-space: nowrap; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.fa-glass-zoom button:focus-visible,
.fa-glass-zoom input:focus-visible {
  outline: 3px solid var(--fa-glass-accent);
  outline-offset: 2px;
}

/* ── A todo's avatar is a sticky note — owner, 2026-09-23 (`b8eq`) ──────
   A square of note yellow, a folded corner and ruled lines: the thing a todo
   is everywhere else on this site. Fixed colours on purpose — a sticky note
   is yellow in both schemes, and the ink is measured against that yellow. */
.fa-sticky-note-avatar {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2.4rem;
  background: #fbe38e;
  border: 1px solid #a88a1f;
  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.35);
  transform: rotate(-3deg);
  clip-path: polygon(0 0, 100% 0, 100% 72%, 72% 100%, 0 100%);
}
.fa-sticky-note-avatar::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: 28%;
  height: 28%;
  background: linear-gradient(to bottom right, #d9bb4f 50%, transparent 50%);
}
.fa-sticky-note-lines {
  position: absolute;
  left: 18%;
  right: 18%;
  top: 24%;
  height: 46%;
  background: repeating-linear-gradient(to bottom, #6b5a12 0 2px, transparent 2px 33%);
  opacity: 0.6;
}
.fa-glass-todo-row .fa-sticky-note-avatar { width: 1.9rem; height: 1.9rem; }
.fa-glass-asset {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  overflow: auto;
  border-radius: var(--fa-folio-radius, 0);
}
.fa-glass-asset-face {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 4.85rem;
  flex: 1 1 auto;
  cursor: grab;
}
.fa-glass-asset-tools { display: flex; flex-wrap: wrap; gap: 0.3rem; justify-content: flex-end; }
/* THE SHELVED-ITEMS NOTE (owner, 2026-09-27: "its own todo") has no picture,
   so its face is just its title: the 4.85rem floor and the grow reserved room
   for a cover and squeezed the sentence into a scroll box. The words take the
   room instead. */
.fa-glass-note-card > .fa-glass-asset-face { flex: 0 0 auto; min-height: 0; }
.fa-glass-note-card > .fa-glass-sticky-body { flex: 1 1 auto; }
.fa-glass-asset[data-fa-moving="true"] {
  outline: 3px dashed var(--fa-glass-accent);
  outline-offset: 2px;
}
/* SEMANTIC ZOOM: below the declared width, the avatar alone. The NAME is
   still the card's accessible name (`aria-label`), so a reader who cannot see
   the picture loses nothing by the zoom. */
.fa-glass-asset[data-fa-zoom="avatar"] .fa-glass-asset-name { display: none; }
.fa-glass-asset[data-fa-zoom="avatar"] .fa-glass-asset-face { justify-content: center; }
/* …EXCEPT A TEXT CARD, whose words are its face. Owner, 2026-10-01: *"if todo
   is small zoomed, it shows no content at all. instead it should cleanup
   whitespace and show condended first part of todo"*. A todo (and the
   shelved-items note, which is drawn as one) shows its GIST in the avatar
   state: whitespace collapsed and markdown dropped by `gistOf`, clamped here
   to the whole lines that fit (the count is set by `zoomGlassCard`). Drawn
   at a constant size on screen — the shelf's scale divided out — because
   words zoomed to 25% with the card are present and unreadable. Hidden at
   full size, where the title and the body already say it. */
/* A LIBRARY card's gist is its title, the same way and for the same reason —
   owner, 2026-10-01: *"where is title of thing from library?"*. Zoomed to its
   cover, a book card had a picture and nothing saying which book; the caption
   is clamped to two lines (`zoomGlassCard`) so the cover stays its face. */
.fa-glass-asset-gist { display: none; }
.fa-glass-asset[data-fa-zoom-kind="todo"][data-fa-zoom="avatar"] .fa-glass-asset-gist,
.fa-glass-asset[data-fa-zoom-kind="library"][data-fa-zoom="avatar"] .fa-glass-asset-gist {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  flex: 1 1 0;
  min-height: 1.3em;
  overflow: hidden;
  margin: 0;
  /* Above the theme's art and scrim, which sit at negative z in the card. */
  position: relative;
  z-index: 1;
  font-size: calc(0.8rem / var(--fa-glass-scale, 1));
  line-height: 1.3;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.fa-glass-avatar {
  flex: 0 0 auto;
  width: 3.5rem;
  height: 4.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--fa-glass-edge);
  background: rgba(var(--fa-glass-tint), 0.35);
}
.fa-glass-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fa-glass-kind-avatar { --fa-avatar-size: 2rem; color: var(--fa-glass-accent); }
.fa-glass-asset-name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--fa-glass-ink);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
a.fa-glass-asset-name { text-decoration: underline; text-underline-offset: 2px; }
.fa-glass-asset-tool,
.fa-glass-panel-close,
.fa-glass-step {
  flex: 0 0 auto;
  min-width: 2.75rem;
  min-height: 2.75rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: var(--fa-glass-ink);
  font-size: 1.2rem;
  cursor: pointer;
}
/* The eye is a link, so it is centred as the buttons' text is. */
a.fa-glass-asset-tool { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; }
a.fa-glass-asset-tool svg { width: 1.2rem; height: 1.2rem; fill: currentColor; }
.fa-glass-notes { max-width: 40rem; margin: 1rem auto 0; }
.fa-glass-notes p { padding: 0.5rem 0.75rem; margin: 0 0 0.5rem; font-size: 0.85rem; }

/* ── The tile strip, along the BOTTOM edge ────────────────────────────── */
/* THE DOCK holds the strip and the tab that slides it away (`b8eq`). The
   dock is what is fixed to the bottom edge; hidden, it drops by the strip's
   height and leaves the tab on screen — the way back is never off-screen. */
/* THE DOCK IS THE TILES PANEL (owner, 2026-09-24: *"HIDE/show tiles should be
   inside of tiles panel"*). It paints the surface and the ONE accent edge; the
   header row holds the Hide/Show control, the strip below it holds the tiles.
   Before, the dock painted nothing and the toggle was its own bordered tab
   sitting on the strip's border -- outside the panel, under a doubled edge. */
.fa-glass-dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  background: var(--fa-glass-card);
  border-top: 2px solid var(--fa-glass-accent);
  color: var(--fa-glass-ink);
  transition: transform 0.25s ease;
  z-index: 3;
}
/* Hidden, the panel drops by everything but its header row, so the part left
   on screen is the panel's own edge WITH the way back in it (`l4zi`). The
   2px is the edge itself. */
.fa-glass-dock[data-fa-strip="hidden"] { transform: translateY(calc(100% - 2.75rem - 2px)); }
@media (prefers-reduced-motion: reduce) {
  .fa-glass-dock { transition: none; }
}
.fa-glass-dock-head {
  display: flex;
  justify-content: flex-end;
  min-height: 2.75rem;
  padding: 0 1rem;
}
.fa-glass-strip-toggle {
  min-height: 2.75rem;
  padding: 0 1rem;
  /* A control IN the panel, drawn like the panel's other controls: one thin
     edge, no second accent border. */
  border: 1px solid var(--fa-glass-edge);
  border-top: 0;
  border-bottom: 0;
  background: transparent;
  color: var(--fa-glass-ink);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}
.fa-glass-strip-toggle:focus-visible {
  outline: 3px solid var(--fa-glass-accent);
  outline-offset: 2px;
}
.fa-glass-tiles {
  align-self: stretch;
  display: flex;
  gap: 0.5rem;
  padding: 0 1rem 0.6rem;
  /* FIT, NOT SCROLL (owner, 2026-10-01, bean `ob3m` finding 10: "no tile may
     be silently off-screen"). `docs-ui.js` hides the pinned tiles that do not
     fit and the last tile counts them as "+N more", so nothing here may
     scroll sideways — a scrolling strip is exactly how 14 of 25 tiles went
     off-screen with no arrow, count or fade. Not `hidden` either: that would
     clip the focus ring, and nothing overflows once the fit has run. */
  overflow: visible;
  flex-wrap: nowrap;
  /* The panel (the dock) paints the surface and the edge; the strip is the
     row of tiles inside it, so it paints neither -- a second edge here is the
     doubled border the owner saw. */
  color: var(--fa-glass-ink);
}
.fa-glass-tiles .fa-tile,
.fa-glass-tiles .fa-glass-chrome-tile {
  flex: 0 0 auto;
  width: 5.5rem;
  min-height: 5.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.35rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: var(--fa-glass-ink);
  font: inherit;
  font-size: 0.75rem;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  position: relative;
}
/* A tile the fit could not place. The tiles set `display: flex`, which beats
   the user agent's `[hidden]`, so the attribute needs saying here. */
.fa-glass-tiles > [hidden] { display: none !important; }
/* A phone gets slightly narrower tiles, so four fit at 390 px — Todos,
   Settings, the first pinned kind and "+N more" — rather than three. */
@media (max-width: 30rem) {
  .fa-glass-tiles { gap: 0.4rem; padding-left: 0.75rem; padding-right: 0.75rem; }
  .fa-glass-tiles .fa-tile,
  .fa-glass-tiles .fa-glass-chrome-tile { width: 4.75rem; }
}
.fa-glass-tiles [data-fa-more] .fa-tile-caption { white-space: nowrap; }
/* A pinned tile the strip had no room for, waiting first in More. Its note
   sits where the "↓ Strip" button would, at the tile's width. */
.fa-glass-more-item[data-fa-overflow] { width: 6.5rem; }
.fa-glass-more-item .fa-glass-arrange-note {
  min-height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 0.75rem;
  line-height: 1.15;
  border: 1px dashed var(--fa-glass-edge);
}
.fa-glass-tiles .fa-tile svg { width: 1.6rem; height: 1.6rem; }
.fa-glass-chrome-tile { font-weight: 700; }
.fa-glass-tile-glyph { font-size: 1.6rem; line-height: 1; }
.fa-glass-tiles .fa-glass-chrome-tile[aria-expanded="true"] {
  border-color: var(--fa-glass-accent);
  box-shadow: inset 0 0 0 2px var(--fa-glass-accent);
}
.fa-glass-tiles .fa-tile-caption { overflow-wrap: anywhere; line-height: 1.15; }
.fa-glass-tiles .fa-tile:focus-visible,
.fa-glass-tiles .fa-glass-chrome-tile:focus-visible,
.fa-glass-sheet button:focus-visible,
.fa-glass-sheet a:focus-visible,
.fa-glass-sheet input:focus-visible {
  outline: 3px solid var(--fa-glass-accent);
  outline-offset: 2px;
}

/* ── The panel a chrome tile opens, above the strip ───────────────────── */
/* IN VIEW, NOT IN THE FLOW — wireframe `navbar` Findings (#2295, first noted
   in #1810). In the flow it came after the shelf (`min-height: 50vh`), so at
   1280×800 Glass settings opened at y = 591 with its body under the tile
   dock. Fixed to the viewport instead; `fitPanel()` in docs-ui.js sets `top`
   below the handle and zoom bar and `max-height` down to the dock's visible
   top edge, for whichever state the dock is in. Centred by `left`/`right`
   with auto margins rather than by a transform, because the transform is
   `placePanel`'s: the pop-out still rides the folio's pan. The HEAD stays and
   the BODY scrolls when the content is taller than the room (Glass settings
   on a phone), so the title and × are never under the dock. */
.fa-glass-panel {
  position: fixed;
  left: 1rem;
  right: 1rem;
  top: 3rem;
  max-width: 40rem;
  margin: 0 auto;
  padding: 0.75rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
/* `display: flex` above would otherwise override the UA's `[hidden]`. */
.fa-glass-panel[hidden] { display: none; }
.fa-glass-panel-head { flex: 0 0 auto; }
.fa-glass-panel-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Room for a focused control's 3px ring inside the scroller. */
  padding: 0 4px;
  margin: 0 -4px;
}
/* GLASS SETTINGS LAID OUT ACROSS THE WIDTH where there is width for it, so
   at 1280×800 the whole panel fits between the zoom bar and the dock with no
   scrolling. In one column it is ~910 px tall. Laid out, Theme takes a wide
   column where each choice's hint fits beside its name (one line each, not
   two), and Avatars and Opacity stack beside it. The actions wrap in a row
   underneath. A GRID, not `columns`: a height-capped multi-column box
   overflows SIDEWAYS into columns nobody can see, while a grid overflows
   down into the body's own scroll. A phone keeps the single column, and
   the body scrolls.
   60rem, not wider: at 1280 it leaves empty glass either side to grab and
   pan the folio by (`glass-pop-outs.e2e.ts` presses at x = 1150). */
.fa-glass-panel[data-fa-panel="glass-settings"] { max-width: 60rem; padding-bottom: 0.5rem; }
.fa-glass-settings-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.5rem; }
.fa-glass-settings-actions .fa-glass-settings-note { flex: 1 1 100%; margin: 0.25rem 0 0; }
@media (min-width: 48rem) {
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-panel-body {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    /* The cross-link shares its row with Avatars rather than taking a
       full-width row of its own: that row was the ~45 px that put the
       actions under the dock at 1280×800. It stays FIRST in the DOM and in
       reading order (`ob3m` 12); only its row is shared. */
    grid-template-areas:
      "theme   avatars"
      "theme   opacity"
      "actions actions";
    grid-template-rows: auto auto auto;
    column-gap: 1rem;
    align-content: start;
  }
  /* Only where the cross-link is drawn: a page with no launcher (a replica,
     the harness page) has none, and an empty "link" row would still take a
     share of Avatars' height and push Theme down. */
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-panel-body:has(> .fa-settings-crosslink) {
    grid-template-areas:
      "link    avatars"
      "theme   avatars"
      "theme   opacity"
      "actions actions";
    grid-template-rows: auto auto auto auto;
  }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-settings-crosslink { grid-area: link; margin: 0; align-self: start; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-setting { margin: 0.5rem 0 0; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-setting:nth-of-type(1) { grid-area: theme; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-setting:nth-of-type(2) { grid-area: avatars; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-setting:nth-of-type(3) { grid-area: opacity; align-self: start; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-settings-actions { grid-area: actions; margin-top: 0.25rem; gap: 0.5rem; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-settings-actions .fa-glass-reset { margin: 0; }
  .fa-glass-panel[data-fa-panel="glass-settings"] .fa-glass-settings-actions .fa-glass-settings-note { flex: 0 1 auto; margin: 0; }
}
.fa-glass-panel-head { display: flex; align-items: center; gap: 0.5rem; }
.fa-glass-panel-title { flex: 1 1 auto; margin: 0; font-size: 1.1rem; color: var(--fa-glass-ink); }
.fa-glass-panel-status { color: var(--fa-glass-muted); font-size: 0.85rem; }
.fa-glass-panel-more { color: var(--fa-glass-ink); text-decoration: underline; }
.fa-glass-todo-list { list-style: none; margin: 0.5rem 0; padding: 0; }
.fa-glass-todo { border-top: 1px solid var(--fa-glass-edge); padding: 0.5rem 0; }
.fa-glass-todo-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.fa-glass-todo-row .fa-avatar { --fa-avatar-size: 1.5rem; color: var(--fa-glass-accent); }
.fa-glass-todo-summary { flex: 1 1 14rem; }
.fa-glass-setting { border: 1px solid var(--fa-glass-edge); margin: 0.75rem 0; padding: 0.5rem 0.75rem; }
.fa-glass-setting legend { font-weight: 700; padding: 0 0.25rem; }
.fa-glass-choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 2.75rem;
  cursor: pointer;
  flex-wrap: wrap;
}
.fa-glass-choice input { width: 1.4rem; height: 1.4rem; margin: 0; }
.fa-glass-choice-label { font-weight: 600; }
.fa-glass-choice-hint { color: var(--fa-glass-muted); font-size: 0.82rem; }
.fa-glass-opacity { display: flex; align-items: center; gap: 0.5rem; }
.fa-glass-opacity input[type="range"] { flex: 1 1 auto; min-width: 6rem; height: 2.75rem; }
.fa-glass-opacity-value { min-width: 3.5rem; font-variant-numeric: tabular-nums; font-weight: 700; }
.fa-glass-reset {
  display: block;
  margin: 0.5rem 0;
  min-height: 2.75rem;
  padding: 0 1rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: var(--fa-glass-ink);
  cursor: pointer;
}

/* ── Each settings panel names the other (bean `ob3m` finding 12) ─────────
 * "Glass settings" and "Page settings" open with a link to each other. It is
 * a button (it opens a panel, it does not navigate) DRESSED as a link, so it
 * reads as a pointer elsewhere rather than as one of this panel's settings.
 * `currentColor` and an underline, never colour alone (WCAG 1.4.1), so it is
 * legible on the glass's themes and on the sidebar's light and dark schemes
 * without a palette of its own. The 2.75rem target is the declared
 * low-dexterity profile's, the same as every other control on the glass. */
.fa-settings-crosslink {
  display: block;
  width: 100%;
  min-height: 2.75rem;
  margin: 0 0 0.75rem;
  padding: 0.25rem 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: start;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}
.fa-settings-crosslink:hover { text-decoration-thickness: 2px; }
.fa-settings-crosslink:focus-visible { outline: 3px solid currentColor; outline-offset: 2px; }

/* ── The pull-out, first in its row ───────────────────────────────────── */
.fa-pullout-slot { display: inline-flex; align-items: center; gap: 0.4rem; margin-left: 0.5rem; vertical-align: middle; }
.fa-pullout {
  min-height: 2.25rem;
  padding: 0 0.7rem;
  border: 2px solid #8c74f0;
  background: #4a34b8;
  color: #ffffff;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.fa-pullout::before { content: "\2934\00a0"; }
.fa-pullout-state { font-size: 0.78rem; }

/* The sheet is the glass's own chrome and is ALWAYS present, which is what
   keeps `:empty` above from hiding an open glass that holds no cards yet. It
   is inert while closed — `visibility` rather than `display`, so a card the
   board floats into the layer is unaffected either way. */
.fa-glass-sheet {
  position: absolute;
  inset: 0;
  /* The BOTTOM is kept clear for the tile strip, which overlays this edge:
     padding to scroll into, and scroll-padding so a control scrolled into
     view — by focus or by a click — lands above the strip, not under it. */
  padding: 3rem 1rem 8.5rem;
  scroll-padding-bottom: 8.5rem;
  overflow: auto;
  /* THE GUTTER IS RESERVED, so the zoom bar does not jump. Zooming in grows
     the shelf until the glass overflows, a classic (non-overlay) scrollbar
     appears, the content box narrows by its width, and the right-aligned zoom
     bar slid 15px left under the pointer — measured 2026-10-01 at 1280×680,
     on the click from 95% to 105%, and back again on Home. */
  scrollbar-gutter: stable;
}
.fa-sticky-layer[data-fa-glass="closed"] .fa-glass-sheet { visibility: hidden; }

/* "Nothing here" is a STATE, not a failure — and saying so is the difference
   between a tidy glass and a broken one. */
.fa-glass-empty {
  max-width: 28rem;
  margin: 0 auto;
  padding: 1rem;
  border: 1px dashed var(--fa-rule, rgba(128, 128, 128, 0.5));
  border-radius: var(--fa-folio-radius, 0);
  text-align: center;
  font-size: 0.9rem;
}

/* The handle. ABOVE the layer's own z-index, so it stays reachable while the
   glass is open — `l4zi`: an action whose inverse is not reachable is not a
   toggle, and a glass you can only leave by guessing at Escape is exactly
   that. */
/* THE TITLE STOPS SHORT OF THE HANDLE on a narrow screen — `ob3m` finding 9.
 * Below 50rem the theme's `.site-title` is a flex-grow link across the top
 * bar, and the handle is fixed at its centre: at 390px the title's box ran
 * 0–244px under a pill at 154–236px. "C@T Harness" happens to end before the
 * pill; a longer instance name would not. So the title is held to the half of
 * the bar left of the pill (half the viewport less half the pill and a gap)
 * and ends in an ellipsis rather than running underneath it. */
@media not all and (min-width: 50rem) {
  .site-header .site-title {
    max-width: calc(50vw - 3rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* The title's row-mates (`.fa-qr-host`'s icon buttons) follow it in the
       flex row, so a shorter title slid them LEFT, under the handle. The auto
       margin takes the space the title gave up and keeps them at the right.
       LOGICAL, with the padding below: on an RTL page the title is the bar's
       first item at the RIGHT edge, and `margin-right: auto` pushed it to the
       centre, under the pill (measured 2026-10-06 at 420px: title 146-314px,
       pill 172-248px). */
    margin-inline-end: auto;
    /* The theme's 14px right padding is what pushed "C@T Harness" into the
       ellipsis: the text ends at ~145px but the padded box needed ~159px.
       Beside a fixed pill the padding buys nothing, so it goes here. */
    padding-inline-end: 0;
  }
}

.fa-glass-handle {
  position: fixed;
  /* BELOW the staging banner, which is sticky at z-index 9999 and publishes
     its height as `--fa-staging-offset` (`staging-banner.ts`). At `top: 0`
     the handle sat under it, and on a preview there was no Folio at all
     (owner, 2026-09-27: "no folio"). 0 on the canonical site. */
  top: var(--fa-staging-offset, 0px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 91;
  /* PRONOUNCED, on the owner's instruction: *"Not too subtle but
     pronounced."* The first draft was an 0.8rem label in the chrome colour,
     which reads as a stray tab rather than as the way to your folio. This is
     the same control asserting itself: bigger target, heavier label, and an
     ACCENT fill so it is the one thing on the page that is not page.

     SC 2.5.8's 24px floor was already cleared at 2.75rem and is cleared by
     more now. That floor is a minimum, not a target, and this instance's
     profile is low-dexterity — a control a reader is meant to find without
     looking for it should be bigger than the rule requires. */
  /* NOT SO TALL — owner, 2026-09-27: "top middle of display screen. not so
     tall". A short pill: 1.75rem clears SC 2.5.8's 24px floor, and the
     1.2rem-wide padding keeps the target wide for a low-dexterity reader.
     The height is what used to cover a viewer's h1 (`015u`) and a replica's
     banner (`269z`); halving it is what lets the handle sit here again. */
  min-height: 1.75rem;
  padding: 0 1.2rem;
  line-height: 1;
  border: 2px solid #8c74f0;
  border-top: 0;
  border-radius: 0 0 0.75rem 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  /* A shadow, because the handle overhangs content rather than sitting in a
     bar of its own — without one it reads as printed ON the page instead of
     as something held above it and pullable. */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  /* STAINED GLASS, the glass theme's own pattern (owner, 2026-09-27:
     "appropriate theme stained glass, not solid purple"). A VEIL over it
     keeps the label readable on every pane: a pattern runs light and dark,
     and the label has to clear 4.5:1 against the lightest of them. The solid
     #4a34b8 is the fallback where the pattern is `none` (a page with no
     theme set, or high contrast). */
  background: var(--fa-handle-veil), var(--fa-glass-pattern, none) #4a34b8;
  --fa-handle-veil: linear-gradient(rgba(16, 8, 36, 0.45), rgba(16, 8, 36, 0.45));
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  /* AN EXPLICIT FOREGROUND, never `inherit`, and this cost an a11y failure
     before it was written: axe measured 1.39:1, `#000000` on `#27262b`.
     The handle is appended to `document.body`, so there is nothing above it
     to inherit from but the document default — a control that carries its own
     BACKGROUND and leans on an inherited FOREGROUND is `23bc` again, and
     being body-level is what makes it certain rather than likely.
     12.46:1 on the dark chrome, computed rather than eyeballed. */
  color: var(--fa-handle-ink, #ffffff);
  font-size: 0.85rem;
  white-space: nowrap;
  cursor: pointer;
}

/* ONE PALETTE FOR BOTH SCHEMES, and no light override — which is the fix for
 * a defect the first draft had rather than a shortcut. A control that is
 * violet in one scheme and grey in the other is TWO controls, and a reader
 * switching schemes has to learn it twice.
 *
 * THE BORDER IS LOAD-BEARING IN DARK, and that is the measurement worth
 * keeping. Every ratio below is computed, because this file has now shipped
 * TWO contrast failures from pairs that looked fine: `23bc`, and this very
 * handle at 1.39:1 when it carried `color: inherit`. A third draft claimed
 * "10.4:1" and "the edge clears 3:1"; both were wrong — 7.32:1, and 1.41:1
 * against its own fill, which is not what SC 1.4.11 asks about anyway.
 *
 *   #ffffff on #4a34b8 ............ 8.49:1   text, needs 4.5
 *   #4a34b8 vs the LIGHT page ..... 8.49:1   the fill separates it
 *   #4a34b8 vs the DARK page ...... 1.77:1   the fill does NOT
 *   #8c74f0 vs the DARK page ...... 4.20:1   ← so the BORDER does, SC 1.4.11
 *   #8c74f0 vs the LIGHT page ..... 3.58:1
 *
 * Drop the border and the handle vanishes into a dark page. It is not
 * decoration. */
/* The light panes (Paper, and any page in the light scheme) take dark ink
   under a light veil, the way the glass itself does. */
:root[data-fa-scheme="light"] .fa-glass-handle:is(:not([data-fa-glass-theme]), [data-fa-glass-theme="glass"]),
.fa-glass-handle[data-fa-glass-theme="paper"] {
  --fa-handle-veil: linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.4));
  --fa-handle-ink: #1b1b1f;
  text-shadow: none;
}
.fa-glass-handle[data-fa-glass-theme="contrast"] {
  --fa-handle-veil: linear-gradient(#000, #000);
  --fa-handle-ink: #ffffff;
  border-color: #ffffff;
  text-shadow: none;
}
.fa-glass-handle:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

/* RESERVE THE HANDLE'S BAND on the harness's own standalone viewers — bean
 * `015u`.
 *
 * On a just-the-docs page the handle sits in the header bar's empty middle,
 * and measured 2026-09-24 it covers no text there at 1280 or 390 px. A
 * standalone VIEWER (folio, library, …) has no theme header and starts its
 * content at y = 0, so at 390 px its own `h1` sat under the 52 px handle.
 *
 * SCOPED TO VIEWERS, by the mark the viewer fixture itself writes
 * (`withNarrowViewport` in `scripts/viewer-page.ts` puts
 * `<style data-folio-narrow-viewport>` in every viewer's head), and NOT to
 * "any page without a theme header". That broader rule was tried first and
 * CI refused it three ways, each for a reason worth keeping:
 *
 *   - a REPLICA must be unchanged with the glass closed (`folio-mount.e2e`,
 *     bean `jpjt`: who-iris exists to look like WHO), and a band moves it;
 *   - two test fixtures with no theme header moved too.
 *
 * REPLICAS TOO, since 2026-09-29 — bean `g9r2`, the owner's call on the
 * question this comment used to leave open: "Band on replicas". The pill sat
 * over the replica's own "INGESTED COPY — not WHO" banner at 390 px and over
 * its folio-assistant link at 1280. A replica is found by the mount script
 * `folio-mount.ts` writes into it (`data-fa-folio-mount`). The band moves
 * the whole replica down at once, and `folio-mount.e2e` now measures from
 * the top of body's content, so it still fails on anything but that.
 *
 * 2.25rem clears the 1.75rem pill (owner, 2026-09-27: "not so tall") with a
 * few pixels of air. */
html:has(style[data-folio-narrow-viewport]) body:has(> .fa-glass-handle),
html:has(script[data-fa-folio-mount]) body:has(> .fa-glass-handle) {
  padding-top: 2.25rem;
}

/* ...AND KEEP IT RESERVED ONCE THE PAGE SCROLLS — issue #1693.
 *
 * The rule above, and the theme header's empty middle, clear the handle at
 * the TOP of a page only. Scrolled, the handle sat over the middle of a body
 * line (9 of 21 scroll positions on `platform.html` at 1280x900). While the
 * page is scrolled `placeHandleBand` in `docs-ui.js` shows this strip behind
 * the handle, across the content column, at the handle's own top and height
 * and in the page's own background, so text scrolls under a bar rather than
 * past a pill. Its geometry and colour are set there because both are read
 * off the page: the handle's height differs by viewport, and a FIXED side bar
 * must stay uncovered.
 *
 * Just under the glass layer (90), so an open glass is unchanged; and
 * `scroll-padding-top` so an anchor jump or a focus scroll lands its target
 * below the strip instead of under it. 2.25rem matches the band above. */
.fa-glass-band {
  position: fixed;
  right: 0;
  z-index: 89;
  pointer-events: auto;
  box-shadow: 0 1px 0 rgba(128, 128, 128, 0.3);
  /* The page's own background: the band is body's child, and a themed page's
     body paints the scheme's colour. NOT `Canvas`, which follows the OS
     scheme rather than `data-fa-scheme` (see `.fa-sticky-floating`). */
  background-color: inherit;
}
.fa-glass-band[hidden] { display: none; }
/* A body that paints no background (the JS marks it): the scheme's page
   colours, the same pair `head_custom.html` gives `html`. */
.fa-glass-band[data-fa-band-fallback] { background-color: #ffffff; }
:root[data-fa-scheme="dark"] .fa-glass-band[data-fa-band-fallback] { background-color: #27262b; }
html:has(body > .fa-glass-handle) {
  scroll-padding-top: calc(var(--fa-staging-offset, 0px) + 2.25rem);
}
/* WITH CONTROLS IN THE BAND, THE PADDING MOVES ONTO THE CONTENT — #2201.
   A stuck band's own controls sit INSIDE a scroll-padding that clears the
   band, so the browser read each one as obscured and scrolled it into view
   the moment it took focus: measured in Chromium, focusing the magnifier at
   scrollY 1500 jumped the page to 1076, and Enter then to 228. So the root
   pads nothing, and every element of the content column carries the margin
   instead — an anchor jump or a focus scroll still lands below the band,
   which is 2.5rem plus its padding. */
html:has(.fa-glass-band[data-fa-band-tools]) { scroll-padding-top: 0; }
html:has(.fa-glass-band[data-fa-band-tools]) .main-content * {
  scroll-margin-top: calc(var(--fa-staging-offset, 0px) + 3rem);
}

/* A pinned sticky sits OVER page content, so it must be opaque — and which
 * opaque colour depends on the scheme.
 *
 * `background: Canvas` was the first attempt and is wrong in a way worth
 * recording: `Canvas` is a SYSTEM colour that follows the OS/UA scheme, while
 * the text inside follows the page's own `data-fa-scheme`. When a reader sets
 * the page to dark on a light system, or the reverse, the two disagree and the
 * card renders dark text on dark or light on light. axe measured it as light
 * lavender on white.
 *
 * So: the same shape `.fa-qr-panel` already uses — a dark base, a light-scheme
 * override, and BOTH background and colour set together in each, because
 * setting only one is exactly how the mismatch happened. */
.fa-sticky-floating {
  position: absolute;
  pointer-events: auto;
  /* `border-box`, so a geometry read back after a write is the geometry that
     was written. `content-box` makes the round-trip grow the card by its own
     padding on every nudge — measured on the board window, 450 → 452. */
  box-sizing: border-box;
  overflow: auto;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

/* In the move mode the card says so, for a reader who can see it. The live
   region says it for one who cannot — both, never either. */
.fa-sticky-floating[data-fa-moving="true"] {
  outline: 2px dashed var(--fa-sidelang-link, #9ec5fe);
  outline-offset: 2px;
}

/* The head is the drag handle, so it says so — but only on a card that can
   actually be dragged. A board sticky showing a move cursor it cannot honour
   is a promise the surface does not keep. */
.fa-sticky-floating .fa-sticky-head { cursor: move; }
.fa-sticky-floating .fa-sticky-head button { cursor: pointer; }

/* THE OPAQUE OVERRIDE IS FOR AN UNTHEMED STICKY, AND ONLY FOR ONE.
 *
 * Bean `ivfw`, the owner: "when you unpin, sticky, it loses its theme." The
 * rules below set `background` and `color` flat, and they sit after `.fa-sticky`
 * at equal specificity — so they won, and a themed sticky lifted onto the page
 * came back as a slab of `#27262b`. The theme is a property of the sticky; the
 * pin button was silently changing what the note IS.
 *
 * `:not([data-fa-sticky-theme])` keeps the reason the rules exist and drops the
 * collateral, and it is sound rather than hopeful: MEASURED 2026-09-20, all 14
 * `--fa-sticky-surface` values in the generated `themes.css` are opaque hex.
 * The one translucent surface in the system is `.fa-sticky`'s own
 * `rgba(128, 128, 128, 0.08)` fallback, which applies exactly when no theme is
 * selected — which is exactly what this now matches. `themes.test.ts` gates it,
 * because a theme added with an `rgba()` surface would reintroduce the bug
 * silently and on a page nobody re-checks.
 *
 * The `Canvas` history below is unchanged and still applies to this case. */
.fa-sticky-floating:not([data-fa-sticky-theme]) {
  background: var(--sidebar-color, #27262b);
  color: #f4f4f6;
}

:root[data-fa-scheme="light"] .fa-sticky-floating:not([data-fa-sticky-theme]) {
  background: #fff;
  color: #27262b;
}

@media (prefers-color-scheme: light) {
  :root:not([data-fa-scheme="dark"]) .fa-sticky-floating:not([data-fa-sticky-theme]) {
    background: #fff;
    color: #27262b;
  }
}

/* The board slot for a sticky that is currently pinned. Greyed, still present,
 * and still a real control — the owner asked that clicking it bring the sticky
 * back, and `disabled` would take it out of the tab order entirely. */
.fa-sticky-slot-floating .fa-sticky-recall {
  width: 100%;
  opacity: 0.55;
  text-align: left;
  font-style: italic;
}

.fa-tile-count {
  display: inline-block;
  min-width: 1.4em;
  margin-left: 0.3em;
  padding: 0 0.35em;
  border-radius: 999px;
  background: rgba(128, 128, 128, 0.3);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
}

/* ZERO, on a graph tile, is the state the badge exists to surface.
 *
 * It is not styled as an error -- an empty graph is often correct, and a tile
 * shouting about it would train the reader to ignore the badge. It is styled
 * as QUIET: the pill loses its fill and keeps only an outline, so a row of
 * tiles reads as "these have things in them, that one does not" at a glance.
 *
 * `currentColor` for the outline rather than a literal, so this needs no
 * second definition under the dark theme and cannot drift from one.
 *
 * NOT the same as a tile with no badge at all. A missing badge means the
 * projection declared no count or could not be read -- the opposite fact from
 * an empty graph, and `dh4f` is this repository's name for conflating them.
 * The difference has to survive at the pixel level, which is why absent draws
 * nothing rather than drawing a dimmer zero. */
.fa-tile-count[data-fa-empty] {
  background: transparent;
  box-shadow: inset 0 0 0 1px currentColor;
  opacity: 0.55;
}

@media (prefers-reduced-motion: no-preference) {
  .fa-sticky-floating { transition: box-shadow 120ms ease; }
}

/* A sticky's knowledge-graph edges. Each is `axis: label`, because a bare
 * `#314` or `folio-assistant-29ij` is opaque without the word that says what
 * kind of thing it is. */
.fa-sticky-rels {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3em;
  margin: 0;
  padding: 0;
  list-style: none;
}

.fa-sticky-rel {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.1em 0.5em;
  border: 1px solid rgba(128, 128, 128, 0.35);
  border-radius: var(--fa-folio-radius, 4px);
  font-size: 0.72rem;
}

.fa-sticky-rel-axis { opacity: 0.75; text-transform: lowercase; }

/* A dangling edge is dashed and italic, NOT hidden and NOT greyed to the point
 * of illegibility. It points somewhere real that this site cannot reach, which
 * is a third state — the reader should be able to read the id and go looking. */
.fa-sticky-rel-dangling {
  border-bottom: 1px dashed currentColor;
  font-style: italic;
}

/* A per-block todo badge, beside the heading its todos are attached to. */
.fa-sticky-inline { margin: 0.2em 0 0.4em; }

.fa-sticky-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  min-height: 2.75rem;
  padding: 0.2em 0.7em;
  border: 1px solid rgba(128, 128, 128, 0.45);
  border-radius: 999px;
  background: rgba(128, 128, 128, 0.08);
  color: inherit;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.fa-sticky-badge .fa-tile-glyph { width: 1em; height: 1em; }

.fa-sticky-badge-count {
  padding: 0 0.4em;
  border-radius: 999px;
  background: rgba(128, 128, 128, 0.3);
  font-variant-numeric: tabular-nums;
}

.fa-sticky-inline-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 0.7em;
  margin: 0 0 1.2em;
}

/* The process a sticky belongs to, and how deep that process sits in the BPMN
 * call hierarchy. Indent is a LEFT BORDER rather than a margin, so a nested
 * card keeps its full width in the grid — indenting by margin would make the
 * deepest column narrower than its neighbours for no reason a reader can use. */
.fa-sticky-slot-in-process { border-left: 2px solid rgba(128, 128, 128, 0.3); padding-left: 0.5em; }
.fa-sticky-slot-in-process[data-fa-depth="1"] { border-left-width: 4px; }
.fa-sticky-slot-in-process[data-fa-depth="2"] { border-left-width: 6px; }
.fa-sticky-slot-in-process[data-fa-depth="3"] { border-left-width: 8px; }

.fa-sticky-process {
  display: block;
  margin-bottom: 0.25em;
  font-family: var(--fa-mono, monospace);
  font-size: 0.7rem;
  opacity: 0.8;
}

/* ── Discarded items — the fsh-guts viewer (bean `7vhe`) ───────────────────
 *
 * Deliberately built from `currentColor` and opacity rather than its own
 * colour tokens. Every named colour here is a pair that has to be checked in
 * both schemes, and this repo has live contrast failures (`y8cm`, `rptk`)
 * from exactly that. Inheriting the panel's foreground means it is correct in
 * whichever scheme the panel is, by construction rather than by a second
 * measurement.
 *
 * The one exception is the count badge, which reuses `.fa-tile-count` — the
 * existing translucent grey, already in use beside it. */

.fa-discarded-slot {
  margin-top: 0.5rem;
  font-size: 0.72rem;
  opacity: 0.75;
}

.fa-discarded-open {
  /* It is a `.fa-tile`, so it already has the grid tile's shape. Inside the
   * settings column it is on its own, so it stretches rather than sitting in
   * a one-cell grid. */
  width: 100%;
}

.fa-discarded-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.fa-discarded-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid currentColor;
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* The border at full `currentColor` would be louder than the text it
   * surrounds. Dimming the BORDER and not the text keeps the label at the
   * panel's own contrast, which is the part a reader has to read. */
  border-color: rgba(128, 128, 128, 0.45);
}

.fa-discarded-item:hover,
.fa-discarded-item:focus-visible {
  background: rgba(128, 128, 128, 0.15);
}

.fa-discarded-item-name { font-size: 0.78rem; }

.fa-discarded-item-kind {
  flex: none;
  font-size: 0.68rem;
  opacity: 0.7;
  font-variant: small-caps;
}

/* TARGET SIZE, measured rather than assumed. At 0.72rem with 0.2rem padding
 * this button drew 95.6 × 18.4px — under the 24px SC 2.5.8 floor, on the one
 * control that leads back out of an opened item. Nothing measured it, because
 * `a11y.e2e.ts` never has a document to open (bean `7vhe`'s last Done-when
 * item). 1.75rem is 28px: past the floor with room, for the low-dexterity
 * profile this instance declares. The source link below is the same shape
 * and gets the same floor. */
/* Finding 7 (folio-assistant-oi3h): singular back navigation in discarded detail */
.fa-tiles:has(.fa-discarded-detail) .fa-tiles-head .fa-tiles-back {
  display: none;
}

.fa-discarded-back {
  display: inline-flex;
  align-items: center;
  min-height: 1.75rem;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.72rem;
  padding: 0.2rem 0;
  cursor: pointer;
  text-decoration: underline;
}

.fa-discarded-title {
  margin: 0.3rem 0 0.4rem;
  font-size: 0.85rem;
}

/* `tabindex="-1"` makes this focusable so the reader is told which item
 * opened. Without an outline that focus is invisible — the WCAG 2.4.7 case
 * that programmatic focus quietly creates. */
.fa-discarded-title:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.fa-discarded-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.1rem 0.5rem;
  margin: 0 0 0.5rem;
  font-size: 0.7rem;
}

.fa-discarded-meta dt { opacity: 0.7; }
.fa-discarded-meta dd { margin: 0; overflow-wrap: anywhere; }

.fa-discarded-summary {
  margin: 0 0 0.5rem;
  font-size: 0.72rem;
  font-style: italic;
  opacity: 0.85;
}

/* "Could not read" and "nothing here" are different messages and must not
 * look the same. The error is at full contrast because it is a fault the
 * reader may need to report; the empty note is quiet because it is ordinary.
 * Neither is colour-only: both say in words which state they are. */
.fa-discarded-error {
  margin: 0.4rem 0;
  font-size: 0.72rem;
  font-weight: 600;
}

.fa-discarded-none {
  margin: 0.4rem 0;
  font-size: 0.72rem;
  opacity: 0.75;
}

.fa-discarded-source {
  display: inline-flex;
  align-items: center;
  min-height: 1.75rem;
  margin-top: 0.6rem;
  font-size: 0.72rem;
}

/* Discarding a sticky (bean `d1r6`). The crumpled glyph replaces the `×`,
 * which used to dock a floating sticky — a job the greyed board slot still
 * does. Sized to the old glyph so the tool row does not reflow. */
.fa-sticky-discard { line-height: 0; }

.fa-crumpled-glyph {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

/* Stickies discarded in THIS BROWSER. A separate block from the repository's
 * own discarded nodes, visibly so: they are different facts with different
 * reach, and one of them is invisible to everybody else. */
.fa-discarded-local {
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid rgba(128, 128, 128, 0.35);
}

.fa-discarded-local:empty { border-top: 0; padding-top: 0; margin-top: 0; }

.fa-discarded-local-title {
  margin: 0 0 0.2rem;
  font-size: 0.75rem;
}

/* The limitation is TEXT, not a colour or an icon. A reader who believes
 * they cleared a todo for the team has been misled by the control. */
.fa-discarded-local-note {
  margin: 0 0 0.4rem;
  font-size: 0.68rem;
  opacity: 0.75;
}

.fa-discarded-local-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.2rem 0;
  font-size: 0.72rem;
}

.fa-discarded-restore {
  flex: none;
  border: 1px solid rgba(128, 128, 128, 0.45);
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.68rem;
  padding: 0.1rem 0.45rem;
  cursor: pointer;
}

.fa-discarded-restore:hover,
.fa-discarded-restore:focus-visible { background: rgba(128, 128, 128, 0.15); }

/* ── The kind fan (bean `4kj4`) ────────────────────────────────────────────
 *
 * The owner asked for all three of: a fan, an autoplaying fade, and a fade on
 * hover. They layer — the fan is complete when still, and the cycle is an
 * EMPHASIS over it. Nothing is conveyed by the motion, which is what makes an
 * autoplaying loop defensible rather than merely pausable. */

.fa-kind-fan-open {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid rgba(128, 128, 128, 0.45);
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.fa-kind-fan-open:hover,
.fa-kind-fan-open:focus-visible { background: rgba(128, 128, 128, 0.15); }

.fa-kind-fan {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

/* OVERLAPPING, the participant-list pattern: it says "several" at a glance
 * without needing the reader to count. The negative margin is on the slot and
 * not the avatar so the lit state can still grow without clipping. */
.fa-kind-fan-strip {
  display: inline-flex;
  align-items: center;
}

.fa-kind-fan-slot {
  --fa-avatar-size: 1.15rem;
  display: inline-flex;
  margin-left: -0.35rem;
  opacity: 0.55;
}

.fa-kind-fan-slot:first-child { margin-left: 0; }

/* The lit one. Opacity and SCALE together, never opacity alone: a highlight
 * carried by one channel is a highlight somebody cannot see. */
.fa-kind-fan-slot[data-fa-lit="true"] {
  opacity: 1;
  transform: scale(1.18);
  z-index: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .fa-kind-fan-slot {
    transition: opacity 400ms ease, transform 400ms ease;
  }
}

/* Under reduced motion there is no cycle at all — the script never starts the
 * timer, and this makes sure nothing animates even if it somehow did. The two
 * guards are deliberate: CSS alone leaves a timer mutating the DOM invisibly,
 * script alone leaves the transition running on whatever else changes. */
@media (prefers-reduced-motion: reduce) {
  .fa-kind-fan-slot {
    transition: none;
    /* Every kind at full strength, since nothing will be highlighting them. */
    opacity: 1;
    transform: none;
  }
}

.fa-kind-fan-pause {
  flex: none;
  min-width: 1.6rem;
  min-height: 1.6rem;
  border: 1px solid rgba(128, 128, 128, 0.45);
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  font-size: 0.6rem;
  line-height: 1;
  cursor: pointer;
}

.fa-kind-fan-pause:hover,
.fa-kind-fan-pause:focus-visible { background: rgba(128, 128, 128, 0.2); }

/* ── The panel the fan opens ──────────────────────────────────────────── */

.fa-kinds-note,
.fa-kinds-legend {
  margin: 0 0 0.5rem;
  font-size: 0.68rem;
  opacity: 0.75;
}

.fa-kinds-legend { margin: 0.5rem 0 0; }

.fa-kinds-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.fa-kinds-row {
  --fa-avatar-size: 1.15rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.22rem 0;
  font-size: 0.74rem;
}

.fa-kinds-name { flex: 1; overflow-wrap: anywhere; }

/* The discarded twin, shown beside the live one — muted is only legible as
 * muted next to the thing it is muted against. */
.fa-kinds-trash { opacity: 0.85; }

/* The fan and its pause are SIBLINGS, never nested: the fan is the face of a
 * button, and a <button> inside a <button> is closed by the parser — the
 * whole control never survives to the DOM. Caught by a spec that clicked
 * pause and found `.fa-kind-fan` did not exist. */
.fa-kind-fan-row {
  display: flex;
  align-items: stretch;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

.fa-kind-fan-row .fa-kind-fan-open { margin-top: 0; }

/* ── The landing board ──────────────────────────────────────────────
 *
 * The landing page is a board of stickies (issue #464). The sticky card
 * itself, its backdrop art and its scrim are all `.fa-sticky` rules above;
 * these are the landing-specific ones. */

/* CLIPPED TO A PIXEL, NOT REMOVED. `display: none` takes a node out of the
 * accessibility tree, and each landing sticky's accessible name comes from a
 * heading referenced by `aria-labelledby` — remove it and the board is three
 * anonymous `<article>` regions. The neighbouring `.fa-tiles .search-label`
 * rule carries the long version of why, found by reading real Jekyll output
 * rather than by a gate: axe accepts a non-empty placeholder as a last-resort
 * pass and does not flag the demotion.
 *
 * Generalised here rather than copied: it was scoped to one selector, and this
 * is the second caller. */
.fa-sr-only,
/* The close control's words, `lib/navbar.ts`'s own screen-reader class. Its
 * rule lives in `navbarCss()`, which this page does not load, so the words
 * were painted: "Close navigation" spilling out of the 1.75rem `×` in the
 * icon row, seen in the same screenshot as the owner's 2026-09-27 harnesses
 * report. */
.side-bar .fa-nav-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* One column on a phone, side by side when there is room. `auto-fit` rather
 * than `auto-fill`: with three cards and a wide viewport, `auto-fill` leaves
 * empty phantom tracks and the board drifts left of centre. */
.fa-landing-board {
  display: grid;
  /* WIDER THAN A BOARD CARD, and the reason is the fixed shape rather than
   * taste. A sticky's words now sit inside its art's cloud, which is a fraction
   * of the card — the grumpy card crop's is 58% x 23.5%. At the 17rem column
   * this board used, that cloud is about 160x65px, which no readable text fits.
   * Measured: three of four cards overflowed at 17rem and none does at 26rem.
   *
   * So the column width is load-bearing for legibility here, not a layout
   * preference, and it is written down because the next person to tighten it
   * will not otherwise know what it costs. */
  /* NOT routed through `--fa-sticky-min-width`, and that is the correction.
   * `themes.css` is generated and gives that property to every THEME
   * (`[data-fa-sticky-theme="pale-sage"] { --fa-sticky-min-width: 17rem }`),
   * so it is a property of a sticky's palette rather than of a board's layout.
   * Writing the landing board's column width into it lost twice: as a `var()`
   * fallback it never applied, and as a declaration it fought the theme. The
   * column width is not a theme decision, so it stops pretending to be one.
   *
   * `min(26rem, 100%)` rather than a flat 26rem: 26rem is 416px, and a 430px
   * phone would otherwise be handed a column it cannot quite fit. */
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
  /* CONDENSED with the board grid, and for the same reason. Not to `0.4rem`:
     these cards carry full-bleed art to their own edges, so at 4px two crops
     read as one image. `0.6rem` is the smallest gap at which the boundary
     survives — the constraint is the art, not a second opinion about
     spacing. */
  gap: 0.6rem;
  align-items: start;
  margin-bottom: 1.5rem;
}

/* The landing stickies are read, not scanned, so they get more room than a
 * board card and their text is not scaled down. */
.fa-landing-sticky {
  padding: 1.1em 1.2em;
}

/* START-ALIGNED, NOT CENTRED. The owner, 2026-09-20: "do not auto-cener
 * markdown. assume left justified (or right if RHS lang)."
 *
 * `start` rather than `left`, and that is the whole of the right-to-left half:
 * it resolves to left under `dir="ltr"` and right under `dir="rtl"`, from the
 * document's own direction. Writing `left` and adding an RTL override would be
 * two rules that can disagree, and this site is translated into six UN
 * languages — Arabic among them — so the RTL case is real rather than
 * hypothetical.
 *
 * This replaces `text-align: center`, which came from an earlier reading of
 * "text centered around cloud towards top". That instruction was about WHERE
 * THE BLOCK SITS — in the cloud, near the top — and the block is now positioned
 * there by `--fa-tx/ty/tw/th`. Centring the prose as well was reading a layout
 * instruction as a typographic one, and centred markdown is hard to read: every
 * line starts in a different place, which is exactly what a ragged left edge
 * costs a reader. */
.fa-landing-sticky__body {
  text-align: start;
}

/* ── A STICKY IS A FIXED SHAPE, AND THE SHAPE IS ITS CROP'S ──────────────────
 *
 * The owner, 2026-09-20, looking at a landscape sticky whose card had been
 * sized by its text: "i want stickys to be the same fixd size, so there would
 * be [no] unused space ... dispalyed content matches layout ratios."
 *
 * So the card takes the art's aspect and the art fills it exactly. The
 * letterboxing that prompted this came from `object-fit: contain` (which stays
 * — "show full sticky image, not cropped") fitting a 1.78 image into a box
 * whose height was set by a paragraph. Give the box the image's ratio and
 * `contain` and `cover` agree, so nothing is cropped AND nothing is banded.
 *
 * `aspect-ratio` has no fallback value on purpose: a sticky with no art has no
 * ratio to take, and `auto` leaves it exactly as it was. */
/* THE TODO BOARD, MOUNTED INSIDE THE LANDING BOARD.
 *
 * The owner: "i want todo board inside of the landing folio/board." The landing
 * board is a grid of harness cards, so the todo board arrives as one more grid
 * item — and it must span the whole row rather than sit in a single column,
 * because it is a board in its own right with its own grid inside it.
 *
 * `--inline` rather than restyling `.fa-sticky-board`: everywhere else that
 * board is an overlay opened by a launcher, and this must not change it there. */
.fa-landing-board > .fa-sticky-board--inline {
  grid-column: 1 / -1;
}

.fa-landing-sticky--fixed {
  aspect-ratio: var(--fa-sticky-aspect, auto);
  /* The padding moves to the text box, which positions itself against the art.
   * Padding on the card would shift the cloud coordinates, which are fractions
   * of the CARD, not of its content box. */
  padding: 0;
}

/* WHICH LAYOUT IS IN FORCE, aliased here rather than in the `style` attribute.
 *
 * An inline custom property cannot be overridden by a stylesheet, so a card
 * that set `--fa-sticky-aspect` inline would keep the laptop crop's ratio on a
 * phone while `<picture>` served the portrait file — the art would letterbox
 * again, and the words would sit in the wrong cloud. The template emits the raw
 * per-layout values and `data-fa-shape`; the aliasing is all below.
 *
 * The fallbacks chain to the laptop values, because a role may declare a region
 * for one crop and not another, and the nearest true thing beats a guess. */
.fa-landing-sticky--fixed[data-fa-shape="laptop"] {
  --fa-sticky-aspect: var(--fa-aspect-laptop);
  --fa-tx: var(--fa-tx-laptop);
  --fa-ty: var(--fa-ty-laptop);
  --fa-tw: var(--fa-tw-laptop);
  --fa-th: var(--fa-th-laptop);
}
.fa-landing-sticky--fixed[data-fa-shape="mobile"] {
  --fa-sticky-aspect: var(--fa-aspect-mobile);
  --fa-tx: var(--fa-tx-mobile);
  --fa-ty: var(--fa-ty-mobile);
  --fa-tw: var(--fa-tw-mobile);
  --fa-th: var(--fa-th-mobile);
}
.fa-landing-sticky--fixed[data-fa-shape="card"] {
  --fa-sticky-aspect: var(--fa-aspect-card);
  --fa-tx: var(--fa-tx-card);
  --fa-ty: var(--fa-ty-card);
  --fa-tw: var(--fa-tw-card);
  --fa-th: var(--fa-th-card);
}

/* THE WORDS, IN THE CLOUD.
 *
 * Fractions of the card, matching how `textRegion` is declared — "fractions
 * rather than pixels so the declaration survives the image being re-exported".
 *
 * The defaults are what a sticky whose art declares NO region gets: a generous
 * inset box, centred. That is deliberately not a guessed cloud — a role nobody
 * measured has no quiet interior to aim at, and putting the words over the
 * cat's face would be worse than putting them in the middle of the card. */
.fa-landing-sticky--fixed > .fa-landing-sticky__text {
  position: absolute;
  left: calc(var(--fa-tx, 0.08) * 100%);
  top: calc(var(--fa-ty, 0.08) * 100%);
  width: calc(var(--fa-tw, 0.84) * 100%);
  height: calc(var(--fa-th, 0.84) * 100%);
  display: flex;
  flex-direction: column;
  justify-content: var(--fa-text-align, center);
  font-size: calc(1em * var(--fa-text-scale, 1));
  /* SCROLL, never clip and never spill. The geometry this restores was dropped
   * because overflow used to be silent — words cut off or running over the art
   * with nothing to say so. A scrollable box is the visible version of the same
   * state: the reader can still read it, and the author can see there is more
   * than fits and lower `text.scale` or widen `text.box`. */
  overflow: auto;
  overscroll-behavior: contain;
}

/* THE BACKDROP SCROLLS WITH THE WORDS — bean `624f`, owner 2026-09-23.
 *
 * Owner, 2026-09-21: *"if the content shape is not square and overruns, then
 * you can scroll contents and background will scrll accoringing"*. Offered
 * four readings on 2026-09-23 — scroll together, keep the art still, repeat
 * it, stretch it — they chose **"scrolls with text"**: *"picture and words
 * move together as one surface. The picture stays whole, and text past its
 * end continues on the plain theme colour."*
 *
 * THE CARD IS THE SCROLLER, NOT THE TEXT BOX. Before this, the text box
 * scrolled inside a still card, so the words slid over a fixed cat. Now the
 * text box grows to its content and the CARD scrolls; the `<picture>` and the
 * scrim are absolutely positioned against the card's scroll origin, so they
 * move with the content — which is what makes it one surface.
 *
 * THE PICTURE STAYS WHOLE, because `object-fit: contain` above is unchanged:
 * *"show full sticky image, not cropped"* (owner, earlier) is not traded
 * away. So it covers exactly the first square, and a long note continues
 * below it on `.fa-sticky`'s own themed surface. That is the honest
 * consequence of "whole and unrepeated", and it is what the owner chose.
 *
 * DOCKED ONLY. On the glass (`.fa-sticky-floating`) a sticky is shaped to its
 * content (the other half of this bean), so there is nothing to overrun. */
.fa-landing-sticky--fixed:not(.fa-sticky-floating) {
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.fa-landing-sticky--fixed:not(.fa-sticky-floating) > .fa-landing-sticky__text {
  /* At least the declared cloud, so a short note still sits where the art
   * put it; taller when the words need it, which is what overflows the CARD. */
  height: auto;
  min-height: calc(var(--fa-th, 0.84) * 100%);
  overflow: visible;
  /* Room below the last line, so it never sits flush on the card's edge. */
  padding-bottom: 0.75em;
}

/* A sticky with no art keeps the old flow behaviour exactly: no fixed shape, no
 * absolute box. `--fixed` is only added when art resolved, so this is the
 * third-state case the template already distinguishes. */
.fa-landing-sticky:not(.fa-landing-sticky--fixed) > .fa-landing-sticky__text {
  display: contents;
}

/* The two breakpoints are THE SAME NUMBERS the `<picture>` sources use. If one
 * side moves and the other does not, the card takes one crop's ratio while the
 * browser loads another's file — the art letterboxes and the cloud is in the
 * wrong place. They are written here together for that reason. */
@media (max-width: 48rem) {
  .fa-landing-sticky--fixed[data-fa-shape] {
    --fa-sticky-aspect: var(--fa-aspect-card, var(--fa-aspect-laptop));
    --fa-tx: var(--fa-tx-card);
    --fa-ty: var(--fa-ty-card);
    --fa-tw: var(--fa-tw-card);
    --fa-th: var(--fa-th-card);
  }
}
@media (max-width: 30rem) {
  .fa-landing-sticky--fixed[data-fa-shape] {
    --fa-sticky-aspect: var(--fa-aspect-mobile, var(--fa-aspect-laptop));
    --fa-tx: var(--fa-tx-mobile);
    --fa-ty: var(--fa-ty-mobile);
    --fa-tw: var(--fa-tw-mobile);
    --fa-th: var(--fa-th-mobile);
  }
}

/* ── SHAPE IS A FUNCTION OF WHERE THE STICKY IS ──────────────────────
 *
 * Bean `624f`. Owner, 2026-09-21:
 *
 *   > when sitckes are square, and they ahould always be in the docker/panel
 *   > […] they are only onsquare to fit contents when the pop to the
 *   > window/glass
 *
 * Until now shape was a function of the sticky's declared crop and the
 * viewport, and of nothing else. These two rules add the missing term: a
 * DOCKED sticky is square whatever its crop says, and a FLOATING one is
 * shaped to its content.
 *
 * ## Square by RULE, art from the card crop — and they are different things
 *
 * The obvious implementation is "docked stickies use the `card` aspect",
 * and it would work today: the card crop is square on 3 of 3 stickies in
 * `_data/stickies.json`. Three is the whole corpus, and `schemas/theme.ts`
 * does not REQUIRE a card crop to be square — `ThemeGeometrySchema` takes
 * whatever aspect a theme declares.
 *
 * So the shape is written here as `1` rather than read from
 * `--fa-aspect-card`. A theme that declares a non-square card still docks
 * square, with its card art contained inside — which is what "always
 * square, full stop" means. Taking the aspect from the declaration would
 * make the rule true only for the three themes somebody measured, which is
 * `hfkl`.
 *
 * The TEXT REGION still comes from the card crop, because that region is a
 * fraction of the card art and the art is unchanged. Only the box's ratio
 * is overridden.
 *
 * ## `!important`-free, by specificity
 *
 * The breakpoint rules below alias `--fa-sticky-aspect` per viewport, and a
 * docked sticky must ignore all of them. Rather than fight that cascade
 * with `!important`, this sets `aspect-ratio` directly: the aliases only
 * feed the custom property, so a direct value wins without touching them.
 * A later reader changing the breakpoints cannot silently un-square the
 * dock. */
.fa-sticky-cell > .fa-landing-sticky--fixed,
.fa-sticky-panel .fa-landing-sticky--fixed,
.fa-sticky-board .fa-landing-sticky--fixed {
  aspect-ratio: 1;
}

/* ON THE GLASS, shaped to its content — the other half of the same rule.
 *
 * `.fa-sticky-floating` is the class `placeFloating` adds when a card is
 * pinned onto the glass, and it already carries its own geometry from
 * `wireMove`. Clearing the ratio is what lets that geometry be the content's
 * rather than the crop's.
 *
 * `auto` rather than dropping the declaration: `--fa-sticky-aspect` is still
 * set by the aliases above, so an absent value here would inherit the crop's
 * ratio and the card would stay letterboxed on the glass. */
.fa-sticky-floating.fa-landing-sticky--fixed,
.fa-sticky-floating .fa-landing-sticky--fixed {
  aspect-ratio: auto;
}

/* A markdown body sets its own margins on the first and last child, which
 * doubles the card's padding at top and bottom. */
.fa-landing-sticky__body > :first-child { margin-top: 0; }
.fa-landing-sticky__body > :last-child { margin-bottom: 0; }

/* The derivation chain is several short lines. Left as separate blocks rather
 * than run together, because the rungs ARE the content — and the NO-BREAK SPACE
 * opening the last one is load-bearing typography, asserted by a test in
 * `kg-node.test.ts`. Nothing here may collapse whitespace. */
.fa-landing-sticky__body p { margin: 0 0 0.45em; }

.fa-landing-sticky__links {
  margin: 0.9em 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.5em;
  /* `start`, matching the body above: a centred list of links has the same
   * ragged-edge problem, and the label and its note would centre independently
   * of each other. */
  text-align: start;
}

/* The link inherits the theme's ink rather than the site's link colour: on a
 * scrimmed photograph the site blue is the one value whose contrast was never
 * measured against this ground. Underlined so it is still identifiable as a
 * link without relying on hue (SC 1.4.1), which is the same rule the priority
 * stripe follows by carrying a width as well as a colour. */
.fa-landing-sticky__links a {
  color: var(--fa-sticky-ink, inherit);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  font-weight: 600;
}

/* ── Inline code belongs to the CARD, not to the site's code chip ───────────
 *
 * The owner, 2026-09-20, looking at the rendered board: *"why blackout on
 * middle image?"* — `uploads/` and `library/` in the folio-assist-core sticky
 * were solid black slabs.
 *
 * Measured, not guessed. Site-wide, inline code is LIGHT TEXT ON A DARK CHIP —
 * `#e6e1e8` on `#0d1117`, which is fine and deliberate. Inside a sticky the
 * chip kept its dark background while `color` was overridden by the rule above
 * it, so the text became the theme's `--fa-sticky-ink` — on `library` that is
 * `#1c2630`. Dark ink on a near-black chip:
 *
 *     #1c2630 on #0d1117  =  1.23:1     (AA needs 4.5:1)
 *     the body text beside it        =  14.42:1
 *
 * So the words were not merely low-contrast, they were invisible, and only in
 * stickies — which is why nothing else on the site showed it.
 *
 * Restoring the light foreground would fix the contrast and keep a black slab
 * on a light paper card, which is what the owner objected to. So the chip joins
 * the CARD's palette instead — and WHICH pair matters, checked across all
 * eleven themes rather than the one in front of me:
 *
 *     ink on EDGE     nine themes 9.33-10.14:1, but `high-contrast-light` and
 *                     `high-contrast-dark` have ink == edge, so 1.00:1 — the
 *                     very defect being fixed, moved to another theme
 *     ink on SURFACE  12.45:1 (library) to 21:1 (both high-contrast). AAA
 *                     everywhere, because a theme's ink is CHOSEN against its
 *                     surface; that is what makes the card readable at all
 *
 * So: the card's own paper, its ink, and `edge` demoted to the border, where
 * ink == edge is a visible outline rather than a vanishing act. `themes.test.ts`
 * pins the surface/ink ratio for every theme, so a new one cannot reintroduce
 * this.
 *
 * Scoped to `__text` rather than the article, so a code span in any future
 * chrome outside the text region keeps the site chip. */
.fa-landing-sticky__text code {
  background: var(--fa-sticky-surface, transparent);
  color: var(--fa-sticky-ink, inherit);
  border: 1px solid var(--fa-sticky-edge, transparent);
  border-radius: 0.25em;
  padding: 0.05em 0.32em;
  font-size: 0.92em;
}

.fa-landing-sticky__note {
  display: block;
  font-size: 0.88em;
  opacity: 0.85;
}

/* ─────────────────────────────────────────────────────────────────────────
 * THE FAT HARNESS TILES, in the left sidebar.
 *
 * Owner, 2026-09-20: "I still want to see for every initiated harness a
 * themed fat navbar tile, boot strap at bottom, that user can click on. And
 * basic stats/info via icon + bafges."
 *
 * FAT is the requirement and it is an ACCESSIBILITY one here, not a taste:
 * the declared interaction profile for this instance is low-dexterity, and a
 * big target is the accommodation. `1le7` takes its tile size from the QR
 * code for the same reason, so these use the same minimum rather than a
 * second number — a tile is at least 2.75rem tall, which clears the 44px
 * pointer-target floor at the default root size.
 *
 * THEMED means the avatar's declared hue, passed in as `--fa-tile-tone` by
 * `harness-tiles.ts` from `schemas/avatars.ts`. It is the mechanism this
 * repository already has: one declared hue, from which both schemes are
 * derived, so no tile can be authored legible in one mode and invisible in
 * the other. A per-instance palette would have been a second colour
 * vocabulary beside `theme.ts`, which is the drift that file exists to end.
 * ───────────────────────────────────────────────────────────────────────── */

/* THE THEME'S FOOTER IS NOT WHERE A TWELVE-ITEM NAVIGATION CAN LIVE.
 *
 * `nav_footer_custom.html` is just-the-docs' seam inside the sidebar, and it
 * renders into `.site-footer`, which the theme styles:
 *
 *     .site-footer { position: absolute; bottom: 0; left: 0;
 *                    padding: 1rem 2rem; width: 16.5rem;
 *                    font-size: .6875rem !important; }
 *
 * Measured on the deployed preview, not guessed: the block is pinned to the
 * bottom of a `position: fixed`, full-height column, so the items are laid out
 * from the bottom edge — over the nav and off the top — and the owner reported
 * exactly that ("i stil ldont see icons on left hand navbar"). The 2rem side
 * padding inside a 16.5rem column is the other half of the report: "they
 * should be full width of hte navbar".
 *
 * `:has()` scopes this to the footer that carries OUR block, so a future
 * footer with ordinary footer content keeps the theme's layout.
 */
/* `sjic`: ALSO the renderer's block, and this is the whole reason the switch
 * to `lib/navbar.ts` looked broken. The `:has()` above is keyed on
 * `.fa-nav-home` — a class the hand-written Liquid emitted and the renderer
 * does not, because it distinguishes home by POSITION (last row of the fixed
 * bottom) rather than by name. So the footer kept the theme's `padding:
 * 1rem 2rem` and the navigation laid out at x=32 inside a 56px strip, with
 * its home glyph clipped off the right edge. Measured, not guessed.
 *
 * `.fa-nav-in` is the regions wrapper and BOTH surfaces emit it, so this
 * matches exactly when our block is present — which is what the comment
 * above says this selector is for. */
.site-footer:has(.fa-nav-home),
.site-footer:has(.fa-nav-in) {
  position: static;
  /* `var(--fa-nav-open)`, NOT `100%`, and the difference is a measured defect.
   *
   * This said `width: 100%` and was correct when written: the sidebar had one
   * width and 100% of it WAS the open width. The icon strip then landed, 100%
   * became `3.5rem`, and the harness tabs began wrapping to about one
   * character per line. Measured in a browser 2026-09-22 at 1280x900: the
   * footer laid out **55px wide and 5262px tall**, inside a 900px column.
   *
   * It was invisible because every label in the strip is held at `opacity: 0`,
   * so the only symptom was a scrollbar on a collapsed navbar. `.side-bar > *`
   * already carries the right value and says why; this rule was simply beating
   * it on specificity, which is the whole failure.
   *
   * Scoped to `.fa-nav-home` rather than `> .fa-harness-tabs` because the tabs
   * moved into the scrolling stack below, and home renders even when no tile
   * does — so this now matches exactly when our block is present, which is
   * what the original `:has()` was for. */
  width: var(--fa-nav-open);
  padding-right: 0;
  padding-left: 0;
  /* The theme's `!important` has to be answered in kind. */
  font-size: 0.75rem !important;

  /* THE FIXED BOTTOM, as a column of its own: the stack scrolls, home does
   * not. `min-height: 0` for the same reason it appears on the middle region
   * — without it a flex child refuses to shrink below its content and the
   * cap below does nothing.
   *
   * `!important` ON THE DISPLAY, and it is not belt-and-braces. The theme puts
   * `d-md-block` on this element, which is `display: block !important` from
   * `mq(md)` up — so a plain `display: flex` loses and every flex value below
   * goes inert. MEASURED: the footer computed `display: block`, the stack took
   * its full 1044px instead of shrinking, and home landed at y=1510 in a 900px
   * viewport — rendered, clipped, and unreachable. The same answer this rule
   * already gives the theme's `font-size` two lines down, for the same
   * reason. */
  display: flex !important;
  flex-direction: column;
  /* YIELDS to the middle's floor. `0 1 auto` rather than `0 0 auto`: this
   * region already scrolls inside its own cap, so giving space back costs no
   * reachability, while refusing to would take the middle below `8rem`. */
  flex: 0 1 auto;
  min-height: 0;
  /* AT MOST HALF THE NAVBAR.
   *
   * Not a taste number and not tuned to today's content: the owner's three
   * regions each have a job, and a "fixed" bottom that may take the whole
   * column has taken the middle's job away. Half is the largest share that
   * cannot crowd the other two out, and it is the only fraction with that
   * property. Beyond it the stack scrolls, which is why the cap costs no
   * reachability. */
  max-height: 50%;
  overflow: hidden;
}

/* The harnesses, between the cap and home. */
.fa-nav-bottom__stack {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* THE SAME STACK, AS `lib/navbar.ts` RENDERS IT — and until this rule it had
 * no scroll box at all.
 *
 * Owner, 2026-09-27: *"navbar component below harnesses is cut-off"*. The
 * rule above scrolls `.fa-nav-bottom__stack`, a class the hand-written Liquid
 * emitted and the renderer does not (`sjic`). The renderer's bottom is
 * `.fa-nav-in > .fa-nav-bottom > details.fa-nav-group`, three ordinary BLOCKS
 * inside the capped footer — so the cap held (`max-height: 50%`,
 * `overflow: hidden`) and nothing inside it could shrink or scroll. MEASURED at
 * 1280x800 with the group open: the footer 400px, its content **2575px**, and
 * the harnesses heading plus ONE row were all a reader could reach. The rest
 * was clipped, not scrolled.
 *
 * So the regions wrapper and the bottom region become columns that may shrink
 * (`min-height: 0`, for the reason stated on the middle region: a flex child
 * otherwise refuses to go below its content), and the harnesses disclosure is
 * the one that scrolls. Home is `flex: 0 0 auto` in its own rule above and
 * stays pinned below it, which is the property that rule exists for.
 * `.fa-nav-top` keeps its height; on this surface it is empty, since the
 * avatar is the open control (#1757). */
.site-footer .fa-nav-in,
.site-footer .fa-nav-bottom {
  display: flex;
  flex: 0 1 auto;
  flex-direction: column;
  min-height: 0;
}

.site-footer .fa-nav-top { flex: 0 0 auto; }

.site-footer .fa-nav-bottom > .fa-nav-group {
  flex: 0 1 auto;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  /* A wheel at the end of the list must not start scrolling the page behind
     an open navbar. */
  overscroll-behavior: contain;
}

/* HOME, PINNED BELOW THE STACK — *"keep home at bottom for who iris."*
 *
 * `flex: 0 0 auto`, so folding or scrolling the harnesses cannot take it with
 * them. It is the one destination that must stay reachable when everything
 * else is collapsed, which is also why the markup puts it outside both
 * `<details>`. */
.fa-nav-home,
.side-bar .fa-nav-bottom > a:last-child {
  display: flex;
  flex: 0 0 auto;
  gap: 0.5rem;
  align-items: center;
  min-height: 2.5rem;
  padding: 0 var(--fa-nav-pad);
  border-top: 1px solid rgba(128, 128, 128, 0.25);
  color: inherit;
  text-decoration: none;
}

.fa-nav-home:hover,
.fa-nav-home:focus-visible,
.side-bar .fa-nav-bottom > a:last-child:hover,
.side-bar .fa-nav-bottom > a:last-child:focus-visible {
  background: rgba(128, 128, 128, 0.18);
  text-decoration: none;
}

.fa-nav-home__mark,
.side-bar .fa-nav-bottom > a:last-child > .fa-nav-glyph {
  flex: none;
  width: var(--fa-nav-mark);
  font-size: 1rem;
  text-align: center;
}

.fa-nav-home__label,
.side-bar .fa-nav-bottom > a:last-child > .fa-nav-label {
  overflow: hidden;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ═══ THE HARNESS DIVIDERS ═════════════════════════════════════════════════
 *
 * Owner, 2026-09-21, on the fat tiles these replaced:
 *
 *   those are too much like a dashboard, i want them to be more like
 *   diverders. as if you are opening a giant tabbed folio, but the tabs are
 *   oriented hoizontally.
 *
 * with a photograph of a ring-bound notebook and its coloured index dividers.
 *
 * THE DIFFERENCE IS NOT SIZE, and that is why the stats and the viewer list
 * are not merely smaller here — they are GONE, and rendered by
 * `harness_details.html` on the page the tab opens. A card is a thing you
 * read; a divider is a thing you reach for.
 *
 * THE STAGGER IS WHAT MAKES IT A STACK. A single coloured bar is a bar. Four
 * of them, each inset a little further from the right than the one above,
 * read as tabs cut into one document — which is the whole of the owner's
 * "as if you are opening a giant tabbed folio". `--fa-tab-i` is the item's
 * index, set by the template from the GENERATED order, so the offsetting is a
 * presentation of a fact rather than a fact of its own.
 *
 * THEMED means the avatar's declared hue, passed in as `--fa-tile-tone` by
 * `harness-tiles.ts` from `schemas/avatars.ts`: one declared hue from which
 * both schemes are derived, so no tab can be authored legible in one mode and
 * invisible in the other.
 * ───────────────────────────────────────────────────────────────────────── */

/* ═══ THIS INSTANCE'S CONTROLLED FOLDERS ═══════════════════════════════════
 *
 * Owner, 2026-09-22: *"next on navbar then is is library docs/ and other
 * controlled folders next would the navigation for the current harness (per
 * its rules)."* So: the folders first, the harness's own navigation under
 * them, both in the one scrollable middle.
 *
 * WHICH folders is the instance's declaration, resolved in
 * `sync-docs-harness.ts` from the same tile the icon row comes from. This
 * stylesheet knows no kind names.
 *
 * A `<details>` and OPEN on arrival, matching the graphs group in
 * `navbar.ts` — the folders are what the navbar is FOR, so a reader should not
 * have to open them to see the KG has anything in it. Foldable because the
 * owner asked for that on the harnesses for the same reason: *"they eat up all
 * the nav bar sspace."*
 * ───────────────────────────────────────────────────────────────────────── */

/* THE PAGES HEADING — owner, 2026-09-27: "no title on the navbar component
 * w/ pages", then "cant minimize them either". "On this page" and "Folders"
 * each had a titled disclosure; the page list under them had neither. A
 * BUTTON in front of `.site-nav` rather than a `<details>` around it, because
 * `.fa-nav-middle > .site-nav` is the shape the scroll rules and their tests
 * are written against. Looks like the other two headings on purpose. */

/* ONE CARET FOR EVERY DISCLOSURE IN THE COLUMN (#1902). Owner: *"all three are
 * collapsible but only one has the arrow thing"* -- the summaries are
 * `display: flex`, which drops the native marker, so only the Pages button
 * (whose caret is drawn above) showed one. The same glyph, the same turn, the
 * same timing, keyed on the element's own open state rather than on a class a
 * script has to keep in step. */

/* PAGES, SCOPED to the instance being viewed (#1902): `scopeSiteNav` marks the
 * rows outside the instance's own subtree, and they are not drawn. */
.site-nav [data-fa-out-of-scope] { display: none !important; }

.site-nav[data-fa-folded] { display: none !important; }
/* The theme pads the nav's top to clear its own header; under a heading of
   its own that padding is the blank band the owner's screenshot shows between
   "Pages" and the first page. */

/* Declared, with no published viewer. Struck through rather than hidden, the
 * same way the harness tabs' graph list marks one: a gap a reader can see is a
 * gap somebody can close. */
/* AN INERT ROW, AND THE WORDS ARE THE STATE.
 *
 * Owner ruling on #1036: a declared graph with no viewer is shown, inert and
 * LABELLED. `gjli` is the constraint that decides how: the state must not be
 * carried by colour alone, and it must not read as a control.
 *
 * So this rule only DE-EMPHASISES; it no longer has to carry the meaning,
 * because `.fa-nav-folders__note` beside it says which of the four inert
 * states this is. `line-through` is gone with the same reasoning — on a row
 * that now reads `code  no viewer yet` it struck through the kind's own name,
 * which says "removed" rather than "not built", and struck text is harder to
 * read for exactly the audience this rule is for.
 *
 * `opacity` raised from 0.45 to 0.7, and BOTH numbers are measured in a
 * browser rather than reasoned about: composited over this panel the row
 * cleared **3.56:1** at 0.45 and clears **6.48:1** at 0.7. The first is under
 * the 4.5:1 a label this size owes; dimmer than a link and still legible is
 * the whole requirement. (An earlier draft of this comment guessed "about
 * 2.4:1" — wrong, and wrong in the direction that oversells the fix.) */

/* The reason, in the same shape the harness tabs already use for theirs —
 * `.fa-harness-graph__note`. One appearance for one meaning across the two
 * surfaces, rather than a second look a reader has to learn. */

/* ═══ THE NAVBAR ICON ROW — line 2 of the fixed top ════════════════════════
 *
 * Owner, 2026-09-22: *"[x] should be on the navbar w/ other icons, can make
 * two lines avatar+name of harness/catalogue/sub-grrraph as approrirate, the
 * second line are the icons."*
 *
 * WHY TWO LINES AND NOT ONE. The single-line header was already over budget:
 * the title, three action controls and the strip's gutters share 264px, and
 * shrinking the title to fit (the first attempt at *"text on top navbar too
 * big"*) only moved the failure — "C@T Harness" then ellipsised to "C@..."
 * with the icons still crowding it. Measured, not guessed; it is in the
 * screenshots of that round. A second line gives the name the whole width and
 * the icons their own.
 *
 * WHICH icons is declared per instance and resolved through `needs`; this
 * stylesheet draws a row of unknown length and knows no ids.
 *
 * IN THE FIXED TOP because `mountNavIconRow` inserts it as a `.side-bar`
 * child, so `.side-bar > * { flex: 0 0 auto }` already places it. No rule
 * here puts it anywhere.
 * ───────────────────────────────────────────────────────────────────────── */

/* LINE 1 IS THE AVATAR AND THE NAME, and nothing else.
 *
 * The header carried three controls beside the title — the scheme mini, the
 * language mini and the panel launcher. With the title shrunk they still left
 * it ellipsised to "C@..." at 264px, which is the measurement that made the
 * second line necessary rather than merely tidier.
 *
 * NOTHING BECOMES UNREACHABLE. The panel those three sat beside holds Search,
 * Settings, Language, QR, Todos, Knowledge graph, JSON-LD and Source, and the
 * row's `launcher` opens that same panel through that same button. The minis
 * were shortcuts into a panel that is now one tap away.
 *
 * SCOPED TO `:has(.fa-nav-icons)`, which is the whole safety argument: the row
 * is mounted by script, so if the script does not run — or the instance
 * declares no icons and `mountNavIconRow` deliberately draws nothing — this
 * selector does not match and the three controls stay exactly where they are.
 * A reader never loses a control to a row that failed to arrive.
 *
 * LANGUAGE is the glass band's globe (#2211); the owner kept that one globe
 * rather than a second in the row (2026-10-05, bean `82qs`).
 */
@media (min-width: 50rem) {
  .side-bar:has(.fa-nav-icons) .fa-scheme-mini,
  .side-bar:has(.fa-nav-icons) .fa-lang-mini,
  .side-bar:has(.fa-nav-icons) .fa-tiles-toggle {
    display: none;
  }
}

/* THE ICON ROW'S OWN RULES moved to `navbar-row.css` (beans `lhvt`, `9rq1`):
 * the row is drawn on pages that never load this stylesheet, so its style
 * travels with its script. The `.side-bar`-specific rules stay here. */

/* ═══ THE NAME OF AN ICON, ON HOVER AND ON KEYBOARD FOCUS ═════════════════
 *
 * Owner's ruling on bean `ob3m` finding 1, 2026-10-01, option 1 of 4: *"show
 * each icon's name as a tooltip on hover or keyboard focus."* Captions that
 * are always visible were option 2 and were NOT chosen — the strip stays
 * `--fa-nav-collapsed` wide.
 *
 * WHICH CONTROLS: those carrying `data-fa-tip`, which `docs-ui.js` writes on
 * every control in the icon row with the same string as its `aria-label`.
 * Those are the controls whose name is in `aria-label` and nowhere a sighted
 * reader can see it, open or closed. A row with its own `.fa-nav-label` is
 * named beside its mark once the strip opens, and hover and focus open it.
 *
 * WHERE: beyond the strip's right edge, level with the control. `fixed` with
 * `top` left `auto` takes the STATIC position vertically, which is the
 * control's own line, and `left` is the strip's current width plus a gap,
 * from the same `--fa-nav-text` flag every other open rule reads. Fixed is
 * also what stops `.side-bar`'s `overflow: hidden` clipping it: an overflow
 * box clips a fixed descendant only when it is that descendant's containing
 * block.
 *
 * ONE NAME FOR A SCREEN READER: `content: … / ""` gives the generated text an
 * empty alternative, so the accessible name stays the `aria-label` and the
 * tooltip is never read as a second one. The first `content` is the fallback
 * for an engine that drops the alt syntax.
 *
 * INVERTED AGAINST THE SCHEME, so it reads as a tooltip over either page
 * rather than as more sidebar. #1f2328 on white and white on #1f2328 are
 * both 15.8:1.
 *
 * Inside `min-width: 50rem` only: below it the theme's own menu labels every
 * row in words, and there is no strip to sit beside. */
:root {
  --fa-tip-bg: #1f2328;
  --fa-tip-ink: #ffffff;
}
:root[data-fa-scheme="dark"] {
  --fa-tip-bg: #ffffff;
  --fa-tip-ink: #1f2328;
}
@media (min-width: 50rem) {
  .side-bar [data-fa-tip]::after {
    content: attr(data-fa-tip);
    content: attr(data-fa-tip) / "";
    position: fixed;
    left: calc(var(--fa-nav-collapsed) + var(--fa-nav-text, 1) * (var(--fa-nav-open) - var(--fa-nav-collapsed)) + 0.5rem);
    z-index: 2147483001;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    background: var(--fa-tip-bg);
    color: var(--fa-tip-ink);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: normal;
    text-transform: none;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
  }
  .side-bar [data-fa-tip]:hover::after,
  .side-bar [data-fa-tip]:focus-visible::after {
    opacity: 1;
  }
  /* THE MIRROR, ON AN RTL PAGE (bean `giiw`). The strip is docked to the
     RIGHT edge there (`[dir="rtl"] .side-bar`), so "beyond the strip's
     edge" is measured from the right. Measured on the Arabic home page at
     1400px before this rule: hovering Beans in the strip at x 1344-1400 drew
     "Beans — 535 open" at x 64, across the page from the icon it names.
     Keyed on the HTML element's `dir`, not the control's: the control is
     marked `dir="ltr"` as English text, and its tooltip still has to sit
     beside a strip that is on the right. */
  html[dir="rtl"] .side-bar [data-fa-tip]::after {
    left: auto;
    right: calc(var(--fa-nav-collapsed) + var(--fa-nav-text, 1) * (var(--fa-nav-open) - var(--fa-nav-collapsed)) + 0.5rem);
  }

  /* THE STRIP DOES NOT PEEK WHEN THE POINTER ARRIVES ON THE ICON COLUMN.
   *
   * Without this the tooltip above could never be seen for a pointer.
   * Measured on #1762's head, 1280x800: the pointer on Beans at (20,105)
   * widened the strip to 264px, which re-flows the column into a ROW, and the
   * pointer was then over NOTHING — the icon it was naming had moved up and
   * to the right. A name for something no longer under the pointer names
   * nothing.
   *
   * `.fa-nav-tip-hold` is set by `docs-ui.js` (`mountNavIconRow`) when the
   * pointer ENTERS the strip inside the column's at-rest box, and cleared the
   * moment it leaves that box. So arriving on an icon keeps the strip a strip
   * — the owner's ruling says it stays narrow — while arriving anywhere else
   * still peeks, and a reader already peeking can still reach the row's
   * icons (there is no [x] any more, ob3m finding 8): holding on `.fa-nav-icons:hover` alone was tried
   * first, and it collapsed the open bar under a pointer moving INTO the row
   * (four `navbar-row.e2e.ts` cases caught it).
   *
   * Re-asserts the rest geometry the way the stay-closed rule above does, at
   * a specificity that beats every open rule, rather than guarding each one.
   * NOT for KEYBOARD focus and NOT while pinned: those are the reader's
   * deliberate opening, and a keyboard reader must never tab through links
   * they cannot see. `:focus-visible` rather than `:focus-within`, because a
   * CLICK on a column icon focuses it too: with `:focus-within` the strip
   * opened on mousedown, re-flowed the column, and the mouseup landed on
   * another element — the click never arrived (`navbar-row.e2e.ts`'s
   * launcher case caught it). */
  .side-bar.fa-nav-tip-hold:hover:not(:has(:focus-visible)):not(:has(.fa-nav-open:checked)) {
    width: var(--fa-nav-collapsed);
    --fa-nav-text: 0;
  }
  .side-bar.fa-nav-tip-hold:hover:not(:has(:focus-visible)):not(:has(.fa-nav-open:checked)) > .fa-nav-icons {
    width: var(--fa-nav-collapsed);
    flex-wrap: wrap;
  }
}
@media (min-width: 50rem) and (prefers-reduced-motion: no-preference) {
  .side-bar [data-fa-tip]::after { transition: opacity .12s ease; }
}

/* ═══ THE OPEN DOCUMENT'S INDEX ════════════════════════════════════════════
 *
 * Owner: *"when a document or other indexed object is opened, the document
 * index/idices are shown in a navbar tab/menu."* Mounted by
 * `mountDocumentIndex` in docs-ui.js, which carries why this half is scripted
 * while the rail's is not.
 *
 * IN THE FIXED TOP, and it needs no rule to get there: it is inserted as a
 * `.side-bar` child, so `.side-bar > * { flex: 0 0 auto }` already makes it
 * one of the two regions that do not scroll.
 *
 * ITS OWN SCROLL, capped. The fixed top must not do to the middle what the
 * fixed bottom was doing before its cap — and a long page's index is exactly
 * the content that would. A third of the navbar, and it scrolls beyond that.
 *
 * CLOSED ON ARRIVAL, unlike the graphs group. The graphs are what the navbar
 * is FOR, so they arrive open; this is about one page, and a reader who
 * wanted the current page's structure is already looking at the page. It is a
 * `<details>` for the reason every disclosure here is one: keyboard-operable,
 * announces its own state, no script needed to open it.
 * ───────────────────────────────────────────────────────────────────────── */

/* ONE SCROLL REGION — the viewer rail's layout on a theme page.
 *
 * Owner ruling on bean ob3m finding 7, 2026-10-01: *"Use the viewer-rail
 * layout on Jekyll pages."* `mountSidebarRail` (docs-ui.js) moves "On this
 * page", the page list and a folded "Graphs" group (FOLDERS plus the harness
 * group) into `.fa-nav-middle`, so the middle is the ONLY region that scrolls.
 * What it replaced, measured on the built landing at 1280x800 with every group
 * open: the middle at its 128px floor showing 0 of 430 page links, the harness
 * group scrolling inside the footer, and `.side-bar` clipping 2108px into 800.
 *
 * So every region that used to scroll inside its own cap stops doing so once
 * it is inside the middle. From 50rem up the collapsed-strip rule already
 * replaces the index's `25%` cap and its scroll (`--fa-nav-text`); below it
 * nothing did, and the index would be a scroller inside the phone menu. */

/* NOT A SHRINKING REGION ANY MORE. The index's base rule is `flex: 0 1 auto`
 * because it once yielded inside its own cap; inside the middle -- a flex
 * column whose content is taller than it by design -- that yield squeezed the
 * OPEN index to a sliver under the Pages heading (measured on the built
 * onboarding guide: "On this page" clipped to one line, its 11 rows gone).
 * The middle scrolls; nothing inside it shrinks. */

/* FOLDERS AT THE FOOT OF THE SCROLLER (#2150): it sits where the "Graphs"
   wrapper sat, so it takes that wrapper's divider -- a rule ABOVE it, against
   the page list -- rather than its own one below, against ▦. */

/* The group no longer sits in a capped footer, so the scroll box the footer
   rule gave it would be a second scroller inside the first. */

/* The harness group beside Folders: the footer's size it had before the move,
   and the same divider, so the two pinned headings read as one set. */

/* ═══ EVERY ROW IN THIS NAVBAR IS A TARGET ═════════════════════════════════
 *
 * `ui-accessibility`: *"Targets are at least 24x24 CSS px (SC 2.5.8), and aim
 * higher: 32px for rows in a list … Density is cheaper than a missed target."*
 * This instance's declared interaction profile is low-dexterity
 * (`interaction/interaction.json`), so that is the binding constraint here
 * rather than a nicety.
 *
 * THREE ROWS WERE UNDER IT, measured in a browser at 1400x900 rather than read
 * off this file:
 *
 * | row | was | why it was missed |
 * |---|---|---|
 * | `.fa-doc-index__link` | 23px | `0.1875rem` padding on a 16.8px line — one pixel short |
 * | `.fa-nav-folders__link` | 23px | the same rule, copied |
 * | `.fa-harness-graph__link` | **13px** | an inline `<a>`, so its box IS its text |
 *
 * The repository already holds this floor in two places — the KG viewer's
 * `a11y.e2e.ts` and the language bar — and the navbar was in neither, which is
 * why three rows drifted under it unnoticed. `navbar-row.e2e.ts` now sweeps
 * every interactive element in `.side-bar`, so the next row added is measured
 * rather than trusted.
 *
 * `min-height` rather than more padding on the two block rows: padding alone
 * would push the text off-centre in a row whose height is set by its line, and
 * the point is the BOX a pointer has to hit. The inline one has to become
 * `inline-block` before a height applies to it at all.
 *
 * ## 1.75rem on the two primary rows, not 1.5
 *
 * The skill asks for the floor AND a margin above it — *"a target that is
 * barely legal is a target that is hard to hit … aim higher: 32px for rows in
 * a list"* — and `a11y.e2e.ts`'s own note says the suite holds the floor *"so
 * the comfort margin can be spent without silence"*. 24px exactly is the
 * legal minimum and nothing more.
 *
 * So the document index and the folder list, which are the navigation a
 * reader actually clicks, get **28px**. Measured cost, in a browser rather
 * than arithmetic: the folder list's 24 rows make it **772px** tall, inside a
 * region that already scrolls. That is the trade `ui-accessibility` names in
 * one line — *"density is cheaper than a missed target"*. (An earlier draft of
 * this paragraph computed 24 x 28 = 672 and wrote it as a measurement; the
 * list has margins the arithmetic did not.)
 *
 * The per-harness graph sub-links stay at the 24px floor plus their padding
 * (26px). They nest under a `.fa-harness-tab__body` that is already 40px, so
 * the reader reaching for a harness is not reaching for these; spending the
 * margin on six sub-rows per harness would push the fixed bottom past its cap
 * and cost the avatars the owner asked to keep.
 * ───────────────────────────────────────────────────────────────────────── */

/* An `h3` under the `h2` it follows. Indented by the SAME step `navbar.ts`
 * gives a sub-item — `--fa-nav-pad` plus the mark column plus its gap — so a
 * reader moving between the two surfaces meets one indent, not two. */

/* THE SUB-SECTIONS' FOLD (bean `r2ld`), under its section's link and at the
 * sub-items' indent, so the summary reads as the first row of what it hides.
 * Sized like the folders' "Sub-graphs of …" fold: a 1.75rem target. */

/* ═══ THE TONE IS A HUE, AND IT WAS BEING SPENT AS A COLOUR ════════════════
 *
 * `harness-tiles.ts` writes `tone` as an ANGLE — 268, 236, 96, 0 — and this
 * file's own comments say so twice: *"the avatar's declared hue, passed in as
 * `--fa-tile-tone`"*. Nine rules then fed that angle straight to
 * `color-mix()` and to a `border-left` shorthand.
 *
 * MEASURED IN A BROWSER, 2026-09-22, with a control rather than from the spec:
 *
 *     --h: 268; background: color-mix(in oklab, var(--h) 28%, transparent)
 *       -> rgba(0, 0, 0, 0)          INVALID at computed-value time
 *     --h: 268; background: hsl(var(--h) 45% 28%)
 *       -> rgb(69, 39, 104)          works
 *
 * So every harness divider rendered with **no background and no left stripe**.
 * The coloured tabs the owner asked for — *"as if you are opening a giant
 * tabbed folio"* — had no colour at all, and nothing caught it because an
 * invalid custom-property substitution fails SILENTLY: no parse error, no
 * console warning, just the initial value.
 *
 * THE TWO LIGHTNESSES ARE THE AVATAR GENERATOR'S OWN, not new ones:
 * `hsl(<tone> 46% 34%)` light and `hsl(<tone> 42% 72%)` dark, from
 * `gen-avatars-css.ts` — the same pair `.fa-site-mark--masked` already uses.
 * Picking a third here would remove one colour vocabulary while adding
 * another.
 *
 * `--fa-tile-on` IS THE ABSENCE, and it is why the saturation is computed
 * rather than declared. `GENERIC.tone` is **0**, and it means *"a question
 * mark — no avatar is declared for this kind"*, NOT red. Nine of the fourteen
 * harnesses are generic today, so saturating hue 0 would paint two thirds of
 * the navbar red on the strength of a missing declaration. At `0` the
 * saturation computes to `0%` and the divider is grey, which is what an
 * absence looks like.
 * ───────────────────────────────────────────────────────────────────────── */

.fa-harness-tab,
.fa-harness-section {
  --fa-tile-ink: hsl(var(--fa-tile-tone, 0) calc(46% * var(--fa-tile-on, 0)) 34%);
  /* WHAT GOES ON THE INK, in the same two schemes and derived the same way.
   *
   * The ink is a 34%-lightness blue here and a 72% one in dark, so a single
   * letter colour cannot sit on both — which is the `y8cm` failure this whole
   * block exists to have ended, and it would have come straight back the
   * moment a mark was painted at full ink. Tinted rather than pure white or
   * black so a knocked-out initial still reads as belonging to its hue. */
  --fa-tile-ink-on: hsl(var(--fa-tile-tone, 0) calc(30% * var(--fa-tile-on, 0)) 97%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-fa-scheme="light"]) .fa-harness-tab,
  :root:not([data-fa-scheme="light"]) .fa-harness-section {
    --fa-tile-ink: hsl(var(--fa-tile-tone, 0) calc(42% * var(--fa-tile-on, 0)) 72%);
    --fa-tile-ink-on: hsl(var(--fa-tile-tone, 0) calc(30% * var(--fa-tile-on, 0)) 12%);
  }
}

:root[data-fa-scheme="dark"] .fa-harness-tab,
:root[data-fa-scheme="dark"] .fa-harness-section {
  --fa-tile-ink: hsl(var(--fa-tile-tone, 0) calc(42% * var(--fa-tile-on, 0)) 72%);
  --fa-tile-ink-on: hsl(var(--fa-tile-tone, 0) calc(30% * var(--fa-tile-on, 0)) 12%);
}

.fa-harness-tabs {
  width: 100%;
  margin: 1rem 0 0;
  padding: 0.75rem 0 0;
  border-top: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.3));
}

.fa-harness-tabs__heading,
.side-bar .fa-nav-group > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  /* A disclosure a low-dexterity reader has to hit. Not the place to
   * economise on target size. */
  --fa-nav-rest-min: 2.25rem;
  min-height: var(--fa-nav-rest-min);
  padding: 0 0.5rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.fa-harness-tabs__heading::-webkit-details-marker,
.side-bar .fa-nav-group > summary::-webkit-details-marker {
  display: none;
}

.fa-harness-tabs__heading::before,
.side-bar .fa-nav-group > summary::before {
  content: "▸";
  font-size: 0.7em;
  transition: transform 120ms ease;
}

.fa-harness-tabs[open] > .fa-harness-tabs__heading::before,
.side-bar .fa-nav-group[open] > summary::before {
  transform: rotate(90deg);
}

.fa-harness-tabs__count {
  padding: 0 0.375rem;
  border-radius: 0.75rem;
  background: var(--fa-rule, rgba(128, 128, 128, 0.25));
  font-variant-numeric: tabular-nums;
}

.fa-harness-tabs__list {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
}

.fa-harness-tab {
  /* THE OFFSET. Each tab is inset a little further from the right than the one
   * above it, capped so a long list does not walk off the left edge — five
   * steps is where a notebook's dividers stop being distinguishable anyway. */
  --fa-tab-step: 0.5rem;
  margin: 0 0 2px;
  padding-right: calc(min(var(--fa-tab-i, 0), 5) * var(--fa-tab-step));
}

.fa-harness-tab__body {
  display: flex;
  align-items: center;
  /* Full width of the navbar, which the owner asked for twice. */
  width: 100%;
  min-height: 2.5rem;
  padding: 0 0.75rem;
  /* The tab shape: square where it meets the spine, rounded where it sticks
   * out. Horizontally oriented, per the owner's photo turned on its side. */
  border-radius: 0 0.375rem 0.375rem 0;
  background: color-mix(in oklab, var(--fa-tile-ink) 28%, transparent);
  border-left: 0.375rem solid var(--fa-tile-ink);
  color: inherit;
  font-weight: 600;
  text-decoration: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-fa-scheme="light"]) .fa-harness-tab__body {
    background: color-mix(in oklab, var(--fa-tile-ink) 34%, transparent);
  }
}

:root[data-fa-scheme="dark"] .fa-harness-tab__body {
  background: color-mix(in oklab, var(--fa-tile-ink) 34%, transparent);
}

a.fa-harness-tab__body:hover,
a.fa-harness-tab__body:focus-visible {
  background: color-mix(in oklab, var(--fa-tile-ink) 48%, transparent);
  text-decoration: none;
  /* The reach-for gesture, made visible: the tab comes out a little. */
  transform: translateX(0.125rem);
}

.fa-harness-tab__body--unlinked {
  /* Not a link, and it must not look like one. `pb04`: a dead link is worse
   * than no link. */
  opacity: 0.55;
  cursor: default;
}

/* THE MARK — the harness's avatar, or its initial.
 *
 * Sized to `--fa-nav-mark`, the SAME column `navbar.ts` gives its own mark, so
 * the two surfaces line their labels up at the same x. That is the point of
 * the shared geometry record: the strip is exactly this column and its two
 * gutters, on both sides.
 *
 * An INITIAL rather than a placeholder glyph when nothing is declared —
 * `navbar.ts`'s own rule, and the reason is the same one: `603s` reports "no
 * avatar declared" as a finding elsewhere, and a navbar is not the place to
 * render a gap as a `?`. Nine of fourteen harnesses take this branch today, so
 * it is the common case and not the edge. */
.fa-harness-tab__mark,
.side-bar .fa-nav-glyph {
  flex: none;
  width: var(--fa-nav-mark);
  height: var(--fa-nav-mark);
  margin-right: 0.5rem;
  object-fit: contain;
}

/* THE THEME AVATAR in a tab: the card art clipped to the cat. Same frame as
 * the plain mark, same column, so a tab with art and a tab with an initial
 * line their labels up. The four percentages come from the declaration via
 * `harness-tiles.ts`; nothing here knows a box. */
.fa-harness-tab__mark--art,
.side-bar .fa-nav-crop {
  position: relative;
  overflow: hidden;
  border-radius: 0.1875rem;
}

.fa-harness-tab__mark--art img,
.side-bar .fa-nav-crop img {
  position: absolute;
  max-width: none;
}

.fa-harness-tab__mark--initial,
.side-bar .fa-nav-tone {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.1875rem;
  /* THE TONE AT FULL STRENGTH, with the letter knocked out of it.
   *
   * Owner, 2026-09-23: *"no logo on who-iris icon (for now). just WHO blue"*.
   * who-iris is the only harness with no art, so this rule IS its mark — and
   * at the 55% mix it carried before, hue 199 arrived as a grey-blue square.
   * A tone diluted until it is not the colour anybody named is the same as not
   * having declared one.
   *
   * A generic harness still computes to grey: `--fa-tile-on` is `0` for a kind
   * with no declared avatar, so the saturation resolves to `0%` and this
   * paints the absence rather than painting hue 0 red. The dilution was never
   * what made that safe — that block above is. */
  background: var(--fa-tile-ink);
  color: var(--fa-tile-ink-on);
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1;
}

.fa-harness-tab__label,
.side-bar .fa-nav-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fa-harness-tab--floor .fa-harness-tab__body {
  /* The floor of the stack, and it says so: a dashed edge reads as "nothing
   * below this". Its POSITION is generated — `bootstrap`'s own declared
   * render exemption — so this styles the fact rather than asserting it. */
  border-left-style: dashed;
}

.fa-harness-tabs--deps {
  margin-top: 0.25rem;
  opacity: 0.8;
}

/* WHAT EACH HARNESS HOLDS, in the sidebar — the owner's *"also missing the
 * doc/, librayr/ etc."*
 *
 * Indented under its tab rather than given a disclosure of its own. A harness
 * carries two or three kinds here, and a `<details>` per tab would put a
 * chevron on every row to hide two lines behind it — the "they eat up all the
 * nav bar sspace" complaint answered by adding a click instead of removing
 * one. The DEPENDENCIES group above is already the disclosure that keeps this
 * list short.
 *
 * Quieter than the tab: the tab is the destination and these are what is
 * inside it, so they read as a sub-list and not as four more tabs. */
.fa-harness-tab__graphs,
.side-bar .fa-nav-kids {
  margin: 0.15rem 0 0.35rem;
  padding: 0 0 0 1.75rem;
  list-style: none;
  font-size: 0.6875rem;
}

.fa-harness-graph {
  margin: 0;
  line-height: 1.6;
}

.fa-harness-graph__link,
.side-bar .fa-nav-kids a {
  /* THE WORST OF THE THREE, at 26x13: an inline `<a>`, so its box was exactly
   * its text and no height applied to it. `inline-block` is what makes the
   * floor reachable at all; the padding is what a pointer aims at. See the
   * target-size block above `.fa-doc-index__link`. */
  display: inline-block;
  min-height: 1.5rem;
  /* WIDTH TOO, and it is the half a height fix leaves behind: SC 2.5.8 is
     24x24, and once the height was right this row was 20x32 because its text
     is the word "docs". Found by the sweep, not by looking. */
  min-width: 1.5rem;
  padding: 0.25rem 0;
  color: inherit;
  opacity: 0.72;
  text-decoration: none;
}

.fa-harness-graph__link:hover,
.fa-harness-graph__link:focus-visible {
  opacity: 1;
  text-decoration: underline;
}

/* A KIND WITH NO PUBLISHED VIEWER is shown and is not a link — `pb04`.
 *
 * The comment here used to argue that opacity alone would make "declared but
 * unbuilt" indistinguishable from "just a sub-item", and answered it with a
 * `line-through`. The argument was right and the answer was not: a struck-out
 * label means REMOVED, and a graph whose viewer has not been built yet has
 * not been removed from anything. It is now `.fa-harness-graph__note`, which
 * says which of the four reasons applies in words — a signal that is neither
 * colour nor typographic convention, and the only one of the three a screen
 * reader gets for free.
 *
 * `cursor: help` went with the `title` it promised. With no tooltip left to
 * reveal, a help cursor is a promise the row cannot keep.
 *
 * NO RATIO IS CLAIMED FOR THE DIM, because none can be: the ink is
 * `inherit`ed from the theme and this file does not own either end of the
 * pair. That is the whole reason the state is not left to the dim — 0.45 of
 * an unknown ink is unmeasurable, so it is decoration here and never the
 * signal. It is raised to 0.6 so the note beside it stays readable, still
 * below the 0.72 a live row carries. */
.fa-harness-graph__link--unlinked {
  opacity: 0.6;
  cursor: default;
}

/* THE REASON, beside the kind. Its own element so it can be quieter than the
 * label without the label having to carry two opacities, and inline so the
 * row keeps the one-line shape every other row in this list has. */
.fa-harness-graph__note {
  margin-left: 0.4em;
  font-size: 0.75em;
  font-style: italic;
  opacity: 0.6;
}

/* THE RENDERER'S HARNESS ROWS, which this file had never styled.
 *
 * Owner, 2026-09-27: *"expanded harnesses looks ugly. hard to tell whats
 * going on. need to be compacted"*. Since `sjic` the sidebar's harnesses are
 * `lib/navbar.ts` markup — `.fa-nav-row`, `.fa-nav-dead`, `.fa-nav-note`,
 * `.fa-nav-action` — and the shape for those classes lives in `navbarCss()`,
 * which the Jekyll page does not load. Only the rules above that were given a
 * second selector (`.side-bar .fa-nav-glyph`, `.fa-nav-tone`, `.fa-nav-kids`)
 * reached it, and they reached it WRONG: `.fa-nav-tone` is `display: flex`,
 * a block-level box, so every mark took a line of its own; the graph letters
 * were centred in whatever width their inline parent happened to have, which
 * is why "C", "D" and "F" sat at three different x positions; and with no rule
 * for `.fa-nav-note` it ran straight into its label — the owner's
 * *"codeno viewer yetdocs"*.
 *
 * THE RAIL'S SHAPE, not a new one: one line per harness (mark, name, ⚙) with
 * its graphs full-width beneath — `navbarCss()` states it as a grid for the
 * reason given there (a row and its control share one line while the children
 * take a second, which is two-dimensional) — and one line per graph with its
 * note pushed to the end of the row. Stated here in this file's tokens so the
 * mark column is `--fa-nav-mark` and the strip still lines up at rest. */
.side-bar .fa-nav-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
}

.side-bar .fa-nav-row > a,
.side-bar .fa-nav-row > .fa-nav-dead {
  display: flex;
  align-items: center;
  min-width: 0;
  /* The harness is the destination a low-dexterity reader reaches for, so it
     keeps the 2.5rem home gives its own row rather than a sub-row's height. */
  min-height: 2.5rem;
  padding: 0 var(--fa-nav-pad);
  color: inherit;
  font-weight: 600;
  text-decoration: none;
}

.side-bar .fa-nav-row > a:hover,
.side-bar .fa-nav-row > a:focus-visible {
  background: rgba(128, 128, 128, 0.18);
  text-decoration: none;
}

/* A mark with no avatar is its tone with the initial knocked out. The tone
 * arrives inline (`style="background:hsl(...)"`) when declared; otherwise the
 * `--fa-tile-ink` pair is only defined under `.fa-harness-tab`, which this
 * markup does not carry, so both fall back to a neutral pair rather than to
 * an invalid `var()` that paints nothing. */
.side-bar .fa-nav-row > a > .fa-nav-tone,
.side-bar .fa-nav-row > .fa-nav-dead > .fa-nav-tone {
  background: var(--fa-tile-ink, hsl(0 0% 40%));
  color: var(--fa-tile-ink-on, #f6f8fa);
}

/* THE ⚙, beside the row and never inside it (#1146). 2rem square: it is a
 * separate target from the row it sits on, so it gets its own clear of the
 * 24px floor rather than borrowing the row's. */
.side-bar .fa-nav-action {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-right: 0.5rem;
  padding: 0;
  border: 0;
  border-radius: 0.25rem;
  background: none;
  color: inherit;
  font-size: 0.875rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.7;
}

.side-bar .fa-nav-action:hover,
.side-bar .fa-nav-action:focus-visible {
  background: rgba(128, 128, 128, 0.18);
  opacity: 1;
}

/* THE GRAPHS: a full-width second line of the grid, indented so each small
 * badge sits under the harness NAME rather than under its avatar. */
.side-bar .fa-nav-row > .fa-nav-kids {
  grid-column: 1 / -1;
  margin: 0 0 0.25rem;
  padding: 0 var(--fa-nav-pad) 0 calc(var(--fa-nav-pad) + var(--fa-nav-mark) + 0.5rem);
}

.side-bar .fa-nav-kids > a,
.side-bar .fa-nav-kids > .fa-nav-dead {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  min-height: 1.5rem;
  padding: 0.125rem 0;
  font-weight: 400;
  opacity: 0.72;
}

.side-bar .fa-nav-kids > a:hover,
.side-bar .fa-nav-kids > a:focus-visible {
  opacity: 1;
  text-decoration: underline;
}

/* Same dim and cursor as `.fa-harness-graph__link--unlinked`, and for the
   reasons given there: not a link, and the dim is never the only signal. */
.side-bar .fa-nav-kids > .fa-nav-dead {
  opacity: 0.6;
  cursor: default;
}

.side-bar .fa-nav-kids .fa-nav-glyph {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  background: rgba(128, 128, 128, 0.22);
  color: inherit;
  font-size: 0.625rem;
}

.side-bar .fa-nav-kids .fa-nav-label { flex: 0 1 auto; min-width: 0; }

/* A graph typology's SVG glyph (bean `yag0`) — distinct per kind, so `docs` and
 * `library` are no longer two adjacent initials. Fills the chip above and
 * strokes in the inherited ink. */
.side-bar .fa-nav-glyph svg {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0.125rem;
  box-sizing: border-box;
}

/* The note is the same meaning as `.fa-harness-graph__note`, so the same
 * look — pushed to the row's end so it can never run into the label, and
 * `nowrap` so the row keeps the one-line shape. */
.side-bar .fa-nav-note {
  flex: none;
  margin-left: auto;
  padding-left: 0.5rem;
  font-size: 0.9em;
  font-style: italic;
  white-space: nowrap;
}

/* ═══ WHAT EACH HARNESS HOLDS — `harness_details.html` ═════════════════════
 *
 * The other half of the divider move. The counts and the viewer links were
 * not deleted when the sidebar became navigational; the owner chose "inside
 * the section it opens", and this is that section.
 * ───────────────────────────────────────────────────────────────────────── */

.fa-harness-sections {
  margin: 2rem 0;
}

.fa-harness-sections__lead {
  font-size: 0.875rem;
  opacity: 0.8;
}

.fa-harness-section {
  margin: 1rem 0;
  padding: 0.75rem 1rem;
  border-left: 0.375rem solid var(--fa-tile-ink);
  border-radius: 0 0.375rem 0.375rem 0;
  background: color-mix(in oklab, var(--fa-tile-ink) 10%, transparent);
}

.fa-harness-section__title {
  margin: 0 0 0.25rem;
  font-size: 1.0625rem;
}

.fa-harness-section__description {
  margin: 0 0 0.5rem;
  font-size: 0.875rem;
}

.fa-harness-section__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
  margin: 0 0 0.5rem;
  font-size: 0.8125rem;
}

.fa-harness-section__stat {
  display: flex;
  gap: 0.375rem;
}

.fa-harness-section__stat dt {
  opacity: 0.75;
}

.fa-harness-section__stat dd {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.fa-harness-section__viewers {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  margin: 0;
  padding: 0;
  font-size: 0.8125rem;
}

.fa-harness-section__gap,
.fa-harness-section__findings,
.fa-harness-section__subgraphs {
  font-size: 0.8125rem;
  opacity: 0.85;
  margin: 0;
}

/* LOCAL / REMOTE as a word in a box (`603s`). No colour of its own — the word
   is the signal, so it survives a missing stylesheet and reads the same to a
   screen reader; the box only lines the tags up. A REMOTE tag is drawn with a
   dashed border as a second, shape-based cue. */
.fa-harness-section__subgraphs ul { margin: 0.25rem 0 0; padding-left: 1rem; }
.fa-harness-section__where {
  display: inline-block;
  min-width: 3.75rem;
  padding: 0 0.3rem;
  border: 1px solid currentColor;
  border-radius: 3px;
  font-size: 0.75rem;
  text-align: center;
}
.fa-harness-section__subgraphs [data-fa-where="remote"] .fa-harness-section__where { border-style: dashed; }

.fa-harness-section__findings summary,
.fa-harness-section__subgraphs summary {
  cursor: pointer;
  min-height: 2rem;
  display: flex;
  align-items: center;
}

/* The inline board's way back — bean `l4zi`.
 *
 * Closing the landing board removes a section of the PAGE, so the control that
 * undoes it sits where the board was rather than in a launcher the reader has
 * to know about. Sized like the board's own controls: this instance's declared
 * interaction profile is low-dexterity, and a "get it back" button is the last
 * place to economise on target size. */
.fa-sticky-board-reopen {
  display: inline-flex;
  min-height: 2.75rem;
  align-items: center;
  padding: 0 1rem;
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.4));
  border-radius: var(--fa-folio-radius, 0.375rem);
  margin: 0.5rem 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
}

.fa-sticky-board-reopen:hover,
.fa-sticky-board-reopen:focus-visible {
  border-color: currentColor;
}

/* ═══ The linear floor — `_includes/generated/todo-listing.html` ═══════════
 *
 * The tile listing is the artefact and the board is the overlay, so this is
 * styled to be READ: a printable, translatable, screen-readable list that is
 * correct with no JavaScript at all. Nothing here hides it — `docs-ui.js`
 * moves it inside a `<details>` once the board is available, and a collapsed
 * disclosure is reachable while `display: none` is not.
 */
.fa-todo-listing {
  margin: 2rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.35));
}

.fa-todo-listing-heading {
  font-size: 1rem;
  margin: 0 0 0.25rem;
}

.fa-todo-listing-lead,
.fa-todo-listing-none {
  font-size: 0.8125rem;
  opacity: 0.8;
  margin: 0 0 1rem;
}

.fa-todo-listing-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* A TILE listing, in the owner's words — "on stndard folio just simple tile
   * based listing". `auto-fill` rather than a fixed count so one note is not
   * stretched across a desktop column and four are not slivers on a phone. */
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: 1rem;
}

.fa-todo-listing-item {
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.35));
  border-radius: 0.375rem;
  padding: 0.75rem 0.875rem;
  /* No fixed height. A sticky on the board is a fixed shape because it is laid
   * out spatially; a listing tile is read top to bottom, and clipping prose to
   * keep a grid tidy is how the board's own overflow defect started. */
}

.fa-todo-listing-summary {
  font-size: 0.9375rem;
  margin: 0 0 0.5rem;
}

.fa-todo-listing-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.125rem 0.5rem;
  font-size: 0.75rem;
  margin: 0 0 0.5rem;
}

.fa-todo-listing-meta dt {
  font-weight: 600;
  opacity: 0.75;
}

.fa-todo-listing-meta dd {
  margin: 0;
}

.fa-todo-listing-unresolved {
  opacity: 0.7;
  font-style: italic;
}

.fa-todo-listing-body {
  font-size: 0.8125rem;
}

.fa-todo-listing-body p {
  margin: 0 0 0.5rem;
}

.fa-todo-listing-empty {
  opacity: 0.7;
  font-style: italic;
}

.fa-todo-listing-links,
.fa-todo-listing-relations {
  font-size: 0.75rem;
  margin: 0.5rem 0 0;
}

.fa-todo-listing-relations {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
}

.fa-todo-listing-details > .fa-todo-listing {
  /* Inside the disclosure the rule above would draw a second line under the
   * summary. The listing keeps its spacing and loses the border only. */
  border-top: 0;
  margin-top: 0.5rem;
  padding-top: 0;
}

.fa-todo-listing-toggle {
  cursor: pointer;
  font-size: 0.8125rem;
  padding: 0.25rem 0;
}

/* A printed page has no board, so the floor is the only listing there is — but
 * a CLOSED `<details>` is not revealed by any stylesheet in Chromium, which
 * hides its contents with `content-visibility` on an internal slot rather than
 * with a rule a page can override. So the print case is handled in
 * `docs-ui.js`, on `beforeprint`, where it can be tested; a `@media print`
 * rule here would LOOK like it worked and quietly print nothing. */

/* ═══ Semantic zoom and windows on the board ═══════════════════════════════
 *
 * Two mechanisms, styled in two blocks that share nothing, because they ARE
 * two mechanisms: the slot's avatar treatment is driven by width, the window
 * layer by what a person opened. See `board-windows` and the note in
 * `docs-ui.js`.
 */

/* THE AVATAR IS ALWAYS THERE — "start everyrting in avatar". It is the control
 * that opens the card AND what semantic zoom leaves behind when the board
 * shrinks, which is why the two mechanisms meet at exactly one element. */
.fa-sticky-avatar {
  display: block;
  /* Low-dexterity profile: a board action is never a small target. */
  min-width: 2.75rem;
  min-height: 2.75rem;
  margin: 0 0 0.375rem;
  border: 1px solid transparent;
  border-radius: var(--fa-folio-radius, 0.375rem);
  background-color: transparent;
  cursor: pointer;
}

.fa-sticky-avatar:hover,
.fa-sticky-avatar:focus-visible {
  border-color: var(--fa-rule, rgba(128, 128, 128, 0.5));
}

/* Below the declared threshold the slot shows its avatar and nothing else.
 * `display: none` on the card rather than `visibility`, so the grid track
 * collapses to the avatar instead of keeping the card's footprint. */
.fa-sticky-slot--avatar > .fa-sticky,
.fa-sticky-slot--avatar > .fa-sticky-process {
  display: none;
}

.fa-sticky-slot--avatar {
  /* The avatar is the whole slot now, so centre it in the track it kept. */
  display: flex;
  justify-content: center;
}

/* THE AVATAR CARRIES THE NOTE'S FIRST WORDS — owner, 2026-10-01: *"if todo is
 * small zoomed, it shows no content at all"*. A glyph alone could not tell one
 * note from its neighbours. Shown only in the avatar state, under the glyph;
 * at full size the card says it. Collapsed and de-marked by `gistOf`. */
.fa-sticky-avatar-gist { display: none; }
.fa-sticky-slot--avatar {
  flex-direction: column;
  align-items: center;
}
.fa-sticky-slot--avatar > .fa-sticky-avatar-gist {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  max-width: 100%;
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.3;
  text-align: center;
  overflow-wrap: anywhere;
}

/* THE WINDOW LAYER. Projected onto the board, so it is positioned against the
 * board rather than living in the grid — an open card is not the card grown
 * large, and putting it back in the grid would make a zoom-out take it with
 * the card. */
.fa-board-windows {
  position: absolute;
  inset: 0;
  /* The layer itself must not swallow clicks meant for the grid beneath it;
   * only the panels in it are interactive. */
  pointer-events: none;
}

.fa-board-windows:empty {
  display: none;
}

.fa-board-window {
  position: absolute;
  /* BORDER-BOX, and it is load-bearing rather than tidy. The move code reads
   * geometry from `getBoundingClientRect()` — which measures the border box —
   * and writes it back as `width`/`height`. Under the default `content-box`
   * those two are not the same number, so every first nudge silently grew the
   * window by its border: 450 -> 452. Caught by the four-directions spec
   * asserting that a move round-trip returns to where it started. */
  box-sizing: border-box;
  top: 2rem;
  left: 2rem;
  width: min(28rem, calc(100% - 4rem));
  max-height: calc(100% - 4rem);
  overflow: auto;
  pointer-events: auto;
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.5));
  border-radius: var(--fa-folio-radius, 0.5rem);
  background: var(--fa-surface, Canvas);
  box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.25);
}

/* Each window is offset from the one below it, so a stack of three reads as
 * three rather than as one. Derived from `data-fa-z`, which is the stack's own
 * one-based index — nothing here decides the order.
 *
 * THE VERTICAL STEP IS LARGER THAN THE TITLE BAR, deliberately: a lower
 * window's bar stays visible under the one above it, so it can be selected —
 * and selecting any part raises it, which is what makes its own `[x]`
 * reachable again. A cascade tight enough to bury the bar would leave a
 * covered window with no way to the front, which is `l4zi` with extra steps.
 *
 * Moving and resizing a window is `le8b`'s, not this unit's. Until it lands a
 * deep stack does cover the grid beneath it, and that limit is real rather
 * than hidden: the keyboard path is unaffected, because focus does not care
 * what is painted on top. */
.fa-board-window[data-fa-z="2"] { top: 5rem; left: 2.5rem; }
.fa-board-window[data-fa-z="3"] { top: 8rem; left: 3rem; }
.fa-board-window[data-fa-z="4"] { top: 11rem; left: 3.5rem; }

.fa-board-window-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.5rem;
  border-bottom: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.35));
  font-size: 0.8125rem;
  font-weight: 600;
}

.fa-board-window-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fa-board-window-control {
  /* The frame's controls. `[x]` is the platform's and is always first; the
   * rest are what the kind declared. A kind fills the panel and chooses which
   * controls it offers; it does not get to remove the way out or move it. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Low-dexterity profile: a frame control is never a small target. */
  min-width: 2.25rem;
  min-height: 2.25rem;
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.4));
  border-radius: var(--fa-folio-radius, 0.25rem);
  background: transparent;
  color: inherit;
  cursor: pointer;
  line-height: 1;
  text-decoration: none;
}
/* The window bar's icons are the row's icons (#1925): the same inline SVGs,
 * filled by the text colour, at the row's size. The fish draws a little
 * larger, as it does in the row, because its outline is thin. */
.fa-board-window-control svg { width: 1.05rem; height: 1.05rem; fill: currentColor; }
.fa-board-window-control[data-fa-control="discard"] svg { width: 1.3rem; height: 1.3rem; }
/* Pressed is said by the border AND the fill, as the row's pin says it. */
.fa-board-window-control[data-fa-control="pin"][aria-pressed="true"] {
  border-color: currentColor;
  background: rgba(128, 128, 128, 0.22);
}

/* THE NODE BADGE, on the avatar and on the open window — one query, two
 * surfaces (R7). Sized so the chip reads at avatar scale without covering the
 * mark it rides on. */
.fa-node-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.25rem;
  border-radius: 0.625rem;
  background: var(--fa-rule, rgba(128, 128, 128, 0.35));
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
}

.fa-node-badge--avatar {
  /* On the avatar it rides the corner rather than sitting beside it: the
   * avatar IS the target, and a chip alongside would shrink it. */
  position: absolute;
  top: -0.25rem;
  right: -0.25rem;
}

.fa-sticky-avatar {
  position: relative;
}

/* The board is the window layer's containing block. Without this the panels
 * would position against the viewport and a scrolled board would leave them
 * behind. */
.fa-sticky-board {
  position: relative;
}

/* ═══ The fishbone's confirm ═══════════════════════════════════════════════
 *
 * `deletion-requires-confirmation` asks for what would go, so the dialog is
 * sized for two paragraphs of prose rather than for a one-line "Are you
 * sure?". The two paragraphs are the point: what this does, and what it does
 * not — and the second is the half a reader would otherwise assume.
 */
.fa-relocate {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  overflow: auto;
  background: var(--fa-surface, Canvas);
}

.fa-relocate-title {
  margin: 0;
  font-size: 0.9375rem;
}

.fa-relocate-scope p {
  margin: 0 0 0.5rem;
  font-size: 0.8125rem;
}

.fa-relocate-does-not {
  /* Set apart, because a reader skimming for the button is exactly the reader
   * this paragraph is for. */
  padding-left: 0.625rem;
  border-left: 0.1875rem solid var(--fa-rule, rgba(128, 128, 128, 0.5));
  opacity: 0.9;
}

.fa-relocate-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: auto;
}

.fa-relocate-actions button {
  /* Low-dexterity profile, and a confirm is the last place to economise on
   * target size: a mis-hit here is the one that costs something. */
  min-height: 2.75rem;
  padding: 0 0.875rem;
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.5));
  border-radius: 0.375rem;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.fa-relocate-cancel {
  /* The safe choice, and it is the one that carries the weight. Focus lands
   * here, so a reader who hits Enter without reading has left the content
   * where it is. */
  font-weight: 600;
}

/* Move mode. Announced through a live region as well as outlined, because a
 * reader who cannot see the outline still has to be told what their arrow keys
 * now do. */
.fa-board-window[data-fa-moving="true"] {
  outline: 2px dashed var(--fa-tile-ink, currentColor);
  outline-offset: 2px;
}

.fa-board-window-bar {
  /* The drag accelerator's handle. The keyboard path does everything this
   * does, so the cursor is a hint rather than the affordance. */
  cursor: move;
}

/* The reader's filter. In the board's head, and in no document anywhere. */
.fa-board-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
  padding: 0 0.5rem 0.5rem;
  font-size: 0.8125rem;
}

.fa-board-filter:empty {
  /* A folio whose todos never vary on a property gets no control for it, and
   * an empty row would read as a control that failed to load. */
  display: none;
}

.fa-board-filter-label {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  opacity: 0.75;
}

.fa-board-filter-select {
  /* Low-dexterity profile: a select is a target like any other. */
  min-height: 2.25rem;
  /* A node label is long (`sec:publication-workflow-…`) and a select sizes
   * to its widest option, which pushed the node filter off a phone screen. */
  max-width: 100%;
  padding: 0 0.375rem;
  border: 1px solid var(--fa-rule, rgba(128, 128, 128, 0.45));
  border-radius: var(--fa-folio-radius, 0.25rem);
  background: transparent;
  color: inherit;
}

.fa-sticky-filtered-out {
  font-size: 0.8125rem;
  opacity: 0.8;
}

/* A group heading when the reader groups the board (node, status, person,
 * bean). It spans the whole grid row, so the stickies under it start a fresh
 * row and every tile keeps its size. */
.fa-sticky-group-head {
  grid-column: 1 / -1;
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.fa-sticky-group-head:first-child { margin-top: 0; }
.fa-sticky-group-head[hidden] { display: none; }

/* ── No tile strip on the sticky board (issue #1905, bean `t6ht`) ─────────
 *
 * Owner, 2026-10-02: *"stickies panel shouldnt have all those icons"* —
 * reversing the 2026-09-21 ruling (`v0jv`) that put a `.fa-board-strip` of
 * square tiles along the top of every sticky board. Its rules went with it;
 * the tiles stay on the navbar and the glass strip. */

/* ── The sticky panel, slid away ──────────────────────────────────────────
 *
 * Owner, 2026-09-21: *"please!!! i dont want to see these giant cats at the
 * top anymore. priority #1 make the stikypanel slid away"* — and earlier,
 * *"they should be closed/tiled to start."*
 *
 * Markup: `_includes/landing.html`, a `<details>` with no `open`. Everything
 * here styles the two states; NOTHING here decides them, because a disclosure
 * a stylesheet can force open is one a reader cannot close.
 *
 * ## BOTH SCHEMES, with the ratios measured rather than remembered
 *
 * Stated up front because this rule was written in the same session that
 * filed bean `23bc` against `.fa-qr-toggle` and `.fa-tiles` for having no
 * light-scheme block at all — shipping a third element with the same hole
 * would have been the defect and its own bug report in one commit.
 *
 * Every pair below clears its threshold, and the thresholds differ: text on
 * its own fill is held at 4.5:1 (WCAG AA body text), a border at 3:1 because
 * it is a shape rather than a glyph.
 *
 * THE FILL IS NOT WHAT SEPARATES THE PANEL FROM THE PAGE — it measures 1.04:1
 * in dark and 1.21:1 in light, which is a surface-on-surface tint and nothing
 * more. The BORDER is what makes the panel an object (5.07:1 and 6.00:1
 * against the page), so it is not decoration and must not be dropped to
 * "clean up" the look.
 */
.fa-sticky-panel {
  /* DARK scheme. Ratios against --fa-stickypanel-bg unless stated. */
  --fa-stickypanel-bg: #232a33;
  --fa-stickypanel-fg: #e6e9ee;         /* 11.90:1                        */
  --fa-stickypanel-border: #8b97a8;     /*  4.89:1 here, 5.07:1 on the page */
  --fa-stickypanel-count-bg: #3b4654;
  --fa-stickypanel-count-fg: #e6e9ee;   /*  7.88:1 on the count's own fill  */

  margin: 0 0 1.5rem;
  border: 1px solid var(--fa-stickypanel-border);
  border-radius: 6px;
  background: var(--fa-stickypanel-bg);
  color: var(--fa-stickypanel-fg);
}

:root[data-fa-scheme="light"] .fa-sticky-panel {
  /* LIGHT scheme. The same five tokens, RE-MEASURED against #ffffff — a
     colour written for one scheme and never rechecked in the other is how
     `.fa-page-lang-bar` failed, and its note is a few hundred lines above. */
  --fa-stickypanel-bg: #e8eaed;
  --fa-stickypanel-fg: #1f2933;         /* 12.24:1                          */
  --fa-stickypanel-border: #5a6472;     /*  4.98:1 here, 6.00:1 on the page */
  --fa-stickypanel-count-bg: #d3d8de;
  --fa-stickypanel-count-fg: #1f2933;   /* 10.29:1 on the count's own fill  */
}

/* The handle is the whole row, not the triangle.
 *
 * This instance's declared interaction profile is LOW-DEXTERITY, so the target
 * is the full width at 2.75rem tall — comfortably past the 24px SC 2.5.8 floor
 * in the axis that is hardest to hit, and past it by enough to still be
 * comfortable rather than merely legal. `1le7` sizes the action tiles from the
 * QR code for the same reason. */
.fa-sticky-panel__handle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.35rem 0.75rem;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  /* `list-style: none` plus the WebKit pseudo-element: Safari ignores the
     former on a summary and keeps drawing its own disclosure triangle, which
     would sit beside the sticky glyph and read as two controls. */
  list-style: none;
}

.fa-sticky-panel__handle::-webkit-details-marker { display: none; }

/* Never left to the user agent. The panel's fill differs per scheme and the
   default focus ring is invisible on one of them; 2px is the floor the a11y
   spec here holds. */
.fa-sticky-panel__handle:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

.fa-sticky-panel__icon {
  width: 1.1rem;
  height: 1.1rem;
  flex: 0 0 auto;
  fill: currentColor;
}

.fa-sticky-panel__label { flex: 1 1 auto; }

/* The count travels with the COLLAPSED state, which is the whole point of
   putting it on the summary: a panel that hides its contents must still say
   how much it is hiding, or "closed" and "empty" render identically. Same
   rule `harness-tiles` states for the collapsed tab groups. */
.fa-sticky-panel__count {
  flex: 0 0 auto;
  min-width: 1.5rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--fa-stickypanel-count-bg);
  color: var(--fa-stickypanel-count-fg);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* The turned-up corner of the glyph rotates to point down when open — the
   disclosure affordance, carried by the icon that is already there rather
   than by a second chevron beside it. */
.fa-sticky-panel[open] .fa-sticky-panel__icon { transform: rotate(180deg); }

.fa-sticky-panel__body {
  padding: 0 0.75rem 0.75rem;
  border-top: 1px solid var(--fa-stickypanel-border);
}

/* THE SLIDE, and it is opt-in rather than default.
 *
 * `<details>` cannot animate its own height — the content goes from
 * `display: none` to laid out in one frame — so the open state animates the
 * BODY instead, which is a normal block and can. Guarded by
 * `prefers-reduced-motion` because a panel that slides is a panel that moves
 * under a reader who asked things not to, and the disclosure works identically
 * without it: this is decoration on a control that is already correct. */
@media (prefers-reduced-motion: no-preference) {
  .fa-sticky-panel[open] .fa-sticky-panel__body {
    animation: fa-sticky-panel-slide 180ms ease-out;
  }
  .fa-sticky-panel__icon { transition: transform 180ms ease-out; }
}

@keyframes fa-sticky-panel-slide {
  from { opacity: 0; transform: translateY(-0.4rem); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Inside the panel the board is content, not a page section, so it drops the
   margin it carries when it stands alone. */
.fa-sticky-panel__body > .fa-landing-board { margin-top: 0.75rem; }

/* ── Light scheme for the launcher, its panel, and the header mini-icons ──
 *
 * Bean `23bc`. Owner, 2026-09-21: *"class=\"fa-qr-toggle fa-tiles-toggle\"
 * does not respect light mode."*
 *
 * ## What was actually wrong, which is wider than the control named
 *
 * `.fa-qr-toggle` was `background: transparent; color: inherit;
 * border: 1px solid var(--border-color, …)` with NO `:root[data-fa-scheme="light"]`
 * block, so in light mode it kept whatever the compiled dark stylesheet had
 * put in `--border-color` and whatever the header happened to inherit. One
 * element out, `.fa-tiles` is `background: var(--sidebar-color, #27262b)` —
 * also with no light block — which is why the whole panel stayed dark on a
 * light page in the reported screenshot. Naming one control understated it.
 *
 * ## Why this is TOKENS rather than a light-mode patch
 *
 * The two schemes are not one palette recoloured, and the site is COMPILED
 * dark: `color_scheme: dark` in `_config.yml` means the theme's own variables
 * (`--border-color`, `--sidebar-color`, `--body-text-color`) hold dark values
 * whatever `data-fa-scheme` says, because that attribute is this file's
 * override and not the theme's. So inheriting from the theme is inheriting
 * the wrong scheme half the time. Every value below is stated explicitly and
 * re-measured in both, the way `.fa-page-lang-bar` was after it failed.
 *
 * Text is held at 4.5:1 and a border at 3:1. Every pair clears it:
 *
 *   glyph on the sidebar    dark 12.34:1   light 13.66:1
 *   border on the sidebar   dark  5.07:1   light  5.55:1
 *   panel text on panel     dark 12.34:1   light 14.76:1
 *   tile border on panel    dark  5.07:1   light  6.00:1
 *
 * ## It now covers three buttons, not one
 *
 * `.fa-scheme-mini` and `.fa-lang-mini` reuse `.fa-qr-toggle` for their box,
 * so they inherit this fix rather than needing their own. That is deliberate:
 * the mini-icons are the control a reader uses to CHANGE scheme, and a scheme
 * switch that is invisible in one of the two schemes is the worst possible
 * instance of this bug.
 */
.fa-qr-toggle,
.fa-tiles {
  /* DARK — the compiled default. */
  --fa-launcher-fg: #e6e9ee;
  --fa-launcher-border: #8b97a8;
  --fa-launcher-panel-bg: #27262b;
}

:root[data-fa-scheme="light"] .fa-qr-toggle,
:root[data-fa-scheme="light"] .fa-tiles {
  /* LIGHT — re-measured against the theme's light sidebar (#f5f6fa) and a
     white panel, NOT the dark values lightened. */
  --fa-launcher-fg: #1f2933;
  --fa-launcher-border: #5a6472;
  --fa-launcher-panel-bg: #ffffff;
}

/* These override the earlier `.fa-qr-toggle` / `.fa-tiles` rules by ordering,
   which is why they are stated as whole declarations rather than as a patch:
   a reader changing the block above must see this one lose or win, not guess.
   `color` in particular replaces `inherit`, which was the original defect. */
.fa-qr-toggle {
  border-color: var(--fa-launcher-border);
  color: var(--fa-launcher-fg);
}

.fa-tiles {
  border-color: var(--fa-launcher-border);
  background: var(--fa-launcher-panel-bg);
  color: var(--fa-launcher-fg);
}

.fa-tile {
  border-color: var(--fa-launcher-border);
  color: inherit;   /* now inherits the PANEL's per-scheme colour, set above */
}

/* The two header mini-icons. They are `.fa-qr-toggle` boxes, so everything
   above applies; all they add is the gap from the title and, for the bulb,
   the pressed state the launcher has no use for.

   NO caption and NO width of their own: the header row is capped at 3.75rem
   and shares its width with the site title (`1le7`), so three 1.75rem boxes
   is the budget. `.fa-qr-host`'s `min-width: 0` on the title is what lets the
   title ellipsis rather than push them off the row. */
.fa-scheme-mini,
.fa-lang-mini { margin-left: 0.25rem; }

/* `aria-pressed` is on the bulb because it toggles. Rendered as a filled box
   rather than only a border change: at 1.1rem the border is two pixels of
   signal, and this instance's interaction profile is low-dexterity, where a
   reader confirming a press should not have to inspect an edge. */
.fa-scheme-mini[aria-pressed="true"] {
  background: rgba(128, 128, 128, 0.22);
}

/* ── The band: the page's own controls, in one row ────────────────────────
 *
 * Owner, 2026-10-05 (issue #2201): the search and the locale selector both
 * live in the `fa-glass-band`, *"so they are always on the page, at least
 * partially"*, and the Folio tab hides while one of them is in use.
 * `glassBandSlot` in `docs-ui.js` says why the band moves into the display
 * panel when the first control arrives, and why a page with no controls keeps
 * the bare strip (`.fa-glass-band` above, unchanged).
 *
 * STICKY AT THE HANDLE'S OFFSET. At rest it is the display panel's first line,
 * below the theme header the handle sits in. Scrolled, it sticks where the
 * handle is, so it is still the opaque strip text scrolls under (#1693), and
 * both controls are on screen at every scroll position (#1732, which had the
 * magnifier sticky on its own 2.5rem lower).
 *
 * `align-items: flex-start`, not `center`: an open search hangs its status
 * lines BELOW the field, and nothing in the row may move when it does — the
 * owner's *"opening search must not force the locale selector onto the next
 * line"* is held as "same line, same top" by `search-band.e2e.ts`.
 *
 * Opaque, from the same per-scheme pair the magnifier row used, because text
 * scrolls under it: #27262b / #e6e9ee 12.34:1, #ffffff / #1f2933 14.76:1. */
.fa-glass-band[data-fa-band-tools] {
  --fa-search-border: #737d8c;
  --fa-search-page-bg: #27262b;

  position: sticky;
  top: var(--fa-staging-offset, 0px);
  right: auto;
  z-index: 89;
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  box-sizing: border-box;
  min-height: 2.5rem;
  padding: 0.125rem 0;
  margin: 0 0 0.75rem;
  box-shadow: none;
  background: var(--fa-search-page-bg);
  color: #e6e9ee;
}
:root[data-fa-scheme="light"] .fa-glass-band[data-fa-band-tools] {
  --fa-search-page-bg: #ffffff;
  color: #1f2933;
}
/* ON A REPLICA THE BAND IS AN OVERLAY — bean `uvt0`, owner 2026-10-06:
   "Overlay, no shift". A replica already reserves 2.25rem at the top of body
   for the handle (`g9r2`, the padding above), so the band is fixed over
   exactly that strip: the controls sit beside the handle, the replica keeps
   its layout, and `folio-mount.e2e` compares a TRANSLATED page again. In the
   flow, as a sticky row, it moved the replica's <main> down 52px. Its height
   is the reservation's, so it covers nothing below it; an open locale list
   or search hangs over the page only while it is open. */
.fa-glass-band[data-fa-band-tools][data-fa-band-overlay] {
  position: fixed;
  top: var(--fa-staging-offset, 0px);
  left: 0;
  right: 0;
  align-items: center;
  min-height: 0;
  height: 2.25rem;
  padding: 0 0.5rem;
  margin: 0;
}
.fa-band-slot {
  display: flex;
  align-items: flex-start;
  min-width: 0;
}
.fa-band-start { flex: 0 0 auto; }
/* The end slot takes the rest of the row, so an open field can grow into it;
   closed, its one child sits at the far end. */
.fa-band-end {
  flex: 1 1 auto;
  justify-content: flex-end;
}

/* THE FOLIO TAB STEPS ASIDE while a band item is open. `visibility`, not
   `display`, so the handle keeps its box and nothing shifts; hidden is also
   out of the tab order, and it comes back the moment the item closes. Never
   while the glass is OPEN: there the handle is the way back out. */
:root[data-fa-band-active] .fa-glass-handle:not([aria-expanded="true"]) {
  visibility: hidden;
}

/* KEEP THE PAGE WHERE IT IS WHILE SEARCHING. just-the-docs sets
   `.search-active .main { position: fixed }` at 50rem and up, for the
   full-screen overlay it draws search in. Search is not drawn there any more,
   and pinned mid-page that rule dropped the reader at the top of the page the
   moment they typed. `:has` scopes this to pages where docs-ui adopted the
   search; a page without it keeps the theme's behaviour. */
@media (min-width: 50rem) {
  .search-active .main:has(.fa-search-home) {
    position: relative;
    left: auto;
    right: auto;
  }
}

/* ── Search: a magnifier at the band's inline-end ─────────────────────────
 *
 * Owner, 2026-09-30 (issue #1715): one magnifier that opens the field across
 * the display panel; 2026-10-05 (issue #2201): the magnifier *"stays on the
 * right always, in both states, with the closed-state styling"*, one click
 * opens and one click closes, and the text is kept.
 *
 * ONE NODE, TWO STATES, switched by `data-open`, because the field must never
 * leave the document — just-the-docs finds its input by id and
 * `getElementById` does not see a detached node. Closing is `display: none`
 * on the holder, never a move of the input, which is also why the typed text
 * survives a close.
 *
 *   data-open="false"  the magnifier alone, at the band's inline-end.
 *   data-open="true"   the field, then the magnifier — still at the
 *                      inline-end, same box, same look. The field takes the
 *                      band's remaining width; what is not the field drops
 *                      below it.
 *
 * Inline-END, so on an RTL page it is at the left: the owner's "right" is
 * the end of the line, and mirroring is what every other control here does.
 *
 * The search paints no background of its own — the band does — so it
 * inherits the band's ink. Its BORDER cannot inherit, so it takes one that
 * clears 3:1 on every background it can land on:
 *
 *   #737d8c   on #ffffff 4.17:1   on #27262b 3.60:1   on #f5f6fa 3.86:1
 */
.fa-search-home {
  --fa-search-border: #737d8c;
  --fa-search-page-bg: #27262b;

  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.375rem;
  min-width: 0;
  color: inherit;
}
:root[data-fa-scheme="light"] .fa-search-home { --fa-search-page-bg: #ffffff; }

.fa-search-home[data-open="false"] .fa-search-holder { display: none; }
.fa-search-home[data-open="true"] { flex: 1 1 auto; }
.fa-search-home[data-open="true"] .fa-search-holder { flex: 1 1 auto; min-width: 0; }
/* The drop hangs from the FIELD, not from field-and-magnifier: its lines
   start and end under the field, and the magnifier is never covered. Named
   here because the theme positions the holder on some pages and not others. */
.fa-search-home .fa-search-holder { position: relative; }

/* The magnifier. 2.25rem — the field's own height — rather than to the glyph,
   because this instance's interaction profile is low-dexterity. ONE LOOK in
   both states (#2201): the round, ringed button the owner saw closed. Its
   state is `aria-expanded`, which is what a screen reader announces. */
.fa-search-peek {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--fa-search-border);
  border-radius: 50%;
  background: var(--fa-search-page-bg, transparent);
  color: inherit;
  cursor: pointer;
  line-height: 1;
}
.fa-search-peek svg { width: 1.1rem; height: 1.1rem; fill: currentColor; }

.fa-search-peek:hover,
.fa-search-peek:focus-visible {
  border-color: var(--link-color, #7253ed);
}
/* Never left to the user agent: the default ring is invisible on one scheme. */
.fa-search-peek:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

/* THE FIELD'S OWN BOX, because the theme does not give it one — bean `tcq2`.
 *
 * The theme makes `.search-input-wrap` `position: absolute` at desktop inside
 * a `.search` of height 0, so anything following the field was painted UNDER
 * it (a hit test at the field's centre once returned the preview notice).
 * Here the wrap is in flow, so `.search` has the field's height by itself.
 *
 * `.search` is `position: static` so the drop below positions against
 * the holder, which is the field's own width. Static, with this
 * specificity, also beats the theme's below-50rem
 * `.search-active .search { position: fixed }` full-screen takeover; the
 * padding, heights and shadow of the theme's mobile box are undone here too.
 *
 * The theme also caps the wrap at `max-width: 33.5rem` (and `width: 33.5rem`
 * while `.search-active`), sized for the header it renders search in. That
 * cap was 44% of the panel at 1280px; it is lifted. */
.fa-search-home .search {
  position: static;
  height: auto;
  min-height: 2.25rem;
  padding: 0;
}
.fa-search-home .search-input-wrap {
  position: relative;
  width: 100%;
  max-width: none;
  height: 2.25rem;
  box-shadow: none;
  border-radius: 0;
  /* `hidden` in the theme clipped the input's own focus ring. The theme's
     magnifier label that the clip was for is already a 1px sr-only box. */
  overflow: visible;
}

.fa-search-home .search-input {
  box-sizing: border-box;
  width: 100%;
  height: 2.25rem;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--fa-search-border);
  border-radius: 4px;
  background: transparent;
  /* Inherits the band's ink, for the reason at the top of this block. */
  color: inherit;
  font-size: 0.85rem;
}
.fa-search-home .search-input:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 1px;
}

/* EVERYTHING THAT IS NOT THE FIELD, BELOW IT — issue #2201.
 *
 * `.fa-search-drop` holds the results list, the theme's "Search everywhere"
 * button, its identifier-lookup links and a preview's index notice
 * (`docs-ui.js` says how they get there). It hangs from the bottom of the
 * row, the row's full width, so none of them can share the field's box — the
 * owner's *"the status/count line must never overlap the input text"*.
 *
 * The status lines come FIRST, directly under the field where the reader is
 * looking, and the results after them. Each line is its own opaque strip
 * because the drop sits over page text; the drop itself paints nothing and
 * takes no clicks, so the gaps between strips do not swallow a click meant
 * for the page. */
.fa-search-drop {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.25rem;
  margin-top: 0.25rem;
  pointer-events: none;
}
.fa-search-drop > * { pointer-events: auto; }
.fa-search-drop > .search-results { order: 2; }

.fa-search-home .search-everywhere,
.fa-search-home .search-remote,
.fa-search-home .fa-search-notice {
  box-sizing: border-box;
  margin: 0;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--fa-search-border);
  border-radius: 4px;
  background: var(--fa-search-page-bg);
  color: inherit;
  font-size: 0.8rem;
  line-height: 1.4;
  text-align: start;
  white-space: normal;
}
/* The theme's `.btn` gives the button a fixed height and a centred label. */
.fa-search-home .search-everywhere {
  height: auto;
  width: 100%;
  box-shadow: none;
  color: var(--link-color, #7253ed);
}
.fa-search-home .search-remote { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }

/* RESULTS: THE FULL WIDTH OF THE ROW, after the status lines.
 *
 * Opaque, because hits render over body text. `Canvas` was rejected for the
 * reason the pinned-sticky rule records — a SYSTEM colour follows the OS
 * scheme while the text follows the page's. */
.fa-search-home .search-results {
  position: static;
  width: auto;
  max-width: none;
  max-height: min(70vh, 32rem);
  overflow-y: auto;
  border: 1px solid var(--fa-search-border);
  border-radius: 4px;
  background: #27262b;
  color: #e6e9ee;
}
:root[data-fa-scheme="light"] .fa-search-home .search-results {
  background: #ffffff;
  color: #1f2933;
}

/* ── A board nested in the sticky panel has no chrome of its own ──────────
 *
 * Owner, 2026-09-21, looking at the staging preview: *"why are there two
 * panels???"*
 *
 * They were two because `.fa-sticky-board` paints a bordered, padded,
 * tinted box with its own heading — which is right for the overlay it is
 * everywhere else, and wrong inside `.fa-sticky-panel`, which already paints
 * exactly that. Nesting one in the other gave a box headed "Todos" inside a
 * box headed "Stickies".
 *
 * NOT a smaller border or a lighter tint. The chrome belongs to the OUTER
 * panel and is already there once; a second, quieter frame would still be a
 * second frame, and the question would come back as "why are there two
 * boxes".
 *
 * Every declaration here UNSETS something the base rule sets, which is why
 * they are spelled out rather than shorthanded away: a reader changing
 * `.fa-sticky-board` needs to see precisely what this cancels.
 */
.fa-sticky-board--bare {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

/* The head is empty when bare — the `<h2>` inside it is clipped to a pixel by
   `fa-sr-only` and the close button is not appended at all — so it would
   otherwise contribute its 0.8em bottom margin as a gap above nothing. */
.fa-sticky-board--bare > .fa-sticky-board-head { margin: 0; }

/* ── The filter row, compacted ────────────────────────────────────────────
 *
 * Owner: *"the todos controls are too clunky / take up too much real
 * estate."* (bean `qefk`, partial.)
 *
 * The row is two labelled selects, and at the panel's font size the labels
 * were reading as loud as the todo titles beneath them. Quieted rather than
 * removed: the labels are what make the selects say WHICH axis they filter,
 * and a bare pair of dropdowns is a guessing game.
 *
 * What is NOT done here: shrinking the select targets. They stay at their
 * full height because this instance's declared interaction profile is
 * low-dexterity, and "takes up less room" must not be bought with a target a
 * reader cannot hit. The saving comes from the label type and the gaps.
 */
.fa-sticky-board--bare > .fa-board-filter {
  gap: 0.35rem 0.6rem;
  margin: 0 0 0.6rem;
  font-size: 0.78rem;
}

.fa-sticky-board--bare > .fa-board-filter .fa-board-filter-label {
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  opacity: 0.72;
}

/* ── The source links, as a caption row under the sticky ──────────────────
 *
 * Owner, 2026-09-21: *"i want the [pencil] edit icon, (edit, view links can
 * be below, not inside stick)"*.
 *
 * They used to sit inside the card — in the sticky's own text box on the
 * landing page, and in the card head on a todo. A sticky is a note; these
 * links are about the FILE the note was declared in, which is a different
 * subject. On a card with backdrop art they also competed with the words for
 * the quiet part of the cloud.
 *
 * The grid item is the CELL now (`.fa-sticky-cell` in `landing.html`,
 * `.fa-sticky-slot` on the board), holding the sticky and this row beneath
 * it. The sticky keeps its declared aspect ratio; the caption adds its own
 * height below rather than eating into the art.
 */
.fa-sticky-cell {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.fa-sticky-links {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  /* Trailing, so the icons sit under the card's right edge and read as
     belonging to it rather than starting a new column of content. */
  justify-content: flex-end;
  margin: 0.3rem 0 0;
  padding: 0;
}

/* TARGET SIZE IS NOT NEGOTIABLE HERE. Icon-only controls are exactly where
   this gets quietly lost: the glyph is 1rem, and without a minimum the hit
   area would be the glyph. 1.75rem matches the header mini-icons, and this
   instance's declared interaction profile is low-dexterity — 24px is the SC
   2.5.8 floor and 28 is the comfort this file holds everywhere else. */
.fa-sticky-links .fa-node-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border-radius: 4px;
  /* The row is quiet until reached for — it is a caption, not an action bar.
     0.62 is about as far down as these go while still clearing 4.5:1 against
     the page in both schemes. */
  opacity: 0.62;
  text-decoration: none;
}

.fa-sticky-links .fa-node-edit svg {
  width: 1rem;
  height: 1rem;
  fill: currentColor;
}

.fa-sticky-links .fa-node-edit:hover,
.fa-sticky-links .fa-node-edit:focus-visible {
  opacity: 1;
  background: rgba(128, 128, 128, 0.18);
}

/* Never left to the user agent: these sit on the page background, which
   differs per scheme, and the default ring is invisible on one of them. */
.fa-sticky-links .fa-node-edit:focus-visible {
  outline: 2px solid var(--link-color, #7253ed);
  outline-offset: 2px;
}

/* ── The sidebar mark, one per scheme ─────────────────────────────────────
 *
 * `title.html` emits BOTH when the instance declares a `mark-dark`, and this
 * shows whichever matches. Liquid cannot choose: the scheme lives in
 * `data-fa-scheme` on `<html>`, written by `docs-ui.js` from the reader's
 * stored choice, which the server does not know.
 *
 * DARK IS THE DEFAULT here, matching `color_scheme: dark` in `_config.yml`.
 * The light mark shows only under the explicit light override, so a reader
 * who never touches the toggle gets the right one with no JavaScript having
 * run — which matters because `docs-ui.js` is `defer`red and would otherwise
 * flash the wrong mark on every page load.
 */
.fa-site-mark--light { display: none; }
.fa-site-mark--dark { display: inline-block; }

:root[data-fa-scheme="light"] .fa-site-mark--light { display: inline-block; }
:root[data-fa-scheme="light"] .fa-site-mark--dark { display: none; }

/* ── A sticky AWAY FROM HOME (bean `pv6g`) ────────────────────────────────
 *
 * A pinned sticky whose home panel is not on this page, drawn from its stored
 * text. Opaque for the reason every floating card is: it sits over page
 * content, and see-through words over words are unreadable. */
.fa-sticky-away {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.6rem 0.75rem;
  background: #1f1e24;
  color: #e8eaed;
  border: 1px solid #5a5670;
  border-radius: var(--fa-folio-radius, 0);
}
:root[data-fa-scheme="light"] .fa-sticky-away {
  background: #ffffff;
  color: #1b1b1f;
  border-color: #8a8699;
}
.fa-sticky-away-title { margin: 0; font-size: 1rem; }
.fa-sticky-away-text { margin: 0; font-size: 0.85rem; line-height: 1.35; }
.fa-sticky-away-home { color: inherit; text-decoration: underline; font-size: 0.85rem; }
.fa-sticky-away .fa-sticky-tools,
.fa-home-tools { display: flex; gap: 0.4rem; flex-wrap: wrap; justify-content: flex-end; }
.fa-sticky-sendhome,
.fa-home-tools .fa-sticky-move,
.fa-sticky-away .fa-sticky-move {
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.7rem;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.fa-home-pin { margin-top: 0.4rem; }

/* ── Touch: where a finger drags, and where it scrolls (bean `c132`) ──────
 *
 * `wireMove` takes a non-mouse drag only from a GRIP or in move mode. These
 * are exactly those, and `touch-action: none` is what stops the browser
 * claiming the gesture as a scroll before the page sees it. Everywhere else
 * a finger scrolls, so a long note is still readable on a tablet. */
[data-fa-grip],
.fa-sticky-floating[data-fa-moving="true"],
.fa-glass-asset[data-fa-moving="true"] {
  touch-action: none;
}

/* ── A PHONE gets one column (bean `c132`) ─────────────────────────────────
 *
 * Owner, 2026-09-23: *"Mobile phone is maybe linearized view due to space.
 * But tablet should be like laptops."* 37.5rem (600px) is the line: every
 * phone in portrait is under it and every tablet is over it, including an
 * iPad mini (744px) held upright.
 *
 * LINEARIZED, and !important is the mechanism, not a shortcut: each card
 * carries its laptop geometry as INLINE left/top/width/height (the reader's
 * saved place), and only !important outranks an inline style. The saved
 * place is left in the store untouched, so the same reader on a laptop finds
 * their cards where they put them. */
@media (max-width: 37.49rem) {
  /* Closed: nothing floats over the page. The handle says how many wait. */
  .fa-sticky-layer[data-fa-glass="closed"] > .fa-sticky-floating { display: none; }
  .fa-glass-handle[data-fa-count]::after {
    content: " \00b7 " attr(data-fa-count);
    font-variant-numeric: tabular-nums;
  }

  /* Open: the layer is ONE scrolling column, in reading order. */
  .fa-sticky-layer[data-fa-glass="open"] {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    overflow-y: auto;
    padding: 3.5rem 0.75rem 8.5rem;
  }
  .fa-sticky-layer[data-fa-glass="open"] > .fa-glass-sheet {
    position: static;
    inset: auto;
    padding: 0;
    overflow: visible;
    order: 1;
  }
  .fa-sticky-layer[data-fa-glass="open"] > .fa-sticky-floating {
    position: static !important;
    width: auto !important;
    height: auto !important;
    max-height: none;
    order: 2;
  }
  .fa-glass-shelf {
    height: auto !important;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }
  .fa-glass-shelf > .fa-glass-asset {
    position: static !important;
    width: auto !important;
    height: auto !important;
  }
  /* The phone's own pinch zooms the column, so the glass's zoom stands down:
     a second zoom over the browser's would be two answers to one gesture. */
  .fa-glass-shelf { transform: none !important; touch-action: auto; min-height: 0; cursor: auto; }
  /* The pop-out rides the folio's pan on the surface (`placePanel`); in the
     column there is no pan, so it stands with the shelf. */
  .fa-glass-panel { transform: none !important; }
  .fa-glass-zoom { display: none; }
  /* A position means nothing in a column, so the controls that set one go.
     Close and Return stay: they are about WHERE IT LIVES, not where it sits. */
  .fa-sticky-layer [data-fa-control="move"],
  .fa-glass-shelf [data-fa-control="size"],
  .fa-glass-tidy { display: none; }
}

/* ── The glass's filter (bean `7m6g`) ─────────────────────────────────────
 *
 * An attribute rather than `hidden`, because a pinned sticky and a shelf card
 * already use `hidden` for their own states, and the reader's filter must
 * never be confused with — or undo — one of those. `!important` because the
 * phone column sets `display` on the same cards. */
.fa-sticky-layer [data-fa-filtered-out] { display: none !important; }
.fa-glass-filter-select {
  min-height: 2.75rem;
  min-width: 12rem;
  padding: 0 0.5rem;
  font: inherit;
  color: var(--fa-glass-ink);
  background: var(--fa-glass-card);
  border: 1px solid var(--fa-glass-edge);
}
.fa-glass-filtered-note {
  max-width: 40rem;
  margin: 1rem auto 0;
  padding: 0.6rem 0.75rem;
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
  border: 1px dashed var(--fa-glass-edge);
}

/* ── A CARD'S METADATA, on hover and on focus — owner, 2026-10-01 ─────────
 *
 * *"hovering should show metadata."* One popover for the glass, outside the
 * scaled shelf, so it is drawn at reading size whatever the view's zoom; the
 * card's `aria-describedby` carries the same facts for a screen reader. */
.fa-glass-meta {
  position: fixed;
  z-index: 1000;
  pointer-events: auto;
  max-width: min(22rem, calc(100vw - 1rem));
  box-sizing: border-box;
  padding: 0.5rem 0.7rem;
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
  border: 1px solid var(--fa-glass-edge);
  border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  font-size: 0.85rem;
  line-height: 1.35;
}
.fa-glass-meta[hidden] { display: none; }
.fa-glass-meta-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.6rem;
  margin: 0;
}
.fa-glass-meta-list dt { font-weight: 700; }
.fa-glass-meta-list dd { margin: 0; overflow-wrap: anywhere; }

/* ── The avatar COVERS the card — owner, 2026-09-23 ──────────────────────
 *
 * *"artefact avatar should cover sheet"*; offered three shapes, the owner
 * chose **"Fills the card"**: the cover (or the kind icon) is the whole card's
 * background, like a sticky's picture, and the title and buttons sit over it
 * on a readable strip. The avatar is positioned against the CARD (which is
 * absolutely positioned, so it is the containing block), not the face. */
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) {
  justify-content: space-between;
  overflow: hidden;
}
.fa-glass-asset > .fa-glass-asset-face > .fa-glass-avatar {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  border: 0;
  z-index: 0;
  background: rgba(var(--fa-glass-tint), 0.35);
}
.fa-glass-asset > .fa-glass-asset-face > .fa-glass-avatar img {
  object-fit: cover;
  object-position: center top;
}
/* Filling a card, the note is square and centred, not stretched to the card. */
.fa-glass-asset > .fa-glass-asset-face > .fa-glass-avatar .fa-sticky-note-avatar {
  width: auto;
  height: 58%;
  aspect-ratio: 1;
  max-width: 70%;
}
.fa-glass-asset > .fa-glass-asset-face > .fa-glass-avatar .fa-glass-kind-avatar {
  --fa-avatar-size: 45%;
  width: 45%;
  height: 45%;
}
/* The words and the controls ride ABOVE the picture, each on its own strip of
   the card's colour, so they are legible over any cover at any opacity. */
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-face {
  align-items: flex-start;
  min-height: 0;
}
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-name,
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-tools {
  position: relative;
  z-index: 1;
  background: var(--fa-glass-card);
  padding: 0.35rem 0.5rem;
}
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-tools { margin: 0 -0.5rem -0.5rem; }
/* The avatar-state gist rides the same strip, so a todo's words sit over its
   plain note rather than through it. A themed sticky has no picture here —
   its art is the backdrop, faded by the theme's scrim — and keeps its ink. */
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-gist {
  margin: 0 -0.5rem;
  /* The strip's top and bottom are BORDERS, not padding: a clamped box clips
     at its padding edge, so bottom padding showed the top of the line after
     the ellipsis (measured on a two-line library caption). */
  padding: 0 0.45em;
  border-block: 0.2em solid var(--fa-glass-card);
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
}

/* ── A popped-out STICKY: the themed sticky, square, its words on top ────
 *
 * Owner, 2026-09-24: *"i expected to see themed square sticky avatar faded
 * with markdown overlayed when stikcy poopped out."* `dressGlassSticky` gives
 * the card the SAME `data-fa-sticky-theme` and `fa-sticky--backdrop` the board
 * and landing stickies carry, so the art, its clipping and the SCRIM that
 * fades it are the rules above (`.fa-sticky--backdrop …`) and the theme's
 * tokens are `themes.css`. These rules only seat that sticky on a glass card:
 * the theme's surface, ink and edge instead of the glass's, and room for the
 * words. No colour is decided here. */
.fa-glass-asset.fa-sticky--backdrop {
  /* The card is PLACED on the glass; the backdrop rule's `position: relative`
     would drop it into the flow and stack every sticky at the origin. */
  position: absolute;
}
.fa-glass-sticky { overflow: hidden; }
.fa-glass-sticky .fa-glass-asset-face { flex: 0 0 auto; min-height: 0; }
.fa-glass-sticky-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  /* Above the art and the scrim, which sit at negative z inside the card. */
  position: relative;
  z-index: 1;
  font-size: 0.9rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.fa-glass-sticky-body p,
.fa-glass-sticky-body ul,
.fa-glass-sticky-body ol { margin: 0 0 0.45em; }
.fa-glass-sticky-body ul,
.fa-glass-sticky-body ol { padding-left: 1.2em; }
.fa-glass-sticky-body .fa-md-heading { font-weight: 700; margin-top: 0.6em; }
.fa-glass-sticky-body a { color: inherit; text-decoration: underline; }
/* An UNTHEMED sticky keeps the plain note as its cover, so its words ride a
   strip of the card's colour exactly as the title and the tools do. */
.fa-glass-sticky:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-sticky-body {
  /* Sized to its words, not to the card, so the note behind still shows. */
  flex: 0 1 auto;
  max-height: 50%;
  background: var(--fa-glass-card);
  padding: 0.35rem 0.5rem;
}
.fa-glass-sticky[data-fa-sticky-theme] {
  background: var(--fa-sticky-surface, var(--fa-glass-card));
  color: var(--fa-sticky-ink, var(--fa-glass-ink));
  border-color: var(--fa-sticky-edge, var(--fa-glass-edge));
}
.fa-glass-sticky[data-fa-sticky-theme] .fa-glass-asset-name,
.fa-glass-sticky[data-fa-sticky-theme] .fa-glass-asset-tool {
  color: inherit;
}
.fa-glass-sticky[data-fa-sticky-theme] .fa-glass-asset-tool {
  background: transparent;
  border-color: var(--fa-sticky-edge, var(--fa-glass-edge));
}
/* Semantic zoom: below the declared width a sticky is its faded art alone,
   the same as every other card is its avatar alone. */
.fa-glass-asset[data-fa-zoom="avatar"] .fa-glass-sticky-body { display: none; }
.fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) .fa-glass-asset-name { margin: -0.5rem -0.5rem 0; width: calc(100% + 1rem); box-sizing: border-box; }
/* Zoomed out to its avatar, the cover is the whole card: no strip over it. */
.fa-glass-asset[data-fa-zoom="avatar"] .fa-glass-asset-tools { background: transparent; }
/* On a phone the column gives a card no height of its own, so a covered card
   keeps a book's shape. */
@media (max-width: 37.49rem) {
  .fa-glass-shelf > .fa-glass-asset:has(> .fa-glass-asset-face > .fa-glass-avatar) { min-height: 16rem; }
}

/* ── Issue #1900: open, and confirm the close ────────────────────────────
 *
 * A card is SIZED by its own − and + only (owner, 2026-10-05: *"No keyboard
 * resize thing. Only the plus minus"*), so there is no corner grip. */
/* A library card OPENS its entry, so it is a tab stop; its focus is drawn. */
.fa-glass-asset[data-fa-opens]:focus-visible {
  outline: 3px solid var(--fa-glass-accent);
  outline-offset: 2px;
}

/* THE CONFIRM × opens: a native modal, so the browser traps focus. Inside the
   layer, so the glass's theme tokens reach it. */
.fa-glass-confirm {
  box-sizing: border-box;
  max-width: min(28rem, calc(100vw - 2rem));
  padding: 1rem 1.25rem;
  border: 1px solid var(--fa-glass-edge);
  border-radius: var(--fa-folio-radius, 0);
  background: var(--fa-glass-card);
  color: var(--fa-glass-ink);
}
.fa-glass-confirm::backdrop { background: rgba(0, 0, 0, 0.45); }
.fa-glass-confirm-title { margin: 0 0 0.5rem; font-size: 1rem; }
.fa-glass-confirm p { margin: 0 0 0.6rem; font-size: 0.9rem; line-height: 1.45; }
.fa-glass-confirm a,
.fa-glass-shelved-say a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.fa-glass-confirm-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: flex-end; }
.fa-glass-confirm-actions button {
  /* Low-dexterity profile: a confirm is the last place to economise on target size. */
  min-height: 2.75rem;
  padding: 0 0.875rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
/* Focus lands on the safe choice, and it carries the weight. */
.fa-glass-confirm-cancel { font-weight: 600; }

/* WHERE IT WENT, said where the card was — and always present, empty until
   something is shelved, so the status region exists before it speaks. */
.fa-glass-shelved-say {
  max-width: 40rem;
  margin: 0 auto;
  font-size: 0.9rem;
  color: var(--fa-glass-ink);
  background: var(--fa-glass-card);
}
.fa-glass-shelved-say:not(:empty) {
  margin: 0.5rem auto;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--fa-glass-edge);
}

/* The browser-only note's dismiss. */
.fa-glass-local-note { position: relative; padding-right: 3.25rem !important; }
.fa-glass-settings-note { padding-right: 0 !important; }
.fa-glass-note-dismiss {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  min-width: 2.75rem;
  min-height: 2.75rem;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 1.2rem;
  cursor: pointer;
}
.fa-glass-more { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.fa-glass-more .fa-tile {
  width: 6.5rem;
  min-height: 5.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.35rem;
  border: 1px solid var(--fa-glass-edge);
  color: var(--fa-glass-ink);
  font-size: 0.75rem;
  text-align: center;
  text-decoration: none;
  position: relative;
}
.fa-glass-more .fa-tile svg { width: 1.6rem; height: 1.6rem; }

/* ── ONE STICKY: the tile and the icon row under it — issue #1925 ──────────
 *
 * Owner, 2026-10-02: *"dont treat stickies differently. combine best of
 * each. lower faded avatar/theme looks nicer. upper smaller same size closed
 * looks niceer. icons are a mess on both. make compact underneath."*
 *
 * So a landing sticky and a todo sticky are the same box: a SQUARE tile of one
 * declared size (`--fa-sticky-tile-size`), the theme's art behind the theme's
 * scrim — the same `.fa-sticky--backdrop` rules the full card uses, so the
 * fade is `--fa-sticky-scrim` and not a second number — and one row of icons
 * of one size beneath it. The card stays in its slot (its home: Pin clones
 * it, the window renders it) but is not drawn while the slot is a tile; the
 * class is added by the script, so with no script the landing cards show
 * whole, which is the floor (bean `z1ug`).
 *
 * A FIXED track, not `1fr`: the landing board and the todo grid inside it are
 * different widths, and `minmax(…, 1fr)` would stretch each to its own
 * container — two sizes of one component, the thing being fixed. */
:root { --fa-sticky-tile-size: 9rem; }
.fa-sticky-cell--tile > .fa-sticky { display: none; }
.fa-landing-board { position: relative; }
.fa-landing-board:has(> .fa-sticky-cell--tile),
.fa-sticky-grid:has(> .fa-sticky-cell--tile) {
  grid-template-columns: repeat(auto-fill, var(--fa-sticky-tile-size));
  gap: 0.5rem;
}
.fa-sticky-cell--tile {
  width: var(--fa-sticky-tile-size);
  align-items: stretch;
}
/* A sticky sent to fsh-guts is `hidden`, and the cell's own `display` would
 * otherwise beat the user agent's `[hidden]` rule (see `.fa-tiles-grid`). */
.fa-sticky-cell[hidden],
.fa-sticky-slot[hidden] { display: none; }
/* The process depth's left rule would push one tile right of its neighbours;
 * a tile names its process in words instead (`.fa-sticky-tile-process`). */
.fa-sticky-cell--tile.fa-sticky-slot-in-process { border-left: 0; padding-left: 0; }
.fa-sticky-tile.fa-sticky-avatar {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.2rem;
  width: var(--fa-sticky-tile-size);
  height: var(--fa-sticky-tile-size);
  min-width: 0;
  min-height: 0;
  margin: 0;
  padding: 0.45rem 0.5rem;
  overflow: hidden;
  isolation: isolate;
  border: 1px solid var(--fa-sticky-edge, rgba(128, 128, 128, 0.4));
  border-top: 3px solid var(--fa-sticky-accent, var(--fa-sticky-edge, rgba(128, 128, 128, 0.45)));
  border-radius: var(--fa-folio-radius, 0.375rem);
  /* The theme's surface AND ink, together, never one without the other: a
     themed surface under an inherited foreground is `23bc` again. With no
     theme, the page's own Canvas and CanvasText pair. */
  background: var(--fa-sticky-surface, Canvas);
  color: var(--fa-sticky-ink, CanvasText);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
/* The square crop, whole and faded — `cover` here because a tile IS square and
 * its crop is the square one; the full card keeps `contain` for its own shape. */
.fa-sticky-tile.fa-sticky--backdrop > picture > .fa-sticky-art { object-fit: cover; }
.fa-sticky-tile-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.fa-sticky-tile-process {
  overflow: hidden;
  font-family: var(--fa-mono, ui-monospace, monospace);
  font-size: 0.65rem;
  white-space: nowrap;
  text-overflow: ellipsis;
  opacity: 0.85;
}
.fa-sticky-tile .fa-node-badge--avatar { top: 0.25rem; right: 0.25rem; }
.fa-sticky-tile:hover { border-color: var(--fa-sticky-accent, var(--fa-rule, rgba(128, 128, 128, 0.7))); }
.fa-sticky-tile:focus-visible { outline: 3px solid var(--link-color, #7253ed); outline-offset: 2px; }
/* ON THE GLASS, the slot it left greys: the row's pressed pin says why. */
.fa-sticky-cell--tile.fa-sticky-slot-floating > .fa-sticky-tile { opacity: 0.45; }

/* THE ROW: four icons, one size, one order — view, edit, pin, send to
 * fsh-guts. 1.75rem is the target size the old View/Edit caption held (28px:
 * above the 24px SC 2.5.8 floor, at the comfort this file holds elsewhere for
 * a low-dexterity profile), and four of them fit under the tile. */
.fa-sticky-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* No fixed gap: `space-between` spaces them. A todo carries FIVE controls
   * (page, view, edit, pin, fsh-guts — #1941), and 5 × 1.75rem plus four
   * 0.25rem gaps is wider than the 9rem tile, which pushed fsh-guts under
   * the next tile and below the 24px target size (axe `target-size`). */
  gap: 0;
  width: var(--fa-sticky-tile-size);
  margin: 0.25rem 0 0;
}
.fa-sticky-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  opacity: 0.7;
  text-decoration: none;
  cursor: pointer;
}
.fa-sticky-act svg { width: 1.05rem; height: 1.05rem; }
.fa-sticky-act-discard svg { width: 1.3rem; height: 1.3rem; }
.fa-sticky-act-view svg,
.fa-sticky-act-edit svg,
.fa-sticky-act-pin svg { fill: currentColor; }
.fa-sticky-act:hover,
.fa-sticky-act:focus-visible {
  opacity: 1;
  background: rgba(128, 128, 128, 0.18);
}
.fa-sticky-act:focus-visible { outline: 2px solid var(--link-color, #7253ed); outline-offset: 2px; }
/* Pressed is said by the border AND the fill, not by colour alone. */
.fa-sticky-act-pin[aria-pressed="true"] {
  opacity: 1;
  border-color: currentColor;
  background: rgba(128, 128, 128, 0.22);
}

/* ── The confirmation before fsh-guts, and the fsh-guts list ───────────────
 * Native `<dialog>`s, opened modal, so the browser supplies the top layer,
 * the focus trap and Escape. Opaque and bordered in both schemes. */
.fa-fsh-confirm,
.fa-fsh-guts-dialog {
  max-width: min(32rem, calc(100vw - 2rem));
  padding: 1rem 1.1rem;
  border: 1px solid #5a5670;
  border-radius: var(--fa-folio-radius, 0.375rem);
  background: #1f1e24;
  color: #e8eaed;
}
:root[data-fa-scheme="light"] .fa-fsh-confirm,
:root[data-fa-scheme="light"] .fa-fsh-guts-dialog {
  background: #ffffff;
  color: #1b1b1f;
  border-color: #8a8699;
}
.fa-fsh-confirm::backdrop,
.fa-fsh-guts-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.fa-fsh-confirm-title { margin: 0 0 0.5rem; font-size: 1.05rem; }
.fa-fsh-confirm p { margin: 0 0 0.6rem; font-size: 0.9rem; line-height: 1.4; }
.fa-fsh-confirm-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; }
.fa-fsh-confirm-actions button,
.fa-fsh-guts-dialog-close {
  min-height: 2.75rem;
  min-width: 2.75rem;
  padding: 0 0.9rem;
  border: 1px solid currentColor;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.fa-fsh-confirm-ok { font-weight: 600; }
.fa-fsh-confirm-actions button:focus-visible,
.fa-fsh-guts-dialog-close:focus-visible { outline: 2px solid var(--link-color, #7253ed); outline-offset: 2px; }
.fa-fsh-guts-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.fa-fsh-guts-dialog-head h2 { margin: 0; font-size: 1.05rem; }

/* ── fsh-guts in the navbar's top (#1925) ─────────────────────────────────
 * The same row shape as ⌂ Home at the bottom: the glyph in the mark column,
 * the word and the count after it. The count is a pill that reads in both
 * schemes; its four states are text, so none is carried by colour. */
.fa-nav-fsh-guts {
  display: flex;
  flex: 0 0 auto;
  gap: 0.5rem;
  align-items: center;
  width: 100%;
  min-height: 2.5rem;
  padding: 0 var(--fa-nav-pad, 0.5rem);
  border: 0;
  border-bottom: 1px solid rgba(128, 128, 128, 0.25);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fa-nav-fsh-guts[hidden] { display: none; }
.fa-nav-fsh-guts:hover,
.fa-nav-fsh-guts:focus-visible { background: rgba(128, 128, 128, 0.18); }
.fa-nav-fsh-guts > .fa-nav-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  margin-right: 0;
}
.fa-nav-fsh-guts > .fa-nav-glyph svg { width: 1.15rem; height: 1.15rem; }
.fa-nav-fsh-guts .fa-nav-count {
  margin-left: auto;
  min-width: 1.25rem;
  padding: 0 0.3rem;
  border-radius: 0.625rem;
  background: rgba(128, 128, 128, 0.3);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* The copy inside a window is shaped to the window, not to the square dock. */
.fa-landing-window .fa-landing-sticky--fixed { aspect-ratio: auto; min-height: 16rem; }
/* THE LANDING WINDOWS OPEN BELOW THE TILES, not over them. On the todo board
 * an open window covers the grid (the limit `le8b` records); in a panel of a
 * few tiles that would bury every other tile under the first window a reader
 * opened — measured, the second tile could not be clicked. So the layer is
 * anchored to the board's BOTTOM edge with no height of its own, and the
 * windows (and their cascade) hang beneath the tile row, over the page. */
/* ONE STICKY, ONE WINDOW PLACE (#1925): a todo window on the inline board
 * opens below the board exactly as a landing window does, so an open window
 * never covers the tiles next to the one that opened it. */
.fa-sticky-board--inline > .fa-board-windows,
.fa-landing-windows {
  top: 100%;
  bottom: auto;
  height: 0;
  overflow: visible;
  z-index: 20;
}
.fa-sticky-board--inline > .fa-board-windows > .fa-board-window { max-height: 70vh; }
.fa-landing-windows .fa-board-window { top: 0.5rem; max-height: 70vh; }
.fa-landing-windows .fa-board-window[data-fa-z="2"] { top: 3.5rem; }
.fa-landing-windows .fa-board-window[data-fa-z="3"] { top: 6.5rem; }
.fa-landing-windows .fa-board-window[data-fa-z="4"] { top: 9.5rem; }
/* A landing window wears its sticky's surface and ink — both, together. */
.fa-landing-window {
  background: var(--fa-sticky-surface, Canvas);
  color: var(--fa-sticky-ink, CanvasText);
  border-color: var(--fa-sticky-edge, rgba(128, 128, 128, 0.5));
}
.fa-landing-window .fa-board-window-control { color: inherit; border-color: currentColor; }

/* ── HARNESSES CONFIG PANEL — issue #1146, the owner's "Hybrid" ───────────
 *
 * The sidebar ⚙ sits BESIDE its tab (a grid of tab + button), and every
 * target is at least 44px: the declared interaction profile is low-dexterity. */
.fa-harness-tab { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; column-gap: 2px; }
.fa-harness-tab > :not(.fa-harness-tab__body):not(.fa-harness-tab__config) { grid-column: 1 / -1; }
.fa-harness-tab__config {
  min-width: 2.75rem;
  min-height: 2.75rem;
  border: 0;
  border-radius: 0.375rem;
  background: color-mix(in oklab, var(--fa-tile-ink) 18%, transparent);
  color: inherit;
  font-size: 1.1rem;
  cursor: pointer;
}
.fa-harness-tab__config:hover,
.fa-harness-tab__config:focus-visible { background: color-mix(in oklab, var(--fa-tile-ink) 40%, transparent); }

.fa-hc { display: grid; grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr); gap: 1rem; margin-top: 0.5rem; }
.fa-hc-group h3, .fa-hc-title { font-size: 0.95rem; margin: 0.5rem 0 0.25rem; }
.fa-hc-group ul { list-style: none; margin: 0; padding: 0; }
.fa-hc-item {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.5rem;
  align-items: center;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--fa-glass-edge);
  border-radius: 0.375rem;
  margin: 0 0 0.25rem;
  background: transparent;
  color: var(--fa-glass-ink);
  text-align: left;
  cursor: pointer;
}
.fa-hc-item code { color: var(--fa-glass-muted); font-size: 0.8rem; }
.fa-hc-item[aria-current="true"] { border-width: 2px; font-weight: 600; }
.fa-hc-back { display: none; min-height: 2.75rem; }
.fa-hc-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.fa-hc-link { display: inline-flex; align-items: center; min-height: 2.75rem; color: var(--fa-glass-ink); text-decoration: underline; overflow-wrap: anywhere; }
.fa-hc-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.fa-hc-table th, .fa-hc-table td { text-align: left; vertical-align: top; padding: 0.25rem 0.4rem; border-bottom: 1px solid var(--fa-glass-edge); overflow-wrap: anywhere; }
.fa-hc-skills .fa-hc-link { min-height: 0; }
.fa-hc-gap, .fa-hc-note { color: var(--fa-glass-muted); }
.fa-hc-undeclared, .fa-hc-findings { margin-top: 0.5rem; }
.fa-hc-undeclared > summary, .fa-hc-findings > summary { min-height: 2.75rem; display: flex; align-items: center; cursor: pointer; }
.fa-hc-detail:empty::before { content: "Choose a harness to see its properties."; color: var(--fa-glass-muted); }

/* A PHONE DRILLS IN: the list, or one harness, never both squeezed. */
@media (max-width: 40rem) {
  .fa-hc { grid-template-columns: minmax(0, 1fr); }
  .fa-hc[data-fa-hc-view="list"] .fa-hc-detail { display: none; }
  .fa-hc[data-fa-hc-view="detail"] .fa-hc-list { display: none; }
  .fa-hc-back { display: inline-block; }
  .fa-hc-table thead { display: none; }
  .fa-hc-table tr { display: grid; grid-template-columns: minmax(0, 1fr); padding: 0.25rem 0; border-bottom: 1px solid var(--fa-glass-edge); }
  .fa-hc-table th, .fa-hc-table td { border: 0; padding: 0.1rem 0.25rem; }
}

/* ── Arranging tiles between the strip and More — bean `b8eq` ────────────
   Each tile in More carries its "Strip" button beneath it, and the strip's
   tiles are listed with a "More" button: the whole arrangement can be made
   by pressing (WCAG 2.5.7). Dragging highlights where a tile would land. */
.fa-glass-more-item { display: flex; flex-direction: column; align-items: stretch; gap: 0.3rem; }
.fa-glass-arrange {
  min-height: 2.75rem;
  padding: 0 0.6rem;
  border: 1px solid var(--fa-glass-edge);
  background: transparent;
  color: var(--fa-glass-ink);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.fa-glass-arrange:focus-visible { outline: 3px solid var(--fa-glass-accent); outline-offset: 2px; }
.fa-glass-arrange-hint { color: var(--fa-glass-muted); font-size: 0.85rem; margin: 0.25rem 0 0.5rem; }
.fa-glass-arrange-title { font-size: 0.95rem; margin: 1rem 0 0.4rem; color: var(--fa-glass-ink); }
.fa-glass-arrange-list { list-style: none; margin: 0; padding: 0; }
.fa-glass-arrange-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.25rem 0;
  border-top: 1px solid var(--fa-glass-edge);
}
.fa-glass-arrange-name { flex: 1 1 auto; font-weight: 600; }
[data-fa-draggable-tile] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
[data-fa-draggable-tile][data-fa-dragging] { opacity: 0.4; }
.fa-glass-tile-ghost {
  position: fixed;
  z-index: 2147483000;
  pointer-events: none;
  opacity: 0.9;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  background: var(--fa-glass-card, #1f2933);
}
.fa-sticky-layer[data-fa-drop="strip"] .fa-glass-tiles,
.fa-sticky-layer[data-fa-drop="more"] .fa-glass-panel[data-fa-panel="glass-more"] {
  outline: 3px dashed var(--fa-glass-accent);
  outline-offset: -3px;
}

/* ── A sub-graph's row, folded under its parent (issue #1164) ─────────── */

/* A SOURCE-LANGUAGE FALLBACK in a translated navbar says so — owner,
 * 2026-09-27. `docs-ui.js` (`mountNavLocale`) sets the attribute only on an
 * item with no page in the chosen locale, and only when that locale is not the
 * source, so an English navbar never shows the tag. */
.site-nav a[data-fa-source-locale]::after {
  content: " (" attr(data-fa-source-locale) ")";
  font-size: 0.8em;
}

/* The filter `mountTableFilters` puts above every long table (bean 0fua).
 * Colours are inherited rather than set, so it reads in both schemes without
 * an override; the input is 44px tall, the WCAG 2.5.5 target size, because the
 * pages it exists for are the ones read on a phone. */
.fa-table-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0.75rem 0 0.5rem;
}
.fa-table-filter label { font-weight: 600; }
.fa-table-filter input {
  flex: 1 1 14rem;
  min-width: 0;
  max-width: 28rem;
  min-height: 44px;
  padding: 0 0.75rem;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 4px;
}
.fa-table-filter-count { font-size: 0.875em; opacity: 0.85; }
/* A filtered-out row must stay out even where a table lays its rows out
 * itself (`.fa-hc-table tr { display: grid }` would otherwise beat `hidden`). */
table[data-fa-filtered] tr[hidden] { display: none !important; }

/* ── The render state: a client-built region, and what print does about it ──
 *
 * AT THE END OF THIS FILE DELIBERATELY, and the reason is coordination rather
 * than cascade. These rules belong beside the `@media print` block ~1340 lines
 * up; PR #1808 rewrites lines 1060-1340 of this file and was `ready-to-merge`
 * on 2026-10-02, and a sibling's in-flight work wins. Nothing here competes
 * with a selector above it, so the position costs nothing. Move it up when
 * that lands.
 *
 * ## What the two obligations are, and which half is enforceable
 *
 * `skills/ui/ui-core/ui-accessibility.md` §"A rendering built client-side owes
 * two things the static one gave for free", owner 2026-10-02: print and PDF
 * SHALL wait for load and render, and a load that FAILS SHALL say so.
 *
 * For a HEADLESS run the wait is enforceable, and the platform's headless
 * PDF path (its render wait) enforces it: it waits on `data-fa-render` and REFUSES to emit over a page
 * that is `pending` or `failed`, because a PDF is not re-checkable after the
 * fact the way a web page is.
 *
 * For an INTERACTIVE print it is not, and saying so plainly is better than
 * implying a mechanism exists. `beforeprint` is synchronous, a `fetch` cannot
 * be awaited inside it, and no browser lets a script hold the print dialogue
 * open. `kg-render.js` starts its requests at script evaluation — `defer`red,
 * so before `DOMContentLoaded` — which makes the common case ready long before
 * anybody reaches the print command. The rules below cover the uncommon one:
 * they do not make print wait, they make a premature print SELF-IDENTIFY on
 * the paper instead of leaving a silent gap.
 *
 * A `::before` on the region rather than `display: none` on it, for the same
 * reason `collapseFloor` opens its `<details>` from script rather than from a
 * stylesheet: hiding the region would print a page that looks complete and is
 * not, which is the exact failure this exists to prevent.
 *
 * TWO SELECTORS because there are two kinds of client-built region in the tree
 * today — `[data-fa-workplan]`, which `work-plan.js` fills on the
 * `beans-and-todos` pages, and `[data-fa-region]`, the generic container.
 * Both, rather than one and a promise: a print rule that matched nothing would
 * look like it worked, which is the `gjli` lesson from this repository's own
 * heading-anchor defect.
 *
 * Keyed on the attribute `head_custom.html` writes at first paint, so the rule
 * holds for a print fired before any script ran. A page that declares no
 * dynamic region carries no attribute and matches neither selector — the third
 * state, which must not render as either of the other two. */
@media print {
  html[data-fa-render="pending"] [data-fa-region]::before,
  html[data-fa-render="pending"] [data-fa-workplan]::before {
    content: "This section had not finished loading when this page was printed. "
             "The published data file named beside it is the complete record.";
    display: block;
    font-style: italic;
  }
  html[data-fa-render="failed"] [data-fa-region]::before,
  html[data-fa-render="failed"] [data-fa-workplan]::before {
    content: "This section could not be loaded when this page was printed. "
             "That is not an empty section \2014 see the linked data file.";
    display: block;
    font-style: italic;
  }
}

/* A client-built region that could not be loaded, and one that is genuinely
 * empty. `kg-render.js`'s `failureNote` and `emptyNote`.
 *
 * TWO CLASSES rather than one, because they are opposite facts — "could not be
 * read" against "there is nothing here" — and a reader who cannot tell them
 * apart has been handed the third-state failure this repository guards
 * everywhere else.
 *
 * The failure is NOT painted as an error: no red, no icon. It is a sentence
 * about what the reader is looking at, and the data file in it is a working
 * link. An alarming box would be louder than the fact and would not help
 * anybody read the artefact. */
.fa-render-failed,
.fa-render-empty,
.fa-workplan-failed {
  font-size: 0.9rem;
  line-height: 1.5;
  margin: 0.75rem 0 0;
}
.fa-render-failed code,
.fa-workplan-failed code { word-break: break-all; }

/* The publication-workflow page's process index (bean `ax6r`), drawn at runtime
   by assets/js/process-index.js. The two grouping buttons toggle with
   `aria-pressed`, so the pressed one is drawn like `.fa-figure-tools`'. */
.fa-process-index-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5rem 0 1rem;
}

.fa-process-index-controls button {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
  line-height: 1.4;
}

.fa-process-index-controls button:hover,
.fa-process-index-controls button:focus-visible {
  border-color: var(--link-color, #7253ed);
}

.fa-process-index-controls button[aria-pressed="true"] {
  border-color: var(--link-color, #7253ed);
  box-shadow: inset 0 0 0 1px var(--link-color, #7253ed);
  font-weight: 600;
}

.fa-process-index-table caption {
  text-align: left;
  font-weight: 600;
  padding: 0.75rem 0 0.25rem;
}

/* Narrow viewports: four columns at 390 px leave a few words per line and push
   the diagram links past the edge. Each row becomes a card whose cells say
   which column they were (`data-label`, set by the script). Scoped deeper than
   narrow-viewport.css's generic `table:not(...)` rule so this one wins. */
@media (max-width: 799.98px) {
  .fa-process-index .fa-process-index-tables table.fa-process-index-table,
  .fa-process-index .fa-process-index-tables table.fa-process-index-table tbody,
  .fa-process-index .fa-process-index-tables table.fa-process-index-table tr,
  .fa-process-index .fa-process-index-tables table.fa-process-index-table td {
    display: block;
    width: auto;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .fa-process-index .fa-process-index-tables table.fa-process-index-table caption {
    display: block;
  }

  .fa-process-index .fa-process-index-tables table.fa-process-index-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .fa-process-index .fa-process-index-tables table.fa-process-index-table tr {
    border: 1px solid var(--border-color, rgba(128, 128, 128, 0.45));
    border-radius: 4px;
    margin: 0 0 0.75rem;
  }

  .fa-process-index .fa-process-index-tables table.fa-process-index-table td {
    border: 0;
    padding: 0.35rem 0.75rem;
  }

  .fa-process-index .fa-process-index-tables table.fa-process-index-table td::before {
    content: attr(data-label);
    display: block;
    font-size: 0.75em;
    font-weight: 600;
    opacity: 0.75;
  }
}
