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 CHANGED
@@ -9,3 +9,4 @@ palettes.json
9
9
  .npmignore
10
10
  icon.svg
11
11
  screenshot.*
12
+ terminal
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 | Bright (terminal) |
164
- | ------ | --------- | ----------------- |
165
- | red | `#ff6963` | `#ffaba3` |
166
- | orange | `#ef7a04` | `#feaf7b` |
167
- | yellow | `#c59208` | `#f6b606` |
168
- | green | `#8aa441` | `#afcc69` |
169
- | cyan | `#15acaa` | `#52d3d1` |
170
- | blue | `#769ae2` | `#a2c1fe` |
171
- | violet | `#d873e1` | `#f8a2ff` |
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 | Bright (terminal) |
204
- | ------ | --------- | ----------------- |
205
- | red | `#c5031e` | `#8f0113` |
206
- | orange | `#9a4c00` | `#703500` |
207
- | yellow | `#7e5c05` | `#5a4100` |
208
- | green | `#546903` | `#3b4b01` |
209
- | cyan | `#036d6c` | `#034d4c` |
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.1",
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": ">=22.0.0",
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": "#ffaba3",
25
- "orange": "#feaf7b",
26
- "yellow": "#f6b606",
27
- "green": "#afcc69",
28
- "cyan": "#52d3d1",
29
- "blue": "#a2c1fe",
30
- "violet": "#f8a2ff"
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": "#034d4c",
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
- ({ type, bg, fg, ink, bright, syntax, fills }) => `### ${capitalize(type)}
98
-
99
- | Surface | Color |
100
- | --- | --- |
101
- ${Object.entries({ ...bg, "fg.default": fg.default, "fg.muted": fg.muted })
102
- .map(([key, color]) => `| ${key} | \`${color}\` |`)
103
- .join("\n")}
104
-
105
- | Hue | Ink | Bright (terminal) |
106
- | --- | --- | --- |
107
- ${Object.keys(ink)
108
- .map((hue) => `| ${hue} | \`${ink[hue]}\` | \`${bright[hue]}\` |`)
109
- .join("\n")}
110
-
111
- | Syntax role | Color | Contrast on editor | WCAG 2.0 |
112
- | --- | --- | --- | --- |
113
- ${syntax.map(({ role, color, ratio, grade }) => `| ${role} | \`${color}\` | ${ratio}:1 | ${grade} |`).join("\n")}
114
-
115
- | Fill | Text | Contrast | WCAG 2.0 |
116
- | --- | --- | --- | --- |
117
- ${fills.map(({ hue, color, text, ratio, grade }) => `| ${hue} \`${color}\` | \`${text}\` | ${ratio}:1 | ${grade} |`).join("\n")}
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 {Object<string, AccentStyle>} */
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 parse = (hex) => fromHex(create(), hex);
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(parse(hex));
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(parse(hex)).slice(0, 3);
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 = create();
66
+ const color = [];
62
67
  while (!inGamut(colorFromOklch(color, L, C, H))) C = Math.max(0, C - 0.002);
63
- return format(color);
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(parse(a));
76
- const [Lb, Ab, Bb] = toOklab(parse(b));
77
- return format(
78
- fromOklab(create(), lerp(La, Lb, t), lerp(Aa, Ab, t), lerp(Ba, Bb, t)),
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 = parse(hex);
164
+ const color = fromHex([], hex);
160
165
  color[3] = opacity;
161
- return format(color);
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 [, , WARM_HUE] = toOklch(black);
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
- bright: mapValues(HUES, (hue) => fit(hue, backgrounds, AAA, INK)),
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
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": ink.red,
467
- "terminal.ansiGreen": ink.green,
468
- "terminal.ansiYellow": ink.yellow,
469
- "terminal.ansiBlue": ink.blue,
470
- "terminal.ansiMagenta": ink.violet,
471
- "terminal.ansiCyan": ink.cyan,
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,