/* gallery.css is the gallery's own chrome: the swatch grid, the sample
   frames, the section rhythm, the rail and the preview widget. None of
   it is vocabulary an app should reach for, and tokens.css shipping a
   class only one page uses is how a stylesheet starts to rot — so it
   is neither in tokens.css nor in a theme. Every value here is a
   token, so the page's own furniture changes theme with everything
   else.

   It lives beside the renderer rather than in ui/ for gallery.js's
   reason: no scaffold writes it, no app receives it, and the only
   pages that load it are the ones this package renders. It is a file
   rather than a Go string constant for gallery.js's other reason —
   a stylesheet is edited as CSS, with a syntax highlighter and a
   linter.

   It used to be inlined into every page's <style>, and it is one asset
   at the tree root now, linked the way tokens.css and the theme are.
   That is most of eight kilobytes off every page in the tree, and it
   makes a missing <link> a new way to render a page unstyled — the
   live bug this tree has had once already, when relative asset paths
   broke at the slash-less URL. TestEveryGalleryPageLinksTheStylesheet
   holds every page to carrying it. */

.ds-head { border-top: 1px solid var(--rst-line); margin: var(--rst-sp-6) 0 var(--rst-sp-3); padding-top: var(--rst-sp-5); }
.ds-head h2 { font-size: 1.35rem; letter-spacing: -0.01em; margin: 0; }
.ds-lead { color: var(--rst-text-muted); margin: 0 0 var(--rst-sp-4); max-width: 62ch; }
.ds-sub { font-size: var(--rst-fs-base); margin: var(--rst-sp-5) 0 var(--rst-sp-2); }
.ds-switch { align-items: center; display: flex; flex-wrap: wrap; gap: var(--rst-sp-3); margin: var(--rst-sp-4) 0; }
.ds-chrome { align-items: center; border-block-end: 1px solid var(--rst-line); display: flex; flex-wrap: wrap; gap: var(--rst-sp-3); justify-content: flex-end; padding: var(--rst-sp-2) var(--rst-sp-4); }
.ds-chrome [rst-dropdown] { position: relative; }
.ds-chrome [rst-dropdown] > summary { padding-block: 0.25rem; }
/* The toggle is hidden until gallery.js says it is there. With scripts
   off it never appears, and the page stays on the theme's own
   color-scheme: light dark — which is the System position anyway, so
   nothing is lost but a control that could not have worked. The
   attribute is set while the head is still parsing, so this is not a
   reveal the reader sees happen. */
.ds-scheme { display: none; }
:root[data-rst-js] .ds-scheme { align-items: stretch; border: 1px solid var(--rst-line); border-radius: var(--rst-radius-sm); display: inline-flex; overflow: hidden; }
.ds-scheme button { background: none; border: 0; box-sizing: border-box; color: var(--rst-text-muted); cursor: pointer; font: inherit; font-size: var(--rst-fs-sm); padding: 0.25rem 0.6rem; }
.ds-scheme button + button { border-inline-start: 1px solid var(--rst-line); }
.ds-scheme button:hover { background: var(--rst-accent-soft); color: var(--rst-text); }
.ds-scheme button[aria-pressed="true"] { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; }
.ds-scheme button:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; }
.ds-partial { margin: var(--rst-sp-5) 0; }
.ds-partial > :is(h3, h4) { font-size: var(--rst-fs-base); margin: 0 0 var(--rst-sp-1); }
.ds-sample { background: var(--rst-surface-2); border: 1px solid var(--rst-line); border-radius: var(--rst-radius); margin: var(--rst-sp-3) 0; padding: var(--rst-sp-4); }
.ds-state { color: var(--rst-text-faint); font-size: var(--rst-fs-xs); font-weight: 650; letter-spacing: 0.06em; margin: 0 0 var(--rst-sp-3); text-transform: uppercase; }
.ds-note { border-inline-start: 2px solid var(--rst-line-strong); color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: var(--rst-sp-3) 0 0; max-width: 62ch; padding-inline-start: var(--rst-sp-3); }
.ds-toks { display: grid; gap: var(--rst-sp-3); grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); list-style: none; margin: 0 0 var(--rst-sp-5); padding: 0; }
.ds-tok { align-items: center; display: flex; gap: var(--rst-sp-3); min-inline-size: 0; }
.ds-tok__text { min-inline-size: 0; }
.ds-tok__name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-tok__value { color: var(--rst-text-muted); display: block; }
.ds-chip { background: var(--rst-surface); border: 1px solid var(--rst-line-strong); border-radius: var(--rst-radius-sm); block-size: 2.25rem; flex: none; inline-size: 2.25rem; }
.ds-chip--fill { background: var(--rst-accent); border-color: var(--rst-accent); }
.ds-bar { background: var(--rst-accent); block-size: 1.25rem; border-radius: 2px; flex: none; }
.ds-type { display: block; flex: none; inline-size: 3.25rem; line-height: 1.15; text-align: center; }
.ds-swatch-note { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: 0 0 var(--rst-sp-4); max-width: 62ch; }
/* The Icons page reuses the token grid: one chip per icon, and the icon
   inside it at the 1em tokens.css gives it, which is the size a
   component draws it at rather than a poster of it. The call under the
   name is long enough to overrun a 13rem column on a 320px screen, so
   this is the one place in the grid that breaks inside a word. */
