/* 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); }
/* 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; }
/* --rst-target is the framework's floor in force: 32px on a desktop
   with a mouse, 44px on a small or touch screen. Read here because a
   ds- control carries no rst- attribute the framework's floors could
   match, and at 0.25rem of padding these were 27px tall. */
.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); min-block-size: var(--rst-target); 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; }
/* The section headings are styled by class, not by level: the outline
   moved once when the pages got real titles, and a rule keyed to h3 or
   h4 would silently unstyle every heading the next time it moves. */
.ds-partial__name { 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); }
/* A grouped widget's note names its state on a line of its own, so no
   joining punctuation has to be written, and translated, between the
   two. */
.ds-note__state { display: block; font-weight: 600; }
/* The Overview's link to the demo is the whole sentence, not a link in
   running text, so it takes the floor. inline-block, because an inline
   box's padding is added to its font's content area, not its line, so
   the same padding came out 28px. */
.ds-demo > .ds-note > a { display: inline-block; padding-block: max(0px, (var(--rst-target) - 1lh) / 2); }
.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); }
/* A grid of one so the floor's height centres the text; the links
   were their 21px line, 24px on a phone. */
.ds-updown__prev, .ds-updown__next { align-content: center; display: grid; min-block-size: var(--rst-target); }
.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__name { font-size: 1.05rem; margin: 0 0 var(--rst-sp-1); }
/* A source block can still scroll sideways where a single token is wider
   than its column, and a box that scrolls must be reachable from the
   keyboard (axe's scrollable-region-focusable; WCAG 2.1.1), so every
   pre.ds-src carries tabindex="0" in page.go. Whether a given block
   overflows depends on the font the browser found. */
.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); }
/* Source soft-wraps rather than scrolling sideways. An inline run (a
   meter's whole output, a choice card) is one line however long,
   because breaking inside it would change the space a reader sees;
   so the block wraps it on screen, and copying takes textContent,
   which has no soft breaks in it. */
.ds-src code { overflow-wrap: anywhere; white-space: pre-wrap; }
/* Code is left to right in every language. Inheriting an Arabic page's
   rtl, a block sits against the right edge and the bidi algorithm
   mirrors its brackets and moves trailing punctuation to the front, so
   what a reader sees is not what Copy puts on the clipboard. isolate
   keeps a name inside a sentence (the wrapper line's) from reordering
   the words around it. */
.ds-src, .ds-wrap code { direction: ltr; text-align: start; unicode-bidi: isolate; }
/* The five token colours, from theme tokens so they follow theme and
   scheme. TestHighlightColoursPassAgainstTheCodeBackground holds each
   to 4.5:1 on --rst-surface, the block's background, in every theme
   and both schemes; it reads these lines, one rule per element. */
ds-t { color: var(--rst-accent); }
ds-a { color: var(--rst-tone-negative-fg); }
ds-v { color: var(--rst-tone-positive-fg); }
ds-x { color: var(--rst-text-muted); }
ds-s { color: var(--rst-tone-warning-fg); }
.ds-wrap { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: 0 0 var(--rst-sp-2); }
/* Sits directly before the block it copies. inline-size: fit-content with
   margin-inline-start: auto pushes a block-level button to the far edge
   without a flex wrapper around it — and does so on the inline axis, so
   it lands on the right in a left-to-right page and the left in Arabic
   or Hebrew without a direction check anywhere in gallery.js. It reads
   the target floor itself, because tokens.css floors a button only
   through rst-btn: as a line of small text it was 27px with a mouse,
   and its old 44px reached a coarse pointer but not a narrow mouse
   window. flex rather than block centres the label in the taller box;
   border-box so the floor is the whole button, not its text. */
