/* =====================================================================
 * DOCS-SITE STYLES
 * =====================================================================
 *
 * Everything that only exists to power the BrandOS docs site itself —
 * the layout shell (top nav, sidebar, TOC, footer), the docs page
 * chrome (page header, feedback widget), the auth UI, and the reusable
 * docs-only components (sticky bar, copy button, asset card, don't
 * card, token copy).
 *
 * None of this ships with the design system to other products — it
 * lives here so design-system.css stays portable.
 *
 * If you find yourself adding something to this file, ask: "would this
 * be useful in a fresh React app or marketing site?" If yes, it
 * probably belongs in design-system.css instead.
 *
 * Loaded after design-system.css. See CLAUDE.md §17 (Layer Discipline)
 * for the rules that govern this split.
 *
 * SECTIONS:
 *
 * Part A — Docs-site components (reusable patterns)
 *   1. Sticky Bar         — fixed sub-header used by docs nav
 *  1A. Toolbar           — page variant of the control bar used by the tools
 *   2. Copy Button        — affordance for copying tokens / snippets
 *   3. Token Copy         — inline copy chips inside generated token tables
 *   4. A–Z Index          — jump-to-letter rail (behaviour: assets/js/az-index.js)
 *   5. Asset Card         — brand-asset showcase pattern
 *   6. Don't Card         — "do/don't" rule cards used in brand docs
 *      (cross-page transition styles ship separately with the site)
 *
 * Part B — Docs-site shell (singletons + page chrome)
 *   8.  Docs code overrides  — markdown code rendering tweaks
 *   9.  Color palette        — brand colour styleguide rows
 *   10. Back to docs / top   — fixed nav buttons
 *   11. Docs layout          — content grid, sidebar offsets
 *   12. Top nav              — fixed header bar
 *   13. Site sidebar         — fixed left rail (collapsible)
 *   14. Nav sections         — sidebar disclosure tree
 *   15. Docs footer
 *   16. (vacated)            — .page-nav moved to design-system.css §38
 *   17. Page feedback        — bottom-of-page feedback widget
 *   18. Feedback modal
 *   19. Docs main content    — page header, scroll offsets
 *   20. Docs TOC             — sticky right rail
 *   21. Demo preview         — live demos in docs
 *  21B. Specimen             — variant sets on the styleguide presentation
 *   22. Docs utilities       — token tags, demo helpers
 *   23. Code blocks          — markdown pre/code wrapper
 *   24. Docs cards           — index/landing layout
 *   25. Responsive           — media queries for the docs shell
 *   26. Auth                 — user-info chip, login overlay, login page chrome
 * ===================================================================== */


/* ------ 1. BAR — page variants only ------
 * The component itself is core (design-system.css §41). What stays here is
 * what belongs to this shell: where a docked bar sits (below this site's
 * header rather than at the window top), and which content column each
 * variant's contents line up with. Keyed on data-width, because the column is
 * the only thing the two variants disagree on.
 *
 * Every value is re-pointed rather than overridden: no rule here has to
 * out-specify §41, so none of this breaks if §41's selectors change.
 */

/* Any bar in this shell that docks, docks under the site header — including
   the full-bleed ones, which carry no data-width. A token re-point, so it is
   inert on a bar that never sticks. */
.bar {
  --bar-top: var(--top-nav-height);
}

/* The docs column — the 1080px reading frame of .docs-content-grid. The left
   inset is the frame's own, not --bar-padding-x: the doc sub-header predates
   the merged component and its contents align with the docs grid, whose
   gutter is --space-l. */
.bar[data-width="docs"] .bar-container {
  max-width: 1080px;
  margin: 0 auto;
  padding-inline: var(--space-l) 0;
}

/* The tool column. Aligns the bar's controls with the page content below —
   every framed tool sits in .padding-global > .container-m. Adding the bar's
   own inline padding back onto the container width is what lands the first
   control on the same left edge as the tool content under the bar — not the
   page heading, which is centred in a wider column; --bar-padding-x and
   .padding-global are both var(--space-xl), so the two edges resolve to the
   same place. A tool built on .container-l would need its own value here. */
.bar[data-width="tool"] .bar-container {
  max-width: calc(var(--container-m) + (2 * var(--bar-padding-x)));
  margin-inline: auto;
}

/* §41 narrows --bar-padding-x to --space-l below 768px, which is right for an
   app shell where the bar is the whole chrome. Here it puts the bar's controls
   8px inside the page content they sit above, and a visible edge that nearly
   lines up reads worse than one that plainly does not. --bar-gap is
   deliberately left narrowed: that one is about density between groups, not
   about lining up with anything.

   .bar[data-width="tool"], not a bare shell class — §41's mobile block
   re-points the same property on .bar at equal specificity, so the attribute
   selector is what makes this win by specificity rather than by load order. */
@media (max-width: 768px) {
  .bar[data-width="tool"] {
    --bar-padding-x: var(--space-xl);
  }
}

/* The bar composes the core Breadcrumb component (design-system.css §18), which
   already knows it is in a bar — `.bar .breadcrumb` there centres it and steps
   the size up.
 *
 * What is left is truncation, which the component deliberately does not do: it
 * wraps to a second line, which is right in a page but wrong in a bar of fixed
 * height whose content region already scrolls horizontally. A documentation
 * title is long and arbitrary, so the current page is clipped with an ellipsis
 * and the section link before it is held on one line. */
.bar .breadcrumb {
  flex-wrap: nowrap;
  min-width: 0;
}

.bar .breadcrumb a {
  white-space: nowrap;
}

.bar .breadcrumb > [aria-current="page"] {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* ------ 2. COPY CHROME ------
 * .copy-btn itself is a core component (design-system.css §32).
 * Only the docs-site copy chrome lives here: the colour-palette copy
 * button and the auto-generated icon table columns.
 */

/* -- Colour palette copy button -- */
.color-copy-btn .copy-btn-default { display: inline-flex; align-items: center; gap: var(--space-s); }
.color-copy-btn .copy-btn-copied  { display: none; }

.color-copy-btn.is-copied .copy-btn-default { display: none; }
.color-copy-btn.is-copied .copy-btn-copied  { display: inline-flex; align-items: center; gap: var(--space-s); }

/* -- Icon table: auto-generated Copy + Download columns -- */
.icon-table td:first-child {
  text-align: center;
}

.icon-table td:first-child .svg-icn {
  width: 1.75rem;
  height: 1.75rem;
}


/* ------ 3. TOKEN COPY ------
 * Inline copy button used in token tables (generated by cms/generator/generate-docs.js).
 * Wraps a <code> chip so the cell stays readable but every value, variable, and hex
 * becomes one click away from the clipboard. Adds an inline swatch for hex values.
 */

/* Both of these have little or no padding, so the design system's inset
   focus ring would be drawn through their own text. Push it outside. */
.token-copy:focus-visible,
.color-copy-btn:focus-visible {
  outline-offset: 2px;
}

.token-copy {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  position: relative;
}

.token-copy code {
  transition: color 0.15s ease, background 0.15s ease;
}

.token-copy:hover code {
  color: var(--text-primary);
  background: color-mix(in srgb, var(--text-primary), var(--alpha-15));
}

/* Swatch dot for hex values */
.token-swatch {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: var(--border-s) solid var(--border-faded);
  vertical-align: middle;
  flex-shrink: 0;
}

.token-copy.is-copied code {
  color: var(--status-success);
}

/* Copy affordance icon — inline visual cue that the chip is clickable.
   Swaps to a check icon while .is-copied, then reverts. */
.token-copy::after {
  content: "";
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  flex-shrink: 0;
  background-color: currentColor;
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 14C8 15.1046 8.89543 16 10 16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V14ZM6 18V2H22V18H6ZM2 22V6H4V20H18V22H2Z'/%3E%3C/svg%3E");
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 14C8 15.1046 8.89543 16 10 16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V14ZM6 18V2H22V18H6ZM2 22V6H4V20H18V22H2Z'/%3E%3C/svg%3E");
  mask-size: contain;
  -webkit-mask-size: contain;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
  opacity: 0.4;
  transition: opacity 0.15s ease;
  pointer-events: none;
}

.token-copy:hover::after {
  opacity: 0.8;
}

.token-copy.is-copied::after {
  opacity: 1;
  color: var(--status-success);
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.54998 18L3.84998 12.3L5.27498 10.875L8.13576 13.7358C8.91681 14.5168 10.1831 14.5168 10.9642 13.7358L18.725 5.97501L20.15 7.40001L9.54998 18Z'/%3E%3C/svg%3E");
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.54998 18L3.84998 12.3L5.27498 10.875L8.13576 13.7358C8.91681 14.5168 10.1831 14.5168 10.9642 13.7358L18.725 5.97501L20.15 7.40001L9.54998 18Z'/%3E%3C/svg%3E");
}


/* NOTE: the book family (.book-shelf, .book-cover, .book-contents) moved to
 * design-system.css section 37 — see CLAUDE.md §17 (Layer Discipline) */


/* ------ 4. A–Z INDEX ------
 * The phone-book jump rail: one square per letter, letters with no entries
 * held in place as disabled spans so the alphabet never reflows as filters
 * change. Behaviour lives in assets/js/az-index.js (ported from the glossary
 * repo); only presentation is here. One consumer today — the glossary page —
 * which is what keeps this docs-site rather than core (CLAUDE.md §17 Rule 7).
 */

:root {
  /* Starting value only. az-index.js measures the rail and overwrites this on
     :root as it rewraps — one row of letters at desktop width, up to three on
     a phone. Nothing should rely on the number here; it exists so the jump
     offset below is sane between first paint and the rail being built. */
  --az-index-height: 3rem;
}

.az-index {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  align-items: center;

  /* Docks under whatever the page already docks — header, plus the sticky
     bar when one is present. --scroll-offset (section 11 below) carries that
     sum plus one --space-xl of breathing room for anchor targets; the rail
     wants the dock line itself, so the breathing room comes back off. */
  position: sticky;
  top: calc(var(--scroll-offset) - var(--space-xl));
  z-index: 1;

  padding-block: var(--space-s);
  background-color: var(--background-primary);
}

.az-index-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;

  /* Fixed square so the rail does not reflow between narrow letters (I) and
     wide ones (W) as the active state changes. */
  min-width: 2rem;
  min-height: 2rem;

  font-size: var(--font-s);
  font-family: var(--font-primary);
  text-transform: uppercase;
  text-decoration: none;

  color: var(--text-secondary);
  background-color: var(--bg-faded-5);
  border-radius: var(--radius-xs);

  transition:
    background-color var(--duration-xs) var(--ease-out),
    color var(--duration-xs) var(--ease-out);
}

