Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/review-editor/hooks/usePierreTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const SHIKI_THEME_MAP: Record<string, { dark: string | null; light: strin
'aurora-x': { dark: 'aurora-x', light: null },
'ayu-dark': { dark: 'ayu-dark', light: null },
'catppuccin': { dark: 'catppuccin-mocha', light: 'catppuccin-latte' },
'colorblind': { dark: 'pierre-dark-protanopia-deuteranopia', light: 'pierre-light-protanopia-deuteranopia' },
'dark-plus': { dark: 'dark-plus', light: 'light-plus' },
'dracula': { dark: 'dracula', light: null },
'everforest': { dark: 'everforest-dark', light: 'everforest-light' },
Expand Down
13 changes: 13 additions & 0 deletions packages/ui/components/ThemeProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ describe('theme mode catalog', () => {
});
});

describe('theme registry', () => {
test('keeps ids unique and registers the colorblind palette for both modes', () => {
const ids = BUILT_IN_THEMES.map(({ id }) => id);
expect(new Set(ids).size).toBe(ids.length);

const theme = BUILT_IN_THEMES.find(candidate => candidate.id === 'colorblind');
if (!theme) throw new Error('colorblind palette is not registered');
expect(theme.modeSupport).toBe('both');
expect(theme.syntaxHighlighting).toBe(true);
expect(theme.colors.dark.background).not.toBe(theme.colors.light.background);
});
});

