| 1 | /* day — rastrillo's default theme: an everyday blue on white and grey. |
| 2 | Structure lives in tokens.css; this file is colour, type and shape. |
| 3 | rastrillo new writes it once as static/theme.css. |
| 4 | |
| 5 | The look: professional, with a point of view. Near-greyscale neutrals |
| 6 | with hairline borders, one cobalt that means "this does something", |
| 7 | crisp 6px and 4px corners, and natural depth: light comes from above, |
| 8 | cards rest on the page, controls press in. Status keeps the |
| 9 | conventional green, amber and red, so a pill is never mistaken for a |
| 10 | link. |
| 11 | |
| 12 | ── One block, both schemes ─────────────────────────────────────────── |
| 13 | Every token is declared once, as light-dark(<light>, <dark>), under a |
| 14 | single :root that sets color-scheme: light dark. The browser picks the |
| 15 | half that matches the resolved scheme, so there is no second copy of |
| 16 | the palette to keep in sync and no way to author dark half-done. The |
| 17 | two rules at the bottom are the whole toggle: setting color-scheme on |
| 18 | :root[data-theme=…] re-resolves every light-dark() in the file at |
| 19 | once, in both directions, which is why an explicit choice beats the OS |
| 20 | without restating a single colour. |
| 21 | |
| 22 | Tokens with one value in both schemes — the font stack, the radii — |
| 23 | are written plain, because light-dark(x, x) says nothing twice. |
| 24 | |
| 25 | light-dark() is a colour function: it may only stand where a <color> |
| 26 | is expected. So a shadow token wraps its colour in it and writes the |
| 27 | geometry once, outside the call — `0 8px 24px light-dark(a, b)`, never |
| 28 | `light-dark(0 8px 24px a, …)`, which no browser would accept as a |
| 29 | box-shadow. |
| 30 | |
| 31 | ── The shape axis ──────────────────────────────────────────────────── |
| 32 | Radius and shadow are the theme's now, not tokens.css's: a theme that |
| 33 | can only change colour cannot change how an app feels. day is crisp |
| 34 | and lit from above: 6px cards, 4px controls, a short contact shadow |
| 35 | where an object meets the page and soft ambient light around it, so |
| 36 | things rest on the page rather than float. |
| 37 | |
| 38 | ── WCAG 2.2 AA, measured ───────────────────────────────────────────── |
| 39 | Normal text needs 4.5:1; large text and UI component boundaries need |
| 40 | 3:1. Every pair below was computed from these exact hex values, in |
| 41 | both schemes. If you change a token, recompute its rows. |
| 42 | |
| 43 | LIGHT ratio requirement |
| 44 | --rst-text on --rst-surface 17.72 4.5:1 text |
| 45 | --rst-text on --rst-bg 17.13 4.5:1 text |
| 46 | --rst-text on --rst-surface-2 15.99 4.5:1 text |
| 47 | --rst-text on --rst-accent-soft 15.65 4.5:1 text |
| 48 | --rst-text-muted on --rst-surface 7.72 4.5:1 text |
| 49 | --rst-text-muted on --rst-bg 7.46 4.5:1 text |
| 50 | --rst-text-muted on --rst-surface-2 6.97 4.5:1 text |
| 51 | --rst-text-muted on --rst-accent-soft 6.82 4.5:1 text |
| 52 | --rst-text-faint on --rst-surface 5.27 4.5:1 text |
| 53 | --rst-text-faint on --rst-bg 5.10 4.5:1 text |
| 54 | --rst-text-faint on --rst-surface-2 4.76 4.5:1 text |
| 55 | --rst-text-faint on --rst-accent-soft 4.65 4.5:1 text |
| 56 | --rst-accent on --rst-surface 6.25 4.5:1 text + focus ring |
| 57 | --rst-accent on --rst-bg 6.04 4.5:1 text + focus ring |
| 58 | --rst-accent on --rst-surface-2 5.64 4.5:1 text |
| 59 | --rst-accent on --rst-accent-soft 5.52 4.5:1 text |
| 60 | --rst-on-accent on --rst-accent 6.25 4.5:1 label where the accent itself fills |
| 61 | --rst-on-accent on --rst-accent-strong 8.47 4.5:1 the same, hover |
| 62 | --rst-line-strong on --rst-surface 3.60 3:1 control border |
| 63 | --rst-line-strong on --rst-bg 3.48 3:1 control border |
| 64 | --rst-line-strong on --rst-surface-2 3.25 3:1 control border |
| 65 | pill neutral fg on its own tint 9.02 4.5:1 text |
| 66 | pill positive fg on its own tint 6.49 4.5:1 text |
| 67 | pill warning fg on its own tint 6.15 4.5:1 text |
| 68 | pill negative fg on its own tint 5.30 4.5:1 text |
| 69 | --rst-tone-positive-fg on --rst-surface 7.13 4.5:1 a stat's rising delta |
| 70 | --rst-tone-negative-fg on --rst-surface 6.47 4.5:1 a stat's falling delta |
| 71 | --rst-on-accent on negative fg (fill) 6.47 4.5:1 danger label, old tokens.css |
| 72 | --rst-on-accent-fill on --rst-accent-fill 5.94 4.5:1 primary button label |
| 73 | --rst-on-accent-fill on --rst-accent-fill-hover 7.11 4.5:1 primary button label, hover |
| 74 | --rst-on-danger-fill on --rst-danger-fill 5.46 4.5:1 danger button label |
| 75 | --rst-on-danger-fill-hover on --rst-danger-fill-hover 6.52 4.5:1 danger button label, hover |
| 76 | --rst-text on --rst-row-tint-hover 16.54 4.5:1 list-row-action text, hovered |
| 77 | --rst-text on --rst-row-tint-press 15.65 4.5:1 list-row-action text, pressed |
| 78 | --rst-control-text-hover on --rst-control-face-hover 16.67 4.5:1 plain button label, hover |
| 79 | --rst-text-muted on --rst-control-face-hover 7.26 4.5:1 muted label, hovered plain face |
| 80 | --rst-text on --rst-tint-hover 16.54 4.5:1 row text, hovered |
| 81 | --rst-text on --rst-tint-press 15.65 4.5:1 row text, pressed |
| 82 | --rst-text-muted on --rst-tint-hover 7.21 4.5:1 muted cell, hovered row |
| 83 | --rst-text-muted on --rst-tint-press 6.82 4.5:1 muted cell, pressed row |
| 84 | --rst-text-faint on --rst-tint-hover 4.92 4.5:1 faint cell, hovered row |
| 85 | --rst-text-faint on --rst-tint-press 4.66 4.5:1 faint cell, pressed row |
| 86 | --rst-accent on --rst-tint-hover 5.83 4.5:1 a link on a hovered row |
| 87 | --rst-nav-current-fg on --rst-nav-current-bg 17.72 4.5:1 the current nav item |
| 88 | --rst-track-off on --rst-surface 3.41 3:1 switch off track |
| 89 | --rst-track-off on --rst-bg 3.30 3:1 switch off track on the page |
| 90 | --rst-knob-shade on --rst-track-off 3.07 3:1 knob on the off track |
| 91 | --rst-knob-shade on --rst-accent-fill 5.34 3:1 knob on the on track |
| 92 | |
| 93 | DARK ratio requirement |
| 94 | --rst-text on --rst-surface 15.63 4.5:1 text |
| 95 | --rst-text on --rst-bg 16.64 4.5:1 text |
| 96 | --rst-text on --rst-surface-2 15.03 4.5:1 text |
| 97 | --rst-text on --rst-accent-soft 13.02 4.5:1 text |
| 98 | --rst-text-muted on --rst-surface 7.12 4.5:1 text |
| 99 | --rst-text-muted on --rst-bg 7.59 4.5:1 text |
| 100 | --rst-text-muted on --rst-surface-2 6.85 4.5:1 text |
| 101 | --rst-text-muted on --rst-accent-soft 5.94 4.5:1 text |
| 102 | --rst-text-faint on --rst-surface 5.48 4.5:1 text |
| 103 | --rst-text-faint on --rst-bg 5.84 4.5:1 text |
| 104 | --rst-text-faint on --rst-surface-2 5.27 4.5:1 text |
| 105 | --rst-text-faint on --rst-accent-soft 4.57 4.5:1 text |
| 106 | --rst-accent on --rst-surface 7.34 4.5:1 text + focus ring |
| 107 | --rst-accent on --rst-bg 7.81 4.5:1 text + focus ring |
| 108 | --rst-accent on --rst-surface-2 7.06 4.5:1 text |
| 109 | --rst-accent on --rst-accent-soft 6.11 4.5:1 text |
| 110 | --rst-on-accent on --rst-accent 7.81 4.5:1 label where the accent itself fills |
| 111 | --rst-on-accent on --rst-accent-strong 10.62 4.5:1 the same, hover |
| 112 | --rst-line-strong on --rst-surface 3.86 3:1 control border |
| 113 | --rst-line-strong on --rst-bg 4.12 3:1 control border |
| 114 | --rst-line-strong on --rst-surface-2 3.72 3:1 control border |
| 115 | pill neutral fg on its own tint 8.44 4.5:1 text |
| 116 | pill positive fg on its own tint 10.18 4.5:1 text |
| 117 | pill warning fg on its own tint 9.62 4.5:1 text |
| 118 | pill negative fg on its own tint 7.85 4.5:1 text |
| 119 | --rst-tone-positive-fg on --rst-surface 11.88 4.5:1 a stat's rising delta |
| 120 | --rst-tone-negative-fg on --rst-surface 8.77 4.5:1 a stat's falling delta |
| 121 | --rst-on-accent on negative fg (fill) 9.34 4.5:1 danger label, old tokens.css |
| 122 | --rst-on-accent-fill on --rst-accent-fill 5.26 4.5:1 primary button label |
| 123 | --rst-on-accent-fill on --rst-accent-fill-hover 6.32 4.5:1 primary button label, hover |
| 124 | --rst-on-danger-fill on --rst-danger-fill 5.46 4.5:1 danger button label |
| 125 | --rst-on-danger-fill-hover on --rst-danger-fill-hover 6.52 4.5:1 danger button label, hover |
| 126 | --rst-text on --rst-row-tint-hover 14.59 4.5:1 list-row-action text, hovered |
| 127 | --rst-text on --rst-row-tint-press 14.10 4.5:1 list-row-action text, pressed |
| 128 | --rst-control-text-hover on --rst-control-face-hover 14.43 4.5:1 plain button label, hover |
| 129 | --rst-text-muted on --rst-control-face-hover 6.58 4.5:1 muted label, hovered plain face |
| 130 | --rst-text on --rst-tint-hover 14.59 4.5:1 row text, hovered |
| 131 | --rst-text on --rst-tint-press 14.10 4.5:1 row text, pressed |
| 132 | --rst-text-muted on --rst-tint-hover 6.65 4.5:1 muted cell, hovered row |
| 133 | --rst-text-muted on --rst-tint-press 6.43 4.5:1 muted cell, pressed row |
| 134 | --rst-text-faint on --rst-tint-hover 5.12 4.5:1 faint cell, hovered row |
| 135 | --rst-text-faint on --rst-tint-press 4.95 4.5:1 faint cell, pressed row |
| 136 | --rst-accent on --rst-tint-hover 6.85 4.5:1 a link on a hovered row |
| 137 | --rst-nav-current-fg on --rst-nav-current-bg 15.63 4.5:1 the current nav item |
| 138 | --rst-track-off on --rst-surface 3.86 3:1 switch off track |
| 139 | --rst-track-off on --rst-bg 4.12 3:1 switch off track on the page |
| 140 | --rst-knob-shade on --rst-track-off 3.50 3:1 knob on the off track |
| 141 | --rst-knob-shade on --rst-accent-fill 3.90 3:1 knob on the on track |
| 142 | |
| 143 | The tightest pair is --rst-knob-shade on --rst-track-off, 3.07:1 in |
| 144 | light against a 3:1 floor. --rst-control-border is below 3:1 by |
| 145 | design; see its comment. Put a token on a surface not listed above |
| 146 | and you are guessing; recompute first. |
| 147 | |
| 148 | --rst-control-border is the one pair kept below the floor: 1.49:1 in |
| 149 | light and 1.56:1 in dark against --rst-surface. The label identifies |
| 150 | the button, so the boundary is decoration under WCAG 1.4.11 and |
| 151 | carries no 3:1 floor. Do not argue it up. |
| 152 | |
| 153 | --rst-line is a hairline divider between rows, not a control border or |
| 154 | a meaningful graphic, so 1.4.11's 3:1 does not apply to it. Control |
| 155 | borders use --rst-line-strong, which does clear 3:1. |
| 156 | |
| 157 | --rst-header-rule is not in the table either, and deliberately so |
| 158 | (design doc §6-v2.2): the page header's rule is decoration, not |
| 159 | structure — the heading's size, weight and spacing carry the |
| 160 | structure — so no contrast floor applies to it and it is not in |
| 161 | ui/contrast_test.go's pair table. Do not argue it up to 3:1. |
| 162 | |
| 163 | Pagination chips are the one deliberate exception: they are bordered |
| 164 | with --rst-line rather than --rst-line-strong, because a page strip |
| 165 | ringed in hard outlines shouts louder than the list it belongs to. |
| 166 | 1.4.11 is satisfied without that border — each chip's meaning is |
| 167 | carried by its text label at 4.5:1 or better, and the current page |
| 168 | adds a filled background, a heavier weight and aria-current on top. |
| 169 | |
| 170 | --rst-shadow-pop, --rst-shadow-knob, --rst-shadow-knob-hover, |
| 171 | --rst-shadow-lift and |
| 172 | --rst-overlay are not in the table: they paint a translucent shadow or |
| 173 | scrim over whatever sits underneath, not text over a fixed |
| 174 | background, so a contrast ratio does not apply. The scrim deepens in |
| 175 | dark, matching --rst-shadow-pop's own jump: black reads lighter |
| 176 | against a dark backdrop, so the overlay needs more alpha to still |
| 177 | read as a dimming layer. |
| 178 | ──────────────────────────────────────────────────────────────────── */ |
| 179 | |
| 180 | :root { |
| 181 | color-scheme: light dark; |
| 182 | |
| 183 | /* Font */ |
| 184 | --rst-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
| 185 | |
| 186 | /* Shape */ |
| 187 | --rst-radius: 6px; |
| 188 | --rst-radius-sm: 4px; |
| 189 | --rst-radius-pill: 999px; |
| 190 | |
| 191 | /* Surfaces and lines */ |
| 192 | --rst-bg: light-dark(#fbfbfc, #0c0d0f); |
| 193 | --rst-surface: light-dark(#ffffff, #141518); |
| 194 | --rst-surface-2: light-dark(#f3f3f5, #18191d); |
| 195 | --rst-line: light-dark(#e6e6ea, #26282e); |
| 196 | /* Light's strong line is four steps darker than the mockup's #8a8a95: |
| 197 | the sign-in card's border has to clear 3:1 against the stage art |
| 198 | drawn over the page, and #8a8a95 measured 2.90:1 there. */ |
| 199 | --rst-line-strong: light-dark(#868691, #71737c); |
| 200 | --rst-header-rule: var(--rst-line); |
| 201 | |
| 202 | /* Text */ |
| 203 | --rst-text: light-dark(#18181b, #ededf0); |
| 204 | --rst-text-muted: light-dark(#52525c, #a1a1aa); |
| 205 | --rst-text-faint: light-dark(#6b6b75, #8c8c96); |
| 206 | |
| 207 | /* Accent. One colour an app sets; the rest follow it. The link hover, |
| 208 | the tints, the halo, the plain sheen and the rim fold the accent |
| 209 | into black, white, a surface or transparent with color-mix(in |
| 210 | srgb), every share a literal percentage: calc(var()) in a |
| 211 | color-mix() percentage invalidates the whole declaration in Safari |
| 212 | (A4). The four fills and the soft ground take the accent's hue at |
| 213 | day's own lightness, because in dark the accent is a light text |
| 214 | blue and no mix of it with black or a surface reaches a deep button |
| 215 | fill. Those five are declared here as day's cobalt and derived in |
| 216 | the @supports block after this one, which an engine without |
| 217 | relative colour skips. The fallback inside each var() applies only |
| 218 | when --rst-accent is unset or set to initial: a custom property |
| 219 | accepts any tokens, so a malformed override is valid as far as |
| 220 | var() can tell and blanks every derived colour. With the default |
| 221 | accent each derived value lands within two units per channel of the |
| 222 | hex it replaced, and the contrast table above is computed from the |
| 223 | resolved values. */ |
| 224 | --rst-accent: light-dark(#1f56d6, #7aa2ff); |
| 225 | --rst-accent-strong: light-dark(color-mix(in srgb, var(--rst-accent, #1f56d6) 80%, #000000), color-mix(in srgb, var(--rst-accent, #7aa2ff) 68.5%, #ffffff)); |
| 226 | --rst-accent-soft: light-dark(#ecf1ff, #16234a); |
| 227 | --rst-on-accent: light-dark(#ffffff, #0c0d0f); |
| 228 | |
| 229 | /* Status tones */ |
| 230 | --rst-tone-neutral-fg: light-dark(#374151, #c3c9d1); |
| 231 | --rst-tone-neutral-bg: light-dark(#eef0f2, #262c34); |
| 232 | --rst-tone-positive-fg: light-dark(#166534, #6ee7a8); |
| 233 | --rst-tone-positive-bg: light-dark(#dcfce7, #10281c); |
| 234 | --rst-tone-warning-fg: light-dark(#854d0e, #f0c46a); |
| 235 | --rst-tone-warning-bg: light-dark(#fef3c7, #2c2110); |
| 236 | --rst-tone-negative-fg: light-dark(#b91c1c, #f79aa0); |
| 237 | --rst-tone-negative-bg: light-dark(#fee2e2, #331819); |
| 238 | |
| 239 | /* Depth */ |
| 240 | --rst-shadow-pop: 0 2px 4px light-dark(rgba(16, 24, 40, 0.06), rgba(0, 0, 0, 0.35)), 0 8px 16px -4px light-dark(rgba(16, 24, 40, 0.1), rgba(0, 0, 0, 0.5)); |
| 241 | /* The knob's dome: a soft drop and a hairline ring, and hovered a |
| 242 | longer drop, so the knob lifts as a button does. */ |
| 243 | --rst-shadow-knob: 0 1px 2px light-dark(rgba(16, 24, 40, 0.22), rgba(0, 0, 0, 0.5)), 0 0 0 0.5px light-dark(rgba(16, 24, 40, 0.08), rgba(0, 0, 0, 0.3)); |
| 244 | --rst-shadow-knob-hover: 0 1.5px 3px light-dark(rgba(16, 24, 40, 0.26), rgba(0, 0, 0, 0.58)), 0 0 0 0.5px light-dark(rgba(16, 24, 40, 0.08), rgba(0, 0, 0, 0.3)); |
| 245 | --rst-shadow-lift: 0 1px 2px light-dark(rgba(16, 24, 40, 0.08), rgba(0, 0, 0, 0.3)); |
| 246 | --rst-overlay: light-dark(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.6)); |
| 247 | |
| 248 | /* Filled controls have a fill, a hover fill and an edge of their own, |
| 249 | so a hover is never a filter and a dark label is never put on a |
| 250 | light accent. */ |
| 251 | --rst-accent-fill: light-dark(#2257df, #2f61e8); |
| 252 | --rst-accent-fill-hover: light-dark(#1d4dc8, #2956d0); |
| 253 | --rst-accent-fill-edge: light-dark(#153ca0, #1f43a6); |
| 254 | --rst-accent-fill-edge-hover: light-dark(#12358f, #1a3b95); |
| 255 | --rst-on-accent-fill: #ffffff; |
| 256 | --rst-danger-fill: light-dark(#c62f2f, #c43232); |
| 257 | --rst-danger-fill-hover: light-dark(#b12929, #af2c2c); |
| 258 | --rst-danger-fill-edge: light-dark(#8d1f1f, #8c2121); |
| 259 | --rst-danger-fill-edge-hover: light-dark(#7e1a1a, #7d1d1d); |
| 260 | --rst-on-danger-fill: #ffffff; |
| 261 | --rst-on-danger-fill-hover: #ffffff; |
| 262 | --rst-danger-hover-filter: none; |
| 263 | |
| 264 | /* Plain buttons and menu summaries: the face at rest, on hover and, |
| 265 | for a ghost button, pressed; the border, which is softer than |
| 266 | --rst-line-strong on purpose; and the hover border and label. The |
| 267 | label identifies the button, so the boundary is decoration under |
| 268 | WCAG 1.4.11 and carries no 3:1 floor. Do not argue it up. */ |
| 269 | --rst-control-face: light-dark(#ffffff, #141518); |
| 270 | --rst-control-face-hover: light-dark(#f7f8fa, #1b1d22); |
| 271 | --rst-control-face-press: light-dark(#e6e9ee, #202329); |
| 272 | --rst-control-border: light-dark(#d0d4dc, #343843); |
| 273 | --rst-control-border-hover: light-dark(#8a8a95, #71737c); |
| 274 | --rst-control-text-hover: light-dark(#18181b, #ededf0); |
| 275 | --rst-ghost-edge: transparent; |
| 276 | --rst-ghost-face-hover: light-dark(#f3f3f5, #18191d); |
| 277 | --rst-ghost-edge-hover: transparent; |
| 278 | --rst-summary-face: light-dark(#ffffff, #141518); |
| 279 | --rst-summary-face-hover: light-dark(#f7f8fa, #1b1d22); |
| 280 | --rst-summary-edge: light-dark(#d0d4dc, #343843); |
| 281 | --rst-summary-edge-hover: light-dark(#8a8a95, #71737c); |
| 282 | /* A menu summary's corner: the small radius, as a token of its own, |
| 283 | because the focus ring follows the corner and a flat theme keeps |
| 284 | the square ring it had. */ |
| 285 | --rst-summary-radius: var(--rst-radius-sm); |
| 286 | |
| 287 | /* The light from above. A highlight along a raised edge, a short |
| 288 | contact shadow where an object meets the page, soft ambient light |
| 289 | around it, the fraction a filled face shades by, and how far a |
| 290 | pressed button drops. Set every one to transparent, 0 and 0px for |
| 291 | a flat theme. */ |
| 292 | --rst-depth-hl: light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.06)); |
| 293 | --rst-depth-contact: light-dark(rgba(16, 24, 40, 0.1), rgba(0, 0, 0, 0.5)); |
| 294 | --rst-depth-ambient: light-dark(rgba(16, 24, 40, 0.06), rgba(0, 0, 0, 0.35)); |
| 295 | --rst-depth-face: 0.04; |
| 296 | --rst-press-drop: 1px; |
| 297 | |
| 298 | /* The switch: a near-white knob in both schemes, so in dark it reads |
| 299 | as the thing you move rather than a hole; and the off track, a grey |
| 300 | dark enough to clear 3:1 against the surface and light enough for the |
| 301 | knob to clear 3:1 against it. */ |
| 302 | --rst-knob: #ffffff; |
| 303 | --rst-knob-shade: light-dark(#f1f3f6, #d9dee8); |
| 304 | --rst-track-off: light-dark(#8a8a95, #71737c); |
| 305 | /* The switch's size: a 40x23 track with a 19px knob under a fine |
| 306 | pointer, 51x31 with a 27px knob on a touch screen (the iOS size). |
| 307 | The knob is inset 2px, so each height is its knob and 4. */ |
| 308 | --rst-switch-w: 40px; |
| 309 | --rst-switch-h: 23px; |
| 310 | --rst-switch-knob: 19px; |
| 311 | --rst-switch-touch-w: 51px; |
| 312 | --rst-switch-touch-h: 31px; |
| 313 | --rst-switch-touch-knob: 27px; |
| 314 | |
| 315 | /* Rows, menu items and sidebar items on hover and pressed: the accent |
| 316 | folded into the surface at 4.5% and 8.5% in light, and in dark into |
| 317 | the page at 10% and into black at 19%, the shares that land on the |
| 318 | hexes the design was measured with. Focus adds the accent as the |
| 319 | edge and the accent at 22% as the halo. */ |
| 320 | --rst-tint-hover: light-dark(color-mix(in srgb, var(--rst-accent, #1f56d6) 4.5%, var(--rst-surface)), color-mix(in srgb, var(--rst-accent, #7aa2ff) 10%, var(--rst-bg))); |
| 321 | --rst-tint-press: light-dark(color-mix(in srgb, var(--rst-accent, #1f56d6) 8.5%, var(--rst-surface)), color-mix(in srgb, var(--rst-accent, #7aa2ff) 19%, #000000)); |
| 322 | --rst-tint-tap: var(--rst-tint-press); |
| 323 | /* The topbar's phone card, hovered: the hover tint again, as a token |
| 324 | of its own, because the card had no hover before and a flat theme |
| 325 | keeps none. */ |
| 326 | --rst-tint-card-hover: var(--rst-tint-hover); |
| 327 | /* A media tile, hovered and pressed: the hover and press tints |
| 328 | again, as a pair of its own, because the tile had no hover before |
| 329 | and a flat theme keeps its surface in both states. */ |
| 330 | --rst-tint-tile-hover: var(--rst-tint-hover); |
| 331 | --rst-tint-tile-press: var(--rst-tint-press); |
| 332 | /* A menu group's summary, hovered: the hover tint again, as a token |
| 333 | of its own, because the summary had no hover before and a flat |
| 334 | theme keeps none. */ |
| 335 | --rst-tint-group-hover: var(--rst-tint-hover); |
| 336 | --rst-row-tint-hover: var(--rst-tint-hover); |
| 337 | --rst-row-tint-press: var(--rst-tint-press); |
| 338 | --rst-focus-edge: var(--rst-accent, light-dark(#1f56d6, #7aa2ff)); |
| 339 | --rst-focus-halo: color-mix(in srgb, var(--rst-accent, light-dark(#1f56d6, #7aa2ff)) 22%, transparent); |
| 340 | |
| 341 | /* The light's colours at full strength: the sheen on a filled button, |
| 342 | the sheen on a plain one (an accent tint in light, because light |
| 343 | cannot brighten white), the rim a card catches, and the wash a dark |
| 344 | card lifts by. The sheen caps are measured: white on the light |
| 345 | danger fill, under the face shade, clears 4.5:1 at 0.08 and fails |
| 346 | at 0.10. The plain sheen is the fill at 9%. The rim is the accent |
| 347 | folded into the line in light and into a mid grey in dark, where no |
| 348 | neutral token sits at that value. */ |
| 349 | --rst-sheen-fill: light-dark(rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.04)); |
| 350 | --rst-sheen-plain: light-dark(color-mix(in srgb, var(--rst-accent-fill) 9%, transparent), rgba(255, 255, 255, 0.04)); |
| 351 | --rst-light-rim: light-dark(color-mix(in srgb, var(--rst-accent, #1f56d6) 23%, var(--rst-line)), color-mix(in srgb, var(--rst-accent, #7aa2ff) 31%, #505050)); |
| 352 | --rst-light-wash: light-dark(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.015)); |
| 353 | |
| 354 | /* The press: a whole-face wash and a spot under the pointer, as |
| 355 | fractions of black, filled and plain. Numbers, because light-dark() |
| 356 | takes colours only; the dark values are restated in the two blocks |
| 357 | at the end of the file. */ |
| 358 | --rst-press-wash: 0.08; |
| 359 | --rst-press-spot: 0.16; |
| 360 | --rst-press-wash-plain: 0.04; |
| 361 | --rst-press-spot-plain: 0.07; |
| 362 | |
| 363 | /* The type and rail choices this theme makes: a smaller page title, |
| 364 | a smaller box heading, stat labels in sentence case, the rail on the |
| 365 | page background, and the current nav item as a surface thumb. */ |
| 366 | --rst-title-size: 1.25rem; |
| 367 | --rst-title-weight: 600; |
| 368 | --rst-title-track: -0.012em; |
| 369 | --rst-heading-size: 0.8125rem; |
| 370 | --rst-label-case: none; |
| 371 | --rst-label-track: 0; |
| 372 | --rst-rail-bg: var(--rst-bg); |
| 373 | --rst-nav-current-bg: light-dark(#ffffff, #141518); |
| 374 | --rst-nav-current-fg: light-dark(#18181b, #ededf0); |
| 375 | } |
| 376 | |
| 377 | /* The five hue-derived tokens, redeclared where the engine has relative |
| 378 | colour syntax. oklch(from) needs Chrome 119, Safari 16.4 and Firefox |
| 379 | 128, and Firefox 128 is above the floor (Chrome 123, Safari 17.5, |
| 380 | Firefox 121). Without this gate an older engine would resolve the |
| 381 | fill to nothing at the use site and paint a white label on a white |
| 382 | button; below it the :root values hold, day's cobalt, which an accent |
| 383 | override does not reach. The lightness is day's own. The chroma is |
| 384 | the accent's, scaled to day's and capped, so a grey accent gives a |
| 385 | grey fill rather than a vivid one at a powerless hue. The fill and |
| 386 | the hover fill lower their lightness around hue 170, yellow through |
| 387 | green to cyan: a colour of that lightness clipped to sRGB gains |
| 388 | luminance, and the label sits not on the fill but on the fill under |
| 389 | the face shade's 4% white and, hovered, under the sheen's white too, |
| 390 | so cyan's dark hover, 4.74:1 on the bare fill, was 4.15:1 on the |
| 391 | painted face (Astra A1). The drop is a cosine bump that is zero |
| 392 | beyond 85 degrees from 170, so the default cobalt, at hue 263, is |
| 393 | outside it and lands where it did; its depth is 0.05 and 0.045 in |
| 394 | light and 0.1 and 0.05 in dark, measured against the brightest pixel |
| 395 | of the photographed button for twelve hues, three greys and the |
| 396 | sRGB edges in TestAnyAccentKeepsTheFillLabelReadable. Every number |
| 397 | is a literal, and the only arithmetic is inside a channel, where |
| 398 | every engine with relative colour takes calc(), min(), max() and |
| 399 | cos(). */ |
| 400 | @supports (color: oklch(from red l c h)) { |
| 401 | :root { |
| 402 | --rst-accent-soft: light-dark(oklch(from var(--rst-accent, #1f56d6) 0.958 min(calc(c * 0.0878), 0.018) h), oklch(from var(--rst-accent, #7aa2ff) 0.268 min(calc(c * 0.5039), 0.072) h)); |
| 403 | --rst-accent-fill: light-dark(oklch(from var(--rst-accent, #1f56d6) calc(0.513 - 0.05 * max(0, cos((h - 170) * 1deg) - 0.087)) min(calc(c * 1.0244), 0.21) h), oklch(from var(--rst-accent, #7aa2ff) calc(0.543 - 0.1 * max(0, cos((h - 170) * 1deg) - 0.087)) min(calc(c * 1.5045), 0.215) h)); |
| 404 | --rst-accent-fill-hover: light-dark(oklch(from var(--rst-accent, #1f56d6) calc(0.472 - 0.045 * max(0, cos((h - 170) * 1deg) - 0.087)) min(calc(c * 0.9463), 0.194) h), oklch(from var(--rst-accent, #7aa2ff) calc(0.5 - 0.05 * max(0, cos((h - 170) * 1deg) - 0.087)) min(calc(c * 1.3786), 0.197) h)); |
| 405 | --rst-accent-fill-edge: light-dark(oklch(from var(--rst-accent, #1f56d6) 0.401 min(calc(c * 0.8049), 0.165) h), oklch(from var(--rst-accent, #7aa2ff) 0.422 min(calc(c * 1.1617), 0.166) h)); |
| 406 | --rst-accent-fill-edge-hover: light-dark(oklch(from var(--rst-accent, #1f56d6) 0.37 min(calc(c * 0.7366), 0.151) h), oklch(from var(--rst-accent, #7aa2ff) 0.389 min(calc(c * 1.0847), 0.155) h)); |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | /* The whole toggle. Setting color-scheme re-resolves every light-dark() |
| 411 | above, so an explicit choice beats the OS in both directions without |
| 412 | restating a colour. */ |
| 413 | :root[data-theme="light"] { color-scheme: light; } |
| 414 | :root[data-theme="dark"] { color-scheme: dark; } |
| 415 | |
| 416 | /* The dark press strengths. Two blocks, because a reader who chose Dark |
| 417 | has data-theme and a reader on System on a dark OS has only the media |
| 418 | query; one block alone would hand the other reader the light numbers. |
| 419 | Numbers only: every colour above is a light-dark() pair and stays |
| 420 | declared once. */ |
| 421 | :root[data-theme="dark"] { --rst-press-wash: 0.18; --rst-press-spot: 0.30; --rst-press-wash-plain: 0.18; --rst-press-spot-plain: 0.30; } |
| 422 | @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --rst-press-wash: 0.18; --rst-press-spot: 0.30; --rst-press-wash-plain: 0.18; --rst-press-spot-plain: 0.30; } } |
| 423 | |