.ds-chip--icon { align-items: center; color: var(--rst-text); display: flex; justify-content: center; }
.ds-icons .ds-tok__value { overflow-wrap: anywhere; }
/* The Overview's opening paragraph and the routes under it. .ds-files is
   the same row — a name, a sentence and a number — on the Getting
   started page. It is a second class rather than a second use of
   .ds-routes because TestTheOverviewRoutesIntoEveryOtherPage identifies
   the Overview's route list BY that class and asserts no other page
   carries one; sharing it would have made that gate stop meaning
   anything. The
   paragraph is the one piece of copy on this site somebody wrote by
   hand rather than derived from the code, so it is set a step larger
   than body text and given the same 62ch measure everything else here
   reads at. */
.ds-intro { font-size: 1.05rem; margin: 0 0 var(--rst-sp-5); max-width: 62ch; }
.ds-routes, .ds-files { display: grid; gap: var(--rst-sp-4); list-style: none; margin: 0 0 var(--rst-sp-5); padding: 0; }
.ds-routes > li, .ds-files > li { border-inline-start: 2px solid var(--rst-line-strong); padding-inline-start: var(--rst-sp-3); }
.ds-routes a, .ds-files a { font-weight: 600; }
.ds-routes span, .ds-files span { color: var(--rst-text-muted); display: block; font-size: var(--rst-fs-sm); max-width: 62ch; }
/* Prev/next at the foot. Two grid columns rather than a flex row with
   space-between, because the ends of the sequence are missing a link
   and not missing a column: the Overview's Next has to stay on the
   inline end rather than sliding across to where Previous would have
   been. */
.ds-updown { border-block-start: 1px solid var(--rst-line); display: grid; gap: var(--rst-sp-3); grid-template-columns: 1fr 1fr; margin-block-start: var(--rst-sp-6); padding-block-start: var(--rst-sp-4); }
.ds-updown__prev { grid-column: 1; justify-self: start; }
.ds-updown__next { grid-column: 2; justify-self: end; text-align: end; }
.ds-shell { margin: var(--rst-sp-5) 0; }
.ds-shell h3 { font-size: 1.05rem; margin: 0 0 var(--rst-sp-1); }
.ds-src { background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: var(--rst-radius); margin: var(--rst-sp-3) 0; overflow-x: auto; padding: var(--rst-sp-4); }
.ds-src code { white-space: pre; }