.ds-copy { align-items: center; background: none; border: 1px solid var(--rst-line); border-radius: var(--rst-radius-sm); box-sizing: border-box; color: var(--rst-text-muted); cursor: pointer; display: flex; font: inherit; font-size: var(--rst-fs-sm); inline-size: fit-content; justify-content: center; margin-inline-start: auto; min-block-size: var(--rst-target); min-inline-size: var(--rst-target); padding-block: 0.2rem; padding-inline: 0.6rem; }
.ds-copy:hover { background: var(--rst-accent-soft); color: var(--rst-text); }
.ds-copy:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; }

/* The preview widget: [Desktop] [Mobile] [HTML] [Template] over one
   frame. HTML and Template are there only when the example has that
   code to show, so a widget has two, three or four tabs.

   No script anywhere in it. The tabs are 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
   views as everyone else.

   None of them 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 Desktop or Mobile, and any 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 Mobile rendering is the
   opening view, and on a phone it is that phone's page at its own
   size: the stage is the screen's width and Mobile is never scaled.

   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-wm: 390px; --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; }
/* A grouped row's frame sits flush with its tabs, not centred: its
   states start at the inline start like the labels above them. */
.ds-view--rows .ds-view__box { margin-inline-start: 0; }
/* Two pairs, the renderings and the sources, that wrap as wholes: four
   tabs in one row are 308px in English and 363px in Portuguese, wider
   than a sample's column on a 320px phone, and a label that may wrap
   breaks a tab into two lines of text. */
.ds-view__tabs { border: 0; display: flex; flex-wrap: wrap; gap: var(--rst-sp-2); margin: 0 0 var(--rst-sp-2); padding: 0; }
.ds-view__pair { display: inline-flex; }
/* A tab is a target like any control: the floor in force, border-box so
   the border and padding are inside it. As its text and padding it was
   27px with a mouse and 29 on a phone. */
.ds-view__tab { align-items: center; border: 1px solid var(--rst-line); box-sizing: border-box; color: var(--rst-text-muted); cursor: pointer; display: inline-flex; font-size: var(--rst-fs-sm); min-block-size: var(--rst-target); padding: 0.2rem 0.7rem; white-space: nowrap; }
.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: var(--ds-wm); }
}
@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: var(--ds-wm); }
}
@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); inset-block-start: 0; inset-inline-start: 0; min-block-size: 100%; position: absolute; transform: scale(var(--ds-k)); transform-origin: top left; }
/* transform-origin has no logical keywords, so the side is a selector,
   as tokens.css does for its drawn chevrons. Anchored at the physical
   left, a right-to-left box could not scroll to the frame's start. */
[dir="rtl"] .ds-view__frame { transform-origin: top right; }
/* A frame shows nothing of its own until gallery.js marks it, once its
   document is parsed and styled and has the page's scheme written on
   it (load if that is missed), and again for each document a
   navigation inside the frame loads. Before that, in every scheme:
   - the frame holds the about:blank it was created with, which Safari
     painted white on a dark page (see the color-scheme rule below);
   - its document is parsing (Safari shows rgb(30,30,30) on a page of
     rgb(15,15,15)), or parsed without its stylesheets;
   - with Light or Dark chosen, it has painted in the OS's scheme: white
     in Dark on a light Mac, while scrolling to a lazy frame, after a
     reload at a remembered scroll, or on a click inside one.
   Hidden, the box behind it shows this page's --rst-bg instead; a
   hidden frame still lazy-loads (measured). data-rst-js because
   gallery.js is what shows it: with scripts off nothing would, and the
   frames show as they come. */
:root[data-rst-js] .ds-view__frame:not([data-ds-painted]) { visibility: hidden; }
/* A browser compares a frame's color-scheme with its document's, and
   Safari draws a document whose scheme differs on an opaque canvas.
   about:blank declares none, so it is light, and with the frame
   inheriting a dark page's scheme every frame was white until its
   document came (measured in WebKit: light dark and dark are white,
   normal and light transparent). Normal matches it. A preview declares
   its own scheme and paints its whole canvas, so this changes nothing
   once it paints. It is all a reader without scripts gets, since
   nothing hides their frames. */