/* One step up the faded ladder, not a cross to --background-faded — that
   token resolves to the same 5% as the rest state in light mode, which
   would leave the hover animating nothing. */
.az-index-letter:hover {
  color: var(--text-primary);
  background-color: var(--bg-faded-10);
}

.az-index-letter:focus-visible {
  outline: 2px solid var(--input-focus);
  outline-offset: 2px;
}

.az-index-letter.is-active {
  color: var(--selected-text);
  background-color: var(--selected-background);
}

/* Letters with no entries stay in place rather than being removed, so the
   alphabet does not shift under the cursor as filters change. */
.az-index-letter.is-disabled {
  color: var(--text-faded);
  background-color: transparent;
  pointer-events: none;
}

/* -- Glossary page tuning --
 * The interactive term list on /glossary.html: search field, letter rail,
 * letter-grouped book-contents lists of details rows (design-system.css §37).
 * Structure is authored in cms/glossary.md; behaviour is assets/js/glossary.js.
 */

/* A lookup box rather than a form control in a column of controls, so it
   reads a step up from body size. Everything else comes from the design
   system's input[type="search"] rules — and the element selector is what
   makes this win: those rules are input[type="search"] at (0,1,1), so a bare
   .glossary-search at (0,1,0) loses on specificity and the size never lands.
   input.glossary-search ties at (0,1,1) and wins on source order. */
input.glossary-search {
  font-size: var(--font-l);
}

/* Jumping to a letter has to clear the header, any docked bar, AND the sticky
   rail itself. --scroll-offset covers the first two plus breathing room; the
   rail's measured height is added on top.

   The [id] is load-bearing, not decoration: design-system.css §39B sets
   scroll-margin-top on `section[id]` at (0,1,1), and a letter group is a
   <section> with an id, so a bare .az-group at (0,1,0) would silently lose
   and the heading would land under the rail. [id] takes this to (0,2,0).
   Same trap the glossary repo documents in its own stylesheet — do not
   simplify it back. */
.az-group[id],
.glossary-terms details[id] {
  scroll-margin-top: calc(var(--scroll-offset) + var(--az-index-height));
}

/* Density re-point for a 13-row page: the shipped --font-4xl title is
   index-page scale. Token re-point on the declaring element, scoped to this
   page's wrapper so the variant keeps its shipped size everywhere else. */
.glossary-terms .book-contents-item {
  --book-contents-item-title-size: var(--font-2xl);
}


/* ------ 5. ASSET CARD ------
 * Generic showcase card for presenting a single brand asset with context.
 * The wrapper, preview frame, and footer stay consistent — only the
 * content inside the preview slot changes:
 *
 *   logo SVG • font specimen • brand icon • brand image • token
 *
 * Three composable parts:
 *
 *   .asset-card
 *     .asset-card-preview       — centred visual area (auto min-height)
 *     .asset-card-footer        — title + actions row at the bottom
 *
 * Light/dark variants set the preview background — useful for any visual
 * asset that needs to be tested against both surfaces:
 *   .asset-card-preview--light  — uses --background-faded
 *   .asset-card-preview--dark   — uses --neutral-950 + light text
 *
 * Edges stay square — no border-radius (project rule).
 *
 * See cms/asset-card.md for usage.
 */

.asset-card {
  display: flex;
  flex-direction: column;
  border: var(--border-s) solid var(--border-faded);
  background: var(--background-plain);
}

.asset-card-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-height: 220px;
  padding: var(--space-3xl);
}

.asset-card-preview--light {
  background-color: var(--background-faded);
  color: var(--text-primary);
}

.asset-card-preview--dark {
  background-color: var(--neutral-950);
  color: var(--white);
}

.asset-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  padding: var(--space-m) var(--space-l);
  border-top: var(--border-s) solid var(--border-faded);
  background: var(--background-plain);
}

.asset-card-title {
  margin: 0;
  font-family: var(--font-primary);
  font-size: var(--font-l);
  font-weight: var(--font-weight-regular);
  color: var(--text-primary);
  line-height: 1.2;
}

.asset-card-actions {
  display: flex;
  gap: var(--space-xs);
  flex-shrink: 0;
}

/* Compact icon-only buttons inside the actions slot */
.asset-card-actions .button {
  padding: var(--space-s);
}

/* Logo preview — constrains the rendered logo width inside an asset card.
 * Apply alongside .asset-card-preview when the slot contains a logo image.
 * Change the max-width here to resize every logo card at once. */
.asset-card-preview.logo-preview img {
  max-width: 200px;
  width: 100%;
  height: auto;
  max-height: 200px;
}

/* Image fill — edge-to-edge <img> previews (OG images, photographic assets).
 * Removes the slot padding; the image spans the full width at its own ratio.
 * No inline styles needed on the <img>. */
.asset-card-preview--fill {
  padding: 0;
}

.asset-card-preview--fill img {
  display: block;
  width: 100%;
  height: auto;
}

/* Contain — template previews of any ratio (feed 4:5, story 9:16, banner 6:1).
 * The image keeps its aspect ratio and is capped to the stage, so tall and
 * wide templates sit at a consistent, legible size instead of one spanning
 * full width while another shrinks. Cards in a grid stay equal height; the
 * image just centres in whatever vertical space its row gets. */
.asset-card-preview--contain {
  padding: var(--space-xl);
}

.asset-card-preview--contain img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 600px;
  object-fit: contain;
}


/* ------ 6. DON'T CARD ------
 * .dont-card extends .card to communicate a "don't" rule —
 * used in brand and design-system docs to flag prohibited
 * patterns.
 *
 *   <div class="card dont-card">
 *     <img class="dont-card-media" src="..." width="800" height="450" loading="lazy" alt="">
 *     <div class="svg-icn dont-card-icon">{{icon:close-circled}}</div>
 *     <h4 class="card-title">Rule title</h4>
 *     <p class="card-description">Why it's a don't.</p>
 *   </div>
 *
 * Pair with .grid.cols-2.gap-m for a rules grid. The media slot shows
 * the failure it warns against; a placeholder holds it until the real
 * example lands.
 */

.dont-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}

.dont-card-media {
  display: block;
  width: 100%;
  height: auto;
}

.dont-card-icon {
  color: var(--status-danger);
}

.dont-card-icon .svg-icn {
  width: 2rem;
  height: 2rem;
}

.dont-card .card-title {
  font-size: var(--font-l);
  margin-bottom: 0;
}

.dont-card .card-description {
  font-size: var(--font-s);
}

/* =====================================================================
 * PART B — DOCS-SITE SHELL
 * =====================================================================
 *
 * Layout, navigation, page chrome, and auth UI for the BrandOS docs
 * site. Singletons (one top nav, one sidebar, one footer) plus the
 * media queries that adapt the shell to small screens.
 * ===================================================================== */


/* ------ DOCS CODE OVERRIDES ------ */

/* Docs-specific code rendering for markdown content */
code br {
  display: none;
}

del code {
  text-decoration: inherit;
}

samp {
  font-family: var(--font-quaternary);
  font-size: var(--body-size);
}

/* -- Code blocks (docs overrides) -- */
pre {
  margin-top: 0;
  margin-bottom: 0;
  word-wrap: normal;
  line-height: var(--line-height-2xl);
  background-color: var(--background-darker);
  transition: border-color 0.3s ease;
  min-height: 67px;
}

pre > code {
  white-space: pre;
}

pre code {
  display: inline;
  overflow: visible;
  line-height: var(--line-height-2xl);
  word-wrap: normal;
}

/* -- Code inside headings -- */
h1 code,
h2 code,
h3 code,
h4 code,
h5 code,
h6 code {
  padding: 0 var(--space-2xs);
  font-size: inherit;
}

/* -- Horizontal rules (docs spacing override) -- */
hr {
  margin: var(--space-2xl) 0;
}

/* -- Page layout structure -- */
.page-wrapper {
  width: 100%;
}

.page-content {
  width: 100%;
}


/* ------ COLOR PALETTE (STYLEGUIDE) ------ */

.color-list {
  overflow: hidden;
}

