From 084bbcaa91d4f58cd69449065add47d45dcf2ad8 Mon Sep 17 00:00:00 2001 From: Jonas Kunert Date: Wed, 22 Jul 2026 21:52:50 +0200 Subject: [PATCH] feat: add colorblind-friendly color preset for chat messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Message types are distinguished purely by hue (user blue #40a5ff, tool blue-purple #7c8bed, claude green #2ecc71, tool-result teal #1cc08c, thinking purple #ba55d3). For color vision deficiency these collapse into indistinguishable pairs — #41: 'the colour used for You is indistinguishable to me from the colour used for task or glob or read' — blue vs purple and green vs teal being classic confusion pairs. - new setting claudeCodeChat.ui.colorblindMode (boolean, default off): contributes entry, settingsData push, updateSettings write-back and a checkbox in the gear modal (new Appearance group), - when enabled, body.colorblind-mode re-colors all six message-type carriers (left border, ::before bar, icon gradients) with a green- and purple-free palette on the blue-orange and blue-yellow axes (user #4aa3ff, tool #e58a00, claude #1ec8d4, tool-result #ffd21a, error #ff4f3b, thinking neutral gray #9aa0a6), luminance-laddered so the ladder survives even full monochromacy, - off by default, zero impact while disabled. Co-Authored-By: Claude Fable 5 --- package.json | 5 +++++ src/extension.ts | 1 + src/script.ts | 13 ++++++++----- src/ui-styles.ts | 28 ++++++++++++++++++++++++++++ src/ui.ts | 12 ++++++++++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6bcdf89..9b6d553 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,11 @@ "type": "boolean", "default": false, "description": "Enable the local router to convert OpenAI format to Anthropic format. Required for providers that use OpenAI-compatible APIs." + }, + "claudeCodeChat.ui.colorblindMode": { + "type": "boolean", + "default": false, + "description": "Use a colorblind-friendly high-contrast color preset for chat message colors." } } } diff --git a/src/extension.ts b/src/extension.ts index 8fa37fb..a4facf2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3400,6 +3400,7 @@ class ClaudeChatProvider { 'executable.path': config.get('executable.path', ''), 'environment.variables': config.get>('environment.variables', {}), 'environment.disabled': config.get('environment.disabled', false), + 'ui.colorblindMode': config.get('ui.colorblindMode', false), 'isOpenCredits': this._isOpenCredits() }; diff --git a/src/script.ts b/src/script.ts index 4c949e2..186b22d 100644 --- a/src/script.ts +++ b/src/script.ts @@ -397,10 +397,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt headerDiv.className = 'message-header'; const iconDiv = document.createElement('div'); - iconDiv.className = data.isError ? 'message-icon error' : 'message-icon'; - iconDiv.style.background = data.isError ? - 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)' : - 'linear-gradient(135deg, #1cc08c 0%, #16a974 100%)'; + iconDiv.className = data.isError ? 'message-icon result-error' : 'message-icon result-success'; iconDiv.textContent = data.isError ? '❌' : '✅'; const labelDiv = document.createElement('div'); @@ -4863,6 +4860,7 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt const yoloMode = document.getElementById('yolo-mode').checked; const executablePath = document.getElementById('executable-path').value; const useRouter = document.getElementById('use-router')?.checked || false; + const colorblindMode = document.getElementById('colorblind-mode').checked; // Collect environment variables from key-value UI const envVariables = getEnvVariablesFromUI(); @@ -4902,7 +4900,8 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt 'permissions.yoloMode': yoloMode, 'executable.path': executablePath, 'environment.variables': envVariables, - 'router.enabled': useRouter + 'router.enabled': useRouter, + 'ui.colorblindMode': colorblindMode } }); } @@ -5159,6 +5158,10 @@ const getScript = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'htt }); } else if (message.type === 'settingsData') { // Update UI with current settings + document.body.classList.toggle('colorblind-mode', !!message.data['ui.colorblindMode']); + document.getElementById('colorblind-mode').checked = !!message.data['ui.colorblindMode']; + + const thinkingIntensity = message.data['thinking.intensity'] || 'think'; const intensityValues = ['think', 'think-hard', 'think-harder', 'ultrathink']; const sliderValue = intensityValues.indexOf(thinkingIntensity); diff --git a/src/ui-styles.ts b/src/ui-styles.ts index 76bd766..17d22a0 100644 --- a/src/ui-styles.ts +++ b/src/ui-styles.ts @@ -1171,6 +1171,14 @@ const styles = ` background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); } + .message-icon.result-success { + background: linear-gradient(135deg, #1cc08c 0%, #16a974 100%); + } + + .message-icon.result-error { + background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); + } + .message-label { font-weight: 500; font-size: 12px; @@ -1179,6 +1187,26 @@ const styles = ` letter-spacing: 0.5px; } + /* Colorblind preset: green- and purple-free palette, luminance-laddered */ + body.colorblind-mode .message.user { border-color: rgba(74, 163, 255, 0.55); } + body.colorblind-mode .message.user::before { background: linear-gradient(180deg, #4aa3ff 0%, #0f6fd0 100%); } + body.colorblind-mode .message.claude { border-color: rgba(30, 200, 212, 0.5); } + body.colorblind-mode .message.claude::before { background: linear-gradient(180deg, #1ec8d4 0%, #0f8f99 100%); } + body.colorblind-mode .message.error { border-color: rgba(255, 79, 59, 0.55); } + body.colorblind-mode .message.error::before { background: linear-gradient(180deg, #ff4f3b 0%, #cc2718 100%); } + body.colorblind-mode .message.tool { border-color: rgba(229, 138, 0, 0.55); } + body.colorblind-mode .message.tool::before { background: linear-gradient(180deg, #e58a00 0%, #b36b00 100%); } + body.colorblind-mode .message.tool-result { border-color: rgba(255, 210, 26, 0.55); } + body.colorblind-mode .message.tool-result::before { background: linear-gradient(180deg, #ffd21a 0%, #d9b000 100%); } + body.colorblind-mode .message.thinking { border-color: rgba(154, 160, 166, 0.5); } + body.colorblind-mode .message.thinking::before { background: linear-gradient(180deg, #9aa0a6 0%, #676c71 100%); } + body.colorblind-mode .message-icon.user { background: linear-gradient(135deg, #4aa3ff 0%, #0f6fd0 100%); } + body.colorblind-mode .message-icon.claude { background: linear-gradient(135deg, #1ec8d4 0%, #0f8f99 100%); } + body.colorblind-mode .message-icon.error { background: linear-gradient(135deg, #ff4f3b 0%, #cc2718 100%); } + body.colorblind-mode .message-icon.result-success { background: linear-gradient(135deg, #ffd21a 0%, #d9b000 100%); } + body.colorblind-mode .message-icon.result-error { background: linear-gradient(135deg, #ff4f3b 0%, #cc2718 100%); } + body.colorblind-mode .tool-icon { background: linear-gradient(135deg, #e58a00 0%, #b36b00 100%); } + .message-content { padding-left: 6px; } diff --git a/src/ui.ts b/src/ui.ts index 4ffcab7..cf1a185 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -414,6 +414,18 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https

+

Appearance

+
+
+ + +
+

+ High-contrast palette distinguishable with color vision deficiency +

+
+ +