@crewlethq/tokens 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -16
- package/dist/css/themes/abyss.css +323 -0
- package/dist/css/themes/all.css +2869 -0
- package/dist/css/themes/dusk.css +323 -0
- package/dist/css/themes/high-contrast.css +323 -0
- package/dist/css/themes/iris.css +323 -0
- package/dist/css/themes/ledger.css +323 -0
- package/dist/css/themes/nightshade.css +323 -0
- package/dist/css/themes/nocturne.css +323 -0
- package/dist/css/themes/origin.css +323 -0
- package/dist/css/themes/violet.css +323 -0
- package/dist/css/themes.css +9 -0
- package/dist/css/tokens.css +4 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/themes.d.ts +457 -0
- package/dist/themes.js +473 -0
- package/package.json +3 -2
- package/test/color.mjs +6 -0
- package/test/palette.d.mts +26 -1
- package/test/palette.mjs +185 -35
- package/test/palette.test.mjs +107 -2
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Single source of truth for the Crewlet design tokens: color, spacing, size, typography (`font`), radius, shadow, blur, breakpoint, density, motion and z-index. Authored as JSON, compiled by Style Dictionary into:
|
|
4
4
|
|
|
5
5
|
- `dist/css/tokens.css` (CSS custom properties on `:root`)
|
|
6
|
-
- `dist/css/themes.css` (the dark and light palettes)
|
|
6
|
+
- `dist/css/themes.css` (the dark and light palettes of the default theme)
|
|
7
|
+
- `dist/css/themes/<id>.css` and `dist/css/themes/all.css` (the named themes, each under its own `data-palette` attribute)
|
|
7
8
|
- `dist/css/density.css`, `dist/css/base.css`, `dist/css/breakpoint.css`
|
|
8
9
|
- `dist/index.js` + `dist/index.d.ts` (typed JS object for runtime use)
|
|
9
10
|
|
|
@@ -17,15 +18,20 @@ import '@crewlethq/tokens/css';
|
|
|
17
18
|
// Import it after the line above; see "Theming" below.
|
|
18
19
|
import '@crewlethq/tokens/css/themes';
|
|
19
20
|
|
|
20
|
-
// 3) Optional: the
|
|
21
|
+
// 3) Optional: the named themes, each under its own data-palette attribute.
|
|
22
|
+
// Import it after the line above if the app offers a theme picker; see
|
|
23
|
+
// "Named themes" below.
|
|
24
|
+
import '@crewlethq/tokens/css/themes/all';
|
|
25
|
+
|
|
26
|
+
// 4) Optional: the density contract. Import it if the app has a density
|
|
21
27
|
// control; see "Density" below.
|
|
22
28
|
import '@crewlethq/tokens/css/density';
|
|
23
29
|
|
|
24
|
-
//
|
|
30
|
+
// 5) Optional: self-hosted Geist and Geist Mono (see "Fonts" below).
|
|
25
31
|
// Skip this if the app already loads its own fonts at the shell.
|
|
26
32
|
import '@crewlethq/tokens/css/fonts';
|
|
27
33
|
|
|
28
|
-
//
|
|
34
|
+
// 6) Optional: the document baseline. The reset, the document's own type and
|
|
29
35
|
// colour, one focus ring, themed scrollbars and the reduced-motion
|
|
30
36
|
// collapse; see "The document baseline" below.
|
|
31
37
|
import '@crewlethq/tokens/css/base';
|
|
@@ -39,6 +45,20 @@ const accent = color.brand.accent;
|
|
|
39
45
|
CSS variables follow Style Dictionary's `--{group}-{path}` convention,
|
|
40
46
|
for example `var(--color-brand-accent)` or `var(--spacing-4)`.
|
|
41
47
|
|
|
48
|
+
## Changed in 0.13.0: named themes
|
|
49
|
+
|
|
50
|
+
Nine named themes ship beside the default, each with a dark and a light palette, for an application that offers a theme picker. A document that sets no `data-palette` attribute is painted exactly as before, and no default value moved.
|
|
51
|
+
|
|
52
|
+
| What changed | What to change | Details |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `@crewlethq/tokens/css/themes/<id>` paints one named theme and `@crewlethq/tokens/css/themes/all` paints every one, each under `:root[data-palette="<id>"]`. | Nothing. To offer the themes, import `css/themes/all` after `css/themes` and set `data-palette` on `<html>`. | [Named themes](#named-themes) |
|
|
55
|
+
| `--color-text-on-danger` and `--color-text-on-scrim` (`color.text.onDanger`, `color.text.onScrim`) are new, white in every theme. The destructive fill's label and a label on the scrim read them, because `--color-text-on-accent` is dark on a theme whose accent is light. | A stylesheet that labels the danger fill or the scrim with `--color-text-on-accent` reads the new token. | [The accent](#the-accent) |
|
|
56
|
+
| `--color-text-link-quiet` (`color.text.linkQuiet`) is new: a link inside running text or a row, the accent ink mixed 65% with the secondary text in OKLab, in every palette of every theme. The palette suite measures it as an ink (`INK_STEPS`), and it clears 5.2:1 on a pressed row in every theme. | Nothing. A quiet link reads it, and takes `--color-brand-accent-ink` under the pointer and on focus. | [The accent](#the-accent) |
|
|
57
|
+
| The typed export adds `themeRegistry`, `themeGroups`, `DEFAULT_THEME_ID` and `isThemeId()`, and the types `ThemeEntry`, `ThemeId` and `ThemeGroupId`. | Nothing. | [The theme registry](#the-theme-registry) |
|
|
58
|
+
| The theme source is one folder per theme: `tokens/themes/dark.json`, `tokens/themes/light.json` and `tokens/intent.json` are now `dark.json`, `light.json` and `intent.json` in `tokens/themes/crewlet/`. | Nothing, for a consumer: the token source is not published. A change in this repository edits the new paths. | [Add a theme](#add-a-theme) |
|
|
59
|
+
| In `@crewlethq/tokens/test/palette`, `runPalette()` and `paletteStates()` take `palette`, `tint` and `contrast`, and measure a named theme in its three states. The rules `a tinted ramp stays inside its declared tint`, `the accent carries more colour than the ground`, `a theme declares exactly the slots the default declares`, `text-muted reads in high contrast`, `ink step clears 7:1 in high contrast` and `a hairline clears 3:1 in high contrast` are new, with the exports `paletteSelectors()`, `NEUTRAL_CHROMA`, `TINT_HUE_RANGE`, `ACCENT_OVER_TINT`, `HIGH_CONTRAST`, `DESTRUCTIVE_LABEL` and `SCRIM_LABEL`. The destructive action's label and a label on the scrim are measured as `--color-text-on-danger` and `--color-text-on-scrim`. | Rename a subject a suite filters on. To measure the named themes, loop the registry as [The palette suite](#the-palette-suite) shows. | [The palette suite](#the-palette-suite) |
|
|
60
|
+
| `scripts/fit-palette.mjs` takes a theme id before the palette: `fit-palette.mjs abyss dark`. | Nothing. | [The approved palette, and the fit](#the-approved-palette-and-the-fit) |
|
|
61
|
+
|
|
42
62
|
## Changed in 0.6.1: the dark text ramp
|
|
43
63
|
|
|
44
64
|
In 0.6.0 the dark primary and tertiary text steps sat 1.98:1 apart, so a word a screen dims from the first step to the third (a calendar's days from the neighbouring month) read as a rounding rather than a step. The dark primary is lifted dE 0.30, and the palette suite now holds the step in every state.
|
|
@@ -50,7 +70,7 @@ In 0.6.0 the dark primary and tertiary text steps sat 1.98:1 apart, so a word a
|
|
|
50
70
|
|
|
51
71
|
## Changed in 0.6.0: the default palette
|
|
52
72
|
|
|
53
|
-
The dark and light themes take the default palette approved on 4 October 2026: blue-black neutrals, a violet accent (`#725be6` in dark, `#735af2` in light) and a cyan working state. No token is renamed or removed; the themed values change. [`tokens/intent.json`](./tokens/intent.json) records the approved values, and every value the palette suite's floors moved names its approved value, the distance and the measurement in its own comment. The marketing root, `tokens.css` on its own, keeps its values.
|
|
73
|
+
The dark and light themes take the default palette approved on 4 October 2026: blue-black neutrals, a violet accent (`#725be6` in dark, `#735af2` in light) and a cyan working state. No token is renamed or removed; the themed values change. [`tokens/themes/crewlet/intent.json`](./tokens/themes/crewlet/intent.json) records the approved values, and every value the palette suite's floors moved names its approved value, the distance and the measurement in its own comment. The marketing root, `tokens.css` on its own, keeps its values.
|
|
54
74
|
|
|
55
75
|
| What changed | What to change | Details |
|
|
56
76
|
| --- | --- | --- |
|
|
@@ -91,7 +111,7 @@ The dark and light themes take the default palette approved on 4 October 2026: b
|
|
|
91
111
|
- A document that sets nothing is dark, or light if the reader's system asks for light. A browser that reports no preference at all gets dark: dark is the palette the product is drawn in, and light is its translation.
|
|
92
112
|
- `document.documentElement.dataset.theme = 'light'` or `'dark'` pins one, and wins in both directions, which a media query alone cannot do. Light wins by a block of its own. Dark wins by keeping the light media block from matching: there is no dark attribute block, because the bare root already is one, and the `:not([data-theme="dark"])` on the media block is what lets an explicit dark beat a light system.
|
|
93
113
|
- Removing the attribute follows the system again. A value the file does not name (`data-theme="system"`, say) passes the media block's guard and misses the light attribute block, so it follows the system as well; `ThemeSwitcher` removes the attribute rather than writing one.
|
|
94
|
-
- Both light blocks are generated from one source, `tokens/themes/light.json`, and the palette suite compares them, key for key and value for value.
|
|
114
|
+
- Both light blocks are generated from one source, `tokens/themes/crewlet/light.json`, and the palette suite compares them, key for key and value for value.
|
|
95
115
|
|
|
96
116
|
Import it **after** `@crewlethq/tokens/css`. The two files share the `:root` selector and neither adds specificity, so the later import is the one that paints. `tokens.css` on its own is the base marketing palette, which is dark, and which carries a value for every themed slot so an application that imports only the token layer is never missing one.
|
|
97
117
|
|
|
@@ -99,6 +119,54 @@ Import it **after** `@crewlethq/tokens/css`. The two files share the `:root` sel
|
|
|
99
119
|
|
|
100
120
|
An application that overrides a themed token for one palette mirrors the same three selectors: its dark value on `:root`, its light value under both the media block and the attribute block. An override written only under `:root[data-theme="light"]` misses a reader whose system is light and who has chosen nothing.
|
|
101
121
|
|
|
122
|
+
### Named themes
|
|
123
|
+
|
|
124
|
+
The default theme, Crewlet, is the one `themes.css` paints on the bare root. Nine more ship beside it, each in a sheet of its own under `dist/css/themes/`, and all of them together in `all.css`:
|
|
125
|
+
|
|
126
|
+
| Group | Themes, by their `data-palette` value |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| Crewlet | `crewlet` (the default, painted with no attribute), `violet`, `origin` |
|
|
129
|
+
| Purple | `abyss`, `iris`, `dusk` |
|
|
130
|
+
| Cool | `nocturne`, `nightshade` |
|
|
131
|
+
| Warm | `ledger` |
|
|
132
|
+
| High contrast | `high-contrast` |
|
|
133
|
+
|
|
134
|
+
A named theme paints the same three blocks the default does, under its own attribute:
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
:root[data-palette="abyss"] { /* dark */ }
|
|
138
|
+
@media (prefers-color-scheme: light) {
|
|
139
|
+
:root[data-palette="abyss"]:not([data-theme="dark"]) { /* light */ }
|
|
140
|
+
}
|
|
141
|
+
:root[data-palette="abyss"][data-theme="light"] { /* light */ }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `data-palette` chooses the theme and `data-theme` keeps choosing the mode, so every theme follows the system, or a pinned mode, exactly as the default does. Removing `data-palette`, or setting a value no sheet names, returns to the default.
|
|
145
|
+
- Every block outranks the default's by its attribute, and a theme's light blocks outrank its own dark one, so source order never decides which palette paints. Import the sheet after `css/themes` all the same, as the header of each sheet says.
|
|
146
|
+
- A theme declares every slot the default declares, key for key, so nothing of the default shows through it. The palette suite holds that, and holds every theme to every rule in every state.
|
|
147
|
+
- Set the attribute before the first paint, from the stored choice, so the page never flashes the default: in a blocking script in `<head>`, or in the application's boot module before it renders. Read a stored value back through `isThemeId()` and drop one it rejects.
|
|
148
|
+
- `all.css` is 124,561 bytes, 9,072 gzipped; one theme's sheet is about 14 KB, 2.5 KB gzipped. One sheet switches every theme with no request at the moment of the switch, which is what a picker that previews a theme under the pointer needs.
|
|
149
|
+
|
|
150
|
+
### The theme registry
|
|
151
|
+
|
|
152
|
+
The typed export lists every theme, the default first, for the picker that offers them:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { DEFAULT_THEME_ID, isThemeId, themeGroups, themeRegistry } from '@crewlethq/tokens';
|
|
156
|
+
|
|
157
|
+
for (const group of themeGroups) {
|
|
158
|
+
const members = themeRegistry.filter((theme) => theme.group === group.id);
|
|
159
|
+
// ...one section of the picker
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const stored = localStorage.getItem('palette');
|
|
163
|
+
const palette = isThemeId(stored) ? stored : DEFAULT_THEME_ID;
|
|
164
|
+
if (palette === DEFAULT_THEME_ID) delete document.documentElement.dataset.palette;
|
|
165
|
+
else document.documentElement.dataset.palette = palette;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Each entry carries `id` (the `data-palette` value), `name`, `group`, `tag`, `description`, `tint` (the tint its grounds declare, or `null`), `contrast` (`'standard'` or `'high'`), and `swatch.dark` and `swatch.light`: the frame, sheet, card, raised, hairline, text, muted, accent, on-accent and the four state fills, which a picker draws a theme's thumbnail with. `@crewlethq/ui`'s `ThemeSwatch` and `ThemeGallery` draw them. The build suite holds every swatch colour to the sheet that paints it, and the registry to the group order `themeGroups` gives.
|
|
169
|
+
|
|
102
170
|
### Breaking change in 0.5.0
|
|
103
171
|
|
|
104
172
|
The palette is dark first. `themes.css` used to paint light on the bare root and dark under `@media (prefers-color-scheme: dark) :root:not([data-theme="light"])` and `:root[data-theme="dark"]`; it now paints dark on the bare root and light under `@media (prefers-color-scheme: light) :root:not([data-theme="dark"])` and `:root[data-theme="light"]`. The palettes themselves did not move.
|
|
@@ -143,7 +211,7 @@ themes.light.color.text.secondary; // '#3e4459'
|
|
|
143
211
|
themes.dark.color.data['4']; // '#eab106'
|
|
144
212
|
```
|
|
145
213
|
|
|
146
|
-
Prefer the custom property wherever CSS can reach: a value read here is taken at build time, so it does not follow a theme the reader changes. The package's own suite compares every exported value against the declaration in `themes.css`, in both directions, because two spellings of one palette is the arrangement that drifts.
|
|
214
|
+
They are the default theme's palettes; a named theme's colours are in its sheet, and the ones a picker draws are in its registry entry's `swatch`. Prefer the custom property wherever CSS can reach: a value read here is taken at build time, so it does not follow a theme the reader changes. The package's own suite compares every exported value against the declaration in `themes.css`, in both directions, because two spellings of one palette is the arrangement that drifts.
|
|
147
215
|
|
|
148
216
|
## Surfaces
|
|
149
217
|
|
|
@@ -358,11 +426,14 @@ The accent is a violet, and it is **per palette**:
|
|
|
358
426
|
| `--color-brand-accent-hover` | `#674ee4` | `#674ee4` | The primary action under the pointer. |
|
|
359
427
|
| `--color-brand-accent-active` | `#5b3dc7` | `#5b3dc7` | The primary action pressed. |
|
|
360
428
|
| `--color-brand-accent-ink` | `#a9b0ff` | `#4f51bd` | The accent as text: a link, an outline button's label. |
|
|
429
|
+
| `--color-text-link-quiet` | `#a9b0ea` | `#484e99` | A link inside running text or a row, which must not compete with the words around it: the accent ink mixed 65% with the secondary text, in OKLab. It takes the accent ink under the pointer and on focus. Measured as an ink. |
|
|
361
430
|
| `--color-brand-accent-soft` | the accent at 0.16 | the accent at 0.125 | The tint behind a selected row or a toggle that is on. |
|
|
362
431
|
| `--color-brand-accent-soft-strong` | the accent at 0.32 | the accent at 0.32 | The border paired with the soft tint. |
|
|
363
432
|
| `--color-brand-accent-rgb` | `114, 91, 230` | `115, 90, 242` | The accent as an `r, g, b` triple, for a translucent tint of a stylesheet's own. `--shadow-glow` is composed from it. |
|
|
364
433
|
| `--color-focus` | `#8572fe` | `#735af2` | The focus ring. |
|
|
365
|
-
| `--color-text-on-accent` | `#ffffff` | `#ffffff` | The label on the accent's three fills
|
|
434
|
+
| `--color-text-on-accent` | `#ffffff` | `#ffffff` | The label on the accent's three fills. |
|
|
435
|
+
|
|
436
|
+
A label on a fill that is not the accent has a token of its own, so a theme whose accent is light enough to carry a dark label (Iris's periwinkle does, at night) never darkens the destructive action's label or a label on the scrim: `--color-text-on-danger` labels the danger fill and its hover, and `--color-text-on-scrim` labels the scrim and the seeded avatar tints. Both are white in every theme.
|
|
366
437
|
|
|
367
438
|
`-rgb` and `-soft-strong` are derived from the accent by the build, per palette, and the build suite holds the written `-soft` to the accent's own channels. The marketing root keeps the brand's `#7c56ff` and its own steps (`tokens/color.json`).
|
|
368
439
|
|
|
@@ -465,6 +536,29 @@ const { failures } = runPalette({
|
|
|
465
536
|
expect(failures.map(describeFailure)).toEqual([]);
|
|
466
537
|
```
|
|
467
538
|
|
|
539
|
+
A named theme is measured the same way: the default sheets with the theme's own after them, under the profile its registry entry gives.
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
import { DEFAULT_THEME_ID, themeRegistry } from '@crewlethq/tokens';
|
|
543
|
+
|
|
544
|
+
for (const theme of themeRegistry.filter((entry) => entry.id !== DEFAULT_THEME_ID)) {
|
|
545
|
+
const { failures } = runPalette({
|
|
546
|
+
tokens: readFileSync(`${dir}/tokens.css`, 'utf8'),
|
|
547
|
+
themes: `${readFileSync(`${dir}/themes.css`, 'utf8')}\n${readFileSync(`${dir}/themes/${theme.id}.css`, 'utf8')}`,
|
|
548
|
+
palette: theme.id,
|
|
549
|
+
tint: theme.tint,
|
|
550
|
+
contrast: theme.contrast,
|
|
551
|
+
});
|
|
552
|
+
expect(failures.map(describeFailure)).toEqual([]);
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
It measures the theme's `dark`, `light (media query)` and `light (attribute)` states (the base marketing root is the default's, and is measured with it) with every rule the default clears, and three more:
|
|
557
|
+
|
|
558
|
+
- **The tint.** A theme that declares no tint keeps every rung under `NEUTRAL_CHROMA` (`the neutral ramp stays neutral`). One that declares `tint: { hue, chroma }` may tint its grounds up to that chroma, within `TINT_HUE_RANGE` degrees of that hue (`a tinted ramp stays inside its declared tint`), and its accent still carries `ACCENT_OVER_TINT` times the chroma of its most tinted rung (`the accent carries more colour than the ground`), so the accent reads as the colour on the page rather than as more of the ground.
|
|
559
|
+
- **High contrast.** `contrast: 'high'` raises every text step and every ink to 7:1, the WCAG AAA floor for body text; the muted step to a readable 4.5:1 in place of the decorative band, because a reader who asked for more contrast cannot rely on a glyph they cannot see; and the card's hairline to 3:1 against the card and the sheet (`HIGH_CONTRAST`).
|
|
560
|
+
- **Completeness.** A theme declares exactly the slots the default declares (`a theme declares exactly the slots the default declares`), so nothing of the default shows through it.
|
|
561
|
+
|
|
468
562
|
One implementation, two gates. The constraint is that the floors are kept, not only that the measurement moves, and a tokens bump that lowered a ratio would otherwise reach a consumer through an auto-merged dependency update with nothing measuring a ratio again.
|
|
469
563
|
|
|
470
564
|
The tarball ships `test/color.mjs`, `test/palette.mjs`, `test/palette.d.mts` and `test/palette.test.mjs`, so the whole table runs over the installed version with no configuration at all, from the application's root:
|
|
@@ -473,22 +567,26 @@ The tarball ships `test/color.mjs`, `test/palette.mjs`, `test/palette.d.mts` and
|
|
|
473
567
|
node node_modules/@crewlethq/tokens/test/palette.test.mjs
|
|
474
568
|
```
|
|
475
569
|
|
|
476
|
-
or with the package's own `node --test "test/*.test.mjs"` from inside `node_modules/@crewlethq/tokens`. Not with `node --test` over a path into `node_modules` from outside it: the test runner skips `node_modules`, a glob included, runs no test at all and exits 0. `test/tarball.test.mjs` packs this package with npm, installs the tarball into an empty directory and runs both commands there, counting the tests that ran, and imports the suite by its package name to hold `runPalette`, `paletteStates`, `describeFailure` and `tightest` to the shapes `test/palette.d.mts` declares, which is how an application's own suite reaches them.
|
|
570
|
+
or with the package's own `node --test "test/*.test.mjs"` from inside `node_modules/@crewlethq/tokens`. Not with `node --test` over a path into `node_modules` from outside it: the test runner skips `node_modules`, a glob included, runs no test at all and exits 0. `test/tarball.test.mjs` packs this package with npm, installs the tarball into an empty directory and runs both commands there, counting the tests that ran, and imports the suite by its package name to hold `runPalette`, `paletteStates`, `describeFailure` and `tightest` to the shapes `test/palette.d.mts` declares, which is how an application's own suite reaches them. It resolves every named theme's sheet the same way, by the ids the installed registry lists, and measures each under its own profile.
|
|
477
571
|
|
|
478
572
|
`test/build.test.mjs` stays out of the tarball: it reads the token source the build compiles from, which a tarball does not carry. So do `test/intent.test.mjs`, which reads the design record below, `test/tarball.test.mjs`, which packs the package from outside it, and `test/fonts.test.mjs`, which decodes every file in `fonts/` and holds [`fonts/README.md`](./fonts/README.md), the built `@font-face` rules and [`fonts/OFL.txt`](./fonts/OFL.txt) to what the files say about themselves: a replaced file is caught in this repository, before it is published, not in an installed copy.
|
|
479
573
|
|
|
480
574
|
### The approved palette, and the fit
|
|
481
575
|
|
|
482
|
-
[`tokens/intent.json`](./tokens/intent.json) is the approved palette: the default dark and light themes approved on 4 October 2026, recorded under the custom property names the design artboards declare, each with the custom property that ships it, and the values the design draws outside those two blocks (the primary action's label, hover and press among them). It is a record rather than a token file: the build skips it and nothing is emitted from it. `test/intent.test.mjs` holds
|
|
576
|
+
[`tokens/themes/crewlet/intent.json`](./tokens/themes/crewlet/intent.json) is the approved palette: the default dark and light themes approved on 4 October 2026, recorded under the custom property names the design artboards declare, each with the custom property that ships it, and the values the design draws outside those two blocks (the primary action's label, hover and press among them). It is a record rather than a token file: the build skips it and nothing is emitted from it. Each named theme has a record of its own beside its theme files, `tokens/themes/<id>/intent.json`: the palette the theme was designed as, under the same design names. `test/intent.test.mjs` holds every record to naming only tokens its theme's palettes declare, the same names for the same tokens in both palettes, and values that parse.
|
|
483
577
|
|
|
484
578
|
Every colour the record names ships the value recorded there, or the least move from it that one of the palette suite's floors forced: the surfaces, the neutral text and border steps, the accent, the four states and their inks, and the four chart series. [`scripts/fit-palette.mjs`](./scripts/fit-palette.mjs) is what finds those moves:
|
|
485
579
|
|
|
486
580
|
```sh
|
|
487
581
|
npm run build --workspace @crewlethq/tokens
|
|
488
|
-
node packages/tokens/scripts/fit-palette.mjs
|
|
489
|
-
node packages/tokens/scripts/fit-palette.mjs light
|
|
582
|
+
node packages/tokens/scripts/fit-palette.mjs # the default, both palettes, about three minutes
|
|
583
|
+
node packages/tokens/scripts/fit-palette.mjs light # one of them
|
|
584
|
+
node packages/tokens/scripts/fit-palette.mjs abyss # a named theme, both palettes
|
|
585
|
+
node packages/tokens/scripts/fit-palette.mjs abyss dark # one of its palettes
|
|
490
586
|
```
|
|
491
587
|
|
|
588
|
+
A named theme is fitted from its own record, over the default sheets with its own after them, under the profile its `theme.json` declares.
|
|
589
|
+
|
|
492
590
|
From the recorded values it searches for the palette that moves the design least in all, in OKLab dE, while clearing every rule, each candidate measured by `runPalette` itself over the candidate stylesheets:
|
|
493
591
|
|
|
494
592
|
- a neutral step and the accent's family move along lightness alone, as every move before the fit was made; a state or chart hue moves inside a hue family 15 degrees either side of the design's; a translucent step moves its alpha;
|
|
@@ -499,13 +597,13 @@ From the recorded values it searches for the palette that moves the design least
|
|
|
499
597
|
- a hue is looked for along lightness first, keeping its hue and chroma, the move every hand fit made, and then in drawn directions inside its family; and a seat found along a drawn direction is then turned toward the design, the direction rotated in halving angles until no turn brings the seat nearer, because a seat on a drawn direction is only as near as that direction lets it be;
|
|
500
598
|
- a value that another is measured ON moves together with it: a state's ink is held to 4.5:1 on its own fill's soft tint and on a code chip over that tint, so a fill walked back toward the design takes its ink with it, the ink repaired at every step. Moved one at a time, neither could go nearer: on the palette before this one, the light danger red ended as a muted maroon dE 15.19 from its design, where the red and its ink moved together needed 10.40 and 3.05.
|
|
501
599
|
|
|
502
|
-
It is seeded (mulberry32 at seed 1). The search spends a fixed budget of palette evaluations repairing and improving the palette, and the finish then runs until no move it knows pays, so the same built stylesheets give the same fit, to the byte. The fit is the best it finds, not a proof that nothing moves the design less; its first line says how many evaluations the search and the finish took, and says so plainly if the finish ever stopped at its ceiling with moves still paying. For each value it fits it prints the design value, the value the fit ships, the dE between them and the binding rule, which is what fails when that value alone goes back to the design; then every token whose built value is not the fit. A token the floors moved says the same three things in its own comment in `
|
|
600
|
+
It is seeded (mulberry32 at seed 1). The search spends a fixed budget of palette evaluations repairing and improving the palette, and the finish then runs until no move it knows pays, so the same built stylesheets give the same fit, to the byte. The fit is the best it finds, not a proof that nothing moves the design less; its first line says how many evaluations the search and the finish took, and says so plainly if the finish ever stopped at its ceiling with moves still paying. For each value it fits it prints the design value, the value the fit ships, the dE between them and the binding rule, which is what fails when that value alone goes back to the design; then every token whose built value is not the fit. A token the floors moved says the same three things in its own comment in its theme's `dark.json` or `light.json`: the design value, the dE of the move and the measurement that forced it.
|
|
503
601
|
|
|
504
602
|
The fit is a development tool. It is not published, and no test runs it or reads what it prints: the gate is the palette suite, and the fit is how a value that passes it is chosen.
|
|
505
603
|
|
|
506
|
-
The fit is held to every rule in the table. Run over this release's stylesheets, it reports that every dark value it fits ships its fitted value. Light is the exception: within its budget the search leaves two light floors failing (the warning fill as a mark on the frame, and raised against the card), so the shipped light values were placed by a walk from a passing palette back toward the approved values, each step kept only while every rule holds. Each moved light token states its move in its comment like any other, and the palette suite, which is the gate, passes in every state.
|
|
604
|
+
The fit is held to every rule in the table. Run over this release's stylesheets, it reports that every dark value it fits ships its fitted value. Light is the exception: within its budget the search leaves two light floors failing (the warning fill as a mark on the frame, and raised against the card), so the shipped light values were placed by a walk from a passing palette back toward the approved values, each step kept only while every rule holds. Each moved light token states its move in its comment like any other, and the palette suite, which is the gate, passes in every state. The named themes were placed the same way: by the fit, except Origin's two palettes and High contrast's dark one, which were walked back from a passing palette.
|
|
507
605
|
|
|
508
|
-
The Storybook shows both halves under **Foundations / Palette**. `Tightest Per Rule` is `tightest()` over the whole table: under each rule, the pair that would break first. `The Fit` is every colour `tokens/intent.json
|
|
606
|
+
The Storybook shows both halves under **Foundations / Palette**. `Tightest Per Rule` is `tightest()` over the whole table: under each rule, the pair that would break first. `The Fit` is every colour the default's record, `tokens/themes/crewlet/intent.json`, declares, per palette, with what ships for it, the dE between them, and what fails when that value alone goes back to the design: the suite run again over the built `themes.css` with that one declaration put back (an overlay the design drew opaque at the alpha nearest it, and the steps the build derives from a fill following it), reduced to the tightest failure under each rule it breaks. It is measured every time the story renders rather than copied from a comment, so a move that stopped being forced says so there first. Its logic is `apps/storybook/src/paletteFit.ts`, and `apps/storybook/test/paletteFit.test.mjs` holds it.
|
|
509
607
|
|
|
510
608
|
## Fonts
|
|
511
609
|
|
|
@@ -548,7 +646,7 @@ It is the one stylesheet in this package that contacts a third-party host, which
|
|
|
548
646
|
|
|
549
647
|
## Add or change a token
|
|
550
648
|
|
|
551
|
-
1. Edit a JSON file in `tokens/`, or `
|
|
649
|
+
1. Edit a JSON file in `tokens/`, or a theme's `dark.json` and `light.json` in `tokens/themes/<id>/` for a slot that changes with the palette. A themed slot needs an entry in **every** theme's two files and a value in `tokens/color.json`, which the palette suite checks. A colour the design draws starts from its value in the theme's `intent.json`, which changes first when the design does, and `scripts/fit-palette.mjs` gives the least move from it that clears the suite (see [The approved palette, and the fit](#the-approved-palette-and-the-fit)).
|
|
552
650
|
2. Run `npm run build` (or `npm run dev` for watch).
|
|
553
651
|
3. Run `npm test`. A colour change that lowers a measured floor fails here.
|
|
554
652
|
4. The generated `dist/` and `src/index.ts` are regenerated.
|
|
@@ -556,6 +654,15 @@ It is the one stylesheet in this package that contacts a third-party host, which
|
|
|
556
654
|
Never edit files in `dist/` or `src/index.ts` directly. They are
|
|
557
655
|
regenerated on every build. `stylesheets/base.css` is the one stylesheet in this package written by hand rather than generated; the build copies it into `dist/css/`.
|
|
558
656
|
|
|
657
|
+
## Add a theme
|
|
658
|
+
|
|
659
|
+
1. Make a folder `tokens/themes/<id>/`. The id is the `data-palette` value, in lower case, digits and dashes. It holds:
|
|
660
|
+
- `theme.json`: `name`, `group` (an id from `tokens/themes/groups.json`), `tag`, `description` and `order` within the group; `tint: { "hue", "chroma" }` if its grounds are tinted, and `"contrast": "high"` if it is held to the high-contrast floors. The default declares neither.
|
|
661
|
+
- `intent.json`: the design, laid out as the default's record is.
|
|
662
|
+
- `dark.json` and `light.json`: every slot the default's declare, key for key.
|
|
663
|
+
2. Run `npm run build`, then `node scripts/fit-palette.mjs <id>`, and write what the fit ships into the two theme files, each moved value with the comment the fit gives it.
|
|
664
|
+
3. Run `npm test`. The palette suite measures the theme under its own profile, the design record suite holds its record, and the build suite holds its sheet, its swatch and its place in the registry.
|
|
665
|
+
|
|
559
666
|
## License
|
|
560
667
|
|
|
561
668
|
The tokens, stylesheets and scripts are licensed under the MIT License, whose text is in [`LICENSE`](./LICENSE). The font files in `fonts/` are licensed under the SIL Open Font License 1.1, whose text and copyright notices are in [`fonts/OFL.txt`](./fonts/OFL.txt). The package manifest records both as the SPDX expression `MIT AND OFL-1.1`.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/* Generated by style-dictionary. The Abyss theme: Ink-violet grounds on #090019 with an electric violet accent.
|
|
2
|
+
Import it after the default palettes, and set data-palette="abyss" on <html>:
|
|
3
|
+
import '@crewlethq/tokens/css';
|
|
4
|
+
import '@crewlethq/tokens/css/themes';
|
|
5
|
+
import '@crewlethq/tokens/css/themes/abyss';
|
|
6
|
+
data-theme keeps choosing light or dark exactly as it does for the default. */
|
|
7
|
+
|
|
8
|
+
:root[data-palette="abyss"] {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
|
|
11
|
+
--color-surface-frame: #090019;
|
|
12
|
+
--color-surface-background: #120425;
|
|
13
|
+
--color-surface-subtle: #180b2c;
|
|
14
|
+
--color-surface-elevated: #1e1430;
|
|
15
|
+
--color-surface-hover: rgba(255, 255, 255, 0.061);
|
|
16
|
+
--color-surface-pressed: rgba(255, 255, 255, 0.094);
|
|
17
|
+
--color-surface-inset: rgba(255, 255, 255, 0.028);
|
|
18
|
+
--color-surface-glass: rgba(24, 11, 44, 0.90);
|
|
19
|
+
--color-surface-scrim: rgba(0, 0, 0, 0.6);
|
|
20
|
+
--color-surface-veil: rgba(9, 0, 25, 0.65);
|
|
21
|
+
--color-surface-inverse: #fdfcff;
|
|
22
|
+
--color-text-primary: #ebe6f8;
|
|
23
|
+
--color-text-secondary: #b4aec6;
|
|
24
|
+
--color-text-link-quiet: #cbb9eb;
|
|
25
|
+
--color-text-tertiary: #9994a8;
|
|
26
|
+
--color-text-muted: #62577c;
|
|
27
|
+
--color-text-on-accent: #ffffff;
|
|
28
|
+
--color-text-on-danger: #ffffff;
|
|
29
|
+
--color-text-on-scrim: #ffffff;
|
|
30
|
+
--color-text-inverse: #090019;
|
|
31
|
+
--color-brand-accent: #8633fe;
|
|
32
|
+
--color-brand-accent-hover: #7a26f0;
|
|
33
|
+
--color-brand-accent-active: #6d1edb;
|
|
34
|
+
--color-brand-accent-ink: #d7beff;
|
|
35
|
+
--color-brand-accent-soft: rgba(134, 51, 254, 0.16);
|
|
36
|
+
--color-brand-accent-rgb: 134, 51, 254;
|
|
37
|
+
--color-brand-accent-soft-strong: rgba(134, 51, 254, 0.32);
|
|
38
|
+
--color-border-default: #2b1f41;
|
|
39
|
+
--color-border-hover: #3c3054;
|
|
40
|
+
--color-border-strong: #3c3054;
|
|
41
|
+
--color-border-strong-hover: #4a3e64;
|
|
42
|
+
--color-border-control: #7b738e;
|
|
43
|
+
--color-focus: #8e4eff;
|
|
44
|
+
--color-feedback-success: #16d297;
|
|
45
|
+
--color-feedback-success-ink: #70e7b5;
|
|
46
|
+
--color-feedback-warning: #ffb22f;
|
|
47
|
+
--color-feedback-warning-ink: #ffca7c;
|
|
48
|
+
--color-feedback-danger: #dd2c5b;
|
|
49
|
+
--color-feedback-danger-hover: #d21d53;
|
|
50
|
+
--color-feedback-danger-ink: #ff8c8a;
|
|
51
|
+
--color-feedback-info: #1281c3;
|
|
52
|
+
--color-feedback-info-ink: #30aff8;
|
|
53
|
+
--color-feedback-success-soft: rgba(22, 210, 151, 0.12);
|
|
54
|
+
--color-feedback-success-line: rgba(22, 210, 151, 0.3);
|
|
55
|
+
--color-feedback-warning-soft: rgba(255, 178, 47, 0.12);
|
|
56
|
+
--color-feedback-warning-line: rgba(255, 178, 47, 0.3);
|
|
57
|
+
--color-feedback-danger-soft: rgba(221, 44, 91, 0.12);
|
|
58
|
+
--color-feedback-danger-line: rgba(221, 44, 91, 0.3);
|
|
59
|
+
--color-feedback-info-soft: rgba(18, 129, 195, 0.12);
|
|
60
|
+
--color-feedback-info-line: rgba(18, 129, 195, 0.3);
|
|
61
|
+
--color-data-1: #798fd3;
|
|
62
|
+
--color-data-2: #f48221;
|
|
63
|
+
--color-data-3: #00a086;
|
|
64
|
+
--color-data-4: #b38d00;
|
|
65
|
+
--color-data-other: #6f6782;
|
|
66
|
+
--color-node-purple: #8e6ed4;
|
|
67
|
+
--color-node-purple-ink: #cabae0;
|
|
68
|
+
--color-node-cyan: #5ca8be;
|
|
69
|
+
--color-node-cyan-ink: #b3d7e0;
|
|
70
|
+
--color-node-green: #62a578;
|
|
71
|
+
--color-node-green-ink: #b8d8c0;
|
|
72
|
+
--color-node-amber: #c89866;
|
|
73
|
+
--color-node-amber-ink: #e0c89e;
|
|
74
|
+
--color-node-rose: #c8757f;
|
|
75
|
+
--color-node-rose-ink: #e0bfc4;
|
|
76
|
+
--color-node-blue: #5b7fc7;
|
|
77
|
+
--color-node-blue-ink: #b9c5e0;
|
|
78
|
+
--color-node-purple-fill: rgba(142, 110, 212, 0.12);
|
|
79
|
+
--color-node-purple-line: rgba(142, 110, 212, 0.38);
|
|
80
|
+
--color-node-purple-halo: rgba(142, 110, 212, 0.16);
|
|
81
|
+
--color-node-cyan-fill: rgba(92, 168, 190, 0.12);
|
|
82
|
+
--color-node-cyan-line: rgba(92, 168, 190, 0.38);
|
|
83
|
+
--color-node-cyan-halo: rgba(92, 168, 190, 0.16);
|
|
84
|
+
--color-node-green-fill: rgba(98, 165, 120, 0.12);
|
|
85
|
+
--color-node-green-line: rgba(98, 165, 120, 0.38);
|
|
86
|
+
--color-node-green-halo: rgba(98, 165, 120, 0.16);
|
|
87
|
+
--color-node-amber-fill: rgba(200, 152, 102, 0.12);
|
|
88
|
+
--color-node-amber-line: rgba(200, 152, 102, 0.38);
|
|
89
|
+
--color-node-amber-halo: rgba(200, 152, 102, 0.16);
|
|
90
|
+
--color-node-rose-fill: rgba(200, 117, 127, 0.12);
|
|
91
|
+
--color-node-rose-line: rgba(200, 117, 127, 0.38);
|
|
92
|
+
--color-node-rose-halo: rgba(200, 117, 127, 0.16);
|
|
93
|
+
--color-node-blue-fill: rgba(91, 127, 199, 0.12);
|
|
94
|
+
--color-node-blue-line: rgba(91, 127, 199, 0.38);
|
|
95
|
+
--color-node-blue-halo: rgba(91, 127, 199, 0.16);
|
|
96
|
+
--color-scrollbar: rgba(255, 255, 255, 0.16);
|
|
97
|
+
--color-scrollbar-hover: rgba(255, 255, 255, 0.26);
|
|
98
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
99
|
+
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
100
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
101
|
+
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
102
|
+
--shadow-xl: 0 12px 32px rgba(0, 0, 0, 0.4);
|
|
103
|
+
--shadow-sheet: -8px 0 32px rgba(0, 0, 0, 0.4);
|
|
104
|
+
--shadow-2xl: 0 16px 64px rgba(0, 0, 0, 0.6);
|
|
105
|
+
--shadow-card: 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
106
|
+
--shadow-rim-lit: 0 4px 24px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
107
|
+
--shadow-hairline: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
|
108
|
+
--shadow-focus: 0 0 0 2px var(--color-surface-background), 0 0 0 4px var(--color-focus);
|
|
109
|
+
--shadow-selection: inset 0 0 0 2px var(--color-brand-accent);
|
|
110
|
+
--shadow-glow: 0 4px 24px rgba(var(--color-brand-accent-rgb), 0.3);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@media (prefers-color-scheme: light) {
|
|
114
|
+
:root[data-palette="abyss"]:not([data-theme="dark"]) {
|
|
115
|
+
color-scheme: light;
|
|
116
|
+
|
|
117
|
+
--color-surface-frame: #e8e3f3;
|
|
118
|
+
--color-surface-background: #f8f6fd;
|
|
119
|
+
--color-surface-subtle: #fdfcff;
|
|
120
|
+
--color-surface-elevated: #edeaf7;
|
|
121
|
+
--color-surface-hover: rgba(9, 0, 25, 0.044);
|
|
122
|
+
--color-surface-pressed: rgba(9, 0, 25, 0.088);
|
|
123
|
+
--color-surface-inset: rgba(9, 0, 25, 0.026);
|
|
124
|
+
--color-surface-glass: rgba(253, 252, 255, 0.90);
|
|
125
|
+
--color-surface-scrim: rgba(0, 0, 0, 0.6);
|
|
126
|
+
--color-surface-veil: rgba(232, 227, 243, 0.65);
|
|
127
|
+
--color-surface-inverse: #090019;
|
|
128
|
+
--color-text-primary: #1d132f;
|
|
129
|
+
--color-text-secondary: #403654;
|
|
130
|
+
--color-text-link-quiet: #5d2aa2;
|
|
131
|
+
--color-text-tertiary: #5b5767;
|
|
132
|
+
--color-text-muted: #8d83a2;
|
|
133
|
+
--color-text-on-accent: #ffffff;
|
|
134
|
+
--color-text-on-danger: #ffffff;
|
|
135
|
+
--color-text-on-scrim: #ffffff;
|
|
136
|
+
--color-text-inverse: #f8f6fd;
|
|
137
|
+
--color-brand-accent: #6b11da;
|
|
138
|
+
--color-brand-accent-hover: #6008c6;
|
|
139
|
+
--color-brand-accent-active: #5403af;
|
|
140
|
+
--color-brand-accent-ink: #6e00ce;
|
|
141
|
+
--color-brand-accent-soft: rgba(107, 17, 218, 0.125);
|
|
142
|
+
--color-brand-accent-rgb: 107, 17, 218;
|
|
143
|
+
--color-brand-accent-soft-strong: rgba(107, 17, 218, 0.32);
|
|
144
|
+
--color-border-default: #dfd9eb;
|
|
145
|
+
--color-border-hover: #cdc7dc;
|
|
146
|
+
--color-border-strong: #cdc7dc;
|
|
147
|
+
--color-border-strong-hover: #bcb5cc;
|
|
148
|
+
--color-border-control: #78708b;
|
|
149
|
+
--color-focus: #6b11da;
|
|
150
|
+
--color-feedback-success: #019472;
|
|
151
|
+
--color-feedback-success-ink: #074f35;
|
|
152
|
+
--color-feedback-warning: #c16f00;
|
|
153
|
+
--color-feedback-warning-ink: #8e4500;
|
|
154
|
+
--color-feedback-danger: #9a0028;
|
|
155
|
+
--color-feedback-danger-hover: #8c0024;
|
|
156
|
+
--color-feedback-danger-ink: #80041a;
|
|
157
|
+
--color-feedback-info: #287ac2;
|
|
158
|
+
--color-feedback-info-ink: #005a9d;
|
|
159
|
+
--color-feedback-success-soft: rgba(1, 148, 114, 0.12);
|
|
160
|
+
--color-feedback-success-line: rgba(1, 148, 114, 0.3);
|
|
161
|
+
--color-feedback-warning-soft: rgba(193, 111, 0, 0.12);
|
|
162
|
+
--color-feedback-warning-line: rgba(193, 111, 0, 0.3);
|
|
163
|
+
--color-feedback-danger-soft: rgba(154, 0, 40, 0.12);
|
|
164
|
+
--color-feedback-danger-line: rgba(154, 0, 40, 0.3);
|
|
165
|
+
--color-feedback-info-soft: rgba(40, 122, 194, 0.12);
|
|
166
|
+
--color-feedback-info-line: rgba(40, 122, 194, 0.3);
|
|
167
|
+
--color-data-1: #5877f4;
|
|
168
|
+
--color-data-2: #c96003;
|
|
169
|
+
--color-data-3: #007c5d;
|
|
170
|
+
--color-data-4: #9e8000;
|
|
171
|
+
--color-data-other: #7d7590;
|
|
172
|
+
--color-node-purple: #7150b3;
|
|
173
|
+
--color-node-purple-ink: #573394;
|
|
174
|
+
--color-node-cyan: #166b80;
|
|
175
|
+
--color-node-cyan-ink: #004f62;
|
|
176
|
+
--color-node-green: #2c7046;
|
|
177
|
+
--color-node-green-ink: #05532b;
|
|
178
|
+
--color-node-amber: #865a28;
|
|
179
|
+
--color-node-amber-ink: #683e03;
|
|
180
|
+
--color-node-rose: #984a55;
|
|
181
|
+
--color-node-rose-ink: #7a2f3c;
|
|
182
|
+
--color-node-blue: #4062a7;
|
|
183
|
+
--color-node-blue-ink: #264588;
|
|
184
|
+
--color-node-purple-fill: rgba(113, 80, 179, 0.12);
|
|
185
|
+
--color-node-purple-line: rgba(113, 80, 179, 0.38);
|
|
186
|
+
--color-node-purple-halo: rgba(113, 80, 179, 0.16);
|
|
187
|
+
--color-node-cyan-fill: rgba(22, 107, 128, 0.12);
|
|
188
|
+
--color-node-cyan-line: rgba(22, 107, 128, 0.38);
|
|
189
|
+
--color-node-cyan-halo: rgba(22, 107, 128, 0.16);
|
|
190
|
+
--color-node-green-fill: rgba(44, 112, 70, 0.12);
|
|
191
|
+
--color-node-green-line: rgba(44, 112, 70, 0.38);
|
|
192
|
+
--color-node-green-halo: rgba(44, 112, 70, 0.16);
|
|
193
|
+
--color-node-amber-fill: rgba(134, 90, 40, 0.12);
|
|
194
|
+
--color-node-amber-line: rgba(134, 90, 40, 0.38);
|
|
195
|
+
--color-node-amber-halo: rgba(134, 90, 40, 0.16);
|
|
196
|
+
--color-node-rose-fill: rgba(152, 74, 85, 0.12);
|
|
197
|
+
--color-node-rose-line: rgba(152, 74, 85, 0.38);
|
|
198
|
+
--color-node-rose-halo: rgba(152, 74, 85, 0.16);
|
|
199
|
+
--color-node-blue-fill: rgba(64, 98, 167, 0.12);
|
|
200
|
+
--color-node-blue-line: rgba(64, 98, 167, 0.38);
|
|
201
|
+
--color-node-blue-halo: rgba(64, 98, 167, 0.16);
|
|
202
|
+
--color-scrollbar: rgba(7, 8, 13, 0.2);
|
|
203
|
+
--color-scrollbar-hover: rgba(7, 8, 13, 0.32);
|
|
204
|
+
--shadow-xs: 0 1px 2px rgba(7, 8, 13, 0.06);
|
|
205
|
+
--shadow-sm: 0 2px 8px rgba(7, 8, 13, 0.07);
|
|
206
|
+
--shadow-md: 0 4px 12px rgba(7, 8, 13, 0.08), 0 1px 3px rgba(7, 8, 13, 0.06);
|
|
207
|
+
--shadow-lg: 0 8px 24px rgba(7, 8, 13, 0.12);
|
|
208
|
+
--shadow-xl: 0 16px 40px rgba(7, 8, 13, 0.16), 0 2px 8px rgba(7, 8, 13, 0.08);
|
|
209
|
+
--shadow-sheet: -16px 0 40px rgba(7, 8, 13, 0.16), -2px 0 8px rgba(7, 8, 13, 0.08);
|
|
210
|
+
--shadow-2xl: 0 16px 64px rgba(7, 8, 13, 0.20);
|
|
211
|
+
--shadow-card: 0 8px 24px rgba(7, 8, 13, 0.07);
|
|
212
|
+
--shadow-rim-lit: 0 4px 24px rgba(7, 8, 13, 0.10), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
213
|
+
--shadow-hairline: none;
|
|
214
|
+
--shadow-focus: 0 0 0 2px var(--color-surface-background), 0 0 0 4px var(--color-focus);
|
|
215
|
+
--shadow-selection: inset 0 0 0 2px var(--color-brand-accent);
|
|
216
|
+
--shadow-glow: 0 4px 24px rgba(var(--color-brand-accent-rgb), 0.22);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
:root[data-palette="abyss"][data-theme="light"] {
|
|
221
|
+
color-scheme: light;
|
|
222
|
+
|
|
223
|
+
--color-surface-frame: #e8e3f3;
|
|
224
|
+
--color-surface-background: #f8f6fd;
|
|
225
|
+
--color-surface-subtle: #fdfcff;
|
|
226
|
+
--color-surface-elevated: #edeaf7;
|
|
227
|
+
--color-surface-hover: rgba(9, 0, 25, 0.044);
|
|
228
|
+
--color-surface-pressed: rgba(9, 0, 25, 0.088);
|
|
229
|
+
--color-surface-inset: rgba(9, 0, 25, 0.026);
|
|
230
|
+
--color-surface-glass: rgba(253, 252, 255, 0.90);
|
|
231
|
+
--color-surface-scrim: rgba(0, 0, 0, 0.6);
|
|
232
|
+
--color-surface-veil: rgba(232, 227, 243, 0.65);
|
|
233
|
+
--color-surface-inverse: #090019;
|
|
234
|
+
--color-text-primary: #1d132f;
|
|
235
|
+
--color-text-secondary: #403654;
|
|
236
|
+
--color-text-link-quiet: #5d2aa2;
|
|
237
|
+
--color-text-tertiary: #5b5767;
|
|
238
|
+
--color-text-muted: #8d83a2;
|
|
239
|
+
--color-text-on-accent: #ffffff;
|
|
240
|
+
--color-text-on-danger: #ffffff;
|
|
241
|
+
--color-text-on-scrim: #ffffff;
|
|
242
|
+
--color-text-inverse: #f8f6fd;
|
|
243
|
+
--color-brand-accent: #6b11da;
|
|
244
|
+
--color-brand-accent-hover: #6008c6;
|
|
245
|
+
--color-brand-accent-active: #5403af;
|
|
246
|
+
--color-brand-accent-ink: #6e00ce;
|
|
247
|
+
--color-brand-accent-soft: rgba(107, 17, 218, 0.125);
|
|
248
|
+
--color-brand-accent-rgb: 107, 17, 218;
|
|
249
|
+
--color-brand-accent-soft-strong: rgba(107, 17, 218, 0.32);
|
|
250
|
+
--color-border-default: #dfd9eb;
|
|
251
|
+
--color-border-hover: #cdc7dc;
|
|
252
|
+
--color-border-strong: #cdc7dc;
|
|
253
|
+
--color-border-strong-hover: #bcb5cc;
|
|
254
|
+
--color-border-control: #78708b;
|
|
255
|
+
--color-focus: #6b11da;
|
|
256
|
+
--color-feedback-success: #019472;
|
|
257
|
+
--color-feedback-success-ink: #074f35;
|
|
258
|
+
--color-feedback-warning: #c16f00;
|
|
259
|
+
--color-feedback-warning-ink: #8e4500;
|
|
260
|
+
--color-feedback-danger: #9a0028;
|
|
261
|
+
--color-feedback-danger-hover: #8c0024;
|
|
262
|
+
--color-feedback-danger-ink: #80041a;
|
|
263
|
+
--color-feedback-info: #287ac2;
|
|
264
|
+
--color-feedback-info-ink: #005a9d;
|
|
265
|
+
--color-feedback-success-soft: rgba(1, 148, 114, 0.12);
|
|
266
|
+
--color-feedback-success-line: rgba(1, 148, 114, 0.3);
|
|
267
|
+
--color-feedback-warning-soft: rgba(193, 111, 0, 0.12);
|
|
268
|
+
--color-feedback-warning-line: rgba(193, 111, 0, 0.3);
|
|
269
|
+
--color-feedback-danger-soft: rgba(154, 0, 40, 0.12);
|
|
270
|
+
--color-feedback-danger-line: rgba(154, 0, 40, 0.3);
|
|
271
|
+
--color-feedback-info-soft: rgba(40, 122, 194, 0.12);
|
|
272
|
+
--color-feedback-info-line: rgba(40, 122, 194, 0.3);
|
|
273
|
+
--color-data-1: #5877f4;
|
|
274
|
+
--color-data-2: #c96003;
|
|
275
|
+
--color-data-3: #007c5d;
|
|
276
|
+
--color-data-4: #9e8000;
|
|
277
|
+
--color-data-other: #7d7590;
|
|
278
|
+
--color-node-purple: #7150b3;
|
|
279
|
+
--color-node-purple-ink: #573394;
|
|
280
|
+
--color-node-cyan: #166b80;
|
|
281
|
+
--color-node-cyan-ink: #004f62;
|
|
282
|
+
--color-node-green: #2c7046;
|
|
283
|
+
--color-node-green-ink: #05532b;
|
|
284
|
+
--color-node-amber: #865a28;
|
|
285
|
+
--color-node-amber-ink: #683e03;
|
|
286
|
+
--color-node-rose: #984a55;
|
|
287
|
+
--color-node-rose-ink: #7a2f3c;
|
|
288
|
+
--color-node-blue: #4062a7;
|
|
289
|
+
--color-node-blue-ink: #264588;
|
|
290
|
+
--color-node-purple-fill: rgba(113, 80, 179, 0.12);
|
|
291
|
+
--color-node-purple-line: rgba(113, 80, 179, 0.38);
|
|
292
|
+
--color-node-purple-halo: rgba(113, 80, 179, 0.16);
|
|
293
|
+
--color-node-cyan-fill: rgba(22, 107, 128, 0.12);
|
|
294
|
+
--color-node-cyan-line: rgba(22, 107, 128, 0.38);
|
|
295
|
+
--color-node-cyan-halo: rgba(22, 107, 128, 0.16);
|
|
296
|
+
--color-node-green-fill: rgba(44, 112, 70, 0.12);
|
|
297
|
+
--color-node-green-line: rgba(44, 112, 70, 0.38);
|
|
298
|
+
--color-node-green-halo: rgba(44, 112, 70, 0.16);
|
|
299
|
+
--color-node-amber-fill: rgba(134, 90, 40, 0.12);
|
|
300
|
+
--color-node-amber-line: rgba(134, 90, 40, 0.38);
|
|
301
|
+
--color-node-amber-halo: rgba(134, 90, 40, 0.16);
|
|
302
|
+
--color-node-rose-fill: rgba(152, 74, 85, 0.12);
|
|
303
|
+
--color-node-rose-line: rgba(152, 74, 85, 0.38);
|
|
304
|
+
--color-node-rose-halo: rgba(152, 74, 85, 0.16);
|
|
305
|
+
--color-node-blue-fill: rgba(64, 98, 167, 0.12);
|
|
306
|
+
--color-node-blue-line: rgba(64, 98, 167, 0.38);
|
|
307
|
+
--color-node-blue-halo: rgba(64, 98, 167, 0.16);
|
|
308
|
+
--color-scrollbar: rgba(7, 8, 13, 0.2);
|
|
309
|
+
--color-scrollbar-hover: rgba(7, 8, 13, 0.32);
|
|
310
|
+
--shadow-xs: 0 1px 2px rgba(7, 8, 13, 0.06);
|
|
311
|
+
--shadow-sm: 0 2px 8px rgba(7, 8, 13, 0.07);
|
|
312
|
+
--shadow-md: 0 4px 12px rgba(7, 8, 13, 0.08), 0 1px 3px rgba(7, 8, 13, 0.06);
|
|
313
|
+
--shadow-lg: 0 8px 24px rgba(7, 8, 13, 0.12);
|
|
314
|
+
--shadow-xl: 0 16px 40px rgba(7, 8, 13, 0.16), 0 2px 8px rgba(7, 8, 13, 0.08);
|
|
315
|
+
--shadow-sheet: -16px 0 40px rgba(7, 8, 13, 0.16), -2px 0 8px rgba(7, 8, 13, 0.08);
|
|
316
|
+
--shadow-2xl: 0 16px 64px rgba(7, 8, 13, 0.20);
|
|
317
|
+
--shadow-card: 0 8px 24px rgba(7, 8, 13, 0.07);
|
|
318
|
+
--shadow-rim-lit: 0 4px 24px rgba(7, 8, 13, 0.10), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
319
|
+
--shadow-hairline: none;
|
|
320
|
+
--shadow-focus: 0 0 0 2px var(--color-surface-background), 0 0 0 4px var(--color-focus);
|
|
321
|
+
--shadow-selection: inset 0 0 0 2px var(--color-brand-accent);
|
|
322
|
+
--shadow-glow: 0 4px 24px rgba(var(--color-brand-accent-rgb), 0.22);
|
|
323
|
+
}
|