.color-row {
  display: flex;
  align-items: center;
  padding: var(--space-xl) var(--space-xl);
  font-size: var(--font-m);
  font-weight: var(--font-weight-medium);
  cursor: default;
  position: relative;
  height: 80px;
}

.color-row-name {
  pointer-events: none;
}

.color-row.is-text-dark .color-row-name,
.color-row.is-text-dark .color-copy-btn {
  color: var(--black);
}

.color-row.is-text-light .color-row-name,
.color-row.is-text-light .color-copy-btn {
  color: var(--white);
}

/* The two above hardcode black and white, which is right for a primitive
   swatch — a primitive does not flip with the theme. A row painted in a
   *semantic* token does, so a fixed label colour inverts with it and vanishes.
   This one tracks the theme instead: on a row whose background is the text
   colour, the label takes the background colour. */
.color-row.is-inverted .color-row-name,
.color-row.is-inverted .color-copy-btn {
  color: var(--text-inverted);
}

/* Hidden with opacity, not display.
 *
 * `display: none` was the original, paired with a `:focus-within` rule that
 * could never fire: a display:none child is not focusable, so focus can never
 * land inside the row to reveal it. That made the Hex and CSS buttons on every
 * colour row pointer-only — WCAG 2.1.1 Keyboard, Level A. Opacity keeps them
 * in the layout and in the tab order, so tabbing into the row reveals them the
 * same way hovering does. */
.color-row-actions {
  display: flex;
  gap: var(--space-s);
  margin-left: auto;
  opacity: 0;
  transition: opacity var(--duration-2xs) var(--ease-out);
}

.color-row:hover .color-row-actions,
.color-row:focus-within .color-row-actions {
  opacity: 1;
}

/* Focus has to be visible before the reveal transition has run, or the first
   Tab into a row shows a ring on something still fading in. */
.color-copy-btn:focus-visible {
  opacity: 1;
}

.color-copy-btn {
  color: inherit;
  background: none;
  border: none;
  font-size: var(--font-xs);
  font-weight: var(--font-weight-semi-bold);
  cursor: pointer;
  padding: var(--space-2xs) var(--space-s);

}

.color-copy-btn:hover {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 2.5px;
}

/* -- Button-flavoured color row (used in the brand book) --
 * The whole row is the copy target. Hover reveals an ::after copy icon
 * (CSS mask of the copy SVG); on .is-copied the icon swaps to a check.
 * Inline background-color always wins over the .copy-btn.is-copied reset. */
button.color-row {
  width: 100%;
  border: none;
  text-align: left;
  font-family: var(--font-quaternary);
  font-size: var(--font-s);
  cursor: pointer;
  justify-content: space-between;
  transition: filter 0.15s;
}

button.color-row:hover {
  filter: brightness(0.96);
}

button.color-row::after {
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8 14C8 15.1046 8.89543 16 10 16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V14ZM6 18V2H22V18H6ZM2 22V6H4V20H18V22H2Z' fill='black'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8 14C8 15.1046 8.89543 16 10 16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V14ZM6 18V2H22V18H6ZM2 22V6H4V20H18V22H2Z' fill='black'/></svg>");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  opacity: 0.6;
  transition: opacity 0.15s;
}

button.color-row:hover::after,
button.color-row:focus-visible::after {
  opacity: 1;
}

button.color-row.is-copied::after {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9.54998 18L3.84998 12.3L5.27498 10.875L8.13576 13.7358C8.91681 14.5168 10.1831 14.5168 10.9642 13.7358L18.725 5.97501L20.15 7.40001L9.54998 18Z' fill='black'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9.54998 18L3.84998 12.3L5.27498 10.875L8.13576 13.7358C8.91681 14.5168 10.1831 14.5168 10.9642 13.7358L18.725 5.97501L20.15 7.40001L9.54998 18Z' fill='black'/></svg>");
  opacity: 1;
}

/* Don't let .copy-btn.is-copied global reset bleed into our coloured row */
button.color-row.is-copied {
  color: inherit;
  border-color: inherit;
}


/* NOTE: .table moved to design-system.css §25 — see CLAUDE.md §17 (Layer Discipline) */


/* ------ BACK TO DOCS / BACK TO TOP ------ */

.docs-back-btn-wrapper {
  position: fixed;
  top: var(--space-m);
  right: var(--space-m);
  z-index: 100;
}

.back-to-top-wrapper {
  position: fixed;
  right: var(--space-m);
  bottom: var(--space-m);
  z-index: 100;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s, visibility 0.3s;
}

.back-to-top-wrapper.is-visible {
  opacity: 1;
  visibility: visible;
}


/* ------ 1. DOCS LAYOUT ------ */


:root {
  /* Alias, not a definition. The header owns its height as
     --site-header-height (design-system.css §40); this name is kept because
     18 places across five stylesheets already read it, including a published
     code sample in cms/bar.md. Renaming those is a next-major job. */
  --top-nav-height: var(--site-header-height);
  --sidebar-width: 280px;
  --sidebar-collapsed-width: 64px;
  --toc-width: 220px;

  /* What this shell docks to the top. Consumed by design-system.css §39B for
     anchor targets, and by the TOC below for where it starts; both used to
     carry this calc separately. The header is always there — the page
     sub-header is not, so its height is added conditionally below rather than
     assumed, or anchors land a bar's height low on the pages without one. */
  --scroll-offset: calc(var(--site-header-height) + var(--space-xl));
  --toc-top-offset: var(--scroll-offset);
  --toc-border: color-mix(in srgb, var(--text-primary) 10%, transparent);
  --toc-border-active: color-mix(in srgb, var(--text-primary) 30%, transparent);
  /* Still hardcoded: the scale has no 300ms step, so tokenising this means
     picking 200ms (--duration-xs) or 400ms (--duration-s) — a motion decision,
     not a find-and-replace. Same for the two transform transitions further
     down. Grouped in ROADMAP rather than changed silently. */
  --sidebar-transition: 0.3s ease;
}

/* Pages with a docked bar stack it under the header, so anchors have to clear
   both. Keyed off the bar actually being present rather than off frontmatter.

   Two sums, one per density, because only regular has a height token — any
   other bar is one control tall, a floor §41 enforces on the container, so
   its height is built from the tokens that produce it: one control, the
   padding above and below it, and the bottom rule.

   Three things the compact sum cannot see, all of them silent if they arrive:
   - Both sums assume one row, which the bar now guarantees — overflow moves
     items to a menu instead of wrapping. If an anchor lands wrong, this is
     still the first place to look.
   - --border-s is the one figure restated rather than read. The rule comes
     from --bar-border, a shorthand with no width to extract, so a brand that
     re-points it to 2px drifts this sum by 1px.
   - data-density="compact" zeroes --bar-padding-y on the .bar element, which a
     body-scoped calc cannot see; the sum reads the :root value. The compact
     preset zeroes it and so does regular, so today the padding term counts 0
     twice over — it protects a default-density bar, which keeps 8px.

   A page carrying BOTH a docked regular and a docked compact bar takes the
   regular sum — its selector is (0,3,1) against (0,2,1) — and under-counts by
   the compact bar's height. No page has both; a third rule with both :has()
   clauses is the fix if one ever does. */
body:has(.bar[data-density="regular"][data-sticky="true"]) {
  --scroll-offset: calc(var(--site-header-height) + var(--bar-height) + var(--space-xl));
}

body:has(.bar[data-sticky="true"]:not([data-density="regular"])) {
  --scroll-offset: calc(
    var(--site-header-height)
    + var(--bar-control-height) + (2 * var(--bar-padding-y)) + var(--border-s)
    + var(--space-xl)
  );
}


/* Smooth scroll for same-page anchor links. Guarded because the glossary's
   A–Z rail makes animated jumps a page's primary navigation, not an
   occasional TOC click — readers who asked for reduced motion get an
   instant jump instead. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* -- Layout grid (mirrors studio/assets/css/studio.css) --
 * Sidebar, top-nav, and main content are siblings in a CSS Grid.
 * The page wrapper lives inside the main grid cell, so absolute-
 * positioned containers during animation are constrained to that
 * cell — the sidebar is a separate grid track, completely unaffected.
 */
/* The main track is minmax(0, 1fr), never a bare 1fr. A bare 1fr floors at the
   min-content width of its widest descendant, so one wide table stretches the
   whole grid and the page scrolls sideways — and .table-scroll never scrolls,
   because the track grew to fit it rather than making it overflow. */
.docs-layout {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  grid-template-rows: var(--top-nav-height) 1fr;
  grid-template-areas:
    "sidebar topbar"
    "sidebar main";
  min-height: 100vh;
  background: var(--background-primary);
  transition: grid-template-columns var(--sidebar-transition);
}

body.sidebar-collapsed .docs-layout {
  grid-template-columns: var(--sidebar-collapsed-width) minmax(0, 1fr);
}

body.no-sidebar .docs-layout {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "topbar"
    "main";
}

/* Main content area — now a grid cell, no margin offsets needed.
 *
 * Also the query container for everything inside it: this track's width is
 * the real space content has to work with (viewport minus 280px open /
 * 64px collapsed / 0 no-sidebar), so content-level breakpoints query it
 * with `@container main-content (…)` instead of the viewport. One set of
 * rules covers every sidebar state. Container name matches studio.css —
 * deliberate mirror.
 *
 * Two consequences of container-type worth knowing: this element is now a
 * stacking context and the containing block for position:fixed
 * descendants — fixed chrome and high-z overlays (toasts, back-to-top)
 * must stay OUTSIDE .docs-main-area, which they already do. Browsers
 * without @container (only Safari 15.4–15.6 also has the :has() these
 * rules lean on) keep a permanent two-column TOC; accepted, no fallback. */
