From 7cd3bf69056196f0f13722cbde311f04f34baec0 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:55:03 -0700 Subject: [PATCH 1/6] refactor: reorganize source into src, scripts, and test Give each directory a single role: src/ for published package source, scripts/ for build tooling, and test/ for the test harness. - Split color-p3.ts into a src/color/ module (srgb, p3, and a barrel). - Split palette.ts into src/palettes.ts and per-variant src/roles/. - Rename theme.ts/zed-theme.ts (and their makeTheme/makeZedThemeFamily exports) to createTheme.ts/createZedTheme.ts. - Move the preview builder to src/previews/ and add a scripts/ createPreviews.ts generator that renders previews to disk. - Move build.ts to scripts/, and the VSIX packager to scripts/ buildVsCodePackage.ts with its marketplace README colocated. - Move the test harness to test/. Update package.json script paths, broaden the tsconfig include to src+scripts+test (dropping rootDir), and fix the publish workflow's README path. Behavior-preserving: the six themes regenerate identically. --- .github/workflows/publish.yml | 2 +- DISPLAY-P3.md | 19 +- package.json | 8 +- .../README.package.md | 0 {src => scripts}/build.ts | 23 +- .../buildVsCodePackage.ts | 9 +- scripts/createPreviews.ts | 34 + src/color-p3.ts | 229 ------ src/color/index.ts | 6 + src/color/p3.ts | 156 +++++ src/color/srgb.ts | 34 + src/{theme.ts => createTheme.ts} | 7 +- src/{zed-theme.ts => createZedTheme.ts} | 7 +- src/demo-p3.ts | 44 -- src/palette.ts | 662 ------------------ src/palettes.ts | 321 +++++++++ src/previews/p3.ts | 111 +++ .../palette.ts} | 20 +- src/roles/Roles.ts | 33 + src/roles/dark.ts | 79 +++ src/roles/darkSoft.ts | 79 +++ src/roles/index.ts | 5 + src/roles/light.ts | 80 +++ src/roles/lightSoft.ts | 79 +++ {src => test}/test.ts | 8 +- tsconfig.json | 5 +- 26 files changed, 1068 insertions(+), 992 deletions(-) rename README.package.md => scripts/README.package.md (100%) rename {src => scripts}/build.ts (89%) rename src/package-vsix.ts => scripts/buildVsCodePackage.ts (71%) create mode 100644 scripts/createPreviews.ts delete mode 100644 src/color-p3.ts create mode 100644 src/color/index.ts create mode 100644 src/color/p3.ts create mode 100644 src/color/srgb.ts rename src/{theme.ts => createTheme.ts} (99%) rename src/{zed-theme.ts => createZedTheme.ts} (99%) delete mode 100644 src/demo-p3.ts delete mode 100644 src/palette.ts create mode 100644 src/palettes.ts create mode 100644 src/previews/p3.ts rename src/{palette-preview.ts => previews/palette.ts} (88%) create mode 100644 src/roles/Roles.ts create mode 100644 src/roles/dark.ts create mode 100644 src/roles/darkSoft.ts create mode 100644 src/roles/index.ts create mode 100644 src/roles/light.ts create mode 100644 src/roles/lightSoft.ts rename {src => test}/test.ts (98%) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8e7b1fe..3ee0e8d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,7 @@ jobs: run: jq '.name = "pierre-theme"' package.json > tmp.json && mv tmp.json package.json - name: Swap in VSCE README - run: cp README.package.md README.md + run: cp scripts/README.package.md README.md - name: Publish to VS Marketplace env: diff --git a/DISPLAY-P3.md b/DISPLAY-P3.md index 1983fcf..42fc7ed 100644 --- a/DISPLAY-P3.md +++ b/DISPLAY-P3.md @@ -91,22 +91,9 @@ const html = highlighter.codeToHtml(code, { ## Relevant files -- **`src/color-p3.ts`** - Color conversion and enhancement -- **`src/demo-p3.ts`** - Demo showing conversions (`npx ts-node src/demo-p3.ts`) -- **`color-comparison.html`** - Visual comparison tool (open in Safari on P3 display) - -## Testing - -```bash -# View color conversions -npx ts-node src/demo-p3.ts - -# Rebuild themes -npm run build - -# Run tests -npm test -``` +- **`src/color/p3.ts`** - Display-P3 conversion and gamut enhancement +- **`src/color/`** - Shared color science (sRGB, contrast, CIEDE2000, CVD) +- **`src/previews/p3.ts`** - Preview showing conversions (`npm run preview`) ## Why this matters diff --git a/package.json b/package.json index 1acf545..e34e539 100644 --- a/package.json +++ b/package.json @@ -54,11 +54,11 @@ ] }, "scripts": { - "build": "ts-node src/build.ts", - "preview": "ts-node src/palette-preview.ts", - "test": "npm run build && ts-node src/test.ts", + "build": "ts-node scripts/build.ts", + "preview": "ts-node scripts/createPreviews.ts", + "test": "npm run build && ts-node test/test.ts", "start": "nodemon --watch src --ext ts --exec npm run build", - "package": "ts-node src/package-vsix.ts", + "package": "ts-node scripts/buildVsCodePackage.ts", "prepublishOnly": "npm run build" }, "devDependencies": { diff --git a/README.package.md b/scripts/README.package.md similarity index 100% rename from README.package.md rename to scripts/README.package.md diff --git a/src/build.ts b/scripts/build.ts similarity index 89% rename from src/build.ts rename to scripts/build.ts index c6d312c..4971394 100644 --- a/src/build.ts +++ b/scripts/build.ts @@ -1,9 +1,8 @@ -// src/build.ts import { writeFileSync, mkdirSync } from "node:fs"; -import { light as rolesLight, lightSoft as rolesLightSoft, dark as rolesDark, darkSoft as rolesDarkSoft } from "./palette"; -import { makeTheme } from "./theme"; -import { makeZedThemeFamily } from "./zed-theme"; -import { convertRolesToP3 } from "./color-p3"; +import { light as rolesLight, lightSoft as rolesLightSoft, dark as rolesDark, darkSoft as rolesDarkSoft } from "../src/roles"; +import { createTheme } from "../src/createTheme"; +import { createZedTheme } from "../src/createZedTheme"; +import { convertRolesToP3 } from "../src/color"; mkdirSync("themes", { recursive: true }); mkdirSync("zed/themes", { recursive: true }); @@ -18,7 +17,7 @@ const rolesDarkP3 = convertRolesToP3(rolesDark); const vscodeThemes = [ { file: "themes/pierre-light.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-light", displayName: "Pierre Light", type: "light", @@ -27,7 +26,7 @@ const vscodeThemes = [ }, { file: "themes/pierre-light-soft.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-light-soft", displayName: "Pierre Light Soft", type: "light", @@ -36,7 +35,7 @@ const vscodeThemes = [ }, { file: "themes/pierre-dark.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-dark", displayName: "Pierre Dark", type: "dark", @@ -45,7 +44,7 @@ const vscodeThemes = [ }, { file: "themes/pierre-dark-soft.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-dark-soft", displayName: "Pierre Dark Soft", type: "dark", @@ -54,7 +53,7 @@ const vscodeThemes = [ }, { file: "themes/pierre-light-vibrant.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-light-vibrant", displayName: "Pierre Light Vibrant", type: "light", @@ -63,7 +62,7 @@ const vscodeThemes = [ }, { file: "themes/pierre-dark-vibrant.json", - theme: makeTheme({ + theme: createTheme({ name: "pierre-dark-vibrant", displayName: "Pierre Dark Vibrant", type: "dark", @@ -80,7 +79,7 @@ for (const {file, theme} of vscodeThemes) { // ============================================ // Zed Theme Family // ============================================ -const zedTheme = makeZedThemeFamily("Pierre", "pierrecomputer", [ +const zedTheme = createZedTheme("Pierre", "pierrecomputer", [ { name: "Pierre Light", appearance: "light", roles: rolesLight }, { name: "Pierre Light Soft", appearance: "light", roles: rolesLightSoft }, { name: "Pierre Dark", appearance: "dark", roles: rolesDark }, diff --git a/src/package-vsix.ts b/scripts/buildVsCodePackage.ts similarity index 71% rename from src/package-vsix.ts rename to scripts/buildVsCodePackage.ts index 8c25dcc..b5196cb 100644 --- a/src/package-vsix.ts +++ b/scripts/buildVsCodePackage.ts @@ -1,3 +1,10 @@ +// VS Code Marketplace packaging shim. The npm package is scoped as +// @pierre/theme, but VSIX packaging expects the extension name to be unscoped +// and reads README.md as the marketplace description. This script temporarily +// swaps package.json's name to pierre-theme and this directory's +// README.package.md to the repo-root README.md, runs `vsce package`, then +// restores the working tree in a finally block. + import { readFileSync, writeFileSync, existsSync, renameSync } from "fs"; import { execSync } from "child_process"; import { join } from "path"; @@ -5,7 +12,7 @@ import { join } from "path"; const root = join(__dirname, ".."); const pkgPath = join(root, "package.json"); const readmePath = join(root, "README.md"); -const vsceReadmePath = join(root, "README.package.md"); +const vsceReadmePath = join(__dirname, "README.package.md"); const readmeBackupPath = join(root, "README.md.bak"); const original = readFileSync(pkgPath, "utf-8"); diff --git a/scripts/createPreviews.ts b/scripts/createPreviews.ts new file mode 100644 index 0000000..61c1a09 --- /dev/null +++ b/scripts/createPreviews.ts @@ -0,0 +1,34 @@ +// Discovers preview modules in src/previews and writes each rendered HTML file +// to preview/. + +import { mkdirSync, readdirSync, writeFileSync } from "node:fs"; +import { basename, extname, join, relative } from "node:path"; + +type Preview = { + filename: string; + render: () => string; +}; + +const root = join(__dirname, ".."); +const previewSourceDir = join(root, "src", "previews"); +const outputDir = join(root, "preview"); + +mkdirSync(outputDir, { recursive: true }); + +const files = readdirSync(previewSourceDir) + .filter((file) => extname(file) === ".ts") + .sort(); + +for (const file of files) { + const exportName = basename(file, ".ts"); + const mod = require(join(previewSourceDir, file)); + const preview = mod[exportName] as Preview | undefined; + + if (!preview || typeof preview.filename !== "string" || typeof preview.render !== "function") { + throw new Error(`${file} must export ${exportName}: { filename, render }`); + } + + const outputPath = join(outputDir, preview.filename); + writeFileSync(outputPath, preview.render(), "utf8"); + console.log("Wrote", relative(root, outputPath)); +} diff --git a/src/color-p3.ts b/src/color-p3.ts deleted file mode 100644 index 41d48be..0000000 --- a/src/color-p3.ts +++ /dev/null @@ -1,229 +0,0 @@ -// src/color-p3.ts -// Convert sRGB hex colors to CSS Display P3 color space format - -/** - * Convert hex color to RGB values (0-1 range) - */ -function hexToRgb01(hex: string): [number, number, number] { - const cleaned = hex.replace('#', ''); - const expanded = cleaned.length === 3 - ? cleaned.split('').map(x => x + x).join('') - : cleaned; - - const num = parseInt(expanded, 16); - const r = ((num >> 16) & 255) / 255; - const g = ((num >> 8) & 255) / 255; - const b = (num & 255) / 255; - - return [r, g, b]; -} - -/** - * Apply sRGB gamma correction (linearize) - */ -function srgbToLinear(c: number): number { - if (c <= 0.04045) { - return c / 12.92; - } - return Math.pow((c + 0.055) / 1.055, 2.4); -} - -/** - * Remove sRGB gamma correction (to display) - */ -function linearToSrgb(c: number): number { - if (c <= 0.0031308) { - return c * 12.92; - } - return 1.055 * Math.pow(c, 1 / 2.4) - 0.055; -} - -/** - * Apply Display P3 gamma correction (same as sRGB) - */ -const linearToP3 = linearToSrgb; - -/** - * Convert linear sRGB to linear Display P3 - * Using the standard conversion matrix from sRGB to P3 - */ -function linearSrgbToLinearP3(r: number, g: number, b: number): [number, number, number] { - // Conversion matrix from linear sRGB to linear Display P3 - // This matrix converts from sRGB primaries to P3 primaries via XYZ - const rOut = 0.82246197 * r + 0.17753803 * g + 0.00000000 * b; - const gOut = 0.03319420 * r + 0.96680580 * g + 0.00000000 * b; - const bOut = 0.01708263 * r + 0.07239744 * g + 0.91051993 * b; - - return [rOut, gOut, bOut]; -} - -/** - * Format a number for CSS color function (round to reasonable precision) - */ -function formatColorValue(value: number): string { - // Clamp to 0-1 range - const clamped = Math.max(0, Math.min(1, value)); - // Round to 6 decimal places for precision without being excessive - return clamped.toFixed(6); -} - -/** - * Convert RGB to HSL color space - */ -function rgbToHsl(r: number, g: number, b: number): [number, number, number] { - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - const delta = max - min; - - let h = 0; - let s = 0; - const l = (max + min) / 2; - - if (delta !== 0) { - s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min); - - switch (max) { - case r: - h = ((g - b) / delta + (g < b ? 6 : 0)) / 6; - break; - case g: - h = ((b - r) / delta + 2) / 6; - break; - case b: - h = ((r - g) / delta + 4) / 6; - break; - } - } - - return [h, s, l]; -} - -/** - * Convert HSL to RGB color space - */ -function hslToRgb(h: number, s: number, l: number): [number, number, number] { - let r, g, b; - - if (s === 0) { - r = g = b = l; - } else { - const hue2rgb = (p: number, q: number, t: number) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }; - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [r, g, b]; -} - -/** - * Enhance colors to take advantage of P3's wider gamut - * Increases saturation and vibrancy for colors that can benefit from P3 - */ -function enhanceForP3Gamut(r: number, g: number, b: number): [number, number, number] { - // Convert to HSL for easier saturation manipulation - const [h, s, l] = rgbToHsl(r, g, b); - - // Only enhance colors that have some saturation and aren't too light or dark - // (grays and near-blacks/whites don't benefit from P3 enhancement) - if (s < 0.1 || l < 0.1 || l > 0.9) { - return [r, g, b]; - } - - // Calculate enhancement factor based on original saturation - // More saturated colors get more enhancement (they can handle it) - const saturationBoost = 0.15 + (s * 0.15); // 15-30% boost depending on saturation - - // Boost saturation, but not beyond 1.0 - let newS = s + (s * saturationBoost); - newS = Math.min(1.0, newS); - - // For highly saturated colors, also slightly increase luminance to make them "pop" - // but only if they're not already too light - let newL = l; - if (s > 0.5 && l < 0.7) { - newL = l + (l * 0.05); // Slight 5% luminance boost for vibrant colors - newL = Math.min(0.9, newL); - } - - // Convert back to RGB - return hslToRgb(h, newS, newL); -} - -/** - * Convert sRGB hex color to CSS Display P3 color format with enhancement - * Returns: "color(display-p3 r g b)" or "color(display-p3 r g b / alpha)" - */ -export function srgbHexToP3Color(srgbHex: string, enhance: boolean = true): string { - // Handle alpha channel if present - const hasAlpha = srgbHex.length === 9 || (srgbHex.startsWith('#') && srgbHex.length === 9); - let alpha = ''; - let colorHex = srgbHex; - - if (hasAlpha) { - const alphaHex = srgbHex.slice(-2); - const alphaValue = parseInt(alphaHex, 16) / 255; - alpha = ` / ${formatColorValue(alphaValue)}`; - colorHex = srgbHex.slice(0, -2); - } - - // Convert to linear sRGB - const [sR, sG, sB] = hexToRgb01(colorHex); - const linearSR = srgbToLinear(sR); - const linearSG = srgbToLinear(sG); - const linearSB = srgbToLinear(sB); - - // Convert to linear P3 - const [linearPR, linearPG, linearPB] = linearSrgbToLinearP3(linearSR, linearSG, linearSB); - - // Apply P3 gamma to get display values - let pR = linearToP3(linearPR); - let pG = linearToP3(linearPG); - let pB = linearToP3(linearPB); - - // Enhance colors to take advantage of P3's wider gamut - if (enhance) { - [pR, pG, pB] = enhanceForP3Gamut(pR, pG, pB); - } - - // Format as CSS color(display-p3 ...) function - return `color(display-p3 ${formatColorValue(pR)} ${formatColorValue(pG)} ${formatColorValue(pB)}${alpha})`; -} - -/** - * Convert all colors in a Roles object to Display P3 - */ -export function convertRolesToP3(obj: T): T { - if (typeof obj === 'string') { - // If it's a hex color string, convert it - if ((obj as string).match(/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/)) { - return srgbHexToP3Color(obj as string) as any; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(item => convertRolesToP3(item)) as any; - } - - if (obj !== null && typeof obj === 'object') { - const result: any = {}; - for (const [key, value] of Object.entries(obj as any)) { - result[key] = convertRolesToP3(value); - } - return result; - } - - return obj; -} diff --git a/src/color/index.ts b/src/color/index.ts new file mode 100644 index 0000000..c9dc2cf --- /dev/null +++ b/src/color/index.ts @@ -0,0 +1,6 @@ +// Color science: pure, dependency-free color math shared by the theme build +// (Display-P3 vibrant variants) and the previews. Each concern is a discrete +// module; this barrel re-exports the public surface. + +export { hexToRgb01, srgbToLinear, linearToSrgb } from "./srgb"; +export { srgbHexToP3Color, convertRolesToP3 } from "./p3"; diff --git a/src/color/p3.ts b/src/color/p3.ts new file mode 100644 index 0000000..62b9106 --- /dev/null +++ b/src/color/p3.ts @@ -0,0 +1,156 @@ +// Convert sRGB hex colors to the CSS Display P3 color space, with an optional +// saturation/luminance boost that pushes colors into P3's wider gamut. This is +// how the "vibrant" theme variants are defined (see scripts/build.ts) and +// previewed (src/previews/p3.ts). + +import { hexToRgb01, srgbToLinear, linearToSrgb } from "./srgb"; + +// Display P3 uses the same transfer function (gamma) as sRGB. +const linearToP3 = linearToSrgb; + +/** Linear sRGB → linear Display P3 (sRGB primaries → P3 primaries via XYZ). */ +function linearSrgbToLinearP3(r: number, g: number, b: number): [number, number, number] { + const rOut = 0.82246197 * r + 0.17753803 * g + 0.0 * b; + const gOut = 0.0331942 * r + 0.9668058 * g + 0.0 * b; + const bOut = 0.01708263 * r + 0.07239744 * g + 0.91051993 * b; + return [rOut, gOut, bOut]; +} + +/** Format a 0–1 channel for a CSS color() function (clamped, 6 dp). */ +function formatColorValue(value: number): string { + const clamped = Math.max(0, Math.min(1, value)); + return clamped.toFixed(6); +} + +function rgbToHsl(r: number, g: number, b: number): [number, number, number] { + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const delta = max - min; + + let h = 0; + let s = 0; + const l = (max + min) / 2; + + if (delta !== 0) { + s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min); + switch (max) { + case r: + h = ((g - b) / delta + (g < b ? 6 : 0)) / 6; + break; + case g: + h = ((b - r) / delta + 2) / 6; + break; + case b: + h = ((r - g) / delta + 4) / 6; + break; + } + } + + return [h, s, l]; +} + +function hslToRgb(h: number, s: number, l: number): [number, number, number] { + let r, g, b; + + if (s === 0) { + r = g = b = l; + } else { + const hue2rgb = (p: number, q: number, t: number) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r, g, b]; +} + +/** + * Enhance colors to take advantage of P3's wider gamut: boost saturation + * (15–30% by original saturation) and, for vivid mid-tones, luminance (~5%). + * Grays and near-black/white are left untouched. + */ +function enhanceForP3Gamut(r: number, g: number, b: number): [number, number, number] { + const [h, s, l] = rgbToHsl(r, g, b); + + if (s < 0.1 || l < 0.1 || l > 0.9) { + return [r, g, b]; + } + + const saturationBoost = 0.15 + s * 0.15; // 15–30% depending on saturation + let newS = Math.min(1.0, s + s * saturationBoost); + + let newL = l; + if (s > 0.5 && l < 0.7) { + newL = Math.min(0.9, l + l * 0.05); + } + + return hslToRgb(h, newS, newL); +} + +/** + * Convert an sRGB hex color to a CSS Display P3 string, + * "color(display-p3 r g b)" (or "… / alpha"). + */ +export function srgbHexToP3Color(srgbHex: string, enhance: boolean = true): string { + const hasAlpha = srgbHex.length === 9 || (srgbHex.startsWith("#") && srgbHex.length === 9); + let alpha = ""; + let colorHex = srgbHex; + + if (hasAlpha) { + const alphaHex = srgbHex.slice(-2); + const alphaValue = parseInt(alphaHex, 16) / 255; + alpha = ` / ${formatColorValue(alphaValue)}`; + colorHex = srgbHex.slice(0, -2); + } + + const [sR, sG, sB] = hexToRgb01(colorHex); + const [linearPR, linearPG, linearPB] = linearSrgbToLinearP3( + srgbToLinear(sR), + srgbToLinear(sG), + srgbToLinear(sB) + ); + + let pR = linearToP3(linearPR); + let pG = linearToP3(linearPG); + let pB = linearToP3(linearPB); + + if (enhance) { + [pR, pG, pB] = enhanceForP3Gamut(pR, pG, pB); + } + + return `color(display-p3 ${formatColorValue(pR)} ${formatColorValue(pG)} ${formatColorValue(pB)}${alpha})`; +} + +/** Recursively convert every hex color in a Roles-shaped object to Display P3. */ +export function convertRolesToP3(obj: T): T { + if (typeof obj === "string") { + if ((obj as string).match(/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/)) { + return srgbHexToP3Color(obj as string) as any; + } + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => convertRolesToP3(item)) as any; + } + + if (obj !== null && typeof obj === "object") { + const result: any = {}; + for (const [key, value] of Object.entries(obj as any)) { + result[key] = convertRolesToP3(value); + } + return result; + } + + return obj; +} diff --git a/src/color/srgb.ts b/src/color/srgb.ts new file mode 100644 index 0000000..c86e716 --- /dev/null +++ b/src/color/srgb.ts @@ -0,0 +1,34 @@ +// Low-level sRGB primitives shared across the color-science modules (P3 +// conversion, CVD simulation, WCAG contrast, CIEDE2000). These are not +// Display-P3 concerns — they're the generic hex ↔ RGB and gamma conversions that +// everything else builds on. + +/** Parse a hex color ("#rgb" or "#rrggbb") to RGB channels in the 0–1 range. */ +export function hexToRgb01(hex: string): [number, number, number] { + const cleaned = hex.replace("#", ""); + const expanded = + cleaned.length === 3 ? cleaned.split("").map((x) => x + x).join("") : cleaned; + + const num = parseInt(expanded, 16); + const r = ((num >> 16) & 255) / 255; + const g = ((num >> 8) & 255) / 255; + const b = (num & 255) / 255; + + return [r, g, b]; +} + +/** Linearize an sRGB channel (remove the sRGB gamma curve). */ +export function srgbToLinear(c: number): number { + if (c <= 0.04045) { + return c / 12.92; + } + return Math.pow((c + 0.055) / 1.055, 2.4); +} + +/** Apply the sRGB gamma curve to a linear channel (encode for display). */ +export function linearToSrgb(c: number): number { + if (c <= 0.0031308) { + return c * 12.92; + } + return 1.055 * Math.pow(c, 1 / 2.4) - 0.055; +} diff --git a/src/theme.ts b/src/createTheme.ts similarity index 99% rename from src/theme.ts rename to src/createTheme.ts index e20e87e..c8f4133 100644 --- a/src/theme.ts +++ b/src/createTheme.ts @@ -1,5 +1,4 @@ -// src/theme.ts -import type { Roles } from "./palette"; +import type { Roles } from "./roles"; type VSCodeTheme = { name: string; @@ -10,14 +9,14 @@ type VSCodeTheme = { semanticTokenColors: Record; }; -type MakeThemeOptions = { +type CreateThemeOptions = { name: string; displayName: string; type: "light" | "dark"; roles: Roles; }; -export function makeTheme({ name, displayName, type: kind, roles: c }: MakeThemeOptions): VSCodeTheme { +export function createTheme({ name, displayName, type: kind, roles: c }: CreateThemeOptions): VSCodeTheme { return { name, displayName, diff --git a/src/zed-theme.ts b/src/createZedTheme.ts similarity index 99% rename from src/zed-theme.ts rename to src/createZedTheme.ts index af83d37..1eb2347 100644 --- a/src/zed-theme.ts +++ b/src/createZedTheme.ts @@ -1,5 +1,4 @@ -// src/zed-theme.ts -import type { Roles } from "./palette"; +import type { Roles } from "./roles"; type ZedHighlightStyle = { color?: string; @@ -196,13 +195,13 @@ type ZedThemeFamilyContent = { themes: ZedTheme[]; }; -export type ZedThemeVariant = { +type ZedThemeVariant = { name: string; appearance: "light" | "dark"; roles: Roles; }; -export function makeZedThemeFamily( +export function createZedTheme( familyName: string, author: string, variants: ZedThemeVariant[] diff --git a/src/demo-p3.ts b/src/demo-p3.ts deleted file mode 100644 index f2d2c7a..0000000 --- a/src/demo-p3.ts +++ /dev/null @@ -1,44 +0,0 @@ -// src/demo-p3.ts -// Demonstration of Display P3 color conversion with enhancement - -import { srgbHexToP3Color } from "./color-p3"; - -console.log("Display P3 Color Conversion Demo"); -console.log("=".repeat(70)); -console.log(""); - -const testColors = [ - { name: "Blue", srgb: "#008cff" }, - { name: "Green", srgb: "#0dbe4e" }, - { name: "Red", srgb: "#ff2e3f" }, - { name: "Purple", srgb: "#c635e4" }, - { name: "Pink", srgb: "#fc2b73" }, - { name: "Orange", srgb: "#fe8c2c" }, - { name: "Cyan", srgb: "#08c0ef" }, - { name: "Teal", srgb: "#00c5d2" } -]; - -console.log("Color conversions from sRGB to Enhanced Display P3:"); -console.log("(Enhanced to take advantage of P3's wider gamut)"); -console.log(""); - -for (const { name, srgb } of testColors) { - const p3Basic = srgbHexToP3Color(srgb, false); - const p3Enhanced = srgbHexToP3Color(srgb, true); - console.log(`${name.padEnd(10)} ${srgb}`); - console.log(`${''.padEnd(10)} Basic: ${p3Basic}`); - console.log(`${''.padEnd(10)} Enhanced: ${p3Enhanced}`); - console.log(""); -} - -console.log("=".repeat(70)); -console.log(""); -console.log("Enhancement Details:"); -console.log("- Saturation boost: 15-30% depending on original saturation"); -console.log("- Luminance boost: 5% for highly saturated colors"); -console.log("- Grays and near-blacks/whites are left unchanged"); -console.log(""); -console.log("These enhanced colors take full advantage of Display P3's"); -console.log("wider color gamut (~25% more colors than sRGB)."); -console.log(""); -console.log("Browser support: Safari 10+, Chrome 111+, Firefox 113+, Edge 111+"); diff --git a/src/palette.ts b/src/palette.ts deleted file mode 100644 index 46ead47..0000000 --- a/src/palette.ts +++ /dev/null @@ -1,662 +0,0 @@ -// src/palette.ts - -// gray is a slightly blue-tinted neutral scale kept as a reference palette for consumers. -// It is not used in any built-in role; all four theme variants use `neutral` instead. -const gray = { - "020":"#fbfbfb", - "040":"#f9f9f9", - "060":"#f8f8f8", - "080":"#f2f2f3", - "100":"#eeeeef", - "200":"#dbdbdd", - "300":"#c6c6c8", - "400":"#adadb1", - "500":"#8E8E95", - "600":"#84848A", - "700":"#79797F", - "800":"#6C6C71", - "900":"#4A4A4E", - "920":"#424245", - "940":"#39393c", - "960":"#2e2e30", - "980":"#1F1F21", - "1000":"#141415", - "1020":"#0B0B0C", - "1040":"#070707" -}; - -const neutral = { - "020":"#fafafa", - "040":"#f7f7f7", - "060":"#f5f5f5", - "080":"#ededed", - "100":"#e5e5e5", - "200":"#d4d4d4", - "300":"#bcbcbc", - "400":"#a3a3a3", - "500":"#8a8a8a", - "600":"#737373", - "700":"#636363", - "800":"#525252", - "900":"#404040", - "920":"#363636", - "940":"#2c2c2c", - "960":"#262626", - "980":"#1d1d1d", - "1000":"#171717", - "1020":"#101010", - "1040":"#0a0a0a" -}; - -const red = { - "050":"#ffedea", - "100":"#ffdbd6", - "200":"#ffb7ae", - "300":"#ff9187", - "400":"#ff6762", - "500":"#ff2e3f", - "600":"#d52c36", - "700":"#ad292e", - "800":"#862425", - "900":"#611e1d", - "950":"#3e1715" -}; - -const vermillion = { - "050":"#fff0ea", - "100":"#ffe2d6", - "200":"#ffc4ad", - "300":"#ffa685", - "400":"#ff855e", - "500":"#ff5d36", - "600":"#d5512f", - "700":"#ad4529", - "800":"#863822", - "900":"#612b1b", - "950":"#3e1e14" -}; - -const orange = { - "050":"#fff3ea", - "100":"#ffe8d5", - "200":"#ffd1ab", - "300":"#ffba82", - "400":"#ffa359", - "500":"#fe8c2c", - "600":"#d47628", - "700":"#ac6023", - "800":"#854c1e", - "900":"#603819", - "950":"#3d2513" -}; - -const amber = { - "050": "#fff6ea", - "100": "#ffeed5", - "200": "#ffddab", - "300": "#ffcc81", - "400": "#ffbc56", - "500": "#ffab16", - "600": "#d5901c", - "700": "#ac741d", - "800": "#855b1b", - "900": "#604218", - "950": "#3d2b13" -}; - -const yellow = { - "050": "#fff9ea", - "100": "#fff4d5", - "200": "#ffe9ab", - "300": "#ffde80", - "400": "#ffd452", - "500": "#ffca00", - "600": "#d5a910", - "700": "#ac8816", - "800": "#856a17", - "900": "#604c16", - "950": "#3d3112" -}; - -const lime = { - "050": "#f6f9ec", - "100": "#edf4d8", - "200": "#dae8b1", - "300": "#c6dc8a", - "400": "#afd062", - "500": "#86c427", - "600": "#77a42a", - "700": "#658527", - "800": "#516723", - "900": "#3e4b1d", - "950": "#2a3016" -}; - -const green = { - "050": "#edf9ed", - "100": "#daf3db", - "200": "#b4e7b7", - "300": "#8cda94", - "400": "#5ecc71", - "500": "#0dbe4e", - "600": "#199f43", - "700": "#1d8138", - "800": "#1d642e", - "900": "#1b4923", - "950": "#162f19" -}; - -const jade = { - "050": "#edfaf2", - "100": "#dbf4e5", - "200": "#b6e9cb", - "300": "#8eddb2", - "400": "#60d199", - "500": "#07c480", - "600": "#18a46c", - "700": "#1d8558", - "800": "#1e6746", - "900": "#1c4b34", - "950": "#163023" -}; - -const mint = { - "050": "#edfaf7", - "100": "#dbf5ef", - "200": "#b7ebdf", - "300": "#8fe0d0", - "400": "#61d5c0", - "500": "#00cab1", - "600": "#16a994", - "700": "#1d8978", - "800": "#1e6a5e", - "900": "#1c4d44", - "950": "#16312c" -}; - -const teal = { - "050": "#eef9fa", - "100": "#ddf4f6", - "200": "#b9e8ed", - "300": "#92dde4", - "400": "#64d1db", - "500": "#00c5d2", - "600": "#17a5af", - "700": "#1e858e", - "800": "#1f686e", - "900": "#1d4b4f", - "950": "#173033" -}; - -const cyan = { - "050": "#eff9fe", - "100": "#def2fc", - "200": "#bce6f9", - "300": "#96d9f6", - "400": "#68cdf2", - "500": "#08c0ef", - "600": "#1ca1c7", - "700": "#2182a1", - "800": "#22657c", - "900": "#1e4959", - "950": "#182f38" -}; - -const blue = { - "050": "#eff5ff", - "100": "#dfebff", - "200": "#bdd7ff", - "300": "#97c4ff", - "400": "#69b1ff", - "500": "#009fff", - "600": "#1a85d4", - "700": "#216cab", - "800": "#215584", - "900": "#1f3e5e", - "950": "#19283c" -}; - -const indigo = { - "050": "#f5ecff", - "100": "#ead9ff", - "200": "#d3b4fe", - "300": "#ba8ffd", - "400": "#9d6afb", - "500": "#7b43f8", - "600": "#693acf", - "700": "#5731a7", - "800": "#462981", - "900": "#35205c", - "950": "#24173a" -}; - -const violet = { - "050": "#f8edfe", - "100": "#f1dafd", - "200": "#e1b5fa", - "300": "#ce90f7", - "400": "#b969f3", - "500": "#a13cee", - "600": "#8836c7", - "700": "#6f2ea1", - "800": "#58287c", - "900": "#412059", - "950": "#2b1738" -}; - -const purple = { - "050": "#fbedfd", - "100": "#f7dbfb", - "200": "#eeb6f6", - "300": "#e290f0", - "400": "#d568ea", - "500": "#c635e4", - "600": "#a631be", - "700": "#872b9a", - "800": "#692677", - "900": "#4d1f56", - "950": "#321736" -}; - -const magenta = { - "050": "#fdedf7", - "100": "#fbdbee", - "200": "#f7b7dd", - "300": "#f191cc", - "400": "#ea68bc", - "500": "#e130ac", - "600": "#bd2e90", - "700": "#992a75", - "800": "#77255b", - "900": "#561f43", - "950": "#38172b" -}; - -const pink = { - "050": "#ffedf0", - "100": "#ffdbe1", - "200": "#ffb7c4", - "300": "#ff91a8", - "400": "#ff678d", - "500": "#fc2b73", - "600": "#d32a61", - "700": "#aa2850", - "800": "#84243f", - "900": "#5f1e2f", - "950": "#3d1720" -}; - -const rose = { - "050": "#ffeded", - "100": "#ffdbdc", - "200": "#ffb7b9", - "300": "#ff9198", - "400": "#ff6778", - "500": "#fe2d59", - "600": "#d42b4c", - "700": "#ac293f", - "800": "#852432", - "900": "#601e26", - "950": "#3e171b" -}; - -const brown = { - "050": "#f8f2ee", - "100": "#f1e4dd", - "200": "#e3cabb", - "300": "#d3b19b", - "400": "#c3987b", - "500": "#b27f5c", - "600": "#956b4f", - "700": "#7a5841", - "800": "#5f4534", - "900": "#453327", - "950": "#2d221b" -}; - -export const palettes = { - gray, neutral, - red, vermillion, orange, amber, yellow, lime, - green, jade, mint, teal, cyan, blue, - indigo, violet, purple, magenta, pink, rose, - brown -}; - -export type Roles = { - bg: { - editor: string; // main editor background (brightest in light, darkest in dark) - window: string; // sidebar, activity bar, status bar, title bar, inactive tabs - inset: string; // inputs, dropdowns - elevated: string; // panels, hover backgrounds - }; - fg: { base: string; fg1: string; fg2: string; fg3: string; fg4: string }; - border: { - window: string; // borders for sidebar, activity bar, status bar, title bar - editor: string; // general editor borders - indentGuide: string; // indent guide lines - indentGuideActive: string; // active indent guide line - inset: string; // borders for inputs, dropdowns - elevated: string; // borders for panels - }; - accent: { primary: string; link: string; subtle: string; contrastOnAccent: string }; - states: { merge: string, success: string; danger: string; warn: string; info: string }; - syntax: { - comment: string; string: string; number: string; keyword: string; - regexp: string; func: string; type: string; variable: string; - // Extended token types - operator: string; punctuation: string; constant: string; - parameter: string; namespace: string; decorator: string; - escape: string; invalid: string; tag: string; attribute: string; - }; - ansi: { - black: string; red: string; green: string; yellow: string; - blue: string; magenta: string; cyan: string; white: string; - brightBlack: string; brightRed: string; brightGreen: string; brightYellow: string; - brightBlue: string; brightMagenta: string; brightCyan: string; brightWhite: string; - }; -}; - -export const light: Roles = { - bg: { - editor: "#ffffff", - window: neutral["060"], - inset: neutral["080"], - elevated: neutral["040"] - }, - fg: { - base: neutral["1040"], - fg1: neutral["900"], - fg2: neutral["800"], - fg3: neutral["600"], - fg4: neutral["500"] - }, - border: { - window: neutral["100"], - editor: neutral["200"], - indentGuide: neutral["100"], - indentGuideActive: neutral["200"], - inset: neutral["200"], - elevated: neutral["100"] - }, - accent: { - primary: blue["500"], - link: blue["500"], - subtle: blue["100"], - contrastOnAccent: "#ffffff" - }, - states: { - merge: indigo["600"], - success: jade["600"], - danger: red["600"], - warn: yellow["600"], - info: cyan["600"] - }, - syntax: { - comment: neutral["600"], - string: green["600"], - number: cyan["600"], - keyword: pink["600"], - regexp: teal["600"], - func: indigo["600"], - type: purple["600"], - variable: orange["600"], - // Extended token types - operator: cyan["500"], - punctuation: neutral["700"], - constant: yellow["600"], - parameter: neutral["700"], - namespace: amber["600"], - decorator: blue["600"], - escape: mint["600"], - invalid: neutral["1040"], - tag: vermillion["600"], - attribute: jade["600"] - }, - ansi: { - black: neutral["980"], - red: red["600"], - green: jade["600"], - yellow: yellow["600"], - blue: blue["600"], - magenta: magenta["600"], - cyan: cyan["600"], - white: neutral["300"], - // make bright colors match the non-bright counterparts - brightBlack: neutral["980"], - brightRed: red["600"], - brightGreen: lime["600"], - brightYellow: yellow["600"], - brightBlue: blue["600"], - brightMagenta: magenta["600"], - brightCyan: cyan["600"], - brightWhite: neutral["300"] - } -}; - -export const lightSoft: Roles = { - bg: { - editor: "#ffffff", - window: neutral["040"], - inset: neutral["060"], - elevated: neutral["020"] - }, - fg: { - base: neutral["800"], - fg1: neutral["700"], - fg2: neutral["600"], - fg3: neutral["500"], - fg4: neutral["400"] - }, - border: { - window: neutral["080"], - editor: neutral["100"], - indentGuide: neutral["080"], - indentGuideActive: neutral["100"], - inset: neutral["200"], - elevated: neutral["100"] - }, - accent: { - primary: blue["500"], - link: blue["500"], - subtle: blue["100"], - contrastOnAccent: "#ffffff" - }, - states: { - merge: indigo["500"], - success: jade["500"], - danger: red["500"], - warn: yellow["500"], - info: cyan["500"] - }, - syntax: { - comment: neutral["500"], - string: green["500"], - number: cyan["500"], - keyword: pink["400"], - regexp: teal["500"], - func: indigo["400"], - type: purple["400"], - variable: orange["500"], - // Extended token types - operator: cyan["400"], - punctuation: neutral["600"], - constant: yellow["500"], - parameter: neutral["600"], - namespace: amber["500"], - decorator: blue["400"], - escape: mint["500"], - invalid: neutral["1000"], - tag: vermillion["500"], - attribute: jade["500"] - }, - ansi: { - black: neutral["980"], - red: red["500"], - green: green["500"], - yellow: yellow["500"], - blue: blue["500"], - magenta: magenta["500"], - cyan: cyan["500"], - white: neutral["300"], - brightBlack: neutral["980"], - brightRed: red["500"], - brightGreen: lime["500"], - brightYellow: yellow["500"], - brightBlue: blue["500"], - brightMagenta: magenta["500"], - brightCyan: cyan["500"], - brightWhite: neutral["300"] - } -}; - -export const dark: Roles = { - bg: { - editor: neutral["1040"], - window: neutral["1000"], - inset: neutral["980"], - elevated: neutral["1020"] - }, - fg: { - base: neutral["020"], - fg1: neutral["200"], - fg2: neutral["400"], - fg3: neutral["600"], - fg4: neutral["700"] - }, - border: { - window: neutral["1040"], - editor: neutral["980"], - indentGuide: neutral["980"], - indentGuideActive: neutral["960"], - inset: neutral["980"], - elevated: neutral["980"] - }, - accent: { - primary: blue["500"], - link: blue["500"], - subtle: blue["950"], - contrastOnAccent: neutral["1040"] - }, - states: { - merge: indigo["500"], - success: jade["500"], - danger: red["500"], - warn: yellow["500"], - info: cyan["500"] - }, - syntax: { - comment: neutral["600"], - string: green["400"], - number: cyan["400"], - keyword: pink["400"], - regexp: teal["400"], - func: indigo["400"], - type: purple["400"], - variable: orange["400"], - // Extended token types - operator: cyan["500"], - punctuation: neutral["700"], - constant: yellow["400"], - parameter: neutral["400"], - namespace: amber["500"], - decorator: blue["400"], - escape: mint["400"], - invalid: neutral["020"], - tag: vermillion["400"], - attribute: jade["400"] - }, - ansi: { - black: neutral["1000"], - red: red["500"], - green: green["500"], - yellow: yellow["500"], - blue: blue["500"], - magenta: magenta["500"], - cyan: cyan["500"], - white: neutral["300"], - brightBlack: neutral["1000"], - brightRed: red["500"], - brightGreen: lime["500"], - brightYellow: yellow["500"], - brightBlue: blue["500"], - brightMagenta: magenta["500"], - brightCyan: cyan["500"], - brightWhite: neutral["300"] - } -}; - -export const darkSoft: Roles = { - bg: { - editor: neutral["1000"], - window: neutral["1020"], - inset: neutral["960"], - elevated: neutral["980"] - }, - fg: { - base: neutral["200"], - fg1: neutral["300"], - fg2: neutral["500"], - fg3: neutral["700"], - fg4: neutral["800"] - }, - border: { - window: neutral["980"], - editor: neutral["940"], - indentGuide: neutral["960"], - indentGuideActive: neutral["940"], - inset: neutral["940"], - elevated: neutral["960"] - }, - accent: { - primary: blue["400"], - link: blue["400"], - subtle: blue["900"], - contrastOnAccent: neutral["1000"] - }, - states: { - merge: indigo["400"], - success: jade["400"], - danger: red["400"], - warn: yellow["400"], - info: cyan["400"] - }, - syntax: { - comment: neutral["700"], - string: green["300"], - number: cyan["300"], - keyword: pink["300"], - regexp: teal["300"], - func: indigo["300"], - type: purple["300"], - variable: orange["300"], - // Extended token types - operator: cyan["400"], - punctuation: neutral["600"], - constant: yellow["300"], - parameter: neutral["500"], - namespace: amber["400"], - decorator: blue["300"], - escape: mint["300"], - invalid: neutral["200"], - tag: vermillion["300"], - attribute: jade["300"] - }, - ansi: { - black: neutral["1000"], - red: red["500"], - green: green["500"], - yellow: yellow["500"], - blue: blue["500"], - magenta: magenta["500"], - cyan: cyan["500"], - white: neutral["300"], - brightBlack: neutral["1000"], - brightRed: red["500"], - brightGreen: lime["500"], - brightYellow: yellow["500"], - brightBlue: blue["500"], - brightMagenta: magenta["500"], - brightCyan: cyan["500"], - brightWhite: neutral["300"] - } -}; diff --git a/src/palettes.ts b/src/palettes.ts new file mode 100644 index 0000000..4ab9bcc --- /dev/null +++ b/src/palettes.ts @@ -0,0 +1,321 @@ +// gray is a slightly blue-tinted neutral scale kept as a reference palette for consumers. +// It is not used in any built-in role; all four theme variants use `neutral` instead. +const gray = { + "020":"#fbfbfb", + "040":"#f9f9f9", + "060":"#f8f8f8", + "080":"#f2f2f3", + "100":"#eeeeef", + "200":"#dbdbdd", + "300":"#c6c6c8", + "400":"#adadb1", + "500":"#8E8E95", + "600":"#84848A", + "700":"#79797F", + "800":"#6C6C71", + "900":"#4A4A4E", + "920":"#424245", + "940":"#39393c", + "960":"#2e2e30", + "980":"#1F1F21", + "1000":"#141415", + "1020":"#0B0B0C", + "1040":"#070707" +}; + +const neutral = { + "020":"#fafafa", + "040":"#f7f7f7", + "060":"#f5f5f5", + "080":"#ededed", + "100":"#e5e5e5", + "200":"#d4d4d4", + "300":"#bcbcbc", + "400":"#a3a3a3", + "500":"#8a8a8a", + "600":"#737373", + "700":"#636363", + "800":"#525252", + "900":"#404040", + "920":"#363636", + "940":"#2c2c2c", + "960":"#262626", + "980":"#1d1d1d", + "1000":"#171717", + "1020":"#101010", + "1040":"#0a0a0a" +}; + +const red = { + "050":"#ffedea", + "100":"#ffdbd6", + "200":"#ffb7ae", + "300":"#ff9187", + "400":"#ff6762", + "500":"#ff2e3f", + "600":"#d52c36", + "700":"#ad292e", + "800":"#862425", + "900":"#611e1d", + "950":"#3e1715" +}; + +const vermillion = { + "050":"#fff0ea", + "100":"#ffe2d6", + "200":"#ffc4ad", + "300":"#ffa685", + "400":"#ff855e", + "500":"#ff5d36", + "600":"#d5512f", + "700":"#ad4529", + "800":"#863822", + "900":"#612b1b", + "950":"#3e1e14" +}; + +const orange = { + "050":"#fff3ea", + "100":"#ffe8d5", + "200":"#ffd1ab", + "300":"#ffba82", + "400":"#ffa359", + "500":"#fe8c2c", + "600":"#d47628", + "700":"#ac6023", + "800":"#854c1e", + "900":"#603819", + "950":"#3d2513" +}; + +const amber = { + "050": "#fff6ea", + "100": "#ffeed5", + "200": "#ffddab", + "300": "#ffcc81", + "400": "#ffbc56", + "500": "#ffab16", + "600": "#d5901c", + "700": "#ac741d", + "800": "#855b1b", + "900": "#604218", + "950": "#3d2b13" +}; + +const yellow = { + "050": "#fff9ea", + "100": "#fff4d5", + "200": "#ffe9ab", + "300": "#ffde80", + "400": "#ffd452", + "500": "#ffca00", + "600": "#d5a910", + "700": "#ac8816", + "800": "#856a17", + "900": "#604c16", + "950": "#3d3112" +}; + +const lime = { + "050": "#f6f9ec", + "100": "#edf4d8", + "200": "#dae8b1", + "300": "#c6dc8a", + "400": "#afd062", + "500": "#86c427", + "600": "#77a42a", + "700": "#658527", + "800": "#516723", + "900": "#3e4b1d", + "950": "#2a3016" +}; + +const green = { + "050": "#edf9ed", + "100": "#daf3db", + "200": "#b4e7b7", + "300": "#8cda94", + "400": "#5ecc71", + "500": "#0dbe4e", + "600": "#199f43", + "700": "#1d8138", + "800": "#1d642e", + "900": "#1b4923", + "950": "#162f19" +}; + +const jade = { + "050": "#edfaf2", + "100": "#dbf4e5", + "200": "#b6e9cb", + "300": "#8eddb2", + "400": "#60d199", + "500": "#07c480", + "600": "#18a46c", + "700": "#1d8558", + "800": "#1e6746", + "900": "#1c4b34", + "950": "#163023" +}; + +const mint = { + "050": "#edfaf7", + "100": "#dbf5ef", + "200": "#b7ebdf", + "300": "#8fe0d0", + "400": "#61d5c0", + "500": "#00cab1", + "600": "#16a994", + "700": "#1d8978", + "800": "#1e6a5e", + "900": "#1c4d44", + "950": "#16312c" +}; + +const teal = { + "050": "#eef9fa", + "100": "#ddf4f6", + "200": "#b9e8ed", + "300": "#92dde4", + "400": "#64d1db", + "500": "#00c5d2", + "600": "#17a5af", + "700": "#1e858e", + "800": "#1f686e", + "900": "#1d4b4f", + "950": "#173033" +}; + +const cyan = { + "050": "#eff9fe", + "100": "#def2fc", + "200": "#bce6f9", + "300": "#96d9f6", + "400": "#68cdf2", + "500": "#08c0ef", + "600": "#1ca1c7", + "700": "#2182a1", + "800": "#22657c", + "900": "#1e4959", + "950": "#182f38" +}; + +const blue = { + "050": "#eff5ff", + "100": "#dfebff", + "200": "#bdd7ff", + "300": "#97c4ff", + "400": "#69b1ff", + "500": "#009fff", + "600": "#1a85d4", + "700": "#216cab", + "800": "#215584", + "900": "#1f3e5e", + "950": "#19283c" +}; + +const indigo = { + "050": "#f5ecff", + "100": "#ead9ff", + "200": "#d3b4fe", + "300": "#ba8ffd", + "400": "#9d6afb", + "500": "#7b43f8", + "600": "#693acf", + "700": "#5731a7", + "800": "#462981", + "900": "#35205c", + "950": "#24173a" +}; + +const violet = { + "050": "#f8edfe", + "100": "#f1dafd", + "200": "#e1b5fa", + "300": "#ce90f7", + "400": "#b969f3", + "500": "#a13cee", + "600": "#8836c7", + "700": "#6f2ea1", + "800": "#58287c", + "900": "#412059", + "950": "#2b1738" +}; + +const purple = { + "050": "#fbedfd", + "100": "#f7dbfb", + "200": "#eeb6f6", + "300": "#e290f0", + "400": "#d568ea", + "500": "#c635e4", + "600": "#a631be", + "700": "#872b9a", + "800": "#692677", + "900": "#4d1f56", + "950": "#321736" +}; + +const magenta = { + "050": "#fdedf7", + "100": "#fbdbee", + "200": "#f7b7dd", + "300": "#f191cc", + "400": "#ea68bc", + "500": "#e130ac", + "600": "#bd2e90", + "700": "#992a75", + "800": "#77255b", + "900": "#561f43", + "950": "#38172b" +}; + +const pink = { + "050": "#ffedf0", + "100": "#ffdbe1", + "200": "#ffb7c4", + "300": "#ff91a8", + "400": "#ff678d", + "500": "#fc2b73", + "600": "#d32a61", + "700": "#aa2850", + "800": "#84243f", + "900": "#5f1e2f", + "950": "#3d1720" +}; + +const rose = { + "050": "#ffeded", + "100": "#ffdbdc", + "200": "#ffb7b9", + "300": "#ff9198", + "400": "#ff6778", + "500": "#fe2d59", + "600": "#d42b4c", + "700": "#ac293f", + "800": "#852432", + "900": "#601e26", + "950": "#3e171b" +}; + +const brown = { + "050": "#f8f2ee", + "100": "#f1e4dd", + "200": "#e3cabb", + "300": "#d3b19b", + "400": "#c3987b", + "500": "#b27f5c", + "600": "#956b4f", + "700": "#7a5841", + "800": "#5f4534", + "900": "#453327", + "950": "#2d221b" +}; + +export const palettes = { + gray, neutral, + red, vermillion, orange, amber, yellow, lime, + green, jade, mint, teal, cyan, blue, + indigo, violet, purple, magenta, pink, rose, + brown +}; diff --git a/src/previews/p3.ts b/src/previews/p3.ts new file mode 100644 index 0000000..f4afdcf --- /dev/null +++ b/src/previews/p3.ts @@ -0,0 +1,111 @@ +// Builds preview/p3.html: a compact comparison of basic and enhanced Display P3 +// conversions for representative Pierre palette colors. +import { srgbHexToP3Color } from "../color"; + +const testColors = [ + { name: "Blue", srgb: "#008cff" }, + { name: "Green", srgb: "#0dbe4e" }, + { name: "Red", srgb: "#ff2e3f" }, + { name: "Purple", srgb: "#c635e4" }, + { name: "Pink", srgb: "#fc2b73" }, + { name: "Orange", srgb: "#fe8c2c" }, + { name: "Cyan", srgb: "#08c0ef" }, + { name: "Teal", srgb: "#00c5d2" }, +]; + +function renderP3Html(): string { + const rows = testColors.map(({ name, srgb }) => { + const basic = srgbHexToP3Color(srgb, false); + const enhanced = srgbHexToP3Color(srgb, true); + return ` + ${name} + ${srgb} + ${basic} + ${enhanced} + `; + }).join("\n"); + + return ` + + + + Pierre Display P3 Preview + + + +
+
+

Pierre Display P3 Preview

+

Basic conversion maps sRGB into Display P3. Enhanced conversion applies the + saturation and luminance boost used by the vibrant theme roles.

+
+ + + + + + + + + + +${rows} + +
ColorsRGBDisplay P3 basicDisplay P3 enhanced
+
+ + +`; +} + +export const p3 = { + filename: "p3.html", + render: renderP3Html, +}; diff --git a/src/palette-preview.ts b/src/previews/palette.ts similarity index 88% rename from src/palette-preview.ts rename to src/previews/palette.ts index eff72cb..d0846f7 100644 --- a/src/palette-preview.ts +++ b/src/previews/palette.ts @@ -1,10 +1,9 @@ -// src/palette-preview.ts -// Generates preview/palette.html — a swatch sheet of every palette in palette.ts. -import { writeFileSync, mkdirSync } from "node:fs"; -import { palettes } from "./palette"; - -mkdirSync("preview", { recursive: true }); +// Builds the palette swatch sheet (every scale in palettes.ts) and returns it as +// HTML. Writing to disk is done by scripts/createPreviews.ts. +import { palettes } from "../palettes"; +/** Render the palette swatch sheet as a standalone HTML document. */ +function renderPaletteHtml(): string { const sections = Object.entries(palettes) .map(([name, scale]) => { // JS reorders integer-indexed keys ahead of string keys, which would move @@ -146,5 +145,10 @@ ${sections} `; -writeFileSync("preview/palette.html", html, "utf8"); -console.log("Wrote preview/palette.html"); + return html; +} + +export const palette = { + filename: "palette.html", + render: renderPaletteHtml, +}; diff --git a/src/roles/Roles.ts b/src/roles/Roles.ts new file mode 100644 index 0000000..d884cbc --- /dev/null +++ b/src/roles/Roles.ts @@ -0,0 +1,33 @@ +export type Roles = { + bg: { + editor: string; // main editor background (brightest in light, darkest in dark) + window: string; // sidebar, activity bar, status bar, title bar, inactive tabs + inset: string; // inputs, dropdowns + elevated: string; // panels, hover backgrounds + }; + fg: { base: string; fg1: string; fg2: string; fg3: string; fg4: string }; + border: { + window: string; // borders for sidebar, activity bar, status bar, title bar + editor: string; // general editor borders + indentGuide: string; // indent guide lines + indentGuideActive: string; // active indent guide line + inset: string; // borders for inputs, dropdowns + elevated: string; // borders for panels + }; + accent: { primary: string; link: string; subtle: string; contrastOnAccent: string }; + states: { merge: string, success: string; danger: string; warn: string; info: string }; + syntax: { + comment: string; string: string; number: string; keyword: string; + regexp: string; func: string; type: string; variable: string; + // Extended token types + operator: string; punctuation: string; constant: string; + parameter: string; namespace: string; decorator: string; + escape: string; invalid: string; tag: string; attribute: string; + }; + ansi: { + black: string; red: string; green: string; yellow: string; + blue: string; magenta: string; cyan: string; white: string; + brightBlack: string; brightRed: string; brightGreen: string; brightYellow: string; + brightBlue: string; brightMagenta: string; brightCyan: string; brightWhite: string; + }; +}; diff --git a/src/roles/dark.ts b/src/roles/dark.ts new file mode 100644 index 0000000..303dbd5 --- /dev/null +++ b/src/roles/dark.ts @@ -0,0 +1,79 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const dark: Roles = { + bg: { + editor: neutral["1040"], + window: neutral["1000"], + inset: neutral["980"], + elevated: neutral["1020"] + }, + fg: { + base: neutral["020"], + fg1: neutral["200"], + fg2: neutral["400"], + fg3: neutral["600"], + fg4: neutral["700"] + }, + border: { + window: neutral["1040"], + editor: neutral["980"], + indentGuide: neutral["980"], + indentGuideActive: neutral["960"], + inset: neutral["980"], + elevated: neutral["980"] + }, + accent: { + primary: blue["500"], + link: blue["500"], + subtle: blue["950"], + contrastOnAccent: neutral["1040"] + }, + states: { + merge: indigo["500"], + success: jade["500"], + danger: red["500"], + warn: yellow["500"], + info: cyan["500"] + }, + syntax: { + comment: neutral["600"], + string: green["400"], + number: cyan["400"], + keyword: pink["400"], + regexp: teal["400"], + func: indigo["400"], + type: purple["400"], + variable: orange["400"], + // Extended token types + operator: cyan["500"], + punctuation: neutral["700"], + constant: yellow["400"], + parameter: neutral["400"], + namespace: amber["500"], + decorator: blue["400"], + escape: mint["400"], + invalid: neutral["020"], + tag: vermillion["400"], + attribute: jade["400"] + }, + ansi: { + black: neutral["1000"], + red: red["500"], + green: green["500"], + yellow: yellow["500"], + blue: blue["500"], + magenta: magenta["500"], + cyan: cyan["500"], + white: neutral["300"], + brightBlack: neutral["1000"], + brightRed: red["500"], + brightGreen: lime["500"], + brightYellow: yellow["500"], + brightBlue: blue["500"], + brightMagenta: magenta["500"], + brightCyan: cyan["500"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/darkSoft.ts b/src/roles/darkSoft.ts new file mode 100644 index 0000000..01b5ceb --- /dev/null +++ b/src/roles/darkSoft.ts @@ -0,0 +1,79 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const darkSoft: Roles = { + bg: { + editor: neutral["1000"], + window: neutral["1020"], + inset: neutral["960"], + elevated: neutral["980"] + }, + fg: { + base: neutral["200"], + fg1: neutral["300"], + fg2: neutral["500"], + fg3: neutral["700"], + fg4: neutral["800"] + }, + border: { + window: neutral["980"], + editor: neutral["940"], + indentGuide: neutral["960"], + indentGuideActive: neutral["940"], + inset: neutral["940"], + elevated: neutral["960"] + }, + accent: { + primary: blue["400"], + link: blue["400"], + subtle: blue["900"], + contrastOnAccent: neutral["1000"] + }, + states: { + merge: indigo["400"], + success: jade["400"], + danger: red["400"], + warn: yellow["400"], + info: cyan["400"] + }, + syntax: { + comment: neutral["700"], + string: green["300"], + number: cyan["300"], + keyword: pink["300"], + regexp: teal["300"], + func: indigo["300"], + type: purple["300"], + variable: orange["300"], + // Extended token types + operator: cyan["400"], + punctuation: neutral["600"], + constant: yellow["300"], + parameter: neutral["500"], + namespace: amber["400"], + decorator: blue["300"], + escape: mint["300"], + invalid: neutral["200"], + tag: vermillion["300"], + attribute: jade["300"] + }, + ansi: { + black: neutral["1000"], + red: red["500"], + green: green["500"], + yellow: yellow["500"], + blue: blue["500"], + magenta: magenta["500"], + cyan: cyan["500"], + white: neutral["300"], + brightBlack: neutral["1000"], + brightRed: red["500"], + brightGreen: lime["500"], + brightYellow: yellow["500"], + brightBlue: blue["500"], + brightMagenta: magenta["500"], + brightCyan: cyan["500"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/index.ts b/src/roles/index.ts new file mode 100644 index 0000000..e513da0 --- /dev/null +++ b/src/roles/index.ts @@ -0,0 +1,5 @@ +export type { Roles } from "./Roles"; +export { light } from "./light"; +export { lightSoft } from "./lightSoft"; +export { dark } from "./dark"; +export { darkSoft } from "./darkSoft"; diff --git a/src/roles/light.ts b/src/roles/light.ts new file mode 100644 index 0000000..dfd8f81 --- /dev/null +++ b/src/roles/light.ts @@ -0,0 +1,80 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const light: Roles = { + bg: { + editor: "#ffffff", + window: neutral["060"], + inset: neutral["080"], + elevated: neutral["040"] + }, + fg: { + base: neutral["1040"], + fg1: neutral["900"], + fg2: neutral["800"], + fg3: neutral["600"], + fg4: neutral["500"] + }, + border: { + window: neutral["100"], + editor: neutral["200"], + indentGuide: neutral["100"], + indentGuideActive: neutral["200"], + inset: neutral["200"], + elevated: neutral["100"] + }, + accent: { + primary: blue["500"], + link: blue["500"], + subtle: blue["100"], + contrastOnAccent: "#ffffff" + }, + states: { + merge: indigo["600"], + success: jade["600"], + danger: red["600"], + warn: yellow["600"], + info: cyan["600"] + }, + syntax: { + comment: neutral["600"], + string: green["600"], + number: cyan["600"], + keyword: pink["600"], + regexp: teal["600"], + func: indigo["600"], + type: purple["600"], + variable: orange["600"], + // Extended token types + operator: cyan["500"], + punctuation: neutral["700"], + constant: yellow["600"], + parameter: neutral["700"], + namespace: amber["600"], + decorator: blue["600"], + escape: mint["600"], + invalid: neutral["1040"], + tag: vermillion["600"], + attribute: jade["600"] + }, + ansi: { + black: neutral["980"], + red: red["600"], + green: jade["600"], + yellow: yellow["600"], + blue: blue["600"], + magenta: magenta["600"], + cyan: cyan["600"], + white: neutral["300"], + // make bright colors match the non-bright counterparts + brightBlack: neutral["980"], + brightRed: red["600"], + brightGreen: lime["600"], + brightYellow: yellow["600"], + brightBlue: blue["600"], + brightMagenta: magenta["600"], + brightCyan: cyan["600"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/lightSoft.ts b/src/roles/lightSoft.ts new file mode 100644 index 0000000..f11f2a7 --- /dev/null +++ b/src/roles/lightSoft.ts @@ -0,0 +1,79 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const lightSoft: Roles = { + bg: { + editor: "#ffffff", + window: neutral["040"], + inset: neutral["060"], + elevated: neutral["020"] + }, + fg: { + base: neutral["800"], + fg1: neutral["700"], + fg2: neutral["600"], + fg3: neutral["500"], + fg4: neutral["400"] + }, + border: { + window: neutral["080"], + editor: neutral["100"], + indentGuide: neutral["080"], + indentGuideActive: neutral["100"], + inset: neutral["200"], + elevated: neutral["100"] + }, + accent: { + primary: blue["500"], + link: blue["500"], + subtle: blue["100"], + contrastOnAccent: "#ffffff" + }, + states: { + merge: indigo["500"], + success: jade["500"], + danger: red["500"], + warn: yellow["500"], + info: cyan["500"] + }, + syntax: { + comment: neutral["500"], + string: green["500"], + number: cyan["500"], + keyword: pink["400"], + regexp: teal["500"], + func: indigo["400"], + type: purple["400"], + variable: orange["500"], + // Extended token types + operator: cyan["400"], + punctuation: neutral["600"], + constant: yellow["500"], + parameter: neutral["600"], + namespace: amber["500"], + decorator: blue["400"], + escape: mint["500"], + invalid: neutral["1000"], + tag: vermillion["500"], + attribute: jade["500"] + }, + ansi: { + black: neutral["980"], + red: red["500"], + green: green["500"], + yellow: yellow["500"], + blue: blue["500"], + magenta: magenta["500"], + cyan: cyan["500"], + white: neutral["300"], + brightBlack: neutral["980"], + brightRed: red["500"], + brightGreen: lime["500"], + brightYellow: yellow["500"], + brightBlue: blue["500"], + brightMagenta: magenta["500"], + brightCyan: cyan["500"], + brightWhite: neutral["300"] + } +}; diff --git a/src/test.ts b/test/test.ts similarity index 98% rename from src/test.ts rename to test/test.ts index 4ebbaa2..567d144 100644 --- a/src/test.ts +++ b/test/test.ts @@ -1,7 +1,7 @@ -// src/test.ts +// test/test.ts import { readFileSync, existsSync } from "node:fs"; -import { light as rolesLight, dark as rolesDark } from "./palette"; -import { makeTheme } from "./theme"; +import { light as rolesLight, dark as rolesDark } from "../src/roles"; +import { createTheme } from "../src/createTheme"; // Color tracking for detecting undefined values const usedColors = new Set(); @@ -52,7 +52,7 @@ function testThemeGeneration( const errors: string[] = []; try { - const theme = makeTheme({ + const theme = createTheme({ name: expectedName, displayName, type: themeType, diff --git a/tsconfig.json b/tsconfig.json index adf931b..bf2df98 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,14 +8,13 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "outDir": "./dist", - "rootDir": "./src" + "outDir": "./dist" }, "ts-node": { "compilerOptions": { "module": "CommonJS" } }, - "include": ["src/**/*"], + "include": ["src/**/*", "scripts/**/*", "test/**/*"], "exclude": ["node_modules"] } From 5c4de29d2922a1dec98d66847def4007536f734e Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:56:51 -0700 Subject: [PATCH 2/6] feat: add color-vision-deficiency themes with an accessibility gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four CVD-tuned themes — Protanopia & Deuteranopia, and Tritanopia, in light and dark. They reuse Pierre's chrome verbatim and re-map only the chromatic signals onto each deficiency's distinguishable hue axis. - src/color/: the supporting color science as discrete modules — cvd (Machado-2009 simulation), contrast (WCAG) and deltaE (CIEDE2000), with invariant self-checks. - src/roles/: the four CVD role sets, derived from the base roles. - test/cvd-test.ts: an objective gate wired into npm test. It simulates every chromatic role for protanopia, deuteranopia and tritanopia under both the linear-RGB and gamma-sRGB conventions, and fails the build if any must-distinguish pair loses separability (CIEDE2000) or legibility (WCAG contrast) under the worst case. Its math is cross-validated against culori, a dev-only dependency. - src/previews/cvd.ts: a normal-vs-simulated proof sheet, with the left column labelled "Colors as defined". Role values are tuned to clear the gate with margin on the tightest pairs. VS Code (package.json) and Zed registrations, the CI theme checks, and ACCESSIBILITY.md (tier rationale, contrast policy) complete the feature. --- .github/workflows/test.yml | 36 +- ACCESSIBILITY.md | 133 + package-lock.json | 19 + package.json | 60 +- scripts/build.ts | 61 +- src/color/contrast.ts | 20 + src/color/cvd.ts | 196 + src/color/deltaE.ts | 88 + src/color/index.ts | 7 +- src/previews/cvd.ts | 179 + src/roles/index.ts | 4 + src/roles/protanDeutanDark.ts | 61 + src/roles/protanDeutanLight.ts | 61 + src/roles/tritanopiaDark.ts | 61 + src/roles/tritanopiaLight.ts | 61 + test/cvd-test.ts | 417 +++ test/test.ts | 27 +- .../pierre-dark-protanopia-deuteranopia.json | 1961 ++++++++++ themes/pierre-dark-tritanopia.json | 1961 ++++++++++ .../pierre-light-protanopia-deuteranopia.json | 1961 ++++++++++ themes/pierre-light-tritanopia.json | 1961 ++++++++++ zed/extension.toml | 2 +- zed/themes/pierre.json | 3148 ++++++++++++++--- 23 files changed, 11875 insertions(+), 610 deletions(-) create mode 100644 ACCESSIBILITY.md create mode 100644 src/color/contrast.ts create mode 100644 src/color/cvd.ts create mode 100644 src/color/deltaE.ts create mode 100644 src/previews/cvd.ts create mode 100644 src/roles/protanDeutanDark.ts create mode 100644 src/roles/protanDeutanLight.ts create mode 100644 src/roles/tritanopiaDark.ts create mode 100644 src/roles/tritanopiaLight.ts create mode 100644 test/cvd-test.ts create mode 100644 themes/pierre-dark-protanopia-deuteranopia.json create mode 100644 themes/pierre-dark-tritanopia.json create mode 100644 themes/pierre-light-protanopia-deuteranopia.json create mode 100644 themes/pierre-light-tritanopia.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 368e2f3..94eac72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,19 +34,37 @@ jobs: - name: Verify theme files exist run: | - if [ ! -f "themes/pierre-light.json" ]; then - echo "❌ pierre-light.json not generated" - exit 1 - fi - if [ ! -f "themes/pierre-dark.json" ]; then - echo "❌ pierre-dark.json not generated" - exit 1 - fi + for theme in \ + pierre-light \ + pierre-light-protanopia-deuteranopia \ + pierre-light-soft \ + pierre-light-tritanopia \ + pierre-light-vibrant \ + pierre-dark \ + pierre-dark-protanopia-deuteranopia \ + pierre-dark-soft \ + pierre-dark-tritanopia \ + pierre-dark-vibrant; do + if [ ! -f "themes/${theme}.json" ]; then + echo "❌ ${theme}.json not generated" + exit 1 + fi + done echo "✅ All theme files generated successfully" - name: Verify ESM wrapper modules exist run: | - for theme in pierre-dark pierre-light pierre-dark-vibrant pierre-light-vibrant; do + for theme in \ + pierre-light \ + pierre-light-protanopia-deuteranopia \ + pierre-light-soft \ + pierre-light-tritanopia \ + pierre-light-vibrant \ + pierre-dark \ + pierre-dark-protanopia-deuteranopia \ + pierre-dark-soft \ + pierre-dark-tritanopia \ + pierre-dark-vibrant; do if [ ! -f "dist/${theme}.mjs" ]; then echo "❌ dist/${theme}.mjs not generated" exit 1 diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md new file mode 100644 index 0000000..1c2b122 --- /dev/null +++ b/ACCESSIBILITY.md @@ -0,0 +1,133 @@ +# Accessibility + +## Color-Vision-Deficiency (CVD) themes + +Pierre ships four themes designed for people with a **color vision deficiency +(CVD)**, the condition commonly called "color blindness." This document explains +what CVD is and how the themes are engineered around it. + +| `name` | label | type | targets | +|---|---|---|---| +| `pierre-light-protanopia-deuteranopia` | Pierre Light Protanopia & Deuteranopia | light | red-green | +| `pierre-light-tritanopia` | Pierre Light Tritanopia | light | blue-yellow | +| `pierre-dark-protanopia-deuteranopia` | Pierre Dark Protanopia & Deuteranopia | dark | red-green | +| `pierre-dark-tritanopia` | Pierre Dark Tritanopia | dark | blue-yellow | + +--- + +### What is CVD? + +The retina senses color with three cone types — **L** (long / "red"), **M** +(medium / "green") and **S** (short / "blue") wavelengths. When one type is +missing or shifted, colors that differ mainly along that cone's axis collapse +into the same perceived color. CVD affects roughly **8% of men and 0.5% of +women**. + +The three dichromacies (the strongest, "complete" forms) we target: + +| Type | Missing cone | Confuses | **Preserved** axis (what can still be told apart) | +|---|---|---|---| +| **Protanopia** | L ("red") | red ↔ green | **blue ↔ orange/yellow** + luminance | +| **Deuteranopia** | M ("green") | red ↔ green | **blue ↔ orange/yellow** + luminance | +| **Tritanopia** | S ("blue") | blue ↔ green (and yellow ↔ violet) | **red ↔ cyan/teal** + luminance | + +> Tritanopia is loosely called "blue-yellow," but blue and yellow differ a lot in +> *luminance* (which is preserved), so they stay apparent. The pair that truly +> collapses is **blue ↔ green**. + +The key consequence for a code editor: a normal theme encodes the most important +signals — **added vs deleted**, **pass vs fail**, **error vs warning** — as +**red vs green**. To a protanope or deuteranope (the most common CVD), red and +green look nearly identical, so those signals become ambiguous. + +--- + +### How the themes are engineered + +Four principles, each enforced or made checkable by the build: + +1. **Identical chrome = family fit.** Each CVD theme reuses the base + `light`/`dark` roles for `bg`, `fg`, and `border` **verbatim**. Windows, text, + and borders are pixel-for-pixel identical to Pierre Light / Pierre Dark — that + is what keeps them recognizably "Pierre." Only the chromatic roles + (`accent`, `states`, `syntax`, `ansi`) change. + +2. **Signals ride the preserved axis.** Every meaningful color is re-mapped onto + the hue axis that the target deficiency keeps: + - **Protan/Deutan:** positive/added → **blue**, negative/deleted → **orange**. + - **Tritan:** positive/added → **teal/cyan**, negative/deleted → **red/vermillion**. + +3. **Luminance is the backup channel.** Under dichromacy there are only ~2 usable + hue poles + luminance, but ~20 chromatic roles. Where two roles must share a + pole (e.g. several "cool" syntax tokens), they are separated by **luminance** + (different palette stops) — the channel CVD users rely on most. + +4. **Reuse the existing palette.** All colors come from scales already in + `src/palettes.ts` (`blue`, `orange`, `teal`, `vermillion`, `magenta`, …). No + off-brand hues were invented. + +#### Role mapping (the actual choices) + +Stops shift lighter on dark backgrounds (mirroring how `dark` shifts vs `light`). + +**Protan/Deutan — axis blue ↔ orange:** + +| Role | Light | Dark | Why | +|---|---|---|---| +| `accent.primary` / `link` | blue 500 | blue 500 | keep Pierre blue (brand) | +| `success` (added) | blue 700 | blue 300 | positive → blue, luminance-split from accent | +| `danger` (deleted/error) | orange 700 | orange 400 | negative → orange | +| `warn` | yellow 500 | yellow 300 | bright caution; big luminance gap from danger | +| `info` | cyan 700 | cyan 400 | cool side; pairs against merge | +| `merge` | violet 700 | violet 400 | blue-violet conflict color | +| `ansi.red` / `ansi.green` | orange / blue | orange / blue | terminal pass/fail separable | +| `syntax.string` (=inserted) | blue 800 | blue 300 | added pole | +| `syntax.tag` (=deleted) | orange 700 | orange 400 | deleted pole | + +**Tritanopia — axis red ↔ cyan/teal:** + +| Role | Light | Dark | Why | +|---|---|---|---| +| `accent.primary` / `link` | blue 500 | blue 500 | reads cyan-blue, clearly ≠ red | +| `success` (added) | teal 700 | teal 300 | positive → teal/cyan | +| `danger` (deleted/error) | vermillion 600 | vermillion 400 | negative → red (preserved) | +| `warn` | amber 600 | amber 400 | caution; ΔE-separated from danger | +| `info` | blue 600 | blue 400 | cyan side | +| `merge` | magenta 700 | magenta 400 | reddish-purple — tritan-safe, far from blue *and* red | +| `ansi.red` / `ansi.green` | vermillion / teal | vermillion / teal | terminal pass/fail separable | +| `syntax.string` (=inserted) | teal 700 | teal 300 | added pole | +| `syntax.tag` (=deleted) | vermillion 600 | vermillion 400 | deleted pole | + +--- + +### The objective test + +A hard gate in `test/` simulates every chromatic role for each deficiency — under +*both* the linear-RGB and gamma-sRGB Machado (2009) conventions — and fails the +build if any must-distinguish pair stops being separable (CIEDE2000 ΔE) or legible +(WCAG contrast). It enforces the tiers and contrast policy below. For how to run and +read the report, see [CONTRIBUTING.md](CONTRIBUTING.md#testing). + +#### Tiers — graded by *what carries the signal when color fails* + +Under dichromacy not every pair can be hue-unique, so we gate hardest where color +is the *only* cue and lean on the editor's built-in non-color cues elsewhere: + +| Tier | Bar | What | Why this bar | +|---|---|---|---| +| **1** | ΔE ≥ 11 | diff add/delete backgrounds, diff inserted/deleted **text**, merge-conflict backgrounds, terminal red/green | color is the **sole** disambiguator — no glyph fallback | +| **2** | ΔE ≥ 8 | diagnostics (error/warn/info), core syntax (keyword/string/variable), comment-vs-code | color **plus** a non-color cue (icon shapes; position) | +| **3** | advisory | git-tree clique, extended syntax | every git entry has an **M/A/D/U/C letter badge**; syntax has bold/italic + position. Reported, not gated. | + +#### Contrast policy + +We hold the themes to WCAG bars, but only the bar that fits how each color renders: + +- **Body text** (editor foreground) → **4.5:1** (SC 1.4.3 normal text). +- **Syntax tokens & meaningful signal colors** → **3:1** (SC 1.4.11 UI / large + text), checked normal **and** after simulation. +- **Report-only** (printed, never fails the build): colors whose canonical/brand + hue is intrinsically high-luminance and that base Pierre itself keeps bright — + `accent.primary`/`link` (brand blue), `warn` (caution yellow/amber), and the + decorative ansi colors. Their *distinguishability* is gated; their raw contrast + is not. diff --git a/package-lock.json b/package-lock.json index 3204fc6..076721b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.3", "license": "MIT", "devDependencies": { + "@types/culori": "^4.0.1", "@vscode/vsce": "^3.2.2", + "culori": "^4.0.2", "nodemon": "^3.1.11", "ts-node": "^10.9.2", "typescript": "^5.9.3" @@ -749,6 +751,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/culori": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/culori/-/culori-4.0.1.tgz", + "integrity": "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.0.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", @@ -1659,6 +1668,16 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/culori": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.2.tgz", + "integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", diff --git a/package.json b/package.json index e34e539..c9e700d 100644 --- a/package.json +++ b/package.json @@ -36,20 +36,40 @@ "uiTheme": "vs", "path": "./themes/pierre-light.json" }, + { + "label": "Pierre Light Protanopia & Deuteranopia", + "uiTheme": "vs", + "path": "./themes/pierre-light-protanopia-deuteranopia.json" + }, { "label": "Pierre Light Soft", "uiTheme": "vs", "path": "./themes/pierre-light-soft.json" }, + { + "label": "Pierre Light Tritanopia", + "uiTheme": "vs", + "path": "./themes/pierre-light-tritanopia.json" + }, { "label": "Pierre Dark", "uiTheme": "vs-dark", "path": "./themes/pierre-dark.json" }, + { + "label": "Pierre Dark Protanopia & Deuteranopia", + "uiTheme": "vs-dark", + "path": "./themes/pierre-dark-protanopia-deuteranopia.json" + }, { "label": "Pierre Dark Soft", "uiTheme": "vs-dark", "path": "./themes/pierre-dark-soft.json" + }, + { + "label": "Pierre Dark Tritanopia", + "uiTheme": "vs-dark", + "path": "./themes/pierre-dark-tritanopia.json" } ] }, @@ -62,7 +82,9 @@ "prepublishOnly": "npm run build" }, "devDependencies": { + "@types/culori": "^4.0.1", "@vscode/vsce": "^3.2.2", + "culori": "^4.0.2", "nodemon": "^3.1.11", "ts-node": "^10.9.2", "typescript": "^5.9.3" @@ -79,30 +101,46 @@ "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, - "./pierre-dark": { - "types": "./dist/pierre-dark.d.mts", - "default": "./dist/pierre-dark.mjs" - }, - "./pierre-dark-soft": { - "types": "./dist/pierre-dark-soft.d.mts", - "default": "./dist/pierre-dark-soft.mjs" - }, "./pierre-light": { "types": "./dist/pierre-light.d.mts", "default": "./dist/pierre-light.mjs" }, + "./pierre-light-protanopia-deuteranopia": { + "types": "./dist/pierre-light-protanopia-deuteranopia.d.mts", + "default": "./dist/pierre-light-protanopia-deuteranopia.mjs" + }, "./pierre-light-soft": { "types": "./dist/pierre-light-soft.d.mts", "default": "./dist/pierre-light-soft.mjs" }, - "./pierre-dark-vibrant": { - "types": "./dist/pierre-dark-vibrant.d.mts", - "default": "./dist/pierre-dark-vibrant.mjs" + "./pierre-light-tritanopia": { + "types": "./dist/pierre-light-tritanopia.d.mts", + "default": "./dist/pierre-light-tritanopia.mjs" }, "./pierre-light-vibrant": { "types": "./dist/pierre-light-vibrant.d.mts", "default": "./dist/pierre-light-vibrant.mjs" }, + "./pierre-dark": { + "types": "./dist/pierre-dark.d.mts", + "default": "./dist/pierre-dark.mjs" + }, + "./pierre-dark-protanopia-deuteranopia": { + "types": "./dist/pierre-dark-protanopia-deuteranopia.d.mts", + "default": "./dist/pierre-dark-protanopia-deuteranopia.mjs" + }, + "./pierre-dark-soft": { + "types": "./dist/pierre-dark-soft.d.mts", + "default": "./dist/pierre-dark-soft.mjs" + }, + "./pierre-dark-tritanopia": { + "types": "./dist/pierre-dark-tritanopia.d.mts", + "default": "./dist/pierre-dark-tritanopia.mjs" + }, + "./pierre-dark-vibrant": { + "types": "./dist/pierre-dark-vibrant.d.mts", + "default": "./dist/pierre-dark-vibrant.mjs" + }, "./themes/*": "./themes/*" }, "publishConfig": { diff --git a/scripts/build.ts b/scripts/build.ts index 4971394..1a08c76 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,5 +1,14 @@ import { writeFileSync, mkdirSync } from "node:fs"; -import { light as rolesLight, lightSoft as rolesLightSoft, dark as rolesDark, darkSoft as rolesDarkSoft } from "../src/roles"; +import { + light as rolesLight, + lightSoft as rolesLightSoft, + dark as rolesDark, + darkSoft as rolesDarkSoft, + protanDeutanLight as rolesProtanDeutanLight, + protanDeutanDark as rolesProtanDeutanDark, + tritanopiaLight as rolesTritanopiaLight, + tritanopiaDark as rolesTritanopiaDark, +} from "../src/roles"; import { createTheme } from "../src/createTheme"; import { createZedTheme } from "../src/createZedTheme"; import { convertRolesToP3 } from "../src/color"; @@ -24,6 +33,15 @@ const vscodeThemes = [ roles: rolesLight }) }, + { + file: "themes/pierre-light-protanopia-deuteranopia.json", + theme: createTheme({ + name: "pierre-light-protanopia-deuteranopia", + displayName: "Pierre Light Protanopia & Deuteranopia", + type: "light", + roles: rolesProtanDeutanLight + }) + }, { file: "themes/pierre-light-soft.json", theme: createTheme({ @@ -33,6 +51,24 @@ const vscodeThemes = [ roles: rolesLightSoft }) }, + { + file: "themes/pierre-light-tritanopia.json", + theme: createTheme({ + name: "pierre-light-tritanopia", + displayName: "Pierre Light Tritanopia", + type: "light", + roles: rolesTritanopiaLight + }) + }, + { + file: "themes/pierre-light-vibrant.json", + theme: createTheme({ + name: "pierre-light-vibrant", + displayName: "Pierre Light Vibrant", + type: "light", + roles: rolesLightP3 + }) + }, { file: "themes/pierre-dark.json", theme: createTheme({ @@ -42,6 +78,15 @@ const vscodeThemes = [ roles: rolesDark }) }, + { + file: "themes/pierre-dark-protanopia-deuteranopia.json", + theme: createTheme({ + name: "pierre-dark-protanopia-deuteranopia", + displayName: "Pierre Dark Protanopia & Deuteranopia", + type: "dark", + roles: rolesProtanDeutanDark + }) + }, { file: "themes/pierre-dark-soft.json", theme: createTheme({ @@ -52,12 +97,12 @@ const vscodeThemes = [ }) }, { - file: "themes/pierre-light-vibrant.json", + file: "themes/pierre-dark-tritanopia.json", theme: createTheme({ - name: "pierre-light-vibrant", - displayName: "Pierre Light Vibrant", - type: "light", - roles: rolesLightP3 + name: "pierre-dark-tritanopia", + displayName: "Pierre Dark Tritanopia", + type: "dark", + roles: rolesTritanopiaDark }) }, { @@ -81,9 +126,13 @@ for (const {file, theme} of vscodeThemes) { // ============================================ const zedTheme = createZedTheme("Pierre", "pierrecomputer", [ { name: "Pierre Light", appearance: "light", roles: rolesLight }, + { name: "Pierre Light Protanopia & Deuteranopia", appearance: "light", roles: rolesProtanDeutanLight }, { name: "Pierre Light Soft", appearance: "light", roles: rolesLightSoft }, + { name: "Pierre Light Tritanopia", appearance: "light", roles: rolesTritanopiaLight }, { name: "Pierre Dark", appearance: "dark", roles: rolesDark }, + { name: "Pierre Dark Protanopia & Deuteranopia", appearance: "dark", roles: rolesProtanDeutanDark }, { name: "Pierre Dark Soft", appearance: "dark", roles: rolesDarkSoft }, + { name: "Pierre Dark Tritanopia", appearance: "dark", roles: rolesTritanopiaDark }, ]); writeFileSync("zed/themes/pierre.json", JSON.stringify(zedTheme, null, 2), "utf8"); diff --git a/src/color/contrast.ts b/src/color/contrast.ts new file mode 100644 index 0000000..f58c98e --- /dev/null +++ b/src/color/contrast.ts @@ -0,0 +1,20 @@ +// WCAG 2.1 contrast ratio — a legibility check, independent of hue. Relative +// luminance uses the linear-RGB luma coefficients; contrast is +// (lighter + 0.05) / (darker + 0.05), from 1:1 (identical) to 21:1 (black on +// white). WCAG AA wants ≥ 4.5:1 for normal text and ≥ 3:1 for large text / UI +// glyphs. The CVD gate re-checks contrast *after* simulation, since simulation +// shifts luminance. + +import { hexToRgb01, srgbToLinear } from "./srgb"; + +function relativeLuminance(hex: string): number { + const [r, g, b] = hexToRgb01(hex).map(srgbToLinear) as [number, number, number]; + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +/** WCAG 2.1 contrast ratio between two sRGB hex colors (order-independent). */ +export function contrastRatio(a: string, b: string): number { + const la = relativeLuminance(a); + const lb = relativeLuminance(b); + return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05); +} diff --git a/src/color/cvd.ts b/src/color/cvd.ts new file mode 100644 index 0000000..b4e7db4 --- /dev/null +++ b/src/color/cvd.ts @@ -0,0 +1,196 @@ +// CVD (Color Vision Deficiency) simulation — "what does this color look like to a +// protan/deutan/tritan viewer?". Consumed by the objective gate (test/cvd-test.ts) +// and the proof-sheet preview (src/previews/cvd.ts). +// +// ───────────────────────────────────────────────────────────────────────────── +// WHAT IS CVD? (the 30-second version for engineers new to this) +// ───────────────────────────────────────────────────────────────────────────── +// "Color vision deficiency" (colloquially "color blindness") means one of the +// three cone types in the retina is missing or shifted, so some hues that look +// distinct to most people collapse into the same perceived color. The three +// dichromacies we target: +// +// • Protanopia — missing L (long/red) cones ┐ both confuse RED ↔ GREEN. +// • Deuteranopia— missing M (medium/green)cones┘ The axis that can still be +// told apart is roughly BLUE ↔ ORANGE/YELLOW. +// • Tritanopia — missing S (short/blue) cones. Confuses BLUE ↔ GREEN. The +// axis that survives is roughly RED ↔ CYAN/TEAL. +// +// Design consequence: a CVD-safe theme must carry meaning (added vs deleted, +// pass vs fail, error vs warning) on the axis that *survives* for that +// deficiency, and lean on LUMINANCE (light/dark) as a second channel whenever a +// hue pole has to be reused. We don't guess whether a palette works — we +// *simulate* how each color looks to a dichromat, then measure separations with +// deltaE2000() and legibility with contrastRatio() (sibling color-science +// modules). The objective gate in test/cvd-test.ts ties those together. +// +// Standards / sources (also cited in ACCESSIBILITY.md): +// • Machado, Oliveira & Fernandes (2009), "A Physiologically-Based Model for +// Simulation of Color Vision Deficiency", IEEE TVCG — the simulation matrices. + +import { hexToRgb01, srgbToLinear, linearToSrgb } from "./srgb"; +import { deltaE2000 } from "./deltaE"; + +export type CVDType = "protan" | "deutan" | "tritan"; + +// ───────────────────────────────────────────────────────────────────────────── +// CVD SIMULATION — Machado, Oliveira & Fernandes (2009) +// ───────────────────────────────────────────────────────────────────────────── +// The Machado model reduces "how a dichromat sees a color" to a single 3×3 +// matrix multiply. The authors published one matrix per deficiency at 11 severity +// steps (0.0 = normal vision … 1.0 = full dichromacy). We embed all 11 (the same +// values the colorspace R package and culori use) but gate the themes at severity +// 1.0 — the worst case — so anything that survives also works for milder +// anomalous trichromacy. +// +// Each matrix row sums to ~1.0, so neutral grays (R=G=B) map to themselves — a +// useful check that the tables were transcribed correctly. +// +// GAMMA CONVENTION (genuinely debated — read before changing). We apply the +// matrices in LINEAR RGB: linearize the sRGB hex, multiply, re-encode. The +// rationale is consistency with the model's derivation — the RGB↔LMS step it +// approximates lives in linear light. But this is a *theoretical* preference, not +// an empirical one: there is no published study proving linear-applied Machado +// matches real dichromat perception better than the gamma-sRGB application used +// by culori and the `colorspace` R package (which it follows). The two only +// diverge noticeably on saturated colors. We implement linear here and the gate +// in test/cvd-test.ts additionally checks culori's gamma convention for Tier-1 +// and Tier-2 distinguishability. + +type Matrix3 = readonly [ + number, number, number, + number, number, number, + number, number, number, +]; + +// Severity 0.0 → 1.0 in 0.1 steps. Index 10 (severity 1.0) is the dichromacy. +const MACHADO: Record = { + protan: [ + [1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000], + [0.856167, 0.182038, -0.038205, 0.029342, 0.955115, 0.015544, -0.002880, -0.001563, 1.004443], + [0.734766, 0.334872, -0.069637, 0.051840, 0.919198, 0.028963, -0.004928, -0.004209, 1.009137], + [0.630323, 0.465641, -0.095964, 0.069181, 0.890046, 0.040773, -0.006308, -0.007724, 1.014032], + [0.539009, 0.579343, -0.118352, 0.082546, 0.866121, 0.051332, -0.007136, -0.011959, 1.019095], + [0.458064, 0.679578, -0.137642, 0.092785, 0.846313, 0.060902, -0.007494, -0.016807, 1.024301], + [0.385450, 0.769005, -0.154455, 0.100526, 0.829802, 0.069673, -0.007442, -0.022190, 1.029632], + [0.319627, 0.849633, -0.169261, 0.106241, 0.815969, 0.077790, -0.007025, -0.028051, 1.035076], + [0.259411, 0.923008, -0.182420, 0.110296, 0.804340, 0.085364, -0.006276, -0.034346, 1.040622], + [0.203876, 0.990338, -0.194214, 0.112975, 0.794542, 0.092483, -0.005222, -0.041043, 1.046265], + [0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998], + ], + deutan: [ + [1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000], + [0.866435, 0.177704, -0.044139, 0.049567, 0.939063, 0.011370, -0.003453, 0.007233, 0.996220], + [0.760729, 0.319078, -0.079807, 0.090568, 0.889315, 0.020117, -0.006027, 0.013325, 0.992702], + [0.675425, 0.433850, -0.109275, 0.125303, 0.847755, 0.026942, -0.007950, 0.018572, 0.989378], + [0.605511, 0.528560, -0.134071, 0.155318, 0.812366, 0.032316, -0.009376, 0.023176, 0.986200], + [0.547494, 0.607765, -0.155259, 0.181692, 0.781742, 0.036566, -0.010410, 0.027275, 0.983136], + [0.498864, 0.674741, -0.173604, 0.205199, 0.754872, 0.039929, -0.011131, 0.030969, 0.980162], + [0.457771, 0.731899, -0.189670, 0.226409, 0.731012, 0.042579, -0.011595, 0.034333, 0.977261], + [0.422823, 0.781057, -0.203881, 0.245752, 0.709602, 0.044646, -0.011843, 0.037423, 0.974421], + [0.392952, 0.823610, -0.216562, 0.263559, 0.690210, 0.046232, -0.011910, 0.040281, 0.971630], + [0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.011820, 0.042940, 0.968881], + ], + tritan: [ + [1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000], + [0.926670, 0.092514, -0.019184, 0.021191, 0.964503, 0.014306, 0.008437, 0.054813, 0.936750], + [0.895720, 0.133330, -0.029050, 0.029997, 0.945400, 0.024603, 0.013027, 0.104707, 0.882266], + [0.905871, 0.127791, -0.033662, 0.026856, 0.941251, 0.031893, 0.013410, 0.148296, 0.838294], + [0.948035, 0.089490, -0.037526, 0.014364, 0.946792, 0.038844, 0.010853, 0.193991, 0.795156], + [1.017277, 0.027029, -0.044306, -0.006113, 0.958479, 0.047634, 0.006379, 0.248708, 0.744913], + [1.104996, -0.046633, -0.058363, -0.032137, 0.971635, 0.060503, 0.001336, 0.317922, 0.680742], + [1.193214, -0.109812, -0.083402, -0.058496, 0.979410, 0.079086, -0.002346, 0.403492, 0.598854], + [1.257728, -0.139648, -0.118081, -0.078003, 0.975409, 0.102594, -0.003316, 0.501214, 0.502102], + [1.278864, -0.125333, -0.153531, -0.084748, 0.957674, 0.127074, -0.000989, 0.601151, 0.399838], + [1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 0.004733, 0.691367, 0.303900], + ], +}; + +function clamp01(v: number): number { + return v < 0 ? 0 : v > 1 ? 1 : v; +} + +function channelToHex(v01: number): string { + return Math.round(clamp01(v01) * 255).toString(16).padStart(2, "0"); +} + +/** + * Simulate how `hex` appears to someone with the given dichromacy. + * + * @param hex sRGB hex color, e.g. "#1a85d4". + * @param type "protan" | "deutan" | "tritan". + * @param severity 0.0 (normal) … 1.0 (full dichromacy). Defaults to 1.0 — the + * worst case the themes are gated against. Snapped to the nearest + * published 0.1 step. + * @returns a new sRGB hex string of the simulated appearance. + */ +export function simulateCVD(hex: string, type: CVDType, severity = 1.0): string { + if (!/^#?[0-9a-fA-F]{6}$/.test(hex)) { + throw new Error(`simulateCVD expects a 6-digit hex color, got: ${hex}`); + } + const step = Math.round(clamp01(severity) * 10); // 0..10 + const m = MACHADO[type][step]; + + // sRGB hex → linear RGB (the matrix lives in linear light, see GAMMA note). + const [r, g, b] = hexToRgb01(hex).map(srgbToLinear) as [number, number, number]; + + const lr = m[0] * r + m[1] * g + m[2] * b; + const lg = m[3] * r + m[4] * g + m[5] * b; + const lb = m[6] * r + m[7] * g + m[8] * b; + + // linear RGB → sRGB hex. + const R = channelToHex(linearToSrgb(clamp01(lr))); + const G = channelToHex(linearToSrgb(clamp01(lg))); + const B = channelToHex(linearToSrgb(clamp01(lb))); + return `#${R}${G}${B}`; +} + +// ───────────────────────────────────────────────────────────────────────────── +// SELF-CHECKS — prove the simulation is wired up correctly +// ───────────────────────────────────────────────────────────────────────────── +// Provable invariants of the model: severity-0 is the identity, neutral grays are +// preserved (every Machado row sums to ~1), and the expected confusable axis +// actually collapses. (contrast/ΔE are additionally cross-checked against culori +// in test/cvd-test.ts.) These run from cvd-test.ts. + +export type SelfCheckResult = { name: string; ok: boolean; detail: string }; + +export function cvdSelfChecks(): SelfCheckResult[] { + const results: SelfCheckResult[] = []; + const near = (a: string, b: string, tol = 2) => { + const [r1, g1, b1] = hexToRgb01(a).map((x) => x * 255); + const [r2, g2, b2] = hexToRgb01(b).map((x) => x * 255); + return Math.abs(r1 - r2) <= tol && Math.abs(g1 - g2) <= tol && Math.abs(b1 - b2) <= tol; + }; + + // (a) Severity 0 is the identity transform for every type. + for (const t of ["protan", "deutan", "tritan"] as CVDType[]) { + const samples = ["#1a85d4", "#d52c36", "#199f43", "#ffca00"]; + const ok = samples.every((h) => near(simulateCVD(h, t, 0), h, 1)); + results.push({ name: `severity-0 identity (${t})`, ok, detail: ok ? "input preserved" : "drifted" }); + } + + // (b) Neutral axis is preserved: gray/white/black map to themselves. + for (const t of ["protan", "deutan", "tritan"] as CVDType[]) { + const grays = ["#000000", "#808080", "#bcbcbc", "#ffffff"]; + const ok = grays.every((h) => near(simulateCVD(h, t, 1), h, 3)); + results.push({ name: `neutral axis preserved (${t})`, ok, detail: ok ? "grays stable" : "grays shifted" }); + } + + // (c) Behavioral: the simulation actually *removes* the confusable axis. + // RED↔GREEN collapses under protan/deutan; BLUE↔GREEN under tritan. + // (Tritanopia is loosely "blue-yellow", but blue & yellow differ in + // luminance — which tritanopes keep — so blue↔green is the real collapse.) + const red = "#ff2e3f", green = "#199f43", blue = "#009fff"; + const collapses = (name: string, t: CVDType, x: string, y: string) => { + const before = deltaE2000(x, y); + const after = deltaE2000(simulateCVD(x, t, 1), simulateCVD(y, t, 1)); + const ok = after < before * 0.5; // confusable axis loses at least half its separation + results.push({ name, ok, detail: `ΔE ${before.toFixed(1)} → ${after.toFixed(1)} under ${t}` }); + }; + collapses("protan collapses red↔green", "protan", red, green); + collapses("deutan collapses red↔green", "deutan", red, green); + collapses("tritan collapses blue↔green", "tritan", blue, green); + + return results; +} diff --git a/src/color/deltaE.ts b/src/color/deltaE.ts new file mode 100644 index 0000000..56824ee --- /dev/null +++ b/src/color/deltaE.ts @@ -0,0 +1,88 @@ +// CIEDE2000 perceptual color difference (ΔE₀₀) — "how far apart do two colors +// look?". The modern CIE standard, tuned to human perception in CIE Lab space. +// Rough reading: <1 imperceptible, ~2–3 just noticeable, > ~10 "clearly +// different / not confused". The CVD gate computes ΔE between the *simulated* +// versions of two roles to prove a dichromat can still tell them apart. +// Pipeline: sRGB → linear → XYZ (D65) → Lab → CIEDE2000. + +import { hexToRgb01, srgbToLinear } from "./srgb"; + +function hexToLab(hex: string): [number, number, number] { + const [r, g, b] = hexToRgb01(hex).map(srgbToLinear) as [number, number, number]; + // linear sRGB → CIE XYZ (D65, Y of white = 1). + const x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; + const y = 0.2126729 * r + 0.7151522 * g + 0.072175 * b; + const z = 0.0193339 * r + 0.119192 * g + 0.9503041 * b; + // XYZ → Lab, D65 reference white. + const xn = 0.95047, yn = 1.0, zn = 1.08883; + const f = (t: number) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116); + const fx = f(x / xn), fy = f(y / yn), fz = f(z / zn); + return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)]; +} + +const deg2rad = (d: number) => (d * Math.PI) / 180; +const rad2deg = (r: number) => (r * 180) / Math.PI; + +/** CIEDE2000 color difference (ΔE₀₀) between two sRGB hex colors. */ +export function deltaE2000(hexA: string, hexB: string): number { + const [L1, a1, b1] = hexToLab(hexA); + const [L2, a2, b2] = hexToLab(hexB); + + const avgL = (L1 + L2) / 2; + const C1 = Math.hypot(a1, b1); + const C2 = Math.hypot(a2, b2); + const avgC = (C1 + C2) / 2; + + const G = 0.5 * (1 - Math.sqrt(Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7)))); + const a1p = a1 * (1 + G); + const a2p = a2 * (1 + G); + const C1p = Math.hypot(a1p, b1); + const C2p = Math.hypot(a2p, b2); + const avgCp = (C1p + C2p) / 2; + + const hp = (ap: number, bp: number) => { + if (ap === 0 && bp === 0) return 0; + let h = rad2deg(Math.atan2(bp, ap)); + if (h < 0) h += 360; + return h; + }; + const h1p = hp(a1p, b1); + const h2p = hp(a2p, b2); + + let dhp: number; + if (C1p * C2p === 0) dhp = 0; + else if (Math.abs(h2p - h1p) <= 180) dhp = h2p - h1p; + else if (h2p - h1p > 180) dhp = h2p - h1p - 360; + else dhp = h2p - h1p + 360; + + const dLp = L2 - L1; + const dCp = C2p - C1p; + const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(deg2rad(dhp) / 2); + + let avgHp: number; + if (C1p * C2p === 0) avgHp = h1p + h2p; + else if (Math.abs(h1p - h2p) <= 180) avgHp = (h1p + h2p) / 2; + else if (h1p + h2p < 360) avgHp = (h1p + h2p + 360) / 2; + else avgHp = (h1p + h2p - 360) / 2; + + const T = + 1 - + 0.17 * Math.cos(deg2rad(avgHp - 30)) + + 0.24 * Math.cos(deg2rad(2 * avgHp)) + + 0.32 * Math.cos(deg2rad(3 * avgHp + 6)) - + 0.2 * Math.cos(deg2rad(4 * avgHp - 63)); + + const dTheta = 30 * Math.exp(-Math.pow((avgHp - 275) / 25, 2)); + const Rc = 2 * Math.sqrt(Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7))); + const Sl = 1 + (0.015 * Math.pow(avgL - 50, 2)) / Math.sqrt(20 + Math.pow(avgL - 50, 2)); + const Sc = 1 + 0.045 * avgCp; + const Sh = 1 + 0.015 * avgCp * T; + const Rt = -Math.sin(deg2rad(2 * dTheta)) * Rc; + + return Math.sqrt( + Math.pow(dLp / Sl, 2) + + Math.pow(dCp / Sc, 2) + + Math.pow(dHp / Sh, 2) + + Rt * (dCp / Sc) * (dHp / Sh) + ); +} diff --git a/src/color/index.ts b/src/color/index.ts index c9dc2cf..62b0da6 100644 --- a/src/color/index.ts +++ b/src/color/index.ts @@ -1,6 +1,9 @@ // Color science: pure, dependency-free color math shared by the theme build -// (Display-P3 vibrant variants) and the previews. Each concern is a discrete -// module; this barrel re-exports the public surface. +// (Display-P3 vibrant variants), the previews, and the CVD accessibility gate. +// Each concern is a discrete module; this barrel re-exports the public surface. export { hexToRgb01, srgbToLinear, linearToSrgb } from "./srgb"; export { srgbHexToP3Color, convertRolesToP3 } from "./p3"; +export { contrastRatio } from "./contrast"; +export { deltaE2000 } from "./deltaE"; +export { simulateCVD, cvdSelfChecks, type CVDType, type SelfCheckResult } from "./cvd"; diff --git a/src/previews/cvd.ts b/src/previews/cvd.ts new file mode 100644 index 0000000..655fdf3 --- /dev/null +++ b/src/previews/cvd.ts @@ -0,0 +1,179 @@ +// src/previews/cvd.ts +// Builds preview/cvd.html (returned as a string; written by scripts/createPreviews.ts) — +// a human-eyeballing companion to the objective gate (test/cvd-test.ts). For each +// CVD (color-vision-deficiency) theme it shows, +// side by side: the colors as defined in the theme, and the same colors pushed +// through the Machado-2009 simulation for that deficiency — i.e. what a person +// with that CVD sees. If the design works, the simulated column still reads as +// "added vs deleted", "pass vs fail", "error vs warning", etc. +// +// The numbers are proven by the gate; this page is for sanity-checking that the +// proof matches intuition. Run with `npm run preview`. +import { + protanDeutanLight, protanDeutanDark, tritanopiaLight, tritanopiaDark, + type Roles, +} from "../roles"; +import { simulateCVD, type CVDType } from "../color"; + +type View = { title: string; roles: Roles; cvd: CVDType; type: "light" | "dark" }; +// The protan/deutan theme targets two deficiencies, so it gets one row per +// deficiency (protanopia and deuteranopia) — both are gated in tests. The +// "normal vision" column is the same in both rows, which is expected. +const VIEWS: View[] = [ + // Light + { title: "Pierre Light Protanopia & Deuteranopia", roles: protanDeutanLight, cvd: "protan", type: "light" }, + { title: "Pierre Light Protanopia & Deuteranopia", roles: protanDeutanLight, cvd: "deutan", type: "light" }, + { title: "Pierre Light Tritanopia", roles: tritanopiaLight, cvd: "tritan", type: "light" }, + // Dark + { title: "Pierre Dark Protanopia & Deuteranopia", roles: protanDeutanDark, cvd: "protan", type: "dark" }, + { title: "Pierre Dark Protanopia & Deuteranopia", roles: protanDeutanDark, cvd: "deutan", type: "dark" }, + { title: "Pierre Dark Tritanopia", roles: tritanopiaDark, cvd: "tritan", type: "dark" }, +]; + +// Simulate every hex in a Roles object → "what the CVD viewer sees". +function simulateRoles(r: Roles, cvd: CVDType): Roles { + const walk = (o: any): any => { + if (typeof o === "string") return /^#[0-9a-fA-F]{6}$/.test(o) ? simulateCVD(o, cvd) : o; + const out: any = {}; + for (const [k, v] of Object.entries(o)) out[k] = walk(v); + return out; + }; + return walk(r); +} + +// Blend a foreground color over a background at the given alpha (for diff tints). +function mix(fg: string, bg: string, a: number): string { + const h = (c: string) => [1, 3, 5].map((i) => parseInt(c.slice(i, i + 2), 16)); + const [r1, g1, b1] = h(fg), [r2, g2, b2] = h(bg); + const m = (x: number, y: number) => Math.round(x * a + y * (1 - a)); + return `#${[m(r1, r2), m(g1, g2), m(b1, b2)].map((x) => x.toString(16).padStart(2, "0")).join("")}`; +} + +const swatch = (label: string, hex: string) => + `
${label}${hex}
`; + +// A mini code-review / editor mock built only from a theme's roles. +function mock(r: Roles): string { + const ed = r.bg.editor, win = r.bg.window; + const addBg = mix(r.states.success, ed, 0.18); + const delBg = mix(r.states.danger, ed, 0.18); + const dot = (c: string, letter: string) => + `${letter}`; + return ` +
+
git
+
+
${dot(r.states.success, "A")}added.ts
+
${dot(r.accent.primary, "M")}changed.ts
+
${dot(r.states.danger, "D")}removed.ts
+
${dot(r.states.merge, "C")}conflict.ts
+
+
+
+ "inserted line"
+
- "deleted line"
+
+
+ const + total = + sum(42); + // note +
+
+ ✓ 12 passed + ✗ 3 failed + ⚠ 1 warning +
+
`; +} + +const CVD_LABEL: Record = { + protan: "protanopia", + deutan: "deuteranopia", + tritan: "tritanopia", +}; + +function section(v: View): string { + const sim = simulateRoles(v.roles, v.cvd); + const roleList: [string, (r: Roles) => string][] = [ + ["success (added)", (r) => r.states.success], + ["danger (deleted)", (r) => r.states.danger], + ["warn", (r) => r.states.warn], + ["info", (r) => r.states.info], + ["merge", (r) => r.states.merge], + ["accent", (r) => r.accent.primary], + ["ansi.red", (r) => r.ansi.red], + ["ansi.green", (r) => r.ansi.green], + ["string", (r) => r.syntax.string], + ["keyword", (r) => r.syntax.keyword], + ["variable", (r) => r.syntax.variable], + ["func", (r) => r.syntax.func], + ]; + const swatches = (r: Roles) => roleList.map(([n, sel]) => swatch(n, sel(r))).join(""); + return ` +
+

${v.title} ${v.type} · simulated as ${CVD_LABEL[v.cvd]}

+
+
+

Colors as defined

+
${swatches(v.roles)}
+ ${mock(v.roles)} +
+
+

Simulated (${CVD_LABEL[v.cvd]})

+
${swatches(sim)}
+ ${mock(sim)} +
+
+
`; +} + +/** Render the CVD normal-vs-simulated proof sheet as a standalone HTML document. */ +function renderCvdHtml(): string { + return ` +Pierre CVD Themes + + +
+

Pierre CVD Themes — proof sheet

+

Left = the colors as defined in the theme. Right = the same colors pushed through + the Machado-2009 simulation for that deficiency. If the design holds, the right column + still reads as added-vs-deleted, pass-vs-fail, and error-vs-warning. + Objective ΔE/contrast checks live in test/cvd-test.ts (run via npm test).

+
+
+ ${VIEWS.map(section).join("\n")} +
+ +`; +} + +export const cvd = { + filename: "cvd.html", + render: renderCvdHtml, +}; diff --git a/src/roles/index.ts b/src/roles/index.ts index e513da0..ef2f616 100644 --- a/src/roles/index.ts +++ b/src/roles/index.ts @@ -3,3 +3,7 @@ export { light } from "./light"; export { lightSoft } from "./lightSoft"; export { dark } from "./dark"; export { darkSoft } from "./darkSoft"; +export { protanDeutanLight } from "./protanDeutanLight"; +export { protanDeutanDark } from "./protanDeutanDark"; +export { tritanopiaLight } from "./tritanopiaLight"; +export { tritanopiaDark } from "./tritanopiaDark"; diff --git a/src/roles/protanDeutanDark.ts b/src/roles/protanDeutanDark.ts new file mode 100644 index 0000000..5d4b764 --- /dev/null +++ b/src/roles/protanDeutanDark.ts @@ -0,0 +1,61 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +import { dark } from "./dark"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const protanDeutanDark: Roles = { + bg: dark.bg, + fg: dark.fg, + border: dark.border, + accent: { + primary: blue["500"], + link: blue["500"], + subtle: blue["950"], + contrastOnAccent: neutral["1040"] + }, + states: { + success: blue["300"], // added → light blue (luminance-split from accent blue 500) + danger: orange["500"], // deleted/error → orange (deeper stop widens the luminance gap from warn yellow) + warn: yellow["300"], // bright caution yellow + info: cyan["400"], // cool side + merge: violet["400"] // blue-violet + }, + syntax: { + comment: neutral["600"], + string: blue["300"], // = diff inserted → blue + number: cyan["300"], + keyword: violet["400"], + regexp: cyan["400"], + func: indigo["300"], + type: purple["300"], + variable: orange["400"], // orange pole + operator: cyan["500"], + punctuation: neutral["700"], + constant: amber["300"], + parameter: neutral["400"], + namespace: amber["400"], + decorator: blue["400"], + escape: teal["400"], + invalid: neutral["020"], + tag: orange["400"], // = diff deleted → orange + attribute: amber["400"] + }, + ansi: { + black: neutral["1000"], + red: orange["400"], + green: blue["400"], + yellow: yellow["400"], + blue: blue["500"], + magenta: violet["400"], + cyan: cyan["400"], + white: neutral["300"], + brightBlack: neutral["1000"], + brightRed: orange["300"], + brightGreen: blue["300"], + brightYellow: yellow["300"], + brightBlue: blue["400"], + brightMagenta: violet["300"], + brightCyan: cyan["300"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/protanDeutanLight.ts b/src/roles/protanDeutanLight.ts new file mode 100644 index 0000000..cd289d4 --- /dev/null +++ b/src/roles/protanDeutanLight.ts @@ -0,0 +1,61 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +import { light } from "./light"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const protanDeutanLight: Roles = { + bg: light.bg, + fg: light.fg, + border: light.border, + accent: { + primary: blue["500"], // keep Pierre blue (brand). Intrinsically bright → + link: blue["500"], // contrast is report-only in the gate, like base Pierre. + subtle: blue["100"], + contrastOnAccent: "#ffffff" + }, + states: { + success: blue["700"], // added/positive → blue pole (diff-add bg, git "A") + danger: orange["700"], // deleted/error → orange pole (diff-del bg, git "D") + warn: yellow["500"], // bright "caution" yellow; ΔE-separated from danger by big luminance gap + info: cyan["700"], // cool side; pairs against merge in the conflict view + merge: violet["800"] // blue-violet conflict color; deep stop widens the luminance split from info + }, + syntax: { + comment: neutral["600"], // neutral — luminance only + string: blue["800"], // = diff "inserted" text → deep blue (≥8 ΔE from keyword) + number: cyan["700"], // cool side + keyword: violet["600"], // blue-violet + regexp: cyan["700"], + func: indigo["700"], // deep blue-violet + type: purple["600"], // reads blue-violet under red-green CVD + variable: orange["700"], // orange pole — workhorse identifier (deep stop clears 3:1 after simulation) + operator: cyan["700"], + punctuation: neutral["700"], + constant: amber["700"], // orange side, deep + parameter: neutral["700"], + namespace: amber["700"], + decorator: blue["700"], + escape: teal["700"], + invalid: neutral["1040"], + tag: orange["700"], // = diff "deleted" text → orange pole + attribute: amber["700"] + }, + ansi: { + black: neutral["980"], + red: orange["700"], // terminal "error red" → orange so red≠green + green: blue["700"], // terminal "success green" → blue + yellow: yellow["500"], + blue: blue["600"], + magenta: violet["600"], + cyan: cyan["700"], + white: neutral["300"], + brightBlack: neutral["980"], + brightRed: orange["600"], + brightGreen: blue["600"], + brightYellow: yellow["500"], + brightBlue: blue["500"], + brightMagenta: violet["500"], + brightCyan: cyan["600"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/tritanopiaDark.ts b/src/roles/tritanopiaDark.ts new file mode 100644 index 0000000..04ee85d --- /dev/null +++ b/src/roles/tritanopiaDark.ts @@ -0,0 +1,61 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +import { dark } from "./dark"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const tritanopiaDark: Roles = { + bg: dark.bg, + fg: dark.fg, + border: dark.border, + accent: { + primary: blue["500"], + link: blue["500"], + subtle: blue["950"], + contrastOnAccent: neutral["1040"] + }, + states: { + success: teal["300"], // added → teal + danger: vermillion["400"],// deleted/error → red + warn: amber["400"], // caution + info: blue["400"], // cyan side + merge: magenta["400"] // reddish-purple + }, + syntax: { + comment: neutral["600"], + string: teal["300"], // = diff inserted → teal + number: blue["400"], + keyword: purple["400"], // reddish-purple (≥8 ΔE from red variable) + regexp: teal["400"], + func: blue["300"], + type: magenta["400"], + variable: vermillion["400"], // red pole + operator: teal["500"], + punctuation: neutral["700"], + constant: amber["300"], + parameter: neutral["400"], + namespace: vermillion["400"], + decorator: blue["400"], + escape: teal["400"], + invalid: neutral["020"], + tag: vermillion["400"], // = diff deleted → red + attribute: teal["400"] + }, + ansi: { + black: neutral["1000"], + red: vermillion["400"], + green: teal["400"], + yellow: amber["400"], + blue: blue["400"], + magenta: magenta["400"], + cyan: teal["300"], + white: neutral["300"], + brightBlack: neutral["1000"], + brightRed: vermillion["300"], + brightGreen: teal["300"], + brightYellow: amber["300"], + brightBlue: blue["300"], + brightMagenta: magenta["300"], + brightCyan: teal["300"], + brightWhite: neutral["300"] + } +}; diff --git a/src/roles/tritanopiaLight.ts b/src/roles/tritanopiaLight.ts new file mode 100644 index 0000000..6c2f80a --- /dev/null +++ b/src/roles/tritanopiaLight.ts @@ -0,0 +1,61 @@ +import type { Roles } from "./Roles"; +import { palettes } from "../palettes"; +import { light } from "./light"; +const { gray, neutral, red, vermillion, orange, amber, yellow, lime, green, jade, mint, teal, cyan, blue, indigo, violet, purple, magenta, pink, rose, brown } = palettes; + +export const tritanopiaLight: Roles = { + bg: light.bg, + fg: light.fg, + border: light.border, + accent: { + primary: blue["500"], // keep Pierre blue (reads cyan-blue, clearly ≠ red) + link: blue["500"], + subtle: blue["100"], + contrastOnAccent: "#ffffff" + }, + states: { + success: teal["700"], // added/positive → teal (cyan pole) + danger: vermillion["600"],// deleted/error → red (red pole preserved under tritanopia) + warn: amber["500"], // caution; brighter stop widens the luminance gap from danger vermillion (report-only contrast) + info: blue["600"], // cyan side + merge: magenta["700"] // reddish-purple — tritan-safe, far from blue/teal AND from vermillion + }, + syntax: { + comment: neutral["600"], + string: teal["700"], // = diff inserted → teal (cyan pole) + number: blue["600"], // cyan side + keyword: purple["600"], // reddish-purple (≥8 ΔE from red variable) + regexp: teal["700"], + func: blue["700"], // deep blue + type: magenta["600"], // reddish-purple + variable: vermillion["700"], // red pole — workhorse identifier + operator: teal["700"], + punctuation: neutral["700"], + constant: amber["700"], + parameter: neutral["700"], + namespace: vermillion["600"], + decorator: blue["600"], + escape: teal["700"], + invalid: neutral["1040"], + tag: vermillion["600"], // = diff deleted → red pole + attribute: teal["700"] + }, + ansi: { + black: neutral["980"], + red: vermillion["600"], // red preserved + green: teal["700"], // success green → teal so red≠green + yellow: amber["600"], + blue: blue["600"], + magenta: magenta["700"], + cyan: teal["600"], + white: neutral["300"], + brightBlack: neutral["980"], + brightRed: vermillion["500"], + brightGreen: teal["600"], + brightYellow: amber["500"], + brightBlue: blue["500"], + brightMagenta: magenta["600"], + brightCyan: teal["500"], + brightWhite: neutral["300"] + } +}; diff --git a/test/cvd-test.ts b/test/cvd-test.ts new file mode 100644 index 0000000..be06e6b --- /dev/null +++ b/test/cvd-test.ts @@ -0,0 +1,417 @@ +// test/cvd-test.ts +// +// OBJECTIVE GATE for the CVD (Color Vision Deficiency) themes. +// +// A CVD-safe palette is a claim that must be *proven*, not eyeballed. This file +// turns the design rules from src/roles into machine-checked assertions and is +// run as part of `npm test`. It fails the build (exit non-zero) if any Tier-1 or +// Tier-2 requirement regresses, so the themes cannot silently drift back into a +// red-green-ambiguous state. +// +// HOW IT WORKS (for engineers new to CVD): +// 1. simulate — recolor each role as a protan/deutan/tritan viewer would see it +// (Machado 2009 model, src/color/cvd.ts), at full dichromacy (severity 1.0). +// 2. distinguishability — for every pair of roles that co-occurs on screen, +// measure the perceptual distance (ΔE₀₀) between the *simulated* colors. If +// two signals (e.g. "added" vs "deleted") still look far apart, a CVD user +// can tell them apart. ΔE₀₀ > ~10 ≈ "clearly different". +// 3. contrast — WCAG legibility of each foreground vs its background, checked +// both normally and after simulation (simulation shifts luminance). +// +// TIERS — graded by WHAT CARRIES THE SIGNAL WHEN COLOR FAILS. Under full +// dichromacy there are only ~2 usable hue poles + luminance but ~20 chromatic +// roles, so not every pair can be hue-unique. We gate hardest where color is the +// *only* cue, and lean on the editor's built-in non-color cues elsewhere. +// • Tier 1 (hard gate, ΔE ≥ 11) — color is the SOLE disambiguator: +// diff add/delete backgrounds (success/danger), diff inserted/deleted TEXT +// (string/tag), merge-conflict backgrounds (merge/info), terminal pass/fail +// (ansi red/green). None of these has a glyph fallback. +// • Tier 2 (hard gate, ΔE ≥ 8) — color PLUS a non-color cue: +// diagnostics (error/warn/info — distinct icon SHAPES), and the +// highest-frequency syntax adjacencies + comment-vs-code. +// • Tier 3 (advisory, reported only) — color is tertiary: +// the git-tree clique (every entry already carries an M/A/D/U/C letter +// badge) and extended syntax (bold/italic + position carry it). Reported +// so regressions stay visible without blocking the build. + +import { + protanDeutanLight, + protanDeutanDark, + tritanopiaLight, + tritanopiaDark, + type Roles, +} from "../src/roles"; +import { simulateCVD, contrastRatio, deltaE2000, cvdSelfChecks, type CVDType } from "../src/color"; +import { + filterDeficiencyProt, filterDeficiencyDeuter, filterDeficiencyTrit, + differenceCiede2000, wcagContrast, formatHex, +} from "culori"; + +// ── Thresholds (standards-derived, tuned empirically during build-out) ────── +const TIER1_DELTA_E = 11; // co-occurring opposite-meaning signals +const TIER2_DELTA_E = 8; // critical syntax adjacencies +const TEXT_CONTRAST = 4.5; // WCAG 2.1 SC 1.4.3 normal text +const UI_CONTRAST = 3.0; // WCAG 2.1 SC 1.4.11 UI glyphs / SC 1.4.3 large text + +// ── Pair definitions ──────────────────────────────────────────────────────── +// A selector plucks one concrete hex from a resolved Roles object. +type Sel = (r: Roles) => string; +type Pair = { tier: 1 | 2 | 3; label: string; a: Sel; b: Sel; group: string }; + +const S = { + success: (r: Roles) => r.states.success, + danger: (r: Roles) => r.states.danger, + warn: (r: Roles) => r.states.warn, + info: (r: Roles) => r.states.info, + merge: (r: Roles) => r.states.merge, + accent: (r: Roles) => r.accent.primary, + ansiRed: (r: Roles) => r.ansi.red, + ansiGreen: (r: Roles) => r.ansi.green, + comment: (r: Roles) => r.syntax.comment, + string: (r: Roles) => r.syntax.string, + keyword: (r: Roles) => r.syntax.keyword, + variable: (r: Roles) => r.syntax.variable, + func: (r: Roles) => r.syntax.func, + type: (r: Roles) => r.syntax.type, + number: (r: Roles) => r.syntax.number, + tag: (r: Roles) => r.syntax.tag, // = diff "deleted" text token +}; + +// Generate every unordered pair within a group of named selectors. +function clique(group: string, tier: 1 | 2 | 3, members: [string, Sel][]): Pair[] { + const out: Pair[] = []; + for (let i = 0; i < members.length; i++) { + for (let j = i + 1; j < members.length; j++) { + out.push({ + tier, + group, + label: `${members[i][0]} vs ${members[j][0]}`, + a: members[i][1], + b: members[j][1], + }); + } + } + return out; +} + +const PAIRS: Pair[] = [ + // ── Tier 1 — color is the only cue (ΔE ≥ 11) ────────────────────────────── + // Diff gutter / overview ruler: added vs deleted backgrounds (no glyph). + { tier: 1, group: "diff bg", label: "success(added) vs danger(deleted)", a: S.success, b: S.danger }, + // Diff TEXT tokens: inserted vs deleted, the semantic core of a review. + { tier: 1, group: "diff text", label: "string(inserted) vs tag(deleted)", a: S.string, b: S.tag }, + // Merge conflict view: current(merge) vs incoming(info) tinted backgrounds. + { tier: 1, group: "merge conflict", label: "merge vs info", a: S.merge, b: S.info }, + // Terminal pass/fail. + { tier: 1, group: "terminal", label: "ansi.red vs ansi.green", a: S.ansiRed, b: S.ansiGreen }, + + // ── Tier 2 — color + a non-color cue (ΔE ≥ 8) ───────────────────────────── + // Diagnostics & notifications: error/warn/info — backed by distinct icon + // shapes (✕ / △ / ⓘ), so color is the secondary channel. + ...clique("diagnostics", 2, [ + ["danger", S.danger], + ["warn", S.warn], + ["info", S.info], + ]), + // Comment must never be mistaken for live code. + ...clique("comment vs code", 2, [ + ["comment", S.comment], + ["string", S.string], + ["keyword", S.keyword], + ["variable", S.variable], + ]).filter((p) => p.label.startsWith("comment")), + // The three highest-frequency code tokens. + ...clique("core syntax", 2, [ + ["keyword", S.keyword], + ["string", S.string], + ["variable", S.variable], + ]), + + // ── Tier 3 (advisory) ───────────────────────────────────────────────────── + // Git tree: added/modified/deleted/conflict — every entry has an M/A/D/U/C + // letter badge, so identical-looking colors are still unambiguous. Reported. + ...clique("git tree", 3, [ + ["success", S.success], + ["danger", S.danger], + ["merge", S.merge], + ["accent.primary", S.accent], + ]), + ...clique("extended syntax", 3, [ + ["func", S.func], + ["type", S.type], + ["number", S.number], + ["keyword", S.keyword], + ["string", S.string], + ["variable", S.variable], + ]), +]; + +// ── Theme registry ────────────────────────────────────────────────────────── +// Each protan/deutan theme must satisfy the gate under BOTH protan and deutan +// simulation; tritanopia themes under tritan. +type CvdThemeDef = { name: string; roles: Roles; cvds: CVDType[] }; +const THEMES: CvdThemeDef[] = [ + { name: "Pierre Light Protanopia & Deuteranopia", roles: protanDeutanLight, cvds: ["protan", "deutan"] }, + { name: "Pierre Dark Protanopia & Deuteranopia", roles: protanDeutanDark, cvds: ["protan", "deutan"] }, + { name: "Pierre Light Tritanopia", roles: tritanopiaLight, cvds: ["tritan"] }, + { name: "Pierre Dark Tritanopia", roles: tritanopiaDark, cvds: ["tritan"] }, +]; + +// CONTRAST POLICY. We hold the CVD themes to WCAG bars, but only the bar that +// fits how each color renders — and we do NOT impose a bar the *standard* Pierre +// themes never met (base Pierre LIGHT runs syntax/signal colors at 2–4.5:1 by +// design; see ACCESSIBILITY.md): +// • Body text (editor foreground) → 4.5:1 (SC 1.4.3 normal text) +// • Syntax tokens & meaningful signal colors → 3.0:1 (SC 1.4.11 UI / large) +// checked NORMAL and AFTER simulation (simulation shifts luminance). +// • Report-only (printed, never fails): colors whose canonical/brand hue is +// intrinsically high-luminance and which base Pierre itself keeps bright — +// `accent.primary`/`link` (brand blue), `warn` (caution yellow/amber), and +// the decorative ansi colors. Their *distinguishability* (ΔE) is what the +// gate enforces, not their raw contrast. +const SYNTAX_REPORT_ONLY = new Set([]); // (none — all syntax tokens gated) +// Syntax tokens are text-on-editor at the 3:1 bar; `invalid` is intentionally a +// background-tinted color, not a foreground, so it is excluded. +function syntaxForegrounds(r: Roles): [string, string][] { + return Object.entries(r.syntax).filter(([k]) => k !== "invalid" && !SYNTAX_REPORT_ONLY.has(k)); +} +// Signal colors gated at 3:1 (carry meaning): states except the bright `warn`, +// plus the terminal pass/fail pair. +function signalForegrounds(r: Roles): [string, string][] { + return [ + ["states.success", r.states.success], + ["states.danger", r.states.danger], + ["states.info", r.states.info], + ["states.merge", r.states.merge], + ["ansi.red", r.ansi.red], + ["ansi.green", r.ansi.green], + ]; +} +// Report-only (never fails the build). +function reportOnlyForegrounds(r: Roles): [string, string][] { + return [ + ["accent.primary", r.accent.primary], + ["states.warn", r.states.warn], + ["ansi.yellow", r.ansi.yellow], + ["ansi.blue", r.ansi.blue], + ["ansi.cyan", r.ansi.cyan], + ["ansi.magenta", r.ansi.magenta], + ]; +} + +// ── Runner ──────────────────────────────────────────────────────────────── +type Failure = { theme: string; kind: string; detail: string }; +type SimulationConvention = "linear" | "gamma"; + +function pad(s: string, n: number) { + return s.length >= n ? s : s + " ".repeat(n - s.length); +} + +const gammaSim: Record unknown> = { + protan: filterDeficiencyProt(1) as unknown as (c: string) => unknown, + deutan: filterDeficiencyDeuter(1) as unknown as (c: string) => unknown, + tritan: filterDeficiencyTrit(1) as unknown as (c: string) => unknown, +}; + +function simulateForConvention(hex: string, cvd: CVDType, convention: SimulationConvention): string { + if (convention === "linear") return simulateCVD(hex, cvd); + const simulated = formatHex(gammaSim[cvd](hex) as any); + if (!simulated) throw new Error(`culori could not simulate ${hex} for ${cvd}`); + return simulated; +} + +// Worst-case contrast of fg on bg after simulation, across both gamma conventions +// (the same linear + gamma pair the distinguishability check uses). +function simulatedContrast(fg: string, bg: string, cvd: CVDType): number { + let worst = Infinity; + for (const convention of ["linear", "gamma"] as const) { + worst = Math.min( + worst, + contrastRatio(simulateForConvention(fg, cvd, convention), simulateForConvention(bg, cvd, convention)) + ); + } + return worst; +} + +// Cross-validate our hand-rolled color math against culori (dev-only oracle). +function referenceCrossChecks(): { name: string; ok: boolean; detail: string }[] { + const ciede = differenceCiede2000(); + // culori parses hex strings at runtime; its types want parsed Color objects, so + // we loosen the signatures here (dev-only oracle). + const samples = [ + "#009fff", "#d52c36", "#199f43", "#ffca00", "#1a85d4", "#d47628", + "#a13cee", "#00c5d2", "#ff5d36", "#737373", "#ffffff", "#0a0a0a", + ]; + + // contrast & ΔE: must match culori to floating-point noise. + let maxC = 0, maxDe = 0; + for (let i = 0; i < samples.length; i++) { + for (let j = i + 1; j < samples.length; j++) { + const a = samples[i], b = samples[j]; + maxC = Math.max(maxC, Math.abs(contrastRatio(a, b) - (wcagContrast(a, b) as number))); + maxDe = Math.max(maxDe, Math.abs(deltaE2000(a, b) - ciede(a, b))); + } + } + + // simulation: differs from culori only in gamma convention, but must collapse + // the same axis — verify each maps the confusable pair to a much smaller ΔE. + const axisOk = (["protan", "deutan", "tritan"] as CVDType[]).every((t) => { + const x = t === "tritan" ? "#009fff" : "#ff2e3f"; // blue (tritan) / red (protan,deutan) + const y = "#199f43"; // green + const before = deltaE2000(x, y); + const ours = deltaE2000(simulateCVD(x, t), simulateCVD(y, t)); + const lib = ciede( + simulateForConvention(x, t, "gamma"), + simulateForConvention(y, t, "gamma") + ); + // Both implementations must collapse the confusable pair to under half its + // un-simulated separation (exact residual differs by gamma convention). + return ours < before * 0.5 && lib < before * 0.5; + }); + + return [ + { name: "contrast matches culori", ok: maxC < 0.01, detail: `max |Δ| ${maxC.toFixed(4)}` }, + { name: "ΔE2000 matches culori", ok: maxDe < 0.1, detail: `max |Δ| ${maxDe.toFixed(4)}` }, + { name: "simulation collapses same axis", ok: axisOk, detail: axisOk ? "ours & culori agree" : "axis mismatch" }, + ]; +} + +export function runCvdGate(): boolean { + console.log("\n🎨 CVD theme objective gate"); + console.log("=".repeat(60)); + + const failures: Failure[] = []; + + // 0) Color-science self-checks (prove the simulation/contrast/ΔE math itself). + console.log("\n🔬 Color-science self-checks (Machado 2009 / WCAG / CIEDE2000):"); + for (const c of cvdSelfChecks()) { + console.log(` ${c.ok ? "✅" : "❌"} ${pad(c.name, 34)} ${c.detail}`); + if (!c.ok) failures.push({ theme: "(self-check)", kind: "color-science", detail: c.name }); + } + + // 0b) Reference cross-validation vs culori. We keep our own implementation + // (its CVD simulation uses the more-correct linear-RGB convention), but + // prove the standardized formulas agree with a vetted library: contrast and + // ΔE must match to floating-point noise, and our simulation must collapse + // the same axes culori's does (it differs only in gamma convention, by + // design — see src/color/cvd.ts). + for (const r of referenceCrossChecks()) { + console.log(` ${r.ok ? "✅" : "❌"} ${pad(r.name, 34)} ${r.detail}`); + if (!r.ok) failures.push({ theme: "(cross-check)", kind: "reference", detail: `${r.name}: ${r.detail}` }); + } + + for (const { name, roles, cvds } of THEMES) { + console.log(`\n■ ${name} [simulated as: ${cvds.join(", ")}]`); + const bgEditor = roles.bg.editor; + const bgWindow = roles.bg.window; + + // 1) Contrast (normal + simulated). The simulated check takes the worst case + // across both gamma conventions; backgrounds are near-neutral so they barely + // move, but we simulate them under each convention for correctness. + const reportOnlyMin: Record = {}; + for (const cvd of cvds) { + // Body text — the one role held to the full 4.5:1 text bar. + { + const normal = contrastRatio(roles.fg.base, bgEditor); + const sim = simulatedContrast(roles.fg.base, bgEditor, cvd); + if (normal < TEXT_CONTRAST || sim < TEXT_CONTRAST) { + failures.push({ + theme: name, + kind: "contrast(body)", + detail: `fg.base on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${TEXT_CONTRAST})`, + }); + } + } + + for (const [key, hex] of syntaxForegrounds(roles)) { + const normal = contrastRatio(hex, bgEditor); + const sim = simulatedContrast(hex, bgEditor, cvd); + if (normal < UI_CONTRAST || sim < UI_CONTRAST) { + failures.push({ + theme: name, + kind: "contrast(syntax)", + detail: `syntax.${key} on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})`, + }); + } + } + for (const [key, hex] of signalForegrounds(roles)) { + const normal = contrastRatio(hex, bgWindow); + const sim = simulatedContrast(hex, bgWindow, cvd); + if (normal < UI_CONTRAST || sim < UI_CONTRAST) { + failures.push({ + theme: name, + kind: "contrast(signal)", + detail: `${key} on window — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})`, + }); + } + } + // Report-only: track the worst contrast seen, printed (never fails). + for (const [key, hex] of reportOnlyForegrounds(roles)) { + const c = Math.min(contrastRatio(hex, bgWindow), simulatedContrast(hex, bgWindow, cvd)); + reportOnlyMin[key] = Math.min(reportOnlyMin[key] ?? Infinity, c); + } + } + console.log( + " Contrast (report-only, intrinsically-bright/brand): " + + Object.entries(reportOnlyMin) + .map(([k, v]) => `${k} ${v.toFixed(2)}`) + .join(", ") + ); + + // 2) Distinguishability under simulation. + for (const tier of [1, 2, 3] as const) { + const pairs = PAIRS.filter((p) => p.tier === tier); + const threshold = tier === 1 ? TIER1_DELTA_E : tier === 2 ? TIER2_DELTA_E : 0; + console.log(` Tier ${tier} ${tier === 3 ? "(advisory)" : `(ΔE ≥ ${threshold})`}:`); + for (const p of pairs) { + const aHex = p.a(roles); + const bHex = p.b(roles); + // Worst case across all CVD types this theme targets, and across both + // common Machado gamma conventions: linear RGB (our implementation) and + // gamma-encoded sRGB (culori/colorspace). + let worst = Infinity; + let worstCvd: CVDType = cvds[0]; + let worstConvention: SimulationConvention = "linear"; + for (const cvd of cvds) { + for (const convention of ["linear", "gamma"] as const) { + const d = deltaE2000( + simulateForConvention(aHex, cvd, convention), + simulateForConvention(bHex, cvd, convention) + ); + if (d < worst) { + worst = d; + worstCvd = cvd; + worstConvention = convention; + } + } + } + const ok = tier === 3 ? true : worst >= threshold; + const flag = tier === 3 ? "·" : ok ? "✅" : "❌"; + console.log( + ` ${flag} ${pad(`[${p.group}] ${p.label}`, 46)} ΔE ${worst.toFixed(1).padStart(5)} (${worstCvd}, ${worstConvention})` + ); + if (!ok) { + failures.push({ + theme: name, + kind: `Tier ${tier} ΔE`, + detail: `${p.label} = ΔE ${worst.toFixed(1)} under ${worstCvd}/${worstConvention} (need ≥ ${threshold})`, + }); + } + } + } + } + + console.log("\n" + "=".repeat(60)); + if (failures.length === 0) { + console.log("✅ CVD gate passed — all Tier-1/Tier-2 pairs distinguishable & legible."); + return true; + } + console.error(`❌ CVD gate failed with ${failures.length} issue(s):`); + for (const f of failures) console.error(` - [${f.theme}] ${f.kind}: ${f.detail}`); + return false; +} + +// Allow standalone execution: `ts-node test/cvd-test.ts`. +if (require.main === module) { + process.exit(runCvdGate() ? 0 : 1); +} diff --git a/test/test.ts b/test/test.ts index 567d144..42dca78 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,7 +1,11 @@ // test/test.ts import { readFileSync, existsSync } from "node:fs"; -import { light as rolesLight, dark as rolesDark } from "../src/roles"; +import { + light as rolesLight, dark as rolesDark, + protanDeutanLight, protanDeutanDark, tritanopiaLight, tritanopiaDark, +} from "../src/roles"; import { createTheme } from "../src/createTheme"; +import { runCvdGate } from "./cvd-test"; // Color tracking for detecting undefined values const usedColors = new Set(); @@ -169,10 +173,14 @@ function testGeneratedFiles() { const files = [ { path: "themes/pierre-light.json", expectedType: "light", expectedName: "pierre-light", expectedDisplayName: "Pierre Light" }, + { path: "themes/pierre-light-protanopia-deuteranopia.json", expectedType: "light", expectedName: "pierre-light-protanopia-deuteranopia", expectedDisplayName: "Pierre Light Protanopia & Deuteranopia" }, { path: "themes/pierre-light-soft.json", expectedType: "light", expectedName: "pierre-light-soft", expectedDisplayName: "Pierre Light Soft" }, + { path: "themes/pierre-light-tritanopia.json", expectedType: "light", expectedName: "pierre-light-tritanopia", expectedDisplayName: "Pierre Light Tritanopia" }, + { path: "themes/pierre-light-vibrant.json", expectedType: "light", expectedName: "pierre-light-vibrant", expectedDisplayName: "Pierre Light Vibrant" }, { path: "themes/pierre-dark.json", expectedType: "dark", expectedName: "pierre-dark", expectedDisplayName: "Pierre Dark" }, + { path: "themes/pierre-dark-protanopia-deuteranopia.json", expectedType: "dark", expectedName: "pierre-dark-protanopia-deuteranopia", expectedDisplayName: "Pierre Dark Protanopia & Deuteranopia" }, { path: "themes/pierre-dark-soft.json", expectedType: "dark", expectedName: "pierre-dark-soft", expectedDisplayName: "Pierre Dark Soft" }, - { path: "themes/pierre-light-vibrant.json", expectedType: "light", expectedName: "pierre-light-vibrant", expectedDisplayName: "Pierre Light Vibrant" }, + { path: "themes/pierre-dark-tritanopia.json", expectedType: "dark", expectedName: "pierre-dark-tritanopia", expectedDisplayName: "Pierre Dark Tritanopia" }, { path: "themes/pierre-dark-vibrant.json", expectedType: "dark", expectedName: "pierre-dark-vibrant", expectedDisplayName: "Pierre Dark Vibrant" } ]; @@ -260,7 +268,11 @@ function testPaletteRoles() { } validateRoles(rolesLight, "light"); + validateRoles(protanDeutanLight, "protanDeutanLight"); + validateRoles(tritanopiaLight, "tritanopiaLight"); validateRoles(rolesDark, "dark"); + validateRoles(protanDeutanDark, "protanDeutanDark"); + validateRoles(tritanopiaDark, "tritanopiaDark"); if (errors.length > 0) { console.error(`❌ Palette roles validation failed:`); @@ -281,13 +293,22 @@ let allPassed = true; // Test palette roles first allPassed = testPaletteRoles() && allPassed; -// Test theme generation +// Test theme generation (light variants, then dark; CVD distinguishability is +// handled by the gate below) allPassed = testThemeGeneration("pierre-light", "Pierre Light", "light", rolesLight) && allPassed; +allPassed = testThemeGeneration("pierre-light-protanopia-deuteranopia", "Pierre Light Protanopia & Deuteranopia", "light", protanDeutanLight) && allPassed; +allPassed = testThemeGeneration("pierre-light-tritanopia", "Pierre Light Tritanopia", "light", tritanopiaLight) && allPassed; allPassed = testThemeGeneration("pierre-dark", "Pierre Dark", "dark", rolesDark) && allPassed; +allPassed = testThemeGeneration("pierre-dark-protanopia-deuteranopia", "Pierre Dark Protanopia & Deuteranopia", "dark", protanDeutanDark) && allPassed; +allPassed = testThemeGeneration("pierre-dark-tritanopia", "Pierre Dark Tritanopia", "dark", tritanopiaDark) && allPassed; // Test generated files (only if they exist - they should after build) allPassed = testGeneratedFiles() && allPassed; +// CVD objective gate (Machado-2009 simulation + WCAG contrast + CIEDE2000) — +// hard gate: fails the build if any CVD theme regresses into ambiguity. +allPassed = runCvdGate() && allPassed; + // Summary console.log("\n" + "=".repeat(50)); console.log(`\n📊 Total unique colors used: ${usedColors.size}`); diff --git a/themes/pierre-dark-protanopia-deuteranopia.json b/themes/pierre-dark-protanopia-deuteranopia.json new file mode 100644 index 0000000..be2db96 --- /dev/null +++ b/themes/pierre-dark-protanopia-deuteranopia.json @@ -0,0 +1,1961 @@ +{ + "name": "pierre-dark-protanopia-deuteranopia", + "displayName": "Pierre Dark Protanopia & Deuteranopia", + "type": "dark", + "colors": { + "editor.background": "#0a0a0a", + "editor.foreground": "#fafafa", + "foreground": "#fafafa", + "focusBorder": "#009fff", + "selection.background": "#19283c", + "editor.selectionBackground": "#009fff4d", + "editor.lineHighlightBackground": "#19283c8c", + "editorCursor.foreground": "#009fff", + "editorLineNumber.foreground": "#737373", + "editorLineNumber.activeForeground": "#a3a3a3", + "editorIndentGuide.background": "#1d1d1d", + "editorIndentGuide.activeBackground": "#262626", + "diffEditor.insertedTextBackground": "#97c4ff1a", + "diffEditor.deletedTextBackground": "#fe8c2c1a", + "sideBar.background": "#171717", + "sideBar.foreground": "#a3a3a3", + "sideBar.border": "#0a0a0a", + "sideBarTitle.foreground": "#fafafa", + "sideBarSectionHeader.background": "#171717", + "sideBarSectionHeader.foreground": "#a3a3a3", + "sideBarSectionHeader.border": "#0a0a0a", + "activityBar.background": "#171717", + "activityBar.foreground": "#fafafa", + "activityBar.border": "#0a0a0a", + "activityBar.activeBorder": "#009fff", + "activityBarBadge.background": "#009fff", + "activityBarBadge.foreground": "#0a0a0a", + "titleBar.activeBackground": "#171717", + "titleBar.activeForeground": "#fafafa", + "titleBar.inactiveBackground": "#171717", + "titleBar.inactiveForeground": "#737373", + "titleBar.border": "#0a0a0a", + "list.activeSelectionBackground": "#19283c99", + "list.activeSelectionForeground": "#fafafa", + "list.inactiveSelectionBackground": "#19283c73", + "list.hoverBackground": "#19283c59", + "list.focusOutline": "#009fff", + "tab.activeBackground": "#0a0a0a", + "tab.activeForeground": "#fafafa", + "tab.activeBorderTop": "#009fff", + "tab.inactiveBackground": "#171717", + "tab.inactiveForeground": "#737373", + "tab.border": "#0a0a0a", + "editorGroupHeader.tabsBackground": "#171717", + "editorGroupHeader.tabsBorder": "#0a0a0a", + "panel.background": "#171717", + "panel.border": "#0a0a0a", + "panelTitle.activeBorder": "#009fff", + "panelTitle.activeForeground": "#fafafa", + "panelTitle.inactiveForeground": "#737373", + "statusBar.background": "#171717", + "statusBar.foreground": "#a3a3a3", + "statusBar.border": "#0a0a0a", + "statusBar.noFolderBackground": "#171717", + "statusBar.debuggingBackground": "#ffde80", + "statusBar.debuggingForeground": "#0a0a0a", + "statusBarItem.remoteBackground": "#171717", + "statusBarItem.remoteForeground": "#a3a3a3", + "input.background": "#1d1d1d", + "input.border": "#1d1d1d", + "input.foreground": "#fafafa", + "input.placeholderForeground": "#636363", + "dropdown.background": "#1d1d1d", + "dropdown.border": "#1d1d1d", + "dropdown.foreground": "#fafafa", + "button.background": "#009fff", + "button.foreground": "#0a0a0a", + "button.hoverBackground": "#0190e7", + "textLink.foreground": "#009fff", + "textLink.activeForeground": "#009fff", + "notifications.background": "#101010", + "notifications.foreground": "#fafafa", + "notifications.border": "#1d1d1d", + "notificationToast.border": "#1d1d1d", + "notificationCenter.border": "#1d1d1d", + "notificationCenterHeader.background": "#101010", + "notificationCenterHeader.foreground": "#a3a3a3", + "notificationLink.foreground": "#009fff", + "notificationsErrorIcon.foreground": "#fe8c2c", + "notificationsWarningIcon.foreground": "#ffde80", + "notificationsInfoIcon.foreground": "#68cdf2", + "quickInput.background": "#101010", + "quickInput.foreground": "#fafafa", + "quickInputTitle.background": "#101010", + "widget.border": "#1d1d1d", + "gitDecoration.addedResourceForeground": "#97c4ff", + "gitDecoration.conflictingResourceForeground": "#b969f3", + "gitDecoration.modifiedResourceForeground": "#009fff", + "gitDecoration.deletedResourceForeground": "#fe8c2c", + "gitDecoration.untrackedResourceForeground": "#97c4ff", + "gitDecoration.ignoredResourceForeground": "#737373", + "merge.currentHeaderBackground": "#b969f34d", + "merge.currentContentBackground": "#b969f31f", + "merge.incomingHeaderBackground": "#68cdf24d", + "merge.incomingContentBackground": "#68cdf21f", + "editorOverviewRuler.currentContentForeground": "#b969f3", + "editorOverviewRuler.incomingContentForeground": "#68cdf2", + "terminal.titleForeground": "#a3a3a3", + "terminal.titleInactiveForeground": "#737373", + "terminal.background": "#171717", + "terminal.foreground": "#a3a3a3", + "terminal.ansiBlack": "#171717", + "terminal.ansiRed": "#ffa359", + "terminal.ansiGreen": "#69b1ff", + "terminal.ansiYellow": "#ffd452", + "terminal.ansiBlue": "#009fff", + "terminal.ansiMagenta": "#b969f3", + "terminal.ansiCyan": "#68cdf2", + "terminal.ansiWhite": "#bcbcbc", + "terminal.ansiBrightBlack": "#171717", + "terminal.ansiBrightRed": "#ffba82", + "terminal.ansiBrightGreen": "#97c4ff", + "terminal.ansiBrightYellow": "#ffde80", + "terminal.ansiBrightBlue": "#69b1ff", + "terminal.ansiBrightMagenta": "#ce90f7", + "terminal.ansiBrightCyan": "#96d9f6", + "terminal.ansiBrightWhite": "#bcbcbc" + }, + "tokenColors": [ + { + "scope": [ + "comment", + "punctuation.definition.comment" + ], + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "comment markup.link", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "string", + "constant.other.symbol" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "punctuation.definition.string.begin", + "punctuation.definition.string.end" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "constant.numeric", + "constant.language.boolean" + ], + "settings": { + "foreground": "#96d9f6" + } + }, + { + "scope": "constant", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.language", + "settings": { + "foreground": "#96d9f6" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.control", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "storage", + "storage.type", + "storage.modifier" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "token.storage", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "keyword.operator.new", + "keyword.operator.expression.instanceof", + "keyword.operator.expression.typeof", + "keyword.operator.expression.void", + "keyword.operator.expression.delete", + "keyword.operator.expression.in", + "keyword.operator.expression.of", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.operator.delete", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "variable", + "identifier", + "meta.definition.variable" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "variable.other.readwrite", + "meta.object-literal.key", + "support.variable.property", + "support.variable.object.process", + "support.variable.object.node" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "function.parameter", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "variable.parameter", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "support.function", + "entity.name.function", + "meta.function-call", + "meta.require", + "support.function.any-method", + "variable.function" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "keyword.other.special-method", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "entity.name.function", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "support.function.console", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": [ + "support.type", + "entity.name.type", + "entity.name.class", + "storage.type" + ], + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": [ + "support.class", + "entity.name.type.class" + ], + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": "entity.name.namespace", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.logical", + "keyword.operator.bitwise", + "keyword.operator.channel" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "keyword.operator.arithmetic", + "keyword.operator.comparison", + "keyword.operator.relational", + "keyword.operator.increment", + "keyword.operator.decrement" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "keyword.operator.assignment", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "keyword.operator.assignment.compound.js", + "keyword.operator.assignment.compound.ts" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "keyword.operator.ternary", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.operator.optional", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "punctuation", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.terminator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.square", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.round", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.parameters", + "punctuation.definition.typeparameters" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.block", + "punctuation.definition.tag" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "meta.tag.tsx", + "meta.tag.jsx", + "meta.tag.js", + "meta.tag.ts" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "keyword.operator.module", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "support.type.object.console", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "support.module.node", + "support.type.object.module", + "entity.name.type.module" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "support.constant.math", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "support.constant.property.math", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "support.constant.json", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "support.type.object.dom", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "support.variable.dom", + "support.variable.property.dom" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "support.variable.property.process", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "meta.property.object", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "variable.parameter.function.js", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "keyword.other.template.begin", + "keyword.other.template.end" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "keyword.other.substitution.begin", + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "meta.template.expression", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.section.embedded", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "variable.interpolation", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "punctuation.section.embedded.begin", + "punctuation.section.embedded.end" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "punctuation.quasi.element", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "support.type.primitive.ts", + "support.type.builtin.ts", + "support.type.primitive.tsx", + "support.type.builtin.tsx" + ], + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": "support.type.type.flowtype", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "support.type.primitive", + "settings": { + "foreground": "#e290f0" + } + }, + { + "scope": [ + "meta.decorator", + "meta.decorator punctuation.decorator" + ], + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "entity.name.function.decorator", + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "punctuation.definition.decorator", + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "support.variable.magic.python", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "punctuation.separator.period.python", + "punctuation.separator.element.python", + "punctuation.parenthesis.begin.python", + "punctuation.parenthesis.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.arguments.begin.python", + "punctuation.definition.arguments.end.python", + "punctuation.separator.arguments.python", + "punctuation.definition.list.begin.python", + "punctuation.definition.list.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.python", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": [ + "support.token.decorator.python", + "meta.function.decorator.identifier.python" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.function.std.rust", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "variable.language.rust", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "support.constant.core.rust", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "meta.function.c", + "meta.function.cpp" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "punctuation.section.block.begin.bracket.curly.cpp", + "punctuation.section.block.end.bracket.curly.cpp", + "punctuation.terminator.statement.c", + "punctuation.section.block.begin.bracket.curly.c", + "punctuation.section.block.end.bracket.curly.c", + "punctuation.section.parens.begin.bracket.round.c", + "punctuation.section.parens.end.bracket.round.c", + "punctuation.section.parameters.begin.bracket.round.c", + "punctuation.section.parameters.end.bracket.round.c" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.assignment.c", + "keyword.operator.comparison.c", + "keyword.operator.c", + "keyword.operator.increment.c", + "keyword.operator.decrement.c", + "keyword.operator.bitwise.shift.c" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "keyword.operator.assignment.cpp", + "keyword.operator.comparison.cpp", + "keyword.operator.cpp", + "keyword.operator.increment.cpp", + "keyword.operator.decrement.cpp", + "keyword.operator.bitwise.shift.cpp" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "punctuation.separator.c", + "punctuation.separator.cpp" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "support.type.posix-reserved.c", + "support.type.posix-reserved.cpp" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "keyword.operator.sizeof.c", + "keyword.operator.sizeof.cpp" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "variable.c", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "source.java", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "punctuation.section.block.begin.java", + "punctuation.section.block.end.java", + "punctuation.definition.method-parameters.begin.java", + "punctuation.definition.method-parameters.end.java", + "meta.method.identifier.java", + "punctuation.section.method.begin.java", + "punctuation.section.method.end.java", + "punctuation.terminator.java", + "punctuation.section.class.begin.java", + "punctuation.section.class.end.java", + "punctuation.section.inner-class.begin.java", + "punctuation.section.inner-class.end.java", + "meta.method-call.java", + "punctuation.section.class.begin.bracket.curly.java", + "punctuation.section.class.end.bracket.curly.java", + "punctuation.section.method.begin.bracket.curly.java", + "punctuation.section.method.end.bracket.curly.java", + "punctuation.separator.period.java", + "punctuation.bracket.angle.java", + "punctuation.definition.annotation.java", + "meta.method.body.java" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.method.java", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": [ + "storage.modifier.import.java", + "storage.type.java", + "storage.type.generic.java" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "import.storage.java", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "token.package.keyword", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "token.package", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.storage.type.java", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "keyword.operator.assignment.go", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "entity.name.package.go", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "support.other.namespace.use.php", + "support.other.namespace.use-as.php", + "support.other.namespace.php", + "entity.other.alias.php", + "meta.interface.php" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "punctuation.section.array.begin.php", + "punctuation.section.array.end.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.php", + "meta.other.type.phpdoc.php", + "keyword.other.type.php", + "keyword.other.array.phpdoc.php" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "meta.function-call.php", + "meta.function-call.object.php", + "meta.function-call.static.php" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": [ + "punctuation.definition.parameters.begin.bracket.round.php", + "punctuation.definition.parameters.end.bracket.round.php", + "punctuation.separator.delimiter.php", + "punctuation.section.scope.begin.php", + "punctuation.section.scope.end.php", + "punctuation.terminator.expression.php", + "punctuation.definition.arguments.begin.bracket.round.php", + "punctuation.definition.arguments.end.bracket.round.php", + "punctuation.definition.storage-type.begin.bracket.round.php", + "punctuation.definition.storage-type.end.bracket.round.php", + "punctuation.definition.array.begin.bracket.round.php", + "punctuation.definition.array.end.bracket.round.php", + "punctuation.definition.begin.bracket.round.php", + "punctuation.definition.end.bracket.round.php", + "punctuation.definition.begin.bracket.curly.php", + "punctuation.definition.end.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php", + "punctuation.definition.section.switch-block.start.bracket.curly.php", + "punctuation.definition.section.switch-block.begin.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "support.constant.ext.php", + "support.constant.std.php", + "support.constant.core.php", + "support.constant.parser-token.php" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "entity.name.goto-label.php", + "support.other.php" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": [ + "keyword.operator.logical.php", + "keyword.operator.bitwise.php", + "keyword.operator.arithmetic.php" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "keyword.operator.heredoc.php", + "keyword.operator.nowdoc.php" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "variable.other.class.php", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "storage.type.haskell", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "storage.type.cs", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "entity.name.label.cs", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "entity.name.scope-resolution.function.call", + "entity.name.scope-resolution.function.definition" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "punctuation.definition.delayed.unison", + "punctuation.definition.list.begin.unison", + "punctuation.definition.list.end.unison", + "punctuation.definition.ability.begin.unison", + "punctuation.definition.ability.end.unison", + "punctuation.operator.assignment.as.unison", + "punctuation.separator.pipe.unison", + "punctuation.separator.delimiter.unison", + "punctuation.definition.hash.unison" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "support.constant.edge", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "support.type.prelude.elm", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "support.constant.elm", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.global.clojure", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "meta.symbol.clojure", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "constant.keyword.clojure", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "storage.modifier.import.groovy", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "meta.method.groovy", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "meta.definition.variable.name.groovy", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "meta.definition.class.inherited.classes.groovy", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "support.variable.semantic.hlsl", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "meta.scope.prerequisites.makefile", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "source.makefile", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "source.ini", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "function.parameter.ruby", + "function.parameter.cs" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "constant.language.symbol.elixir", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "entity.name.function.xi", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "entity.name.class.xi", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "constant.character.character-class.regexp.xi", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "constant.regexp.xi", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "keyword.control.xi", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "invalid.xi", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "beginning.punctuation.definition.quote.markdown.xi", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "constant.character.xi", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "accent.xi", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "wikiword.xi", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.other.color.rgb-value.xi", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "punctuation.definition.tag.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "support.constant.property-value.scss", + "support.constant.property-value.css" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "keyword.operator.css", + "keyword.operator.scss", + "keyword.operator.less" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": [ + "support.constant.color.w3c-standard-color-name.css", + "support.constant.color.w3c-standard-color-name.scss" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.vendored.property-name.css", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "support.type.property-name.css", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "support.type.property-name", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.property-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.font-name", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.other.attribute-name.class.css", + "settings": { + "foreground": "#ffbc56", + "fontStyle": "normal" + } + }, + { + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#ba8ffd", + "fontStyle": "normal" + } + }, + { + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "meta.selector", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "selector.sass", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "rgb-value", + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "less rgb-value", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "control.elements", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "keyword.operator.less", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#ffbc56", + "fontStyle": "normal" + } + }, + { + "scope": "constant.character.entity", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "meta.tag", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "markup.heading punctuation.definition.heading", + "entity.name.section" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "markup.heading.setext", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "markup.heading.setext.1.markdown", + "markup.heading.setext.2.markdown" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "markup.bold", + "todo.bold" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "markup.italic", + "punctuation.definition.italic", + "todo.emphasis" + ], + "settings": { + "foreground": "#b969f3", + "fontStyle": "italic" + } + }, + { + "scope": "emphasis md", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": [ + "markup.underline.link.markdown", + "markup.underline.link.image.markdown" + ], + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": [ + "string.other.link.title.markdown", + "string.other.link.description.markdown" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "markup.inline.raw.markdown", + "markup.inline.raw.string.markdown" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "punctuation.definition.list.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown" + ], + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "markup.quote.markdown", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "markup.changed.diff", + "settings": { + "foreground": "#ffbc56" + } + }, + { + "scope": [ + "meta.diff.header.from-file", + "meta.diff.header.to-file", + "punctuation.definition.from-file.diff", + "punctuation.definition.to-file.diff" + ], + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#68cdf2" + } + }, + { + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#64d1db" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > value.json > string.quoted.json", + "source.json meta.structure.array.json > value.json > string.quoted.json", + "source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation", + "source.json meta.structure.array.json > value.json > string.quoted.json > punctuation" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > constant.language.json", + "source.json meta.structure.array.json > constant.language.json" + ], + "settings": { + "foreground": "#08c0ef" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "support.type.property-name.json punctuation", + "settings": { + "foreground": "#ffa359" + } + }, + { + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.end", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.begin", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#ba8ffd" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#b969f3" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "#fafafa" + } + } + ], + "semanticTokenColors": { + "comment": "#737373", + "string": "#97c4ff", + "number": "#96d9f6", + "regexp": "#68cdf2", + "keyword": "#b969f3", + "variable": "#ffa359", + "parameter": "#a3a3a3", + "property": "#ffa359", + "function": "#ba8ffd", + "method": "#ba8ffd", + "type": "#e290f0", + "class": "#e290f0", + "namespace": "#ffbc56", + "enumMember": "#08c0ef", + "variable.constant": "#ffcc81", + "variable.defaultLibrary": "#ffbc56", + "decorator": "#69b1ff" + } +} \ No newline at end of file diff --git a/themes/pierre-dark-tritanopia.json b/themes/pierre-dark-tritanopia.json new file mode 100644 index 0000000..3f57407 --- /dev/null +++ b/themes/pierre-dark-tritanopia.json @@ -0,0 +1,1961 @@ +{ + "name": "pierre-dark-tritanopia", + "displayName": "Pierre Dark Tritanopia", + "type": "dark", + "colors": { + "editor.background": "#0a0a0a", + "editor.foreground": "#fafafa", + "foreground": "#fafafa", + "focusBorder": "#009fff", + "selection.background": "#19283c", + "editor.selectionBackground": "#009fff4d", + "editor.lineHighlightBackground": "#19283c8c", + "editorCursor.foreground": "#009fff", + "editorLineNumber.foreground": "#737373", + "editorLineNumber.activeForeground": "#a3a3a3", + "editorIndentGuide.background": "#1d1d1d", + "editorIndentGuide.activeBackground": "#262626", + "diffEditor.insertedTextBackground": "#92dde41a", + "diffEditor.deletedTextBackground": "#ff855e1a", + "sideBar.background": "#171717", + "sideBar.foreground": "#a3a3a3", + "sideBar.border": "#0a0a0a", + "sideBarTitle.foreground": "#fafafa", + "sideBarSectionHeader.background": "#171717", + "sideBarSectionHeader.foreground": "#a3a3a3", + "sideBarSectionHeader.border": "#0a0a0a", + "activityBar.background": "#171717", + "activityBar.foreground": "#fafafa", + "activityBar.border": "#0a0a0a", + "activityBar.activeBorder": "#009fff", + "activityBarBadge.background": "#009fff", + "activityBarBadge.foreground": "#0a0a0a", + "titleBar.activeBackground": "#171717", + "titleBar.activeForeground": "#fafafa", + "titleBar.inactiveBackground": "#171717", + "titleBar.inactiveForeground": "#737373", + "titleBar.border": "#0a0a0a", + "list.activeSelectionBackground": "#19283c99", + "list.activeSelectionForeground": "#fafafa", + "list.inactiveSelectionBackground": "#19283c73", + "list.hoverBackground": "#19283c59", + "list.focusOutline": "#009fff", + "tab.activeBackground": "#0a0a0a", + "tab.activeForeground": "#fafafa", + "tab.activeBorderTop": "#009fff", + "tab.inactiveBackground": "#171717", + "tab.inactiveForeground": "#737373", + "tab.border": "#0a0a0a", + "editorGroupHeader.tabsBackground": "#171717", + "editorGroupHeader.tabsBorder": "#0a0a0a", + "panel.background": "#171717", + "panel.border": "#0a0a0a", + "panelTitle.activeBorder": "#009fff", + "panelTitle.activeForeground": "#fafafa", + "panelTitle.inactiveForeground": "#737373", + "statusBar.background": "#171717", + "statusBar.foreground": "#a3a3a3", + "statusBar.border": "#0a0a0a", + "statusBar.noFolderBackground": "#171717", + "statusBar.debuggingBackground": "#ffbc56", + "statusBar.debuggingForeground": "#0a0a0a", + "statusBarItem.remoteBackground": "#171717", + "statusBarItem.remoteForeground": "#a3a3a3", + "input.background": "#1d1d1d", + "input.border": "#1d1d1d", + "input.foreground": "#fafafa", + "input.placeholderForeground": "#636363", + "dropdown.background": "#1d1d1d", + "dropdown.border": "#1d1d1d", + "dropdown.foreground": "#fafafa", + "button.background": "#009fff", + "button.foreground": "#0a0a0a", + "button.hoverBackground": "#0190e7", + "textLink.foreground": "#009fff", + "textLink.activeForeground": "#009fff", + "notifications.background": "#101010", + "notifications.foreground": "#fafafa", + "notifications.border": "#1d1d1d", + "notificationToast.border": "#1d1d1d", + "notificationCenter.border": "#1d1d1d", + "notificationCenterHeader.background": "#101010", + "notificationCenterHeader.foreground": "#a3a3a3", + "notificationLink.foreground": "#009fff", + "notificationsErrorIcon.foreground": "#ff855e", + "notificationsWarningIcon.foreground": "#ffbc56", + "notificationsInfoIcon.foreground": "#69b1ff", + "quickInput.background": "#101010", + "quickInput.foreground": "#fafafa", + "quickInputTitle.background": "#101010", + "widget.border": "#1d1d1d", + "gitDecoration.addedResourceForeground": "#92dde4", + "gitDecoration.conflictingResourceForeground": "#ea68bc", + "gitDecoration.modifiedResourceForeground": "#009fff", + "gitDecoration.deletedResourceForeground": "#ff855e", + "gitDecoration.untrackedResourceForeground": "#92dde4", + "gitDecoration.ignoredResourceForeground": "#737373", + "merge.currentHeaderBackground": "#ea68bc4d", + "merge.currentContentBackground": "#ea68bc1f", + "merge.incomingHeaderBackground": "#69b1ff4d", + "merge.incomingContentBackground": "#69b1ff1f", + "editorOverviewRuler.currentContentForeground": "#ea68bc", + "editorOverviewRuler.incomingContentForeground": "#69b1ff", + "terminal.titleForeground": "#a3a3a3", + "terminal.titleInactiveForeground": "#737373", + "terminal.background": "#171717", + "terminal.foreground": "#a3a3a3", + "terminal.ansiBlack": "#171717", + "terminal.ansiRed": "#ff855e", + "terminal.ansiGreen": "#64d1db", + "terminal.ansiYellow": "#ffbc56", + "terminal.ansiBlue": "#69b1ff", + "terminal.ansiMagenta": "#ea68bc", + "terminal.ansiCyan": "#92dde4", + "terminal.ansiWhite": "#bcbcbc", + "terminal.ansiBrightBlack": "#171717", + "terminal.ansiBrightRed": "#ffa685", + "terminal.ansiBrightGreen": "#92dde4", + "terminal.ansiBrightYellow": "#ffcc81", + "terminal.ansiBrightBlue": "#97c4ff", + "terminal.ansiBrightMagenta": "#f191cc", + "terminal.ansiBrightCyan": "#92dde4", + "terminal.ansiBrightWhite": "#bcbcbc" + }, + "tokenColors": [ + { + "scope": [ + "comment", + "punctuation.definition.comment" + ], + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "comment markup.link", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "string", + "constant.other.symbol" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": [ + "punctuation.definition.string.begin", + "punctuation.definition.string.end" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": [ + "constant.numeric", + "constant.language.boolean" + ], + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "constant", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.language", + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.control", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "storage", + "storage.type", + "storage.modifier" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "token.storage", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "keyword.operator.new", + "keyword.operator.expression.instanceof", + "keyword.operator.expression.typeof", + "keyword.operator.expression.void", + "keyword.operator.expression.delete", + "keyword.operator.expression.in", + "keyword.operator.expression.of", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.operator.delete", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "variable", + "identifier", + "meta.definition.variable" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "variable.other.readwrite", + "meta.object-literal.key", + "support.variable.property", + "support.variable.object.process", + "support.variable.object.node" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "function.parameter", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "variable.parameter", + "settings": { + "foreground": "#a3a3a3" + } + }, + { + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "support.function", + "entity.name.function", + "meta.function-call", + "meta.require", + "support.function.any-method", + "variable.function" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "keyword.other.special-method", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "entity.name.function", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "support.function.console", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "support.type", + "entity.name.type", + "entity.name.class", + "storage.type" + ], + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": [ + "support.class", + "entity.name.type.class" + ], + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": "entity.name.namespace", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.logical", + "keyword.operator.bitwise", + "keyword.operator.channel" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "keyword.operator.arithmetic", + "keyword.operator.comparison", + "keyword.operator.relational", + "keyword.operator.increment", + "keyword.operator.decrement" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "keyword.operator.assignment", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "keyword.operator.assignment.compound.js", + "keyword.operator.assignment.compound.ts" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "keyword.operator.ternary", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.operator.optional", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "punctuation", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.terminator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.square", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.round", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.parameters", + "punctuation.definition.typeparameters" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.block", + "punctuation.definition.tag" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "meta.tag.tsx", + "meta.tag.jsx", + "meta.tag.js", + "meta.tag.ts" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "keyword.operator.module", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "support.type.object.console", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "support.module.node", + "support.type.object.module", + "entity.name.type.module" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "support.constant.math", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "support.constant.property.math", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "support.constant.json", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "support.type.object.dom", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "support.variable.dom", + "support.variable.property.dom" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "support.variable.property.process", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "meta.property.object", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.parameter.function.js", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "keyword.other.template.begin", + "keyword.other.template.end" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": [ + "keyword.other.substitution.begin", + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "meta.template.expression", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.section.embedded", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.interpolation", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.section.embedded.begin", + "punctuation.section.embedded.end" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "punctuation.quasi.element", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "support.type.primitive.ts", + "support.type.builtin.ts", + "support.type.primitive.tsx", + "support.type.builtin.tsx" + ], + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": "support.type.type.flowtype", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "support.type.primitive", + "settings": { + "foreground": "#ea68bc" + } + }, + { + "scope": [ + "meta.decorator", + "meta.decorator punctuation.decorator" + ], + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "entity.name.function.decorator", + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "punctuation.definition.decorator", + "settings": { + "foreground": "#69b1ff" + } + }, + { + "scope": "support.variable.magic.python", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.separator.period.python", + "punctuation.separator.element.python", + "punctuation.parenthesis.begin.python", + "punctuation.parenthesis.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.arguments.begin.python", + "punctuation.definition.arguments.end.python", + "punctuation.separator.arguments.python", + "punctuation.definition.list.begin.python", + "punctuation.definition.list.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.python", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "support.token.decorator.python", + "meta.function.decorator.identifier.python" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.function.std.rust", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "variable.language.rust", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "support.constant.core.rust", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "meta.function.c", + "meta.function.cpp" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.section.block.begin.bracket.curly.cpp", + "punctuation.section.block.end.bracket.curly.cpp", + "punctuation.terminator.statement.c", + "punctuation.section.block.begin.bracket.curly.c", + "punctuation.section.block.end.bracket.curly.c", + "punctuation.section.parens.begin.bracket.round.c", + "punctuation.section.parens.end.bracket.round.c", + "punctuation.section.parameters.begin.bracket.round.c", + "punctuation.section.parameters.end.bracket.round.c" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.assignment.c", + "keyword.operator.comparison.c", + "keyword.operator.c", + "keyword.operator.increment.c", + "keyword.operator.decrement.c", + "keyword.operator.bitwise.shift.c" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "keyword.operator.assignment.cpp", + "keyword.operator.comparison.cpp", + "keyword.operator.cpp", + "keyword.operator.increment.cpp", + "keyword.operator.decrement.cpp", + "keyword.operator.bitwise.shift.cpp" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "punctuation.separator.c", + "punctuation.separator.cpp" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "support.type.posix-reserved.c", + "support.type.posix-reserved.cpp" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "keyword.operator.sizeof.c", + "keyword.operator.sizeof.cpp" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "variable.c", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "source.java", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.section.block.begin.java", + "punctuation.section.block.end.java", + "punctuation.definition.method-parameters.begin.java", + "punctuation.definition.method-parameters.end.java", + "meta.method.identifier.java", + "punctuation.section.method.begin.java", + "punctuation.section.method.end.java", + "punctuation.terminator.java", + "punctuation.section.class.begin.java", + "punctuation.section.class.end.java", + "punctuation.section.inner-class.begin.java", + "punctuation.section.inner-class.end.java", + "meta.method-call.java", + "punctuation.section.class.begin.bracket.curly.java", + "punctuation.section.class.end.bracket.curly.java", + "punctuation.section.method.begin.bracket.curly.java", + "punctuation.section.method.end.bracket.curly.java", + "punctuation.separator.period.java", + "punctuation.bracket.angle.java", + "punctuation.definition.annotation.java", + "meta.method.body.java" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.method.java", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "storage.modifier.import.java", + "storage.type.java", + "storage.type.generic.java" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "import.storage.java", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "token.package.keyword", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "token.package", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.storage.type.java", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator.assignment.go", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "entity.name.package.go", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "support.other.namespace.use.php", + "support.other.namespace.use-as.php", + "support.other.namespace.php", + "entity.other.alias.php", + "meta.interface.php" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "punctuation.section.array.begin.php", + "punctuation.section.array.end.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.php", + "meta.other.type.phpdoc.php", + "keyword.other.type.php", + "keyword.other.array.phpdoc.php" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "meta.function-call.php", + "meta.function-call.object.php", + "meta.function-call.static.php" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "punctuation.definition.parameters.begin.bracket.round.php", + "punctuation.definition.parameters.end.bracket.round.php", + "punctuation.separator.delimiter.php", + "punctuation.section.scope.begin.php", + "punctuation.section.scope.end.php", + "punctuation.terminator.expression.php", + "punctuation.definition.arguments.begin.bracket.round.php", + "punctuation.definition.arguments.end.bracket.round.php", + "punctuation.definition.storage-type.begin.bracket.round.php", + "punctuation.definition.storage-type.end.bracket.round.php", + "punctuation.definition.array.begin.bracket.round.php", + "punctuation.definition.array.end.bracket.round.php", + "punctuation.definition.begin.bracket.round.php", + "punctuation.definition.end.bracket.round.php", + "punctuation.definition.begin.bracket.curly.php", + "punctuation.definition.end.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php", + "punctuation.definition.section.switch-block.start.bracket.curly.php", + "punctuation.definition.section.switch-block.begin.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "support.constant.ext.php", + "support.constant.std.php", + "support.constant.core.php", + "support.constant.parser-token.php" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "entity.name.goto-label.php", + "support.other.php" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": [ + "keyword.operator.logical.php", + "keyword.operator.bitwise.php", + "keyword.operator.arithmetic.php" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "keyword.operator.heredoc.php", + "keyword.operator.nowdoc.php" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "variable.other.class.php", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "storage.type.haskell", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "storage.type.cs", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "entity.name.label.cs", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "entity.name.scope-resolution.function.call", + "entity.name.scope-resolution.function.definition" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.definition.delayed.unison", + "punctuation.definition.list.begin.unison", + "punctuation.definition.list.end.unison", + "punctuation.definition.ability.begin.unison", + "punctuation.definition.ability.end.unison", + "punctuation.operator.assignment.as.unison", + "punctuation.separator.pipe.unison", + "punctuation.separator.delimiter.unison", + "punctuation.definition.hash.unison" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "support.constant.edge", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "support.type.prelude.elm", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "support.constant.elm", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.global.clojure", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "meta.symbol.clojure", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "constant.keyword.clojure", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "storage.modifier.import.groovy", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "meta.method.groovy", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "meta.definition.variable.name.groovy", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "meta.definition.class.inherited.classes.groovy", + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": "support.variable.semantic.hlsl", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "meta.scope.prerequisites.makefile", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "source.makefile", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "source.ini", + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "function.parameter.ruby", + "function.parameter.cs" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "constant.language.symbol.elixir", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "entity.name.function.xi", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "entity.name.class.xi", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "constant.character.character-class.regexp.xi", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "constant.regexp.xi", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "keyword.control.xi", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "invalid.xi", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "beginning.punctuation.definition.quote.markdown.xi", + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "constant.character.xi", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "accent.xi", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "wikiword.xi", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.other.color.rgb-value.xi", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "punctuation.definition.tag.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "support.constant.property-value.scss", + "support.constant.property-value.css" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "keyword.operator.css", + "keyword.operator.scss", + "keyword.operator.less" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": [ + "support.constant.color.w3c-standard-color-name.css", + "support.constant.color.w3c-standard-color-name.scss" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.vendored.property-name.css", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "support.type.property-name.css", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "support.type.property-name", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.property-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.font-name", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.other.attribute-name.class.css", + "settings": { + "foreground": "#64d1db", + "fontStyle": "normal" + } + }, + { + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#97c4ff", + "fontStyle": "normal" + } + }, + { + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "meta.selector", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "selector.sass", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "rgb-value", + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "less rgb-value", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "control.elements", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "keyword.operator.less", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#64d1db", + "fontStyle": "normal" + } + }, + { + "scope": "constant.character.entity", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "meta.tag", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "markup.heading punctuation.definition.heading", + "entity.name.section" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "markup.heading.setext", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "markup.heading.setext.1.markdown", + "markup.heading.setext.2.markdown" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "markup.bold", + "todo.bold" + ], + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": [ + "markup.italic", + "punctuation.definition.italic", + "todo.emphasis" + ], + "settings": { + "foreground": "#d568ea", + "fontStyle": "italic" + } + }, + { + "scope": "emphasis md", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": [ + "markup.underline.link.markdown", + "markup.underline.link.image.markdown" + ], + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": [ + "string.other.link.title.markdown", + "string.other.link.description.markdown" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "markup.inline.raw.markdown", + "markup.inline.raw.string.markdown" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "punctuation.definition.list.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown" + ], + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "markup.quote.markdown", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "markup.changed.diff", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "meta.diff.header.from-file", + "meta.diff.header.to-file", + "punctuation.definition.from-file.diff", + "punctuation.definition.to-file.diff" + ], + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#64d1db" + } + }, + { + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#64d1db" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > value.json > string.quoted.json", + "source.json meta.structure.array.json > value.json > string.quoted.json", + "source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation", + "source.json meta.structure.array.json > value.json > string.quoted.json > punctuation" + ], + "settings": { + "foreground": "#92dde4" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > constant.language.json", + "source.json meta.structure.array.json > constant.language.json" + ], + "settings": { + "foreground": "#00c5d2" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "support.type.property-name.json punctuation", + "settings": { + "foreground": "#ff855e" + } + }, + { + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.end", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.begin", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#97c4ff" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#ffcc81" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#d568ea" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "#fafafa" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "#fafafa" + } + } + ], + "semanticTokenColors": { + "comment": "#737373", + "string": "#92dde4", + "number": "#69b1ff", + "regexp": "#64d1db", + "keyword": "#d568ea", + "variable": "#ff855e", + "parameter": "#a3a3a3", + "property": "#ff855e", + "function": "#97c4ff", + "method": "#97c4ff", + "type": "#ea68bc", + "class": "#ea68bc", + "namespace": "#ff855e", + "enumMember": "#00c5d2", + "variable.constant": "#ffcc81", + "variable.defaultLibrary": "#ff855e", + "decorator": "#69b1ff" + } +} \ No newline at end of file diff --git a/themes/pierre-light-protanopia-deuteranopia.json b/themes/pierre-light-protanopia-deuteranopia.json new file mode 100644 index 0000000..b454eae --- /dev/null +++ b/themes/pierre-light-protanopia-deuteranopia.json @@ -0,0 +1,1961 @@ +{ + "name": "pierre-light-protanopia-deuteranopia", + "displayName": "Pierre Light Protanopia & Deuteranopia", + "type": "light", + "colors": { + "editor.background": "#ffffff", + "editor.foreground": "#0a0a0a", + "foreground": "#0a0a0a", + "focusBorder": "#009fff", + "selection.background": "#dfebff", + "editor.selectionBackground": "#009fff2e", + "editor.lineHighlightBackground": "#dfebff8c", + "editorCursor.foreground": "#009fff", + "editorLineNumber.foreground": "#737373", + "editorLineNumber.activeForeground": "#525252", + "editorIndentGuide.background": "#e5e5e5", + "editorIndentGuide.activeBackground": "#d4d4d4", + "diffEditor.insertedTextBackground": "#216cab33", + "diffEditor.deletedTextBackground": "#ac602333", + "sideBar.background": "#f5f5f5", + "sideBar.foreground": "#525252", + "sideBar.border": "#e5e5e5", + "sideBarTitle.foreground": "#0a0a0a", + "sideBarSectionHeader.background": "#f5f5f5", + "sideBarSectionHeader.foreground": "#525252", + "sideBarSectionHeader.border": "#e5e5e5", + "activityBar.background": "#f5f5f5", + "activityBar.foreground": "#0a0a0a", + "activityBar.border": "#e5e5e5", + "activityBar.activeBorder": "#009fff", + "activityBarBadge.background": "#009fff", + "activityBarBadge.foreground": "#ffffff", + "titleBar.activeBackground": "#f5f5f5", + "titleBar.activeForeground": "#0a0a0a", + "titleBar.inactiveBackground": "#f5f5f5", + "titleBar.inactiveForeground": "#737373", + "titleBar.border": "#e5e5e5", + "list.activeSelectionBackground": "#dfebffcc", + "list.activeSelectionForeground": "#0a0a0a", + "list.inactiveSelectionBackground": "#dfebff73", + "list.hoverBackground": "#dfebff59", + "list.focusOutline": "#009fff", + "tab.activeBackground": "#ffffff", + "tab.activeForeground": "#0a0a0a", + "tab.activeBorderTop": "#009fff", + "tab.inactiveBackground": "#f5f5f5", + "tab.inactiveForeground": "#737373", + "tab.border": "#e5e5e5", + "editorGroupHeader.tabsBackground": "#f5f5f5", + "editorGroupHeader.tabsBorder": "#e5e5e5", + "panel.background": "#f5f5f5", + "panel.border": "#e5e5e5", + "panelTitle.activeBorder": "#009fff", + "panelTitle.activeForeground": "#0a0a0a", + "panelTitle.inactiveForeground": "#737373", + "statusBar.background": "#f5f5f5", + "statusBar.foreground": "#525252", + "statusBar.border": "#e5e5e5", + "statusBar.noFolderBackground": "#f5f5f5", + "statusBar.debuggingBackground": "#ffca00", + "statusBar.debuggingForeground": "#ffffff", + "statusBarItem.remoteBackground": "#f5f5f5", + "statusBarItem.remoteForeground": "#525252", + "input.background": "#ededed", + "input.border": "#d4d4d4", + "input.foreground": "#0a0a0a", + "input.placeholderForeground": "#8a8a8a", + "dropdown.background": "#ededed", + "dropdown.border": "#d4d4d4", + "dropdown.foreground": "#0a0a0a", + "button.background": "#009fff", + "button.foreground": "#ffffff", + "button.hoverBackground": "#1aa9ff", + "textLink.foreground": "#009fff", + "textLink.activeForeground": "#009fff", + "notifications.background": "#f7f7f7", + "notifications.foreground": "#0a0a0a", + "notifications.border": "#e5e5e5", + "notificationToast.border": "#e5e5e5", + "notificationCenter.border": "#e5e5e5", + "notificationCenterHeader.background": "#f7f7f7", + "notificationCenterHeader.foreground": "#525252", + "notificationLink.foreground": "#009fff", + "notificationsErrorIcon.foreground": "#ac6023", + "notificationsWarningIcon.foreground": "#ffca00", + "notificationsInfoIcon.foreground": "#2182a1", + "quickInput.background": "#f7f7f7", + "quickInput.foreground": "#0a0a0a", + "quickInputTitle.background": "#f7f7f7", + "widget.border": "#e5e5e5", + "gitDecoration.addedResourceForeground": "#216cab", + "gitDecoration.conflictingResourceForeground": "#58287c", + "gitDecoration.modifiedResourceForeground": "#009fff", + "gitDecoration.deletedResourceForeground": "#ac6023", + "gitDecoration.untrackedResourceForeground": "#216cab", + "gitDecoration.ignoredResourceForeground": "#737373", + "merge.currentHeaderBackground": "#58287c33", + "merge.currentContentBackground": "#58287c14", + "merge.incomingHeaderBackground": "#2182a133", + "merge.incomingContentBackground": "#2182a114", + "editorOverviewRuler.currentContentForeground": "#58287c", + "editorOverviewRuler.incomingContentForeground": "#2182a1", + "terminal.titleForeground": "#525252", + "terminal.titleInactiveForeground": "#737373", + "terminal.background": "#f5f5f5", + "terminal.foreground": "#525252", + "terminal.ansiBlack": "#1d1d1d", + "terminal.ansiRed": "#ac6023", + "terminal.ansiGreen": "#216cab", + "terminal.ansiYellow": "#ffca00", + "terminal.ansiBlue": "#1a85d4", + "terminal.ansiMagenta": "#8836c7", + "terminal.ansiCyan": "#2182a1", + "terminal.ansiWhite": "#bcbcbc", + "terminal.ansiBrightBlack": "#1d1d1d", + "terminal.ansiBrightRed": "#d47628", + "terminal.ansiBrightGreen": "#1a85d4", + "terminal.ansiBrightYellow": "#ffca00", + "terminal.ansiBrightBlue": "#009fff", + "terminal.ansiBrightMagenta": "#a13cee", + "terminal.ansiBrightCyan": "#1ca1c7", + "terminal.ansiBrightWhite": "#bcbcbc" + }, + "tokenColors": [ + { + "scope": [ + "comment", + "punctuation.definition.comment" + ], + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "comment markup.link", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "string", + "constant.other.symbol" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": [ + "punctuation.definition.string.begin", + "punctuation.definition.string.end" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": [ + "constant.numeric", + "constant.language.boolean" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "constant", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.language", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.control", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "storage", + "storage.type", + "storage.modifier" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "token.storage", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "keyword.operator.new", + "keyword.operator.expression.instanceof", + "keyword.operator.expression.typeof", + "keyword.operator.expression.void", + "keyword.operator.expression.delete", + "keyword.operator.expression.in", + "keyword.operator.expression.of", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.operator.delete", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "variable", + "identifier", + "meta.definition.variable" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "variable.other.readwrite", + "meta.object-literal.key", + "support.variable.property", + "support.variable.object.process", + "support.variable.object.node" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.parameter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "variable.parameter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "support.function", + "entity.name.function", + "meta.function-call", + "meta.require", + "support.function.any-method", + "variable.function" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "keyword.other.special-method", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "entity.name.function", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "support.function.console", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": [ + "support.type", + "entity.name.type", + "entity.name.class", + "storage.type" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "support.class", + "entity.name.type.class" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "entity.name.namespace", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.logical", + "keyword.operator.bitwise", + "keyword.operator.channel" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "keyword.operator.arithmetic", + "keyword.operator.comparison", + "keyword.operator.relational", + "keyword.operator.increment", + "keyword.operator.decrement" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "keyword.operator.assignment", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "keyword.operator.assignment.compound.js", + "keyword.operator.assignment.compound.ts" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "keyword.operator.ternary", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.operator.optional", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "punctuation", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.terminator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.square", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.round", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.parameters", + "punctuation.definition.typeparameters" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.block", + "punctuation.definition.tag" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "meta.tag.tsx", + "meta.tag.jsx", + "meta.tag.js", + "meta.tag.ts" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "keyword.operator.module", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "support.type.object.console", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "support.module.node", + "support.type.object.module", + "entity.name.type.module" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.constant.math", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.constant.property.math", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.constant.json", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.type.object.dom", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "support.variable.dom", + "support.variable.property.dom" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "support.variable.property.process", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.property.object", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "variable.parameter.function.js", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "keyword.other.template.begin", + "keyword.other.template.end" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": [ + "keyword.other.substitution.begin", + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "meta.template.expression", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.section.embedded", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "variable.interpolation", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "punctuation.section.embedded.begin", + "punctuation.section.embedded.end" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "punctuation.quasi.element", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "support.type.primitive.ts", + "support.type.builtin.ts", + "support.type.primitive.tsx", + "support.type.builtin.tsx" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "support.type.type.flowtype", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "support.type.primitive", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "meta.decorator", + "meta.decorator punctuation.decorator" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "entity.name.function.decorator", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "punctuation.definition.decorator", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "support.variable.magic.python", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "punctuation.separator.period.python", + "punctuation.separator.element.python", + "punctuation.parenthesis.begin.python", + "punctuation.parenthesis.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.arguments.begin.python", + "punctuation.definition.arguments.end.python", + "punctuation.separator.arguments.python", + "punctuation.definition.list.begin.python", + "punctuation.definition.list.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.python", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": [ + "support.token.decorator.python", + "meta.function.decorator.identifier.python" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.function.std.rust", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "variable.language.rust", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "support.constant.core.rust", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "meta.function.c", + "meta.function.cpp" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "punctuation.section.block.begin.bracket.curly.cpp", + "punctuation.section.block.end.bracket.curly.cpp", + "punctuation.terminator.statement.c", + "punctuation.section.block.begin.bracket.curly.c", + "punctuation.section.block.end.bracket.curly.c", + "punctuation.section.parens.begin.bracket.round.c", + "punctuation.section.parens.end.bracket.round.c", + "punctuation.section.parameters.begin.bracket.round.c", + "punctuation.section.parameters.end.bracket.round.c" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.assignment.c", + "keyword.operator.comparison.c", + "keyword.operator.c", + "keyword.operator.increment.c", + "keyword.operator.decrement.c", + "keyword.operator.bitwise.shift.c" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "keyword.operator.assignment.cpp", + "keyword.operator.comparison.cpp", + "keyword.operator.cpp", + "keyword.operator.increment.cpp", + "keyword.operator.decrement.cpp", + "keyword.operator.bitwise.shift.cpp" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "punctuation.separator.c", + "punctuation.separator.cpp" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "support.type.posix-reserved.c", + "support.type.posix-reserved.cpp" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "keyword.operator.sizeof.c", + "keyword.operator.sizeof.cpp" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "variable.c", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "source.java", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "punctuation.section.block.begin.java", + "punctuation.section.block.end.java", + "punctuation.definition.method-parameters.begin.java", + "punctuation.definition.method-parameters.end.java", + "meta.method.identifier.java", + "punctuation.section.method.begin.java", + "punctuation.section.method.end.java", + "punctuation.terminator.java", + "punctuation.section.class.begin.java", + "punctuation.section.class.end.java", + "punctuation.section.inner-class.begin.java", + "punctuation.section.inner-class.end.java", + "meta.method-call.java", + "punctuation.section.class.begin.bracket.curly.java", + "punctuation.section.class.end.bracket.curly.java", + "punctuation.section.method.begin.bracket.curly.java", + "punctuation.section.method.end.bracket.curly.java", + "punctuation.separator.period.java", + "punctuation.bracket.angle.java", + "punctuation.definition.annotation.java", + "meta.method.body.java" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.method.java", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": [ + "storage.modifier.import.java", + "storage.type.java", + "storage.type.generic.java" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "import.storage.java", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "token.package.keyword", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "token.package", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.storage.type.java", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator.assignment.go", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "entity.name.package.go", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "support.other.namespace.use.php", + "support.other.namespace.use-as.php", + "support.other.namespace.php", + "entity.other.alias.php", + "meta.interface.php" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "punctuation.section.array.begin.php", + "punctuation.section.array.end.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.php", + "meta.other.type.phpdoc.php", + "keyword.other.type.php", + "keyword.other.array.phpdoc.php" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "meta.function-call.php", + "meta.function-call.object.php", + "meta.function-call.static.php" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": [ + "punctuation.definition.parameters.begin.bracket.round.php", + "punctuation.definition.parameters.end.bracket.round.php", + "punctuation.separator.delimiter.php", + "punctuation.section.scope.begin.php", + "punctuation.section.scope.end.php", + "punctuation.terminator.expression.php", + "punctuation.definition.arguments.begin.bracket.round.php", + "punctuation.definition.arguments.end.bracket.round.php", + "punctuation.definition.storage-type.begin.bracket.round.php", + "punctuation.definition.storage-type.end.bracket.round.php", + "punctuation.definition.array.begin.bracket.round.php", + "punctuation.definition.array.end.bracket.round.php", + "punctuation.definition.begin.bracket.round.php", + "punctuation.definition.end.bracket.round.php", + "punctuation.definition.begin.bracket.curly.php", + "punctuation.definition.end.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php", + "punctuation.definition.section.switch-block.start.bracket.curly.php", + "punctuation.definition.section.switch-block.begin.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "support.constant.ext.php", + "support.constant.std.php", + "support.constant.core.php", + "support.constant.parser-token.php" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "entity.name.goto-label.php", + "support.other.php" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": [ + "keyword.operator.logical.php", + "keyword.operator.bitwise.php", + "keyword.operator.arithmetic.php" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "keyword.operator.heredoc.php", + "keyword.operator.nowdoc.php" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "variable.other.class.php", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "storage.type.haskell", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "storage.type.cs", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "entity.name.label.cs", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "entity.name.scope-resolution.function.call", + "entity.name.scope-resolution.function.definition" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "punctuation.definition.delayed.unison", + "punctuation.definition.list.begin.unison", + "punctuation.definition.list.end.unison", + "punctuation.definition.ability.begin.unison", + "punctuation.definition.ability.end.unison", + "punctuation.operator.assignment.as.unison", + "punctuation.separator.pipe.unison", + "punctuation.separator.delimiter.unison", + "punctuation.definition.hash.unison" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "support.constant.edge", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "support.type.prelude.elm", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "support.constant.elm", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.global.clojure", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.symbol.clojure", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "constant.keyword.clojure", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "storage.modifier.import.groovy", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.method.groovy", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "meta.definition.variable.name.groovy", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "meta.definition.class.inherited.classes.groovy", + "settings": { + "foreground": "#215584" + } + }, + { + "scope": "support.variable.semantic.hlsl", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.scope.prerequisites.makefile", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "source.makefile", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "source.ini", + "settings": { + "foreground": "#215584" + } + }, + { + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "function.parameter.ruby", + "function.parameter.cs" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "constant.language.symbol.elixir", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "entity.name.function.xi", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.name.class.xi", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "constant.character.character-class.regexp.xi", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "constant.regexp.xi", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "keyword.control.xi", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "invalid.xi", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "beginning.punctuation.definition.quote.markdown.xi", + "settings": { + "foreground": "#215584" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "constant.character.xi", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "accent.xi", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "wikiword.xi", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.other.color.rgb-value.xi", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "punctuation.definition.tag.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "support.constant.property-value.scss", + "support.constant.property-value.css" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "keyword.operator.css", + "keyword.operator.scss", + "keyword.operator.less" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": [ + "support.constant.color.w3c-standard-color-name.css", + "support.constant.color.w3c-standard-color-name.scss" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.vendored.property-name.css", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "support.type.property-name.css", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "support.type.property-name", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.property-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.font-name", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.other.attribute-name.class.css", + "settings": { + "foreground": "#ac741d", + "fontStyle": "normal" + } + }, + { + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#5731a7", + "fontStyle": "normal" + } + }, + { + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "meta.selector", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "selector.sass", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "rgb-value", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "less rgb-value", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "control.elements", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator.less", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#ac741d", + "fontStyle": "normal" + } + }, + { + "scope": "constant.character.entity", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "meta.tag", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "markup.heading punctuation.definition.heading", + "entity.name.section" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "markup.heading.setext", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "markup.heading.setext.1.markdown", + "markup.heading.setext.2.markdown" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "markup.bold", + "todo.bold" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "markup.italic", + "punctuation.definition.italic", + "todo.emphasis" + ], + "settings": { + "foreground": "#8836c7", + "fontStyle": "italic" + } + }, + { + "scope": "emphasis md", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": [ + "markup.underline.link.markdown", + "markup.underline.link.image.markdown" + ], + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": [ + "string.other.link.title.markdown", + "string.other.link.description.markdown" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "markup.inline.raw.markdown", + "markup.inline.raw.string.markdown" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "punctuation.definition.list.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown" + ], + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "markup.quote.markdown", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "markup.changed.diff", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "meta.diff.header.from-file", + "meta.diff.header.to-file", + "punctuation.definition.from-file.diff", + "punctuation.definition.to-file.diff" + ], + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#215584" + } + }, + { + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > value.json > string.quoted.json", + "source.json meta.structure.array.json > value.json > string.quoted.json", + "source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation", + "source.json meta.structure.array.json > value.json > string.quoted.json > punctuation" + ], + "settings": { + "foreground": "#215584" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > constant.language.json", + "source.json meta.structure.array.json > constant.language.json" + ], + "settings": { + "foreground": "#2182a1" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "support.type.property-name.json punctuation", + "settings": { + "foreground": "#ac6023" + } + }, + { + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.end", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.begin", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#5731a7" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#8836c7" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "#0a0a0a" + } + } + ], + "semanticTokenColors": { + "comment": "#737373", + "string": "#215584", + "number": "#2182a1", + "regexp": "#2182a1", + "keyword": "#8836c7", + "variable": "#ac6023", + "parameter": "#636363", + "property": "#ac6023", + "function": "#5731a7", + "method": "#5731a7", + "type": "#a631be", + "class": "#a631be", + "namespace": "#ac741d", + "enumMember": "#2182a1", + "variable.constant": "#ac741d", + "variable.defaultLibrary": "#ac741d", + "decorator": "#216cab" + } +} \ No newline at end of file diff --git a/themes/pierre-light-tritanopia.json b/themes/pierre-light-tritanopia.json new file mode 100644 index 0000000..304b560 --- /dev/null +++ b/themes/pierre-light-tritanopia.json @@ -0,0 +1,1961 @@ +{ + "name": "pierre-light-tritanopia", + "displayName": "Pierre Light Tritanopia", + "type": "light", + "colors": { + "editor.background": "#ffffff", + "editor.foreground": "#0a0a0a", + "foreground": "#0a0a0a", + "focusBorder": "#009fff", + "selection.background": "#dfebff", + "editor.selectionBackground": "#009fff2e", + "editor.lineHighlightBackground": "#dfebff8c", + "editorCursor.foreground": "#009fff", + "editorLineNumber.foreground": "#737373", + "editorLineNumber.activeForeground": "#525252", + "editorIndentGuide.background": "#e5e5e5", + "editorIndentGuide.activeBackground": "#d4d4d4", + "diffEditor.insertedTextBackground": "#1e858e33", + "diffEditor.deletedTextBackground": "#d5512f33", + "sideBar.background": "#f5f5f5", + "sideBar.foreground": "#525252", + "sideBar.border": "#e5e5e5", + "sideBarTitle.foreground": "#0a0a0a", + "sideBarSectionHeader.background": "#f5f5f5", + "sideBarSectionHeader.foreground": "#525252", + "sideBarSectionHeader.border": "#e5e5e5", + "activityBar.background": "#f5f5f5", + "activityBar.foreground": "#0a0a0a", + "activityBar.border": "#e5e5e5", + "activityBar.activeBorder": "#009fff", + "activityBarBadge.background": "#009fff", + "activityBarBadge.foreground": "#ffffff", + "titleBar.activeBackground": "#f5f5f5", + "titleBar.activeForeground": "#0a0a0a", + "titleBar.inactiveBackground": "#f5f5f5", + "titleBar.inactiveForeground": "#737373", + "titleBar.border": "#e5e5e5", + "list.activeSelectionBackground": "#dfebffcc", + "list.activeSelectionForeground": "#0a0a0a", + "list.inactiveSelectionBackground": "#dfebff73", + "list.hoverBackground": "#dfebff59", + "list.focusOutline": "#009fff", + "tab.activeBackground": "#ffffff", + "tab.activeForeground": "#0a0a0a", + "tab.activeBorderTop": "#009fff", + "tab.inactiveBackground": "#f5f5f5", + "tab.inactiveForeground": "#737373", + "tab.border": "#e5e5e5", + "editorGroupHeader.tabsBackground": "#f5f5f5", + "editorGroupHeader.tabsBorder": "#e5e5e5", + "panel.background": "#f5f5f5", + "panel.border": "#e5e5e5", + "panelTitle.activeBorder": "#009fff", + "panelTitle.activeForeground": "#0a0a0a", + "panelTitle.inactiveForeground": "#737373", + "statusBar.background": "#f5f5f5", + "statusBar.foreground": "#525252", + "statusBar.border": "#e5e5e5", + "statusBar.noFolderBackground": "#f5f5f5", + "statusBar.debuggingBackground": "#ffab16", + "statusBar.debuggingForeground": "#ffffff", + "statusBarItem.remoteBackground": "#f5f5f5", + "statusBarItem.remoteForeground": "#525252", + "input.background": "#ededed", + "input.border": "#d4d4d4", + "input.foreground": "#0a0a0a", + "input.placeholderForeground": "#8a8a8a", + "dropdown.background": "#ededed", + "dropdown.border": "#d4d4d4", + "dropdown.foreground": "#0a0a0a", + "button.background": "#009fff", + "button.foreground": "#ffffff", + "button.hoverBackground": "#1aa9ff", + "textLink.foreground": "#009fff", + "textLink.activeForeground": "#009fff", + "notifications.background": "#f7f7f7", + "notifications.foreground": "#0a0a0a", + "notifications.border": "#e5e5e5", + "notificationToast.border": "#e5e5e5", + "notificationCenter.border": "#e5e5e5", + "notificationCenterHeader.background": "#f7f7f7", + "notificationCenterHeader.foreground": "#525252", + "notificationLink.foreground": "#009fff", + "notificationsErrorIcon.foreground": "#d5512f", + "notificationsWarningIcon.foreground": "#ffab16", + "notificationsInfoIcon.foreground": "#1a85d4", + "quickInput.background": "#f7f7f7", + "quickInput.foreground": "#0a0a0a", + "quickInputTitle.background": "#f7f7f7", + "widget.border": "#e5e5e5", + "gitDecoration.addedResourceForeground": "#1e858e", + "gitDecoration.conflictingResourceForeground": "#992a75", + "gitDecoration.modifiedResourceForeground": "#009fff", + "gitDecoration.deletedResourceForeground": "#d5512f", + "gitDecoration.untrackedResourceForeground": "#1e858e", + "gitDecoration.ignoredResourceForeground": "#737373", + "merge.currentHeaderBackground": "#992a7533", + "merge.currentContentBackground": "#992a7514", + "merge.incomingHeaderBackground": "#1a85d433", + "merge.incomingContentBackground": "#1a85d414", + "editorOverviewRuler.currentContentForeground": "#992a75", + "editorOverviewRuler.incomingContentForeground": "#1a85d4", + "terminal.titleForeground": "#525252", + "terminal.titleInactiveForeground": "#737373", + "terminal.background": "#f5f5f5", + "terminal.foreground": "#525252", + "terminal.ansiBlack": "#1d1d1d", + "terminal.ansiRed": "#d5512f", + "terminal.ansiGreen": "#1e858e", + "terminal.ansiYellow": "#d5901c", + "terminal.ansiBlue": "#1a85d4", + "terminal.ansiMagenta": "#992a75", + "terminal.ansiCyan": "#17a5af", + "terminal.ansiWhite": "#bcbcbc", + "terminal.ansiBrightBlack": "#1d1d1d", + "terminal.ansiBrightRed": "#ff5d36", + "terminal.ansiBrightGreen": "#17a5af", + "terminal.ansiBrightYellow": "#ffab16", + "terminal.ansiBrightBlue": "#009fff", + "terminal.ansiBrightMagenta": "#bd2e90", + "terminal.ansiBrightCyan": "#00c5d2", + "terminal.ansiBrightWhite": "#bcbcbc" + }, + "tokenColors": [ + { + "scope": [ + "comment", + "punctuation.definition.comment" + ], + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "comment markup.link", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "string", + "constant.other.symbol" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "punctuation.definition.string.begin", + "punctuation.definition.string.end" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "constant.numeric", + "constant.language.boolean" + ], + "settings": { + "foreground": "#1a85d4" + } + }, + { + "scope": "constant", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.definition.constant", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.language", + "settings": { + "foreground": "#1a85d4" + } + }, + { + "scope": "variable.other.constant", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.control", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "storage", + "storage.type", + "storage.modifier" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "token.storage", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "keyword.operator.new", + "keyword.operator.expression.instanceof", + "keyword.operator.expression.typeof", + "keyword.operator.expression.void", + "keyword.operator.expression.delete", + "keyword.operator.expression.in", + "keyword.operator.expression.of", + "keyword.operator.expression.keyof" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.operator.delete", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "variable", + "identifier", + "meta.definition.variable" + ], + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": [ + "variable.other.readwrite", + "meta.object-literal.key", + "support.variable.property", + "support.variable.object.process", + "support.variable.object.node" + ], + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.parameter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "variable.parameter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "variable.parameter.function.language.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "variable.parameter.function.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "support.function", + "entity.name.function", + "meta.function-call", + "meta.require", + "support.function.any-method", + "variable.function" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "keyword.other.special-method", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "entity.name.function", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "support.function.console", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": [ + "support.type", + "entity.name.type", + "entity.name.class", + "storage.type" + ], + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": [ + "support.class", + "entity.name.type.class" + ], + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": [ + "entity.name.class", + "variable.other.class.js", + "variable.other.class.ts" + ], + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": "entity.name.class.identifier.namespace.type", + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": "entity.name.type.namespace", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": "entity.name.namespace", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.logical", + "keyword.operator.bitwise", + "keyword.operator.channel" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "keyword.operator.arithmetic", + "keyword.operator.comparison", + "keyword.operator.relational", + "keyword.operator.increment", + "keyword.operator.decrement" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "keyword.operator.assignment", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "keyword.operator.assignment.compound", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "keyword.operator.assignment.compound.js", + "keyword.operator.assignment.compound.ts" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "keyword.operator.ternary", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.operator.optional", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "punctuation", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.delimiter", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.separator.key-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.terminator", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.square", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.brace.round", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "function.brace", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.parameters", + "punctuation.definition.typeparameters" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.block", + "punctuation.definition.tag" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "meta.tag.tsx", + "meta.tag.jsx", + "meta.tag.js", + "meta.tag.ts" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.expression.import", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "keyword.operator.module", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "support.type.object.console", + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": [ + "support.module.node", + "support.type.object.module", + "entity.name.type.module" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "support.constant.math", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "support.constant.property.math", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.constant.json", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "support.type.object.dom", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "support.variable.dom", + "support.variable.property.dom" + ], + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": "support.variable.property.process", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.property.object", + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": "variable.parameter.function.js", + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": [ + "keyword.other.template.begin", + "keyword.other.template.end" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "keyword.other.substitution.begin", + "keyword.other.substitution.end" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "meta.template.expression", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "punctuation.section.embedded", + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": "variable.interpolation", + "settings": { + "foreground": "#ad4529" + } + }, + { + "scope": [ + "punctuation.section.embedded.begin", + "punctuation.section.embedded.end" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "punctuation.quasi.element", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "support.type.primitive.ts", + "support.type.builtin.ts", + "support.type.primitive.tsx", + "support.type.builtin.tsx" + ], + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": "support.type.type.flowtype", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "support.type.primitive", + "settings": { + "foreground": "#bd2e90" + } + }, + { + "scope": [ + "meta.decorator", + "meta.decorator punctuation.decorator" + ], + "settings": { + "foreground": "#1a85d4" + } + }, + { + "scope": "entity.name.function.decorator", + "settings": { + "foreground": "#1a85d4" + } + }, + { + "scope": "punctuation.definition.decorator", + "settings": { + "foreground": "#1a85d4" + } + }, + { + "scope": "support.variable.magic.python", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "variable.parameter.function.language.special.self.python", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "punctuation.separator.period.python", + "punctuation.separator.element.python", + "punctuation.parenthesis.begin.python", + "punctuation.parenthesis.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "punctuation.definition.arguments.begin.python", + "punctuation.definition.arguments.end.python", + "punctuation.separator.arguments.python", + "punctuation.definition.list.begin.python", + "punctuation.definition.list.end.python" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.python", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "meta.function-call.generic.python", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "constant.character.format.placeholder.other.python", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "meta.function.decorator.python", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": [ + "support.token.decorator.python", + "meta.function.decorator.identifier.python" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "storage.modifier.lifetime.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.function.std.rust", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "entity.name.lifetime.rust", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "variable.language.rust", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator.misc.rust", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "keyword.operator.sigil.rust", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "support.constant.core.rust", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "meta.function.c", + "meta.function.cpp" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "punctuation.section.block.begin.bracket.curly.cpp", + "punctuation.section.block.end.bracket.curly.cpp", + "punctuation.terminator.statement.c", + "punctuation.section.block.begin.bracket.curly.c", + "punctuation.section.block.end.bracket.curly.c", + "punctuation.section.parens.begin.bracket.round.c", + "punctuation.section.parens.end.bracket.round.c", + "punctuation.section.parameters.begin.bracket.round.c", + "punctuation.section.parameters.end.bracket.round.c" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "keyword.operator.assignment.c", + "keyword.operator.comparison.c", + "keyword.operator.c", + "keyword.operator.increment.c", + "keyword.operator.decrement.c", + "keyword.operator.bitwise.shift.c" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "keyword.operator.assignment.cpp", + "keyword.operator.comparison.cpp", + "keyword.operator.cpp", + "keyword.operator.increment.cpp", + "keyword.operator.decrement.cpp", + "keyword.operator.bitwise.shift.cpp" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "punctuation.separator.c", + "punctuation.separator.cpp" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "support.type.posix-reserved.c", + "support.type.posix-reserved.cpp" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "keyword.operator.sizeof.c", + "keyword.operator.sizeof.cpp" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "variable.c", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.annotation.java", + "storage.type.object.array.java" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "source.java", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "punctuation.section.block.begin.java", + "punctuation.section.block.end.java", + "punctuation.definition.method-parameters.begin.java", + "punctuation.definition.method-parameters.end.java", + "meta.method.identifier.java", + "punctuation.section.method.begin.java", + "punctuation.section.method.end.java", + "punctuation.terminator.java", + "punctuation.section.class.begin.java", + "punctuation.section.class.end.java", + "punctuation.section.inner-class.begin.java", + "punctuation.section.inner-class.end.java", + "meta.method-call.java", + "punctuation.section.class.begin.bracket.curly.java", + "punctuation.section.class.end.bracket.curly.java", + "punctuation.section.method.begin.bracket.curly.java", + "punctuation.section.method.end.bracket.curly.java", + "punctuation.separator.period.java", + "punctuation.bracket.angle.java", + "punctuation.definition.annotation.java", + "meta.method.body.java" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "meta.method.java", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": [ + "storage.modifier.import.java", + "storage.type.java", + "storage.type.generic.java" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator.instanceof.java", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "meta.definition.variable.name.java", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "token.variable.parameter.java", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "import.storage.java", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "token.package.keyword", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "token.package", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.storage.type.java", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator.assignment.go", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "keyword.operator.arithmetic.go", + "keyword.operator.address.go" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "entity.name.package.go", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "support.other.namespace.use.php", + "support.other.namespace.use-as.php", + "support.other.namespace.php", + "entity.other.alias.php", + "meta.interface.php" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator.error-control.php", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.operator.type.php", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "punctuation.section.array.begin.php", + "punctuation.section.array.end.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "storage.type.php", + "meta.other.type.phpdoc.php", + "keyword.other.type.php", + "keyword.other.array.phpdoc.php" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "meta.function-call.php", + "meta.function-call.object.php", + "meta.function-call.static.php" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": [ + "punctuation.definition.parameters.begin.bracket.round.php", + "punctuation.definition.parameters.end.bracket.round.php", + "punctuation.separator.delimiter.php", + "punctuation.section.scope.begin.php", + "punctuation.section.scope.end.php", + "punctuation.terminator.expression.php", + "punctuation.definition.arguments.begin.bracket.round.php", + "punctuation.definition.arguments.end.bracket.round.php", + "punctuation.definition.storage-type.begin.bracket.round.php", + "punctuation.definition.storage-type.end.bracket.round.php", + "punctuation.definition.array.begin.bracket.round.php", + "punctuation.definition.array.end.bracket.round.php", + "punctuation.definition.begin.bracket.round.php", + "punctuation.definition.end.bracket.round.php", + "punctuation.definition.begin.bracket.curly.php", + "punctuation.definition.end.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php", + "punctuation.definition.section.switch-block.start.bracket.curly.php", + "punctuation.definition.section.switch-block.begin.bracket.curly.php", + "punctuation.definition.section.switch-block.end.bracket.curly.php" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "support.constant.ext.php", + "support.constant.std.php", + "support.constant.core.php", + "support.constant.parser-token.php" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "entity.name.goto-label.php", + "support.other.php" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": [ + "keyword.operator.logical.php", + "keyword.operator.bitwise.php", + "keyword.operator.arithmetic.php" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "keyword.operator.regexp.php", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.operator.comparison.php", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "keyword.operator.heredoc.php", + "keyword.operator.nowdoc.php" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "variable.other.class.php", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "invalid.illegal.non-null-typehinted.php", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "variable.other.generic-type.haskell", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "storage.type.haskell", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "storage.type.cs", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "entity.name.variable.local.cs", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "entity.name.label.cs", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "entity.name.scope-resolution.function.call", + "entity.name.scope-resolution.function.definition" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "punctuation.definition.delayed.unison", + "punctuation.definition.list.begin.unison", + "punctuation.definition.list.end.unison", + "punctuation.definition.ability.begin.unison", + "punctuation.definition.ability.end.unison", + "punctuation.operator.assignment.as.unison", + "punctuation.separator.pipe.unison", + "punctuation.separator.delimiter.unison", + "punctuation.definition.hash.unison" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "support.constant.edge", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "support.type.prelude.elm", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "support.constant.elm", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.global.clojure", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "meta.symbol.clojure", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "constant.keyword.clojure", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "meta.arguments.coffee", + "variable.parameter.function.coffee" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "storage.modifier.import.groovy", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "meta.method.groovy", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "meta.definition.variable.name.groovy", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "meta.definition.class.inherited.classes.groovy", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "support.variable.semantic.hlsl", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "support.type.texture.hlsl", + "support.type.sampler.hlsl", + "support.type.object.hlsl", + "support.type.object.rw.hlsl", + "support.type.fx.hlsl", + "support.type.object.hlsl" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "text.variable", + "text.bracketed" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "support.type.swift", + "support.type.vb.asp" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "meta.scope.prerequisites.makefile", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "source.makefile", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "source.ini", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "constant.language.symbol.ruby", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "function.parameter.ruby", + "function.parameter.cs" + ], + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "constant.language.symbol.elixir", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "entity.name.function.xi", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "entity.name.class.xi", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "constant.character.character-class.regexp.xi", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "constant.regexp.xi", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "keyword.control.xi", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "invalid.xi", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "beginning.punctuation.definition.quote.markdown.xi", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "constant.character.xi", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "accent.xi", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "wikiword.xi", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.other.color.rgb-value.xi", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "punctuation.definition.tag.xi", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": [ + "support.constant.property-value.scss", + "support.constant.property-value.css" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "keyword.operator.css", + "keyword.operator.scss", + "keyword.operator.less" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "support.constant.color.w3c-standard-color-name.css", + "support.constant.color.w3c-standard-color-name.scss" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.separator.list.comma.css", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.type.vendored.property-name.css", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "support.type.property-name.css", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "support.type.property-name", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.property-value", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "support.constant.font-name", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.other.attribute-name.class.css", + "settings": { + "foreground": "#1e858e", + "fontStyle": "normal" + } + }, + { + "scope": "entity.other.attribute-name.id", + "settings": { + "foreground": "#216cab", + "fontStyle": "normal" + } + }, + { + "scope": [ + "entity.other.attribute-name.pseudo-element", + "entity.other.attribute-name.pseudo-class" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "meta.selector", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "selector.sass", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "rgb-value", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "inline-color-decoration rgb-value", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "less rgb-value", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "control.elements", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "keyword.operator.less", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#1e858e", + "fontStyle": "normal" + } + }, + { + "scope": "constant.character.entity", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "meta.tag", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "invalid.illegal.bad-ampersand.html", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "markup.heading punctuation.definition.heading", + "entity.name.section" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "entity.name.section.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "punctuation.definition.heading.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "markup.heading.setext", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": [ + "markup.heading.setext.1.markdown", + "markup.heading.setext.2.markdown" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "markup.bold", + "todo.bold" + ], + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "punctuation.definition.bold", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "punctuation.definition.bold.markdown", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": [ + "markup.italic", + "punctuation.definition.italic", + "todo.emphasis" + ], + "settings": { + "foreground": "#a631be", + "fontStyle": "italic" + } + }, + { + "scope": "emphasis md", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "markup.italic.markdown", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": [ + "markup.underline.link.markdown", + "markup.underline.link.image.markdown" + ], + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": [ + "string.other.link.title.markdown", + "string.other.link.description.markdown" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "punctuation.definition.metadata.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "markup.inline.raw.markdown", + "markup.inline.raw.string.markdown" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "punctuation.definition.list.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "beginning.punctuation.definition.list.markdown", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "punctuation.definition.string.begin.markdown", + "punctuation.definition.string.end.markdown" + ], + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "markup.quote.markdown", + "settings": { + "foreground": "#737373" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "markup.changed.diff", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "meta.diff.header.from-file", + "meta.diff.header.to-file", + "punctuation.definition.from-file.diff", + "punctuation.definition.to-file.diff" + ], + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "markup.inserted.diff", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "markup.deleted.diff", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "constant.other.character-class.regexp", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > value.json > string.quoted.json", + "source.json meta.structure.array.json > value.json > string.quoted.json", + "source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation", + "source.json meta.structure.array.json > value.json > string.quoted.json > punctuation" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": [ + "source.json meta.structure.dictionary.json > constant.language.json", + "source.json meta.structure.array.json > constant.language.json" + ], + "settings": { + "foreground": "#1e858e" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "support.type.property-name.json punctuation", + "settings": { + "foreground": "#d5512f" + } + }, + { + "scope": "punctuation.definition.block.sequence.item.yaml", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.end", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "block.scope.begin", + "settings": { + "foreground": "#636363" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#216cab" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#ac741d" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#a631be" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "#0a0a0a" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "#0a0a0a" + } + } + ], + "semanticTokenColors": { + "comment": "#737373", + "string": "#1e858e", + "number": "#1a85d4", + "regexp": "#1e858e", + "keyword": "#a631be", + "variable": "#ad4529", + "parameter": "#636363", + "property": "#ad4529", + "function": "#216cab", + "method": "#216cab", + "type": "#bd2e90", + "class": "#bd2e90", + "namespace": "#d5512f", + "enumMember": "#1e858e", + "variable.constant": "#ac741d", + "variable.defaultLibrary": "#d5512f", + "decorator": "#1a85d4" + } +} \ No newline at end of file diff --git a/zed/extension.toml b/zed/extension.toml index da6d905..58ab0fd 100644 --- a/zed/extension.toml +++ b/zed/extension.toml @@ -1,7 +1,7 @@ id = "pierre-theme" name = "Pierre" description = "Modern light and dark color themes built by Pierre for Diffs.com, and now for you." -version = "0.0.29" +version = "0.0.30" schema_version = 1 authors = ["pierrecomputer"] repository = "https://github.com/pierrecomputer/pierre-theme" diff --git a/zed/themes/pierre.json b/zed/themes/pierre.json index 7111bb1..4e1b168 100644 --- a/zed/themes/pierre.json +++ b/zed/themes/pierre.json @@ -502,132 +502,132 @@ } }, { - "name": "Pierre Light Soft", + "name": "Pierre Light Protanopia & Deuteranopia", "appearance": "light", "style": { - "background": "#f7f7f7", - "surface.background": "#f7f7f7", - "elevated_surface.background": "#fafafa", + "background": "#f5f5f5", + "surface.background": "#f5f5f5", + "elevated_surface.background": "#f7f7f7", "drop_target.background": "#009fff26", "editor.background": "#ffffff", - "editor.foreground": "#525252", + "editor.foreground": "#0a0a0a", "editor.gutter.background": "#ffffff", "editor.active_line.background": "#dfebff8c", - "editor.active_line_number": "#737373", - "editor.line_number": "#8a8a8a", + "editor.active_line_number": "#525252", + "editor.line_number": "#737373", "editor.highlighted_line.background": "#dfebff59", - "editor.indent_guide": "#ededed", - "editor.indent_guide_active": "#e5e5e5", - "editor.invisible": "#a3a3a3", - "editor.wrap_guide": "#ededed", - "editor.active_wrap_guide": "#e5e5e5", + "editor.indent_guide": "#e5e5e5", + "editor.indent_guide_active": "#d4d4d4", + "editor.invisible": "#8a8a8a", + "editor.wrap_guide": "#e5e5e5", + "editor.active_wrap_guide": "#d4d4d4", "editor.document_highlight.read_background": "#009fff1a", "editor.document_highlight.write_background": "#009fff2e", "editor.document_highlight.bracket_background": "#009fff33", - "editor.subheader.background": "#f7f7f7", - "text": "#525252", - "text.muted": "#8a8a8a", - "text.placeholder": "#a3a3a3", - "text.disabled": "#a3a3a3", + "editor.subheader.background": "#f5f5f5", + "text": "#0a0a0a", + "text.muted": "#737373", + "text.placeholder": "#8a8a8a", + "text.disabled": "#8a8a8a", "text.accent": "#009fff", - "border": "#e5e5e5", - "border.variant": "#ededed", + "border": "#d4d4d4", + "border.variant": "#e5e5e5", "border.focused": "#009fff", "border.selected": "#009fff", "border.transparent": "transparent", "border.disabled": "#d4d4d4", - "element.background": "#f5f5f5", + "element.background": "#ededed", "element.hover": "#dfebff80", "element.active": "#dfebffb3", "element.selected": "#dfebffcc", - "element.disabled": "#f5f5f580", + "element.disabled": "#ededed80", "ghost_element.background": "transparent", "ghost_element.hover": "#dfebff59", "ghost_element.active": "#dfebff8c", "ghost_element.selected": "#dfebffa6", "ghost_element.disabled": "transparent", - "icon": "#737373", - "icon.muted": "#8a8a8a", - "icon.disabled": "#a3a3a3", - "icon.placeholder": "#a3a3a3", + "icon": "#525252", + "icon.muted": "#737373", + "icon.disabled": "#8a8a8a", + "icon.placeholder": "#8a8a8a", "icon.accent": "#009fff", "link_text.hover": "#009fff", - "error": "#ff2e3f", - "error.background": "#ff2e3f1a", - "error.border": "#ff2e3f4d", + "error": "#ac6023", + "error.background": "#ac60231a", + "error.border": "#ac60234d", "warning": "#009fff", "warning.background": "#009fff1a", "warning.border": "#009fff4d", - "success": "#07c480", - "success.background": "#07c4801a", - "success.border": "#07c4804d", - "info": "#08c0ef", - "info.background": "#08c0ef1a", - "info.border": "#08c0ef4d", - "hint": "#8a8a8a", - "hint.background": "#8a8a8a1a", - "hint.border": "#8a8a8a33", - "predictive": "#a3a3a3", - "predictive.background": "#a3a3a31a", - "predictive.border": "#a3a3a333", - "unreachable": "#a3a3a3", - "unreachable.background": "#a3a3a30d", - "unreachable.border": "#a3a3a31a", - "created": "#07c480", - "created.background": "#07c4801a", - "created.border": "#07c4804d", + "success": "#216cab", + "success.background": "#216cab1a", + "success.border": "#216cab4d", + "info": "#2182a1", + "info.background": "#2182a11a", + "info.border": "#2182a14d", + "hint": "#737373", + "hint.background": "#7373731a", + "hint.border": "#73737333", + "predictive": "#8a8a8a", + "predictive.background": "#8a8a8a1a", + "predictive.border": "#8a8a8a33", + "unreachable": "#8a8a8a", + "unreachable.background": "#8a8a8a0d", + "unreachable.border": "#8a8a8a1a", + "created": "#216cab", + "created.background": "#216cab1a", + "created.border": "#216cab4d", "modified": "#009fff", "modified.background": "#009fff1a", "modified.border": "#009fff4d", - "deleted": "#ff2e3f", - "deleted.background": "#ff2e3f1a", - "deleted.border": "#ff2e3f4d", - "conflict": "#7b43f8", - "conflict.background": "#7b43f81a", - "conflict.border": "#7b43f84d", - "hidden": "#a3a3a3", - "hidden.background": "#a3a3a30d", - "hidden.border": "#a3a3a31a", - "ignored": "#8a8a8a", - "ignored.background": "#8a8a8a0d", - "ignored.border": "#8a8a8a1a", - "renamed": "#08c0ef", - "renamed.background": "#08c0ef1a", - "renamed.border": "#08c0ef4d", + "deleted": "#ac6023", + "deleted.background": "#ac60231a", + "deleted.border": "#ac60234d", + "conflict": "#58287c", + "conflict.background": "#58287c1a", + "conflict.border": "#58287c4d", + "hidden": "#8a8a8a", + "hidden.background": "#8a8a8a0d", + "hidden.border": "#8a8a8a1a", + "ignored": "#737373", + "ignored.background": "#7373730d", + "ignored.border": "#7373731a", + "renamed": "#2182a1", + "renamed.background": "#2182a11a", + "renamed.border": "#2182a14d", "search.match_background": "#ffca004d", - "tab_bar.background": "#f7f7f7", - "tab.active_background": "#f7f7f7", - "tab.inactive_background": "#f7f7f7", - "toolbar.background": "#f7f7f7", - "title_bar.background": "#f7f7f7", - "title_bar.inactive_background": "#f7f7f7", - "panel.background": "#f7f7f7", + "tab_bar.background": "#f5f5f5", + "tab.active_background": "#f5f5f5", + "tab.inactive_background": "#f5f5f5", + "toolbar.background": "#f5f5f5", + "title_bar.background": "#f5f5f5", + "title_bar.inactive_background": "#f5f5f5", + "panel.background": "#f5f5f5", "panel.focused_border": "#009fff", - "status_bar.background": "#f7f7f7", - "scrollbar.thumb.background": "#a3a3a34d", - "scrollbar.thumb.hover_background": "#a3a3a380", + "status_bar.background": "#f5f5f5", + "scrollbar.thumb.background": "#8a8a8a4d", + "scrollbar.thumb.hover_background": "#8a8a8a80", "scrollbar.thumb.border": "transparent", "scrollbar.track.background": "transparent", "scrollbar.track.border": "transparent", - "terminal.background": "#f7f7f7", - "terminal.foreground": "#737373", - "terminal.bright_foreground": "#525252", - "terminal.dim_foreground": "#8a8a8a", + "terminal.background": "#f5f5f5", + "terminal.foreground": "#525252", + "terminal.bright_foreground": "#0a0a0a", + "terminal.dim_foreground": "#737373", "terminal.ansi.black": "#1d1d1d", - "terminal.ansi.red": "#ff2e3f", - "terminal.ansi.green": "#0dbe4e", + "terminal.ansi.red": "#ac6023", + "terminal.ansi.green": "#216cab", "terminal.ansi.yellow": "#ffca00", - "terminal.ansi.blue": "#009fff", - "terminal.ansi.magenta": "#e130ac", - "terminal.ansi.cyan": "#08c0ef", + "terminal.ansi.blue": "#1a85d4", + "terminal.ansi.magenta": "#8836c7", + "terminal.ansi.cyan": "#2182a1", "terminal.ansi.white": "#bcbcbc", "terminal.ansi.bright_black": "#1d1d1d", - "terminal.ansi.bright_red": "#ff2e3f", - "terminal.ansi.bright_green": "#86c427", + "terminal.ansi.bright_red": "#d47628", + "terminal.ansi.bright_green": "#1a85d4", "terminal.ansi.bright_yellow": "#ffca00", "terminal.ansi.bright_blue": "#009fff", - "terminal.ansi.bright_magenta": "#e130ac", - "terminal.ansi.bright_cyan": "#08c0ef", + "terminal.ansi.bright_magenta": "#a13cee", + "terminal.ansi.bright_cyan": "#1ca1c7", "terminal.ansi.bright_white": "#bcbcbc", "players": [ { @@ -636,24 +636,24 @@ "selection": "#009fff40" }, { - "cursor": "#07c480", - "background": "#07c480", - "selection": "#07c48040" + "cursor": "#216cab", + "background": "#216cab", + "selection": "#216cab40" }, { - "cursor": "#ff678d", - "background": "#ff678d", - "selection": "#ff678d40" + "cursor": "#8836c7", + "background": "#8836c7", + "selection": "#8836c740" }, { - "cursor": "#9d6afb", - "background": "#9d6afb", - "selection": "#9d6afb40" + "cursor": "#5731a7", + "background": "#5731a7", + "selection": "#5731a740" }, { - "cursor": "#0dbe4e", - "background": "#0dbe4e", - "selection": "#0dbe4e40" + "cursor": "#215584", + "background": "#215584", + "selection": "#21558440" }, { "cursor": "#ffca00", @@ -661,94 +661,94 @@ "selection": "#ffca0040" }, { - "cursor": "#d568ea", - "background": "#d568ea", - "selection": "#d568ea40" + "cursor": "#a631be", + "background": "#a631be", + "selection": "#a631be40" }, { - "cursor": "#08c0ef", - "background": "#08c0ef", - "selection": "#08c0ef40" + "cursor": "#2182a1", + "background": "#2182a1", + "selection": "#2182a140" } ], "syntax": { "comment": { - "color": "#8a8a8a" + "color": "#737373" }, "comment.doc": { - "color": "#8a8a8a" + "color": "#737373" }, "string": { - "color": "#0dbe4e" + "color": "#215584" }, "string.escape": { - "color": "#00cab1" + "color": "#1e858e" }, "string.regex": { - "color": "#00c5d2" + "color": "#2182a1" }, "string.special": { - "color": "#00cab1" + "color": "#1e858e" }, "string.special.symbol": { - "color": "#ffca00" + "color": "#ac741d" }, "number": { - "color": "#08c0ef" + "color": "#2182a1" }, "constant": { - "color": "#ffca00" + "color": "#ac741d" }, "boolean": { - "color": "#08c0ef" + "color": "#2182a1" }, "keyword": { - "color": "#ff678d" + "color": "#8836c7" }, "keyword.operator": { - "color": "#68cdf2" + "color": "#2182a1" }, "function": { - "color": "#9d6afb" + "color": "#5731a7" }, "function.method": { - "color": "#9d6afb" + "color": "#5731a7" }, "function.builtin": { - "color": "#9d6afb" + "color": "#5731a7" }, "function.special.definition": { - "color": "#9d6afb" + "color": "#5731a7" }, "function.call": { - "color": "#9d6afb" + "color": "#5731a7" }, "type": { - "color": "#d568ea" + "color": "#a631be" }, "type.builtin": { - "color": "#d568ea" + "color": "#a631be" }, "constructor": { - "color": "#d568ea" + "color": "#a631be" }, "variable": { - "color": "#fe8c2c" + "color": "#ac6023" }, "variable.builtin": { - "color": "#ffab16" + "color": "#ac741d" }, "variable.member": { - "color": "#fe8c2c" + "color": "#ac6023" }, "variable.parameter": { - "color": "#737373" + "color": "#636363" }, "variable.special": { - "color": "#ffab16" + "color": "#ac741d" }, "property": { - "color": "#fe8c2c" + "color": "#ac6023" }, "property.css": { "color": "#009fff" @@ -760,215 +760,215 @@ "color": "#009fff" }, "value": { - "color": "#08c0ef" + "color": "#2182a1" }, "constant.css": { - "color": "#ffca00" + "color": "#ac741d" }, "string.plain": { - "color": "#08c0ef" + "color": "#2182a1" }, "plain_value": { - "color": "#08c0ef" + "color": "#2182a1" }, "tag.css": { - "color": "#ff5d36" + "color": "#ac6023" }, "tag_name": { - "color": "#ff5d36" + "color": "#ac6023" }, "class": { - "color": "#07c480" + "color": "#ac741d" }, "class_name": { - "color": "#07c480" + "color": "#ac741d" }, "selector.class": { - "color": "#07c480" + "color": "#ac741d" }, "selector.id": { - "color": "#9d6afb" + "color": "#5731a7" }, "id_name": { - "color": "#9d6afb" + "color": "#5731a7" }, "selector.pseudo": { - "color": "#68cdf2" + "color": "#2182a1" }, "pseudo_class_selector": { - "color": "#68cdf2" + "color": "#2182a1" }, "pseudo_element_selector": { - "color": "#68cdf2" + "color": "#2182a1" }, "keyword.directive": { - "color": "#ff678d" + "color": "#8836c7" }, "keyword.control.at-rule": { - "color": "#ff678d" + "color": "#8836c7" }, "at_keyword": { - "color": "#ff678d" + "color": "#8836c7" }, "variable.scss": { - "color": "#fe8c2c" + "color": "#ac6023" }, "variable.css": { - "color": "#fe8c2c" + "color": "#ac6023" }, "property.custom": { - "color": "#fe8c2c" + "color": "#ac6023" }, "unit": { - "color": "#08c0ef" + "color": "#2182a1" }, "number.unit": { - "color": "#08c0ef" + "color": "#2182a1" }, "color": { - "color": "#ffca00" + "color": "#ac741d" }, "constant.color": { - "color": "#ffca00" + "color": "#ac741d" }, "keyword.important": { - "color": "#ff678d" + "color": "#8836c7" }, "variable.language": { - "color": "#ffab16" + "color": "#ac741d" }, "this": { - "color": "#ffab16" + "color": "#ac741d" }, "self": { - "color": "#ffab16" + "color": "#ac741d" }, "type.class": { - "color": "#d568ea" + "color": "#a631be" }, "property.object": { - "color": "#fe8c2c" + "color": "#ac6023" }, "property_identifier": { - "color": "#fe8c2c" + "color": "#ac6023" }, "shorthand_property_identifier": { - "color": "#fe8c2c" + "color": "#ac6023" }, "shorthand_property_identifier_pattern": { - "color": "#fe8c2c" + "color": "#ac6023" }, "method_definition": { - "color": "#9d6afb" + "color": "#5731a7" }, "function.method.call": { - "color": "#9d6afb" + "color": "#5731a7" }, "string.template": { - "color": "#0dbe4e" + "color": "#215584" }, "template_string": { - "color": "#0dbe4e" + "color": "#215584" }, "tag.jsx": { - "color": "#ff5d36" + "color": "#ac6023" }, "tag.component": { - "color": "#d568ea" + "color": "#a631be" }, "punctuation": { - "color": "#737373" + "color": "#636363" }, "punctuation.bracket": { - "color": "#737373" + "color": "#636363" }, "punctuation.delimiter": { - "color": "#737373" + "color": "#636363" }, "punctuation.list_marker": { - "color": "#737373" + "color": "#636363" }, "punctuation.special": { - "color": "#ff678d" + "color": "#8836c7" }, "operator": { - "color": "#68cdf2" + "color": "#2182a1" }, "tag": { - "color": "#ff5d36" + "color": "#ac6023" }, "attribute": { - "color": "#07c480" + "color": "#ac741d" }, "label": { - "color": "#ffab16" + "color": "#ac741d" }, "namespace": { - "color": "#ffab16" + "color": "#ac741d" }, "decorator": { - "color": "#69b1ff" + "color": "#216cab" }, "attribute.builtin": { - "color": "#69b1ff" + "color": "#216cab" }, "embedded": { - "color": "#525252" + "color": "#0a0a0a" }, "preproc": { - "color": "#ff678d" + "color": "#8836c7" }, "text.literal": { - "color": "#0dbe4e" + "color": "#215584" }, "markup.heading": { - "color": "#ff5d36", + "color": "#ac6023", "font_weight": 700 }, "markup.bold": { - "color": "#ffca00", + "color": "#ac741d", "font_weight": 700 }, "markup.italic": { - "color": "#ff678d", + "color": "#8836c7", "font_style": "italic" }, "markup.strikethrough": { - "color": "#8a8a8a" + "color": "#737373" }, "markup.link.url": { "color": "#009fff" }, "markup.link.text": { - "color": "#9d6afb" + "color": "#5731a7" }, "markup.quote": { - "color": "#8a8a8a", + "color": "#737373", "font_style": "italic" }, "markup.list": { - "color": "#ff5d36" + "color": "#ac6023" }, "markup.list.numbered": { - "color": "#ff5d36" + "color": "#ac6023" }, "markup.list.unnumbered": { - "color": "#ff5d36" + "color": "#ac6023" }, "markup.raw": { - "color": "#0dbe4e" + "color": "#215584" }, "markup.raw.inline": { - "color": "#0dbe4e" + "color": "#215584" }, "markup.raw.block": { - "color": "#0dbe4e" + "color": "#215584" }, "diff.plus": { - "color": "#07c480" + "color": "#216cab" }, "diff.minus": { - "color": "#ff2e3f" + "color": "#ac6023" }, "diff.delta": { "color": "#ffca00" @@ -977,7 +977,7 @@ "color": "#009fff" }, "link_uri": { - "color": "#ff678d" + "color": "#8836c7" }, "emphasis": { "font_style": "italic" @@ -989,65 +989,65 @@ "color": "#009fff" }, "title": { - "color": "#ff5d36", + "color": "#ac6023", "font_weight": 700 }, "predictive": { - "color": "#a3a3a3", + "color": "#8a8a8a", "font_style": "italic" } } } }, { - "name": "Pierre Dark", - "appearance": "dark", + "name": "Pierre Light Soft", + "appearance": "light", "style": { - "background": "#171717", - "surface.background": "#171717", - "elevated_surface.background": "#101010", + "background": "#f7f7f7", + "surface.background": "#f7f7f7", + "elevated_surface.background": "#fafafa", "drop_target.background": "#009fff26", - "editor.background": "#0a0a0a", - "editor.foreground": "#fafafa", - "editor.gutter.background": "#0a0a0a", - "editor.active_line.background": "#19283c8c", - "editor.active_line_number": "#a3a3a3", - "editor.line_number": "#737373", - "editor.highlighted_line.background": "#19283c59", - "editor.indent_guide": "#1d1d1d", - "editor.indent_guide_active": "#262626", - "editor.invisible": "#636363", - "editor.wrap_guide": "#1d1d1d", - "editor.active_wrap_guide": "#262626", - "editor.document_highlight.read_background": "#009fff26", - "editor.document_highlight.write_background": "#009fff40", + "editor.background": "#ffffff", + "editor.foreground": "#525252", + "editor.gutter.background": "#ffffff", + "editor.active_line.background": "#dfebff8c", + "editor.active_line_number": "#737373", + "editor.line_number": "#8a8a8a", + "editor.highlighted_line.background": "#dfebff59", + "editor.indent_guide": "#ededed", + "editor.indent_guide_active": "#e5e5e5", + "editor.invisible": "#a3a3a3", + "editor.wrap_guide": "#ededed", + "editor.active_wrap_guide": "#e5e5e5", + "editor.document_highlight.read_background": "#009fff1a", + "editor.document_highlight.write_background": "#009fff2e", "editor.document_highlight.bracket_background": "#009fff33", - "editor.subheader.background": "#171717", - "text": "#fafafa", - "text.muted": "#737373", - "text.placeholder": "#636363", - "text.disabled": "#636363", + "editor.subheader.background": "#f7f7f7", + "text": "#525252", + "text.muted": "#8a8a8a", + "text.placeholder": "#a3a3a3", + "text.disabled": "#a3a3a3", "text.accent": "#009fff", - "border": "#1d1d1d", - "border.variant": "#262626", + "border": "#e5e5e5", + "border.variant": "#ededed", "border.focused": "#009fff", "border.selected": "#009fff", "border.transparent": "transparent", - "border.disabled": "#262626", - "element.background": "#1d1d1d", - "element.hover": "#19283c80", - "element.active": "#19283cb3", - "element.selected": "#19283c99", - "element.disabled": "#1d1d1d80", + "border.disabled": "#d4d4d4", + "element.background": "#f5f5f5", + "element.hover": "#dfebff80", + "element.active": "#dfebffb3", + "element.selected": "#dfebffcc", + "element.disabled": "#f5f5f580", "ghost_element.background": "transparent", - "ghost_element.hover": "#19283c59", - "ghost_element.active": "#19283c8c", - "ghost_element.selected": "#19283c80", + "ghost_element.hover": "#dfebff59", + "ghost_element.active": "#dfebff8c", + "ghost_element.selected": "#dfebffa6", "ghost_element.disabled": "transparent", - "icon": "#a3a3a3", - "icon.muted": "#737373", - "icon.disabled": "#636363", - "icon.placeholder": "#636363", + "icon": "#737373", + "icon.muted": "#8a8a8a", + "icon.disabled": "#a3a3a3", + "icon.placeholder": "#a3a3a3", "icon.accent": "#009fff", "link_text.hover": "#009fff", "error": "#ff2e3f", @@ -1062,15 +1062,15 @@ "info": "#08c0ef", "info.background": "#08c0ef1a", "info.border": "#08c0ef4d", - "hint": "#737373", - "hint.background": "#7373731a", - "hint.border": "#73737333", - "predictive": "#636363", - "predictive.background": "#6363631a", - "predictive.border": "#63636333", - "unreachable": "#636363", - "unreachable.background": "#6363630d", - "unreachable.border": "#6363631a", + "hint": "#8a8a8a", + "hint.background": "#8a8a8a1a", + "hint.border": "#8a8a8a33", + "predictive": "#a3a3a3", + "predictive.background": "#a3a3a31a", + "predictive.border": "#a3a3a333", + "unreachable": "#a3a3a3", + "unreachable.background": "#a3a3a30d", + "unreachable.border": "#a3a3a31a", "created": "#07c480", "created.background": "#07c4801a", "created.border": "#07c4804d", @@ -1083,35 +1083,35 @@ "conflict": "#7b43f8", "conflict.background": "#7b43f81a", "conflict.border": "#7b43f84d", - "hidden": "#636363", - "hidden.background": "#6363630d", - "hidden.border": "#6363631a", - "ignored": "#737373", - "ignored.background": "#7373730d", - "ignored.border": "#7373731a", + "hidden": "#a3a3a3", + "hidden.background": "#a3a3a30d", + "hidden.border": "#a3a3a31a", + "ignored": "#8a8a8a", + "ignored.background": "#8a8a8a0d", + "ignored.border": "#8a8a8a1a", "renamed": "#08c0ef", "renamed.background": "#08c0ef1a", "renamed.border": "#08c0ef4d", "search.match_background": "#ffca004d", - "tab_bar.background": "#171717", - "tab.active_background": "#171717", - "tab.inactive_background": "#171717", - "toolbar.background": "#171717", - "title_bar.background": "#171717", - "title_bar.inactive_background": "#171717", - "panel.background": "#171717", + "tab_bar.background": "#f7f7f7", + "tab.active_background": "#f7f7f7", + "tab.inactive_background": "#f7f7f7", + "toolbar.background": "#f7f7f7", + "title_bar.background": "#f7f7f7", + "title_bar.inactive_background": "#f7f7f7", + "panel.background": "#f7f7f7", "panel.focused_border": "#009fff", - "status_bar.background": "#171717", - "scrollbar.thumb.background": "#6363634d", - "scrollbar.thumb.hover_background": "#63636380", + "status_bar.background": "#f7f7f7", + "scrollbar.thumb.background": "#a3a3a34d", + "scrollbar.thumb.hover_background": "#a3a3a380", "scrollbar.thumb.border": "transparent", "scrollbar.track.background": "transparent", "scrollbar.track.border": "transparent", - "terminal.background": "#171717", - "terminal.foreground": "#a3a3a3", - "terminal.bright_foreground": "#fafafa", - "terminal.dim_foreground": "#737373", - "terminal.ansi.black": "#171717", + "terminal.background": "#f7f7f7", + "terminal.foreground": "#737373", + "terminal.bright_foreground": "#525252", + "terminal.dim_foreground": "#8a8a8a", + "terminal.ansi.black": "#1d1d1d", "terminal.ansi.red": "#ff2e3f", "terminal.ansi.green": "#0dbe4e", "terminal.ansi.yellow": "#ffca00", @@ -1119,7 +1119,7 @@ "terminal.ansi.magenta": "#e130ac", "terminal.ansi.cyan": "#08c0ef", "terminal.ansi.white": "#bcbcbc", - "terminal.ansi.bright_black": "#171717", + "terminal.ansi.bright_black": "#1d1d1d", "terminal.ansi.bright_red": "#ff2e3f", "terminal.ansi.bright_green": "#86c427", "terminal.ansi.bright_yellow": "#ffca00", @@ -1149,9 +1149,9 @@ "selection": "#9d6afb40" }, { - "cursor": "#5ecc71", - "background": "#5ecc71", - "selection": "#5ecc7140" + "cursor": "#0dbe4e", + "background": "#0dbe4e", + "selection": "#0dbe4e40" }, { "cursor": "#ffca00", @@ -1171,40 +1171,40 @@ ], "syntax": { "comment": { - "color": "#737373" + "color": "#8a8a8a" }, "comment.doc": { - "color": "#737373" + "color": "#8a8a8a" }, "string": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "string.escape": { - "color": "#61d5c0" + "color": "#00cab1" }, "string.regex": { - "color": "#64d1db" + "color": "#00c5d2" }, "string.special": { - "color": "#61d5c0" + "color": "#00cab1" }, "string.special.symbol": { - "color": "#ffd452" + "color": "#ffca00" }, "number": { - "color": "#68cdf2" + "color": "#08c0ef" }, "constant": { - "color": "#ffd452" + "color": "#ffca00" }, "boolean": { - "color": "#68cdf2" + "color": "#08c0ef" }, "keyword": { "color": "#ff678d" }, "keyword.operator": { - "color": "#08c0ef" + "color": "#68cdf2" }, "function": { "color": "#9d6afb" @@ -1231,22 +1231,22 @@ "color": "#d568ea" }, "variable": { - "color": "#ffa359" + "color": "#fe8c2c" }, "variable.builtin": { "color": "#ffab16" }, "variable.member": { - "color": "#ffa359" + "color": "#fe8c2c" }, "variable.parameter": { - "color": "#a3a3a3" + "color": "#737373" }, "variable.special": { "color": "#ffab16" }, "property": { - "color": "#ffa359" + "color": "#fe8c2c" }, "property.css": { "color": "#009fff" @@ -1258,31 +1258,31 @@ "color": "#009fff" }, "value": { - "color": "#68cdf2" + "color": "#08c0ef" }, "constant.css": { - "color": "#ffd452" + "color": "#ffca00" }, "string.plain": { - "color": "#68cdf2" + "color": "#08c0ef" }, "plain_value": { - "color": "#68cdf2" + "color": "#08c0ef" }, "tag.css": { - "color": "#ff855e" + "color": "#ff5d36" }, "tag_name": { - "color": "#ff855e" + "color": "#ff5d36" }, "class": { - "color": "#60d199" + "color": "#07c480" }, "class_name": { - "color": "#60d199" + "color": "#07c480" }, "selector.class": { - "color": "#60d199" + "color": "#07c480" }, "selector.id": { "color": "#9d6afb" @@ -1291,13 +1291,13 @@ "color": "#9d6afb" }, "selector.pseudo": { - "color": "#08c0ef" + "color": "#68cdf2" }, "pseudo_class_selector": { - "color": "#08c0ef" + "color": "#68cdf2" }, "pseudo_element_selector": { - "color": "#08c0ef" + "color": "#68cdf2" }, "keyword.directive": { "color": "#ff678d" @@ -1309,25 +1309,25 @@ "color": "#ff678d" }, "variable.scss": { - "color": "#ffa359" + "color": "#fe8c2c" }, "variable.css": { - "color": "#ffa359" + "color": "#fe8c2c" }, "property.custom": { - "color": "#ffa359" + "color": "#fe8c2c" }, "unit": { - "color": "#68cdf2" + "color": "#08c0ef" }, "number.unit": { - "color": "#68cdf2" + "color": "#08c0ef" }, "color": { - "color": "#ffd452" + "color": "#ffca00" }, "constant.color": { - "color": "#ffd452" + "color": "#ffca00" }, "keyword.important": { "color": "#ff678d" @@ -1345,16 +1345,16 @@ "color": "#d568ea" }, "property.object": { - "color": "#ffa359" + "color": "#fe8c2c" }, "property_identifier": { - "color": "#ffa359" + "color": "#fe8c2c" }, "shorthand_property_identifier": { - "color": "#ffa359" + "color": "#fe8c2c" }, "shorthand_property_identifier_pattern": { - "color": "#ffa359" + "color": "#fe8c2c" }, "method_definition": { "color": "#9d6afb" @@ -1363,40 +1363,40 @@ "color": "#9d6afb" }, "string.template": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "template_string": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "tag.jsx": { - "color": "#ff855e" + "color": "#ff5d36" }, "tag.component": { "color": "#d568ea" }, "punctuation": { - "color": "#636363" + "color": "#737373" }, "punctuation.bracket": { - "color": "#636363" + "color": "#737373" }, "punctuation.delimiter": { - "color": "#636363" + "color": "#737373" }, "punctuation.list_marker": { - "color": "#636363" + "color": "#737373" }, "punctuation.special": { "color": "#ff678d" }, "operator": { - "color": "#08c0ef" + "color": "#68cdf2" }, "tag": { - "color": "#ff855e" + "color": "#ff5d36" }, "attribute": { - "color": "#60d199" + "color": "#07c480" }, "label": { "color": "#ffab16" @@ -1411,20 +1411,20 @@ "color": "#69b1ff" }, "embedded": { - "color": "#fafafa" + "color": "#525252" }, "preproc": { "color": "#ff678d" }, "text.literal": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "markup.heading": { - "color": "#ff855e", + "color": "#ff5d36", "font_weight": 700 }, "markup.bold": { - "color": "#ffd452", + "color": "#ffca00", "font_weight": 700 }, "markup.italic": { @@ -1432,7 +1432,7 @@ "font_style": "italic" }, "markup.strikethrough": { - "color": "#737373" + "color": "#8a8a8a" }, "markup.link.url": { "color": "#009fff" @@ -1441,26 +1441,26 @@ "color": "#9d6afb" }, "markup.quote": { - "color": "#737373", + "color": "#8a8a8a", "font_style": "italic" }, "markup.list": { - "color": "#ff855e" + "color": "#ff5d36" }, "markup.list.numbered": { - "color": "#ff855e" + "color": "#ff5d36" }, "markup.list.unnumbered": { - "color": "#ff855e" + "color": "#ff5d36" }, "markup.raw": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "markup.raw.inline": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "markup.raw.block": { - "color": "#5ecc71" + "color": "#0dbe4e" }, "diff.plus": { "color": "#07c480" @@ -1487,493 +1487,2485 @@ "color": "#009fff" }, "title": { - "color": "#ff855e", + "color": "#ff5d36", "font_weight": 700 }, "predictive": { - "color": "#636363", + "color": "#a3a3a3", "font_style": "italic" } } } }, { - "name": "Pierre Dark Soft", - "appearance": "dark", + "name": "Pierre Light Tritanopia", + "appearance": "light", "style": { - "background": "#101010", - "surface.background": "#101010", - "elevated_surface.background": "#1d1d1d", - "drop_target.background": "#69b1ff26", - "editor.background": "#171717", - "editor.foreground": "#d4d4d4", - "editor.gutter.background": "#171717", - "editor.active_line.background": "#1f3e5e8c", - "editor.active_line_number": "#8a8a8a", - "editor.line_number": "#636363", - "editor.highlighted_line.background": "#1f3e5e59", - "editor.indent_guide": "#262626", - "editor.indent_guide_active": "#2c2c2c", - "editor.invisible": "#525252", - "editor.wrap_guide": "#262626", - "editor.active_wrap_guide": "#2c2c2c", - "editor.document_highlight.read_background": "#69b1ff26", - "editor.document_highlight.write_background": "#69b1ff40", - "editor.document_highlight.bracket_background": "#69b1ff33", - "editor.subheader.background": "#101010", - "text": "#d4d4d4", - "text.muted": "#636363", - "text.placeholder": "#525252", - "text.disabled": "#525252", - "text.accent": "#69b1ff", - "border": "#262626", - "border.variant": "#2c2c2c", - "border.focused": "#69b1ff", - "border.selected": "#69b1ff", + "background": "#f5f5f5", + "surface.background": "#f5f5f5", + "elevated_surface.background": "#f7f7f7", + "drop_target.background": "#009fff26", + "editor.background": "#ffffff", + "editor.foreground": "#0a0a0a", + "editor.gutter.background": "#ffffff", + "editor.active_line.background": "#dfebff8c", + "editor.active_line_number": "#525252", + "editor.line_number": "#737373", + "editor.highlighted_line.background": "#dfebff59", + "editor.indent_guide": "#e5e5e5", + "editor.indent_guide_active": "#d4d4d4", + "editor.invisible": "#8a8a8a", + "editor.wrap_guide": "#e5e5e5", + "editor.active_wrap_guide": "#d4d4d4", + "editor.document_highlight.read_background": "#009fff1a", + "editor.document_highlight.write_background": "#009fff2e", + "editor.document_highlight.bracket_background": "#009fff33", + "editor.subheader.background": "#f5f5f5", + "text": "#0a0a0a", + "text.muted": "#737373", + "text.placeholder": "#8a8a8a", + "text.disabled": "#8a8a8a", + "text.accent": "#009fff", + "border": "#d4d4d4", + "border.variant": "#e5e5e5", + "border.focused": "#009fff", + "border.selected": "#009fff", "border.transparent": "transparent", - "border.disabled": "#2c2c2c", - "element.background": "#262626", - "element.hover": "#1f3e5e80", - "element.active": "#1f3e5eb3", - "element.selected": "#1f3e5e99", - "element.disabled": "#26262680", + "border.disabled": "#d4d4d4", + "element.background": "#ededed", + "element.hover": "#dfebff80", + "element.active": "#dfebffb3", + "element.selected": "#dfebffcc", + "element.disabled": "#ededed80", "ghost_element.background": "transparent", - "ghost_element.hover": "#1f3e5e59", - "ghost_element.active": "#1f3e5e8c", - "ghost_element.selected": "#1f3e5e80", + "ghost_element.hover": "#dfebff59", + "ghost_element.active": "#dfebff8c", + "ghost_element.selected": "#dfebffa6", "ghost_element.disabled": "transparent", - "icon": "#8a8a8a", - "icon.muted": "#636363", - "icon.disabled": "#525252", - "icon.placeholder": "#525252", - "icon.accent": "#69b1ff", - "link_text.hover": "#69b1ff", - "error": "#ff6762", - "error.background": "#ff67621a", - "error.border": "#ff67624d", - "warning": "#69b1ff", - "warning.background": "#69b1ff1a", - "warning.border": "#69b1ff4d", - "success": "#60d199", - "success.background": "#60d1991a", - "success.border": "#60d1994d", - "info": "#68cdf2", - "info.background": "#68cdf21a", - "info.border": "#68cdf24d", - "hint": "#636363", - "hint.background": "#6363631a", - "hint.border": "#63636333", - "predictive": "#525252", - "predictive.background": "#5252521a", - "predictive.border": "#52525233", - "unreachable": "#525252", - "unreachable.background": "#5252520d", - "unreachable.border": "#5252521a", - "created": "#60d199", - "created.background": "#60d1991a", - "created.border": "#60d1994d", - "modified": "#69b1ff", - "modified.background": "#69b1ff1a", - "modified.border": "#69b1ff4d", - "deleted": "#ff6762", - "deleted.background": "#ff67621a", - "deleted.border": "#ff67624d", - "conflict": "#9d6afb", - "conflict.background": "#9d6afb1a", - "conflict.border": "#9d6afb4d", - "hidden": "#525252", - "hidden.background": "#5252520d", - "hidden.border": "#5252521a", - "ignored": "#636363", - "ignored.background": "#6363630d", - "ignored.border": "#6363631a", - "renamed": "#68cdf2", - "renamed.background": "#68cdf21a", - "renamed.border": "#68cdf24d", - "search.match_background": "#ffd4524d", - "tab_bar.background": "#101010", - "tab.active_background": "#101010", - "tab.inactive_background": "#101010", - "toolbar.background": "#101010", - "title_bar.background": "#101010", - "title_bar.inactive_background": "#101010", - "panel.background": "#101010", - "panel.focused_border": "#69b1ff", - "status_bar.background": "#101010", - "scrollbar.thumb.background": "#5252524d", - "scrollbar.thumb.hover_background": "#52525280", + "icon": "#525252", + "icon.muted": "#737373", + "icon.disabled": "#8a8a8a", + "icon.placeholder": "#8a8a8a", + "icon.accent": "#009fff", + "link_text.hover": "#009fff", + "error": "#d5512f", + "error.background": "#d5512f1a", + "error.border": "#d5512f4d", + "warning": "#009fff", + "warning.background": "#009fff1a", + "warning.border": "#009fff4d", + "success": "#1e858e", + "success.background": "#1e858e1a", + "success.border": "#1e858e4d", + "info": "#1a85d4", + "info.background": "#1a85d41a", + "info.border": "#1a85d44d", + "hint": "#737373", + "hint.background": "#7373731a", + "hint.border": "#73737333", + "predictive": "#8a8a8a", + "predictive.background": "#8a8a8a1a", + "predictive.border": "#8a8a8a33", + "unreachable": "#8a8a8a", + "unreachable.background": "#8a8a8a0d", + "unreachable.border": "#8a8a8a1a", + "created": "#1e858e", + "created.background": "#1e858e1a", + "created.border": "#1e858e4d", + "modified": "#009fff", + "modified.background": "#009fff1a", + "modified.border": "#009fff4d", + "deleted": "#d5512f", + "deleted.background": "#d5512f1a", + "deleted.border": "#d5512f4d", + "conflict": "#992a75", + "conflict.background": "#992a751a", + "conflict.border": "#992a754d", + "hidden": "#8a8a8a", + "hidden.background": "#8a8a8a0d", + "hidden.border": "#8a8a8a1a", + "ignored": "#737373", + "ignored.background": "#7373730d", + "ignored.border": "#7373731a", + "renamed": "#1a85d4", + "renamed.background": "#1a85d41a", + "renamed.border": "#1a85d44d", + "search.match_background": "#ffab164d", + "tab_bar.background": "#f5f5f5", + "tab.active_background": "#f5f5f5", + "tab.inactive_background": "#f5f5f5", + "toolbar.background": "#f5f5f5", + "title_bar.background": "#f5f5f5", + "title_bar.inactive_background": "#f5f5f5", + "panel.background": "#f5f5f5", + "panel.focused_border": "#009fff", + "status_bar.background": "#f5f5f5", + "scrollbar.thumb.background": "#8a8a8a4d", + "scrollbar.thumb.hover_background": "#8a8a8a80", + "scrollbar.thumb.border": "transparent", + "scrollbar.track.background": "transparent", + "scrollbar.track.border": "transparent", + "terminal.background": "#f5f5f5", + "terminal.foreground": "#525252", + "terminal.bright_foreground": "#0a0a0a", + "terminal.dim_foreground": "#737373", + "terminal.ansi.black": "#1d1d1d", + "terminal.ansi.red": "#d5512f", + "terminal.ansi.green": "#1e858e", + "terminal.ansi.yellow": "#d5901c", + "terminal.ansi.blue": "#1a85d4", + "terminal.ansi.magenta": "#992a75", + "terminal.ansi.cyan": "#17a5af", + "terminal.ansi.white": "#bcbcbc", + "terminal.ansi.bright_black": "#1d1d1d", + "terminal.ansi.bright_red": "#ff5d36", + "terminal.ansi.bright_green": "#17a5af", + "terminal.ansi.bright_yellow": "#ffab16", + "terminal.ansi.bright_blue": "#009fff", + "terminal.ansi.bright_magenta": "#bd2e90", + "terminal.ansi.bright_cyan": "#00c5d2", + "terminal.ansi.bright_white": "#bcbcbc", + "players": [ + { + "cursor": "#009fff", + "background": "#009fff", + "selection": "#009fff40" + }, + { + "cursor": "#1e858e", + "background": "#1e858e", + "selection": "#1e858e40" + }, + { + "cursor": "#a631be", + "background": "#a631be", + "selection": "#a631be40" + }, + { + "cursor": "#216cab", + "background": "#216cab", + "selection": "#216cab40" + }, + { + "cursor": "#1e858e", + "background": "#1e858e", + "selection": "#1e858e40" + }, + { + "cursor": "#ffab16", + "background": "#ffab16", + "selection": "#ffab1640" + }, + { + "cursor": "#bd2e90", + "background": "#bd2e90", + "selection": "#bd2e9040" + }, + { + "cursor": "#1a85d4", + "background": "#1a85d4", + "selection": "#1a85d440" + } + ], + "syntax": { + "comment": { + "color": "#737373" + }, + "comment.doc": { + "color": "#737373" + }, + "string": { + "color": "#1e858e" + }, + "string.escape": { + "color": "#1e858e" + }, + "string.regex": { + "color": "#1e858e" + }, + "string.special": { + "color": "#1e858e" + }, + "string.special.symbol": { + "color": "#ac741d" + }, + "number": { + "color": "#1a85d4" + }, + "constant": { + "color": "#ac741d" + }, + "boolean": { + "color": "#1a85d4" + }, + "keyword": { + "color": "#a631be" + }, + "keyword.operator": { + "color": "#1e858e" + }, + "function": { + "color": "#216cab" + }, + "function.method": { + "color": "#216cab" + }, + "function.builtin": { + "color": "#216cab" + }, + "function.special.definition": { + "color": "#216cab" + }, + "function.call": { + "color": "#216cab" + }, + "type": { + "color": "#bd2e90" + }, + "type.builtin": { + "color": "#bd2e90" + }, + "constructor": { + "color": "#bd2e90" + }, + "variable": { + "color": "#ad4529" + }, + "variable.builtin": { + "color": "#d5512f" + }, + "variable.member": { + "color": "#ad4529" + }, + "variable.parameter": { + "color": "#636363" + }, + "variable.special": { + "color": "#d5512f" + }, + "property": { + "color": "#ad4529" + }, + "property.css": { + "color": "#009fff" + }, + "property.definition": { + "color": "#009fff" + }, + "property_name": { + "color": "#009fff" + }, + "value": { + "color": "#1a85d4" + }, + "constant.css": { + "color": "#ac741d" + }, + "string.plain": { + "color": "#1a85d4" + }, + "plain_value": { + "color": "#1a85d4" + }, + "tag.css": { + "color": "#d5512f" + }, + "tag_name": { + "color": "#d5512f" + }, + "class": { + "color": "#1e858e" + }, + "class_name": { + "color": "#1e858e" + }, + "selector.class": { + "color": "#1e858e" + }, + "selector.id": { + "color": "#216cab" + }, + "id_name": { + "color": "#216cab" + }, + "selector.pseudo": { + "color": "#1e858e" + }, + "pseudo_class_selector": { + "color": "#1e858e" + }, + "pseudo_element_selector": { + "color": "#1e858e" + }, + "keyword.directive": { + "color": "#a631be" + }, + "keyword.control.at-rule": { + "color": "#a631be" + }, + "at_keyword": { + "color": "#a631be" + }, + "variable.scss": { + "color": "#ad4529" + }, + "variable.css": { + "color": "#ad4529" + }, + "property.custom": { + "color": "#ad4529" + }, + "unit": { + "color": "#1a85d4" + }, + "number.unit": { + "color": "#1a85d4" + }, + "color": { + "color": "#ac741d" + }, + "constant.color": { + "color": "#ac741d" + }, + "keyword.important": { + "color": "#a631be" + }, + "variable.language": { + "color": "#d5512f" + }, + "this": { + "color": "#d5512f" + }, + "self": { + "color": "#d5512f" + }, + "type.class": { + "color": "#bd2e90" + }, + "property.object": { + "color": "#ad4529" + }, + "property_identifier": { + "color": "#ad4529" + }, + "shorthand_property_identifier": { + "color": "#ad4529" + }, + "shorthand_property_identifier_pattern": { + "color": "#ad4529" + }, + "method_definition": { + "color": "#216cab" + }, + "function.method.call": { + "color": "#216cab" + }, + "string.template": { + "color": "#1e858e" + }, + "template_string": { + "color": "#1e858e" + }, + "tag.jsx": { + "color": "#d5512f" + }, + "tag.component": { + "color": "#bd2e90" + }, + "punctuation": { + "color": "#636363" + }, + "punctuation.bracket": { + "color": "#636363" + }, + "punctuation.delimiter": { + "color": "#636363" + }, + "punctuation.list_marker": { + "color": "#636363" + }, + "punctuation.special": { + "color": "#a631be" + }, + "operator": { + "color": "#1e858e" + }, + "tag": { + "color": "#d5512f" + }, + "attribute": { + "color": "#1e858e" + }, + "label": { + "color": "#d5512f" + }, + "namespace": { + "color": "#d5512f" + }, + "decorator": { + "color": "#1a85d4" + }, + "attribute.builtin": { + "color": "#1a85d4" + }, + "embedded": { + "color": "#0a0a0a" + }, + "preproc": { + "color": "#a631be" + }, + "text.literal": { + "color": "#1e858e" + }, + "markup.heading": { + "color": "#d5512f", + "font_weight": 700 + }, + "markup.bold": { + "color": "#ac741d", + "font_weight": 700 + }, + "markup.italic": { + "color": "#a631be", + "font_style": "italic" + }, + "markup.strikethrough": { + "color": "#737373" + }, + "markup.link.url": { + "color": "#009fff" + }, + "markup.link.text": { + "color": "#216cab" + }, + "markup.quote": { + "color": "#737373", + "font_style": "italic" + }, + "markup.list": { + "color": "#d5512f" + }, + "markup.list.numbered": { + "color": "#d5512f" + }, + "markup.list.unnumbered": { + "color": "#d5512f" + }, + "markup.raw": { + "color": "#1e858e" + }, + "markup.raw.inline": { + "color": "#1e858e" + }, + "markup.raw.block": { + "color": "#1e858e" + }, + "diff.plus": { + "color": "#1e858e" + }, + "diff.minus": { + "color": "#d5512f" + }, + "diff.delta": { + "color": "#ffab16" + }, + "link_text": { + "color": "#009fff" + }, + "link_uri": { + "color": "#a631be" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "primary": { + "color": "#009fff" + }, + "title": { + "color": "#d5512f", + "font_weight": 700 + }, + "predictive": { + "color": "#8a8a8a", + "font_style": "italic" + } + } + } + }, + { + "name": "Pierre Dark", + "appearance": "dark", + "style": { + "background": "#171717", + "surface.background": "#171717", + "elevated_surface.background": "#101010", + "drop_target.background": "#009fff26", + "editor.background": "#0a0a0a", + "editor.foreground": "#fafafa", + "editor.gutter.background": "#0a0a0a", + "editor.active_line.background": "#19283c8c", + "editor.active_line_number": "#a3a3a3", + "editor.line_number": "#737373", + "editor.highlighted_line.background": "#19283c59", + "editor.indent_guide": "#1d1d1d", + "editor.indent_guide_active": "#262626", + "editor.invisible": "#636363", + "editor.wrap_guide": "#1d1d1d", + "editor.active_wrap_guide": "#262626", + "editor.document_highlight.read_background": "#009fff26", + "editor.document_highlight.write_background": "#009fff40", + "editor.document_highlight.bracket_background": "#009fff33", + "editor.subheader.background": "#171717", + "text": "#fafafa", + "text.muted": "#737373", + "text.placeholder": "#636363", + "text.disabled": "#636363", + "text.accent": "#009fff", + "border": "#1d1d1d", + "border.variant": "#262626", + "border.focused": "#009fff", + "border.selected": "#009fff", + "border.transparent": "transparent", + "border.disabled": "#262626", + "element.background": "#1d1d1d", + "element.hover": "#19283c80", + "element.active": "#19283cb3", + "element.selected": "#19283c99", + "element.disabled": "#1d1d1d80", + "ghost_element.background": "transparent", + "ghost_element.hover": "#19283c59", + "ghost_element.active": "#19283c8c", + "ghost_element.selected": "#19283c80", + "ghost_element.disabled": "transparent", + "icon": "#a3a3a3", + "icon.muted": "#737373", + "icon.disabled": "#636363", + "icon.placeholder": "#636363", + "icon.accent": "#009fff", + "link_text.hover": "#009fff", + "error": "#ff2e3f", + "error.background": "#ff2e3f1a", + "error.border": "#ff2e3f4d", + "warning": "#009fff", + "warning.background": "#009fff1a", + "warning.border": "#009fff4d", + "success": "#07c480", + "success.background": "#07c4801a", + "success.border": "#07c4804d", + "info": "#08c0ef", + "info.background": "#08c0ef1a", + "info.border": "#08c0ef4d", + "hint": "#737373", + "hint.background": "#7373731a", + "hint.border": "#73737333", + "predictive": "#636363", + "predictive.background": "#6363631a", + "predictive.border": "#63636333", + "unreachable": "#636363", + "unreachable.background": "#6363630d", + "unreachable.border": "#6363631a", + "created": "#07c480", + "created.background": "#07c4801a", + "created.border": "#07c4804d", + "modified": "#009fff", + "modified.background": "#009fff1a", + "modified.border": "#009fff4d", + "deleted": "#ff2e3f", + "deleted.background": "#ff2e3f1a", + "deleted.border": "#ff2e3f4d", + "conflict": "#7b43f8", + "conflict.background": "#7b43f81a", + "conflict.border": "#7b43f84d", + "hidden": "#636363", + "hidden.background": "#6363630d", + "hidden.border": "#6363631a", + "ignored": "#737373", + "ignored.background": "#7373730d", + "ignored.border": "#7373731a", + "renamed": "#08c0ef", + "renamed.background": "#08c0ef1a", + "renamed.border": "#08c0ef4d", + "search.match_background": "#ffca004d", + "tab_bar.background": "#171717", + "tab.active_background": "#171717", + "tab.inactive_background": "#171717", + "toolbar.background": "#171717", + "title_bar.background": "#171717", + "title_bar.inactive_background": "#171717", + "panel.background": "#171717", + "panel.focused_border": "#009fff", + "status_bar.background": "#171717", + "scrollbar.thumb.background": "#6363634d", + "scrollbar.thumb.hover_background": "#63636380", + "scrollbar.thumb.border": "transparent", + "scrollbar.track.background": "transparent", + "scrollbar.track.border": "transparent", + "terminal.background": "#171717", + "terminal.foreground": "#a3a3a3", + "terminal.bright_foreground": "#fafafa", + "terminal.dim_foreground": "#737373", + "terminal.ansi.black": "#171717", + "terminal.ansi.red": "#ff2e3f", + "terminal.ansi.green": "#0dbe4e", + "terminal.ansi.yellow": "#ffca00", + "terminal.ansi.blue": "#009fff", + "terminal.ansi.magenta": "#e130ac", + "terminal.ansi.cyan": "#08c0ef", + "terminal.ansi.white": "#bcbcbc", + "terminal.ansi.bright_black": "#171717", + "terminal.ansi.bright_red": "#ff2e3f", + "terminal.ansi.bright_green": "#86c427", + "terminal.ansi.bright_yellow": "#ffca00", + "terminal.ansi.bright_blue": "#009fff", + "terminal.ansi.bright_magenta": "#e130ac", + "terminal.ansi.bright_cyan": "#08c0ef", + "terminal.ansi.bright_white": "#bcbcbc", + "players": [ + { + "cursor": "#009fff", + "background": "#009fff", + "selection": "#009fff40" + }, + { + "cursor": "#07c480", + "background": "#07c480", + "selection": "#07c48040" + }, + { + "cursor": "#ff678d", + "background": "#ff678d", + "selection": "#ff678d40" + }, + { + "cursor": "#9d6afb", + "background": "#9d6afb", + "selection": "#9d6afb40" + }, + { + "cursor": "#5ecc71", + "background": "#5ecc71", + "selection": "#5ecc7140" + }, + { + "cursor": "#ffca00", + "background": "#ffca00", + "selection": "#ffca0040" + }, + { + "cursor": "#d568ea", + "background": "#d568ea", + "selection": "#d568ea40" + }, + { + "cursor": "#08c0ef", + "background": "#08c0ef", + "selection": "#08c0ef40" + } + ], + "syntax": { + "comment": { + "color": "#737373" + }, + "comment.doc": { + "color": "#737373" + }, + "string": { + "color": "#5ecc71" + }, + "string.escape": { + "color": "#61d5c0" + }, + "string.regex": { + "color": "#64d1db" + }, + "string.special": { + "color": "#61d5c0" + }, + "string.special.symbol": { + "color": "#ffd452" + }, + "number": { + "color": "#68cdf2" + }, + "constant": { + "color": "#ffd452" + }, + "boolean": { + "color": "#68cdf2" + }, + "keyword": { + "color": "#ff678d" + }, + "keyword.operator": { + "color": "#08c0ef" + }, + "function": { + "color": "#9d6afb" + }, + "function.method": { + "color": "#9d6afb" + }, + "function.builtin": { + "color": "#9d6afb" + }, + "function.special.definition": { + "color": "#9d6afb" + }, + "function.call": { + "color": "#9d6afb" + }, + "type": { + "color": "#d568ea" + }, + "type.builtin": { + "color": "#d568ea" + }, + "constructor": { + "color": "#d568ea" + }, + "variable": { + "color": "#ffa359" + }, + "variable.builtin": { + "color": "#ffab16" + }, + "variable.member": { + "color": "#ffa359" + }, + "variable.parameter": { + "color": "#a3a3a3" + }, + "variable.special": { + "color": "#ffab16" + }, + "property": { + "color": "#ffa359" + }, + "property.css": { + "color": "#009fff" + }, + "property.definition": { + "color": "#009fff" + }, + "property_name": { + "color": "#009fff" + }, + "value": { + "color": "#68cdf2" + }, + "constant.css": { + "color": "#ffd452" + }, + "string.plain": { + "color": "#68cdf2" + }, + "plain_value": { + "color": "#68cdf2" + }, + "tag.css": { + "color": "#ff855e" + }, + "tag_name": { + "color": "#ff855e" + }, + "class": { + "color": "#60d199" + }, + "class_name": { + "color": "#60d199" + }, + "selector.class": { + "color": "#60d199" + }, + "selector.id": { + "color": "#9d6afb" + }, + "id_name": { + "color": "#9d6afb" + }, + "selector.pseudo": { + "color": "#08c0ef" + }, + "pseudo_class_selector": { + "color": "#08c0ef" + }, + "pseudo_element_selector": { + "color": "#08c0ef" + }, + "keyword.directive": { + "color": "#ff678d" + }, + "keyword.control.at-rule": { + "color": "#ff678d" + }, + "at_keyword": { + "color": "#ff678d" + }, + "variable.scss": { + "color": "#ffa359" + }, + "variable.css": { + "color": "#ffa359" + }, + "property.custom": { + "color": "#ffa359" + }, + "unit": { + "color": "#68cdf2" + }, + "number.unit": { + "color": "#68cdf2" + }, + "color": { + "color": "#ffd452" + }, + "constant.color": { + "color": "#ffd452" + }, + "keyword.important": { + "color": "#ff678d" + }, + "variable.language": { + "color": "#ffab16" + }, + "this": { + "color": "#ffab16" + }, + "self": { + "color": "#ffab16" + }, + "type.class": { + "color": "#d568ea" + }, + "property.object": { + "color": "#ffa359" + }, + "property_identifier": { + "color": "#ffa359" + }, + "shorthand_property_identifier": { + "color": "#ffa359" + }, + "shorthand_property_identifier_pattern": { + "color": "#ffa359" + }, + "method_definition": { + "color": "#9d6afb" + }, + "function.method.call": { + "color": "#9d6afb" + }, + "string.template": { + "color": "#5ecc71" + }, + "template_string": { + "color": "#5ecc71" + }, + "tag.jsx": { + "color": "#ff855e" + }, + "tag.component": { + "color": "#d568ea" + }, + "punctuation": { + "color": "#636363" + }, + "punctuation.bracket": { + "color": "#636363" + }, + "punctuation.delimiter": { + "color": "#636363" + }, + "punctuation.list_marker": { + "color": "#636363" + }, + "punctuation.special": { + "color": "#ff678d" + }, + "operator": { + "color": "#08c0ef" + }, + "tag": { + "color": "#ff855e" + }, + "attribute": { + "color": "#60d199" + }, + "label": { + "color": "#ffab16" + }, + "namespace": { + "color": "#ffab16" + }, + "decorator": { + "color": "#69b1ff" + }, + "attribute.builtin": { + "color": "#69b1ff" + }, + "embedded": { + "color": "#fafafa" + }, + "preproc": { + "color": "#ff678d" + }, + "text.literal": { + "color": "#5ecc71" + }, + "markup.heading": { + "color": "#ff855e", + "font_weight": 700 + }, + "markup.bold": { + "color": "#ffd452", + "font_weight": 700 + }, + "markup.italic": { + "color": "#ff678d", + "font_style": "italic" + }, + "markup.strikethrough": { + "color": "#737373" + }, + "markup.link.url": { + "color": "#009fff" + }, + "markup.link.text": { + "color": "#9d6afb" + }, + "markup.quote": { + "color": "#737373", + "font_style": "italic" + }, + "markup.list": { + "color": "#ff855e" + }, + "markup.list.numbered": { + "color": "#ff855e" + }, + "markup.list.unnumbered": { + "color": "#ff855e" + }, + "markup.raw": { + "color": "#5ecc71" + }, + "markup.raw.inline": { + "color": "#5ecc71" + }, + "markup.raw.block": { + "color": "#5ecc71" + }, + "diff.plus": { + "color": "#07c480" + }, + "diff.minus": { + "color": "#ff2e3f" + }, + "diff.delta": { + "color": "#ffca00" + }, + "link_text": { + "color": "#009fff" + }, + "link_uri": { + "color": "#ff678d" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "primary": { + "color": "#009fff" + }, + "title": { + "color": "#ff855e", + "font_weight": 700 + }, + "predictive": { + "color": "#636363", + "font_style": "italic" + } + } + } + }, + { + "name": "Pierre Dark Protanopia & Deuteranopia", + "appearance": "dark", + "style": { + "background": "#171717", + "surface.background": "#171717", + "elevated_surface.background": "#101010", + "drop_target.background": "#009fff26", + "editor.background": "#0a0a0a", + "editor.foreground": "#fafafa", + "editor.gutter.background": "#0a0a0a", + "editor.active_line.background": "#19283c8c", + "editor.active_line_number": "#a3a3a3", + "editor.line_number": "#737373", + "editor.highlighted_line.background": "#19283c59", + "editor.indent_guide": "#1d1d1d", + "editor.indent_guide_active": "#262626", + "editor.invisible": "#636363", + "editor.wrap_guide": "#1d1d1d", + "editor.active_wrap_guide": "#262626", + "editor.document_highlight.read_background": "#009fff26", + "editor.document_highlight.write_background": "#009fff40", + "editor.document_highlight.bracket_background": "#009fff33", + "editor.subheader.background": "#171717", + "text": "#fafafa", + "text.muted": "#737373", + "text.placeholder": "#636363", + "text.disabled": "#636363", + "text.accent": "#009fff", + "border": "#1d1d1d", + "border.variant": "#262626", + "border.focused": "#009fff", + "border.selected": "#009fff", + "border.transparent": "transparent", + "border.disabled": "#262626", + "element.background": "#1d1d1d", + "element.hover": "#19283c80", + "element.active": "#19283cb3", + "element.selected": "#19283c99", + "element.disabled": "#1d1d1d80", + "ghost_element.background": "transparent", + "ghost_element.hover": "#19283c59", + "ghost_element.active": "#19283c8c", + "ghost_element.selected": "#19283c80", + "ghost_element.disabled": "transparent", + "icon": "#a3a3a3", + "icon.muted": "#737373", + "icon.disabled": "#636363", + "icon.placeholder": "#636363", + "icon.accent": "#009fff", + "link_text.hover": "#009fff", + "error": "#fe8c2c", + "error.background": "#fe8c2c1a", + "error.border": "#fe8c2c4d", + "warning": "#009fff", + "warning.background": "#009fff1a", + "warning.border": "#009fff4d", + "success": "#97c4ff", + "success.background": "#97c4ff1a", + "success.border": "#97c4ff4d", + "info": "#68cdf2", + "info.background": "#68cdf21a", + "info.border": "#68cdf24d", + "hint": "#737373", + "hint.background": "#7373731a", + "hint.border": "#73737333", + "predictive": "#636363", + "predictive.background": "#6363631a", + "predictive.border": "#63636333", + "unreachable": "#636363", + "unreachable.background": "#6363630d", + "unreachable.border": "#6363631a", + "created": "#97c4ff", + "created.background": "#97c4ff1a", + "created.border": "#97c4ff4d", + "modified": "#009fff", + "modified.background": "#009fff1a", + "modified.border": "#009fff4d", + "deleted": "#fe8c2c", + "deleted.background": "#fe8c2c1a", + "deleted.border": "#fe8c2c4d", + "conflict": "#b969f3", + "conflict.background": "#b969f31a", + "conflict.border": "#b969f34d", + "hidden": "#636363", + "hidden.background": "#6363630d", + "hidden.border": "#6363631a", + "ignored": "#737373", + "ignored.background": "#7373730d", + "ignored.border": "#7373731a", + "renamed": "#68cdf2", + "renamed.background": "#68cdf21a", + "renamed.border": "#68cdf24d", + "search.match_background": "#ffde804d", + "tab_bar.background": "#171717", + "tab.active_background": "#171717", + "tab.inactive_background": "#171717", + "toolbar.background": "#171717", + "title_bar.background": "#171717", + "title_bar.inactive_background": "#171717", + "panel.background": "#171717", + "panel.focused_border": "#009fff", + "status_bar.background": "#171717", + "scrollbar.thumb.background": "#6363634d", + "scrollbar.thumb.hover_background": "#63636380", + "scrollbar.thumb.border": "transparent", + "scrollbar.track.background": "transparent", + "scrollbar.track.border": "transparent", + "terminal.background": "#171717", + "terminal.foreground": "#a3a3a3", + "terminal.bright_foreground": "#fafafa", + "terminal.dim_foreground": "#737373", + "terminal.ansi.black": "#171717", + "terminal.ansi.red": "#ffa359", + "terminal.ansi.green": "#69b1ff", + "terminal.ansi.yellow": "#ffd452", + "terminal.ansi.blue": "#009fff", + "terminal.ansi.magenta": "#b969f3", + "terminal.ansi.cyan": "#68cdf2", + "terminal.ansi.white": "#bcbcbc", + "terminal.ansi.bright_black": "#171717", + "terminal.ansi.bright_red": "#ffba82", + "terminal.ansi.bright_green": "#97c4ff", + "terminal.ansi.bright_yellow": "#ffde80", + "terminal.ansi.bright_blue": "#69b1ff", + "terminal.ansi.bright_magenta": "#ce90f7", + "terminal.ansi.bright_cyan": "#96d9f6", + "terminal.ansi.bright_white": "#bcbcbc", + "players": [ + { + "cursor": "#009fff", + "background": "#009fff", + "selection": "#009fff40" + }, + { + "cursor": "#97c4ff", + "background": "#97c4ff", + "selection": "#97c4ff40" + }, + { + "cursor": "#b969f3", + "background": "#b969f3", + "selection": "#b969f340" + }, + { + "cursor": "#ba8ffd", + "background": "#ba8ffd", + "selection": "#ba8ffd40" + }, + { + "cursor": "#97c4ff", + "background": "#97c4ff", + "selection": "#97c4ff40" + }, + { + "cursor": "#ffde80", + "background": "#ffde80", + "selection": "#ffde8040" + }, + { + "cursor": "#e290f0", + "background": "#e290f0", + "selection": "#e290f040" + }, + { + "cursor": "#68cdf2", + "background": "#68cdf2", + "selection": "#68cdf240" + } + ], + "syntax": { + "comment": { + "color": "#737373" + }, + "comment.doc": { + "color": "#737373" + }, + "string": { + "color": "#97c4ff" + }, + "string.escape": { + "color": "#64d1db" + }, + "string.regex": { + "color": "#68cdf2" + }, + "string.special": { + "color": "#64d1db" + }, + "string.special.symbol": { + "color": "#ffcc81" + }, + "number": { + "color": "#96d9f6" + }, + "constant": { + "color": "#ffcc81" + }, + "boolean": { + "color": "#96d9f6" + }, + "keyword": { + "color": "#b969f3" + }, + "keyword.operator": { + "color": "#08c0ef" + }, + "function": { + "color": "#ba8ffd" + }, + "function.method": { + "color": "#ba8ffd" + }, + "function.builtin": { + "color": "#ba8ffd" + }, + "function.special.definition": { + "color": "#ba8ffd" + }, + "function.call": { + "color": "#ba8ffd" + }, + "type": { + "color": "#e290f0" + }, + "type.builtin": { + "color": "#e290f0" + }, + "constructor": { + "color": "#e290f0" + }, + "variable": { + "color": "#ffa359" + }, + "variable.builtin": { + "color": "#ffbc56" + }, + "variable.member": { + "color": "#ffa359" + }, + "variable.parameter": { + "color": "#a3a3a3" + }, + "variable.special": { + "color": "#ffbc56" + }, + "property": { + "color": "#ffa359" + }, + "property.css": { + "color": "#009fff" + }, + "property.definition": { + "color": "#009fff" + }, + "property_name": { + "color": "#009fff" + }, + "value": { + "color": "#96d9f6" + }, + "constant.css": { + "color": "#ffcc81" + }, + "string.plain": { + "color": "#96d9f6" + }, + "plain_value": { + "color": "#96d9f6" + }, + "tag.css": { + "color": "#ffa359" + }, + "tag_name": { + "color": "#ffa359" + }, + "class": { + "color": "#ffbc56" + }, + "class_name": { + "color": "#ffbc56" + }, + "selector.class": { + "color": "#ffbc56" + }, + "selector.id": { + "color": "#ba8ffd" + }, + "id_name": { + "color": "#ba8ffd" + }, + "selector.pseudo": { + "color": "#08c0ef" + }, + "pseudo_class_selector": { + "color": "#08c0ef" + }, + "pseudo_element_selector": { + "color": "#08c0ef" + }, + "keyword.directive": { + "color": "#b969f3" + }, + "keyword.control.at-rule": { + "color": "#b969f3" + }, + "at_keyword": { + "color": "#b969f3" + }, + "variable.scss": { + "color": "#ffa359" + }, + "variable.css": { + "color": "#ffa359" + }, + "property.custom": { + "color": "#ffa359" + }, + "unit": { + "color": "#96d9f6" + }, + "number.unit": { + "color": "#96d9f6" + }, + "color": { + "color": "#ffcc81" + }, + "constant.color": { + "color": "#ffcc81" + }, + "keyword.important": { + "color": "#b969f3" + }, + "variable.language": { + "color": "#ffbc56" + }, + "this": { + "color": "#ffbc56" + }, + "self": { + "color": "#ffbc56" + }, + "type.class": { + "color": "#e290f0" + }, + "property.object": { + "color": "#ffa359" + }, + "property_identifier": { + "color": "#ffa359" + }, + "shorthand_property_identifier": { + "color": "#ffa359" + }, + "shorthand_property_identifier_pattern": { + "color": "#ffa359" + }, + "method_definition": { + "color": "#ba8ffd" + }, + "function.method.call": { + "color": "#ba8ffd" + }, + "string.template": { + "color": "#97c4ff" + }, + "template_string": { + "color": "#97c4ff" + }, + "tag.jsx": { + "color": "#ffa359" + }, + "tag.component": { + "color": "#e290f0" + }, + "punctuation": { + "color": "#636363" + }, + "punctuation.bracket": { + "color": "#636363" + }, + "punctuation.delimiter": { + "color": "#636363" + }, + "punctuation.list_marker": { + "color": "#636363" + }, + "punctuation.special": { + "color": "#b969f3" + }, + "operator": { + "color": "#08c0ef" + }, + "tag": { + "color": "#ffa359" + }, + "attribute": { + "color": "#ffbc56" + }, + "label": { + "color": "#ffbc56" + }, + "namespace": { + "color": "#ffbc56" + }, + "decorator": { + "color": "#69b1ff" + }, + "attribute.builtin": { + "color": "#69b1ff" + }, + "embedded": { + "color": "#fafafa" + }, + "preproc": { + "color": "#b969f3" + }, + "text.literal": { + "color": "#97c4ff" + }, + "markup.heading": { + "color": "#ffa359", + "font_weight": 700 + }, + "markup.bold": { + "color": "#ffcc81", + "font_weight": 700 + }, + "markup.italic": { + "color": "#b969f3", + "font_style": "italic" + }, + "markup.strikethrough": { + "color": "#737373" + }, + "markup.link.url": { + "color": "#009fff" + }, + "markup.link.text": { + "color": "#ba8ffd" + }, + "markup.quote": { + "color": "#737373", + "font_style": "italic" + }, + "markup.list": { + "color": "#ffa359" + }, + "markup.list.numbered": { + "color": "#ffa359" + }, + "markup.list.unnumbered": { + "color": "#ffa359" + }, + "markup.raw": { + "color": "#97c4ff" + }, + "markup.raw.inline": { + "color": "#97c4ff" + }, + "markup.raw.block": { + "color": "#97c4ff" + }, + "diff.plus": { + "color": "#97c4ff" + }, + "diff.minus": { + "color": "#fe8c2c" + }, + "diff.delta": { + "color": "#ffde80" + }, + "link_text": { + "color": "#009fff" + }, + "link_uri": { + "color": "#b969f3" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "primary": { + "color": "#009fff" + }, + "title": { + "color": "#ffa359", + "font_weight": 700 + }, + "predictive": { + "color": "#636363", + "font_style": "italic" + } + } + } + }, + { + "name": "Pierre Dark Soft", + "appearance": "dark", + "style": { + "background": "#101010", + "surface.background": "#101010", + "elevated_surface.background": "#1d1d1d", + "drop_target.background": "#69b1ff26", + "editor.background": "#171717", + "editor.foreground": "#d4d4d4", + "editor.gutter.background": "#171717", + "editor.active_line.background": "#1f3e5e8c", + "editor.active_line_number": "#8a8a8a", + "editor.line_number": "#636363", + "editor.highlighted_line.background": "#1f3e5e59", + "editor.indent_guide": "#262626", + "editor.indent_guide_active": "#2c2c2c", + "editor.invisible": "#525252", + "editor.wrap_guide": "#262626", + "editor.active_wrap_guide": "#2c2c2c", + "editor.document_highlight.read_background": "#69b1ff26", + "editor.document_highlight.write_background": "#69b1ff40", + "editor.document_highlight.bracket_background": "#69b1ff33", + "editor.subheader.background": "#101010", + "text": "#d4d4d4", + "text.muted": "#636363", + "text.placeholder": "#525252", + "text.disabled": "#525252", + "text.accent": "#69b1ff", + "border": "#262626", + "border.variant": "#2c2c2c", + "border.focused": "#69b1ff", + "border.selected": "#69b1ff", + "border.transparent": "transparent", + "border.disabled": "#2c2c2c", + "element.background": "#262626", + "element.hover": "#1f3e5e80", + "element.active": "#1f3e5eb3", + "element.selected": "#1f3e5e99", + "element.disabled": "#26262680", + "ghost_element.background": "transparent", + "ghost_element.hover": "#1f3e5e59", + "ghost_element.active": "#1f3e5e8c", + "ghost_element.selected": "#1f3e5e80", + "ghost_element.disabled": "transparent", + "icon": "#8a8a8a", + "icon.muted": "#636363", + "icon.disabled": "#525252", + "icon.placeholder": "#525252", + "icon.accent": "#69b1ff", + "link_text.hover": "#69b1ff", + "error": "#ff6762", + "error.background": "#ff67621a", + "error.border": "#ff67624d", + "warning": "#69b1ff", + "warning.background": "#69b1ff1a", + "warning.border": "#69b1ff4d", + "success": "#60d199", + "success.background": "#60d1991a", + "success.border": "#60d1994d", + "info": "#68cdf2", + "info.background": "#68cdf21a", + "info.border": "#68cdf24d", + "hint": "#636363", + "hint.background": "#6363631a", + "hint.border": "#63636333", + "predictive": "#525252", + "predictive.background": "#5252521a", + "predictive.border": "#52525233", + "unreachable": "#525252", + "unreachable.background": "#5252520d", + "unreachable.border": "#5252521a", + "created": "#60d199", + "created.background": "#60d1991a", + "created.border": "#60d1994d", + "modified": "#69b1ff", + "modified.background": "#69b1ff1a", + "modified.border": "#69b1ff4d", + "deleted": "#ff6762", + "deleted.background": "#ff67621a", + "deleted.border": "#ff67624d", + "conflict": "#9d6afb", + "conflict.background": "#9d6afb1a", + "conflict.border": "#9d6afb4d", + "hidden": "#525252", + "hidden.background": "#5252520d", + "hidden.border": "#5252521a", + "ignored": "#636363", + "ignored.background": "#6363630d", + "ignored.border": "#6363631a", + "renamed": "#68cdf2", + "renamed.background": "#68cdf21a", + "renamed.border": "#68cdf24d", + "search.match_background": "#ffd4524d", + "tab_bar.background": "#101010", + "tab.active_background": "#101010", + "tab.inactive_background": "#101010", + "toolbar.background": "#101010", + "title_bar.background": "#101010", + "title_bar.inactive_background": "#101010", + "panel.background": "#101010", + "panel.focused_border": "#69b1ff", + "status_bar.background": "#101010", + "scrollbar.thumb.background": "#5252524d", + "scrollbar.thumb.hover_background": "#52525280", + "scrollbar.thumb.border": "transparent", + "scrollbar.track.background": "transparent", + "scrollbar.track.border": "transparent", + "terminal.background": "#101010", + "terminal.foreground": "#8a8a8a", + "terminal.bright_foreground": "#d4d4d4", + "terminal.dim_foreground": "#636363", + "terminal.ansi.black": "#171717", + "terminal.ansi.red": "#ff2e3f", + "terminal.ansi.green": "#0dbe4e", + "terminal.ansi.yellow": "#ffca00", + "terminal.ansi.blue": "#009fff", + "terminal.ansi.magenta": "#e130ac", + "terminal.ansi.cyan": "#08c0ef", + "terminal.ansi.white": "#bcbcbc", + "terminal.ansi.bright_black": "#171717", + "terminal.ansi.bright_red": "#ff2e3f", + "terminal.ansi.bright_green": "#86c427", + "terminal.ansi.bright_yellow": "#ffca00", + "terminal.ansi.bright_blue": "#009fff", + "terminal.ansi.bright_magenta": "#e130ac", + "terminal.ansi.bright_cyan": "#08c0ef", + "terminal.ansi.bright_white": "#bcbcbc", + "players": [ + { + "cursor": "#69b1ff", + "background": "#69b1ff", + "selection": "#69b1ff40" + }, + { + "cursor": "#60d199", + "background": "#60d199", + "selection": "#60d19940" + }, + { + "cursor": "#ff91a8", + "background": "#ff91a8", + "selection": "#ff91a840" + }, + { + "cursor": "#ba8ffd", + "background": "#ba8ffd", + "selection": "#ba8ffd40" + }, + { + "cursor": "#8cda94", + "background": "#8cda94", + "selection": "#8cda9440" + }, + { + "cursor": "#ffd452", + "background": "#ffd452", + "selection": "#ffd45240" + }, + { + "cursor": "#e290f0", + "background": "#e290f0", + "selection": "#e290f040" + }, + { + "cursor": "#68cdf2", + "background": "#68cdf2", + "selection": "#68cdf240" + } + ], + "syntax": { + "comment": { + "color": "#636363" + }, + "comment.doc": { + "color": "#636363" + }, + "string": { + "color": "#8cda94" + }, + "string.escape": { + "color": "#8fe0d0" + }, + "string.regex": { + "color": "#92dde4" + }, + "string.special": { + "color": "#8fe0d0" + }, + "string.special.symbol": { + "color": "#ffde80" + }, + "number": { + "color": "#96d9f6" + }, + "constant": { + "color": "#ffde80" + }, + "boolean": { + "color": "#96d9f6" + }, + "keyword": { + "color": "#ff91a8" + }, + "keyword.operator": { + "color": "#68cdf2" + }, + "function": { + "color": "#ba8ffd" + }, + "function.method": { + "color": "#ba8ffd" + }, + "function.builtin": { + "color": "#ba8ffd" + }, + "function.special.definition": { + "color": "#ba8ffd" + }, + "function.call": { + "color": "#ba8ffd" + }, + "type": { + "color": "#e290f0" + }, + "type.builtin": { + "color": "#e290f0" + }, + "constructor": { + "color": "#e290f0" + }, + "variable": { + "color": "#ffba82" + }, + "variable.builtin": { + "color": "#ffbc56" + }, + "variable.member": { + "color": "#ffba82" + }, + "variable.parameter": { + "color": "#8a8a8a" + }, + "variable.special": { + "color": "#ffbc56" + }, + "property": { + "color": "#ffba82" + }, + "property.css": { + "color": "#69b1ff" + }, + "property.definition": { + "color": "#69b1ff" + }, + "property_name": { + "color": "#69b1ff" + }, + "value": { + "color": "#96d9f6" + }, + "constant.css": { + "color": "#ffde80" + }, + "string.plain": { + "color": "#96d9f6" + }, + "plain_value": { + "color": "#96d9f6" + }, + "tag.css": { + "color": "#ffa685" + }, + "tag_name": { + "color": "#ffa685" + }, + "class": { + "color": "#8eddb2" + }, + "class_name": { + "color": "#8eddb2" + }, + "selector.class": { + "color": "#8eddb2" + }, + "selector.id": { + "color": "#ba8ffd" + }, + "id_name": { + "color": "#ba8ffd" + }, + "selector.pseudo": { + "color": "#68cdf2" + }, + "pseudo_class_selector": { + "color": "#68cdf2" + }, + "pseudo_element_selector": { + "color": "#68cdf2" + }, + "keyword.directive": { + "color": "#ff91a8" + }, + "keyword.control.at-rule": { + "color": "#ff91a8" + }, + "at_keyword": { + "color": "#ff91a8" + }, + "variable.scss": { + "color": "#ffba82" + }, + "variable.css": { + "color": "#ffba82" + }, + "property.custom": { + "color": "#ffba82" + }, + "unit": { + "color": "#96d9f6" + }, + "number.unit": { + "color": "#96d9f6" + }, + "color": { + "color": "#ffde80" + }, + "constant.color": { + "color": "#ffde80" + }, + "keyword.important": { + "color": "#ff91a8" + }, + "variable.language": { + "color": "#ffbc56" + }, + "this": { + "color": "#ffbc56" + }, + "self": { + "color": "#ffbc56" + }, + "type.class": { + "color": "#e290f0" + }, + "property.object": { + "color": "#ffba82" + }, + "property_identifier": { + "color": "#ffba82" + }, + "shorthand_property_identifier": { + "color": "#ffba82" + }, + "shorthand_property_identifier_pattern": { + "color": "#ffba82" + }, + "method_definition": { + "color": "#ba8ffd" + }, + "function.method.call": { + "color": "#ba8ffd" + }, + "string.template": { + "color": "#8cda94" + }, + "template_string": { + "color": "#8cda94" + }, + "tag.jsx": { + "color": "#ffa685" + }, + "tag.component": { + "color": "#e290f0" + }, + "punctuation": { + "color": "#737373" + }, + "punctuation.bracket": { + "color": "#737373" + }, + "punctuation.delimiter": { + "color": "#737373" + }, + "punctuation.list_marker": { + "color": "#737373" + }, + "punctuation.special": { + "color": "#ff91a8" + }, + "operator": { + "color": "#68cdf2" + }, + "tag": { + "color": "#ffa685" + }, + "attribute": { + "color": "#8eddb2" + }, + "label": { + "color": "#ffbc56" + }, + "namespace": { + "color": "#ffbc56" + }, + "decorator": { + "color": "#97c4ff" + }, + "attribute.builtin": { + "color": "#97c4ff" + }, + "embedded": { + "color": "#d4d4d4" + }, + "preproc": { + "color": "#ff91a8" + }, + "text.literal": { + "color": "#8cda94" + }, + "markup.heading": { + "color": "#ffa685", + "font_weight": 700 + }, + "markup.bold": { + "color": "#ffde80", + "font_weight": 700 + }, + "markup.italic": { + "color": "#ff91a8", + "font_style": "italic" + }, + "markup.strikethrough": { + "color": "#636363" + }, + "markup.link.url": { + "color": "#69b1ff" + }, + "markup.link.text": { + "color": "#ba8ffd" + }, + "markup.quote": { + "color": "#636363", + "font_style": "italic" + }, + "markup.list": { + "color": "#ffa685" + }, + "markup.list.numbered": { + "color": "#ffa685" + }, + "markup.list.unnumbered": { + "color": "#ffa685" + }, + "markup.raw": { + "color": "#8cda94" + }, + "markup.raw.inline": { + "color": "#8cda94" + }, + "markup.raw.block": { + "color": "#8cda94" + }, + "diff.plus": { + "color": "#60d199" + }, + "diff.minus": { + "color": "#ff6762" + }, + "diff.delta": { + "color": "#ffd452" + }, + "link_text": { + "color": "#69b1ff" + }, + "link_uri": { + "color": "#ff91a8" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "primary": { + "color": "#69b1ff" + }, + "title": { + "color": "#ffa685", + "font_weight": 700 + }, + "predictive": { + "color": "#525252", + "font_style": "italic" + } + } + } + }, + { + "name": "Pierre Dark Tritanopia", + "appearance": "dark", + "style": { + "background": "#171717", + "surface.background": "#171717", + "elevated_surface.background": "#101010", + "drop_target.background": "#009fff26", + "editor.background": "#0a0a0a", + "editor.foreground": "#fafafa", + "editor.gutter.background": "#0a0a0a", + "editor.active_line.background": "#19283c8c", + "editor.active_line_number": "#a3a3a3", + "editor.line_number": "#737373", + "editor.highlighted_line.background": "#19283c59", + "editor.indent_guide": "#1d1d1d", + "editor.indent_guide_active": "#262626", + "editor.invisible": "#636363", + "editor.wrap_guide": "#1d1d1d", + "editor.active_wrap_guide": "#262626", + "editor.document_highlight.read_background": "#009fff26", + "editor.document_highlight.write_background": "#009fff40", + "editor.document_highlight.bracket_background": "#009fff33", + "editor.subheader.background": "#171717", + "text": "#fafafa", + "text.muted": "#737373", + "text.placeholder": "#636363", + "text.disabled": "#636363", + "text.accent": "#009fff", + "border": "#1d1d1d", + "border.variant": "#262626", + "border.focused": "#009fff", + "border.selected": "#009fff", + "border.transparent": "transparent", + "border.disabled": "#262626", + "element.background": "#1d1d1d", + "element.hover": "#19283c80", + "element.active": "#19283cb3", + "element.selected": "#19283c99", + "element.disabled": "#1d1d1d80", + "ghost_element.background": "transparent", + "ghost_element.hover": "#19283c59", + "ghost_element.active": "#19283c8c", + "ghost_element.selected": "#19283c80", + "ghost_element.disabled": "transparent", + "icon": "#a3a3a3", + "icon.muted": "#737373", + "icon.disabled": "#636363", + "icon.placeholder": "#636363", + "icon.accent": "#009fff", + "link_text.hover": "#009fff", + "error": "#ff855e", + "error.background": "#ff855e1a", + "error.border": "#ff855e4d", + "warning": "#009fff", + "warning.background": "#009fff1a", + "warning.border": "#009fff4d", + "success": "#92dde4", + "success.background": "#92dde41a", + "success.border": "#92dde44d", + "info": "#69b1ff", + "info.background": "#69b1ff1a", + "info.border": "#69b1ff4d", + "hint": "#737373", + "hint.background": "#7373731a", + "hint.border": "#73737333", + "predictive": "#636363", + "predictive.background": "#6363631a", + "predictive.border": "#63636333", + "unreachable": "#636363", + "unreachable.background": "#6363630d", + "unreachable.border": "#6363631a", + "created": "#92dde4", + "created.background": "#92dde41a", + "created.border": "#92dde44d", + "modified": "#009fff", + "modified.background": "#009fff1a", + "modified.border": "#009fff4d", + "deleted": "#ff855e", + "deleted.background": "#ff855e1a", + "deleted.border": "#ff855e4d", + "conflict": "#ea68bc", + "conflict.background": "#ea68bc1a", + "conflict.border": "#ea68bc4d", + "hidden": "#636363", + "hidden.background": "#6363630d", + "hidden.border": "#6363631a", + "ignored": "#737373", + "ignored.background": "#7373730d", + "ignored.border": "#7373731a", + "renamed": "#69b1ff", + "renamed.background": "#69b1ff1a", + "renamed.border": "#69b1ff4d", + "search.match_background": "#ffbc564d", + "tab_bar.background": "#171717", + "tab.active_background": "#171717", + "tab.inactive_background": "#171717", + "toolbar.background": "#171717", + "title_bar.background": "#171717", + "title_bar.inactive_background": "#171717", + "panel.background": "#171717", + "panel.focused_border": "#009fff", + "status_bar.background": "#171717", + "scrollbar.thumb.background": "#6363634d", + "scrollbar.thumb.hover_background": "#63636380", "scrollbar.thumb.border": "transparent", "scrollbar.track.background": "transparent", "scrollbar.track.border": "transparent", - "terminal.background": "#101010", - "terminal.foreground": "#8a8a8a", - "terminal.bright_foreground": "#d4d4d4", - "terminal.dim_foreground": "#636363", + "terminal.background": "#171717", + "terminal.foreground": "#a3a3a3", + "terminal.bright_foreground": "#fafafa", + "terminal.dim_foreground": "#737373", "terminal.ansi.black": "#171717", - "terminal.ansi.red": "#ff2e3f", - "terminal.ansi.green": "#0dbe4e", - "terminal.ansi.yellow": "#ffca00", - "terminal.ansi.blue": "#009fff", - "terminal.ansi.magenta": "#e130ac", - "terminal.ansi.cyan": "#08c0ef", + "terminal.ansi.red": "#ff855e", + "terminal.ansi.green": "#64d1db", + "terminal.ansi.yellow": "#ffbc56", + "terminal.ansi.blue": "#69b1ff", + "terminal.ansi.magenta": "#ea68bc", + "terminal.ansi.cyan": "#92dde4", "terminal.ansi.white": "#bcbcbc", "terminal.ansi.bright_black": "#171717", - "terminal.ansi.bright_red": "#ff2e3f", - "terminal.ansi.bright_green": "#86c427", - "terminal.ansi.bright_yellow": "#ffca00", - "terminal.ansi.bright_blue": "#009fff", - "terminal.ansi.bright_magenta": "#e130ac", - "terminal.ansi.bright_cyan": "#08c0ef", + "terminal.ansi.bright_red": "#ffa685", + "terminal.ansi.bright_green": "#92dde4", + "terminal.ansi.bright_yellow": "#ffcc81", + "terminal.ansi.bright_blue": "#97c4ff", + "terminal.ansi.bright_magenta": "#f191cc", + "terminal.ansi.bright_cyan": "#92dde4", "terminal.ansi.bright_white": "#bcbcbc", "players": [ { - "cursor": "#69b1ff", - "background": "#69b1ff", - "selection": "#69b1ff40" + "cursor": "#009fff", + "background": "#009fff", + "selection": "#009fff40" }, { - "cursor": "#60d199", - "background": "#60d199", - "selection": "#60d19940" + "cursor": "#92dde4", + "background": "#92dde4", + "selection": "#92dde440" }, { - "cursor": "#ff91a8", - "background": "#ff91a8", - "selection": "#ff91a840" + "cursor": "#d568ea", + "background": "#d568ea", + "selection": "#d568ea40" }, { - "cursor": "#ba8ffd", - "background": "#ba8ffd", - "selection": "#ba8ffd40" + "cursor": "#97c4ff", + "background": "#97c4ff", + "selection": "#97c4ff40" }, { - "cursor": "#8cda94", - "background": "#8cda94", - "selection": "#8cda9440" + "cursor": "#92dde4", + "background": "#92dde4", + "selection": "#92dde440" }, { - "cursor": "#ffd452", - "background": "#ffd452", - "selection": "#ffd45240" + "cursor": "#ffbc56", + "background": "#ffbc56", + "selection": "#ffbc5640" }, { - "cursor": "#e290f0", - "background": "#e290f0", - "selection": "#e290f040" + "cursor": "#ea68bc", + "background": "#ea68bc", + "selection": "#ea68bc40" }, { - "cursor": "#68cdf2", - "background": "#68cdf2", - "selection": "#68cdf240" + "cursor": "#69b1ff", + "background": "#69b1ff", + "selection": "#69b1ff40" } ], "syntax": { "comment": { - "color": "#636363" + "color": "#737373" }, "comment.doc": { - "color": "#636363" + "color": "#737373" }, "string": { - "color": "#8cda94" + "color": "#92dde4" }, "string.escape": { - "color": "#8fe0d0" + "color": "#64d1db" }, "string.regex": { - "color": "#92dde4" + "color": "#64d1db" }, "string.special": { - "color": "#8fe0d0" + "color": "#64d1db" }, "string.special.symbol": { - "color": "#ffde80" + "color": "#ffcc81" }, "number": { - "color": "#96d9f6" + "color": "#69b1ff" }, "constant": { - "color": "#ffde80" + "color": "#ffcc81" }, "boolean": { - "color": "#96d9f6" + "color": "#69b1ff" }, "keyword": { - "color": "#ff91a8" + "color": "#d568ea" }, "keyword.operator": { - "color": "#68cdf2" + "color": "#00c5d2" }, "function": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "function.method": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "function.builtin": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "function.special.definition": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "function.call": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "type": { - "color": "#e290f0" + "color": "#ea68bc" }, "type.builtin": { - "color": "#e290f0" + "color": "#ea68bc" }, "constructor": { - "color": "#e290f0" + "color": "#ea68bc" }, "variable": { - "color": "#ffba82" + "color": "#ff855e" }, "variable.builtin": { - "color": "#ffbc56" + "color": "#ff855e" }, "variable.member": { - "color": "#ffba82" + "color": "#ff855e" }, "variable.parameter": { - "color": "#8a8a8a" + "color": "#a3a3a3" }, "variable.special": { - "color": "#ffbc56" + "color": "#ff855e" }, "property": { - "color": "#ffba82" + "color": "#ff855e" }, "property.css": { - "color": "#69b1ff" + "color": "#009fff" }, "property.definition": { - "color": "#69b1ff" + "color": "#009fff" }, "property_name": { - "color": "#69b1ff" + "color": "#009fff" }, "value": { - "color": "#96d9f6" + "color": "#69b1ff" }, "constant.css": { - "color": "#ffde80" + "color": "#ffcc81" }, "string.plain": { - "color": "#96d9f6" + "color": "#69b1ff" }, "plain_value": { - "color": "#96d9f6" + "color": "#69b1ff" }, "tag.css": { - "color": "#ffa685" + "color": "#ff855e" }, "tag_name": { - "color": "#ffa685" + "color": "#ff855e" }, "class": { - "color": "#8eddb2" + "color": "#64d1db" }, "class_name": { - "color": "#8eddb2" + "color": "#64d1db" }, "selector.class": { - "color": "#8eddb2" + "color": "#64d1db" }, "selector.id": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "id_name": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "selector.pseudo": { - "color": "#68cdf2" + "color": "#00c5d2" }, "pseudo_class_selector": { - "color": "#68cdf2" + "color": "#00c5d2" }, "pseudo_element_selector": { - "color": "#68cdf2" + "color": "#00c5d2" }, "keyword.directive": { - "color": "#ff91a8" + "color": "#d568ea" }, "keyword.control.at-rule": { - "color": "#ff91a8" + "color": "#d568ea" }, "at_keyword": { - "color": "#ff91a8" + "color": "#d568ea" }, "variable.scss": { - "color": "#ffba82" + "color": "#ff855e" }, "variable.css": { - "color": "#ffba82" + "color": "#ff855e" }, "property.custom": { - "color": "#ffba82" + "color": "#ff855e" }, "unit": { - "color": "#96d9f6" + "color": "#69b1ff" }, "number.unit": { - "color": "#96d9f6" + "color": "#69b1ff" }, "color": { - "color": "#ffde80" + "color": "#ffcc81" }, "constant.color": { - "color": "#ffde80" + "color": "#ffcc81" }, "keyword.important": { - "color": "#ff91a8" + "color": "#d568ea" }, "variable.language": { - "color": "#ffbc56" + "color": "#ff855e" }, "this": { - "color": "#ffbc56" + "color": "#ff855e" }, "self": { - "color": "#ffbc56" + "color": "#ff855e" }, "type.class": { - "color": "#e290f0" + "color": "#ea68bc" }, "property.object": { - "color": "#ffba82" + "color": "#ff855e" }, "property_identifier": { - "color": "#ffba82" + "color": "#ff855e" }, "shorthand_property_identifier": { - "color": "#ffba82" + "color": "#ff855e" }, "shorthand_property_identifier_pattern": { - "color": "#ffba82" + "color": "#ff855e" }, "method_definition": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "function.method.call": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "string.template": { - "color": "#8cda94" + "color": "#92dde4" }, "template_string": { - "color": "#8cda94" + "color": "#92dde4" }, "tag.jsx": { - "color": "#ffa685" + "color": "#ff855e" }, "tag.component": { - "color": "#e290f0" + "color": "#ea68bc" }, "punctuation": { - "color": "#737373" + "color": "#636363" }, "punctuation.bracket": { - "color": "#737373" + "color": "#636363" }, "punctuation.delimiter": { - "color": "#737373" + "color": "#636363" }, "punctuation.list_marker": { - "color": "#737373" + "color": "#636363" }, "punctuation.special": { - "color": "#ff91a8" + "color": "#d568ea" }, "operator": { - "color": "#68cdf2" + "color": "#00c5d2" }, "tag": { - "color": "#ffa685" + "color": "#ff855e" }, "attribute": { - "color": "#8eddb2" + "color": "#64d1db" }, "label": { - "color": "#ffbc56" + "color": "#ff855e" }, "namespace": { - "color": "#ffbc56" + "color": "#ff855e" }, "decorator": { - "color": "#97c4ff" + "color": "#69b1ff" }, "attribute.builtin": { - "color": "#97c4ff" + "color": "#69b1ff" }, "embedded": { - "color": "#d4d4d4" + "color": "#fafafa" }, "preproc": { - "color": "#ff91a8" + "color": "#d568ea" }, "text.literal": { - "color": "#8cda94" + "color": "#92dde4" }, "markup.heading": { - "color": "#ffa685", + "color": "#ff855e", "font_weight": 700 }, "markup.bold": { - "color": "#ffde80", + "color": "#ffcc81", "font_weight": 700 }, "markup.italic": { - "color": "#ff91a8", + "color": "#d568ea", "font_style": "italic" }, "markup.strikethrough": { - "color": "#636363" + "color": "#737373" }, "markup.link.url": { - "color": "#69b1ff" + "color": "#009fff" }, "markup.link.text": { - "color": "#ba8ffd" + "color": "#97c4ff" }, "markup.quote": { - "color": "#636363", + "color": "#737373", "font_style": "italic" }, "markup.list": { - "color": "#ffa685" + "color": "#ff855e" }, "markup.list.numbered": { - "color": "#ffa685" + "color": "#ff855e" }, "markup.list.unnumbered": { - "color": "#ffa685" + "color": "#ff855e" }, "markup.raw": { - "color": "#8cda94" + "color": "#92dde4" }, "markup.raw.inline": { - "color": "#8cda94" + "color": "#92dde4" }, "markup.raw.block": { - "color": "#8cda94" + "color": "#92dde4" }, "diff.plus": { - "color": "#60d199" + "color": "#92dde4" }, "diff.minus": { - "color": "#ff6762" + "color": "#ff855e" }, "diff.delta": { - "color": "#ffd452" + "color": "#ffbc56" }, "link_text": { - "color": "#69b1ff" + "color": "#009fff" }, "link_uri": { - "color": "#ff91a8" + "color": "#d568ea" }, "emphasis": { "font_style": "italic" @@ -1982,14 +3974,14 @@ "font_weight": 700 }, "primary": { - "color": "#69b1ff" + "color": "#009fff" }, "title": { - "color": "#ffa685", + "color": "#ff855e", "font_weight": 700 }, "predictive": { - "color": "#525252", + "color": "#636363", "font_style": "italic" } } From 467756b939fce9f1349173cc400fda053b7c0150 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:57:03 -0700 Subject: [PATCH 3/6] chore: rename the start script to dev and rebuild previews Rename the watch script from start to dev, widen its watch to scripts/ as well as src/, and have it rebuild both the themes and the previews on change (build && preview) rather than just the themes. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9e700d..14583ef 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "build": "ts-node scripts/build.ts", "preview": "ts-node scripts/createPreviews.ts", "test": "npm run build && ts-node test/test.ts", - "start": "nodemon --watch src --ext ts --exec npm run build", + "dev": "nodemon --watch src --watch scripts --ext ts --exec \"npm run build && npm run preview\"", "package": "ts-node scripts/buildVsCodePackage.ts", "prepublishOnly": "npm run build" }, From 0a12f2a3a6bf940ced7fbcf3cde8f664d81aa590 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:57:36 -0700 Subject: [PATCH 4/6] docs: refresh README and consolidate testing into CONTRIBUTING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README: list the CVD theme entry points and point contributors at CONTRIBUTING and ACCESSIBILITY. CONTRIBUTING: update the source-layout pointers to src/createTheme.ts and src/roles, refresh the scripts table (dev, preview), and add a Testing section covering how to run and read the CVD accessibility gate — the operational counterpart to the design rationale in ACCESSIBILITY. --- CONTRIBUTING.md | 23 +++++++++++++++++------ README | 16 +++++++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92153d2..606dda0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,19 +56,30 @@ To override this (or any other) theme in your personal config file, please follo 2. Run `npm install` to install the dependencies. 3. Press `F5` to open a new window with your extension loaded 4. Open `Code > Preferences > Color Theme` [`⌘k ⌘t`] and pick the "Pierre…" theme you want to test. -5. Make changes to the [`/src/theme.ts`](https://github.com/pierrecomputer/theme/blob/main/src/theme.ts) file. -6. Run `npm run build` to update the theme. You can also run `npm run start` instead to automatically rebuild the theme while making changes and no reloading should be necessary. -7. Run `npm test` to validate your changes (this runs automatically on PRs). +5. Make changes under [`/src`](https://github.com/pierrecomputer/theme/blob/main/src). Theme construction lives in `src/createTheme.ts`; role values live in `src/roles`. +6. Run `npm run build` to update the theme. You can also run `npm run dev` instead to automatically rebuild the themes and previews while making changes and no reloading should be necessary. +7. Run `npm test` to validate your changes (this runs automatically on PRs); see + [Testing](#testing) below. 8. Once you're happy, commit your changes and open a PR. +## Testing + +`npm test` builds the themes, runs structural validation, and runs the CVD +accessibility gate (the design it enforces is documented in +[`ACCESSIBILITY.md`](ACCESSIBILITY.md)). The gate, in `test/`:g + +For visual proofing, `npm run preview` writes `preview/*.html`: the palette +scales, the Display-P3 conversions, and a normal-vs-simulated CVD proof sheet. + ## Scripts | Script | Description | | --- | --- | | `npm run build` | Builds the theme `.json` files in `./themes` directory | -| `npm test` | Runs validation tests on the theme (includes build) | -| `npm run package` | Compiles the theme `.vsix` file at the project root | -| `npm start` | Automatically runs build on file change | +| `npm test` | Runs validation tests + the CVD accessibility gate (includes build) | +| `npm run preview` | Writes preview HTML files from `src/previews` into `preview/` | +| `npm run package` | Temporarily applies the VSIX package name/README shim, then writes the `.vsix` file at the project root | +| `npm run dev` | Rebuilds themes and previews on file change | ## Credit diff --git a/README b/README index e931dec..a27bec9 100644 --- a/README +++ b/README @@ -29,10 +29,20 @@ Usage: import pierreLight from '@pierre/theme/pierre-light' Available themes: - - @pierre/theme/pierre-dark - @pierre/theme/pierre-light - - @pierre/theme/pierre-dark-vibrant (Display P3) - - @pierre/theme/pierre-light-vibrant (Display P3) + - @pierre/theme/pierre-light-protanopia-deuteranopia (CVD, red-green) + - @pierre/theme/pierre-light-tritanopia (CVD, blue-yellow) + - @pierre/theme/pierre-light-vibrant (Display P3) + - @pierre/theme/pierre-dark + - @pierre/theme/pierre-dark-protanopia-deuteranopia (CVD, red-green) + - @pierre/theme/pierre-dark-tritanopia (CVD, blue-yellow) + - @pierre/theme/pierre-dark-vibrant (Display P3) Zed: Install "Pierre" from the Zed extension registry + +~~~ + +Contributing: + - Development, build, and testing: [CONTRIBUTING.md](CONTRIBUTING.md) + - Accessibility (CVD themes): [ACCESSIBILITY.md](ACCESSIBILITY.md) From 422db0ffd5292adf6dedc1afabf51d6b36fc2aa6 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Sun, 14 Jun 2026 22:04:05 -0700 Subject: [PATCH 5/6] test: port hand-rolled runner to node:test Replace the manual test runner (console.log + error accumulation + process.exit) with the built-in node:test runner using describe/test blocks and node:assert/strict, preserving every existing assertion. - test/theme.test.ts: palette-role and theme-generation structural checks - test/build.test.ts: generated theme-file (build-output) checks - test/cvd.test.ts: CVD accessibility gate as describe/test; advisory Tier-3 pairs and report-only contrast surfaced via test diagnostics - test/helpers/cvd.ts: shared color-science utilities (CVD simulation, contrast, deltaE, culori cross-check) - run via `node --require ts-node/register --test`; non-zero exit on failure --- package.json | 2 +- src/color/cvd.ts | 8 +- src/previews/cvd.ts | 4 +- test/build.test.ts | 105 +++++++++++ test/cvd-test.ts | 417 -------------------------------------------- test/cvd.test.ts | 300 +++++++++++++++++++++++++++++++ test/helpers/cvd.ts | 111 ++++++++++++ test/test.ts | 322 ---------------------------------- test/theme.test.ts | 235 +++++++++++++++++++++++++ 9 files changed, 758 insertions(+), 746 deletions(-) create mode 100644 test/build.test.ts delete mode 100644 test/cvd-test.ts create mode 100644 test/cvd.test.ts create mode 100644 test/helpers/cvd.ts delete mode 100644 test/test.ts create mode 100644 test/theme.test.ts diff --git a/package.json b/package.json index 14583ef..ab25524 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "scripts": { "build": "ts-node scripts/build.ts", "preview": "ts-node scripts/createPreviews.ts", - "test": "npm run build && ts-node test/test.ts", + "test": "npm run build && node --require ts-node/register --test --test-reporter spec test/*.test.ts", "dev": "nodemon --watch src --watch scripts --ext ts --exec \"npm run build && npm run preview\"", "package": "ts-node scripts/buildVsCodePackage.ts", "prepublishOnly": "npm run build" diff --git a/src/color/cvd.ts b/src/color/cvd.ts index b4e7db4..8f25f48 100644 --- a/src/color/cvd.ts +++ b/src/color/cvd.ts @@ -1,5 +1,5 @@ // CVD (Color Vision Deficiency) simulation — "what does this color look like to a -// protan/deutan/tritan viewer?". Consumed by the objective gate (test/cvd-test.ts) +// protan/deutan/tritan viewer?". Consumed by the objective gate (test/cvd.test.ts) // and the proof-sheet preview (src/previews/cvd.ts). // // ───────────────────────────────────────────────────────────────────────────── @@ -22,7 +22,7 @@ // hue pole has to be reused. We don't guess whether a palette works — we // *simulate* how each color looks to a dichromat, then measure separations with // deltaE2000() and legibility with contrastRatio() (sibling color-science -// modules). The objective gate in test/cvd-test.ts ties those together. +// modules). The objective gate in test/cvd.test.ts ties those together. // // Standards / sources (also cited in ACCESSIBILITY.md): // • Machado, Oliveira & Fernandes (2009), "A Physiologically-Based Model for @@ -54,7 +54,7 @@ export type CVDType = "protan" | "deutan" | "tritan"; // matches real dichromat perception better than the gamma-sRGB application used // by culori and the `colorspace` R package (which it follows). The two only // diverge noticeably on saturated colors. We implement linear here and the gate -// in test/cvd-test.ts additionally checks culori's gamma convention for Tier-1 +// in test/cvd.test.ts additionally checks culori's gamma convention for Tier-1 // and Tier-2 distinguishability. type Matrix3 = readonly [ @@ -151,7 +151,7 @@ export function simulateCVD(hex: string, type: CVDType, severity = 1.0): string // Provable invariants of the model: severity-0 is the identity, neutral grays are // preserved (every Machado row sums to ~1), and the expected confusable axis // actually collapses. (contrast/ΔE are additionally cross-checked against culori -// in test/cvd-test.ts.) These run from cvd-test.ts. +// in test/cvd.test.ts.) These run from cvd.test.ts. export type SelfCheckResult = { name: string; ok: boolean; detail: string }; diff --git a/src/previews/cvd.ts b/src/previews/cvd.ts index 655fdf3..e68db7b 100644 --- a/src/previews/cvd.ts +++ b/src/previews/cvd.ts @@ -1,6 +1,6 @@ // src/previews/cvd.ts // Builds preview/cvd.html (returned as a string; written by scripts/createPreviews.ts) — -// a human-eyeballing companion to the objective gate (test/cvd-test.ts). For each +// a human-eyeballing companion to the objective gate (test/cvd.test.ts). For each // CVD (color-vision-deficiency) theme it shows, // side by side: the colors as defined in the theme, and the same colors pushed // through the Machado-2009 simulation for that deficiency — i.e. what a person @@ -164,7 +164,7 @@ function renderCvdHtml(): string {

Left = the colors as defined in the theme. Right = the same colors pushed through the Machado-2009 simulation for that deficiency. If the design holds, the right column still reads as added-vs-deleted, pass-vs-fail, and error-vs-warning. - Objective ΔE/contrast checks live in test/cvd-test.ts (run via npm test).

+ Objective ΔE/contrast checks live in test/cvd.test.ts (run via npm test).

${VIEWS.map(section).join("\n")} diff --git a/test/build.test.ts b/test/build.test.ts new file mode 100644 index 0000000..b52aad1 --- /dev/null +++ b/test/build.test.ts @@ -0,0 +1,105 @@ +/** + * Guards the build *output* rather than theme design. After `npm run build` writes + * themes/*.json, this checks each expected file exists and carries the right + * metadata — catching build-step regressions (a missing, empty, or misnamed file). + */ +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync, existsSync } from "node:fs"; + +type GeneratedFile = { path: string; expectedType: "light" | "dark"; expectedName: string; expectedDisplayName: string }; + +const GENERATED_FILES: GeneratedFile[] = [ + { + path: "themes/pierre-light.json", + expectedType: "light", + expectedName: "pierre-light", + expectedDisplayName: "Pierre Light" + }, + { + path: "themes/pierre-light-protanopia-deuteranopia.json", + expectedType: "light", + expectedName: "pierre-light-protanopia-deuteranopia", + expectedDisplayName: "Pierre Light Protanopia & Deuteranopia" + }, + { + path: "themes/pierre-light-soft.json", + expectedType: "light", + expectedName: "pierre-light-soft", + expectedDisplayName: "Pierre Light Soft" + }, + { + path: "themes/pierre-light-tritanopia.json", + expectedType: "light", + expectedName: "pierre-light-tritanopia", + expectedDisplayName: "Pierre Light Tritanopia" + }, + { + path: "themes/pierre-light-vibrant.json", + expectedType: "light", + expectedName: "pierre-light-vibrant", + expectedDisplayName: "Pierre Light Vibrant" + }, + { + path: "themes/pierre-dark.json", + expectedType: "dark", + expectedName: "pierre-dark", + expectedDisplayName: "Pierre Dark" + }, + { + path: "themes/pierre-dark-protanopia-deuteranopia.json", + expectedType: "dark", + expectedName: "pierre-dark-protanopia-deuteranopia", + expectedDisplayName: "Pierre Dark Protanopia & Deuteranopia" + }, + { + path: "themes/pierre-dark-soft.json", + expectedType: "dark", + expectedName: "pierre-dark-soft", + expectedDisplayName: "Pierre Dark Soft" + }, + { + path: "themes/pierre-dark-tritanopia.json", + expectedType: "dark", + expectedName: "pierre-dark-tritanopia", + expectedDisplayName: "Pierre Dark Tritanopia" + }, + { + path: "themes/pierre-dark-vibrant.json", + expectedType: "dark", + expectedName: "pierre-dark-vibrant", + expectedDisplayName: "Pierre Dark Vibrant" + }, +]; + +describe("generated theme files", () => { + for (const file of GENERATED_FILES) { + describe(file.path, () => { + test("exists", () => { + assert.ok(existsSync(file.path), `file does not exist: ${file.path}`); + }); + + test("is non-empty, valid JSON with the expected metadata", () => { + const content = readFileSync(file.path, "utf8"); + assert.notEqual(content.trim(), "", `file is empty: ${file.path}`); + + const theme = JSON.parse(content); + + assert.ok(theme.name, `${file.path}: missing name`); + assert.ok(theme.displayName, `${file.path}: missing displayName`); + assert.equal(theme.name, file.expectedName, `${file.path}: unexpected name`); + assert.equal(theme.displayName, file.expectedDisplayName, `${file.path}: unexpected displayName`); + assert.ok(theme.type, `${file.path}: missing type`); + assert.equal(theme.type, file.expectedType, `${file.path}: unexpected type`); + assert.ok( + theme.colors && Object.keys(theme.colors).length > 0, + `${file.path}: missing or empty colors object` + ); + assert.ok( + Array.isArray(theme.tokenColors) && theme.tokenColors.length > 0, + `${file.path}: missing or empty tokenColors array` + ); + }); + }); + } +}); diff --git a/test/cvd-test.ts b/test/cvd-test.ts deleted file mode 100644 index be06e6b..0000000 --- a/test/cvd-test.ts +++ /dev/null @@ -1,417 +0,0 @@ -// test/cvd-test.ts -// -// OBJECTIVE GATE for the CVD (Color Vision Deficiency) themes. -// -// A CVD-safe palette is a claim that must be *proven*, not eyeballed. This file -// turns the design rules from src/roles into machine-checked assertions and is -// run as part of `npm test`. It fails the build (exit non-zero) if any Tier-1 or -// Tier-2 requirement regresses, so the themes cannot silently drift back into a -// red-green-ambiguous state. -// -// HOW IT WORKS (for engineers new to CVD): -// 1. simulate — recolor each role as a protan/deutan/tritan viewer would see it -// (Machado 2009 model, src/color/cvd.ts), at full dichromacy (severity 1.0). -// 2. distinguishability — for every pair of roles that co-occurs on screen, -// measure the perceptual distance (ΔE₀₀) between the *simulated* colors. If -// two signals (e.g. "added" vs "deleted") still look far apart, a CVD user -// can tell them apart. ΔE₀₀ > ~10 ≈ "clearly different". -// 3. contrast — WCAG legibility of each foreground vs its background, checked -// both normally and after simulation (simulation shifts luminance). -// -// TIERS — graded by WHAT CARRIES THE SIGNAL WHEN COLOR FAILS. Under full -// dichromacy there are only ~2 usable hue poles + luminance but ~20 chromatic -// roles, so not every pair can be hue-unique. We gate hardest where color is the -// *only* cue, and lean on the editor's built-in non-color cues elsewhere. -// • Tier 1 (hard gate, ΔE ≥ 11) — color is the SOLE disambiguator: -// diff add/delete backgrounds (success/danger), diff inserted/deleted TEXT -// (string/tag), merge-conflict backgrounds (merge/info), terminal pass/fail -// (ansi red/green). None of these has a glyph fallback. -// • Tier 2 (hard gate, ΔE ≥ 8) — color PLUS a non-color cue: -// diagnostics (error/warn/info — distinct icon SHAPES), and the -// highest-frequency syntax adjacencies + comment-vs-code. -// • Tier 3 (advisory, reported only) — color is tertiary: -// the git-tree clique (every entry already carries an M/A/D/U/C letter -// badge) and extended syntax (bold/italic + position carry it). Reported -// so regressions stay visible without blocking the build. - -import { - protanDeutanLight, - protanDeutanDark, - tritanopiaLight, - tritanopiaDark, - type Roles, -} from "../src/roles"; -import { simulateCVD, contrastRatio, deltaE2000, cvdSelfChecks, type CVDType } from "../src/color"; -import { - filterDeficiencyProt, filterDeficiencyDeuter, filterDeficiencyTrit, - differenceCiede2000, wcagContrast, formatHex, -} from "culori"; - -// ── Thresholds (standards-derived, tuned empirically during build-out) ────── -const TIER1_DELTA_E = 11; // co-occurring opposite-meaning signals -const TIER2_DELTA_E = 8; // critical syntax adjacencies -const TEXT_CONTRAST = 4.5; // WCAG 2.1 SC 1.4.3 normal text -const UI_CONTRAST = 3.0; // WCAG 2.1 SC 1.4.11 UI glyphs / SC 1.4.3 large text - -// ── Pair definitions ──────────────────────────────────────────────────────── -// A selector plucks one concrete hex from a resolved Roles object. -type Sel = (r: Roles) => string; -type Pair = { tier: 1 | 2 | 3; label: string; a: Sel; b: Sel; group: string }; - -const S = { - success: (r: Roles) => r.states.success, - danger: (r: Roles) => r.states.danger, - warn: (r: Roles) => r.states.warn, - info: (r: Roles) => r.states.info, - merge: (r: Roles) => r.states.merge, - accent: (r: Roles) => r.accent.primary, - ansiRed: (r: Roles) => r.ansi.red, - ansiGreen: (r: Roles) => r.ansi.green, - comment: (r: Roles) => r.syntax.comment, - string: (r: Roles) => r.syntax.string, - keyword: (r: Roles) => r.syntax.keyword, - variable: (r: Roles) => r.syntax.variable, - func: (r: Roles) => r.syntax.func, - type: (r: Roles) => r.syntax.type, - number: (r: Roles) => r.syntax.number, - tag: (r: Roles) => r.syntax.tag, // = diff "deleted" text token -}; - -// Generate every unordered pair within a group of named selectors. -function clique(group: string, tier: 1 | 2 | 3, members: [string, Sel][]): Pair[] { - const out: Pair[] = []; - for (let i = 0; i < members.length; i++) { - for (let j = i + 1; j < members.length; j++) { - out.push({ - tier, - group, - label: `${members[i][0]} vs ${members[j][0]}`, - a: members[i][1], - b: members[j][1], - }); - } - } - return out; -} - -const PAIRS: Pair[] = [ - // ── Tier 1 — color is the only cue (ΔE ≥ 11) ────────────────────────────── - // Diff gutter / overview ruler: added vs deleted backgrounds (no glyph). - { tier: 1, group: "diff bg", label: "success(added) vs danger(deleted)", a: S.success, b: S.danger }, - // Diff TEXT tokens: inserted vs deleted, the semantic core of a review. - { tier: 1, group: "diff text", label: "string(inserted) vs tag(deleted)", a: S.string, b: S.tag }, - // Merge conflict view: current(merge) vs incoming(info) tinted backgrounds. - { tier: 1, group: "merge conflict", label: "merge vs info", a: S.merge, b: S.info }, - // Terminal pass/fail. - { tier: 1, group: "terminal", label: "ansi.red vs ansi.green", a: S.ansiRed, b: S.ansiGreen }, - - // ── Tier 2 — color + a non-color cue (ΔE ≥ 8) ───────────────────────────── - // Diagnostics & notifications: error/warn/info — backed by distinct icon - // shapes (✕ / △ / ⓘ), so color is the secondary channel. - ...clique("diagnostics", 2, [ - ["danger", S.danger], - ["warn", S.warn], - ["info", S.info], - ]), - // Comment must never be mistaken for live code. - ...clique("comment vs code", 2, [ - ["comment", S.comment], - ["string", S.string], - ["keyword", S.keyword], - ["variable", S.variable], - ]).filter((p) => p.label.startsWith("comment")), - // The three highest-frequency code tokens. - ...clique("core syntax", 2, [ - ["keyword", S.keyword], - ["string", S.string], - ["variable", S.variable], - ]), - - // ── Tier 3 (advisory) ───────────────────────────────────────────────────── - // Git tree: added/modified/deleted/conflict — every entry has an M/A/D/U/C - // letter badge, so identical-looking colors are still unambiguous. Reported. - ...clique("git tree", 3, [ - ["success", S.success], - ["danger", S.danger], - ["merge", S.merge], - ["accent.primary", S.accent], - ]), - ...clique("extended syntax", 3, [ - ["func", S.func], - ["type", S.type], - ["number", S.number], - ["keyword", S.keyword], - ["string", S.string], - ["variable", S.variable], - ]), -]; - -// ── Theme registry ────────────────────────────────────────────────────────── -// Each protan/deutan theme must satisfy the gate under BOTH protan and deutan -// simulation; tritanopia themes under tritan. -type CvdThemeDef = { name: string; roles: Roles; cvds: CVDType[] }; -const THEMES: CvdThemeDef[] = [ - { name: "Pierre Light Protanopia & Deuteranopia", roles: protanDeutanLight, cvds: ["protan", "deutan"] }, - { name: "Pierre Dark Protanopia & Deuteranopia", roles: protanDeutanDark, cvds: ["protan", "deutan"] }, - { name: "Pierre Light Tritanopia", roles: tritanopiaLight, cvds: ["tritan"] }, - { name: "Pierre Dark Tritanopia", roles: tritanopiaDark, cvds: ["tritan"] }, -]; - -// CONTRAST POLICY. We hold the CVD themes to WCAG bars, but only the bar that -// fits how each color renders — and we do NOT impose a bar the *standard* Pierre -// themes never met (base Pierre LIGHT runs syntax/signal colors at 2–4.5:1 by -// design; see ACCESSIBILITY.md): -// • Body text (editor foreground) → 4.5:1 (SC 1.4.3 normal text) -// • Syntax tokens & meaningful signal colors → 3.0:1 (SC 1.4.11 UI / large) -// checked NORMAL and AFTER simulation (simulation shifts luminance). -// • Report-only (printed, never fails): colors whose canonical/brand hue is -// intrinsically high-luminance and which base Pierre itself keeps bright — -// `accent.primary`/`link` (brand blue), `warn` (caution yellow/amber), and -// the decorative ansi colors. Their *distinguishability* (ΔE) is what the -// gate enforces, not their raw contrast. -const SYNTAX_REPORT_ONLY = new Set([]); // (none — all syntax tokens gated) -// Syntax tokens are text-on-editor at the 3:1 bar; `invalid` is intentionally a -// background-tinted color, not a foreground, so it is excluded. -function syntaxForegrounds(r: Roles): [string, string][] { - return Object.entries(r.syntax).filter(([k]) => k !== "invalid" && !SYNTAX_REPORT_ONLY.has(k)); -} -// Signal colors gated at 3:1 (carry meaning): states except the bright `warn`, -// plus the terminal pass/fail pair. -function signalForegrounds(r: Roles): [string, string][] { - return [ - ["states.success", r.states.success], - ["states.danger", r.states.danger], - ["states.info", r.states.info], - ["states.merge", r.states.merge], - ["ansi.red", r.ansi.red], - ["ansi.green", r.ansi.green], - ]; -} -// Report-only (never fails the build). -function reportOnlyForegrounds(r: Roles): [string, string][] { - return [ - ["accent.primary", r.accent.primary], - ["states.warn", r.states.warn], - ["ansi.yellow", r.ansi.yellow], - ["ansi.blue", r.ansi.blue], - ["ansi.cyan", r.ansi.cyan], - ["ansi.magenta", r.ansi.magenta], - ]; -} - -// ── Runner ──────────────────────────────────────────────────────────────── -type Failure = { theme: string; kind: string; detail: string }; -type SimulationConvention = "linear" | "gamma"; - -function pad(s: string, n: number) { - return s.length >= n ? s : s + " ".repeat(n - s.length); -} - -const gammaSim: Record unknown> = { - protan: filterDeficiencyProt(1) as unknown as (c: string) => unknown, - deutan: filterDeficiencyDeuter(1) as unknown as (c: string) => unknown, - tritan: filterDeficiencyTrit(1) as unknown as (c: string) => unknown, -}; - -function simulateForConvention(hex: string, cvd: CVDType, convention: SimulationConvention): string { - if (convention === "linear") return simulateCVD(hex, cvd); - const simulated = formatHex(gammaSim[cvd](hex) as any); - if (!simulated) throw new Error(`culori could not simulate ${hex} for ${cvd}`); - return simulated; -} - -// Worst-case contrast of fg on bg after simulation, across both gamma conventions -// (the same linear + gamma pair the distinguishability check uses). -function simulatedContrast(fg: string, bg: string, cvd: CVDType): number { - let worst = Infinity; - for (const convention of ["linear", "gamma"] as const) { - worst = Math.min( - worst, - contrastRatio(simulateForConvention(fg, cvd, convention), simulateForConvention(bg, cvd, convention)) - ); - } - return worst; -} - -// Cross-validate our hand-rolled color math against culori (dev-only oracle). -function referenceCrossChecks(): { name: string; ok: boolean; detail: string }[] { - const ciede = differenceCiede2000(); - // culori parses hex strings at runtime; its types want parsed Color objects, so - // we loosen the signatures here (dev-only oracle). - const samples = [ - "#009fff", "#d52c36", "#199f43", "#ffca00", "#1a85d4", "#d47628", - "#a13cee", "#00c5d2", "#ff5d36", "#737373", "#ffffff", "#0a0a0a", - ]; - - // contrast & ΔE: must match culori to floating-point noise. - let maxC = 0, maxDe = 0; - for (let i = 0; i < samples.length; i++) { - for (let j = i + 1; j < samples.length; j++) { - const a = samples[i], b = samples[j]; - maxC = Math.max(maxC, Math.abs(contrastRatio(a, b) - (wcagContrast(a, b) as number))); - maxDe = Math.max(maxDe, Math.abs(deltaE2000(a, b) - ciede(a, b))); - } - } - - // simulation: differs from culori only in gamma convention, but must collapse - // the same axis — verify each maps the confusable pair to a much smaller ΔE. - const axisOk = (["protan", "deutan", "tritan"] as CVDType[]).every((t) => { - const x = t === "tritan" ? "#009fff" : "#ff2e3f"; // blue (tritan) / red (protan,deutan) - const y = "#199f43"; // green - const before = deltaE2000(x, y); - const ours = deltaE2000(simulateCVD(x, t), simulateCVD(y, t)); - const lib = ciede( - simulateForConvention(x, t, "gamma"), - simulateForConvention(y, t, "gamma") - ); - // Both implementations must collapse the confusable pair to under half its - // un-simulated separation (exact residual differs by gamma convention). - return ours < before * 0.5 && lib < before * 0.5; - }); - - return [ - { name: "contrast matches culori", ok: maxC < 0.01, detail: `max |Δ| ${maxC.toFixed(4)}` }, - { name: "ΔE2000 matches culori", ok: maxDe < 0.1, detail: `max |Δ| ${maxDe.toFixed(4)}` }, - { name: "simulation collapses same axis", ok: axisOk, detail: axisOk ? "ours & culori agree" : "axis mismatch" }, - ]; -} - -export function runCvdGate(): boolean { - console.log("\n🎨 CVD theme objective gate"); - console.log("=".repeat(60)); - - const failures: Failure[] = []; - - // 0) Color-science self-checks (prove the simulation/contrast/ΔE math itself). - console.log("\n🔬 Color-science self-checks (Machado 2009 / WCAG / CIEDE2000):"); - for (const c of cvdSelfChecks()) { - console.log(` ${c.ok ? "✅" : "❌"} ${pad(c.name, 34)} ${c.detail}`); - if (!c.ok) failures.push({ theme: "(self-check)", kind: "color-science", detail: c.name }); - } - - // 0b) Reference cross-validation vs culori. We keep our own implementation - // (its CVD simulation uses the more-correct linear-RGB convention), but - // prove the standardized formulas agree with a vetted library: contrast and - // ΔE must match to floating-point noise, and our simulation must collapse - // the same axes culori's does (it differs only in gamma convention, by - // design — see src/color/cvd.ts). - for (const r of referenceCrossChecks()) { - console.log(` ${r.ok ? "✅" : "❌"} ${pad(r.name, 34)} ${r.detail}`); - if (!r.ok) failures.push({ theme: "(cross-check)", kind: "reference", detail: `${r.name}: ${r.detail}` }); - } - - for (const { name, roles, cvds } of THEMES) { - console.log(`\n■ ${name} [simulated as: ${cvds.join(", ")}]`); - const bgEditor = roles.bg.editor; - const bgWindow = roles.bg.window; - - // 1) Contrast (normal + simulated). The simulated check takes the worst case - // across both gamma conventions; backgrounds are near-neutral so they barely - // move, but we simulate them under each convention for correctness. - const reportOnlyMin: Record = {}; - for (const cvd of cvds) { - // Body text — the one role held to the full 4.5:1 text bar. - { - const normal = contrastRatio(roles.fg.base, bgEditor); - const sim = simulatedContrast(roles.fg.base, bgEditor, cvd); - if (normal < TEXT_CONTRAST || sim < TEXT_CONTRAST) { - failures.push({ - theme: name, - kind: "contrast(body)", - detail: `fg.base on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${TEXT_CONTRAST})`, - }); - } - } - - for (const [key, hex] of syntaxForegrounds(roles)) { - const normal = contrastRatio(hex, bgEditor); - const sim = simulatedContrast(hex, bgEditor, cvd); - if (normal < UI_CONTRAST || sim < UI_CONTRAST) { - failures.push({ - theme: name, - kind: "contrast(syntax)", - detail: `syntax.${key} on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})`, - }); - } - } - for (const [key, hex] of signalForegrounds(roles)) { - const normal = contrastRatio(hex, bgWindow); - const sim = simulatedContrast(hex, bgWindow, cvd); - if (normal < UI_CONTRAST || sim < UI_CONTRAST) { - failures.push({ - theme: name, - kind: "contrast(signal)", - detail: `${key} on window — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})`, - }); - } - } - // Report-only: track the worst contrast seen, printed (never fails). - for (const [key, hex] of reportOnlyForegrounds(roles)) { - const c = Math.min(contrastRatio(hex, bgWindow), simulatedContrast(hex, bgWindow, cvd)); - reportOnlyMin[key] = Math.min(reportOnlyMin[key] ?? Infinity, c); - } - } - console.log( - " Contrast (report-only, intrinsically-bright/brand): " + - Object.entries(reportOnlyMin) - .map(([k, v]) => `${k} ${v.toFixed(2)}`) - .join(", ") - ); - - // 2) Distinguishability under simulation. - for (const tier of [1, 2, 3] as const) { - const pairs = PAIRS.filter((p) => p.tier === tier); - const threshold = tier === 1 ? TIER1_DELTA_E : tier === 2 ? TIER2_DELTA_E : 0; - console.log(` Tier ${tier} ${tier === 3 ? "(advisory)" : `(ΔE ≥ ${threshold})`}:`); - for (const p of pairs) { - const aHex = p.a(roles); - const bHex = p.b(roles); - // Worst case across all CVD types this theme targets, and across both - // common Machado gamma conventions: linear RGB (our implementation) and - // gamma-encoded sRGB (culori/colorspace). - let worst = Infinity; - let worstCvd: CVDType = cvds[0]; - let worstConvention: SimulationConvention = "linear"; - for (const cvd of cvds) { - for (const convention of ["linear", "gamma"] as const) { - const d = deltaE2000( - simulateForConvention(aHex, cvd, convention), - simulateForConvention(bHex, cvd, convention) - ); - if (d < worst) { - worst = d; - worstCvd = cvd; - worstConvention = convention; - } - } - } - const ok = tier === 3 ? true : worst >= threshold; - const flag = tier === 3 ? "·" : ok ? "✅" : "❌"; - console.log( - ` ${flag} ${pad(`[${p.group}] ${p.label}`, 46)} ΔE ${worst.toFixed(1).padStart(5)} (${worstCvd}, ${worstConvention})` - ); - if (!ok) { - failures.push({ - theme: name, - kind: `Tier ${tier} ΔE`, - detail: `${p.label} = ΔE ${worst.toFixed(1)} under ${worstCvd}/${worstConvention} (need ≥ ${threshold})`, - }); - } - } - } - } - - console.log("\n" + "=".repeat(60)); - if (failures.length === 0) { - console.log("✅ CVD gate passed — all Tier-1/Tier-2 pairs distinguishable & legible."); - return true; - } - console.error(`❌ CVD gate failed with ${failures.length} issue(s):`); - for (const f of failures) console.error(` - [${f.theme}] ${f.kind}: ${f.detail}`); - return false; -} - -// Allow standalone execution: `ts-node test/cvd-test.ts`. -if (require.main === module) { - process.exit(runCvdGate() ? 0 : 1); -} diff --git a/test/cvd.test.ts b/test/cvd.test.ts new file mode 100644 index 0000000..6123449 --- /dev/null +++ b/test/cvd.test.ts @@ -0,0 +1,300 @@ +/** + * OBJECTIVE GATE for the CVD (Color Vision Deficiency) themes. + * This file turns the design rules from src/roles into machine-checked assertions + * and is run as part of `npm test` (via the node:test runner). It fails the build + * if any Tier-1 or Tier-2 requirement regresses, so the themes cannot silently drift + * into an ambiguous state. + * + * How it works: + * 1. simulate — recolor each role as a protan/deutan/tritan viewer would see it + * (Machado 2009 model), at full dichromacy (severity 1.0). + * 2. distinguishability — for every pair of roles that co-occurs on screen, + * measure the perceptual distance (ΔE₀₀) between the simulated colors. If + * two signals (e.g. "added" vs "deleted") still look far apart, then they can + * be distinguished. ΔE₀₀ > ~10 ≈ "clearly different". + * 3. contrast — WCAG legibility of each foreground vs its background, checked + * both normally and after simulation (simulation shifts luminance). + * + * TIERS + * Graded by what carries the signal when color fails. Under full dichromacy there + * are only ~2 usable hue poles + luminance but ~20 chromatic roles, so not every + * pair can be hue-unique. We gate hardest where color is the only cue, and lean on + * the editor's built-in non-color cues elsewhere. + * • Tier 1 (hard gate, ΔE ≥ 11) — color is the SOLE disambiguator: + * diff add/delete backgrounds (success/danger), diff inserted/deleted TEXT + * (string/tag), merge-conflict backgrounds (merge/info), terminal pass/fail + * (ansi red/green). None of these has a glyph fallback. + * • Tier 2 (hard gate, ΔE ≥ 8) — color PLUS a non-color cue: + * diagnostics (error/warn/info — distinct icon SHAPES), and the + * highest-frequency syntax adjacencies + comment-vs-code. + * • Tier 3 (advisory, reported only) — color is tertiary: + * the git-tree group (every entry already carries an M/A/D/U/C letter + * badge) and extended syntax (bold/italic + position carry it). Reported + * via test diagnostics so regressions stay visible without blocking the build. + */ +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { + protanDeutanLight, + protanDeutanDark, + tritanopiaLight, + tritanopiaDark, + type Roles, +} from "../src/roles"; +import { contrastRatio, cvdSelfChecks, type CVDType } from "../src/color"; +import { simulatedContrast, worstDeltaE, referenceCrossChecks } from "./helpers/cvd"; + +// Thresholds (standards-derived, tuned empirically during build-out) +const TIER1_DELTA_E = 11; // co-occurring opposite-meaning signals +const TIER2_DELTA_E = 8; // critical syntax adjacencies +const TEXT_CONTRAST = 4.5; // WCAG 2.1 SC 1.4.3 normal text +const UI_CONTRAST = 3.0; // WCAG 2.1 SC 1.4.11 UI glyphs / SC 1.4.3 large text + +// The role-color pairs the gate checks +// A Selector plucks one concrete hex from a resolved Roles object, so a RolePair +// names two roles whose simulated colors we then compare. +type Selector = (r: Roles) => string; +type RolePair = { tier: 1 | 2 | 3; label: string; a: Selector; b: Selector; group: string }; + +// Named selectors for every role the gate references, so the pair list below can +// say `selectors.success` instead of repeating `(r) => r.states.success`. +const selectors = { + success: (r: Roles) => r.states.success, + danger: (r: Roles) => r.states.danger, + warn: (r: Roles) => r.states.warn, + info: (r: Roles) => r.states.info, + merge: (r: Roles) => r.states.merge, + accent: (r: Roles) => r.accent.primary, + ansiRed: (r: Roles) => r.ansi.red, + ansiGreen: (r: Roles) => r.ansi.green, + comment: (r: Roles) => r.syntax.comment, + string: (r: Roles) => r.syntax.string, + keyword: (r: Roles) => r.syntax.keyword, + variable: (r: Roles) => r.syntax.variable, + func: (r: Roles) => r.syntax.func, + type: (r: Roles) => r.syntax.type, + number: (r: Roles) => r.syntax.number, + tag: (r: Roles) => r.syntax.tag, // = diff "deleted" text token +}; + +// Expand a group whose members must each be distinguishable from every other into +// one RolePair per unordered combination — e.g. [danger, warn, info] becomes +// danger·warn, danger·info, warn·info. +function allPairsWithin(group: string, tier: 1 | 2 | 3, members: [string, Selector][]): RolePair[] { + const out: RolePair[] = []; + for (let i = 0; i < members.length; i++) { + for (let j = i + 1; j < members.length; j++) { + out.push({ + tier, + group, + label: `${members[i][0]} vs ${members[j][0]}`, + a: members[i][1], + b: members[j][1], + }); + } + } + return out; +} + +// Every role-color pair the gate measures, grouped by tier (see the header for +// what each tier means and why). +const DISTINGUISHABILITY_PAIRS: RolePair[] = [ + // ── Tier 1 — color is the only cue (ΔE ≥ 11) ────────────────────────────── + // Diff gutter / overview ruler: added vs deleted backgrounds (no glyph). + { tier: 1, group: "diff bg", label: "success(added) vs danger(deleted)", a: selectors.success, b: selectors.danger }, + // Diff TEXT tokens: inserted vs deleted, the semantic core of a review. + { tier: 1, group: "diff text", label: "string(inserted) vs tag(deleted)", a: selectors.string, b: selectors.tag }, + // Merge conflict view: current(merge) vs incoming(info) tinted backgrounds. + { tier: 1, group: "merge conflict", label: "merge vs info", a: selectors.merge, b: selectors.info }, + // Terminal pass/fail. + { tier: 1, group: "terminal", label: "ansi.red vs ansi.green", a: selectors.ansiRed, b: selectors.ansiGreen }, + + // ── Tier 2 — color + a non-color cue (ΔE ≥ 8) ───────────────────────────── + // Diagnostics & notifications: error/warn/info — backed by distinct icon + // shapes (✕ / △ / ⓘ), so color is the secondary channel. + ...allPairsWithin("diagnostics", 2, [ + ["danger", selectors.danger], + ["warn", selectors.warn], + ["info", selectors.info], + ]), + // Comment must never be mistaken for live code, so pair it against each token + // kind it sits next to (we only care about comment-vs-X, not X-vs-Y here). + { tier: 2, group: "comment vs code", label: "comment vs string", a: selectors.comment, b: selectors.string }, + { tier: 2, group: "comment vs code", label: "comment vs keyword", a: selectors.comment, b: selectors.keyword }, + { tier: 2, group: "comment vs code", label: "comment vs variable", a: selectors.comment, b: selectors.variable }, + // The three highest-frequency code tokens. + ...allPairsWithin("core syntax", 2, [ + ["keyword", selectors.keyword], + ["string", selectors.string], + ["variable", selectors.variable], + ]), + + // ── Tier 3 (advisory) ───────────────────────────────────────────────────── + // Git tree: added/modified/deleted/conflict — every entry has an M/A/D/U/C + // letter badge, so identical-looking colors are still unambiguous. Reported. + ...allPairsWithin("git tree", 3, [ + ["success", selectors.success], + ["danger", selectors.danger], + ["merge", selectors.merge], + ["accent.primary", selectors.accent], + ]), + ...allPairsWithin("extended syntax", 3, [ + ["func", selectors.func], + ["type", selectors.type], + ["number", selectors.number], + ["keyword", selectors.keyword], + ["string", selectors.string], + ["variable", selectors.variable], + ]), +]; + +// ── Theme registry ────────────────────────────────────────────────────────── +// Each protan/deutan theme must satisfy the gate under BOTH protan and deutan +// simulation; tritanopia themes under tritan. +type CvdThemeDef = { name: string; roles: Roles; cvds: CVDType[] }; +const THEMES: CvdThemeDef[] = [ + { name: "Pierre Light Protanopia & Deuteranopia", roles: protanDeutanLight, cvds: ["protan", "deutan"] }, + { name: "Pierre Dark Protanopia & Deuteranopia", roles: protanDeutanDark, cvds: ["protan", "deutan"] }, + { name: "Pierre Light Tritanopia", roles: tritanopiaLight, cvds: ["tritan"] }, + { name: "Pierre Dark Tritanopia", roles: tritanopiaDark, cvds: ["tritan"] }, +]; + +// ── Tests ───────────────────────────────────────────────────────────────────── + +// Color-science self-checks (prove the simulation/contrast/ΔE math itself). +describe("color-science self-checks (Machado 2009 / WCAG / CIEDE2000)", () => { + for (const c of cvdSelfChecks()) { + test(c.name, () => assert.ok(c.ok, c.detail)); + } +}); + +// Reference cross-validation vs culori (a dev-only oracle; ships nothing). +describe("reference cross-validation vs culori", () => { + for (const r of referenceCrossChecks()) { + test(r.name, () => assert.ok(r.ok, `${r.name}: ${r.detail}`)); + } +}); + +// CONTRAST POLICY. We hold the CVD themes to WCAG bars, but only the bar that fits +// how each color renders — and we do NOT impose a bar the *standard* Pierre themes +// never met (base Pierre LIGHT runs syntax/signal colors at 2–4.5:1 by design; see +// ACCESSIBILITY.md): +// • Body text (editor foreground) → 4.5:1 (SC 1.4.3 normal text) +// • Syntax tokens & meaningful signal colors → 3.0:1 (SC 1.4.11 UI / large text), +// checked NORMAL and AFTER simulation (simulation shifts luminance). +// • Report-only (printed, never fails): intrinsically-bright / brand colors that +// base Pierre itself keeps bright — their *distinguishability* (ΔE) is gated, +// not their raw contrast. +describe("CVD theme gate", () => { + for (const { name, roles, cvds } of THEMES) { + describe(`${name} [simulated as: ${cvds.join(", ")}]`, () => { + const bgEditor = roles.bg.editor; + const bgWindow = roles.bg.window; + + // Contrast (normal + simulated). The simulated check takes the worst case + // across both gamma conventions; backgrounds are near-neutral so they barely + // move, but we simulate them under each convention for correctness. + describe("contrast", () => { + // Syntax tokens are text-on-editor at the 3:1 bar; `invalid` is a + // background tint, not a foreground, so it's excluded (all others gated). + const syntaxForegrounds = Object.entries(roles.syntax).filter(([k]) => k !== "invalid"); + // Signal colors gated at 3:1 (carry meaning): states except the bright + // `warn`, plus the terminal pass/fail pair. + const signalForegrounds: [string, string][] = [ + ["states.success", roles.states.success], + ["states.danger", roles.states.danger], + ["states.info", roles.states.info], + ["states.merge", roles.states.merge], + ["ansi.red", roles.ansi.red], + ["ansi.green", roles.ansi.green], + ]; + + for (const cvd of cvds) { + // Body text — the one role held to the full 4.5:1 text bar. + test(`fg.base on editor — body text ≥ ${TEXT_CONTRAST}:1 (${cvd})`, () => { + const normal = contrastRatio(roles.fg.base, bgEditor); + const sim = simulatedContrast(roles.fg.base, bgEditor, cvd); + assert.ok( + normal >= TEXT_CONTRAST && sim >= TEXT_CONTRAST, + `fg.base on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${TEXT_CONTRAST})` + ); + }); + + for (const [key, hex] of syntaxForegrounds) { + test(`syntax.${key} on editor ≥ ${UI_CONTRAST}:1 (${cvd})`, () => { + const normal = contrastRatio(hex, bgEditor); + const sim = simulatedContrast(hex, bgEditor, cvd); + assert.ok( + normal >= UI_CONTRAST && sim >= UI_CONTRAST, + `syntax.${key} on editor — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})` + ); + }); + } + + for (const [key, hex] of signalForegrounds) { + test(`${key} on window ≥ ${UI_CONTRAST}:1 (${cvd})`, () => { + const normal = contrastRatio(hex, bgWindow); + const sim = simulatedContrast(hex, bgWindow, cvd); + assert.ok( + normal >= UI_CONTRAST && sim >= UI_CONTRAST, + `${key} on window — normal ${normal.toFixed(2)}, ${cvd} ${sim.toFixed(2)} (< ${UI_CONTRAST})` + ); + }); + } + } + + // Report-only: worst contrast seen for the intrinsically-bright / brand + // colors, surfaced as a diagnostic (never fails the build). + test("report-only contrast (intrinsically-bright / brand colors)", (t) => { + const reportOnlyForegrounds: [string, string][] = [ + ["accent.primary", roles.accent.primary], + ["states.warn", roles.states.warn], + ["ansi.yellow", roles.ansi.yellow], + ["ansi.blue", roles.ansi.blue], + ["ansi.cyan", roles.ansi.cyan], + ["ansi.magenta", roles.ansi.magenta], + ]; + const reportOnlyMin: Record = {}; + for (const cvd of cvds) { + for (const [key, hex] of reportOnlyForegrounds) { + const c = Math.min(contrastRatio(hex, bgWindow), simulatedContrast(hex, bgWindow, cvd)); + reportOnlyMin[key] = Math.min(reportOnlyMin[key] ?? Infinity, c); + } + } + t.diagnostic( + Object.entries(reportOnlyMin) + .map(([k, v]) => `${k} ${v.toFixed(2)}`) + .join(", ") + ); + }); + }); + + // Distinguishability under simulation. + describe("distinguishability under simulation", () => { + for (const tier of [1, 2] as const) { + const threshold = tier === 1 ? TIER1_DELTA_E : TIER2_DELTA_E; + describe(`Tier ${tier} (ΔE ≥ ${threshold})`, () => { + for (const p of DISTINGUISHABILITY_PAIRS.filter((p) => p.tier === tier)) { + test(`[${p.group}] ${p.label}`, () => { + const { worst, worstCvd, worstConvention } = worstDeltaE(p.a(roles), p.b(roles), cvds); + assert.ok( + worst >= threshold, + `${p.label} = ΔE ${worst.toFixed(1)} under ${worstCvd}/${worstConvention} (need ≥ ${threshold})` + ); + }); + } + }); + } + + // Tier 3 is advisory: reported via diagnostics so regressions stay visible + // without blocking the build. + test("Tier 3 (advisory — reported, never fails)", (t) => { + for (const p of DISTINGUISHABILITY_PAIRS.filter((p) => p.tier === 3)) { + const { worst, worstCvd, worstConvention } = worstDeltaE(p.a(roles), p.b(roles), cvds); + t.diagnostic(`[${p.group}] ${p.label} — ΔE ${worst.toFixed(1)} (${worstCvd}, ${worstConvention})`); + } + }); + }); + }); + } +}); diff --git a/test/helpers/cvd.ts b/test/helpers/cvd.ts new file mode 100644 index 0000000..76829e7 --- /dev/null +++ b/test/helpers/cvd.ts @@ -0,0 +1,111 @@ +/** + * Color-science utilities shared by the CVD accessibility gate: + * simulate a color as a dichromat sees it, measure WCAG contrast and CIEDE2000 + * separation after simulation, and cross-validate our hand-rolled math against + * culori. + */ +import { simulateCVD, contrastRatio, deltaE2000, type CVDType } from "../../src/color"; +import { + filterDeficiencyProt, filterDeficiencyDeuter, filterDeficiencyTrit, + differenceCiede2000, wcagContrast, formatHex, +} from "culori"; + +// The two common Machado gamma conventions: linear RGB (our implementation) and +// gamma-encoded sRGB (culori / the `colorspace` R package). The gate checks both. +export type SimulationConvention = "linear" | "gamma"; + +const gammaSim: Record unknown> = { + protan: filterDeficiencyProt(1) as unknown as (c: string) => unknown, + deutan: filterDeficiencyDeuter(1) as unknown as (c: string) => unknown, + tritan: filterDeficiencyTrit(1) as unknown as (c: string) => unknown, +}; + +export function simulateForConvention(hex: string, cvd: CVDType, convention: SimulationConvention): string { + if (convention === "linear") return simulateCVD(hex, cvd); + const simulated = formatHex(gammaSim[cvd](hex) as any); + if (!simulated) throw new Error(`culori could not simulate ${hex} for ${cvd}`); + return simulated; +} + +// Worst-case contrast of fg on bg after simulation, across both gamma conventions +// (the same linear + gamma pair the distinguishability check uses). +export function simulatedContrast(fg: string, bg: string, cvd: CVDType): number { + let worst = Infinity; + for (const convention of ["linear", "gamma"] as const) { + worst = Math.min( + worst, + contrastRatio(simulateForConvention(fg, cvd, convention), simulateForConvention(bg, cvd, convention)) + ); + } + return worst; +} + +// Worst-case ΔE for a pair across every CVD type a theme targets and both Machado +// gamma conventions; reports which (cvd, convention) produced the minimum. +export function worstDeltaE(aHex: string, bHex: string, cvds: CVDType[]) { + let worst = Infinity; + let worstCvd: CVDType = cvds[0]; + let worstConvention: SimulationConvention = "linear"; + for (const cvd of cvds) { + for (const convention of ["linear", "gamma"] as const) { + const d = deltaE2000( + simulateForConvention(aHex, cvd, convention), + simulateForConvention(bHex, cvd, convention) + ); + if (d < worst) { + worst = d; + worstCvd = cvd; + worstConvention = convention; + } + } + } + return { worst, worstCvd, worstConvention }; +} + +// Cross-validate our hand-rolled color math against culori (dev-only oracle). We +// keep our own implementation (its CVD simulation uses the more-correct linear-RGB +// convention), but prove the standardized formulas agree with a vetted library: +// contrast and ΔE must match to floating-point noise, and our simulation must +// collapse the same confusable axes culori's does (it differs only in gamma +// convention, by design — see src/color/cvd.ts). +export function referenceCrossChecks(): { name: string; ok: boolean; detail: string }[] { + const ciede = differenceCiede2000(); + // culori parses hex strings at runtime; its types want parsed Color objects, so + // we loosen the signatures here (dev-only oracle). + const samples = [ + "#009fff", "#d52c36", "#199f43", "#ffca00", "#1a85d4", "#d47628", + "#a13cee", "#00c5d2", "#ff5d36", "#737373", "#ffffff", "#0a0a0a", + ]; + + // contrast & ΔE: must match culori to floating-point noise. + let maxC = 0, maxDe = 0; + for (let i = 0; i < samples.length; i++) { + for (let j = i + 1; j < samples.length; j++) { + const a = samples[i], b = samples[j]; + maxC = Math.max(maxC, Math.abs(contrastRatio(a, b) - (wcagContrast(a, b) as number))); + maxDe = Math.max(maxDe, Math.abs(deltaE2000(a, b) - ciede(a, b))); + } + } + + // simulation: differs from culori only in gamma convention, but must collapse + // the same axis — verify each maps the confusable pair to a much smaller ΔE. + const axisOk = (["protan", "deutan", "tritan"] as CVDType[]).every((t) => { + const x = t === "tritan" ? "#009fff" : "#ff2e3f"; // blue (tritan) / red (protan,deutan) + const y = "#199f43"; // green + const before = deltaE2000(x, y); + const ours = deltaE2000(simulateCVD(x, t), simulateCVD(y, t)); + const lib = ciede( + simulateForConvention(x, t, "gamma"), + simulateForConvention(y, t, "gamma") + ); + // Both implementations must collapse the confusable pair to under half its + // un-simulated separation (exact residual differs by gamma convention). + return ours < before * 0.5 && lib < before * 0.5; + }); + + return [ + { name: "contrast matches culori", ok: maxC < 0.01, detail: `max |Δ| ${maxC.toFixed(4)}` }, + { name: "ΔE2000 matches culori", ok: maxDe < 0.1, detail: `max |Δ| ${maxDe.toFixed(4)}` }, + { name: "simulation collapses same axis", ok: axisOk, detail: axisOk ? "ours & culori agree" : "axis mismatch" }, + ]; +} diff --git a/test/test.ts b/test/test.ts deleted file mode 100644 index 42dca78..0000000 --- a/test/test.ts +++ /dev/null @@ -1,322 +0,0 @@ -// test/test.ts -import { readFileSync, existsSync } from "node:fs"; -import { - light as rolesLight, dark as rolesDark, - protanDeutanLight, protanDeutanDark, tritanopiaLight, tritanopiaDark, -} from "../src/roles"; -import { createTheme } from "../src/createTheme"; -import { runCvdGate } from "./cvd-test"; - -// Color tracking for detecting undefined values -const usedColors = new Set(); - -// Helper functions -function isValidHexColor(color: string): boolean { - // Match #RGB, #RRGGBB, #RRGGBBAA formats - return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color); -} - -function isValidP3Color(color: string): boolean { - // Match color(display-p3 r g b) or color(display-p3 r g b / alpha) - return /^color\(display-p3\s+[\d.]+\s+[\d.]+\s+[\d.]+(\s+\/\s+[\d.]+)?\)$/.test(color); -} - -function isValidColor(color: string): boolean { - return isValidHexColor(color) || isValidP3Color(color); -} - -function collectColors(obj: any, path = ""): string[] { - const issues: string[] = []; - - for (const [key, value] of Object.entries(obj)) { - const currentPath = path ? `${path}.${key}` : key; - - if (typeof value === "string") { - usedColors.add(value); - if (value.startsWith("#") || value.startsWith("color(")) { - if (!isValidColor(value)) { - issues.push(`Invalid color at ${currentPath}: ${value}`); - } - } - } else if (typeof value === "object" && value !== null) { - issues.push(...collectColors(value, currentPath)); - } - } - - return issues; -} - -function testThemeGeneration( - expectedName: string, - displayName: string, - themeType: "light" | "dark", - roles: any -) { - console.log(`\n🧪 Testing ${displayName}...`); - const errors: string[] = []; - - try { - const theme = createTheme({ - name: expectedName, - displayName, - type: themeType, - roles - }); - const themeMetadata = theme as { name?: string; displayName?: string }; - - // Test 1: Required properties exist - if (!themeMetadata.name) errors.push("Missing theme name"); - if (!themeMetadata.displayName) errors.push("Missing theme displayName"); - if (!theme.type) errors.push("Missing theme type"); - if (!theme.colors) errors.push("Missing colors object"); - if (!theme.tokenColors) errors.push("Missing tokenColors array"); - if (!theme.semanticTokenColors) errors.push("Missing semanticTokenColors object"); - - // Test 2: Theme metadata uses package-safe names plus display labels - if (themeMetadata.name !== expectedName) { - errors.push(`Expected name "${expectedName}" but got "${themeMetadata.name}"`); - } - if (themeMetadata.displayName !== displayName) { - errors.push(`Expected displayName "${displayName}" but got "${themeMetadata.displayName}"`); - } - - // Test 3: Type is correct - if (theme.type !== themeType) { - errors.push(`Expected type "${themeType}" but got "${theme.type}"`); - } - - // Test 4: Critical editor colors exist - const criticalColors = [ - "editor.background", - "editor.foreground", - "foreground", - "focusBorder", - "sideBar.background", - "activityBar.background", - "statusBar.background" - ]; - - for (const key of criticalColors) { - if (!theme.colors[key]) { - errors.push(`Missing critical color: ${key}`); - } - } - - // Test 5: Validate all color values - const colorIssues = collectColors(theme.colors); - errors.push(...colorIssues); - - // Test 6: Check for undefined/null values in colors - for (const [key, value] of Object.entries(theme.colors)) { - if (value === undefined || value === null) { - errors.push(`Color "${key}" is ${value}`); - } - } - - // Test 7: TokenColors should be an array with entries - if (!Array.isArray(theme.tokenColors)) { - errors.push("tokenColors is not an array"); - } else if (theme.tokenColors.length === 0) { - errors.push("tokenColors array is empty"); - } - - // Test 8: Validate tokenColors structure - theme.tokenColors.forEach((token, idx) => { - if (!token.scope) { - errors.push(`tokenColors[${idx}] missing scope`); - } - if (!token.settings) { - errors.push(`tokenColors[${idx}] missing settings`); - } else if (token.settings.foreground) { - usedColors.add(token.settings.foreground); - if (!isValidColor(token.settings.foreground)) { - errors.push(`tokenColors[${idx}] has invalid foreground color: ${token.settings.foreground}`); - } - } - }); - - // Test 9: Semantic tokens validation - for (const [key, value] of Object.entries(theme.semanticTokenColors)) { - if (typeof value === "string") { - usedColors.add(value); - if (!isValidColor(value)) { - errors.push(`semanticTokenColors["${key}"] has invalid color: ${value}`); - } - } else if (typeof value === "object" && value !== null) { - const semanticValue = value as any; - if (semanticValue.foreground) { - usedColors.add(semanticValue.foreground); - if (!isValidColor(semanticValue.foreground)) { - errors.push(`semanticTokenColors["${key}"].foreground has invalid color: ${semanticValue.foreground}`); - } - } - } - } - - if (errors.length === 0) { - console.log(`✅ ${displayName} passed all checks`); - return true; - } else { - console.error(`❌ ${displayName} failed with ${errors.length} error(s):`); - errors.forEach(err => console.error(` - ${err}`)); - return false; - } - } catch (error) { - console.error(`❌ ${displayName} threw an error:`, error); - return false; - } -} - -function testGeneratedFiles() { - console.log("\n🧪 Testing generated theme files..."); - const errors: string[] = []; - - const files = [ - { path: "themes/pierre-light.json", expectedType: "light", expectedName: "pierre-light", expectedDisplayName: "Pierre Light" }, - { path: "themes/pierre-light-protanopia-deuteranopia.json", expectedType: "light", expectedName: "pierre-light-protanopia-deuteranopia", expectedDisplayName: "Pierre Light Protanopia & Deuteranopia" }, - { path: "themes/pierre-light-soft.json", expectedType: "light", expectedName: "pierre-light-soft", expectedDisplayName: "Pierre Light Soft" }, - { path: "themes/pierre-light-tritanopia.json", expectedType: "light", expectedName: "pierre-light-tritanopia", expectedDisplayName: "Pierre Light Tritanopia" }, - { path: "themes/pierre-light-vibrant.json", expectedType: "light", expectedName: "pierre-light-vibrant", expectedDisplayName: "Pierre Light Vibrant" }, - { path: "themes/pierre-dark.json", expectedType: "dark", expectedName: "pierre-dark", expectedDisplayName: "Pierre Dark" }, - { path: "themes/pierre-dark-protanopia-deuteranopia.json", expectedType: "dark", expectedName: "pierre-dark-protanopia-deuteranopia", expectedDisplayName: "Pierre Dark Protanopia & Deuteranopia" }, - { path: "themes/pierre-dark-soft.json", expectedType: "dark", expectedName: "pierre-dark-soft", expectedDisplayName: "Pierre Dark Soft" }, - { path: "themes/pierre-dark-tritanopia.json", expectedType: "dark", expectedName: "pierre-dark-tritanopia", expectedDisplayName: "Pierre Dark Tritanopia" }, - { path: "themes/pierre-dark-vibrant.json", expectedType: "dark", expectedName: "pierre-dark-vibrant", expectedDisplayName: "Pierre Dark Vibrant" } - ]; - - for (const { path, expectedType, expectedName, expectedDisplayName } of files) { - // Test 1: File exists - if (!existsSync(path)) { - errors.push(`File does not exist: ${path}`); - continue; - } - - try { - // Test 2: File is valid JSON - const content = readFileSync(path, "utf8"); - if (content.trim() === "") { - errors.push(`File is empty: ${path}`); - continue; - } - - const theme = JSON.parse(content); - - // Test 3: Has required structure - if (!theme.name) errors.push(`${path}: Missing name`); - if (!theme.displayName) errors.push(`${path}: Missing displayName`); - if (theme.name !== expectedName) { - errors.push(`${path}: Expected name "${expectedName}" but got "${theme.name}"`); - } - if (theme.displayName !== expectedDisplayName) { - errors.push(`${path}: Expected displayName "${expectedDisplayName}" but got "${theme.displayName}"`); - } - if (!theme.type) errors.push(`${path}: Missing type`); - if (theme.type !== expectedType) { - errors.push(`${path}: Expected type "${expectedType}" but got "${theme.type}"`); - } - if (!theme.colors || Object.keys(theme.colors).length === 0) { - errors.push(`${path}: Missing or empty colors object`); - } - if (!Array.isArray(theme.tokenColors) || theme.tokenColors.length === 0) { - errors.push(`${path}: Missing or empty tokenColors array`); - } - - console.log(`✅ ${path} is valid`); - } catch (error) { - errors.push(`${path}: Invalid JSON - ${error}`); - } - } - - if (errors.length > 0) { - console.error(`❌ Generated files validation failed:`); - errors.forEach(err => console.error(` - ${err}`)); - return false; - } - - console.log("✅ All generated files are valid"); - return true; -} - -function testPaletteRoles() { - console.log("\n🧪 Testing palette roles..."); - const errors: string[] = []; - - function validateRoles(roles: any, name: string) { - // Check all required role categories exist - const requiredCategories = ["bg", "fg", "border", "accent", "states", "syntax", "ansi"]; - for (const category of requiredCategories) { - if (!roles[category]) { - errors.push(`${name}: Missing "${category}" category`); - } - } - - // Validate that all role values are hex colors - function checkRoleColors(obj: any, path: string) { - for (const [key, value] of Object.entries(obj)) { - const fullPath = `${path}.${key}`; - if (typeof value === "string") { - if (!isValidHexColor(value)) { - errors.push(`${name}.${fullPath}: Invalid color "${value}"`); - } - } else if (typeof value === "object" && value !== null) { - checkRoleColors(value, fullPath); - } - } - } - - checkRoleColors(roles, name); - } - - validateRoles(rolesLight, "light"); - validateRoles(protanDeutanLight, "protanDeutanLight"); - validateRoles(tritanopiaLight, "tritanopiaLight"); - validateRoles(rolesDark, "dark"); - validateRoles(protanDeutanDark, "protanDeutanDark"); - validateRoles(tritanopiaDark, "tritanopiaDark"); - - if (errors.length > 0) { - console.error(`❌ Palette roles validation failed:`); - errors.forEach(err => console.error(` - ${err}`)); - return false; - } - - console.log("✅ Palette roles are valid"); - return true; -} - -// Run all tests -console.log("🚀 Running Pierre Theme Tests\n"); -console.log("=" .repeat(50)); - -let allPassed = true; - -// Test palette roles first -allPassed = testPaletteRoles() && allPassed; - -// Test theme generation (light variants, then dark; CVD distinguishability is -// handled by the gate below) -allPassed = testThemeGeneration("pierre-light", "Pierre Light", "light", rolesLight) && allPassed; -allPassed = testThemeGeneration("pierre-light-protanopia-deuteranopia", "Pierre Light Protanopia & Deuteranopia", "light", protanDeutanLight) && allPassed; -allPassed = testThemeGeneration("pierre-light-tritanopia", "Pierre Light Tritanopia", "light", tritanopiaLight) && allPassed; -allPassed = testThemeGeneration("pierre-dark", "Pierre Dark", "dark", rolesDark) && allPassed; -allPassed = testThemeGeneration("pierre-dark-protanopia-deuteranopia", "Pierre Dark Protanopia & Deuteranopia", "dark", protanDeutanDark) && allPassed; -allPassed = testThemeGeneration("pierre-dark-tritanopia", "Pierre Dark Tritanopia", "dark", tritanopiaDark) && allPassed; - -// Test generated files (only if they exist - they should after build) -allPassed = testGeneratedFiles() && allPassed; - -// CVD objective gate (Machado-2009 simulation + WCAG contrast + CIEDE2000) — -// hard gate: fails the build if any CVD theme regresses into ambiguity. -allPassed = runCvdGate() && allPassed; - -// Summary -console.log("\n" + "=".repeat(50)); -console.log(`\n📊 Total unique colors used: ${usedColors.size}`); - -if (allPassed) { - console.log("\n✅ All tests passed!"); - process.exit(0); -} else { - console.log("\n❌ Some tests failed!"); - process.exit(1); -} diff --git a/test/theme.test.ts b/test/theme.test.ts new file mode 100644 index 0000000..3b3fb60 --- /dev/null +++ b/test/theme.test.ts @@ -0,0 +1,235 @@ +/** + * Structural validation for the themes: it proves that createTheme produces + * well-formed VS Code themes and that the palette role tables are valid hex. + */ +import { describe, test, after } from "node:test"; +import assert from "node:assert/strict"; +import { + dark as rolesDark, + light as rolesLight, + protanDeutanDark as rolesProtanDeutanDark, + protanDeutanLight as rolesProtanDeutanLight, + tritanopiaDark as rolesTritanopiaDark, + tritanopiaLight as rolesTritanopiaLight, + type Roles, +} from "../src/roles"; +import { createTheme } from "../src/createTheme"; + +// ── Color-string validators (shared across the theme / token / semantic checks) ── +function isValidHexColor(color: string): boolean { + // Match #RGB, #RRGGBB, #RRGGBBAA formats + return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color); +} + +function isValidColor(color: string): boolean { + // Hex, or a Display-P3 color() — color(display-p3 r g b) with optional / alpha. + return ( + isValidHexColor(color) || + /^color\(display-p3\s+[\d.]+\s+[\d.]+\s+[\d.]+(\s+\/\s+[\d.]+)?\)$/.test(color) + ); +} + +// Every color string the suite touches — surfaced as a diagnostic at the end so +// the palette's breadth stays visible (the old runner printed this total). +const usedColors = new Set(); + +// ── Palette roles ───────────────────────────────────────────────────────────── +const REQUIRED_ROLE_CATEGORIES = ["bg", "fg", "border", "accent", "states", "syntax", "ansi"]; + +const ROLE_SETS: [string, Roles][] = [ + ["dark", rolesDark], + ["light", rolesLight], + ["protanDeutanDark", rolesProtanDeutanDark], + ["protanDeutanLight", rolesProtanDeutanDark], + ["tritanopiaDark", rolesTritanopiaDark], + ["tritanopiaLight", rolesTritanopiaLight], +]; + +describe("palette roles", () => { + for (const [name, roles] of ROLE_SETS) { + describe(name, () => { + test("defines every required role category", () => { + for (const category of REQUIRED_ROLE_CATEGORIES) { + assert.ok((roles as Record)[category], `${name}: missing "${category}" category`); + } + }); + + test("uses only valid hex colors", () => { + // Collect every role-table path whose value is not valid hex. Role tables + // are authored in hex; P3 conversion happens later, at build time. + const issues: string[] = []; + const collectRoleHexIssues = (obj: unknown, path: string) => { + for (const [key, value] of Object.entries(obj as Record)) { + const fullPath = `${path}.${key}`; + if (typeof value === "string") { + if (!isValidHexColor(value)) issues.push(`${fullPath}: invalid color "${value}"`); + } else if (value && typeof value === "object") { + collectRoleHexIssues(value, fullPath); + } + } + }; + collectRoleHexIssues(roles, name); + assert.deepEqual(issues, [], issues.join("\n")); + }); + }); + } +}); + +// ── Theme generation ────────────────────────────────────────────────────────── +const CRITICAL_COLORS = [ + "editor.background", + "editor.foreground", + "foreground", + "focusBorder", + "sideBar.background", + "activityBar.background", + "statusBar.background", +]; + +type ThemeVariant = { name: string; displayName: string; type: "light" | "dark"; roles: Roles }; + +// CVD distinguishability is handled by the gate in cvd.test.ts; here we only +// validate the structure of each generated light/dark variant. +const THEME_VARIANTS: ThemeVariant[] = [ + { + name: "pierre-dark", + displayName: "Pierre Dark", + type: "dark", + roles: rolesDark + }, + { + name: "pierre-dark-protanopia-deuteranopia", + displayName: "Pierre Dark Protanopia & Deuteranopia", + type: "dark", + roles: rolesProtanDeutanDark + }, + { + name: "pierre-dark-tritanopia", + displayName: "Pierre Dark Tritanopia", + type: "dark", + roles: rolesTritanopiaDark + }, + { + name: "pierre-light", + displayName: "Pierre Light", + type: "light", + roles: rolesLight + }, + { + name: "pierre-light-protanopia-deuteranopia", + displayName: "Pierre Light Protanopia & Deuteranopia", + type: "light", + roles: rolesProtanDeutanLight + }, + { + name: "pierre-light-tritanopia", + displayName: "Pierre Light Tritanopia", + type: "light", + roles: rolesTritanopiaLight + }, +]; + +describe("theme generation", () => { + for (const variant of THEME_VARIANTS) { + describe(variant.displayName, () => { + const theme = createTheme({ + name: variant.name, + displayName: variant.displayName, + type: variant.type, + roles: variant.roles, + }); + + test("has the required top-level properties", () => { + assert.ok(theme.name, "missing theme name"); + assert.ok(theme.displayName, "missing theme displayName"); + assert.ok(theme.type, "missing theme type"); + assert.ok(theme.colors, "missing colors object"); + assert.ok(theme.tokenColors, "missing tokenColors array"); + assert.ok(theme.semanticTokenColors, "missing semanticTokenColors object"); + }); + + test("uses the expected package-safe name and display label", () => { + assert.equal(theme.name, variant.name); + assert.equal(theme.displayName, variant.displayName); + }); + + test(`has type "${variant.type}"`, () => { + assert.equal(theme.type, variant.type); + }); + + test("defines the critical editor colors", () => { + for (const key of CRITICAL_COLORS) { + assert.ok(theme.colors[key], `missing critical color: ${key}`); + } + }); + + test("has only valid color values", () => { + // Walk the colors tree: record every color string (for the unique-color + // tally) and flag any malformed #hex / color() value. + const issues: string[] = []; + const collectColorIssues = (obj: unknown, path: string) => { + for (const [key, value] of Object.entries(obj as Record)) { + const currentPath = path ? `${path}.${key}` : key; + if (typeof value === "string") { + usedColors.add(value); + if ((value.startsWith("#") || value.startsWith("color(")) && !isValidColor(value)) { + issues.push(`Invalid color at ${currentPath}: ${value}`); + } + } else if (value && typeof value === "object") { + collectColorIssues(value, currentPath); + } + } + }; + collectColorIssues(theme.colors, ""); + assert.deepEqual(issues, [], issues.join("\n")); + }); + + test("has no undefined or null colors", () => { + for (const [key, value] of Object.entries(theme.colors)) { + assert.ok(value !== undefined && value !== null, `color "${key}" is ${value}`); + } + }); + + test("tokenColors is a non-empty array", () => { + assert.ok(Array.isArray(theme.tokenColors), "tokenColors is not an array"); + assert.ok(theme.tokenColors.length > 0, "tokenColors array is empty"); + }); + + test("every tokenColors entry is well-formed", () => { + theme.tokenColors.forEach((token, idx) => { + assert.ok(token.scope, `tokenColors[${idx}] missing scope`); + assert.ok(token.settings, `tokenColors[${idx}] missing settings`); + if (token.settings?.foreground) { + usedColors.add(token.settings.foreground); + assert.ok( + isValidColor(token.settings.foreground), + `tokenColors[${idx}] has invalid foreground color: ${token.settings.foreground}` + ); + } + }); + }); + + test("every semanticTokenColors entry is valid", () => { + for (const [key, value] of Object.entries(theme.semanticTokenColors)) { + if (typeof value === "string") { + usedColors.add(value); + assert.ok(isValidColor(value), `semanticTokenColors["${key}"] has invalid color: ${value}`); + } else if (typeof value === "object" && value !== null) { + const foreground = (value as { foreground?: string }).foreground; + if (foreground) { + usedColors.add(foreground); + assert.ok( + isValidColor(foreground), + `semanticTokenColors["${key}"].foreground has invalid color: ${foreground}` + ); + } + } + } + }); + }); + } +}); + +after(() => { + console.log(`📊 Total unique colors used: ${usedColors.size}`); +}); From d52cf080d718732c5555e4461335bb0146e4b764 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher <239676+necolas@users.noreply.github.com> Date: Mon, 15 Jun 2026 15:58:07 -0700 Subject: [PATCH 6/6] chore: update VS Code tasks --- .vscode/tasks.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d161c23..7fe8652 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,14 +3,14 @@ "tasks": [ { "type": "npm", - "script": "start", + "script": "dev", "group": { "kind": "build", "isDefault": true }, "problemMatcher": [], - "label": "npm: start", - "detail": "nodemon --watch src src/index.js" + "label": "npm: dev", + "detail": "nodemon --watch src --watch scripts --ext ts --exec \"npm run build && npm run preview\"" }, { "type": "npm", @@ -18,7 +18,7 @@ "group": "build", "problemMatcher": [], "label": "npm: build", - "detail": "node src/index.js" + "detail": "ts-node scripts/build.ts" } ] }