vscode-bauhaus-theme 1.0.1 → 1.1.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 +6 -0
- package/README.md +26 -19
- package/package.json +7 -5
- package/palettes.json +26 -8
- package/scripts/build.js +49 -21
- package/scripts/terminal.js +131 -0
- package/src/accents.js +1 -1
- package/src/color.js +34 -15
- package/src/palettes.js +7 -3
- package/src/plist.js +42 -0
- package/src/syntax.js +3 -0
- package/src/workbench.js +8 -8
- 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 +12 -12
- package/themes/bauhaus-light.json +3 -3
- package/types/accents.d.ts +2 -4
- package/types/color.d.ts +10 -0
- package/types/palettes.d.ts +4 -0
- package/types/plist.d.ts +1 -0
- package/types/syntax.d.ts +5 -44
package/.vscodeignore
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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.1.0](https://github.com/dmnsgn/vscode-bauhaus-theme/compare/v1.0.1...v1.1.0) (2026-10-01)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* update terminal colors ([7e64545](https://github.com/dmnsgn/vscode-bauhaus-theme/commit/7e64545df5ad05aae0e25ea694c6804cabe3302e))
|
|
10
|
+
|
|
5
11
|
## [1.0.1](https://github.com/dmnsgn/vscode-bauhaus-theme/compare/v1.0.0...v1.0.1) (2026-09-25)
|
|
6
12
|
|
|
7
13
|
# 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.1.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,11 @@
|
|
|
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
43
|
"icon": "node scripts/icon.js",
|
|
44
44
|
"reference": "node scripts/reference.js",
|
|
45
|
-
"screenshot": "node scripts/screenshot.js"
|
|
45
|
+
"screenshot": "node scripts/screenshot.js",
|
|
46
|
+
"terminal": "node scripts/terminal.js"
|
|
46
47
|
},
|
|
47
48
|
"contributes": {
|
|
48
49
|
"themes": [
|
|
@@ -60,10 +61,11 @@
|
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@resvg/resvg-js": "^2.6.2",
|
|
63
|
-
"pex-color": "^2.3.0"
|
|
64
|
+
"pex-color": "^2.3.0",
|
|
65
|
+
"value-format-table": "^1.0.0"
|
|
64
66
|
},
|
|
65
67
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
68
|
+
"node": ">=24.0.0",
|
|
67
69
|
"npm": ">=10.5.1",
|
|
68
70
|
"snowdev": ">=3.0.0-alpha.3",
|
|
69
71
|
"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
|
@@ -42,7 +42,7 @@ const active = (p, activity, panel = activity) => ({
|
|
|
42
42
|
* @property {function(Palette): Accent} accent
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
/** @type {
|
|
45
|
+
/** @type {Record<string, AccentStyle>} */
|
|
46
46
|
export const accents = {
|
|
47
47
|
blocks: {
|
|
48
48
|
name: "Blocks",
|
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
|
|
|
@@ -67,6 +67,7 @@ const INK = { exact: true, chroma: 1.6 };
|
|
|
67
67
|
* @property {Backgrounds} bg
|
|
68
68
|
* @property {Foregrounds} fg
|
|
69
69
|
* @property {Hues} ink Hues for text and marks on backgrounds.
|
|
70
|
+
* @property {Hues} ansi Terminal hues.
|
|
70
71
|
* @property {Hues} bright Terminal bright variants.
|
|
71
72
|
* @property {Fills} fill Hues as solid surfaces.
|
|
72
73
|
* @property {Fills} onFill Text on each fill.
|
|
@@ -118,6 +119,7 @@ function source(type) {
|
|
|
118
119
|
bg: { editor, chrome, line },
|
|
119
120
|
fg: { default: text, muted },
|
|
120
121
|
ink: HUES,
|
|
122
|
+
ansi: HUES,
|
|
121
123
|
bright: mapValues(HUES, (hue) => mix(hue, text, 0.3)),
|
|
122
124
|
fill: FILLS,
|
|
123
125
|
onFill: mapValues(FILLS, (fill) => mostContrasted(fill, [white, black])),
|
|
@@ -127,7 +129,8 @@ function source(type) {
|
|
|
127
129
|
/**
|
|
128
130
|
* Source hues, saturated, at the lightness closest to the backgrounds that
|
|
129
131
|
* reaches WCAG 2.0 AA against editor and chrome. Equal contrast keeps hues
|
|
130
|
-
* apart from the text color on both sides.
|
|
132
|
+
* apart from the text color on both sides. Terminal hues reach AA (bright: AAA)
|
|
133
|
+
* against the terminal background only.
|
|
131
134
|
*
|
|
132
135
|
* @param {"dark" | "light"} type
|
|
133
136
|
* @returns {Palette}
|
|
@@ -139,7 +142,8 @@ function wcag(type) {
|
|
|
139
142
|
...base,
|
|
140
143
|
fg: { ...base.fg, muted: fit(base.fg.muted, backgrounds, AA) },
|
|
141
144
|
ink: mapValues(HUES, (hue) => fit(hue, backgrounds, AA, INK)),
|
|
142
|
-
|
|
145
|
+
ansi: mapValues(HUES, (hue) => fit(hue, base.bg.chrome, AA, INK)),
|
|
146
|
+
bright: mapValues(HUES, (hue) => fit(hue, base.bg.chrome, AAA, INK)),
|
|
143
147
|
};
|
|
144
148
|
}
|
|
145
149
|
|
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);
|
package/src/workbench.js
CHANGED
|
@@ -13,7 +13,7 @@ import { roles } from "./syntax.js";
|
|
|
13
13
|
* @returns {Record<string, string>}
|
|
14
14
|
*/
|
|
15
15
|
export function workbench(p, a) {
|
|
16
|
-
const { bg, fg, ink, fill } = p;
|
|
16
|
+
const { bg, fg, ink, ansi, fill } = p;
|
|
17
17
|
const dark = p.type === "dark";
|
|
18
18
|
const r = roles(p);
|
|
19
19
|
|
|
@@ -450,7 +450,7 @@ export function workbench(p, a) {
|
|
|
450
450
|
"keybindingLabel.bottomBorder": bg.line,
|
|
451
451
|
"keybindingTable.rowsBackground": shade(0.03),
|
|
452
452
|
|
|
453
|
-
// Terminal
|
|
453
|
+
// Terminal: palette ansi and bright hues are fitted against bg.chrome
|
|
454
454
|
"terminal.background": bg.chrome,
|
|
455
455
|
"terminal.foreground": fg.default,
|
|
456
456
|
"terminal.border": bg.line,
|
|
@@ -463,12 +463,12 @@ export function workbench(p, a) {
|
|
|
463
463
|
"terminalCommandDecoration.successBackground": status.success,
|
|
464
464
|
"terminalCommandDecoration.errorBackground": status.error,
|
|
465
465
|
"terminal.ansiBlack": dark ? bg.line : fg.default,
|
|
466
|
-
"terminal.ansiRed":
|
|
467
|
-
"terminal.ansiGreen":
|
|
468
|
-
"terminal.ansiYellow":
|
|
469
|
-
"terminal.ansiBlue":
|
|
470
|
-
"terminal.ansiMagenta":
|
|
471
|
-
"terminal.ansiCyan":
|
|
466
|
+
"terminal.ansiRed": ansi.red,
|
|
467
|
+
"terminal.ansiGreen": ansi.green,
|
|
468
|
+
"terminal.ansiYellow": ansi.yellow,
|
|
469
|
+
"terminal.ansiBlue": ansi.blue,
|
|
470
|
+
"terminal.ansiMagenta": ansi.violet,
|
|
471
|
+
"terminal.ansiCyan": ansi.cyan,
|
|
472
472
|
"terminal.ansiWhite": dark ? fg.muted : bg.line,
|
|
473
473
|
"terminal.ansiBrightBlack": fg.muted,
|
|
474
474
|
"terminal.ansiBrightRed": p.bright.red,
|