diff --git a/scripts/e2e-settings-visual-theme.test.mjs b/scripts/e2e-settings-visual-theme.test.mjs index 34c49c7..0871c1f 100644 --- a/scripts/e2e-settings-visual-theme.test.mjs +++ b/scripts/e2e-settings-visual-theme.test.mjs @@ -22,6 +22,7 @@ test('theme registry exposes the requested Monaco themes and safe fallback', () assert.equal(DEFAULT_VISUAL_THEME_ID, 'browser-cpp-dark'); assert.deepEqual(VISUAL_THEMES.map(({ id }) => id), [ 'browser-cpp-dark', 'vs', 'vs-dark', 'hc-light', 'hc-black', + 'monochrome-light', 'monochrome-dark', ]); assert.equal(getVisualTheme('not-a-theme').id, DEFAULT_VISUAL_THEME_ID); for (const theme of VISUAL_THEMES) { @@ -34,10 +35,25 @@ test('theme registry exposes the requested Monaco themes and safe fallback', () } assert.deepEqual(VISUAL_THEMES.map(({ id }) => id), [ 'browser-cpp-dark', 'vs', 'vs-dark', 'hc-light', 'hc-black', + 'monochrome-light', 'monochrome-dark', ]); assert.deepEqual(VISUAL_THEMES.slice(1).map(({ monacoTheme }) => monacoTheme), [ 'browser-cpp-vs', 'browser-cpp-vs-dark', 'browser-cpp-hc-light', 'browser-cpp-hc-black', + 'browser-cpp-monochrome-light', 'browser-cpp-monochrome-dark', ]); + + for (const theme of VISUAL_THEMES.slice(-2)) { + const colors = [ + ...Object.values(theme.cssTokens), + ...Object.values(theme.terminalTheme), + ...Object.values(theme.preview), + theme.selection.foreground, + theme.selection.background, + theme.selection.inactiveBackground, + ].filter((value) => typeof value === 'string' && value.startsWith('#')); + assert.ok(colors.length > 0); + assert.ok(colors.every(isGrayscale), `${theme.id} contains a chromatic color`); + } }); test('terminal viewport inherits the active visual theme background', async () => { @@ -50,10 +66,31 @@ test('terminal viewport inherits the active visual theme background', async () = ); assert.deepEqual( [...css.matchAll(/--terminal-background:\s*(#[\da-f]{3,6});/gi)].map(([, color]) => color.toLowerCase()), - ['#11111b', '#fff', '#181818', '#fff', '#000'] + ['#11111b', '#fff', '#181818', '#fff', '#000', '#ddd', '#222'] ); }); +test('monochrome themes separate panels and outline buttons', async () => { + const css = await readFile(new URL('../src/ui/styles.css', import.meta.url), 'utf8'); + assert.match(css, /--panel-explorer:#ddd/); + assert.match(css, /--panel-editor:#fff/); + assert.match(css, /--panel-terminal:#ddd/); + assert.match(css, /--panel-explorer:#222/); + assert.match(css, /--panel-editor:#000/); + assert.match(css, /--panel-terminal:#222/); + assert.match(css, /--button-border:#666/); + assert.match(css, /--button-border:#999/); + assert.match(css, /:root\[data-visual-theme="monochrome-light"\][\s\S]*button[\s\S]*border-color: var\(--button-border\);/); + assert.match(css, /:root\[data-visual-theme="monochrome-dark"\][\s\S]*button[\s\S]*border-color: var\(--button-border\);/); + assert.match(css, /\.panel-header-btn[\s\S]*border: none;/); + assert.match(css, /\.tab-close[\s\S]*border: none;/); + assert.match(css, /\.panel-controls button[\s\S]*border: none;/); + assert.match(css, /monochrome-light[^}]*\.panel-header-btn[\s\S]*border: 1px solid var\(--button-border\);/); + assert.match(css, /monochrome-light[^}]*\.tab[\s\S]*border: 1px solid var\(--panel-border\);/); + assert.match(css, /monochrome-dark[^}]*\.tab\.active[\s\S]*border: 1px solid var\(--panel-border\);/); + assert.match(css, /monochrome-dark[^}]*border-color: var\(--blue\);/); +}); + function contrastRatio(foreground, background) { const luminance = (color) => { const channels = color.match(/[0-9a-f]{2}/gi).map((channel) => parseInt(channel, 16) / 255); @@ -77,3 +114,20 @@ test('visual theme defaults, sanitizes legacy storage, and persists immediately' assert.deepEqual(settings.get(), { memoryDebugMode: true, visualTheme: 'vs' }); assert.deepEqual(storage.value, { memoryDebugMode: true, visualTheme: 'vs' }); }); + +test('monochrome themes persist and unknown values still use the existing default', async () => { + const storage = createStorage({ memoryDebugMode: false, visualTheme: 'monochrome-light' }); + const settings = createExtensionSettings({ storage }); + assert.equal((await settings.load()).visualTheme, 'monochrome-light'); + await settings.setVisualTheme('monochrome-dark'); + assert.equal(storage.value.visualTheme, 'monochrome-dark'); + + const invalidStorage = createStorage({ visualTheme: 'not-a-theme' }); + const invalidSettings = createExtensionSettings({ storage: invalidStorage }); + assert.equal((await invalidSettings.load()).visualTheme, DEFAULT_VISUAL_THEME_ID); +}); + +function isGrayscale(color) { + const channels = color.slice(1).match(/.{2}/g)?.map((channel) => parseInt(channel, 16)); + return channels?.length === 3 && channels[0] === channels[1] && channels[1] === channels[2]; +} diff --git a/scripts/smoke-browser.mjs b/scripts/smoke-browser.mjs index acc63e5..3d3c547 100644 --- a/scripts/smoke-browser.mjs +++ b/scripts/smoke-browser.mjs @@ -1039,7 +1039,7 @@ int main() { assert(helpOutput.includes('Run the last compiled binary'), 'Real help output is missing the run-command description'); let previousSelection = null; - for (const themeId of ['vs', 'hc-light', 'browser-cpp-dark', 'vs-dark', 'hc-black']) { + for (const themeId of ['vs', 'hc-light', 'browser-cpp-dark', 'vs-dark', 'hc-black', 'monochrome-light', 'monochrome-dark']) { await evaluate(cdp, sessionId, `document.querySelector('input[name="visual-theme"][value="${themeId}"]')?.click()`); await waitFor(async () => evaluate( cdp, @@ -1051,6 +1051,17 @@ int main() { `${themeId} computed Monaco selection styles`, 5_000); assert(monacoColors?.background && monacoColors.foreground && monacoColors.inactiveBackground, `${themeId} Monaco selection styles were not computed`); + if (themeId.startsWith('monochrome-')) { + const editorTextColors = await evaluate(cdp, sessionId, `(() => { + const spans = [...document.querySelectorAll('.monaco-editor .view-lines .view-line span')]; + return spans.map((element) => getComputedStyle(element).color).filter(Boolean); + })()`); + assert(editorTextColors.length > 0, `${themeId} rendered no Monaco text spans`); + assert(editorTextColors.every((color) => { + const channels = color.match(/[\\d.]+/g)?.map(Number); + return channels?.length >= 3 && channels[0] === channels[1] && channels[1] === channels[2]; + }), `${themeId} Monaco text contains chromatic syntax or bracket colors`); + } if (previousSelection) { const retained = await evaluate(cdp, sessionId, `document.getElementById('terminal-container')?.__browserCppReadTerminalSelection?.() || null`); @@ -1167,7 +1178,7 @@ int main() { assert(createdFileText === 'hello from fstream\n', `Expected output.txt to be created, got: ${JSON.stringify(createdFileText)}`); assert(terminalText.includes('5 stream wrote output.txt'), `Expected stream-insertion output, got: ${JSON.stringify(terminalText)}`); - for (const themeId of ['vs', 'hc-light']) { + for (const themeId of ['vs', 'hc-light', 'monochrome-light', 'monochrome-dark']) { await evaluate(cdp, sessionId, `(() => { const input = document.querySelector('input[name="visual-theme"][value="${themeId}"]'); input?.click(); @@ -1179,14 +1190,56 @@ int main() { `document.documentElement.dataset.visualTheme === '${themeId}'` ), `${themeId} theme selection`, 10_000); + if (themeId.startsWith('monochrome-')) { + const panelStyles = await evaluate(cdp, sessionId, `(() => { + const style = (selector) => { + const element = document.querySelector(selector); + const computed = element && getComputedStyle(element); + return computed && { + background: computed.backgroundColor, + border: computed.borderRightColor || computed.borderTopColor, + }; + }; + const button = document.querySelector('#toolbar button'); + return { + explorer: style('#sidebar'), + editor: style('#editor-container'), + terminal: style('#terminal-panel'), + buttonBorder: button ? getComputedStyle(button).borderTopColor : null, + }; + })()`); + const expectedPanels = themeId === 'monochrome-light' + ? { explorer: '#dddddd', editor: '#ffffff', terminal: '#dddddd', border: '#666666' } + : { explorer: '#222222', editor: '#000000', terminal: '#222222', border: '#999999' }; + assert(normalizeCssColor(panelStyles.explorer.background) === expectedPanels.explorer, + `${themeId} explorer panel has the wrong background`); + assert(normalizeCssColor(panelStyles.editor.background) === expectedPanels.editor, + `${themeId} editor panel has the wrong background`); + assert(normalizeCssColor(panelStyles.terminal.background) === expectedPanels.terminal, + `${themeId} terminal panel has the wrong background`); + assert(normalizeCssColor(panelStyles.buttonBorder) === expectedPanels.border, + `${themeId} buttons do not have a contrasting outline`); + } + const selection = await selectTerminalText(cdp, sessionId); - const expectedSelectionBackground = themeId === 'vs' ? '#0969da' : '#0037da'; + const expectedSelectionBackground = { + vs: '#0969da', + 'hc-light': '#0037da', + 'monochrome-light': '#666666', + 'monochrome-dark': '#999999', + }[themeId]; assert(selection.theme.selectionBackground, `${themeId} terminal selection background is missing`); assert(selection.theme.selectionForeground, `${themeId} terminal selection foreground is missing`); assert(selection.theme.selectionBackground === expectedSelectionBackground, `${themeId} did not apply its configured terminal selection background`); assert(selection.theme.selectionBackground !== selection.theme.background, `${themeId} terminal selection is not distinct from its background`); + if (themeId.startsWith('monochrome-')) { + const colors = [selection.theme.background, selection.theme.foreground, + selection.theme.selectionBackground, selection.theme.selectionForeground]; + assert(colors.every((color) => /^#([\da-f]{2})\1\1$/i.test(color)), + `${themeId} terminal colors are not grayscale: ${colors.join(', ')}`); + } const currentTerminal = await evaluate( cdp, sessionId, diff --git a/src/ui/editor.js b/src/ui/editor.js index 2dfb152..eee011c 100644 --- a/src/ui/editor.js +++ b/src/ui/editor.js @@ -32,6 +32,10 @@ self.MonacoEnvironment = { // ── Internal state ──────────────────────────────────────────────────────────── let _editor = null; +function isMonochromeTheme(theme) { + return theme === 'browser-cpp-monochrome-light' || theme === 'browser-cpp-monochrome-dark'; +} + // ── Public API ──────────────────────────────────────────────────────────────── /** @@ -73,6 +77,39 @@ export function createEditor(container, { theme = 'browser-cpp-dark' } = {}) { for (const visualTheme of VISUAL_THEMES) { if (visualTheme.id === 'browser-cpp-dark') continue; + if (visualTheme.id.startsWith('monochrome-')) { + const { cssTokens: colors } = visualTheme; + const isLight = visualTheme.id === 'monochrome-light'; + monaco.editor.defineTheme(visualTheme.monacoTheme, { + base: isLight ? 'vs' : 'vs-dark', + inherit: false, + rules: [], + colors: { + 'editor.background': colors.base, + 'editor.foreground': colors.text, + 'editorLineNumber.foreground': colors.subtext0, + 'editorLineNumber.activeForeground': colors.text, + 'editor.lineHighlightBackground': colors.surface0, + 'editorCursor.foreground': colors.text, + 'editor.selectionForeground': visualTheme.selection.foreground, + 'editor.selectionBackground': visualTheme.selection.background, + 'editor.inactiveSelectionBackground': visualTheme.selection.inactiveBackground, + 'editorIndentGuide.background': colors.surface1, + 'editorWidget.background': colors.mantle, + 'editorSuggestWidget.background': colors.mantle, + 'editorSuggestWidget.border': colors.surface1, + 'minimap.background': colors.mantle, + 'editorBracketHighlight.foreground1': colors.text, + 'editorBracketHighlight.foreground2': colors.text, + 'editorBracketHighlight.foreground3': colors.text, + 'editorBracketHighlight.foreground4': colors.text, + 'editorBracketHighlight.foreground5': colors.text, + 'editorBracketHighlight.foreground6': colors.text, + 'editorBracketHighlight.unexpectedBracket.foreground': colors.text, + }, + }); + continue; + } monaco.editor.defineTheme(visualTheme.monacoTheme, { base: visualTheme.id, inherit: true, @@ -107,6 +144,11 @@ export function createEditor(container, { theme = 'browser-cpp-dark' } = {}) { automaticLayout: true, // resize with container }); + _editor.updateOptions({ + bracketPairColorization: { enabled: !isMonochromeTheme(theme) }, + guides: { bracketPairs: !isMonochromeTheme(theme), indentation: true }, + }); + if (new URLSearchParams(globalThis.location?.search || '').has('smoke-test')) { container.__browserCppPrepareSelectionColorRead = () => { return true; @@ -140,6 +182,10 @@ export function createEditor(container, { theme = 'browser-cpp-dark' } = {}) { export function setTheme(theme) { monaco.editor.setTheme(theme); + _editor?.updateOptions({ + bracketPairColorization: { enabled: !isMonochromeTheme(theme) }, + guides: { bracketPairs: !isMonochromeTheme(theme), indentation: true }, + }); } /** Get the full text of the current document. */ diff --git a/src/ui/styles.css b/src/ui/styles.css index 7fe3546..54d736d 100644 --- a/src/ui/styles.css +++ b/src/ui/styles.css @@ -23,6 +23,15 @@ --mauve: #cba6f7; --peach: #fab387; --terminal-background: #11111b; + --primary-hover: #b9f0b3; + --danger-wash: rgba(243, 139, 168, 0.12); + --danger-wash-hover: rgba(243, 139, 168, 0.22); + --reload-overlay: rgba(17, 17, 27, 0.6); + --panel-explorer: var(--mantle); + --panel-editor: var(--base); + --panel-terminal: var(--crust); + --panel-border: var(--surface0); + --button-border: transparent; --toolbar-h: 36px; --statusbar-h: 22px; @@ -40,6 +49,8 @@ :root[data-visual-theme="vs-dark"] { --crust:#181818; --mantle:#202020; --base:#1e1e1e; --surface0:#2d2d30; --surface1:#3e3e42; --overlay0:#858585; --text:#d4d4d4; --subtext0:#a0a0a0; --green:#89d185; --red:#f48771; --yellow:#dcdcaa; --blue:#569cd6; --mauve:#c586c0; --peach:#ce9178; --terminal-background:#181818; } :root[data-visual-theme="hc-light"] { --crust:#fff; --mantle:#fff; --base:#fff; --surface0:#e5e5e5; --surface1:#6f6f6f; --overlay0:#333; --text:#000; --subtext0:#333; --green:#064; --red:#a80000; --yellow:#704d00; --blue:#0037da; --mauve:#5b00a5; --peach:#8a3100; --terminal-background:#fff; } :root[data-visual-theme="hc-black"] { --crust:#000; --mantle:#000; --base:#000; --surface0:#1f1f1f; --surface1:#fff; --overlay0:#d0d0d0; --text:#fff; --subtext0:#fff; --green:#0f0; --red:#ff6b6b; --yellow:#ff0; --blue:#3daee9; --mauve:#ff8cff; --peach:#ffb86c; --terminal-background:#000; } +:root[data-visual-theme="monochrome-light"] { --crust:#fff; --mantle:#fff; --base:#fff; --surface0:#fff; --surface1:#666; --overlay0:#666; --text:#000; --subtext0:#666; --green:#000; --red:#666; --yellow:#666; --blue:#000; --mauve:#000; --peach:#666; --terminal-background:#ddd; --primary-hover:#000; --danger-wash:rgba(102,102,102,.12); --danger-wash-hover:rgba(102,102,102,.22); --reload-overlay:rgba(102,102,102,.6); --panel-explorer:#ddd; --panel-editor:#fff; --panel-terminal:#ddd; --panel-border:#666; --button-border:#666; } +:root[data-visual-theme="monochrome-dark"] { --crust:#000; --mantle:#000; --base:#000; --surface0:#000; --surface1:#999; --overlay0:#999; --text:#fff; --subtext0:#999; --green:#fff; --red:#999; --yellow:#999; --blue:#fff; --mauve:#fff; --peach:#999; --terminal-background:#222; --primary-hover:#fff; --danger-wash:rgba(153,153,153,.12); --danger-wash-hover:rgba(153,153,153,.22); --reload-overlay:rgba(153,153,153,.6); --panel-explorer:#222; --panel-editor:#000; --panel-terminal:#222; --panel-border:#999; --button-border:#999; } body { height: 100vh; @@ -107,6 +118,11 @@ button { transition: background 0.1s; } +:root[data-visual-theme="monochrome-light"] button, +:root[data-visual-theme="monochrome-dark"] button { + border-color: var(--button-border); +} + button:hover { background: var(--surface1); border-color: var(--overlay0); @@ -123,7 +139,7 @@ button:disabled:active { cursor: not-allowed; opacity: 0.5; background: var(--surface0); - border-color: transparent; + border-color: var(--button-border); filter: none; } @@ -141,8 +157,8 @@ button:disabled:active { } .btn-primary:hover { - background: #b9f0b3; - border-color: transparent; + background: var(--primary-hover); + border-color: var(--button-border); } select { @@ -336,8 +352,8 @@ select:focus { #sidebar { width: var(--sidebar-w); min-width: 120px; - background: var(--mantle); - border-right: 1px solid var(--surface0); + background: var(--panel-explorer); + border-right: 1px solid var(--panel-border); display: flex; flex-direction: column; overflow: hidden; @@ -490,6 +506,7 @@ select:focus { display: flex; flex-direction: column; overflow: hidden; + background: var(--panel-editor); } /* ── Tab bar ──────────────────────────────────────────────────────────────── */ @@ -497,8 +514,8 @@ select:focus { #tab-bar { display: flex; height: var(--tab-h); - background: var(--mantle); - border-bottom: 1px solid var(--surface0); + background: var(--panel-editor); + border-bottom: 1px solid var(--panel-border); overflow-x: auto; overflow-y: hidden; flex-shrink: 0; @@ -541,6 +558,18 @@ select:focus { border-top: 1px solid var(--blue); } +:root[data-visual-theme="monochrome-light"] .tab, +:root[data-visual-theme="monochrome-dark"] .tab, +:root[data-visual-theme="monochrome-light"] .tab.active, +:root[data-visual-theme="monochrome-dark"] .tab.active { + border: 1px solid var(--panel-border); +} + +:root[data-visual-theme="monochrome-light"] .tab.active, +:root[data-visual-theme="monochrome-dark"] .tab.active { + border-color: var(--blue); +} + .tab.dirty::after { content: '●'; color: var(--yellow); @@ -569,6 +598,7 @@ select:focus { #editor-container { flex: 1; overflow: hidden; + background: var(--panel-editor); } /* ── Terminal panel ───────────────────────────────────────────────────────── */ @@ -576,8 +606,8 @@ select:focus { #terminal-panel { height: var(--terminal-h); min-height: 80px; - background: var(--crust); - border-top: 2px solid var(--surface0); + background: var(--panel-terminal); + border-top: 2px solid var(--panel-border); display: flex; flex-direction: column; flex-shrink: 0; @@ -594,8 +624,8 @@ select:focus { align-items: center; justify-content: space-between; padding: 4px 10px; - background: var(--mantle); - border-bottom: 1px solid var(--surface0); + background: var(--panel-terminal); + border-bottom: 1px solid var(--panel-border); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; @@ -620,7 +650,7 @@ select:focus { padding: 1px 6px; border: 1px solid var(--red); border-radius: 3px; - background: rgba(243, 139, 168, 0.12); + background: var(--danger-wash); color: var(--red); font-size: 11px; font-weight: 700; @@ -637,7 +667,7 @@ select:focus { } .terminal-stop-btn:hover:not(:disabled) { - background: rgba(243, 139, 168, 0.22); + background: var(--danger-wash-hover); border-color: var(--red); color: var(--text); } @@ -667,10 +697,20 @@ select:focus { background: var(--surface0); } +:root[data-visual-theme="monochrome-light"] .panel-header-btn, +:root[data-visual-theme="monochrome-dark"] .panel-header-btn, +:root[data-visual-theme="monochrome-light"] .tab-close, +:root[data-visual-theme="monochrome-dark"] .tab-close, +:root[data-visual-theme="monochrome-light"] .panel-controls button, +:root[data-visual-theme="monochrome-dark"] .panel-controls button { + border: 1px solid var(--button-border); +} + #terminal-container { flex: 1; overflow: hidden; padding: 4px 6px; + background: var(--panel-terminal); } /* xterm overrides */ @@ -742,7 +782,7 @@ body.is-resizing-horizontal * { display: flex; align-items: center; justify-content: center; - background: rgba(17, 17, 27, 0.6); + background: var(--reload-overlay); } .session-reload-dialog { diff --git a/src/ui/themes.mjs b/src/ui/themes.mjs index 2f792bb..a20295c 100644 --- a/src/ui/themes.mjs +++ b/src/ui/themes.mjs @@ -2,16 +2,30 @@ export const DEFAULT_VISUAL_THEME_ID = 'browser-cpp-dark'; -const terminal = (background, foreground, accent, selection) => ({ +const DEFAULT_ANSI_COLORS = { + black: foreground => foreground, red: '#c01c28', green: '#2d7d46', yellow: '#9a6700', + blue: '#0969da', magenta: '#8250df', cyan: '#0a7b83', white: foreground => foreground, + brightBlack: '#768390', brightRed: '#d1242f', brightGreen: '#1a7f37', + brightYellow: '#bf8700', brightBlue: '#218bff', brightMagenta: '#a475f9', + brightCyan: '#1b9aaa', brightWhite: foreground => foreground, +}; + +const terminal = (background, foreground, accent, selection, ansi = DEFAULT_ANSI_COLORS) => ({ background, foreground, cursor: accent, cursorAccent: background, selectionForeground: selection.foreground, selectionBackground: selection.background, selectionInactiveBackground: selection.inactiveBackground, - black: foreground, red: '#c01c28', green: '#2d7d46', yellow: '#9a6700', - blue: '#0969da', magenta: '#8250df', cyan: '#0a7b83', white: foreground, - brightBlack: '#768390', brightRed: '#d1242f', brightGreen: '#1a7f37', - brightYellow: '#bf8700', brightBlue: '#218bff', brightMagenta: '#a475f9', - brightCyan: '#1b9aaa', brightWhite: foreground, + ...Object.fromEntries(Object.entries(ansi).map(([key, value]) => [ + key, typeof value === 'function' ? value(foreground) : value, + ])), +}); + +const monochromeAnsi = (foreground, tertiary) => ({ + black: foreground, red: tertiary, green: tertiary, yellow: tertiary, + blue: tertiary, magenta: tertiary, cyan: tertiary, white: foreground, + brightBlack: tertiary, brightRed: foreground, brightGreen: foreground, + brightYellow: foreground, brightBlue: foreground, brightMagenta: foreground, + brightCyan: foreground, brightWhite: foreground, }); const visualTheme = ({ id, label, monacoTheme, selection, cssTokens, terminalColors, preview }) => ({ @@ -24,7 +38,8 @@ const visualTheme = ({ id, label, monacoTheme, selection, cssTokens, terminalCol terminalColors.background, terminalColors.foreground, terminalColors.accent, - selection + selection, + terminalColors.ansi ), preview, }); @@ -60,6 +75,18 @@ export const VISUAL_THEMES = Object.freeze([ cssTokens: { crust: '#000000', mantle: '#000000', base: '#000000', surface0: '#1f1f1f', surface1: '#ffffff', overlay0: '#d0d0d0', text: '#ffffff', subtext0: '#ffffff', green: '#00ff00', red: '#ff6b6b', yellow: '#ffff00', blue: '#3daee9', mauve: '#ff8cff', peach: '#ffb86c' }, terminalColors: { background: '#000000', foreground: '#ffffff', accent: '#ffffff' }, preview: { background: '#000000', foreground: '#ffffff', accent: '#ffff00' }, }), + visualTheme({ + id: 'monochrome-light', label: 'Monochrome Light', monacoTheme: 'browser-cpp-monochrome-light', + selection: { foreground: '#ffffff', background: '#666666', inactiveBackground: '#999999' }, + cssTokens: { crust: '#ffffff', mantle: '#ffffff', base: '#ffffff', surface0: '#ffffff', surface1: '#666666', overlay0: '#666666', text: '#000000', subtext0: '#666666', green: '#000000', red: '#666666', yellow: '#666666', blue: '#000000', mauve: '#000000', peach: '#666666' }, + terminalColors: { background: '#dddddd', foreground: '#000000', accent: '#666666', ansi: monochromeAnsi('#000000', '#666666') }, preview: { background: '#ffffff', foreground: '#000000', accent: '#666666' }, + }), + visualTheme({ + id: 'monochrome-dark', label: 'Monochrome Dark', monacoTheme: 'browser-cpp-monochrome-dark', + selection: { foreground: '#000000', background: '#999999', inactiveBackground: '#666666' }, + cssTokens: { crust: '#000000', mantle: '#000000', base: '#000000', surface0: '#000000', surface1: '#999999', overlay0: '#999999', text: '#ffffff', subtext0: '#999999', green: '#ffffff', red: '#999999', yellow: '#999999', blue: '#ffffff', mauve: '#ffffff', peach: '#999999' }, + terminalColors: { background: '#222222', foreground: '#ffffff', accent: '#999999', ansi: monochromeAnsi('#ffffff', '#999999') }, preview: { background: '#000000', foreground: '#ffffff', accent: '#999999' }, + }), ]); export function getVisualTheme(id) {