.docs-main-area {
  grid-area: main;
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--top-nav-height));
  position: relative;
  background-color: var(--background-primary);
  container-type: inline-size;
  container-name: main-content;
}

.docs-content-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr); /* Default: single column */
  gap: var(--space-2xl);
  max-width: 1080px;
  margin: 0 auto;
  width: 100%;
  padding: var(--space-xl) var(--space-xl) 0;
  flex: 1;
}

/* When TOC exists, use 2-column layout */
.docs-content-grid:has(.docs-toc) {
  grid-template-columns: minmax(0, 1fr) var(--toc-width);
}

/* Wide frame — the `page` type (see cms/page-types.md).
 *
 * Same grid, same padding and rhythm, measure released. A doc page is prose
 * and 1080px is the reading measure that serves it; a `page` carries custom
 * markup — a comparison table, a gallery, a wide diagram — that the measure
 * would crop for no benefit. It is a frame, not a full-bleed: the page keeps
 * its gutters, which is what separates it from the `tool` type's `free`.
 *
 * No max-width rather than a larger one: .docs-main-area is already the
 * sidebar-adjacent grid track, so the column is bounded by the layout. */
.docs-content-grid[data-width="wide"] {
  max-width: none;
}


/* ------ 1B. SITE HEADER — placement only ------
 * The component is core (design-system.css §40). All that belongs here is
 * where it sits in this shell's grid.
 *
 * Deleted rather than moved, because nothing produced them: the empty
 * .top-nav-auth-container div and its .auth-login-btn child rule, plus
 * .top-nav-logo-text and .top-nav-contact-link, which were emitted with no
 * CSS anywhere in the repo.
 */

.docs-layout > .site-header {
  grid-area: topbar;
}

/* The header is the only place the menu button appears, and it is hidden
   above the mobile breakpoint. */
.site-header .header-menu-btn {
  display: none;
}

/* Icon swap is driven off aria-expanded rather than a body class, so the
   visible state cannot disagree with what a screen reader is told. */
.header-menu-btn .hamburger-icon-close { display: none; }
.header-menu-btn[aria-expanded="true"] .hamburger-icon-open { display: none; }
.header-menu-btn[aria-expanded="true"] .hamburger-icon-close { display: block; }

/* Swap icons to match the rendered theme. With no data-theme set the OS
   preference decides (mirrors the token fallback in design-system.css §2c).
   Four mutually exclusive hide-only branches — nothing ever needs its
   display restored */
[data-theme="dark"] .dark-mode-icon-light { display: none; }
:root[data-theme="light"] .dark-mode-icon-dark { display: none; }

@media not all and (prefers-color-scheme: dark) {
  :root:not([data-theme]) .dark-mode-icon-dark { display: none; }
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .dark-mode-icon-light { display: none; }
}


/* ------ 1C. SITE SIDEBAR (Sticky in grid) ------
 * The element is <dialog class="site-sidebar drawer">, one element in two
 * modes. Below 769px it is a real modal drawer, which is what removes its
 * ~130 links from the accessibility tree rather than only moving them
 * off-screen. At 769px and up the reset block below turns it back into an
 * ordinary in-flow sidebar; the menu button is hidden at that width, so
 * showModal() is never called and it never enters the top layer.
 *
 * That reset is coupled to design-system.css §30 on purpose and is the one
 * fragile thing here: it undoes §30's *closed* state (opacity, translate,
 * insets, size, padding, gap, shadow, transition). Add a property to
 * `.drawer`'s closed state and this block must learn about it, or the desktop
 * sidebar breaks with no error.
 *
 * Specificity ledger, corrected 2026-08-24 (the old note miscounted itself):
 * the reset `dialog.site-sidebar.drawer:not([open])` is (0,3,1), NOT (0,4,1).
 * §30's widest placement rule — `.drawer:not(...):not(...):not(...)` at
 * (0,4,0) — would BEAT it in the class column; the reset survives only
 * because that rule requires no data-placement="start" and this sidebar
 * carries exactly that. The margin is the attribute value, not the number.
 *
 * §30's drag handle (added 2026-08-24) raises the stakes: `.drawer-handle`
 * puts conditional padding on the closed state via :has(), and the
 * default-placement variant reaches (0,5,0) — the first §30 selector that
 * beats this reset outright, unmatchable here only while the sidebar stays
 * data-placement="start". The rule, stated plainly: THE DUAL-MODE SIDEBAR
 * MUST NEVER CARRY A .drawer-handle. If it ever does, this block needs
 * `display: none` on the handle plus a padding reset at (0,3,1) or higher —
 * otherwise the desktop sidebar gets a 44px touch-action strip over its own
 * scrollbar edge.
 */

.site-sidebar {
  grid-area: sidebar;
  z-index: 50;
  overflow-y: auto;
  overflow-x: hidden;
  color: var(--text-sidebar);
  flex-direction: column;
}

/* Desktop mode — see the section note. Every declaration here exists to undo
   something §30 sets on a closed drawer; none of it is styling in its own
   right.

   background and border-right live here rather than on .site-sidebar above.
   Both would tie §30 at (0,1,0) and win only because this file loads second —
   true today, but not something that survives a bundler reordering the two,
   an inlined critical-CSS pass, or @layer arriving. */
@media (min-width: 769px) {
  dialog.site-sidebar.drawer:not([open]) {
    display: flex;
    position: sticky;
    inset: 0 auto auto auto;
    block-size: 100vh;
    max-block-size: none;
    inline-size: auto;
    max-inline-size: none;
    padding: 0 0 var(--space-l);
    gap: 0;
    opacity: 1;
    translate: none;
    background: var(--background-sidebar);
    border-right: var(--border-s) solid var(--border-faded);
    border-radius: 0;
    box-shadow: none;
    transition: padding var(--sidebar-transition);
  }

  /* Collapsed rail. Must match the reset's specificity or it loses to it —
     at (0,2,0) this silently did nothing and the rail kept the wrong padding. */
  body.sidebar-collapsed dialog.site-sidebar.drawer:not([open]) {
    padding: 0 0 var(--space-m);
  }
}

/* Sidebar header: just the toggle button now (logo moved to header) */
.site-sidebar-header {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 0 var(--space-m);
  height: var(--top-nav-height);
}

/* Dismiss control for the drawer mode. Hidden at desktop, where the sidebar
   is not a modal and there is nothing to dismiss.

   Qualified with .button for the reason spelled out at design-system.css:1417
   — the base also matches as `button.button` (0,1,1), so a bare
   `.site-sidebar-close` (0,1,0) loses and the button stays visible. */
.button.site-sidebar-close {
  display: none;
}

/* Desktop sidebar toggle button */
.site-sidebar-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-primary);
  padding: 0;
  transition: color var(--duration-xs) var(--ease-out), background var(--duration-xs) var(--ease-out);
}

.site-sidebar-toggle:hover {
  color: var(--text-plain);
  background: var(--background-faded);
}

/* Sidebar toggle: swap between open/close icons */
.sidebar-icon-close { display: none; }
body.sidebar-collapsed .sidebar-icon-open { display: none; }
body.sidebar-collapsed .sidebar-icon-close { display: block; }

/* Sidebar nav content wrapper */
.site-sidebar-content {
  flex: 1;
  overflow-y: auto;
}

/* The hand-rolled mobile backdrop is gone — <dialog> has ::backdrop, styled
   by §30, and showModal() makes the page behind genuinely inert rather than
   merely covered. */

/* --- Collapsed sidebar state (desktop) --- */

/* The collapsed width comes from the grid track narrowing (.docs-layout above),
   not from a width here — a width declaration at this specificity loses to the
   desktop reset's `inline-size: auto`. Padding is handled in that same block
   for the same reason. */
body.sidebar-collapsed .site-sidebar {
  overflow: visible;
}

body.sidebar-collapsed .site-sidebar-header {
  justify-content: center;
}

body.sidebar-collapsed .site-sidebar-content {
  overflow: visible;
}

/* Collapsed: hide text, lists, toggle chevrons, labels */
body.sidebar-collapsed .sidebar-nav-section-toggle span:not(.sidebar-nav-toggle-icon),
body.sidebar-collapsed .sidebar-nav-section-toggle .sidebar-nav-toggle-icon,
body.sidebar-collapsed .sidebar-nav-list,
body.sidebar-collapsed .sidebar-nav-link > span,
body.sidebar-collapsed .sidebar-nav-home > span {
  display: none;
}

/* Collapsed: center icons in the narrow strip */
body.sidebar-collapsed .sidebar-nav-section-toggle,
body.sidebar-collapsed .sidebar-nav-link,
body.sidebar-collapsed .sidebar-nav-home {
  justify-content: center;
  padding: var(--space-l) 0;
  gap: 0;
}

/* Collapsed: remove open section background */
body.sidebar-collapsed .sidebar-nav-section[open] {
  background: none;
}

/* Sidebar tooltips: only visible when collapsed. Esc dismisses them
   (.sidebar-tooltips-dismissed set by nav.js — WCAG 1.4.13); pointer or
   focus movement inside the sidebar re-arms. */
