Override ChatGPT dark theme variables with floating color panel
// ==UserScript==
// @name ChatGPT Theme
// @namespace http://tampermonkey.net/
// @version 1.00
// @author fuyu2022
// @description Override ChatGPT dark theme variables with floating color panel
// @match https://chatgpt.com/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
/*
三种主题,使用dar即可
const css = html, .light, .dark .light { }
.dark {
--bg-secondary-surface: #121212 !important; 与之相关:智能体新开对话时的顶部栏
--black: #121212 !important;
--main-surface-primary: #121212 !important; 与之相关:页面整体颜色
--bg-elevated-secondary: #2f2f2f !important; 与之相关:markdown块
}
.dark[data-oled] { } ;
*/
(function () {
'use strict';
const THEME_STYLE_ID = 'chatgpt-theme-vars-style';
const UI_STYLE_ID = 'chatgpt-theme-ui-style';
const STORAGE_KEY = 'chatgpt-theme-vars';
const VARS = {
'--bg-secondary-surface': '#101010',
'--black': '#121212',
'--main-surface-primary': '#121212',
'--bg-elevated-secondary': '#2f2f2f'
};
function getConfig() {
try {
return {
...VARS,
...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
};
} catch {
return { ...VARS };
}
}
function saveConfig(config) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
}
function buildThemeCSS(config) {
const body = Object.entries(config)
.map(([key, value]) => ` ${key}: ${value} !important;`)
.join('\n');
return `
html.dark,
html.dark[data-oled],
.dark,
.dark[data-oled],
html.dark *:not(.light):not(.light *),
html.dark[data-oled] *:not(.light):not(.light *) {
${body}
}
`;
}
function applyTheme() {
const config = getConfig();
const html = document.documentElement;
for (const [key, value] of Object.entries(config)) {
html.style.setProperty(key, value, 'important');
}
let style = document.getElementById(THEME_STYLE_ID);
if (!style) {
style = document.createElement('style');
style.id = THEME_STYLE_ID;
(document.head || document.documentElement).appendChild(style);
}
style.textContent = buildThemeCSS(config);
}
function createUI() {
if (!document.body) return;
if (document.getElementById('chatgpt-theme-ball')) return;
const uiStyle = document.createElement('style');
uiStyle.id = UI_STYLE_ID;
uiStyle.textContent = `
#chatgpt-theme-ball {
position: fixed;
right: 20px;
bottom: 90px;
z-index: 2147483647;
width: 42px;
height: 42px;
border-radius: 999px;
background: #2f2f2f;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
box-shadow: 0 8px 24px rgba(0,0,0,.4);
user-select: none;
}
#chatgpt-theme-panel {
position: fixed;
right: 20px;
bottom: 140px;
z-index: 2147483647;
width: 390px;
padding: 14px;
border-radius: 14px;
background: #1f1f1f;
color: #fff;
font: 13px/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
box-shadow: 0 12px 36px rgba(0,0,0,.45);
display: none;
}
#chatgpt-theme-panel.open {
display: block;
}
#chatgpt-theme-panel h3 {
margin: 0 0 12px;
font-size: 15px;
}
.chatgpt-theme-row {
display: grid;
grid-template-columns: 1fr 90px 44px;
gap: 8px;
align-items: center;
margin-bottom: 10px;
}
.chatgpt-theme-row code {
font-size: 12px;
color: #ddd;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chatgpt-theme-text {
width: 90px;
box-sizing: border-box;
background: #111;
color: #fff;
border: 1px solid #555;
border-radius: 6px;
padding: 5px 6px;
}
.chatgpt-theme-color {
width: 44px;
height: 30px;
padding: 0;
border: 1px solid #555;
border-radius: 6px;
background: transparent;
cursor: pointer;
}
#chatgpt-theme-reset {
width: 100%;
margin-top: 6px;
padding: 7px 10px;
border: 1px solid #555;
border-radius: 8px;
background: #2a2a2a;
color: #fff;
cursor: pointer;
}
`;
const ball = document.createElement('div');
ball.id = 'chatgpt-theme-ball';
ball.textContent = '🎨';
const panel = document.createElement('div');
panel.id = 'chatgpt-theme-panel';
function renderPanel() {
const config = getConfig();
panel.innerHTML = `
<h3>ChatGPT Theme</h3>
${Object.keys(VARS).map(name => `
<div class="chatgpt-theme-row" data-var="${name}">
<code>${name}</code>
<input class="chatgpt-theme-text" type="text" value="${config[name]}">
<input class="chatgpt-theme-color" type="color" value="${config[name]}">
</div>
`).join('')}
<button id="chatgpt-theme-reset">Reset</button>
`;
}
renderPanel();
ball.addEventListener('click', () => {
renderPanel();
panel.classList.toggle('open');
});
panel.addEventListener('input', e => {
const row = e.target.closest('.chatgpt-theme-row');
if (!row) return;
const name = row.dataset.var;
const value = e.target.value.trim();
if (!/^#[0-9a-fA-F]{6}$/.test(value)) return;
const config = getConfig();
config[name] = value;
saveConfig(config);
const text = row.querySelector('.chatgpt-theme-text');
const color = row.querySelector('.chatgpt-theme-color');
text.value = value;
color.value = value;
applyTheme();
});
panel.addEventListener('click', e => {
if (e.target.id !== 'chatgpt-theme-reset') return;
saveConfig({ ...VARS });
applyTheme();
renderPanel();
});
document.head.appendChild(uiStyle);
document.body.appendChild(ball);
document.body.appendChild(panel);
}
applyTheme();
document.addEventListener('DOMContentLoaded', () => {
applyTheme();
createUI();
});
new MutationObserver(() => {
applyTheme();
createUI();
}).observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'style', 'data-oled', 'data-theme']
});
})();