/* gallery.css: the gallery's own chrome, and nothing an application receives. The section rhythm, the routes, the previous and next pair, the token and icon grids, the rail and its filter, the pinned bar, the phone index and the display settings menu. Every value is a token, so the page's own furniture changes theme with everything else. The preview widget, the code block and the highlight colours are view.css's; the two are served joined as one gallery.css. It is Rastrillo's gallery.css for the same chrome, with the scheme toggle's rules gone (Fiesta's scheme control is the system's own dropdown, lifted from the scheme-menu example) and data-gallery-js where Rastrillo's reads data-rst-js: the marker gallery.js sets on , which is the whole of the scriptless story for the filter below. */ .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); } /* A heading is one word on the index, the tokens and the icons pages, and a word has to be able to break at 320px: the page may not scroll sideways for anything. */ [rst-page] h1, [rst-page] h2 { overflow-wrap: anywhere; } /* An example page's family, under its heading: the family's name and its sentence, as a kicker back to the page that files it. Then the example's own sentence and the two every example page says. All three read as Rastrillo's page head reads its leads: one size, one colour, one step between them, so the head is a column of sentences rather than three type sizes stacked. */ .ds-kicker, [rst-page] > [rst-page-header-sub] { color: var(--rst-text-muted); font-size: inherit; margin: 0 0 var(--rst-sp-4); max-width: 62ch; } .ds-kicker b { color: var(--rst-text); font-weight: 600; } /* One state: a card, its label, the note on it, the widget view.css draws, and the routes it stands for. The label is styled by class, not by level: the outline moved once when the pages got real titles, and a rule keyed to h2 would silently unstyle the label the next time it moves. */ .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; } .ds-routes-note { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: var(--rst-sp-3) 0 0; max-width: 62ch; } /* 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); } /* The token grid, and the icon grid that reuses it: one chip per token, its name and value beside it. The icon sits inside its chip 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 an icon's name is long enough to overrun a 13rem column on a 320px screen, so that is the one place in the grid that breaks inside a word. */ .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; overflow-wrap: anywhere; } .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-chip--icon { align-items: center; color: var(--rst-text); display: flex; justify-content: center; } .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 Overview's opening paragraph and the routes under it, which a family page reuses for its examples. .ds-files is the same row, a name, a sentence and a number, on the Getting started page. 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. A route's link takes the target floor like any control. */ .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 { align-items: center; display: inline-flex; font-weight: 600; min-block-size: var(--rst-target); } .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); } /* 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 icon set's own chevron inside an [rst-caret], which tokens.css already flips 180 degrees on [open] and already stills under prefers-reduced-motion. 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 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; } /* No script, no filter, and no box sitting there looking like one. The nav under it is a complete list of every page either way. */ .ds-search { display: none; } :root[data-gallery-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-gallery-js] .ds-search { position: relative; } /* 16px like the system's 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; } @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 system'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 above the menus beside them, 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 shell'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, .ds-view__stage { 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 shell's own profile menu sits, and at the inline end of a content page's back strip. It is an rst-dropdown, so fiesta.js closes it on an outside click and on Escape, and its card is the system'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 system'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 system'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 scheme menu and the language menu inside the card open in place: a popover inside a popover has nowhere to go on a phone. Each is the card's full width so its rows read as rows. */ .ds-prefs :is([rst-locale], [rst-scheme]) { align-self: stretch; } .ds-prefs :is([rst-locale], [rst-scheme])[open] > [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; } @media (max-width: 799.98px) { .ds-prefs { display: block; } /* Lifted into the index's header row, the place and size of the shell'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; } }