.site-sidebar [data-tooltip]::before {
  display: none;
}
body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:hover::before,
body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:focus-visible::before {
  display: block;
}

/* Keyboard focus on the icon link nested in a section row — the tooltip
   lives on the summary, so :focus-visible alone misses it. Kept out of the
   list above so a browser without :has() can't invalidate the whole list,
   and the bubble needs its own opacity lift: the base pattern only lifts
   opacity for :hover/:focus-visible on the trigger itself. */
body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:has(:focus-visible)::before {
  display: block;
  opacity: 1;
}

/* Summary rows share ::before with the global disclosure chevron
   (summary::before in design-system.css): once display flips back on, the
   chevron's 0.5em box and borders bleed into the bubble, and
   details[open] > summary::before outranks the position-right transform
   with rotate(45deg). Pin the bubble's box and transform back. */
body.sidebar-collapsed .site-sidebar .sidebar-nav-section-toggle[data-tooltip]::before {
  width: auto;
  height: auto;
  border: 0;
  transform: translateY(-50%);
}


/* ------ 1D. NAV SECTIONS & LINKS ------ */

/* Desktop: Toggle styling */
.sidebar-nav-section-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-l);
  padding: var(--space-l);
  margin-bottom: 0;
  color: var(--text-primary);
  font-family: var(--font-primary);
  font-size: var(--font-m);
  line-height: 1;
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: color var(--duration-xs) var(--ease-out), background var(--duration-xs) var(--ease-out);
}

.sidebar-nav-section-toggle:hover {
  color: var(--text-secondary);
  background: var(--background-faded);
}

/* Remove default WebKit arrow */
.sidebar-nav-section-toggle::-webkit-details-marker {
  display: none;
}

/* Override global disclosure chevron — nav uses its own SVG icon */
.sidebar-nav-section-toggle::before {
  display: none;
}

/* Section icon link (wraps .svg-icn inside summary) */
.sidebar-nav-section-icon {
  display: flex;
  text-decoration: none;
  color: inherit;
}

/* Toggle + link shared icon sizing */
.sidebar-nav-section-toggle .svg-icn {
  width: 1.5rem;
  height: 1.5rem;
  flex-shrink: 0;
}

/* Toggle icon styling */
.sidebar-nav-toggle-icon {
  display: inline-flex;
  align-items: center;
  transition: transform 0.3s ease;
  margin-left: auto;
}

.sidebar-nav-toggle-icon svg {
  display: block;
  width: 10px;
  height: 10px;
}

/* Rotate icon when open (90deg to point down) — scoped to the section's
   own toggle so subsection chevrons inside an open section stay governed
   by their own [open] rule */
.sidebar-nav-section[open] > .sidebar-nav-section-toggle .sidebar-nav-toggle-icon {
  transform: rotate(90deg);
}

/* Desktop: Show/hide sidebar-nav-list based on details open state */
.sidebar-nav-section:not([open]) .sidebar-nav-list {
  display: none;
}

.sidebar-nav-section[open] {
  background: var(--bg-faded-3);
}

body:not(.sidebar-collapsed) .sidebar-nav-section[open] .sidebar-nav-list {
  display: block;
}

.sidebar-nav-list {
  list-style: none;
  /* breathing room lives on the container, not li:last-child — with nested
     subsection sublists, last-child would over-match and stagger the rhythm */
  padding: 0 0 var(--space-m);
  margin: 0;
}

.sidebar-nav-list li {
  margin: 0;
}

.sidebar-nav-home {
}

.sidebar-nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-l);
  padding: var(--space-l);
  color: var(--text-primary);
  text-decoration: none;
  font-family: var(--font-primary);
  font-size: var(--font-m);
    line-height: var(--line-height-l);
  transition: color var(--duration-xs) var(--ease-out), background var(--duration-xs) var(--ease-out);
}

.sidebar-nav-link:hover {
  color: var(--text-plain);
  background: var(--background-faded);
}

.sidebar-nav-link:focus-visible {
  outline: 2px solid var(--text-link);
  outline-offset: -2px;
}

.sidebar-nav-link .svg-icn {
  width: 1.5rem;
  height: 1.5rem;
  flex-shrink: 0;
}

.sidebar-nav-link-active {
  color: var(--text-link);
  font-weight: var(--font-weight-semi-bold);
  background: var(--background-faded);
}

.sidebar-nav-list .sidebar-nav-link {
  padding: var(--space-l) var(--space-l) var(--space-l) var(--space-4xl);
}

/* Subsection dropdowns — nested <details> inside a section's sidebar-nav-list.
   Collapsed by default; setActiveLink opens the active page's subsection. */
/* Reads exactly like a nav link — same font, size, and colour. Level is
   carried by indentation (deeper than the section, shallower than its
   links) and the disclosure chevron, not by a smaller label treatment. */
.sidebar-nav-subsection-toggle {
  display: flex;
  align-items: center;
  padding: var(--space-l) var(--space-l) var(--space-l) var(--space-xl);
  color: var(--text-primary);
  font-family: var(--font-primary);
  font-size: var(--font-m);
  line-height: var(--line-height-l);
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: color var(--duration-xs) var(--ease-out), background var(--duration-xs) var(--ease-out);
}

.sidebar-nav-subsection-toggle:hover {
  color: var(--text-plain);
  background: var(--background-faded);
}

.sidebar-nav-subsection-toggle:focus-visible {
  outline: 2px solid var(--text-link);
  outline-offset: -2px;
}

/* Remove default WebKit arrow */
.sidebar-nav-subsection-toggle::-webkit-details-marker {
  display: none;
}

/* Override global disclosure chevron — nav uses its own SVG icon */
.sidebar-nav-subsection-toggle::before {
  display: none;
}

.sidebar-nav-subsection[open] > .sidebar-nav-subsection-toggle .sidebar-nav-toggle-icon {
  transform: rotate(90deg);
}

.sidebar-nav-sublist {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* ------ 2. DOCS FOOTER ------
 * The docs site uses the Footer component (design-system.css §45). All that
 * belongs here is the width the docs column reads at — .docs-footer,
 * .docs-footer-inner and .docs-footer-text restated the component's
 * background, border, padding, max-width, centring and text colour one for
 * one, and are gone.
 *
 * `margin-top: auto` did not come across, and it was not dropped by oversight:
 * it never did anything. It was set on a footer whose parent chain is
 * [data-barba="container"] > [data-barba="wrapper"], both `display: block`, and
 * a vertical auto margin in normal flow computes to zero. The flex column is
 * .docs-main-area, two levels up. So the docs site has never pinned its footer
 * to the bottom of a short viewport, and this change does not start — it would
 * be a new behaviour, not a preserved one. §45 is right that this is the
 * shell's job; the shell simply is not doing it. Recorded in ROADMAP. */

.docs-main-area .footer {
  /* The docs content column, not the component's full-bleed default. Scoped to
     the shell so it never reaches a .footer inside a component demo. */
  --footer-max-width: 1400px;
}

.back-to-top-btn {
  --button-primary: var(--background-secondary);
  --button-text: var(--text-primary);
}


/* NOTE: .page-nav moved to design-system.css section 38
 * — see CLAUDE.md §17 (Layer Discipline) */


/* ------ 2B-bis. FORM HONEYPOT ------ */

/* Spam-trap field shared by the Notion-backed forms (feedback, access-support,
   notion-form). Kept in the DOM so bots fill it, but off-screen and out of the
   tab/AT order so no human ever sees or focuses it. A non-empty value server-side
   marks the submission as a bot. Not display:none — some bots skip hidden inputs. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}


/* ------ 2C. PAGE FEEDBACK ------ */

.page-feedback {
  border-top: var(--border-s) solid var(--border-faded);
  padding: var(--section-m) 0;
  text-align: center;
}

.page-feedback-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-l);
}

.page-feedback-prompt {
  margin: 0;
}

.page-feedback-description {
  color: var(--text-secondary);
  margin: 0;
}

/* Success state — hidden by default, shown when .is-submitted is set */
.page-feedback-success {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: var(--space-s);
}

.page-feedback-success-icon {
  color: var(--status-success);
}

.page-feedback-success-icon .svg-icn {
  width: 2.5rem;
  height: 2.5rem;
}

.page-feedback-success-title {
  margin: 0;
}

.page-feedback.is-submitted .page-feedback-prompt,
.page-feedback.is-submitted .page-feedback-description,
.page-feedback.is-submitted .button-group {
  display: none;
}

.page-feedback.is-submitted .page-feedback-success {
  display: flex;
}


/* ------ 2D. FEEDBACK MODAL ------
 * Composes the Dialog component (design-system.css §29). .feedback-modal is a
 * role class on top of .dialog, the way .close-btn composes with .button, and
 * it exists for feedback.js to bind to as much as for these two rules.
 *
 * What used to be here and is now the component's: the surface, the backdrop,
 * the padding, the max-height, the header/body/footer rhythm, and the enter
 * and exit motion the hand-rolled version never had. .feedback-modal-content,
 * .feedback-modal-title and .feedback-modal-actions are gone with it —
 * .dialog-body, .dialog-title and .dialog-footer do those jobs.
 *
 * Two deliberate losses, both recorded rather than restored:
 *   - The backdrop's `backdrop-filter: blur(2px)`. It was the only
 *     backdrop-filter in the repo, so it made this one overlay behave unlike
 *     every other dialog and drawer on the site. If the blur is wanted it
 *     belongs on the component as a token, not here.
 *   - `.feedback-textarea`'s `min-height: 100px`, which undercut the system's
 *     own 120px floor for a textarea (design-system.css §12). The class stays
 *     in the markup because feedback.js selects on it. */

