vscode-bauhaus-theme 1.0.1 → 1.2.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/.vscodeignore +1 -0
- package/CHANGELOG.md +12 -0
- package/README.md +26 -19
- package/package.json +8 -5
- package/palettes.json +26 -8
- package/scripts/build.js +49 -21
- package/scripts/terminal.js +131 -0
- package/src/accents.js +12 -2
- package/src/color.js +34 -15
- package/src/palettes.js +10 -5
- package/src/plist.js +42 -0
- package/src/syntax.js +3 -0
- package/src/workbench.js +12 -12
- package/terminal/iterm2/Bauhaus Dark.itermcolors +318 -0
- package/terminal/iterm2/Bauhaus Light.itermcolors +318 -0
- package/terminal/macos/Bauhaus Dark.terminal +54 -0
- package/terminal/macos/Bauhaus Light.terminal +54 -0
- package/themes/bauhaus-dark.json +16 -16
- package/themes/bauhaus-light.json +7 -7
- package/types/accents.d.ts +7 -5
- package/types/color.d.ts +10 -0
- package/types/palettes.d.ts +6 -1
- package/types/plist.d.ts +1 -0
- package/types/syntax.d.ts +5 -44
package/.vscodeignore
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
# [1.2.0](https://github.com/dmnsgn/vscode-bauhaus-theme/compare/v1.1.0...v1.2.0) (2026-10-26)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* use a dedicated caret color fitted to WCAG 2.1 non-text contrast ([9353c25](https://github.com/dmnsgn/vscode-bauhaus-theme/commit/9353c25d172f46f6a2f67e9bd9a627fd2dd511a4))
|
|
10
|
+
|
|
11
|
+
# [1.1.0](https://github.com/dmnsgn/vscode-bauhaus-theme/compare/v1.0.1...v1.1.0) (2026-10-01)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* update terminal colors ([7e64545](https://github.com/dmnsgn/vscode-bauhaus-theme/commit/7e64545df5ad05aae0e25ea694c6804cabe3302e))
|
|
16
|
+
|
|
5
17
|
## [1.0.1](https://github.com/dmnsgn/vscode-bauhaus-theme/compare/v1.0.0...v1.0.1) (2026-09-25)
|
|
6
18
|
|
|
7
19
|
# 1.0.0 (2026-09-25)
|
package/README.md
CHANGED
|
@@ -141,9 +141,16 @@ To switch, add the matching snippet to your `settings.json`.
|
|
|
141
141
|
|
|
142
142
|
</details>
|
|
143
143
|
|
|
144
|
+
## Terminal themes
|
|
145
|
+
|
|
146
|
+
The same colors for the macOS Terminal and iTerm2, generated from the VS Code themes.
|
|
147
|
+
|
|
148
|
+
- **Terminal**: download [Bauhaus Dark](terminal/macos/Bauhaus%20Dark.terminal) or [Bauhaus Light](terminal/macos/Bauhaus%20Light.terminal), then in `Settings… > Profiles`, click <kbd>…</kbd> > `Import…`.
|
|
149
|
+
- **iTerm2**: download [Bauhaus Dark](terminal/iterm2/Bauhaus%20Dark.itermcolors) or [Bauhaus Light](terminal/iterm2/Bauhaus%20Light.itermcolors), then in `Settings > Profiles > Colors`, pick `Color Presets… > Import…`.
|
|
150
|
+
|
|
144
151
|
## Palettes
|
|
145
152
|
|
|
146
|
-
Contrast is WCAG 2.0 against the editor background.
|
|
153
|
+
Contrast is WCAG 2.0 against the editor background. Terminal hues reach AA (bright: AAA) against the terminal background.
|
|
147
154
|
|
|
148
155
|
<details>
|
|
149
156
|
<summary>Colors and contrast ratios</summary>
|
|
@@ -160,15 +167,15 @@ Contrast is WCAG 2.0 against the editor background.
|
|
|
160
167
|
| fg.default | `#f2f2f2` |
|
|
161
168
|
| fg.muted | `#9a9a9a` |
|
|
162
169
|
|
|
163
|
-
| Hue | Ink |
|
|
164
|
-
| ------ | --------- |
|
|
165
|
-
| red | `#ff6963` | `#
|
|
166
|
-
| orange | `#ef7a04` | `#
|
|
167
|
-
| yellow | `#c59208` | `#
|
|
168
|
-
| green | `#8aa441` | `#
|
|
169
|
-
| cyan | `#15acaa` | `#
|
|
170
|
-
| blue | `#769ae2` | `#
|
|
171
|
-
| violet | `#d873e1` | `#
|
|
170
|
+
| Hue | Ink | Terminal | Terminal bright |
|
|
171
|
+
| ------ | --------- | --------- | --------------- |
|
|
172
|
+
| red | `#ff6963` | `#ff3e42` | `#ff8d84` |
|
|
173
|
+
| orange | `#ef7a04` | `#d66c03` | `#ff9240` |
|
|
174
|
+
| yellow | `#c59208` | `#b18201` | `#dea301` |
|
|
175
|
+
| green | `#8aa441` | `#79932d` | `#9cb855` |
|
|
176
|
+
| cyan | `#15acaa` | `#099997` | `#38bfbc` |
|
|
177
|
+
| blue | `#769ae2` | `#6689cf` | `#88adf6` |
|
|
178
|
+
| violet | `#d873e1` | `#c661cf` | `#ed86f6` |
|
|
172
179
|
|
|
173
180
|
| Syntax role | Color | Contrast on editor | WCAG 2.0 |
|
|
174
181
|
| ----------- | --------- | ------------------ | -------- |
|
|
@@ -200,15 +207,15 @@ Contrast is WCAG 2.0 against the editor background.
|
|
|
200
207
|
| fg.default | `#2b2119` |
|
|
201
208
|
| fg.muted | `#616161` |
|
|
202
209
|
|
|
203
|
-
| Hue | Ink |
|
|
204
|
-
| ------ | --------- |
|
|
205
|
-
| red | `#c5031e` | `#8f0113`
|
|
206
|
-
| orange | `#9a4c00` | `#703500`
|
|
207
|
-
| yellow | `#7e5c05` | `#5a4100`
|
|
208
|
-
| green | `#546903` | `#3b4b01`
|
|
209
|
-
| cyan | `#036d6c` | `#
|
|
210
|
-
| blue | `#4060a3` | `#254283`
|
|
211
|
-
| violet | `#9935a3` | `#790d83`
|
|
210
|
+
| Hue | Ink | Terminal | Terminal bright |
|
|
211
|
+
| ------ | --------- | --------- | --------------- |
|
|
212
|
+
| red | `#c5031e` | `#c5031f` | `#8f0113` |
|
|
213
|
+
| orange | `#9a4c00` | `#9a4c00` | `#703500` |
|
|
214
|
+
| yellow | `#7e5c05` | `#7e5c05` | `#5a4100` |
|
|
215
|
+
| green | `#546903` | `#546903` | `#3b4b01` |
|
|
216
|
+
| cyan | `#036d6c` | `#036d6c` | `#034d4d` |
|
|
217
|
+
| blue | `#4060a3` | `#4060a3` | `#254283` |
|
|
218
|
+
| violet | `#9935a3` | `#9935a2` | `#790d83` |
|
|
212
219
|
|
|
213
220
|
| Syntax role | Color | Contrast on editor | WCAG 2.0 |
|
|
214
221
|
| ----------- | --------- | ------------------ | -------- |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vscode-bauhaus-theme",
|
|
3
3
|
"displayName": "Bauhaus Theme",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Bauhaus primary colors on warm black and paper. Dark and light themes, every syntax color WCAG AA.",
|
|
6
6
|
"categories": [
|
|
7
7
|
"Themes"
|
|
@@ -39,10 +39,12 @@
|
|
|
39
39
|
"publisher": "dmnsgn",
|
|
40
40
|
"type": "module",
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "node scripts/build.js",
|
|
42
|
+
"build": "node scripts/build.js && node scripts/terminal.js",
|
|
43
|
+
"dev": "code --extensionDevelopmentPath=\"$PWD\" samples",
|
|
43
44
|
"icon": "node scripts/icon.js",
|
|
44
45
|
"reference": "node scripts/reference.js",
|
|
45
|
-
"screenshot": "node scripts/screenshot.js"
|
|
46
|
+
"screenshot": "node scripts/screenshot.js",
|
|
47
|
+
"terminal": "node scripts/terminal.js"
|
|
46
48
|
},
|
|
47
49
|
"contributes": {
|
|
48
50
|
"themes": [
|
|
@@ -60,10 +62,11 @@
|
|
|
60
62
|
},
|
|
61
63
|
"devDependencies": {
|
|
62
64
|
"@resvg/resvg-js": "^2.6.2",
|
|
63
|
-
"pex-color": "^2.3.0"
|
|
65
|
+
"pex-color": "^2.3.0",
|
|
66
|
+
"value-format-table": "^1.0.0"
|
|
64
67
|
},
|
|
65
68
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
69
|
+
"node": ">=24.0.0",
|
|
67
70
|
"npm": ">=10.5.1",
|
|
68
71
|
"snowdev": ">=3.0.0-alpha.3",
|
|
69
72
|
"vscode": "^1.90.0"
|
package/palettes.json
CHANGED
|
@@ -20,14 +20,23 @@
|
|
|
20
20
|
"blue": "#769ae2",
|
|
21
21
|
"violet": "#d873e1"
|
|
22
22
|
},
|
|
23
|
+
"ansi": {
|
|
24
|
+
"red": "#ff3e42",
|
|
25
|
+
"orange": "#d66c03",
|
|
26
|
+
"yellow": "#b18201",
|
|
27
|
+
"green": "#79932d",
|
|
28
|
+
"cyan": "#099997",
|
|
29
|
+
"blue": "#6689cf",
|
|
30
|
+
"violet": "#c661cf"
|
|
31
|
+
},
|
|
23
32
|
"bright": {
|
|
24
|
-
"red": "#
|
|
25
|
-
"orange": "#
|
|
26
|
-
"yellow": "#
|
|
27
|
-
"green": "#
|
|
28
|
-
"cyan": "#
|
|
29
|
-
"blue": "#
|
|
30
|
-
"violet": "#
|
|
33
|
+
"red": "#ff8d84",
|
|
34
|
+
"orange": "#ff9240",
|
|
35
|
+
"yellow": "#dea301",
|
|
36
|
+
"green": "#9cb855",
|
|
37
|
+
"cyan": "#38bfbc",
|
|
38
|
+
"blue": "#88adf6",
|
|
39
|
+
"violet": "#ed86f6"
|
|
31
40
|
},
|
|
32
41
|
"fill": {
|
|
33
42
|
"red": "#95171d",
|
|
@@ -155,12 +164,21 @@
|
|
|
155
164
|
"blue": "#4060a3",
|
|
156
165
|
"violet": "#9935a3"
|
|
157
166
|
},
|
|
167
|
+
"ansi": {
|
|
168
|
+
"red": "#c5031f",
|
|
169
|
+
"orange": "#9a4c00",
|
|
170
|
+
"yellow": "#7e5c05",
|
|
171
|
+
"green": "#546903",
|
|
172
|
+
"cyan": "#036d6c",
|
|
173
|
+
"blue": "#4060a3",
|
|
174
|
+
"violet": "#9935a2"
|
|
175
|
+
},
|
|
158
176
|
"bright": {
|
|
159
177
|
"red": "#8f0113",
|
|
160
178
|
"orange": "#703500",
|
|
161
179
|
"yellow": "#5a4100",
|
|
162
180
|
"green": "#3b4b01",
|
|
163
|
-
"cyan": "#
|
|
181
|
+
"cyan": "#034d4d",
|
|
164
182
|
"blue": "#254283",
|
|
165
183
|
"violet": "#790d83"
|
|
166
184
|
},
|
package/scripts/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
|
+
import { valueFormatTableAsMarkdown } from "value-format-table";
|
|
3
4
|
import reference from "../reference/theme-colors.json" with { type: "json" };
|
|
4
5
|
import { accents } from "../src/accents.js";
|
|
5
6
|
import { contrast } from "../src/color.js";
|
|
@@ -92,29 +93,56 @@ const report = palettes.map((palette) => {
|
|
|
92
93
|
});
|
|
93
94
|
await writeJson("palettes.json", report);
|
|
94
95
|
|
|
96
|
+
const table = (rows) => valueFormatTableAsMarkdown(rows).trim();
|
|
97
|
+
const code = (color) => `\`${color}\``;
|
|
98
|
+
|
|
95
99
|
const markdown = report
|
|
96
100
|
.map(
|
|
97
|
-
({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
({
|
|
102
|
+
type,
|
|
103
|
+
bg,
|
|
104
|
+
fg,
|
|
105
|
+
ink,
|
|
106
|
+
ansi,
|
|
107
|
+
bright,
|
|
108
|
+
syntax,
|
|
109
|
+
fills,
|
|
110
|
+
}) => `### ${capitalize(type)}
|
|
111
|
+
|
|
112
|
+
${table(
|
|
113
|
+
Object.entries({
|
|
114
|
+
...bg,
|
|
115
|
+
"fg.default": fg.default,
|
|
116
|
+
"fg.muted": fg.muted,
|
|
117
|
+
}).map(([surface, color]) => ({ Surface: surface, Color: code(color) })),
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
${table(
|
|
121
|
+
Object.keys(ink).map((hue) => ({
|
|
122
|
+
Hue: hue,
|
|
123
|
+
Ink: code(ink[hue]),
|
|
124
|
+
Terminal: code(ansi[hue]),
|
|
125
|
+
"Terminal bright": code(bright[hue]),
|
|
126
|
+
})),
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
${table(
|
|
130
|
+
syntax.map(({ role, color, ratio, grade }) => ({
|
|
131
|
+
"Syntax role": role,
|
|
132
|
+
Color: code(color),
|
|
133
|
+
"Contrast on editor": `${ratio}:1`,
|
|
134
|
+
"WCAG 2.0": grade,
|
|
135
|
+
})),
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
${table(
|
|
139
|
+
fills.map(({ hue, color, text, ratio, grade }) => ({
|
|
140
|
+
Fill: `${hue} ${code(color)}`,
|
|
141
|
+
Text: code(text),
|
|
142
|
+
Contrast: `${ratio}:1`,
|
|
143
|
+
"WCAG 2.0": grade,
|
|
144
|
+
})),
|
|
145
|
+
)}
|
|
118
146
|
`,
|
|
119
147
|
)
|
|
120
148
|
.join("\n");
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { flatten, toRgb } from "../src/color.js";
|
|
4
|
+
import { plist } from "../src/plist.js";
|
|
5
|
+
|
|
6
|
+
const root = new URL("../", import.meta.url);
|
|
7
|
+
const ANSI = [
|
|
8
|
+
"Black",
|
|
9
|
+
"Red",
|
|
10
|
+
"Green",
|
|
11
|
+
"Yellow",
|
|
12
|
+
"Blue",
|
|
13
|
+
"Magenta",
|
|
14
|
+
"Cyan",
|
|
15
|
+
"White",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// The VS Code themes are the source: terminal colors are read back from the build output.
|
|
19
|
+
function scheme(colors) {
|
|
20
|
+
const background = colors["terminal.background"];
|
|
21
|
+
const foreground = colors["terminal.foreground"];
|
|
22
|
+
return {
|
|
23
|
+
background,
|
|
24
|
+
foreground,
|
|
25
|
+
cursor: colors["terminalCursor.foreground"],
|
|
26
|
+
cursorText: background,
|
|
27
|
+
// Neither terminal blends a translucent selection like VS Code does.
|
|
28
|
+
selection: flatten(colors["terminal.selectionBackground"], background),
|
|
29
|
+
link: colors["textLink.foreground"],
|
|
30
|
+
ansi: [
|
|
31
|
+
...ANSI.map((name) => colors[`terminal.ansi${name}`]),
|
|
32
|
+
...ANSI.map((name) => colors[`terminal.ansiBright${name}`]),
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// iTerm2
|
|
38
|
+
function itermColor(hex) {
|
|
39
|
+
const [r, g, b] = toRgb(hex);
|
|
40
|
+
return {
|
|
41
|
+
"Alpha Component": 1,
|
|
42
|
+
"Blue Component": b,
|
|
43
|
+
"Color Space": "sRGB",
|
|
44
|
+
"Green Component": g,
|
|
45
|
+
"Red Component": r,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const iterm = (s) => ({
|
|
50
|
+
...Object.fromEntries(
|
|
51
|
+
s.ansi.map((hex, i) => [`Ansi ${i} Color`, itermColor(hex)]),
|
|
52
|
+
),
|
|
53
|
+
"Background Color": itermColor(s.background),
|
|
54
|
+
"Bold Color": itermColor(s.foreground),
|
|
55
|
+
"Cursor Color": itermColor(s.cursor),
|
|
56
|
+
"Cursor Text Color": itermColor(s.cursorText),
|
|
57
|
+
"Foreground Color": itermColor(s.foreground),
|
|
58
|
+
"Link Color": itermColor(s.link),
|
|
59
|
+
"Selected Text Color": itermColor(s.foreground),
|
|
60
|
+
"Selection Color": itermColor(s.selection),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Terminal.app stores each color as an NSKeyedArchiver NSColor. The XML archive format is
|
|
64
|
+
// accepted, and NSID 7 resolves to sRGB without embedding its ICC profile. NSRGB is the
|
|
65
|
+
// fallback for readers ignoring NSCustomColorSpace.
|
|
66
|
+
const uid = (id) => ({ CF$UID: BigInt(id) });
|
|
67
|
+
const classDef = (name) => ({ $classes: [name, "NSObject"], $classname: name });
|
|
68
|
+
|
|
69
|
+
function archive(hex) {
|
|
70
|
+
const rgb = toRgb(hex).join(" ");
|
|
71
|
+
return Buffer.from(
|
|
72
|
+
plist({
|
|
73
|
+
$archiver: "NSKeyedArchiver",
|
|
74
|
+
$objects: [
|
|
75
|
+
"$null",
|
|
76
|
+
{
|
|
77
|
+
$class: uid(4),
|
|
78
|
+
NSColorSpace: 1n,
|
|
79
|
+
NSComponents: Buffer.from(`${rgb} 1`),
|
|
80
|
+
NSCustomColorSpace: uid(2),
|
|
81
|
+
NSRGB: Buffer.from(`${rgb}\0`),
|
|
82
|
+
},
|
|
83
|
+
{ $class: uid(3), NSID: 7n },
|
|
84
|
+
classDef("NSColorSpace"),
|
|
85
|
+
classDef("NSColor"),
|
|
86
|
+
],
|
|
87
|
+
$top: { root: uid(1) },
|
|
88
|
+
$version: 100_000n,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const terminal = (s, name) => ({
|
|
94
|
+
name,
|
|
95
|
+
type: "Window Settings",
|
|
96
|
+
ProfileCurrentVersion: 2.09,
|
|
97
|
+
BackgroundColor: archive(s.background),
|
|
98
|
+
TextColor: archive(s.foreground),
|
|
99
|
+
TextBoldColor: archive(s.foreground),
|
|
100
|
+
CursorColor: archive(s.cursor),
|
|
101
|
+
SelectionColor: archive(s.selection),
|
|
102
|
+
...Object.fromEntries(
|
|
103
|
+
s.ansi.map((hex, i) => [
|
|
104
|
+
`ANSI${i < 8 ? "" : "Bright"}${ANSI[i % 8]}Color`,
|
|
105
|
+
archive(hex),
|
|
106
|
+
]),
|
|
107
|
+
),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const TARGETS = [
|
|
111
|
+
{ dir: "terminal/iterm2", extension: "itermcolors", format: iterm },
|
|
112
|
+
{ dir: "terminal/macos", extension: "terminal", format: terminal },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
await rm(new URL("terminal", root), { recursive: true, force: true });
|
|
116
|
+
for (const { dir } of TARGETS)
|
|
117
|
+
await mkdir(new URL(dir, root), { recursive: true });
|
|
118
|
+
|
|
119
|
+
const pkg = JSON.parse(await readFile(new URL("package.json", root)));
|
|
120
|
+
for (const { label, path } of pkg.contributes.themes) {
|
|
121
|
+
const { colors } = JSON.parse(await readFile(new URL(path, root)));
|
|
122
|
+
const s = scheme(colors);
|
|
123
|
+
for (const { dir, extension, format } of TARGETS)
|
|
124
|
+
await writeFile(
|
|
125
|
+
new URL(`${dir}/${label}.${extension}`, root),
|
|
126
|
+
plist(format(s, label)),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
console.log(
|
|
130
|
+
`${pkg.contributes.themes.length * TARGETS.length} terminal themes written`,
|
|
131
|
+
);
|
package/src/accents.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { fit } from "./color.js";
|
|
2
|
+
import { CHROMA } from "./palettes.js";
|
|
3
|
+
|
|
1
4
|
/** @typedef {import("./palettes.js").Palette} Palette */
|
|
2
5
|
|
|
3
6
|
/**
|
|
@@ -15,7 +18,8 @@
|
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* @typedef {object} Accent
|
|
18
|
-
* @property {string} focus Focus border,
|
|
21
|
+
* @property {string} focus Focus border, progress, links.
|
|
22
|
+
* @property {string} caret Editor and terminal cursors.
|
|
19
23
|
* @property {ActiveItems} active
|
|
20
24
|
* @property {Pair} statusBar
|
|
21
25
|
* @property {Pair} debugging
|
|
@@ -29,6 +33,9 @@ const fill = (p, hue) => ({
|
|
|
29
33
|
background: p.fill[hue],
|
|
30
34
|
foreground: p.onFill[hue],
|
|
31
35
|
});
|
|
36
|
+
// WCAG 2.1 non-text contrast, on both surfaces a cursor can blink on.
|
|
37
|
+
const caret = (p, hue) =>
|
|
38
|
+
fit(p.fill[hue], [p.bg.editor, p.bg.chrome], 3, { chroma: CHROMA });
|
|
32
39
|
const chrome = (p) => ({ background: p.bg.chrome, foreground: p.fg.muted });
|
|
33
40
|
const active = (p, activity, panel = activity) => ({
|
|
34
41
|
tab: p.ink.red,
|
|
@@ -42,12 +49,13 @@ const active = (p, activity, panel = activity) => ({
|
|
|
42
49
|
* @property {function(Palette): Accent} accent
|
|
43
50
|
*/
|
|
44
51
|
|
|
45
|
-
/** @type {
|
|
52
|
+
/** @type {Record<string, AccentStyle>} */
|
|
46
53
|
export const accents = {
|
|
47
54
|
blocks: {
|
|
48
55
|
name: "Blocks",
|
|
49
56
|
accent: (p) => ({
|
|
50
57
|
focus: p.ink.red,
|
|
58
|
+
caret: caret(p, "red"),
|
|
51
59
|
active: active(p, "red"),
|
|
52
60
|
statusBar: fill(p, "red"),
|
|
53
61
|
debugging: fill(p, "yellow"),
|
|
@@ -61,6 +69,7 @@ export const accents = {
|
|
|
61
69
|
name: "Restrained",
|
|
62
70
|
accent: (p) => ({
|
|
63
71
|
focus: p.ink.red,
|
|
72
|
+
caret: caret(p, "red"),
|
|
64
73
|
active: active(p, "red"),
|
|
65
74
|
statusBar: chrome(p),
|
|
66
75
|
debugging: fill(p, "red"),
|
|
@@ -74,6 +83,7 @@ export const accents = {
|
|
|
74
83
|
name: "Tricolor",
|
|
75
84
|
accent: (p) => ({
|
|
76
85
|
focus: p.ink.red,
|
|
86
|
+
caret: caret(p, "red"),
|
|
77
87
|
active: active(p, "yellow", "blue"),
|
|
78
88
|
statusBar: fill(p, "blue"),
|
|
79
89
|
debugging: fill(p, "red"),
|
package/src/color.js
CHANGED
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
toOklch as colorToOklch,
|
|
10
10
|
} from "pex-color";
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const format = (color) => toHex(color).toLowerCase();
|
|
12
|
+
const toLowerCaseHex = (color) => toHex(color).toLowerCase();
|
|
14
13
|
const lerp = (a, b, t) => a + (b - a) * t;
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -21,7 +20,7 @@ const lerp = (a, b, t) => a + (b - a) * t;
|
|
|
21
20
|
*/
|
|
22
21
|
export function luminance(hex) {
|
|
23
22
|
// WCAG 2.0's 0.03928 linearization threshold and sRGB's 0.04045 give the same result on 8-bit channels.
|
|
24
|
-
const [r, g, b] = toLinear(
|
|
23
|
+
const [r, g, b] = toLinear(fromHex([], hex));
|
|
25
24
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -37,19 +36,25 @@ export function contrast(a, b) {
|
|
|
37
36
|
return (hi + 0.05) / (lo + 0.05);
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Convert to sRGB channels.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} hex
|
|
43
|
+
* @returns {[number, number, number]} Red, green, blue from 0 to 1.
|
|
44
|
+
*/
|
|
45
|
+
export const toRgb = (hex) => fromHex([], hex);
|
|
46
|
+
|
|
40
47
|
/**
|
|
41
48
|
* Convert to OKLCH.
|
|
42
49
|
*
|
|
43
50
|
* @param {string} hex
|
|
44
51
|
* @returns {[number, number, number]} Lightness, chroma, hue in turns.
|
|
45
52
|
*/
|
|
46
|
-
export const toOklch = (hex) => colorToOklch(
|
|
53
|
+
export const toOklch = (hex) => colorToOklch(fromHex([], hex));
|
|
47
54
|
|
|
48
55
|
// Tolerance applies to linear values: gamma encoding would make it ~13x stricter near 0.
|
|
49
56
|
const inGamut = (color) =>
|
|
50
|
-
toLinear(color)
|
|
51
|
-
.slice(0, 3)
|
|
52
|
-
.every((v) => v >= -1e-4 && v <= 1 + 1e-4);
|
|
57
|
+
toLinear(color).every((v) => v >= -1e-4 && v <= 1 + 1e-4);
|
|
53
58
|
|
|
54
59
|
/**
|
|
55
60
|
* Convert from OKLCH, reducing chroma until the color fits in sRGB.
|
|
@@ -58,9 +63,9 @@ const inGamut = (color) =>
|
|
|
58
63
|
* @returns {string}
|
|
59
64
|
*/
|
|
60
65
|
export function fromOklch([L, C, H]) {
|
|
61
|
-
const color =
|
|
66
|
+
const color = [];
|
|
62
67
|
while (!inGamut(colorFromOklch(color, L, C, H))) C = Math.max(0, C - 0.002);
|
|
63
|
-
return
|
|
68
|
+
return toLowerCaseHex(color);
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
@@ -72,10 +77,10 @@ export function fromOklch([L, C, H]) {
|
|
|
72
77
|
* @returns {string}
|
|
73
78
|
*/
|
|
74
79
|
export function mix(a, b, t = 0.5) {
|
|
75
|
-
const [La, Aa, Ba] = toOklab(
|
|
76
|
-
const [Lb, Ab, Bb] = toOklab(
|
|
77
|
-
return
|
|
78
|
-
fromOklab(
|
|
80
|
+
const [La, Aa, Ba] = toOklab(fromHex([], a));
|
|
81
|
+
const [Lb, Ab, Bb] = toOklab(fromHex([], b));
|
|
82
|
+
return toLowerCaseHex(
|
|
83
|
+
fromOklab([], lerp(La, Lb, t), lerp(Aa, Ab, t), lerp(Ba, Bb, t)),
|
|
79
84
|
);
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -156,7 +161,21 @@ export const mostContrasted = (bg, candidates) =>
|
|
|
156
161
|
* @returns {string} #RRGGBBAA, or #RRGGBB when opaque.
|
|
157
162
|
*/
|
|
158
163
|
export function alpha(hex, opacity) {
|
|
159
|
-
const color =
|
|
164
|
+
const color = fromHex([], hex);
|
|
160
165
|
color[3] = opacity;
|
|
161
|
-
return
|
|
166
|
+
return toLowerCaseHex(color);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Composite a translucent color over an opaque background, in sRGB as browsers
|
|
171
|
+
* do.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} hex #RRGGBBAA or #RRGGBB.
|
|
174
|
+
* @param {string} bg
|
|
175
|
+
* @returns {string} #RRGGBB.
|
|
176
|
+
*/
|
|
177
|
+
export function flatten(hex, bg) {
|
|
178
|
+
const [r, g, b, a] = fromHex(create(), hex);
|
|
179
|
+
const [R, G, B] = fromHex([], bg);
|
|
180
|
+
return toLowerCaseHex([lerp(R, r, a), lerp(G, g, a), lerp(B, b, a), 1]);
|
|
162
181
|
}
|
package/src/palettes.js
CHANGED
|
@@ -34,7 +34,7 @@ const mapValues = (object, fn) =>
|
|
|
34
34
|
// keeps the tint.
|
|
35
35
|
// Light surfaces have no source match: a paper tint at the same hue.
|
|
36
36
|
// Lines (borders, guides, selected rows) keep the grey-40 on grey-22 step: 1.53:1.
|
|
37
|
-
const
|
|
37
|
+
const WARM_HUE = toOklch(black)[2];
|
|
38
38
|
const darkEditor = fit(black, black, 1.23);
|
|
39
39
|
const lightEditor = fromOklch([0.945, 0.015, WARM_HUE]);
|
|
40
40
|
|
|
@@ -58,7 +58,8 @@ const SURFACES = {
|
|
|
58
58
|
const AA = 4.5;
|
|
59
59
|
const AAA = 7;
|
|
60
60
|
// Source blue, green and cyan are greyish (OKLCH chroma ~0.07): too close to the text color once dark.
|
|
61
|
-
const
|
|
61
|
+
export const CHROMA = 1.6;
|
|
62
|
+
const INK = { exact: true, chroma: CHROMA };
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* @typedef {object} Palette
|
|
@@ -66,7 +67,8 @@ const INK = { exact: true, chroma: 1.6 };
|
|
|
66
67
|
* @property {"dark" | "light"} type
|
|
67
68
|
* @property {Backgrounds} bg
|
|
68
69
|
* @property {Foregrounds} fg
|
|
69
|
-
* @property {Hues} ink Hues for text
|
|
70
|
+
* @property {Hues} ink Hues for text on backgrounds.
|
|
71
|
+
* @property {Hues} ansi Terminal hues.
|
|
70
72
|
* @property {Hues} bright Terminal bright variants.
|
|
71
73
|
* @property {Fills} fill Hues as solid surfaces.
|
|
72
74
|
* @property {Fills} onFill Text on each fill.
|
|
@@ -118,6 +120,7 @@ function source(type) {
|
|
|
118
120
|
bg: { editor, chrome, line },
|
|
119
121
|
fg: { default: text, muted },
|
|
120
122
|
ink: HUES,
|
|
123
|
+
ansi: HUES,
|
|
121
124
|
bright: mapValues(HUES, (hue) => mix(hue, text, 0.3)),
|
|
122
125
|
fill: FILLS,
|
|
123
126
|
onFill: mapValues(FILLS, (fill) => mostContrasted(fill, [white, black])),
|
|
@@ -127,7 +130,8 @@ function source(type) {
|
|
|
127
130
|
/**
|
|
128
131
|
* Source hues, saturated, at the lightness closest to the backgrounds that
|
|
129
132
|
* reaches WCAG 2.0 AA against editor and chrome. Equal contrast keeps hues
|
|
130
|
-
* apart from the text color on both sides.
|
|
133
|
+
* apart from the text color on both sides. Terminal hues reach AA (bright: AAA)
|
|
134
|
+
* against the terminal background only.
|
|
131
135
|
*
|
|
132
136
|
* @param {"dark" | "light"} type
|
|
133
137
|
* @returns {Palette}
|
|
@@ -139,7 +143,8 @@ function wcag(type) {
|
|
|
139
143
|
...base,
|
|
140
144
|
fg: { ...base.fg, muted: fit(base.fg.muted, backgrounds, AA) },
|
|
141
145
|
ink: mapValues(HUES, (hue) => fit(hue, backgrounds, AA, INK)),
|
|
142
|
-
|
|
146
|
+
ansi: mapValues(HUES, (hue) => fit(hue, base.bg.chrome, AA, INK)),
|
|
147
|
+
bright: mapValues(HUES, (hue) => fit(hue, base.bg.chrome, AAA, INK)),
|
|
143
148
|
};
|
|
144
149
|
}
|
|
145
150
|
|
package/src/plist.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const escape = (text) =>
|
|
2
|
+
text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
3
|
+
|
|
4
|
+
function node(value, depth) {
|
|
5
|
+
const pad = "\t".repeat(depth);
|
|
6
|
+
if (Buffer.isBuffer(value))
|
|
7
|
+
return `${pad}<data>${value.toString("base64")}</data>`;
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return [
|
|
10
|
+
`${pad}<array>`,
|
|
11
|
+
...value.map((item) => node(item, depth + 1)),
|
|
12
|
+
`${pad}</array>`,
|
|
13
|
+
].join("\n");
|
|
14
|
+
if (typeof value === "object")
|
|
15
|
+
return [
|
|
16
|
+
`${pad}<dict>`,
|
|
17
|
+
...Object.entries(value).flatMap(([key, item]) => [
|
|
18
|
+
`${pad}\t<key>${escape(key)}</key>`,
|
|
19
|
+
node(item, depth + 1),
|
|
20
|
+
]),
|
|
21
|
+
`${pad}</dict>`,
|
|
22
|
+
].join("\n");
|
|
23
|
+
if (typeof value === "boolean") return `${pad}<${value}/>`;
|
|
24
|
+
if (typeof value === "bigint") return `${pad}<integer>${value}</integer>`;
|
|
25
|
+
return typeof value === "number"
|
|
26
|
+
? `${pad}<real>${value}</real>`
|
|
27
|
+
: `${pad}<string>${escape(String(value))}</string>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Serialize to an XML property list.
|
|
32
|
+
*
|
|
33
|
+
* @param {unknown} value Objects become dicts, Buffers become data, BigInts
|
|
34
|
+
* become integers and numbers become reals.
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export const plist = (value) => `<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
39
|
+
<plist version="1.0">
|
|
40
|
+
${node(value, 0)}
|
|
41
|
+
</plist>
|
|
42
|
+
`;
|
package/src/syntax.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* tags, constants) → violet, other hues keep their role.
|
|
7
7
|
*
|
|
8
8
|
* @param {Palette} p
|
|
9
|
+
* @returns {Record<string, string>}
|
|
9
10
|
*/
|
|
10
11
|
export const roles = (p) => ({
|
|
11
12
|
text: p.fg.default,
|
|
@@ -43,6 +44,7 @@ const jsonKeys = (colors) =>
|
|
|
43
44
|
* TextMate rules. Later rules win on equal specificity.
|
|
44
45
|
*
|
|
45
46
|
* @param {Palette} p
|
|
47
|
+
* @returns {object[]}
|
|
46
48
|
*/
|
|
47
49
|
export function tokenColors(p) {
|
|
48
50
|
const r = roles(p);
|
|
@@ -251,6 +253,7 @@ export function tokenColors(p) {
|
|
|
251
253
|
* mapping.
|
|
252
254
|
*
|
|
253
255
|
* @param {Palette} p
|
|
256
|
+
* @returns {Record<string, string | object>}
|
|
254
257
|
*/
|
|
255
258
|
export function semanticTokenColors(p) {
|
|
256
259
|
const r = roles(p);
|