| 1 | /* gallery.css: the gallery's own chrome, and nothing an application |
| 2 | receives. The section rhythm, the routes, the previous and next |
| 3 | pair, the token and icon grids, the rail and its filter, the pinned |
| 4 | bar, the phone index and the display settings menu. Every value is a |
| 5 | token, so the page's own furniture changes theme with everything |
| 6 | else. The preview widget, the code block and the highlight colours |
| 7 | are view.css's; the two are served joined as one gallery.css. |
| 8 | |
| 9 | It is Rastrillo's gallery.css for the same chrome, with the scheme |
| 10 | toggle's rules gone (Fiesta's scheme control is the system's own |
| 11 | dropdown, lifted from the scheme-menu example) and data-gallery-js |
| 12 | where Rastrillo's reads data-rst-js: the marker gallery.js sets on |
| 13 | <html>, which is the whole of the scriptless story for the filter |
| 14 | below. */ |
| 15 | |
| 16 | .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); } |
| 17 | .ds-head h2 { font-size: 1.35rem; letter-spacing: -0.01em; margin: 0; } |
| 18 | .ds-lead { color: var(--rst-text-muted); margin: 0 0 var(--rst-sp-4); max-width: 62ch; } |
| 19 | .ds-sub { font-size: var(--rst-fs-base); margin: var(--rst-sp-5) 0 var(--rst-sp-2); } |
| 20 | /* A heading is one word on the index, the tokens and the icons pages, |
| 21 | and a word has to be able to break at 320px: the page may not scroll |
| 22 | sideways for anything. */ |
| 23 | [rst-page] h1, [rst-page] h2 { overflow-wrap: anywhere; } |
| 24 | /* An example page's family, under its heading: the family's name and |
| 25 | its sentence, as a kicker back to the page that files it. Then the |
| 26 | example's own sentence and the two every example page says. All |
| 27 | three read as Rastrillo's page head reads its leads: one size, one |
| 28 | colour, one step between them, so the head is a column of sentences |
| 29 | rather than three type sizes stacked. */ |
| 30 | .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; } |
| 31 | .ds-kicker b { color: var(--rst-text); font-weight: 600; } |
| 32 | /* One state: a card, its label, the note on it, the widget view.css |
| 33 | draws, and the routes it stands for. The label is styled by class, |
| 34 | not by level: the outline moved once when the pages got real titles, |
| 35 | and a rule keyed to h2 would silently unstyle the label the next |
| 36 | time it moves. */ |
| 37 | .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); } |
| 38 | .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; } |
| 39 | .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); } |
| 40 | /* A grouped widget's note names its state on a line of its own, so no |
| 41 | joining punctuation has to be written, and translated, between the |
| 42 | two. */ |
| 43 | .ds-note__state { display: block; font-weight: 600; } |
| 44 | .ds-routes-note { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: var(--rst-sp-3) 0 0; max-width: 62ch; } |
| 45 | /* The Overview's link to the demo is the whole sentence, not a link in |
| 46 | running text, so it takes the floor. inline-block, because an inline |
| 47 | box's padding is added to its font's content area, not its line, so |
| 48 | the same padding came out 28px. */ |
| 49 | .ds-demo > .ds-note > a { display: inline-block; padding-block: max(0px, (var(--rst-target) - 1lh) / 2); } |
| 50 | /* The token grid, and the icon grid that reuses it: one chip per |
| 51 | token, its name and value beside it. The icon sits inside its chip |
| 52 | at the 1em tokens.css gives it, which is the size a component draws |
| 53 | it at rather than a poster of it. The call under an icon's name is |
| 54 | long enough to overrun a 13rem column on a 320px screen, so that is |
| 55 | the one place in the grid that breaks inside a word. */ |
| 56 | .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; } |
| 57 | .ds-tok { align-items: center; display: flex; gap: var(--rst-sp-3); min-inline-size: 0; } |
| 58 | .ds-tok__text { min-inline-size: 0; } |
| 59 | .ds-tok__name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| 60 | .ds-tok__value { color: var(--rst-text-muted); display: block; overflow-wrap: anywhere; } |
| 61 | .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; } |
| 62 | .ds-chip--fill { background: var(--rst-accent); border-color: var(--rst-accent); } |
| 63 | .ds-chip--icon { align-items: center; color: var(--rst-text); display: flex; justify-content: center; } |
| 64 | .ds-bar { background: var(--rst-accent); block-size: 1.25rem; border-radius: 2px; flex: none; } |
| 65 | .ds-type { display: block; flex: none; inline-size: 3.25rem; line-height: 1.15; text-align: center; } |
| 66 | .ds-swatch-note { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: 0 0 var(--rst-sp-4); max-width: 62ch; } |
| 67 | /* The Overview's opening paragraph and the routes under it, which a |
| 68 | family page reuses for its examples. .ds-files is the same row, a |
| 69 | name, a sentence and a number, on the Getting started page. The |
| 70 | paragraph is the one piece of copy on this site somebody wrote by |
| 71 | hand rather than derived from the code, so it is set a step larger |
| 72 | than body text and given the same 62ch measure everything else here |
| 73 | reads at. A route's link takes the target floor like any control. */ |
| 74 | .ds-intro { font-size: 1.05rem; margin: 0 0 var(--rst-sp-5); max-width: 62ch; } |
| 75 | .ds-routes, .ds-files { display: grid; gap: var(--rst-sp-4); list-style: none; margin: 0 0 var(--rst-sp-5); padding: 0; } |
| 76 | .ds-routes > li, .ds-files > li { border-inline-start: 2px solid var(--rst-line-strong); padding-inline-start: var(--rst-sp-3); } |
| 77 | .ds-routes a, .ds-files a { align-items: center; display: inline-flex; font-weight: 600; min-block-size: var(--rst-target); } |
| 78 | .ds-routes span, .ds-files span { color: var(--rst-text-muted); display: block; font-size: var(--rst-fs-sm); max-width: 62ch; } |
| 79 | /* Prev/next at the foot. Two grid columns rather than a flex row with |
| 80 | space-between, because the ends of the sequence are missing a link |
| 81 | and not missing a column: the Overview's Next has to stay on the |
| 82 | inline end rather than sliding across to where Previous would have |
| 83 | been. */ |
| 84 | .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); } |
| 85 | /* A grid of one so the floor's height centres the text; the links |
| 86 | were their 21px line, 24px on a phone. */ |
| 87 | .ds-updown__prev, .ds-updown__next { align-content: center; display: grid; min-block-size: var(--rst-target); } |
| 88 | .ds-updown__prev { grid-column: 1; justify-self: start; } |
| 89 | .ds-updown__next { grid-column: 2; justify-self: end; text-align: end; } |
| 90 | .ds-shell { margin: var(--rst-sp-5) 0; } |
| 91 | .ds-shell__name { font-size: 1.05rem; margin: 0 0 var(--rst-sp-1); } |
| 92 | |
| 93 | /* The rail is the shipped sidebar layout's, below 800px the phone |
| 94 | index on the Overview and hidden on every other page, all by |
| 95 | tokens.css's own rules. What is left here is what a rail of links |
| 96 | has no attribute for: a collapsible section per group, and the |
| 97 | filter over them. */ |
| 98 | .ds-rail { gap: var(--rst-sp-2); } |
| 99 | /* border-box: on the content box the floor came on top of the padding, |
| 100 | and the headings grew from 28 to 43px. */ |
| 101 | .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; } |
| 102 | .ds-nav > details > summary::-webkit-details-marker { display: none; } |
| 103 | /* The disclosure glyph is the icon set's own chevron inside an |
| 104 | [rst-caret], which tokens.css already flips 180 degrees on [open] |
| 105 | and already stills under prefers-reduced-motion. It is aria-hidden |
| 106 | because it sits beside the section's own name. */ |
| 107 | .ds-nav > details > summary > [rst-caret] { flex: none; } |
| 108 | .ds-nav > details[aria-current] > summary { color: var(--rst-accent); } |
| 109 | /* A section with nothing to list is a link to its page rather than a |
| 110 | disclosure over an empty box. It wears the summary's typography so |
| 111 | the rail still reads as one list. */ |
| 112 | .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; } |
| 113 | .ds-nav__page[aria-current] { color: var(--rst-accent); } |
| 114 | .ds-nav__page:hover { color: var(--rst-text); } |
| 115 | .ds-nav > details > summary:hover { color: var(--rst-text); } |
| 116 | .ds-nav > details > summary:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: 2px; } |
| 117 | /* The filter hides a link by setting [hidden] on it, and the shell |
| 118 | paints every rail link display: block, which the browser's own |
| 119 | [hidden] rule loses to. This selector outranks it rather than |
| 120 | shouting !important at it. */ |
| 121 | [rst-shell-sidebar] .ds-nav a[hidden], .ds-nav details[hidden] { display: none; } |
| 122 | /* No script, no filter, and no box sitting there looking like one. The |
| 123 | nav under it is a complete list of every page either way. */ |
| 124 | .ds-search { display: none; } |
| 125 | :root[data-gallery-js] .ds-search { display: block; } |
| 126 | /* The magnifier sits inside the field at its inline start, over the |
| 127 | field's padding, and lets a tap through to the field under it. It is |
| 128 | the icon set's own search glyph, aria-hidden, because the field's |
| 129 | label already names it. */ |
| 130 | :root[data-gallery-js] .ds-search { position: relative; } |
| 131 | /* 16px like the system's search box's icon: tokens.css sizes an icon |
| 132 | that way only inside its own controls, and this box is the gallery's. */ |
| 133 | .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%); } |
| 134 | .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; } |
| 135 | .ds-search input:focus-visible { border-color: var(--rst-accent); outline: 2px solid var(--rst-accent); outline-offset: -1px; } |
| 136 | .ds-nav__empty { color: var(--rst-text-muted); font-size: var(--rst-fs-sm); margin: var(--rst-sp-3) 0 0; } |
| 137 | /* The rail's filter box is 12.5px like everything else in here, and a |
| 138 | sub-16px field is exactly what makes mobile Safari zoom the whole |
| 139 | page when it takes focus. tokens.css lifts the app's own controls on |
| 140 | a coarse pointer; this one carries a ds- class of its own, so it |
| 141 | outranks that rule and has to say the same thing again. */ |
| 142 | @media (pointer: coarse) { |
| 143 | .ds-search input { font-size: max(1rem, 1em); } |
| 144 | } |
| 145 | |
| 146 | /* The frame. At 800px and up the bar is pinned over main. A grid |
| 147 | item's containing block is its grid area and a sticky box cannot |
| 148 | leave it, so a bar in a one-row area has no room to stick; it gets |
| 149 | the rail's shape instead, an area spanning every row, with the first |
| 150 | row reserving its height. Lines are numbered, not sided, so in |
| 151 | Arabic the rail is on the right and the bar over main on the left |
| 152 | with no rule here. |
| 153 | |
| 154 | --ds-bar-h is the bar's border-box height per band, the measured |
| 155 | tallest over every theme and language. From 1024px the no-wrap rule |
| 156 | below keeps the bar to one row everywhere, so the reservation is |
| 157 | that one line's height. Below 1024px the controls can still wrap to |
| 158 | a second line, and the reservation is the measured two-row maximum. |
| 159 | TestThePinnedBarFitsItsReservation measures what is inside the bar |
| 160 | against it. */ |
| 161 | :root { --ds-bar-h: 3.25rem; } |
| 162 | @media (min-width: 800px) and (max-width: 1023.98px) { :root { --ds-bar-h: 6rem; } } |
| 163 | .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); } |
| 164 | /* Padding rather than a min-block-size, because from 1024px the brand |
| 165 | elides its text, which a flex or grid box cannot do: the link grows |
| 166 | to the floor round its one line, and the bar centres it. */ |
| 167 | .ds-top__brand { color: var(--rst-text); font-weight: 650; padding-block: max(0px, (var(--rst-target) - 1lh) / 2); text-decoration: none; } |
| 168 | .ds-top__controls { align-items: center; display: flex; flex-wrap: wrap; gap: var(--rst-sp-3); justify-content: flex-end; } |
| 169 | .ds-top__controls [rst-dropdown] { position: relative; } |
| 170 | /* The theme switcher is the system's seg-tabs, which leaves a block's |
| 171 | gap under itself. Here it sits in a row and in a column that space |
| 172 | their own items: in the bar's centred row the margin lifted the tabs |
| 173 | above the menus beside them, and in the phone card it more than |
| 174 | doubled the gap under them. */ |
| 175 | .ds-top__controls [rst-seg-tabs], .ds-prefs [rst-seg-tabs] { margin: 0; } |
| 176 | .ds-top__controls [rst-dropdown] > summary { padding-block: 0.25rem; } |
| 177 | /* The bar's own box is fixed at --ds-bar-h and cannot grow to a second |
| 178 | row without overflowing its grid area (above), so from 1024px it |
| 179 | must not wrap at all. The brand is what yields: eliding its text |
| 180 | leaves its accessible name untouched, while every control has to |
| 181 | stay whole for its hit target and its label to both still work. */ |
| 182 | @media (min-width: 1024px) { |
| 183 | .ds-top { flex-wrap: nowrap; } |
| 184 | .ds-top__controls { flex-shrink: 0; } |
| 185 | .ds-top__brand { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| 186 | } |
| 187 | @media (min-width: 800px) { |
| 188 | body > [rst-shell-sidebar] { grid-template-rows: var(--ds-bar-h) 1fr; } |
| 189 | body > [rst-shell-sidebar] > [rst-shell-rail] { grid-column: 1; grid-row: 1 / -1; } |
| 190 | /* Opaque, and above page content (auto, the positioned preview boxes |
| 191 | and transformed frames included) but below the skip link's 60. */ |
| 192 | .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; } |
| 193 | body > [rst-shell-sidebar] > [rst-shell-main] { grid-column: 2; grid-row: 2; } |
| 194 | /* Fragments and focus stop below the pinned bar (WCAG technique C43). |
| 195 | Only here: below 800px what is pinned is the shell's back strip, |
| 196 | and its clearance is tokens.css's to set; a rule on html at every |
| 197 | width would override that. */ |
| 198 | html { scroll-padding-block-start: calc(var(--ds-bar-h) + var(--rst-sp-2)); } |
| 199 | } |
| 200 | /* Pieces only the phone index shows, written on the Overview alone. At |
| 201 | 800px and up the Overview is a page with the rail beside it, as the |
| 202 | shell's index is, and these would be a second list under the tree |
| 203 | and a second set of controls beside the bar's. */ |
| 204 | .ds-index-lead, .ds-rail > .ds-index { display: none; } |
| 205 | @media (max-width: 799.98px) { |
| 206 | /* On a phone a section page is the way back and the content, and the |
| 207 | controls are in the display settings menu at the top of every page |
| 208 | (below). */ |
| 209 | .ds-top { display: none; } |
| 210 | [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; } |
| 211 | [rst-shell-sidebar~="index"] > .ds-rail > .ds-index { display: flex; } |
| 212 | /* The rail on the index has no gap of its own (the rows sit flush |
| 213 | under the heading), so the field would touch the first row. */ |
| 214 | [rst-shell-sidebar~="index"] .ds-search { margin-block-end: var(--rst-sp-3); } |
| 215 | /* A query swaps the rows for the tree it filters. Two rules, so an |
| 216 | engine without :has() drops both and shows rows and tree one after |
| 217 | the other: long, but with no route missing. */ |
| 218 | [rst-shell-sidebar~="index"]:not(:has(#ds-filter:not(:placeholder-shown))) > .ds-rail > .ds-nav { display: none; } |
| 219 | [rst-shell-sidebar~="index"]:has(#ds-filter:not(:placeholder-shown)) > .ds-rail > .ds-index { display: none; } |
| 220 | } |
| 221 | @media (max-width: 799.98px) { |
| 222 | /* On a phone the stage is the screen's width. The bleed is what sits |
| 223 | between the widget and the screen's edge: the page's padding, and |
| 224 | inside a sample card its padding and border as well. Written per |
| 225 | context rather than measured, so a change to either shows up as a |
| 226 | sideways scroll in the reflow and phone drives instead of a quietly |
| 227 | narrower stage. The tabs and the code stay in the text column. */ |
| 228 | .ds-view { --ds-bleed: var(--rst-sp-4); margin-inline: calc(-1 * var(--ds-bleed)); } |
| 229 | .ds-sample .ds-view { --ds-bleed: calc(2 * var(--rst-sp-4) + 1px); } |
| 230 | .ds-view__tabs, .ds-view__code { margin-inline: var(--ds-bleed); } |
| 231 | /* Content-box sizing under max-inline-size: 100%: a border here |
| 232 | would make a full-width box 2px wider than the screen. */ |
| 233 | .ds-view__box, .ds-view__stage { border-inline-width: 0; border-radius: 0; } |
| 234 | } |
| 235 | |
| 236 | /* Display settings, on a phone. Below 800px the pinned bar is gone, |
| 237 | and the theme, scheme and language controls are one menu at the top |
| 238 | of every page: in the index's header row beside the heading, where |
| 239 | the shell's own profile menu sits, and at the inline end of a content |
| 240 | page's back strip. It is an rst-dropdown, so fiesta.js closes it on |
| 241 | an outside click and on Escape, and its card is the system's |
| 242 | anchored panel. At 800px and up the bar has the controls and the |
| 243 | menu is not drawn. */ |
| 244 | .ds-prefs { display: none; } |
| 245 | /* border-box, so the system's target floor is the whole button rather |
| 246 | than 44px of icon inside its padding. */ |
| 247 | .ds-prefs > summary { border-radius: var(--rst-radius-sm); box-sizing: border-box; color: var(--rst-text); justify-content: center; padding: 0.4rem; } |
| 248 | .ds-prefs > summary:hover, .ds-prefs[open] > summary { background: var(--rst-accent-soft); } |
| 249 | .ds-prefs > summary:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; } |
| 250 | /* Scripts off, a tap outside the card still closes it: the open |
| 251 | summary grows a layer over the viewport, under the card, so the tap |
| 252 | is a click on the summary. The system's menu cards do the same. */ |
| 253 | .ds-prefs[open] > summary::before { content: ""; cursor: default; inset: 0; position: fixed; z-index: 1; } |
| 254 | /* Taller than a menu's usual 20rem cap: with the language list open in |
| 255 | place the card holds twelve 44px rows, and the screen is the room it |
| 256 | has, not a row of a toolbar. */ |
| 257 | .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); } |
| 258 | /* The scheme menu and the language menu inside the card open in place: |
| 259 | a popover inside a popover has nowhere to go on a phone. Each is the |
| 260 | card's full width so its rows read as rows. */ |
| 261 | .ds-prefs :is([rst-locale], [rst-scheme]) { align-self: stretch; } |
| 262 | .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; } |
| 263 | @media (max-width: 799.98px) { |
| 264 | .ds-prefs { display: block; } |
| 265 | /* Lifted into the index's header row, the place and size of the |
| 266 | shell's profile menu: the rail is positioned on the index, and the |
| 267 | heading keeps that corner clear. */ |
| 268 | [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; } |
| 269 | [rst-shell-back] > .ds-prefs { margin-inline-start: auto; } |
| 270 | } |
| 271 | |