.ds-view__frame { color-scheme: normal; }
@supports (block-size: calc(1px * clamp(0.5, tan(atan2(100cqw, 1200px)), 1))) {
  /* Mobile is the stage's width up to 390px. Only below 390px of stage
     was Mobile ever scaled, and that is exactly when laying the page
     out at the stage's width is what that screen would show; min()
     keeps a 760px window's Mobile the 390px phone rather than a 760px
     tablet. --ds-k is then at least 1, so Mobile is never scaled. The
     cqw resolves where --ds-w is used, inside .ds-view, its container.
     An engine without container units keeps 390px and the old scale. */
  .ds-view { --ds-wm: min(390px, 100cqw); }
  .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: var(--ds-wm); }
/* HTML and Template each replace the frame with their own panel, and
   only theirs: the radios are one group, so one is checked at most. */
.ds-view__code { display: none; margin-block-start: 0; }
.ds-view:has(.ds-view__tab--h input:checked) .ds-view__stage, .ds-view:has(.ds-view__tab--t input:checked) .ds-view__stage { display: none; }
.ds-view:has(.ds-view__tab--h input:checked) .ds-view__code--h, .ds-view:has(.ds-view__tab--t input:checked) .ds-view__code--t { display: block; }

/* The rail is the shipped sidebar layout's, below 800px the phone
   index on the Overview and hidden on every other page, all by
   tokens.css's own rules. What is left here is what a rail of links
   has no attribute for: a collapsible section per group, and the
   filter over them. */
.ds-rail { gap: var(--rst-sp-2); }
/* border-box: on the content box the floor came on top of the padding,
   and the headings grew from 28 to 43px. */
.ds-nav > details > summary { align-items: center; box-sizing: border-box; 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; min-block-size: var(--rst-target); 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; }
/* The magnifier sits inside the field at its inline start, over the
   field's padding, and lets a tap through to the field under it. It is
   the icon set's own search glyph, aria-hidden, because the field's
   label already names it. */
:root[data-rst-js] .ds-search { position: relative; }
/* 16px like the framework search box's icon: tokens.css sizes an icon
   that way only inside its own controls, and this box is the gallery's. */
.ds-search > .icon { block-size: 1rem; color: var(--rst-text-faint); inline-size: 1rem; inset-block-start: 50%; inset-inline-start: 0.55rem; pointer-events: none; position: absolute; transform: translateY(-50%); }
.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%; min-block-size: var(--rst-target); padding-block: 0.35rem; padding-inline: 1.9rem 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); }
}

/* ── The frame ───────────────────────────────────────────────────────
   At 800px and up the bar is pinned over main. A grid item's
   containing block is its grid area and a sticky box cannot leave it,
   so a bar in a one-row area has no room to stick; it gets the rail's
   shape instead, an area spanning every row, with the first row
   reserving its height. Lines are numbered, not sided, so in Arabic
   the rail is on the right and the bar over main on the left with no
   rule here.

   --ds-bar-h is the bar's border-box height per band, the measured
   tallest over every theme and language. From 1024px the no-wrap rule
   below keeps the bar to one row everywhere, so the reservation is
   that one line's height. Below 1024px the controls can still wrap to
   a second line, and the reservation is the measured two-row maximum.
   TestThePinnedBarFitsItsReservation measures what is inside the bar
   against it. */
:root { --ds-bar-h: 3.25rem; }
/* measured over every theme and locale on 2026-10-08: room for two.
   It was 5rem until the 32px target floor reached the bar: the brand's
   row and the theme tabs' 36px track made the two rows 93px. */
@media (min-width: 800px) and (max-width: 1023.98px) { :root { --ds-bar-h: 6rem; } }
.ds-top { align-items: center; border-block-end: 1px solid var(--rst-line); display: flex; flex-wrap: wrap; gap: var(--rst-sp-2) var(--rst-sp-3); justify-content: space-between; padding: var(--rst-sp-2) var(--rst-sp-4); }
/* Padding rather than a min-block-size, because from 1024px the brand
   elides its text, which a flex or grid box cannot do: the link grows
   to the floor round its one line, and the bar centres it. */