describe('ThemeProvider', () => {
beforeEach(() => {
if (hasDom) {
Expand Down
1 change: 1 addition & 0 deletions packages/ui/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
@import "./themes/cursor.css";
@import "./themes/cursor-hc.css";
@import "./themes/cursor-midnight.css";
@import "./themes/colorblind.css";
@import "./themes/everforest.css";
@import "./themes/everforest-hard.css";
@import "./themes/everforest-soft.css";
Expand Down
191 changes: 191 additions & 0 deletions packages/ui/themes/colorblind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* Colorblind — tuned for red-green color vision deficiency (protanopia /
* deuteranopia, the common ~95% of CVD). Additions are blue, deletions are
* orange: the pair differs along the blue-yellow axis that survives red-green
* dichromacy. Diff pair verified with Machado et al. (2009) severity-1.0
* simulation: simulated add/del line backgrounds stay at CIEDE2000 >= 8.9
* across every diffLineBgIntensity (subtle/normal/strong) in both modes, and
* the theme foreground keeps >= 4.7:1 WCAG contrast over the strongest tint.
*
* --diffs-addition-color-override / --diffs-deletion-color-override are
* @pierre/diffs' theme-override entry points; custom properties inherit into
* the diff shadow DOM, so defining them here recolors the review editor's
* line backgrounds, gutter +/- signs, indicator bars, and per-file counts
* without any app-code changes. Plan-diff views and file-tree +/- follow
* --success / --destructive, which map to the same pair. */
.theme-colorblind {
--background: #16181d;
--foreground: #eaedf2;
--card: #1c1f26;
--card-foreground: #eaedf2;
--popover: #22262e;
--popover-foreground: #eaedf2;
--primary: #7fb0f2;
--primary-foreground: #10131a;
--secondary: #2a2e37;
--secondary-foreground: #dfe3ea;
--muted: #232730;
--muted-foreground: #a7aeba;
--accent: #e5b84a;
--accent-foreground: #16181d;
--destructive: #c96e12;
--destructive-foreground: #16181d;
--border: #343945;
--input: #232730;
--ring: #7fb0f2;
--success: #4f83d1;
--success-foreground: #10131a;
--warning: #d9a839;
--warning-foreground: #16181d;

--font-sans: 'Inter Variable', 'Inter', system-ui, sans-serif;
--font-mono: 'Geist Mono Variable', 'Geist Mono', 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
--radius: 0.625rem;

--code-bg: #1d212a;
--focus-highlight: #35b6d9;

/* Review-editor diff pair (Pierre pipeline). */
--diffs-addition-color-override: #4f83d1;
--diffs-deletion-color-override: #c96e12;
}

.theme-colorblind.light {
--background: #f8f9fb;
--foreground: #22262e;
--card: #ffffff;
--card-foreground: #22262e;
--popover: #ffffff;
--popover-foreground: #22262e;
--primary: #1a63b8;
--primary-foreground: #ffffff;
--secondary: #e7eaef;
--secondary-foreground: #333a45;
--muted: #eceef3;
--muted-foreground: #565e6b;
--accent: #946300;
--accent-foreground: #ffffff;
--destructive: #a55c1e;
--destructive-foreground: #ffffff;
--border: #d8dce3;
--input: #eceef3;
--ring: #1a63b8;
--success: #216cab;
--success-foreground: #ffffff;
--warning: #8f6400;
--warning-foreground: #ffffff;

--code-bg: #eef0f4;
--focus-highlight: #0e7d9e;

--diffs-addition-color-override: #216cab;
--diffs-deletion-color-override: #a55c1e;
}

/* Plan-editor code blocks (bundled highlight.js, github-dark base stylesheet).
* Token palette mirrors @pierre/theme's protanopia-deuteranopia shiki themes:
* purples/blues for keywords, blue for strings and numbers, orange reserved
* for variables/tags — no red-vs-green token pairs anywhere. !important is
* required to beat the app-level `.light .hljs-*` override rules. */
.theme-colorblind .hljs-keyword,
.theme-colorblind .hljs-selector-tag,
.theme-colorblind .hljs-doctag {
color: #b969f3 !important;
}
.theme-colorblind .hljs-string,
.theme-colorblind .hljs-quote,
.theme-colorblind .hljs-regexp {
color: #97c4ff !important;
}
.theme-colorblind .hljs-number,
.theme-colorblind .hljs-literal,
.theme-colorblind .hljs-symbol,
.theme-colorblind .hljs-bullet {
color: #96d9f6 !important;
}
.theme-colorblind .hljs-title,
.theme-colorblind .hljs-section,
.theme-colorblind .hljs-function,
.theme-colorblind .hljs-class .hljs-title {
color: #ba8ffd !important;
}
.theme-colorblind .hljs-type,
.theme-colorblind .hljs-built_in,
.theme-colorblind .hljs-builtin-name,
.theme-colorblind .hljs-selector-class,
.theme-colorblind .hljs-selector-pseudo {
color: #e290f0 !important;
}
.theme-colorblind .hljs-variable,
.theme-colorblind .hljs-template-variable,
.theme-colorblind .hljs-attr,
.theme-colorblind .hljs-attribute,
.theme-colorblind .hljs-name,
.theme-colorblind .hljs-tag,
.theme-colorblind .hljs-selector-id,
.theme-colorblind .hljs-template-tag {
color: #ffa359 !important;
}
.theme-colorblind .hljs-comment,
.theme-colorblind .hljs-meta {
color: #9198a1 !important;
}
.theme-colorblind .hljs-addition {
color: #97c4ff !important;
background-color: rgb(79 131 209 / 0.18) !important;
}
.theme-colorblind .hljs-deletion {
color: #ffa359 !important;
background-color: rgb(201 110 18 / 0.18) !important;
}

.theme-colorblind.light .hljs-keyword,
.theme-colorblind.light .hljs-selector-tag,
.theme-colorblind.light .hljs-doctag {
color: #8836c7 !important;
}
.theme-colorblind.light .hljs-string,
.theme-colorblind.light .hljs-quote,
.theme-colorblind.light .hljs-regexp {
color: #215584 !important;
}
.theme-colorblind.light .hljs-number,
.theme-colorblind.light .hljs-literal,
.theme-colorblind.light .hljs-symbol,
.theme-colorblind.light .hljs-bullet {
color: #2182a1 !important;
}
.theme-colorblind.light .hljs-title,
.theme-colorblind.light .hljs-section,
.theme-colorblind.light .hljs-function,
.theme-colorblind.light .hljs-class .hljs-title {
color: #5731a7 !important;
}
.theme-colorblind.light .hljs-type,
.theme-colorblind.light .hljs-built_in,
.theme-colorblind.light .hljs-builtin-name,
.theme-colorblind.light .hljs-selector-class,
.theme-colorblind.light .hljs-selector-pseudo {
color: #a631be !important;
}
.theme-colorblind.light .hljs-variable,
.theme-colorblind.light .hljs-template-variable,
.theme-colorblind.light .hljs-attr,
.theme-colorblind.light .hljs-attribute,
.theme-colorblind.light .hljs-name,
.theme-colorblind.light .hljs-tag,
.theme-colorblind.light .hljs-selector-id,
.theme-colorblind.light .hljs-template-tag {
color: #ac6023 !important;
}
.theme-colorblind.light .hljs-comment,
.theme-colorblind.light .hljs-meta {
color: #6b7280 !important;
}
.theme-colorblind.light .hljs-addition {
color: #215584 !important;
background-color: rgb(33 108 171 / 0.12) !important;
}
.theme-colorblind.light .hljs-deletion {
color: #ac6023 !important;
background-color: rgb(165 92 30 / 0.12) !important;
}
11 changes: 11 additions & 0 deletions packages/ui/utils/themeRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ export const BUILT_IN_THEMES: ThemeInfo[] = [
light: { primary: '#3C7CAB', secondary: '#E8E8E8', accent: '#4C7F8C', background: '#FCFCFC', foreground: '#141414' },
},
},
{
id: 'colorblind',
name: 'Colorblind',
builtIn: true,
modeSupport: 'both',
syntaxHighlighting: true,
colors: {
dark: { primary: '#7fb0f2', secondary: '#2a2e37', accent: '#e5b84a', background: '#16181d', foreground: '#eaedf2' },
light: { primary: '#1a63b8', secondary: '#e7eaef', accent: '#946300', background: '#f8f9fb', foreground: '#22262e' },
},
},
{
id: 'dark-plus',
name: 'Dark+',
Expand Down