/* The preview widget: [Desktop] [Mobile] [Code] over one frame.

   No script anywhere in it. The tabs are three radio inputs sharing a
   name, hidden but focusable inside their labels, and :has() reads
   which one is checked from the wrapper — so the panels switch on the
   browser's own form behaviour and a reader with JavaScript off has
   the same three views as everyone else.

   None of the three starts checked, and that is the whole of how the
   opening view follows the reader. CSS cannot tell "the reader chose
   Desktop" from "Desktop is what the markup shipped checked", so a
   widget that opens on a checked radio can never let the width pick
   the opening view AND still take an explicit choice. With nothing
   checked the width picks, and either tab, once clicked, overrides it
   at any size.

   WHICH width picks is the part that took two goes. It is the STAGE's
   width, asked with a container query on .ds-view, and not the
   viewport's. The viewport is the wrong ruler because this gallery's
   own rail arrives at 800px and takes 240px out of the column: the
   stage measures 718px in a 799px window and 479px in an 800px one, so
   a reader who WIDENS their window makes the preview narrower. A
   viewport rule at 800px therefore switched to the rendering that
   needs the most room at exactly the width where it had the least, and
   was non-monotone in the only thing that matters. A container query
   measures the box a reader actually gets, so the rail is accounted
   for by arithmetic rather than by a second number, and the next
   person to change the rail's width cannot silently move this line.

   ONE number does the deciding, and it is written twice because CSS
   cannot put a custom property in a query condition: --ds-kmin, the
   least the frame may be scaled to, and the stage width at which the
   desktop rendering reaches it — --ds-wd × --ds-kmin. Keep them in
   step; the drive asserts the identity, and it asserts it off each
   widget's own --ds-wd rather than off a number written down here, so
   a third width class could not land with a stale threshold.

   ── Two width classes ───────────────────────────────────────────────

   --ds-wd is the width the desktop rendering is laid out at, and there
   are two of them because there are two kinds of example on this site.

     .ds-view--page   1200px   a whole PAGE: the four shell demos, the
                               two shell idioms, the modal, the demo
                               application. A page frame wants a window
                               to be a page frame in, and 1200px is a
                               window.
     .ds-view         900px    everything else: a field, a pill, a row,
                               a menu. A component is something INSIDE
                               a page, and it never gets a window's
                               width in an app either.

   The default is the component one, because 105 of the 114 examples on
   this site are components and a default that is right 92% of the time
   is the one to have. page.go's pageFrame() decides which is which.

   Why the component class exists at all, which is the bug it fixed:
   every example on this site used to be laid out at 1200px, and this
   gallery's own stage caps at 958px — the [rst-page] column, less its
   padding, less the 240px rail — at EVERY window width, because the
   column has a max-width and the rail is fixed. So --ds-k was
   958/1200 = 0.798 on every desktop, forever, and every component on
   the site was rendered at four-fifths size with its 12.5px type at
   10px. Nothing was broken and nothing looked broken; it just looked
   small, which is a thing a gallery of a design system must not do.
   At 900px the same stage gives --ds-k = 1 and a field is a field.

   900px, and not the 958px that would fill the column: tokens.css
   folds .rst-m-hide away and switches the shells to their narrow
   layout at 800px, so a "desktop" rendering under that width would
   quietly be the MOBILE layout under a lit Desktop tab — the worst
   failure this widget has. 900px clears that line by 100px, and the
   remaining 58px of column is slack the gallery's own layout can spend
   without dragging every preview back under 1. Coupling the number to
   958 would have meant re-measuring every height on this site the next
   time the rail or the column moved.

   Both classes share --ds-kmin. It protects a type size, not a width,
   and the type inside the two is the same type.

   That arithmetic is exact at a 16px root and only there. --ds-kmin is
   a ratio and does not move; 54rem does. A reader who scales their
   root type up scales the type inside the frames with it, so the stage
   width legibility needs really does grow and rem is the right unit —
   but the two stop meeting, and the gap falls the safe way: the
   desktop rendering stays available at 0.72 while the default holds on
   to the 390px one for longer. Change one of these and the other
   moves.

   Where 0.72 comes from, measured rather than assumed. The type in
   these samples is --rst-fs-base (14px), --rst-fs-sm (12.5px) and
   --rst-fs-xs (11.5px), with an 11px badge as the smallest thing
   anywhere. Which of them DOMINATES depends on the page — 14px carries
   70% of the characters on the display page and none at all on
   date-and-time, where 12.5px carries half — so the honest number to
   protect is 12.5px, not the 14px one page suggests. --ds-k scales all
   of it linearly and 9px is about where rendered text stops being read
   and starts being texture. 12.5 × 0.72 = 9.0px exactly. That gives
   both the floor and the threshold, and it is why they are one number.

   54rem is also comfortably over the 718px the stage can reach before
   the rail arrives, so the page class's default flips once as a window
   widens rather than three times. Under it the 390px rendering is the
   opening view: not because it is perfect — at a real 390px phone the
   stage is 309px and that rendering is scaled to 0.79, so its 12.5px
   type lands at 9.9px — but because it is three times the 0.26 the
   desktop rendering would be scaled to in the same column.

   The component class's threshold is the same identity at its own
   width: 900 × 0.72 = 648px = 40.5rem. It is LOWER, and that is the
   point rather than a side effect — a 900px page is still legible in a
   column where a 1200px one is not, so a component holds on to its
   desktop rendering for 216px of stage more than a shell does. One
   threshold shared between the two would have had to pick which of
   them to be wrong for.

   The pair is written as (min-width: 54rem) and its exact negation
   rather than as a min/max pair: a max-width one pixel under leaves a
   gap a fractional width can land in, and in that gap neither rule
   applies and no tab is lit at all. The highlight below is driven by
   those same two queries, so the tab a reader sees lit is always the
   rendering in front of them. A 390px rendering under a lit "Desktop"
   would be a worse bug than the one this fixed. An engine with :has()
   but no container queries — Safari 15.4 to 15.6 is the real one —
   gets neither query, so it gets a third rule that lights Desktop to
   match the 1200px rendering it will be showing.

   The scale is the other half. The frame is laid out at a virtual
   --ds-w — its class's --ds-wd, or 390px on Mobile — and scaled to fit
   whatever the reader's column actually is, so the desktop rendering
   is the desktop rendering on a phone too. CSS can divide two lengths
   into a plain number exactly one way — tan(atan2(a, b)) — and 100cqw
   is the container's own width, so --ds-k is that fraction; the 1
   stops it scaling anything UP and --ds-kmin stops it scaling anything
   into illegibility. Where the trig functions are missing the
   @supports block never applies, --ds-k stays 1, and the frame is a
   virtual-width page clipped to the column: smaller, not broken.

   The 1 is not a formality now that the component class exists. It is
   the whole of what makes a 900px sample in a 958px stage render at
   its own size rather than being stretched to fill the column — the
   box is 900px, centred, and the 58px either side of it is the column
   being wider than the sample needs.

   Clamping the scale is what makes the desktop rendering worth asking
   for on a phone, and it is the widget's own doctrine — the box is a
   window on a document, not a fit to it — applied to the inline axis
   at last. Below its class's threshold the desktop rendering stops
   SHRINKING and starts PANNING: --ds-k holds at 0.72, the box stays as
   wide as the column, and the 864px (or 648px) of scaled page inside
   it scrolls horizontally within the box. Nothing about it reaches the
   page, so the document itself never scrolls sideways.

   The scroller is on the box itself and not on the state that made it
   necessary, and that distinction cost a regression. Scoped to the
   chosen-Desktop rule it looked complete, because the MOBILE rendering
   clamps at 0.72 as well: under 281px of stage a 390px virtual page is
   281px wide too, and the opening view — nothing chosen, nothing to
   scope on — was cropping 42px at a 320px window and 82px at a 280px
   one, with no way to reach it. 320px is the reflow width tokens.css
   and this file both commit to in writing. The rule is that a box
   which can clamp can overflow, and one which can overflow has to
   scroll; the state that produced the overflow is not part of it. The alternative, which
   this replaces, was to leave the scale alone and make the BOX taller:
   that bought 126px of blank space per widget and not one pixel of
   legibility, because previewHeights are measured content heights and
   the frame already fitted its document.

   The container is .ds-view and not .ds-view__stage, because the tabs
   are the stage's SIBLINGS and a query on the stage cannot reach them,
   and a highlight and a rendering answering to two different rulers is
   how a CSS-only default goes wrong. .ds-view carries no padding and
   no border, so the two elements are the same number of pixels wide
   and --ds-k did not change when the container moved up. That is not
   left to argument: the drive measures both widths and requires them
   equal, and checks --ds-k against the stage's own width, because a
   divergence here would quietly rescale every preview in the gallery.

   --ds-h is the frame's virtual height, written per example by
   previewStyle. It sizes the BOX; the frame takes its height back off
   the box, 100% / --ds-k, which is the same number until a reader
   drags the resize grip and then is whatever they dragged it to. Doing
   it the other way round — a fixed height on the frame — gave the grip
   nothing to move: the box grew and the document inside it stayed the
   size it was, leaving 300px of empty box under a 255px rendering.

   The box is a window on a document, not a fit to it: a taller sample
   scrolls inside its frame, and the grip is there for the ones a
   reader wants more of.

   min-block-size is insurance and nothing else — it buys no
   legibility, and it is not what fixed anything here. It exists
   because block-size names --ds-k, and a --ds-k an engine parses but
   cannot resolve makes that declaration invalid at computed-value
   time, which drops it to auto, which for a box whose only child is
   absolutely positioned is ZERO. A separate declaration naming nothing
   that can go bad keeps the box open in that engine, and the frame is
   then cropped rather than gone. Its value is the smallest height the
   healthy cascade can produce, --ds-h × --ds-kmin, so it never binds
   when things work and adds no whitespace to any preview at any width.

   The @supports guard tests the same shape the declaration uses,
   100cqw and all, rather than the trig alone. Be clear about what that
   can and cannot do: @supports tests whether a declaration PARSES, so
   it catches an engine that will not take a container-query unit in
   that position, and it cannot catch one that parses it and then fails
   to resolve it. The min-block-size above is what covers the second
   case; this guard covers the first. */