.feedback-modal {
  /* Narrower than the component's 560px: the form is a label and one field. */
  --dialog-max-width: 480px;
}

.feedback-modal-subtitle {
  font-size: var(--font-s);
  color: var(--text-secondary);
  margin: 0 0 var(--space-m) 0;
}

.feedback-error {
  font-size: var(--font-s);
  color: var(--status-danger);
  margin-top: var(--space-s);
}


/* ------ 3. DOCS MAIN CONTENT ------ */

.docs-main {
  width: 100%;
  padding: var(--space-m) 0 var(--space-5xl) 0;
  /* clip, not hidden: both cut off horizontal blowout, but hidden makes this
     element the scroll container for every sticky descendant — and since
     .docs-main itself never scrolls, position: sticky inside it silently
     never sticks. The A–Z rail on the glossary page docks only under clip.
     (The docs TOC never hit this because it is a grid sibling, not a
     descendant.) No JS reads this element's scroll state. */
  overflow: clip;
}

/* Anchor offset is design-system.css §39B now, driven by --scroll-offset
   above — the rule was identical to the TOC's calc and is no longer repeated
   in two places. */

/* -- Page header --
 *
 * Nothing here. `.page-header` is a core component (design-system.css §44) and
 * every page the generator writes now emits its documented anatomy —
 * `<header class="page-header" data-align="center">` with
 * `.page-header-container`, `.page-header-title`, `.page-header-subtitle` and
 * `.page-header-actions` — from one builder, `buildPageHeaderHtml`.
 *
 * What stood here until 2026-08-11 was a shim for the pre-component markup: a
 * centred `.container-s`, the title and eyebrow margins that went with it, and
 * a `.page-subtitle` class that shadowed the component's own
 * `.page-header-subtitle`. It was scoped `div.page-header` so it could not
 * reach the tool pages, which were written to the component's spec first. With
 * the last three emit sites migrated there is no `div.page-header` left to
 * match, and `data-align="center"` in the markup does what the centring rules
 * did.
 *
 * The docs site now owns no page-header rule at all — the mobile height went
 * with it, for the reasons in the note in the responsive section. */

/* -- Brand book section spacing -- */
.brand-book-section {
  padding-top: var(--space-3xl);
  padding-bottom: var(--space-3xl);
  border-top: var(--border-s) solid var(--border-faded);
}

.brand-book-section:first-of-type {
  border-top: none;
  padding-top: var(--space-l);
}

.brand-book-section > h2 {
  margin-top: 0;
}

/* NOTE: .page-source-link and .page-action-link had `display: inline-block`
   here. Both are gone. Nothing in the repo emits .page-source-link at all, and
   .page-action-link is now a flex item inside .page-header-actions, where
   inline-block is blockified and ignored. The class stays on the anchor as a
   hook; the rule did nothing. */


/* ------ 4. DOCS TABLE OF CONTENTS ------ */

.docs-toc {
  position: sticky;
  top: var(--toc-top-offset);
  height: fit-content;
  max-height: calc(100vh - var(--toc-top-offset));
  overflow-y: auto;
  padding: var(--space-l);
}

/* Narrow-state TOC — the disclosure the generator emits at the top of
   .docs-main, under the sticky breadcrumb bar. Hidden here; §8 shows it
   (and hides the aside) when the content area drops below 860px. Inherits
   the core <details> disclosure and the .toc-* list styles. */
.docs-toc-dropdown {
  display: none;
  margin-bottom: var(--space-l);
  border: var(--border-s) solid var(--border-faded);
  border-radius: var(--radius-s);
  padding: var(--space-s) var(--space-l);
  background-color: var(--background-primary);
}

/* The wrapper's own padding plus the core disclosure indent plus each
   .toc-link's padding-left stacks to ~70px of inset on a phone column —
   drop the middle layer; the links keep their own. */
.docs-toc-dropdown .disclosure-content {
  padding-inline: 0;
}

.toc-header {
  display: block;
  font-size: var(--font-xs);
  font-weight: var(--font-weight-semi-bold);
  color: var(--text-faded);
  margin-bottom: var(--space-m);
}


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

.toc-item {
  margin: 0;
}

.toc-link {
  color: var(--text-plain);
  text-decoration: none;
  font-size: var(--font-s);
  font-weight: var(--font-weight-medium);
  display: block;
  padding: var(--space-s) 0 var(--space-s) var(--space-l);
  border-left: var(--border-m) solid var(--toc-border);
  transition: color 0.2s, border-color 0.2s;
}

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

.toc-link-active {
  color: var(--text-link);
  font-weight: var(--font-weight-semi-bold);
  border-left-color: var(--toc-border-active);
}



/* ------ 5. DEMO PREVIEW ------ */

.demo-preview {
  padding: var(--space-xl);
  border: var(--border-s) solid var(--border-faded);
  margin-bottom: 1rem;
}

.demo-preview.is-joined {
  margin-bottom: 0;
  border-bottom: none;
}

.demo-preview.is-centered {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-xl);
}

/* A left-aligned wrapping row of variants — the shape a styleguide entry wants
   when it shows six buttons or four badges side by side. `is-centered` centres
   and is wrong for scanning a set; this reads from the left edge like the text
   above it. Was inline on every such demo until the styleguide made it twenty. */
.demo-preview.is-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-l);
}

.demo-preview .bar {
  top: 0;
}

/* A sticky component demoed in flat flow would creep around inside its demo
   box as the page scrolls past. Direct children are pinned to relative;
   anything inside a .demo-scroll below keeps its sticky behaviour, which is
   the point of that container. */
.demo-preview > .site-header,
.demo-preview > .bar,
.demo-preview > .app-nav {
  position: relative;
}

/* A demo header carries the real one's z-index and sits later in the DOM, so
   without this it paints straight through the actual site header as the page
   scrolls past it. Safe inside .demo-scroll too — that container's overflow
   clips its own sticky child. */
.demo-preview .site-header,
.demo-preview .bar,
.demo-preview .app-nav {
  z-index: auto;
}

/* A short scrolling viewport, so a sticky component can be shown actually
   sticking rather than described as if it did. */
.demo-scroll {
  max-height: 240px;
  overflow-y: auto;
  border: var(--border-s) solid var(--border-faded);
  border-radius: var(--radius-s);
}

.demo-scroll-filler {
  padding: var(--space-l);
  color: var(--text-secondary);
}

/* A phone-width viewport, not a phone-width div. Media queries resolve against
   the iframe's own viewport, so a component's responsive rules genuinely fire
   inside one — a narrow <div> on a desktop page cannot show them at all. */
.demo-frame {
  display: block;
  width: 390px;
  max-width: 100%;
  height: 320px;
  border: var(--border-s) solid var(--border-faded);
  border-radius: var(--radius-s);
}

/* Format preview — placeholder or template image shown at its intrinsic
   ratio, capped so tall (9:16) formats stay scannable on the page */
.demo-preview .format-preview {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 420px;
}

/* Caption — joins under .demo-preview.is-joined the way a code block
   does, but carries text (format name, dimensions) instead of code */
.demo-caption {
  margin-bottom: 1rem;
  padding: var(--space-l);
  background-color: var(--background-darker);
  font-family: var(--font-quaternary);
  font-size: var(--font-s);
  color: var(--text-secondary);
}

.demo-caption p {
  margin: 0;
}

.demo-eyebrow {
  margin: 0;
  font-size: var(--font-s);
  /* --text-secondary, not --text-faded: body-size text needs 4.5:1 (AA) */
  color: var(--text-secondary);
}


/* ------ 21B. SPECIMEN — the styleguide presentation ------
 * /styleguide.html is not a doc that contains components; it is a
 * presentation of them. Components sit in the page as real elements with
 * real copy — no .demo-preview frame, no code block, no copy affordance —
 * so this vocabulary does one job: lay a set of variants out and name each
 * one. Anything that wants a box still wants .demo-preview (Part B §5), and
 * a left-aligned row inside one wants .demo-preview.is-row.
 *
 * There is no .specimen-label. .demo-eyebrow already is one (--font-s at
 * --text-secondary, AA at body size) and a second name for the same rule is
 * how two rules drift apart. Nor is there a neutral-ground box: when a
 * specimen needs one, derive it from .asset-card-preview (Part A §5) against
 * a real case rather than guessing the metrics in advance.
 *
 * Not .grid + .cols-*: that collapses at a hard 768px viewport breakpoint,
 * where this needs a floor and auto-fill. If a second consumer ever wants
 * this shape, the move is a .cols-auto modifier in design-system.css §7 —
 * not a third copy of the same idea.
 *
 * No breakpoint here on purpose. The set self-collapses through auto-fill
 * plus the min() floor below, so it needs no width to key on — which is
 * what makes it correct at 1808px (sidebar collapsed), 1592px (expanded)
 * and on a phone, without a query that can see only one of the three.
 *
 * One rule holds focus order honest across a page of ~130 specimens: never
 * set order, grid-row/grid-column, grid-auto-flow: dense, or a reverse flex
 * direction on a specimen. Auto-placement is row-major in source order, so
 * DOM order, visual order and focus order agree — and nudging one awkward
 * cell into place is exactly how WCAG 1.3.2 breaks. */