.ds-top__brand { color: var(--rst-text); font-weight: 650; padding-block: max(0px, (var(--rst-target) - 1lh) / 2); text-decoration: none; }
.ds-top__controls { align-items: center; display: flex; flex-wrap: wrap; gap: var(--rst-sp-3); justify-content: flex-end; }
.ds-top__controls [rst-dropdown] { position: relative; }
/* The theme switcher is the framework's seg-tabs, which leaves a
   block's gap under itself. Here it sits in a row and in a column that
   space their own items: in the bar's centred row the margin lifted the
   tabs 8px above the scheme buttons, and in the phone card it more than
   doubled the gap under them. */
.ds-top__controls [rst-seg-tabs], .ds-prefs [rst-seg-tabs] { margin: 0; }
.ds-top__controls [rst-dropdown] > summary { padding-block: 0.25rem; }
/* The bar's own box is fixed at --ds-bar-h and cannot grow to a second
   row without overflowing its grid area (above), so from 1024px it
   must not wrap at all. The brand is what yields: eliding its text
   leaves its accessible name untouched, while every control has to
   stay whole for its hit target and its label to both still work. */
@media (min-width: 1024px) {
  .ds-top { flex-wrap: nowrap; }
  .ds-top__controls { flex-shrink: 0; }
  .ds-top__brand { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (min-width: 800px) {
  body > [rst-shell-sidebar] { grid-template-rows: var(--ds-bar-h) 1fr; }
  body > [rst-shell-sidebar] > [rst-shell-rail] { grid-column: 1; grid-row: 1 / -1; }
  /* Opaque, and above page content (auto, the positioned preview boxes
     and transformed frames included) but below the skip link's 60. */
  .ds-top { align-self: start; background: var(--rst-bg); block-size: var(--ds-bar-h); box-sizing: border-box; grid-column: 2; grid-row: 1 / -1; inset-block-start: 0; position: sticky; z-index: 10; }
  body > [rst-shell-sidebar] > [rst-shell-main] { grid-column: 2; grid-row: 2; }
  /* Fragments and focus stop below the pinned bar (WCAG technique C43).
     Only here: below 800px what is pinned is the framework's back
     strip, and its clearance is tokens.css's to set; a rule on html at
     every width would override that. */
  html { scroll-padding-block-start: calc(var(--ds-bar-h) + var(--rst-sp-2)); }
}
/* Pieces only the phone index shows, written on the Overview alone. At
   800px and up the Overview is a page with the rail beside it, as the
   shell's index is, and these would be a second list under the tree
   and a second set of controls beside the bar's. */
.ds-index-lead, .ds-rail > .ds-index { display: none; }
@media (max-width: 799.98px) {
  /* On a phone a section page is the way back and the content, and the
     controls are in the display settings menu at the top of every page
     (below). */
  .ds-top { display: none; }
  [rst-shell-sidebar~="index"] .ds-index-lead { color: var(--rst-text-muted); display: block; margin: 0 0 var(--rst-sp-4); padding-inline: 0.25rem; }
  [rst-shell-sidebar~="index"] > .ds-rail > .ds-index { display: flex; }
  /* The rail on the index has no gap of its own (the rows sit flush
     under the heading), so the field would touch the first row. */
  [rst-shell-sidebar~="index"] .ds-search { margin-block-end: var(--rst-sp-3); }
  /* A query swaps the rows for the tree it filters. Two rules, so an
     engine without :has() drops both and shows rows and tree one after
     the other: long, but with no route missing. */
  [rst-shell-sidebar~="index"]:not(:has(#ds-filter:not(:placeholder-shown))) > .ds-rail > .ds-nav { display: none; }
  [rst-shell-sidebar~="index"]:has(#ds-filter:not(:placeholder-shown)) > .ds-rail > .ds-index { display: none; }
}
@media (max-width: 799.98px) {
  /* On a phone the stage is the screen's width. The bleed is what sits
     between the widget and the screen's edge: the page's padding, and
     inside a sample card its padding and border as well. Written per
     context rather than measured, so a change to either shows up as a
     sideways scroll in the reflow and phone drives instead of a quietly
     narrower stage. The tabs and the code stay in the text column. */
  .ds-view { --ds-bleed: var(--rst-sp-4); margin-inline: calc(-1 * var(--ds-bleed)); }
  .ds-sample .ds-view { --ds-bleed: calc(2 * var(--rst-sp-4) + 1px); }
  .ds-view__tabs, .ds-view__code { margin-inline: var(--ds-bleed); }
  /* Content-box sizing under max-inline-size: 100%: a border here
     would make a full-width box 2px wider than the screen. */
  .ds-view__box { border-inline-width: 0; border-radius: 0; }
}

/* ── Display settings, on a phone ────────────────────────────────────
   Below 800px the pinned bar is gone, and the theme, scheme and language
   controls are one menu at the top of every page: in the index's header
   row beside the heading, where the framework's own profile menu sits,
   and at the inline end of a content page's back strip. It is an
   rst-dropdown, so rastrillo.js closes it on an outside click and on
   Escape, and its card is the framework's anchored panel. At 800px and
   up the bar has the controls and the menu is not drawn. */
.ds-prefs { display: none; }
/* border-box, so the framework's target floor is the whole button
   rather than 44px of icon inside its padding. */
.ds-prefs > summary { border-radius: var(--rst-radius-sm); box-sizing: border-box; color: var(--rst-text); justify-content: center; padding: 0.4rem; }
.ds-prefs > summary:hover, .ds-prefs[open] > summary { background: var(--rst-accent-soft); }
.ds-prefs > summary:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; }
/* Scripts off, a tap outside the card still closes it: the open
   summary grows a layer over the viewport, under the card, so the tap
   is a click on the summary. The framework's menu cards do the same. */
.ds-prefs[open] > summary::before { content: ""; cursor: default; inset: 0; position: fixed; z-index: 1; }
/* Taller than a menu's usual 20rem cap: with the language list open in
   place the card holds twelve 44px rows, and the screen is the room it
   has, not a row of a toolbar. */
.ds-prefs > [rst-dropdown-menu] { align-items: flex-start; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--rst-sp-3); inline-size: max-content; max-block-size: calc(100dvh - 6rem); max-inline-size: calc(100vw - 1.5rem); padding: var(--rst-sp-3); }
/* The language menu inside the card opens in place: a popover inside a
   popover has nowhere to go on a phone. It is the card's full width so
   its rows read as rows. */
.ds-prefs [rst-locale] { align-self: stretch; }
.ds-prefs [rst-dropdown-menu] [rst-dropdown-menu] { border: 0; box-shadow: none; inset: auto; margin: 0; max-block-size: none; min-width: 0; padding-block: 0; padding-inline: 0.5rem 0; position: static; }
/* The card's own button rule (a full-width menu row) would stretch the
   scheme buttons out of their segmented group. */
.ds-prefs .ds-scheme button { border-radius: 0; inline-size: auto; }
@media (max-width: 799.98px) {
  .ds-prefs { display: block; }
  /* Lifted into the index's header row, the place and size of the
     framework's profile menu: the rail is positioned on the index, and
     the heading keeps that corner clear. */
  [rst-shell-sidebar~="index"] > .ds-rail > .ds-prefs { align-items: center; block-size: var(--rst-shell-strip); display: flex; inset-block-start: 0; inset-inline-end: var(--rst-sp-4); position: absolute; }
  [rst-shell-back] > .ds-prefs { margin-inline-start: auto; }
}