.ds-view { --ds-wd: 900px; --ds-w: var(--ds-wd); --ds-h: 220px; --ds-hm: 330px; --ds-kmin: 0.72; container-name: ds-view; container-type: inline-size; margin: var(--rst-sp-3) 0; }
.ds-view--page { --ds-wd: 1200px; }
.ds-view__tabs { border: 0; display: flex; margin: 0 0 var(--rst-sp-2); padding: 0; }
.ds-view__tab { align-items: center; border: 1px solid var(--rst-line); color: var(--rst-text-muted); cursor: pointer; display: inline-flex; font-size: var(--rst-fs-sm); padding: 0.2rem 0.7rem; }
.ds-view__tab + .ds-view__tab { border-inline-start: 0; }
.ds-view__tab:first-of-type { border-end-start-radius: var(--rst-radius-sm); border-start-start-radius: var(--rst-radius-sm); }
.ds-view__tab:last-of-type { border-end-end-radius: var(--rst-radius-sm); border-start-end-radius: var(--rst-radius-sm); }
.ds-view__tab input { block-size: 1px; clip-path: inset(50%); inline-size: 1px; margin: 0; overflow: hidden; position: absolute; }
.ds-view__tab:hover { background: var(--rst-accent-soft); color: var(--rst-text); }
.ds-view__tab:has(input:checked) { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; }
/* One pair of queries per width class, and the two declarations each
   pair drives are written INSIDE it rather than in a block of their
   own further down. They are one decision — which rendering opens, and
   which tab is lit over it — and the previous arrangement, two rules
   answering to two copies of the same condition, is the shape a
   divergence hides in. The condition is now written once per class.

   Why they cannot be one pair: the threshold is --ds-wd × --ds-kmin
   and a query condition cannot name a custom property, so a shared
   condition would have to be one class's threshold used for both. See
   the argument above. */