/* A set of labelled variants. auto-fill rather than a per-section column
   count, so a set of four badges and a set of eleven buttons read as one
   rhythm. --specimen-min is the cell floor, raised in named steps rather
   than per section: the first draft defaulted to 15rem and two sections
   immediately needed five inline overrides to bring it down, which is a
   token surface too coarse to use (§17 Rule 3). min(…, 100%) is what stops
   a floor wider than the container overflowing — auto-fill on its own would
   keep the cell at --specimen-min and push the page sideways. */
.specimen-set {
  --specimen-min: 10rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--specimen-min), 100%), 1fr));
  gap: var(--space-2xl) var(--space-xl);
}

.specimen-set[data-min="m"] {
  --specimen-min: 15rem;
}

.specimen-set[data-min="l"] {
  --specimen-min: 20rem;
}

/* One cell: the thing, then its name. space-between pins the label to the
   bottom so labels line up across a row whose specimens are different
   heights — the row stretches, the label does not float mid-cell. */
.specimen {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-m);
  min-width: 0;
}

/* A block of colour, a bar of space, a corner radius. The foundations are the
   one thing on this page with no component to stand in for them — a token has
   no rendering of its own — so they get the one primitive that draws a value
   directly. Three custom properties rather than three classes, because a
   swatch, a spacing bar and a radius sample differ only in which value they
   are showing. Four custom properties rather than four classes.

   Inline `style="--swatch-color: …"` is the intended way to use it and is what
   §3 permits on this page. Passing a value through a declared custom property
   is not the same thing as hand-rolling a rule inline. All four knobs are
   declared here rather than left as inline var() fallbacks, so the API is
   readable from the rule and an ancestor can set one for a whole set.

   One place it is the wrong primitive: a container scale. Those have to be
   stacked against a shared width to be comparable, and inside a .specimen-set
   every cell is narrower than --container-xs, so all five would clamp to the
   cell and render identically. Same reasoning as the duration bars. */
.specimen-swatch {
  --swatch-color: var(--background-faded);
  --swatch-height: 4rem;
  --swatch-width: 100%;
  --swatch-radius: 0;

  width: var(--swatch-width);
  height: var(--swatch-height);
  background-color: var(--swatch-color);
  border-radius: var(--swatch-radius);
  /* An inset ring rather than a border: --background-primary and
     --text-inverted are both near-white on a near-white page and would
     otherwise be invisible swatches. Inset because it must not change the box,
     which the radius and spacing specimens are measuring.

     --border-control, not --border-faded. The faded token is documented in
     design-system.css as decorative at 1.4:1 and explicitly not for a boundary
     anyone has to perceive — and here the ring IS the boundary, the only thing
     separating a white swatch from a white page. --border-control is the
     system's 3:1-and-above token in both themes (4.74:1 light, 4.77:1 dark).
     Its own comment frames it as an interactive boundary; it is defined by its
     contrast contract rather than by interactivity, which is what this needs. */
  box-shadow: inset 0 0 0 var(--border-s) var(--border-control);
}

/* A phone-sized viewport, for the components that only make sense docked to an
   edge. .app-nav is the case that needs it: it is position: fixed with
   inset-block-end: 0, so in flat flow it either pins to the real viewport or,
   pinned to relative like the rule below does, sits in the page as a plain row
   — which is not what a bottom bar is. Given a positioned ancestor and
   position: absolute, its own insets dock it to the bottom of this box, which
   is what a reader's phone actually shows.

   390px to match .demo-frame (Part B §5), so the two agree on what "a phone"
   means. Not an iframe, because these specimens are the page's own markup. */
.specimen-viewport {
  position: relative;
  width: 390px;
  max-width: 100%;
  height: 26rem;
  overflow: hidden;
  background-color: var(--background-primary);
  border: var(--border-s) solid var(--border-control);
}

.specimen-viewport > .app-nav,
.specimen-viewport > .site-header,
.specimen-viewport > .bar {
  position: absolute;
}

/* A sticky or fixed component demoed in flat flow would leave the page and
   pin itself to the viewport — .app-nav docks to the bottom edge, .site-header
   sticks to the top. Pinned to relative inside a specimen so it sits where it
   was written. This mirrors the same rule for .demo-preview (Part B §5); the
   two are separate because neither container is inside the other.

   .site-header also takes data-sticky="false", which is its own API and the
   better answer where a component offers one — this rule is the fallback for
   the components that do not. */
.specimen > .site-header,
.specimen > .bar,
.specimen > .app-nav {
  position: relative;
  inset: auto;
}

/* flex-start above keeps a button at its intrinsic width, which is right for
   almost everything. The exception is a component that IS full width — a
   button with data-full-width, a text input, a progress bar — which has to
   fill the cell or it reads as the wrong variant. Named data-full-width to
   match the system's own boolean for this (design-system.css §12), not a
   second word for the same idea. */
.specimen[data-full-width] {
  align-items: stretch;
}


/* ------ DOCS UTILITIES ------ */
/* Docs-specific display classes — not part of the design system */

/* -- Token labels -- */
.token-tag {
  display: inline-block;
  font-size: var(--font-2xs);
  font-family: var(--font-quaternary);
  color: var(--text-plain);
  background-color: var(--background-faded);
  padding: var(--2xs) var(--s);
  align-self: flex-start;
  margin-bottom: var(--space-m);
}

.token-block {
  padding: var(--space-s) 0;
}

/* -- Demo specific styles -- */
.type-demo > h1,
.type-demo > h2,
.type-demo > h3,
.type-demo > h4,
.type-demo > h5,
.type-demo > h6 {
  margin-top: var(--space-none);
  margin-bottom: var(--space-xl);
}

.type-demo > details {
  margin-bottom: var(--space-2xl);
}

.type-demo > details .token-tag {
  margin-bottom: var(--space-none);
  padding: var(--space-none) var(--space-2xs);
}

.button-demo > .button {
  margin-top: var(--space-none);
  margin-bottom: var(--space-l);
}

.button-demo > .token-tag {
  margin-top: var(--space-none);
  margin-bottom: var(--space-none);
}

/* -- Demo state classes -- */
.demo-hover.button {
  transform: scale(1.05);
}

.demo-active.button {
  transform: scale3d(1.0, 1.0, 1.0);
}

/* ------ 6. CODE BLOCKS ------ */

.code-block-wrapper {
  position: relative;
  margin-bottom: 1rem;
  display: grid;
}

.code-block-wrapper pre code {
  display: block;
  padding-right: 0;
  background-color: transparent;
  font-family: inherit;
}

.code-block-wrapper .copy-btn {
  position: absolute;
  top: var(--space-m);
  right: var(--space-m);
  z-index: 10;
  /* xsmall text would shrink the icon to ~.8em of 12px; restore a legible,
     tappable icon size (matches the design system's icon-only convention). */
  --icon-size: 1.5em;
  padding: var(--space-s);
}


/* ------ 7. DOCS CARDS (Index Page) ------ */

/* NOTE: .docs-hero is gone. Index pages now open with the Page Header
 * component (design-system.css §44) in the {{PAGE_HEADER}} slot, the same
 * band every documentation page uses, so there is nothing left for the docs
 * site to restate here. */

.docs-section {
  margin-bottom: var(--space-3xl);
}

.docs-section-title {
  font-size: var(--font-4xl);
  margin-bottom: var(--space-xl);
  color: var(--text-plain);
}

/* ------ 8. RESPONSIVE ------
 * The split, ported from studio.css: `@media` is only for chrome anchored
 * to the viewport (sidebar, drawer, top bar); `@container main-content`
 * is for everything inside the content area, so rules see the width the
 * content actually has — already minus the sidebar in whatever state it
 * is in. Future content breakpoints go in container queries, never
 * viewport media queries. The container is .docs-main-area (§1A). */

/* ---- Narrow content: TOC column becomes the dropdown ---- */
/* One breakpoint, owner-approved 2026-08-21: below 860px of content the
   220px column can't share the row, so the aside hides and the disclosure
   at the top of .docs-main takes over — at every narrower width, phones
   included. 859.98 not 860: with the 64px collapsed sidebar a 924px
   viewport lands exactly on 860 of content and both rules would fire
   (same epsilon convention as studio.css). Selector repeats
   :has(.docs-toc) to outrank the base two-column rule's specificity.
   The 720px cap keeps body text readable once the column's 300px of
   TOC + gap + padding is no longer holding it in. */
@container main-content (max-width: 859.98px) {

  .docs-content-grid:has(.docs-toc) {
    grid-template-columns: minmax(0, 1fr);
  }

  .docs-content-grid:has(.docs-toc) .docs-main {
    width: 100%;
    max-width: 720px;
    margin-inline: auto;
  }

  .docs-toc {
    display: none;
  }

  .docs-toc-dropdown {
    display: block;
  }
}

/* ---- Cramped content: tighter page padding ---- */
/* Fires on phones (drawer sidebar, container = viewport) and equally in a
   narrow desktop window with the sidebar open. */
@container main-content (max-width: 767.98px) {

  .docs-content-grid {
    display: block;
    padding-left: var(--space-l);
    padding-right: var(--space-l);
    padding-top: 0;
  }
}

