oxedyne/daimond/www/css/variables.css
23.8 KiB, 1 run
created by r2519314175:1141, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | :root { |
| 2 | /* Dark theme (default) — warm palette from doc/red mockup. */ |
| 3 | --bg-primary: #15120F; |
| 4 | --bg-secondary: #1E1A16; |
| 5 | --bg-tertiary: #241F1A; |
| 6 | --bg-hover: #2B251E; |
| 7 | --border: #332C25; |
| 8 | --border-2: #3F372E; |
| 9 | /* The boundary of a control, where the boundary is the ONLY cue. |
| 10 | `--border` is a divider: it separates two things that are both visible |
| 11 | without it, and it is drawn quietly on purpose. But every text field in |
| 12 | this app is `background: var(--bg-tertiary); border: 1px solid |
| 13 | var(--border)`, and the fill inside it sits 1.06-1.24 from the panel |
| 14 | outside it -- so on a field that line is not a divider, it is the entire |
| 15 | edge of the control, and it measured 1.02-1.26 against the surfaces it |
| 16 | was supposed to separate, on all eleven palettes. WCAG 2.2 SC 1.4.11 |
| 17 | asks 3.0 of it. |
| 18 | Lifting `--border` itself would have given every panel in the app a hard |
| 19 | rule and changed the whole look, so this is a second token, used only |
| 20 | where nothing else says where the control is. Every value clears 3.01-3.08 |
| 21 | against every surface of its own palette; `dev/verify_theme.mjs` holds |
| 22 | them there. */ |
| 23 | --border-strong: #756F69; |
| 24 | --text-primary: #ECE6DC; |
| 25 | --text-secondary: #A79E92; |
| 26 | /* The muted rung, matched across the three palettes. At #736A5F this cleared |
| 27 | only 3.1:1 on the tertiary surface -- fine by the audit's floor, but once |
| 28 | the light and candy palettes were lifted to ~4.5 the dark theme became the |
| 29 | hardest of the three to read, which is the incoherence the other way. |
| 30 | #877D70 then measured 4.04 on `--bg-tertiary` and 4.28 on `--bg-secondary`. |
| 31 | That cleared the audit's own 4.0 for this rung, but the rung is drawn at |
| 32 | 11-12px -- `.dview-btn`, `.pop-note`, `.cfg-fieldnote`, `.sys-note` -- and |
| 33 | 11px is small text, which WCAG 2.2 SC 1.4.3 asks 4.5 of. This was the |
| 34 | worst of the eleven and is now the last to clear it: 5.16/4.78/4.52 on the |
| 35 | three stable surfaces, 4.19 on hover. */ |
| 36 | --text-muted: #908577; |
| 37 | --accent: #D2604F; |
| 38 | --accent-hover: #E07A67; |
| 39 | --accent-soft: #3A211B; |
| 40 | --accent-text: #E9A392; |
| 41 | --ok: #66B98C; |
| 42 | --ok-bg: #1C3227; |
| 43 | --warn: #D6A24A; |
| 44 | --warn-bg: #33260F; |
| 45 | --danger: #E07A67; |
| 46 | --success: #66B98C; |
| 47 | /* The chip a tag filter is being held by. One neutral pair per INK -- not per |
| 48 | palette. A tag's hue already says which tag it is, so the state saying |
| 49 | "this one is working" must not also vary by tag; and it must not vary by |
| 50 | palette either, or eleven of them become eleven different states. But it |
| 51 | cannot be a single pair across both inks: a mid grey reads 6.17 against a |
| 52 | paper ground and 1.13 against a near-black one, where the only thing left |
| 53 | distinguishing a filtered tag was that its hue had gone -- and hue alone is |
| 54 | not a signal. The ink axis is the smallest thing it can key on that is not |
| 55 | a palette's name. */ |
| 56 | --tag-on-bg: #4A4A4A; |
| 57 | --tag-on-hover: #5C5C5C; |
| 58 | --tag-on-fg: #FFFFFF; |
| 59 | |
| 60 | /* What letters a FILLED control -- a button whose ground is --accent, --ok |
| 61 | or --danger. White is right on a light palette, where those fills are deep |
| 62 | reds and greens. On a dark palette they are light mints and salmons, and |
| 63 | white on them measured 2.15-3.46: a label close to invisible on the one |
| 64 | control the eye is meant to go to. The rule is the same one the rest of the |
| 65 | app follows -- dark lettering on a light ground -- so a dark palette's |
| 66 | filled button letters in its own darkest surface. */ |
| 67 | --on-fill: #FFFFFF; |
| 68 | --radius: 8px; |
| 69 | --radius-sm: 6px; |
| 70 | --radius-lg: 12px; |
| 71 | --font: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, var(--font-cjk, sans-serif), sans-serif; |
| 72 | --font-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace; |
| 73 | |
| 74 | /* ── The safe area ─────────────────────────────────────────────── |
| 75 | What the hardware has already taken: the notch or the status bar at the |
| 76 | top, the home indicator at the foot, and in landscape a notch down one |
| 77 | side. In a browser tab these are all zero, because the browser's own |
| 78 | chrome is standing in that space. Installed on a home screen there is no |
| 79 | chrome, the page owns the whole screen, and anything laid out to the |
| 80 | viewport's edge lands under the hardware. |
| 81 | |
| 82 | Named once here rather than written as `env(...)` at each use, for two |
| 83 | reasons: a stylesheet that says `var(--safe-t)` reads as a decision |
| 84 | rather than a browser quirk, and one that says it can be TESTED -- |
| 85 | Chrome reports zero insets on every desktop, so dev/verify_pwa.mjs sets |
| 86 | these four to an iPhone's real numbers and looks at what moved. */ |
| 87 | --safe-t: env(safe-area-inset-top, 0px); |
| 88 | --safe-r: env(safe-area-inset-right, 0px); |
| 89 | --safe-b: env(safe-area-inset-bottom, 0px); |
| 90 | --safe-l: env(safe-area-inset-left, 0px); |
| 91 | |
| 92 | /* ── The type scale ────────────────────────────────────────────── |
| 93 | Every font size in the app comes from here, so that text can be made |
| 94 | larger without the frame growing with it: the user's choice moves |
| 95 | --fs-scale, and only the type responds. Padding, icons and panel |
| 96 | widths are deliberately left alone -- this is a reading control, not |
| 97 | a zoom. |
| 98 | |
| 99 | The steps are anchored on --fs-base, which is the body size, and the |
| 100 | px values are exactly what the stylesheets used before the scale |
| 101 | existed, so at scale 1 nothing moves by a pixel. */ |
| 102 | --fs-scale: 1; |
| 103 | --fs-4xs: calc(9px * var(--fs-scale)); |
| 104 | --fs-3xs: calc(10px * var(--fs-scale)); |
| 105 | --fs-2xs: calc(11px * var(--fs-scale)); /* section headers, chips */ |
| 106 | --fs-xs: calc(12px * var(--fs-scale)); /* meta lines, the commonest */ |
| 107 | --fs-sm: calc(13px * var(--fs-scale)); /* secondary prose, controls */ |
| 108 | --fs-base: calc(14px * var(--fs-scale)); /* body */ |
| 109 | |
| 110 | /* Half steps. The dense monospace furniture -- tile meters, model chips, |
| 111 | artefact kinds, the Web panel's mode pill, the mail rows -- was drawn on |
| 112 | a rung between these sizes, consistently enough that it is plainly a |
| 113 | choice rather than an accident. Rounding them away moved the pill and |
| 114 | shifted its neighbours, so the rungs are kept. */ |
| 115 | --fs-3xs-2xs: calc(10.5px * var(--fs-scale)); |
| 116 | --fs-2xs-xs: calc(11.5px * var(--fs-scale)); |
| 117 | --fs-xs-sm: calc(12.5px * var(--fs-scale)); |
| 118 | --fs-sm-base: calc(13.5px * var(--fs-scale)); |
| 119 | --fs-lg: calc(15px * var(--fs-scale)); |
| 120 | --fs-xl: calc(16px * var(--fs-scale)); |
| 121 | --fs-xl-2xl: calc(17px * var(--fs-scale)); |
| 122 | --fs-2xl: calc(18px * var(--fs-scale)); |
| 123 | --fs-3xl: calc(20px * var(--fs-scale)); |
| 124 | --fs-4xl: calc(22px * var(--fs-scale)); |
| 125 | --fs-5xl: calc(26px * var(--fs-scale)); |
| 126 | --fs-6xl: calc(34px * var(--fs-scale)); |
| 127 | |
| 128 | --header-h: 44px; |
| 129 | --logo-h: 24px; |
| 130 | /* ── The attachment footer's cap, and why it is six ────────────── |
| 131 | ATTACH_CONTRACT.md §5 asks for "a cap on the stack, after which the |
| 132 | footer scrolls, visibly", because it "currently grows without limit and |
| 133 | pushes the composer up the screen". |
| 134 | |
| 135 | The number is measured, not chosen for being round. One tile is 32px |
| 136 | tall at `--fs-scale: 1` -- the tallest thing on it is the Note/Read pill |
| 137 | at 23px, plus the row's 8px of padding and its rule -- which is what |
| 138 | `--attach-row-h` states, and what `.arte-row` is held to as a minimum. |
| 139 | The chat panel at a 1500x950 window is 884px; an empty composer takes |
| 140 | 76px, leaving the conversation 762px. Six tiles is 192px: a quarter of |
| 141 | the reading area, leaving 570px of conversation, which is still most of |
| 142 | the panel. Seven crosses a third of it, and the panel is for the |
| 143 | conversation. |
| 144 | |
| 145 | Six is also, to a fraction of a tile, the accidental cap that was |
| 146 | already here: `max-height: 22vh` came to 209px at that height, or 6.4 |
| 147 | tiles. That cap was invisible, cut the seventh tile at an arbitrary |
| 148 | place, and moved with the window instead of with the tiles. This one is |
| 149 | stated in the unit a person counts in. |
| 150 | |
| 151 | Measured without any cap: at 22 attachments the footer stands 713px and |
| 152 | the conversation above it is 43px, one line. It is the reading area the |
| 153 | stack eats, the panel being a column anchored at the bottom. |
| 154 | |
| 155 | `--attach-peek` leaves the seventh tile showing by 14px. A stack cut |
| 156 | mid-tile is the plainest statement that there is more below, and it is |
| 157 | there as well as the scrollbar, not instead of it. */ |
| 158 | --attach-row-h: calc(23px * var(--fs-scale) + 9px); |
| 159 | --attach-cap: 6; |
| 160 | --attach-peek: 14px; |
| 161 | /* One icon cell, sized so two rows of them come to the same 192px that six |
| 162 | tiles do: one cap, one footer height, whichever view is showing. */ |
| 163 | --attach-icon-h: 84px; |
| 164 | --attach-icon-w: 88px; |
| 165 | /* Default panel widths (user-pullable, persisted). */ |
| 166 | --rail-w: 220px; |
| 167 | --agents-w: 240px; |
| 168 | --work-w: 260px; |
| 169 | /* The phone's bottom bar, WITHOUT the home indicator's inset, which the bar |
| 170 | adds to its own padding. It was 54px here and 58px in fact, and nothing |
| 171 | read it: css/mobile.css and js/mobile.js each carried their own copy of the |
| 172 | real number, and the sheet's foot is placed from one and its height from the |
| 173 | other. Both read this now, and mobile.js measures the bar rather than |
| 174 | trusting any of the three. 62 is a 44px chip -- the AAA target the row of |
| 175 | destinations before it met -- plus the strip's own 8 and 9 and the bar's |
| 176 | 1px rule. */ |
| 177 | --mnav-h: 62px; |
| 178 | } |
| 179 | |
| 180 | /* Lollypop — an inviting candy theme. A soft multicolour glow (pink, lavender, mint) washes the |
| 181 | frame, while the panels stay white so text stays crisp and readable; the accent is a raspberry |
| 182 | sweet, and the state colours are deliberately varied (mint, amber, cherry) so the whole thing |
| 183 | reads as colourful without ever fighting the words. The gradient itself is applied to the body |
| 184 | in app.css, where the background shorthand lives. */ |
| 185 | :root[data-theme="lollypop"] { |
| 186 | --bg-primary: #FDEFF7; |
| 187 | --bg-secondary: #FFFFFF; |
| 188 | --bg-tertiary: #FBE6F2; |
| 189 | --bg-hover: #F7D8EC; |
| 190 | --border: #F3D3E6; |
| 191 | --border-2: #E7B4D3; |
| 192 | --border-strong: #967589; |
| 193 | --text-primary: #3A1E3E; |
| 194 | --text-secondary: #7C5578; |
| 195 | /* Same reasoning as the light theme's muted rung: #B08AAA read at 2.5-3.0:1 |
| 196 | on the candy surfaces. #8A5E85 kept the mauve and cleared 4.40 on |
| 197 | `--bg-tertiary`, which is under the 4.5 that 11px text asks for; two units |
| 198 | darker clears it at 4.53 and does not visibly change the hue. */ |
| 199 | --text-muted: #885C83; |
| 200 | --accent: #C42B7C; |
| 201 | --accent-hover: #D13988; |
| 202 | --accent-soft: #FBDDEF; |
| 203 | --accent-text: #AE1E6E; |
| 204 | /* Dark enough to read AS TEXT on the candy surfaces (4.5:1 on tertiary); |
| 205 | the brighter #12B26E sat at 2.3:1, fine for a dot but not for words. */ |
| 206 | --ok: #0C7A4C; |
| 207 | --ok-bg: #DAF6EA; |
| 208 | --warn: #9A5C04; |
| 209 | --warn-bg: #FCEDD1; |
| 210 | --danger: #C62B48; |
| 211 | --success: #0C7A4C; |
| 212 | } |
| 213 | |
| 214 | /* ── Skin axis ─────────────────────────────────────────────────────── |
| 215 | The skin is ORTHOGONAL to the theme (palette). The theme decides the |
| 216 | colours; the skin decides the shape -- corners, typeface, spacing and |
| 217 | how loud the furniture is. "sharp" (no data-skin, or data-skin="sharp") |
| 218 | is the original precise, dense, developer-facing look. "warm" is an |
| 219 | approachable skin in the manner of Google or Canva: rounder, a friendly |
| 220 | rounded typeface, more room to breathe, and the dense telemetry calmed. |
| 221 | |
| 222 | Only the shape tokens live here; the density and the calmer chrome are in |
| 223 | skin-warm.css, all of it scoped to :root[data-skin="warm"], so the sharp |
| 224 | skin is not touched by a single rule. Type SIZE is left to the reading |
| 225 | control (--fs-scale is the user's, set inline) and is not moved here. */ |
| 226 | :root[data-skin="warm"] { |
| 227 | --radius: 14px; |
| 228 | --radius-sm: 10px; |
| 229 | --radius-lg: 22px; |
| 230 | /* A rounded, friendly sans. Nunito (self-hosted, OFL) leads; ui-rounded |
| 231 | gives Apple platforms SF Pro Rounded even before the webfont lands, and |
| 232 | the rest fall back to the cleanest available system sans. */ |
| 233 | --font: 'Nunito', ui-rounded, 'Segoe UI Variable Display', 'Segoe UI', system-ui, -apple-system, Roboto, var(--font-cjk, sans-serif), sans-serif; |
| 234 | /* How much the warm skin loosens spacing. skin-warm.css multiplies its |
| 235 | paddings and gaps by this, so the whole skin can breathe more or less |
| 236 | from one number. 1 would match the sharp skin; 1.4 is a clear loosening. */ |
| 237 | --space-scale: 1.4; |
| 238 | /* Soft, diffuse shadows replace some of the hard 1px borders, the way an |
| 239 | approachable UI lifts a card off the page rather than ruling a line. */ |
| 240 | --shadow-1: 0 1px 2px rgba(17, 12, 8, 0.04), 0 3px 10px rgba(17, 12, 8, 0.06); |
| 241 | --shadow-2: 0 2px 6px rgba(17, 12, 8, 0.06), 0 12px 30px rgba(17, 12, 8, 0.10); |
| 242 | } |
| 243 | |
| 244 | /* Light theme */ |
| 245 | :root[data-theme="light"] { |
| 246 | --bg-primary: #FBF9F5; |
| 247 | --bg-secondary: #FFFFFF; |
| 248 | --bg-tertiary: #F4F1EA; |
| 249 | --bg-hover: #EDE9E0; |
| 250 | --border: #E7E1D6; |
| 251 | --border-2: #D6CEBF; |
| 252 | --border-strong: #89847A; |
| 253 | --text-primary: #211C18; |
| 254 | --text-secondary: #6B635A; |
| 255 | /* Muted is the quietest text in the app, not the least readable: it carries |
| 256 | whole explanatory paragraphs (the Prompts note), the section headers and |
| 257 | the spend labels. At #9A9187 those sat at 2.75-3.1:1 on these surfaces -- |
| 258 | below every legibility floor. #787068 lifted them to 4.31-4.87, which is |
| 259 | what the note here used to record as "clears 4.3:1 everywhere"; but the |
| 260 | figure it clears has to be 4.5, because every one of those paragraphs is |
| 261 | set at 11-12px and that is small text. Three units darker per channel |
| 262 | reaches 4.83/5.08/4.51 on the stable surfaces and 4.20 on hover, and still |
| 263 | reads a rung lighter than --text-secondary (1.16 apart, where Mist has |
| 264 | always shipped at 1.13). */ |
| 265 | --text-muted: #756D65; |
| 266 | --accent: #A6322A; |
| 267 | --accent-hover: #C24A38; |
| 268 | --accent-soft: #F6E7E2; |
| 269 | --accent-text: #7E2A20; |
| 270 | --ok: #2F7A54; |
| 271 | --ok-bg: #E4F1E8; |
| 272 | --warn: #9A6410; |
| 273 | --warn-bg: #F7ECD6; |
| 274 | --danger: #A33227; |
| 275 | --success: #2F7A54; |
| 276 | } |
| 277 | |
| 278 | /* ── The rest of the palettes ───────────────────────────────────────── |
| 279 | Each one sets the same tokens and nothing else: no palette may introduce a |
| 280 | rule, because a rule cannot be chosen from a list. The three above (dark, |
| 281 | lollypop, light) came first and keep their ids; these seven fill out the |
| 282 | three bands the picker offers. |
| 283 | |
| 284 | Every one is checked by dev/verify_theme.mjs, which computes the real WCAG |
| 285 | ratio for each text rung on each surface. The floors are the ones the first |
| 286 | three were tuned to: 4.5 for primary and secondary, and 4.0 for the muted |
| 287 | rung, which carries whole explanatory paragraphs and is the one that was |
| 288 | found sitting at 2.75 when nobody was measuring. */ |
| 289 | |
| 290 | /* Mist -- a cool light palette: paper with a blue-grey cast, and a slate |
| 291 | accent. The quietest of the light band. */ |
| 292 | :root[data-theme="mist"] { |
| 293 | --bg-primary: #F7F9FB; |
| 294 | --bg-secondary: #FFFFFF; |
| 295 | --bg-tertiary: #EDF1F5; |
| 296 | --bg-hover: #E2E8EF; |
| 297 | --border: #DCE3EA; |
| 298 | --border-2: #C3CDD8; |
| 299 | --border-strong: #7F858C; |
| 300 | --text-primary: #17202A; |
| 301 | --text-secondary: #56636F; |
| 302 | --text-muted: #5F6B77; |
| 303 | --accent: #26648F; |
| 304 | --accent-hover: #3A87BA; |
| 305 | --accent-soft: #E1EDF6; |
| 306 | --accent-text: #1F5478; |
| 307 | --ok: #2C7A54; |
| 308 | --ok-bg: #DFF1E7; |
| 309 | --warn: #8A5B0C; |
| 310 | --warn-bg: #F8ECD4; |
| 311 | --danger: #A9302C; |
| 312 | --success: #2C7A54; |
| 313 | } |
| 314 | |
| 315 | /* Linen -- warm light: unbleached paper, ink brown, a muted terracotta accent. |
| 316 | The counterpart to Mist at the other end of the light band. */ |
| 317 | :root[data-theme="linen"] { |
| 318 | --bg-primary: #FAF6EF; |
| 319 | --bg-secondary: #FFFDF9; |
| 320 | --bg-tertiary: #F2EBDF; |
| 321 | --bg-hover: #E8DFCF; |
| 322 | --border: #E3DACB; |
| 323 | --border-2: #CDC0AB; |
| 324 | --border-strong: #857D6F; |
| 325 | --text-primary: #241E16; |
| 326 | --text-secondary: #635849; |
| 327 | --text-muted: #726655; |
| 328 | --accent: #9C4A22; |
| 329 | --accent-hover: #B85E32; |
| 330 | --accent-soft: #F5E5DA; |
| 331 | --accent-text: #7A3818; |
| 332 | --ok: #33704A; |
| 333 | --ok-bg: #E3EFE4; |
| 334 | --warn: #8A5B0C; |
| 335 | --warn-bg: #F6EBD3; |
| 336 | --danger: #A03A2A; |
| 337 | --success: #33704A; |
| 338 | } |
| 339 | |
| 340 | /* Sage -- intermediate, dark ink: a green-grey study, low chroma, restful for |
| 341 | long reading without going pale. */ |
| 342 | :root[data-theme="sage"] { |
| 343 | --bg-primary: #EAEEE7; |
| 344 | --bg-secondary: #F5F7F3; |
| 345 | --bg-tertiary: #DFE5DA; |
| 346 | --bg-hover: #D2DACB; |
| 347 | --border: #CFD8C8; |
| 348 | --border-2: #B4C0AA; |
| 349 | --border-strong: #737B6D; |
| 350 | --text-primary: #1B2118; |
| 351 | --text-secondary: #4E5A48; |
| 352 | --text-muted: #5C6855; |
| 353 | --accent: #3F6B4A; |
| 354 | --accent-hover: #4F8A5E; |
| 355 | --accent-soft: #DCEADF; |
| 356 | --accent-text: #2F5439; |
| 357 | --ok: #2F6B48; |
| 358 | --ok-bg: #DAEBDF; |
| 359 | --warn: #7E570D; |
| 360 | --warn-bg: #F0E6CC; |
| 361 | --danger: #99332B; |
| 362 | --success: #2F6B48; |
| 363 | } |
| 364 | |
| 365 | /* Dusk -- intermediate, LIGHT ink: a blue slate that is neither paper nor |
| 366 | night. This is the palette the tone/ink split exists for; it sits in the |
| 367 | middle band and still needs light lettering. */ |
| 368 | :root[data-theme="dusk"] { |
| 369 | --bg-primary: #222A34; |
| 370 | --bg-secondary: #29323D; |
| 371 | --bg-tertiary: #2F3946; |
| 372 | --bg-hover: #384352; |
| 373 | --border: #46525F; |
| 374 | --border-2: #5A6879; |
| 375 | --border-strong: #838E9B; |
| 376 | --text-primary: #EDF1F6; |
| 377 | --text-secondary: #BCC7D4; |
| 378 | --text-muted: #A4B1C0; |
| 379 | --accent: #5AA6E4; |
| 380 | --accent-hover: #79BEF5; |
| 381 | --accent-soft: #33455A; |
| 382 | --accent-text: #A9CDEC; |
| 383 | --ok: #6FC194; |
| 384 | --ok-bg: #263D33; |
| 385 | --warn: #DFB059; |
| 386 | --warn-bg: #3D3421; |
| 387 | --danger: #EC8578; |
| 388 | --success: #6FC194; |
| 389 | } |
| 390 | |
| 391 | /* Midnight -- cool deep dark: near-black with a blue cast, an ice accent. */ |
| 392 | :root[data-theme="midnight"] { |
| 393 | --bg-primary: #0E1218; |
| 394 | --bg-secondary: #151B23; |
| 395 | --bg-tertiary: #1B222C; |
| 396 | --bg-hover: #232C38; |
| 397 | --border: #262F3B; |
| 398 | --border-2: #333E4C; |
| 399 | --border-strong: #6E7680; |
| 400 | --text-primary: #E4EAF2; |
| 401 | --text-secondary: #9BA8B8; |
| 402 | --text-muted: #808E9F; |
| 403 | --accent: #3082C0; |
| 404 | --accent-hover: #3B93D2; |
| 405 | --accent-soft: #16293A; |
| 406 | --accent-text: #8FC4EA; |
| 407 | --ok: #56BE8C; |
| 408 | --ok-bg: #12301F; |
| 409 | --warn: #D7A54F; |
| 410 | --warn-bg: #33280F; |
| 411 | --danger: #E8776B; |
| 412 | --success: #56BE8C; |
| 413 | } |
| 414 | |
| 415 | /* Forest -- deep dark green, a moss accent. Warmer than Midnight, darker than |
| 416 | Sage, and no relation to either. */ |
| 417 | :root[data-theme="forest"] { |
| 418 | --bg-primary: #0F1511; |
| 419 | --bg-secondary: #161E18; |
| 420 | --bg-tertiary: #1C251E; |
| 421 | --bg-hover: #253026; |
| 422 | --border: #27322A; |
| 423 | --border-2: #354437; |
| 424 | --border-strong: #707A71; |
| 425 | --text-primary: #E6EDE6; |
| 426 | --text-secondary: #A0AFA1; |
| 427 | --text-muted: #86968A; |
| 428 | --accent: #4E9C55; |
| 429 | --accent-hover: #63B36B; |
| 430 | --accent-soft: #1B2E1D; |
| 431 | --accent-text: #A2CFA4; |
| 432 | --ok: #5FBE85; |
| 433 | --ok-bg: #143020; |
| 434 | --warn: #D2A052; |
| 435 | --warn-bg: #33290F; |
| 436 | --danger: #E38377; |
| 437 | --success: #5FBE85; |
| 438 | } |
| 439 | |
| 440 | /* Plum -- deep dark violet, a magenta accent. The loudest of the dark band. */ |
| 441 | :root[data-theme="plum"] { |
| 442 | --bg-primary: #14101A; |
| 443 | --bg-secondary: #1C1725; |
| 444 | --bg-tertiary: #231D2E; |
| 445 | --bg-hover: #2E2639; |
| 446 | --border: #302840; |
| 447 | --border-2: #40354F; |
| 448 | --border-strong: #777082; |
| 449 | --text-primary: #ECE6F2; |
| 450 | --text-secondary: #ADA0BC; |
| 451 | --text-muted: #9386A2; |
| 452 | --accent: #B06BC9; |
| 453 | --accent-hover: #C588DC; |
| 454 | --accent-soft: #2A1E36; |
| 455 | --accent-text: #CFA5E0; |
| 456 | --ok: #64BE93; |
| 457 | --ok-bg: #16301F; |
| 458 | --warn: #D3A257; |
| 459 | --warn-bg: #332A0F; |
| 460 | --danger: #E88090; |
| 461 | --success: #64BE93; |
| 462 | } |
| 463 | |
| 464 | /* Amber -- a dark palette for the end of the day. |
| 465 | Short-wavelength light is what suppresses melatonin, so this palette holds |
| 466 | its BLUE CHANNEL down everywhere rather than merely wearing a warm accent: |
| 467 | every surface, every rung of text and every state colour is mixed from red |
| 468 | and green, with blue kept to the minimum the hue needs. The greens and the |
| 469 | "ok" state come out olive rather than emerald for exactly that reason -- a |
| 470 | mint tick would put back at one point on screen what the whole palette is |
| 471 | avoiding. |
| 472 | |
| 473 | It is a reading palette, not a filter: the contrast floors are the same ones |
| 474 | every other palette is held to, and verify_theme checks the blue channel as |
| 475 | well, so a later edit cannot quietly brighten it back. */ |
| 476 | :root[data-theme="amber"] { |
| 477 | --bg-primary: #16100A; |
| 478 | --bg-secondary: #1E1610; |
| 479 | --bg-tertiary: #251C13; |
| 480 | --bg-hover: #2F2418; |
| 481 | --border: #33271A; |
| 482 | --border-2: #423220; |
| 483 | --border-strong: #796D61; |
| 484 | --text-primary: #F2DFC4; |
| 485 | --text-secondary: #C6AE88; |
| 486 | --text-muted: #A28C6B; |
| 487 | --accent: #B86A22; |
| 488 | --accent-hover: #D08133; |
| 489 | --accent-soft: #33220F; |
| 490 | --accent-text: #E6B677; |
| 491 | --ok: #A8B457; |
| 492 | --ok-bg: #262A10; |
| 493 | --warn: #D9A840; |
| 494 | --warn-bg: #33280F; |
| 495 | --danger: #DC7B55; |
| 496 | --success: #A8B457; |
| 497 | } |
| 498 | |
| 499 | /* Dark grounds take the selected chip the other way up: light block, dark |
| 500 | lettering. 4.86 against the chip it replaces, 4.63 against every dark-band |
| 501 | surface. */ |
| 502 | /* What the user agent should assume when it draws something we have not |
| 503 | styled: a scrollbar, a date picker, a default form control, a link. Without |
| 504 | it Chromium draws light-mode furniture on a black panel. */ |
| 505 | :root[data-ink="dark"] { color-scheme: light; } |
| 506 | :root[data-ink="light"] { color-scheme: dark; } |
| 507 | |
| 508 | :root[data-ink="light"] { |
| 509 | --tag-on-bg: #B0B0B0; |
| 510 | --tag-on-hover: #C4C4C4; |
| 511 | --tag-on-fg: #1A1A1A; |
| 512 | --on-fill: var(--bg-primary); |
| 513 | } |
| 514 | |
| 515 | /* ── CJK faces ───────────────────────────────────────────────── |
| 516 | The Latin stacks above name no CJK family, so ideographs fell to the |
| 517 | browser's generic fallback -- a mismatched face mid-string beside Nunito, |
| 518 | and Japanese kanji drawn in Chinese glyph forms wherever that fallback is |
| 519 | an SC face (Han unification). Naming the regional families per language |
| 520 | makes the glyphs deterministic; the Latin runs keep their own face because |
| 521 | the Latin families still come first in each stack. `zh-Hans` matches |
| 522 | `:lang(zh)` by prefix. */ |
| 523 | :root:lang(zh) { --font-cjk: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans SC'; } |
| 524 | :root:lang(ja) { --font-cjk: 'Hiragino Kaku Gothic ProN', 'Yu Gothic UI', Meiryo, 'Noto Sans CJK JP', 'Noto Sans JP'; } |
| 525 | :root:lang(ko) { --font-cjk: 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans CJK KR', 'Noto Sans KR'; } |
| 526 | |
| 527 | /* A short window is mostly composer and chrome already, so six tiles there |
| 528 | would be half of what is left. Four. */ |
| 529 | @media (max-height: 700px) { |
| 530 | :root { --attach-cap: 4; } |
| 531 | } |