@container ds-view (min-width: 40.5rem) { .ds-view:not(.ds-view--page):not(:has(input:checked)) .ds-view__tab--d { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; } }
@container ds-view not (min-width: 40.5rem) {
  .ds-view:not(.ds-view--page):not(:has(input:checked)) .ds-view__tab--m { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; }
  .ds-view:not(.ds-view--page):not(:has(.ds-view__tab--d input:checked)) .ds-view__box { --ds-h: var(--ds-hm); --ds-w: 390px; }
}
@container ds-view (min-width: 54rem) { .ds-view--page:not(:has(input:checked)) .ds-view__tab--d { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; } }
@container ds-view not (min-width: 54rem) {
  .ds-view--page:not(:has(input:checked)) .ds-view__tab--m { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; }
  .ds-view--page:not(:has(.ds-view__tab--d input:checked)) .ds-view__box { --ds-h: var(--ds-hm); --ds-w: 390px; }
}
@supports not (container-type: inline-size) { .ds-view:not(:has(input:checked)) .ds-view__tab--d { background: var(--rst-accent-soft); color: var(--rst-accent); font-weight: 600; } }
.ds-view__tab:has(input:focus-visible) { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
.ds-view__box { --ds-k: 1; background: var(--rst-bg); block-size: calc(var(--ds-h) * var(--ds-k)); border: 1px solid var(--rst-line); border-radius: var(--rst-radius); inline-size: calc(var(--ds-w) * var(--ds-k)); margin-inline: auto; max-inline-size: 100%; min-block-size: calc(var(--ds-h) * var(--ds-kmin)); overflow: hidden; overflow-x: auto; overscroll-behavior-x: contain; position: relative; resize: vertical; scrollbar-width: thin; }
.ds-view__frame { block-size: calc(100% / var(--ds-k)); border: 0; inline-size: var(--ds-w); left: 0; min-block-size: 100%; position: absolute; top: 0; transform: scale(var(--ds-k)); transform-origin: top left; }
@supports (block-size: calc(1px * clamp(0.5, tan(atan2(100cqw, 1200px)), 1))) {
  .ds-view__box { --ds-k: clamp(var(--ds-kmin), tan(atan2(100cqw, var(--ds-w))), 1); }
}
/* An explicit Mobile beats the width, at either class and any size.
   The two width-driven copies of this declaration are up in the
   container queries; this one answers a click and needs no condition
   beyond it. */
.ds-view:has(.ds-view__tab--m input:checked) .ds-view__box { --ds-h: var(--ds-hm); --ds-w: 390px; }
.ds-view__code { display: none; margin-block-start: 0; }
.ds-view:has(.ds-view__tab--c input:checked) .ds-view__stage { display: none; }
.ds-view:has(.ds-view__tab--c input:checked) .ds-view__code { display: block; }

/* The sidebar. The rail itself is the framework's own sidebar shell —
   rst-shell-sidebar, rst-shell-rail, rst-shell-nav, and the details
   chrome strip that collapses the rail below 800px — so the page that
   documents the vocabulary is laid out in it. What is left here is the
   two things a rail of links has no class for yet: a collapsible
   section per group, and the filter over them. */
.ds-rail { gap: var(--rst-sp-2); }
.ds-nav > details > summary { align-items: center; color: var(--rst-text-faint); cursor: pointer; display: flex; font-size: var(--rst-fs-xs); font-weight: 650; gap: 0.35rem; letter-spacing: 0.06em; list-style: none; padding-block: 0.35rem; text-transform: uppercase; }
.ds-nav > details > summary::-webkit-details-marker { display: none; }
/* The disclosure glyph is the framework's own chevron: the vendored
   Lucide chevron-down inside an [rst-caret], which tokens.css already
   flips 180 degrees on [open] and already stills under
   prefers-reduced-motion. It replaces content: "\25b8"/"\25be", two
   geometric-shape characters that were the only arrow in the whole
   vocabulary not drawn by the icon set — and that render as an emoji
   triangle on some platforms and as nothing at all where the font has
   no glyph for them. It is aria-hidden because it sits beside the
   section's own name. */
.ds-nav > details > summary > [rst-caret] { flex: none; }
.ds-nav > details[aria-current] > summary { color: var(--rst-accent); }
/* A section with nothing to list yet is a link to its page rather than
   a disclosure over an empty box. It wears the summary's typography so
   the rail still reads as one list. */
.ds-nav__page { color: var(--rst-text-faint); font-size: var(--rst-fs-xs); font-weight: 650; letter-spacing: 0.06em; padding-block: 0.35rem; text-transform: uppercase; }
.ds-nav__page[aria-current] { color: var(--rst-accent); }
.ds-nav__page:hover { color: var(--rst-text); }
.ds-nav > details > summary:hover { color: var(--rst-text); }
.ds-nav > details > summary:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }
/* The filter hides a link by setting [hidden] on it, and the shell
   paints every rail link display: block — which the browser's own
   [hidden] rule loses to. This selector outranks it rather than
   shouting !important at it. */
[rst-shell-sidebar] .ds-nav a[hidden], .ds-nav details[hidden] { display: none; }
/* The same scriptless story as the scheme toggle above: no script, no
   filter, and no box sitting there looking like one. The nav under it
   is a complete list of every anchor on the page either way. */
.ds-search { display: none; }
:root[data-rst-js] .ds-search { display: block; }
.ds-search input { background: var(--rst-bg); border: 1px solid var(--rst-line-strong); border-radius: var(--rst-radius-sm); box-sizing: border-box; color: var(--rst-text); font: inherit; font-size: var(--rst-fs-sm); inline-size: 100%; padding: 0.35rem 0.5rem; }
.ds-search input:focus-visible { border-color: var(--rst-accent); outline: 2px solid var(--rst-accent); outline-offset: -1px; }
.ds-nav__empty { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: var(--rst-sp-3) 0 0; }
/* The rail's filter box is 12.5px like everything else in here, and a
   sub-16px field is exactly what makes mobile Safari zoom the whole
   page when it takes focus. tokens.css lifts the app's own controls on
   a coarse pointer; this one carries a ds- class of its own, so it
   outranks that rule and has to say the same thing again. */
@media (pointer: coarse) {
  .ds-search input { font-size: max(1rem, 1em); }
}