/* ---- Mobile: Overlay sidebar ---- */
@media (max-width: 768px) {

  /* -- Grid: single column on mobile -- */
  .grid {
    grid-template-columns: 1fr;
  }

  .cols-3 {
    grid-template-columns: 1fr;
  }

  .cols-4 {
    grid-template-columns: 1fr;
  }

  /* The header's own mobile behaviour (flush edges, icon-only actions, 45px
     logo) is in design-system.css §40. What is docs-site-specific is the menu
     button, which only exists because this shell has a sidebar to open. */
  .site-header .header-menu-btn {
    display: inline-flex;
  }

  /* Token re-point only — the button's own padding rule reads --button-padding-*,
     so restating `padding` here would be a class override of a core component
     (§17 Rule 3) and would make the token line dead weight. */
  .site-header .header-action,
  .site-header .dropdown-trigger {
    --button-padding-x: var(--space-l);
    --dropdown-trigger-padding-x: var(--space-l);
    gap: var(--space-xs);
  }

  /* No-sidebar pages: the logo is the first child, so it supplies the inset
     the menu button would otherwise have provided. */
  .site-header-start > .site-header-logo:first-child {
    padding-left: var(--space-l);
  }

  /* Hide desktop sidebar toggle on mobile */
  .site-sidebar-toggle {
    display: none;
  }

  /* Grid: single column on mobile (sidebar becomes overlay) */
  .docs-layout,
  body.sidebar-collapsed .docs-layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--top-nav-height) 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }

  /* Sidebar is a modal drawer at this width. §30 supplies the docking, the
     slide, the backdrop and the reduced-motion handling; the only thing this
     shell has an opinion about is how wide it opens. */
  .site-sidebar {
    --drawer-size: 75vw;
    --drawer-padding: 0 0 var(--space-l);
    z-index: 101;
  }

  /* The header stays, but now it carries the drawer's close button rather
     than the collapse toggle (hidden above). */
  .site-sidebar-header {
    display: flex;
    justify-content: flex-end;
    height: auto;
    padding: var(--space-m) var(--space-m) 0;
  }

  .button.site-sidebar-close {
    display: inline-flex;
  }

  /* No collapsed state at this width — the sidebar is a modal drawer, and
     `sidebar-collapsed` persists in localStorage regardless of viewport. The
     rules that used to sit here were pre-dialog leftovers, and one of them
     (`padding: 0`) silently cancelled the drawer's own --drawer-padding. */


  body.sidebar-collapsed .sidebar-nav-section-toggle span:not(.sidebar-nav-toggle-icon),
  body.sidebar-collapsed .sidebar-nav-section-toggle .sidebar-nav-toggle-icon,
  body.sidebar-collapsed .sidebar-nav-list,
  body.sidebar-collapsed .sidebar-nav-link > span,
  body.sidebar-collapsed .sidebar-nav-home > span {
    display: revert;
  }

  body.sidebar-collapsed .sidebar-nav-section-toggle,
  body.sidebar-collapsed .sidebar-nav-link,
  body.sidebar-collapsed .sidebar-nav-home {
    justify-content: flex-start;
    padding: var(--space-l) var(--space-m);
    gap: var(--space-s);
  }

  body.sidebar-collapsed .sidebar-nav-section[open] {
    background: var(--background-faded);
  }

  body:not(.sidebar-collapsed) .sidebar-nav-section[open] .sidebar-nav-list,
  body.sidebar-collapsed .sidebar-nav-section[open] .sidebar-nav-list {
    display: block;
  }

  /* Sidebar tooltips: never on the mobile overlay — labels are visible
     there. Selectors mirror the desktop enabling rules exactly (same
     specificity, later in source) so these always win here. */
  body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:hover::before,
  body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:focus-visible::before {
    display: none;
  }

  body.sidebar-collapsed:not(.sidebar-tooltips-dismissed) .site-sidebar [data-tooltip]:has(:focus-visible)::before {
    display: none;
  }

  /* Content-grid and TOC rules moved to the @container main-content
     blocks in §8 — content layout queries the content area, not the
     viewport. Only viewport-anchored chrome stays in this block. */

  /* Mobile: Home link and root-link separators */
  .sidebar-nav-home,
  .sidebar-nav-root-link {
    margin-bottom: 0;
    padding-bottom: var(--space-s);
    border-bottom: var(--border-s) solid var(--border-faded);
  }

  /* Mobile: Collapsible nav sections */
  .sidebar-nav-section {
    margin-bottom: 0;
    padding: 0;
    background: none;
    border: none;
    border-bottom: var(--border-s) solid var(--border-faded);
    transition: none;
  }

  .sidebar-nav-section:last-of-type {
    border-bottom: none;
  }

  .sidebar-nav-section:hover {
    background: none;
  }

  /* Toggle takes full width */
  .sidebar-nav-section-toggle {
    width: 100%;
    padding: var(--space-xl) var(--space-l);
  }

  /* Show/hide sidebar-nav-list based on details open state */
  .sidebar-nav-section:not([open]) .sidebar-nav-list {
    display: none;
  }

  .sidebar-nav-section[open] .sidebar-nav-list {
    display: block;
    margin-top: 0;
    padding-bottom: var(--space-s);
  }

  /* Child links: full-width, no borders */
  .sidebar-nav-link {
    padding: var(--space-xl) var(--space-l);
    white-space: normal;
  }

  .sidebar-nav-list li {
    margin: 0;
  }

  /* NOTE: the docs site has no page-header rule here any more, on mobile or
   * anywhere else. It used to force `--section-l` padding and a `min-height:
   * 30vh` hero below 768px.
   *
   * It was deleted rather than repaired, and the distinction matters. The
   * selector was `[data-barba="container"] > .page-header`, and the header
   * moved inside a `<main>` when the landmark tree was fixed on 2026-08-11, so
   * the child combinator stopped resolving and the rule had already been
   * matching nothing for several commits. Every page was quietly running on
   * §44's own defaults — 64px padding, no minimum height — and rendering
   * correctly at 390px. Reinstating it would have introduced a behaviour, not
   * restored one.
   *
   * The exclusion it needed was never expressible either. What the rule meant
   * was "a page opening, not a tool page", and every key tried for that was
   * wrong: the element name broke when index pages became `<header>`s, and
   * `:not(:has(.bar))` excludes `website/bar.html`, a documentation
   * page carrying a dozen bar demos. If a taller mobile header is wanted,
   * it belongs on the component as a size, where the page asks for it. */

  /* Auth: stack user info vertically on mobile */
  .auth-user-info {
    flex-direction: column;
    align-items: stretch;
  }

  .auth-user-details {
    margin-bottom: var(--space-xs);
  }
}


/* ------ AUTH ------ */

/* Auth was removed 2026-08-01 (the site is public but unlisted; `_headers`
   carries noindex). What remains here styles auth/login.html and
   auth/account.html, kept as unwired layout references.

   Deleted with it: the `.auth-loading` / `.auth-ready` visibility pair, which
   hid the page until auth.js swapped the class. auth.js was the only code that
   ever removed it, so those rules would have left every page blank. */

.auth-login-btn {
  width: 100%;
}

/* --- Logged-in User Info --- */

.auth-user-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.auth-user-details {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.auth-user-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--background-faded);
  color: var(--text-inverted);
  font-size: var(--font-xs);
  font-weight: var(--font-weight-semi-bold);
  flex-shrink: 0;
}

.auth-user-role {
  font-size: var(--font-xs);
  color: var(--text-secondary);
  text-transform: capitalize;
}

.auth-logout-btn {
  width: 100%;
}

/* --- Element-Level Role Gating (removed 2026-08-01) --- */

/* `[data-auth-role] { display: none }` used to hide every tagged element until
   auth.js set a matching `body.auth-role-*` class. With auth gone that class is
   never set, so the rule would have permanently hidden all 95 elements carrying
   it — the "view / copy markdown source" affordances on every docs page.
   Removing the gating shows them again.

   This changes nothing about real exposure: enforcement was always client-side,
   so the markup shipped to every visitor regardless and JavaScript merely hid
   it (documented in docs/access-control.html). The `data-auth-role` attributes
   stay in the generated output as the record of intent.

   The `body.auth-role-public` sidebar rules went the same way — that class is
   also never set now, so they could never match. */

/* --- Login Overlay --- */

.login-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--background-primary);
  padding: var(--space-l);
}

.login-overlay.is-visible {
  display: flex;
}

/* The login screens themselves (card, panes, status regions, social buttons)
   are the .login component in design-system.css §46 — the hand-rolled
   .login-* rules that lived here were retired when auth/login.html and
   auth/account.html moved onto it. Only page chrome remains below. */

/* --- Login Page (standalone /auth/login.html) --- */

.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  /* The top-nav is sticky (in normal flow) on auth pages — it already
     reserves its own height, so min-height fills the remaining viewport.
     No margin-top: that would double-count the nav height and push the
     card off-centre with an overflow scroll. */
  min-height: calc(100vh - var(--top-nav-height));
  padding: var(--space-l);
  background: var(--background-primary);
}

/* --- Access Denied Page --- */

.access-denied-wrapper {
  min-height: 60vh;
  justify-content: center;
  text-align: center;
}

.access-denied-wrapper p {
  color: var(--text-secondary);
}

.access-denied-icon {
  width: 2.5rem;
  height: 2.5rem;
}
