ChatGPT Theme

ChatGPT 主题

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ChatGPT Theme
// @namespace    https://loongphy.com
// @version      0.1.0
// @description  ChatGPT 主题
// @author       Loongphy
// @license      PolyForm-Noncommercial-1.0.0; https://polyformproject.org/licenses/noncommercial/1.0.0/
// @icon64       https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==

(function () {
    "use strict";

    // ============================================================
    // GitHub Dark Default 配色(提取自 https://github.com 实际 CSS 变量)
    //   --bgColor-default:  #0d1117   主背景
    //   --bgColor-muted:    #151b23   次要背景
    //   --bgColor-inset:    #010409   内嵌背景(最深)
    //   --bgColor-disabled: #212830   禁用背景
    //   --fgColor-default:  #f0f6fc   主文字
    //   --fgColor-muted:    #9198a1   次要文字
    //   --fgColor-disabled: #656c76   第三文字
    // ============================================================
    const DEFAULTS = {
        dark: {
            bgPrimary: "#282d3e",
            bgSecondary: "#151b23",
            bgTertiary: "#212830",
            mainSurfacePrimary: "#282d3e",
            mainSurfaceSecondary: "#151b23",
            mainSurfaceTertiary: "#212830",
            messageSurface: "#151b23",
            composerSurface: "#151b23",
            composerSurfacePrimary: "#282d3e",
            sidebarSurface: "#0d1117",
            sidebarSurfacePrimary: "#0d1117",
            sidebarSurfaceSecondary: "#151b23",
            sidebarSurfaceTertiary: "#212830",
            bgElevatedPrimary: "#161b22",
            bgElevatedSecondary: "#0d1117",
            bgSecondarySurface: "#151b23",
            textPrimary: "#e5ebfa",
            textSecondary: "#9198a1",
            textTertiary: "#656c76",
        },
        light: {
            bgPrimary: "",
            bgSecondary: "",
            bgTertiary: "",
            mainSurfacePrimary: "",
            mainSurfaceSecondary: "",
            mainSurfaceTertiary: "",
            messageSurface: "",
            composerSurface: "",
            composerSurfacePrimary: "",
            sidebarSurface: "",
            sidebarSurfacePrimary: "",
            sidebarSurfaceSecondary: "",
            sidebarSurfaceTertiary: "",
            bgElevatedPrimary: "",
            bgElevatedSecondary: "",
            bgSecondarySurface: "",
            textPrimary: "",
            textSecondary: "",
            textTertiary: "",
        },
    };

    // CSS 变量名映射
    const VAR_MAP = {
        bgPrimary: "--bg-primary",
        bgSecondary: "--bg-secondary",
        bgTertiary: "--bg-tertiary",
        mainSurfacePrimary: "--main-surface-primary",
        mainSurfaceSecondary: "--main-surface-secondary",
        mainSurfaceTertiary: "--main-surface-tertiary",
        messageSurface: "--message-surface",
        composerSurface: "--composer-surface",
        composerSurfacePrimary: "--composer-surface-primary",
        sidebarSurface: "--sidebar-surface",
        sidebarSurfacePrimary: "--sidebar-surface-primary",
        sidebarSurfaceSecondary: "--sidebar-surface-secondary",
        sidebarSurfaceTertiary: "--sidebar-surface-tertiary",
        bgElevatedPrimary: "--bg-elevated-primary",
        bgElevatedSecondary: "--bg-elevated-secondary",
        bgSecondarySurface: "--bg-secondary-surface",
        textPrimary: "--text-primary",
        textSecondary: "--text-secondary",
        textTertiary: "--text-tertiary",
    };

    // 中文标签 & 默认颜色提示
    const LABELS = {
        bgPrimary: { label: "页面底色 / Header", hint: "#0d1117 / #ffffff" },
        bgSecondary: { label: "次要背景色", hint: "#151b23 / #e8e8e8" },
        bgTertiary: { label: "第三背景色", hint: "#212830 / #f3f3f3" },
        mainSurfacePrimary: {
            label: "★ 主界面背景",
            hint: "#282d3e / #ffffff",
        },
        mainSurfaceSecondary: {
            label: "界面次要色",
            hint: "#151b23 / #f7f7f7",
        },
        mainSurfaceTertiary: { label: "界面第三色", hint: "#212830 / #f1f1f1" },
        messageSurface: {
            label: "消息气泡背景",
            hint: "#151b23 / transparent",
        },
        composerSurface: { label: "输入框背景", hint: "#151b23 / transparent" },
        composerSurfacePrimary: {
            label: "输入框主色",
            hint: "#0d1117 / #ffffff",
        },
        sidebarSurface: { label: "侧边栏背景", hint: "#010409 / #ffffff" },
        sidebarSurfacePrimary: {
            label: "侧边栏主色",
            hint: "#010409 / #ffffff",
        },
        sidebarSurfaceSecondary: {
            label: "侧边栏次要色",
            hint: "#0d1117 / #f9f9f9",
        },
        sidebarSurfaceTertiary: {
            label: "侧边栏第三色",
            hint: "#151b23 / #f3f3f3",
        },
        bgElevatedPrimary: { label: "浮层主背景", hint: "#161b22 / #ffffff" },
        bgElevatedSecondary: {
            label: "浮层次要背景",
            hint: "#0d1117 / #f9f9f9",
        },
        bgSecondarySurface: {
            label: "次要表面背景",
            hint: "#151b23 / #f9f9f9",
        },
        textPrimary: { label: "主要文字色", hint: "#e5ebfa / #0d0d0d" },
        textSecondary: { label: "次要文字色", hint: "#9198a1 / #5d5d5d" },
        textTertiary: { label: "第三文字色", hint: "#656c76 / #8f8f8f" },
    };

    // ============================================================
    // 存储(带内存缓存,避免频繁 GM_getValue)
    // ============================================================
    let configCache = null;

    function loadConfig() {
        try {
            const raw = GM_getValue("chatgpt_bg_config", "{}");
            const parsed = JSON.parse(raw);
            return {
                server_oled_theme: parsed.server_oled_theme === true,
                dark: { ...DEFAULTS.dark, ...(parsed.dark || {}) },
                light: { ...DEFAULTS.light, ...(parsed.light || {}) },
            };
        } catch {
            return {
                server_oled_theme: false,
                ...JSON.parse(JSON.stringify(DEFAULTS)),
            };
        }
    }

    function getConfig(forceReload) {
        if (!configCache || forceReload) {
            configCache = loadConfig();
        }
        return configCache;
    }

    function setConfig(config) {
        configCache = config;
        GM_setValue(
            "chatgpt_bg_config",
            JSON.stringify({
                server_oled_theme: config.server_oled_theme,
                dark: config.dark,
                light: config.light,
            }),
        );
    }

    // ============================================================
    // 样式注入(使用 !important 确保覆盖 .dark[data-oled] 等规则)
    // ============================================================
    function buildStyle(config) {
        const isDark = document.documentElement.classList.contains("dark");
        const theme = isDark ? config.dark : config.light;
        const vars = Object.entries(VAR_MAP)
            .filter(([key]) => theme[key])
            .map(([key, cssVar]) => `  ${cssVar}: ${theme[key]} !important;`)
            .join("\n");
        if (!vars) return "";
        return `:root {\n${vars}\n}`;
    }

    let styleEl = null;
    let lastCss = "";

    function applyStyle(config) {
        const css = buildStyle(config);

        // 缓存:CSS 未变则跳过 DOM 写入
        if (css === lastCss) return;
        lastCss = css;

        if (styleEl) {
            if (css) styleEl.textContent = css;
            else {
                styleEl.remove();
                styleEl = null;
            }
            return;
        }
        if (!css) return;
        styleEl = document.createElement("style");
        styleEl.id = "chatgpt-bg-customizer-style";
        styleEl.textContent = css;
        // 同等优先级下后出现的规则胜出,appendChild 到末尾即可
        const target = document.head || document.documentElement;
        target.appendChild(styleEl);
    }

    function refreshStyle() {
        const config = getConfig();
        applyStyle(config);
        applyOledSetting(config);
    }

    // ============================================================
    // 监听 class 变化(仅在 dark/light 切换时刷新,避免无差别触发)
    // ============================================================
    let observer = null;
    let lastIsDark = false;
    let refreshQueued = false;

    function scheduleRefreshStyle() {
        if (refreshQueued) return;
        refreshQueued = true;
        requestAnimationFrame(() => {
            refreshQueued = false;
            refreshStyle();
        });
    }

    function startObserver() {
        if (observer) observer.disconnect();
        // 记录当前主题状态
        lastIsDark =
            document.documentElement?.classList.contains("dark") ?? false;

        observer = new MutationObserver(() => {
            if (!document.documentElement) return;
            const nowIsDark =
                document.documentElement.classList.contains("dark");
            if (nowIsDark !== lastIsDark) {
                lastIsDark = nowIsDark;
                scheduleRefreshStyle();
            }
        });

        if (document.documentElement) {
            observer.observe(document.documentElement, {
                attributes: true,
                attributeFilter: ["class"],
            });
        } else {
            const ready = () => {
                if (document.documentElement) {
                    observer.observe(document.documentElement, {
                        attributes: true,
                        attributeFilter: ["class"],
                    });
                } else {
                    requestAnimationFrame(ready);
                }
            };
            ready();
        }
    }

    // ============================================================
    // 设置面板
    // ============================================================
    let panelEl = null;

    function openPanel() {
        if (panelEl && document.body.contains(panelEl)) return;
        panelEl = null;
        const config = getConfig();

        const container = document.createElement("div");
        container.id = "chatgpt-bg-panel";

        // 面板样式
        const style = document.createElement("style");
        style.textContent = `
            #chatgpt-bg-panel {
                position: fixed !important;
                top: 0 !important; left: 0 !important;
                width: 100% !important; height: 100% !important;
                z-index: 999999 !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                background: rgba(0,0,0,0.55) !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important;
                font-size: 14px !important;
                line-height: 1.5 !important;
                color: #e8e8e8 !important;
                margin: 0 !important;
                padding: 0 !important;
                border: none !important;
            }
            #chatgpt-bg-panel * {
                box-sizing: border-box;
            }
            #chatgpt-bg-panel-inner {
                background: #2a2a2a;
                border: 1px solid #404040;
                border-radius: 16px;
                width: 680px;
                max-width: 92vw;
                max-height: 88vh;
                display: flex;
                flex-direction: column;
                box-shadow: 0 24px 80px rgba(0,0,0,0.6);
                overflow: hidden;
            }
            #chatgpt-bg-panel-header {
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 14px 20px;
                border-bottom: 1px solid #404040;
                font-weight: 600;
                font-size: 16px;
                color: #fff;
                flex-shrink: 0;
            }
            #chatgpt-bg-panel-header span { display: flex; align-items: center; gap: 8px; }
            #chatgpt-bg-close {
                background: none; border: none;
                color: #999; font-size: 20px; cursor: pointer;
                padding: 2px 8px; border-radius: 6px;
                line-height: 1;
            }
            #chatgpt-bg-close:hover { background: #3a3a3a; color: #fff; }
            #chatgpt-bg-panel-body {
                padding: 16px 20px 20px;
                overflow-y: auto;
                flex: 1;
            }
            #chatgpt-bg-tabs {
                display: flex;
                gap: 8px;
                margin-bottom: 16px;
            }
            .chatgpt-bg-tab {
                flex: 1;
                padding: 9px 16px;
                border: 1px solid #404040;
                background: transparent;
                color: #aaa;
                border-radius: 10px;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all 0.2s;
                text-align: center;
            }
            .chatgpt-bg-tab.active {
                background: #3a3a3a;
                color: #fff;
                border-color: #666;
            }
            .chatgpt-bg-tab:hover:not(.active) { background: #333; }
            #chatgpt-bg-indicator {
                font-size: 12px;
                color: #888;
                margin-bottom: 12px;
                padding: 6px 10px;
                background: #222;
                border-radius: 8px;
                display: flex;
                align-items: center;
                gap: 6px;
            }
            #chatgpt-bg-indicator .dot {
                display: inline-block;
                width: 8px; height: 8px;
                border-radius: 50%;
            }
            #chatgpt-bg-indicator .dot.dark-dot { background: #000; border: 1px solid #555; }
            #chatgpt-bg-indicator .dot.light-dot { background: #fff; border: 1px solid #555; }
            #chatgpt-bg-theme-content {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 10px;
            }
            .chatgpt-bg-field {
                display: flex;
                flex-direction: column;
                gap: 3px;
            }
            .chatgpt-bg-field label {
                font-size: 12px;
                color: #aaa;
                font-weight: 500;
            }
            .chatgpt-bg-field .input-row {
                display: flex;
                gap: 6px;
                align-items: center;
            }
            .chatgpt-bg-field input[type="text"] {
                flex: 1;
                padding: 6px 10px;
                border: 1px solid #404040;
                border-radius: 8px;
                background: #1a1a1a;
                color: #e8e8e8;
                font-size: 13px;
                font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
                outline: none;
                transition: border-color 0.2s;
                width: 100%;
                box-sizing: border-box;
                min-width: 0;
            }
            .chatgpt-bg-field input[type="text"]:focus {
                border-color: #66b5ff;
            }
            .chatgpt-bg-field input[type="text"]::placeholder {
                color: #666;
                font-size: 11px;
            }
            .chatgpt-bg-field input[type="color"] {
                width: 32px;
                height: 32px;
                padding: 0;
                border: 1px solid #404040;
                border-radius: 8px;
                cursor: pointer;
                background: none;
                flex-shrink: 0;
            }
            .chatgpt-bg-field input[type="color"]::-webkit-color-swatch-wrapper {
                padding: 2px;
            }
            .chatgpt-bg-field input[type="color"]::-webkit-color-swatch {
                border: none;
                border-radius: 5px;
            }
            #chatgpt-bg-oled-toggle {
                border-top: 1px solid #404040;
                padding: 12px 0;
                margin-top: 12px;
            }
            #chatgpt-bg-oled-toggle label {
                display: flex;
                align-items: center;
                gap: 10px;
                cursor: pointer;
                font-size: 13px;
                color: #ccc;
            }
            #chatgpt-bg-oled-toggle input[type="checkbox"] {
                width: 18px;
                height: 18px;
                cursor: pointer;
                accent-color: #66b5ff;
            }
            #chatgpt-bg-actions {
                display: flex;
                gap: 8px;
                justify-content: flex-end;
                border-top: 1px solid #404040;
                padding-top: 16px;
                margin-top: 12px;
                flex-shrink: 0;
            }
            #chatgpt-bg-actions button {
                padding: 8px 20px;
                border-radius: 8px;
                border: 1px solid #404040;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
                transition: all 0.2s;
            }
            #chatgpt-bg-reset { background: transparent; color: #aaa; }
            #chatgpt-bg-reset:hover { background: #333; }
            #chatgpt-bg-reset-all { background: transparent; color: #888; }
            #chatgpt-bg-reset-all:hover { background: #331111; color: #ff6666; }
            #chatgpt-bg-apply {
                background: #66b5ff;
                color: #000;
                border-color: transparent;
            }
            #chatgpt-bg-apply:hover { opacity: 0.85; }
        `;

        const isDark = document.documentElement.classList.contains("dark");

        container.innerHTML = `
            <div id="chatgpt-bg-panel-inner">
                <div id="chatgpt-bg-panel-header">
                    <span>🎨 ChatGPT 背景色自定义</span>
                    <button id="chatgpt-bg-close">✕</button>
                </div>
                <div id="chatgpt-bg-panel-body">
                    <div id="chatgpt-bg-tabs">
                        <button class="chatgpt-bg-tab active" data-theme="dark">🌙 深色模式</button>
                        <button class="chatgpt-bg-tab" data-theme="light">☀️ 浅色模式</button>
                    </div>
                    <div id="chatgpt-bg-indicator">
                        <span class="dot ${isDark ? "dark-dot" : "light-dot"}"></span>
                        当前页面:<strong>${isDark ? "深色模式 (dark)" : "浅色模式 (light)"}</strong>
                        ${isDark ? "— 以下编辑的是深色模式颜色" : "— 以下编辑的是浅色模式颜色"}
                    </div>
                    <div id="chatgpt-bg-theme-content"></div>
                    <div id="chatgpt-bg-oled-toggle">
                        <label>
                            <input type="checkbox" id="chatgpt-bg-oled-checkbox" ${config.server_oled_theme ? "checked" : ""}>
                            <span>🌙 服务端 OLED 主题</span>
                        </label>
                    </div>
                    <div id="chatgpt-bg-actions">
                        <button id="chatgpt-bg-reset-all">🔄 重置全部配置</button>
                        <button id="chatgpt-bg-reset">↩️ 重置当前主题</button>
                        <button id="chatgpt-bg-apply">✅ 应用</button>
                    </div>
                </div>
            </div>
        `;

        container.appendChild(style);
        panelEl = container;
        document.body.appendChild(container);
        panelEl = container;

        // ---- 状态 ----
        let currentTheme = "dark";

        // ---- 渲染字段 ----
        function renderFields(theme) {
            const content = container.querySelector(
                "#chatgpt-bg-theme-content",
            );
            const cfg = config[theme];
            content.innerHTML = "";
            for (const [key, meta] of Object.entries(LABELS)) {
                const field = document.createElement("div");
                field.className = "chatgpt-bg-field";

                const label = document.createElement("label");
                label.textContent = meta.label;

                const row = document.createElement("div");
                row.className = "input-row";

                const textInput = document.createElement("input");
                textInput.type = "text";
                textInput.dataset.key = key;
                textInput.dataset.theme = theme;
                textInput.placeholder = meta.hint;
                textInput.value = cfg[key] || "";

                const colorInput = document.createElement("input");
                colorInput.type = "color";
                colorInput.dataset.key = key;
                colorInput.dataset.theme = theme;
                // 从已有值解析颜色
                const existingColor = cfg[key] || "";
                const hexMatch = existingColor.match(/#[0-9a-fA-F]{6}/);
                colorInput.value = hexMatch
                    ? hexMatch[0]
                    : theme === "dark"
                      ? "#212121"
                      : "#ffffff";

                // 双向同步
                textInput.addEventListener("input", () => {
                    const hex = textInput.value.match(/#[0-9a-fA-F]{6}/);
                    if (hex) colorInput.value = hex[0];
                });
                colorInput.addEventListener("input", () => {
                    // 颜色选择器的值是纯 #rrggbb,保留原有 alpha 部分
                    const oldVal = textInput.value;
                    const alphaMatch = oldVal.match(/(\d+(\.\d+)?%?)\)?$/);
                    textInput.value =
                        colorInput.value + (alphaMatch ? alphaMatch[0] : "");
                });

                row.appendChild(textInput);
                row.appendChild(colorInput);
                field.appendChild(label);
                field.appendChild(row);
                content.appendChild(field);
            }
        }

        // ---- 事件绑定 ----
        container
            .querySelector("#chatgpt-bg-close")
            .addEventListener("click", () => {
                panelEl.remove();
                panelEl = null;
            });

        container.addEventListener("click", (e) => {
            if (e.target === container) {
                panelEl.remove();
                panelEl = null;
            }
        });

        // Tab 切换
        const tabs = container.querySelectorAll(".chatgpt-bg-tab");
        tabs.forEach((tab) => {
            tab.addEventListener("click", () => {
                tabs.forEach((t) => t.classList.remove("active"));
                tab.classList.add("active");
                currentTheme = tab.dataset.theme;
                renderFields(currentTheme);
            });
        });

        // 应用
        container
            .querySelector("#chatgpt-bg-apply")
            .addEventListener("click", () => {
                const fields = container.querySelectorAll(
                    '#chatgpt-bg-theme-content input[type="text"]',
                );
                fields.forEach((input) => {
                    const theme = input.dataset.theme;
                    const key = input.dataset.key;
                    const val = input.value.trim();
                    config[theme][key] = val;
                });
                // OLED 开关
                config.server_oled_theme = container.querySelector(
                    "#chatgpt-bg-oled-checkbox",
                ).checked;
                setConfig(config);
                refreshStyle();
                panelEl.remove();
                panelEl = null;
            });

        // 重置当前主题
        container
            .querySelector("#chatgpt-bg-reset")
            .addEventListener("click", () => {
                config[currentTheme] = { ...DEFAULTS[currentTheme] };
                renderFields(currentTheme);
            });

        // 重置全部
        container
            .querySelector("#chatgpt-bg-reset-all")
            .addEventListener("click", () => {
                config.dark = { ...DEFAULTS.dark };
                config.light = { ...DEFAULTS.light };
                config.server_oled_theme = false;
                container.querySelector("#chatgpt-bg-oled-checkbox").checked =
                    false;
                renderFields(currentTheme);
            });

        // 初始渲染
        renderFields("dark");
    }

    // ============================================================
    // 浮动按钮(防重复注入)
    // ============================================================
    function addFloatingButton() {
        if (document.getElementById("chatgpt-bg-toggle-btn")) return;

        const btn = document.createElement("div");
        btn.id = "chatgpt-bg-toggle-btn";
        btn.title = "自定义 ChatGPT 背景色";
        btn.textContent = "🎨";
        btn.addEventListener("click", openPanel);
        document.body.appendChild(btn);

        if (!document.getElementById("chatgpt-bg-toggle-style")) {
            const style = document.createElement("style");
            style.id = "chatgpt-bg-toggle-style";
            style.textContent = `
                #chatgpt-bg-toggle-btn {
                    position: fixed;
                    bottom: 24px;
                    right: 24px;
                    z-index: 99999;
                    width: 44px;
                    height: 44px;
                    border-radius: 50%;
                    background: rgba(40,40,40,0.85);
                    border: 1px solid rgba(255,255,255,0.12);
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;
                    font-size: 22px;
                    box-shadow: 0 2px 12px rgba(0,0,0,0.4);
                    transition: transform 0.2s, box-shadow 0.2s;
                    user-select: none;
                    backdrop-filter: blur(12px);
                    -webkit-backdrop-filter: blur(12px);
                }
                #chatgpt-bg-toggle-btn:hover {
                    transform: scale(1.1) translateY(-2px);
                    box-shadow: 0 6px 24px rgba(0,0,0,0.5);
                }
            `;
            document.head.appendChild(style);
        }
    }

    // ============================================================
    // 菜单命令
    // ============================================================
    GM_registerMenuCommand("🎨 打开背景色设置面板", openPanel);
    GM_registerMenuCommand("🔄 重置所有颜色配置", () => {
        const empty = {
            server_oled_theme: false,
            ...JSON.parse(JSON.stringify(DEFAULTS)),
        };
        setConfig(empty);
        refreshStyle();
    });

    // ============================================================
    // OLED 关闭逻辑
    // ============================================================
    let oledObserver = null;

    function applyOledSetting(config) {
        const html = document.documentElement;
        if (!html) return;

        if (!config.server_oled_theme) {
            if (html.hasAttribute("data-oled")) {
                html.removeAttribute("data-oled");
            }
            // 监视是否被 ChatGPT 重新加上
            if (!oledObserver) {
                oledObserver = new MutationObserver(() => {
                    if (html.hasAttribute("data-oled")) {
                        html.removeAttribute("data-oled");
                    }
                });
                oledObserver.observe(html, {
                    attributes: true,
                    attributeFilter: ["data-oled"],
                });
            }
        } else {
            if (oledObserver) {
                oledObserver.disconnect();
                oledObserver = null;
            }
        }
    }

    // ============================================================
    // 初始化:分两阶段
    //   阶段 1 (document-start) — 尽早注入背景样式,减少闪烁
    //   阶段 2 (DOMContentLoaded) — 添加浮动按钮等 UI
    // ============================================================
    function initEarly() {
        if (!document.documentElement) {
            requestAnimationFrame(initEarly);
            return;
        }
        const config = getConfig(true);
        applyStyle(config);
        applyOledSetting(config);
        startObserver();
    }

    function initUI() {
        if (document.body) {
            addFloatingButton();
        } else {
            document.addEventListener(
                "DOMContentLoaded",
                () => addFloatingButton(),
                { once: true },
            );
        }
    }

    initEarly();
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", initUI, { once: true });
    } else {
        initUI();
    }
})();