change-gpt-logo.user.js
· 6.2 KiB · JavaScript
Ham
// ==UserScript==
// @name ChatGPT Logo 换成 DeepSeek
// @namespace https://gist.doracoin.cc/doracoin/39fd3ec77d3044fc95b408bdf6c0ac13
// @version 2025.09.12
// @license MIT
// @description 替换 ChatGPT 左上角 sidebar-header 的 SVG logo 为 DeepSeek logo,并实现 #page-header 向上收缩(垂直折叠),支持 SPA 重绘、状态记忆,并增加可配置项与主题适配按钮。
// @author Doracoin
// @match https://chatgpt.com/*
// @icon https://deepseek.com/favicon.ico
// @run-at document-body
// @grant GM_addStyle
// ==/UserScript==
(function () {
'use strict';
// ====== 配置区 ======
const CONFIG = {
deepseekLogoUrl: 'https://commons.wikimedia.org/wiki/Special:Redirect/file/DeepSeek_logo.svg',
storageKey: 'deepseek_header_collapsed_v2',
headerMaxHeight: 82, // header 默认高度
buttonSize: 36, // 按钮尺寸(px)
buttonPosition: { top: 10, right: 12 }, // 按钮固定位置
enableLogs: false // 是否启用 console.log 调试
};
// ====================
// 动态样式,支持深色/浅色主题自动适配
GM_addStyle(`
#page-header {
--ds-header-max-height: ${CONFIG.headerMaxHeight}px;
max-height: var(--ds-header-max-height);
overflow: hidden;
transition: max-height 260ms ease, padding 260ms ease;
}
#page-header.collapsed {
max-height: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
#page-header.collapsed * {
visibility: hidden;
pointer-events: none;
}
#ds-page-header-toggle {
position: fixed;
top: ${CONFIG.buttonPosition.top}px;
right: ${CONFIG.buttonPosition.right}px;
z-index: 2147483647;
display: inline-flex;
align-items: center;
justify-content: center;
width: ${CONFIG.buttonSize}px;
height: ${CONFIG.buttonSize}px;
border-radius: 6px;
font-size: 16px;
line-height: 1;
background: var(--ds-btn-bg, rgba(0,0,0,0.6));
color: var(--ds-btn-fg, #fff);
cursor: pointer;
user-select: none;
transition: transform 140ms ease, opacity 140ms ease, background 200ms ease;
box-shadow: 0 6px 18px rgba(0,0,0,0.35);
margin-top: 38px;
}
#ds-page-header-toggle:hover {
transform: translateY(-2px);
}
@media (prefers-color-scheme: light) {
#ds-page-header-toggle {
--ds-btn-bg: rgba(255,255,255,0.85);
--ds-btn-fg: #000;
}
}
@media (prefers-color-scheme: dark) {
#ds-page-header-toggle {
--ds-btn-bg: rgba(0,0,0,0.6);
--ds-btn-fg: #fff;
}
}
`);
function log(...args) {
if (CONFIG.enableLogs) console.log('[DeepSeek]', ...args);
}
// 替换 sidebar header logo(保留用户调整的宽高逻辑)
function replaceLogo() {
try {
const sidebarHeader = document.querySelector('.sidebar-header') || document.querySelector('.sidebar .header') || document.querySelector('nav[aria-label]');
if (!sidebarHeader) return;
const origSvg = sidebarHeader.querySelector('svg');
const origImg = sidebarHeader.querySelector('img');
if (sidebarHeader.querySelector('img[data-deepseek-logo]')) return;
const img = document.createElement('img');
img.setAttribute('src', CONFIG.deepseekLogoUrl);
img.setAttribute('alt', 'DeepSeek');
img.setAttribute('data-deepseek-logo', '1');
img.style.height = '56px'; // 用户修改的高度
img.style.width = 'auto';
img.style.objectFit = 'contain';
img.style.verticalAlign = 'middle';
const parent = origSvg?.parentNode || origImg?.parentNode || sidebarHeader.querySelector('a') || sidebarHeader;
parent.innerHTML = '';
parent.appendChild(img);
// 调整父容器以适配新 logo 宽高
if (parent.classList.contains("w-9")) {
parent.classList.remove("w-9");
parent.classList.add("w-21");
}
log('logo replaced');
} catch (e) {
log('replaceLogo error', e);
}
}
function ensureToggle() {
let btn = document.getElementById('ds-page-header-toggle');
if (btn) return btn;
btn = document.createElement('div');
btn.id = 'ds-page-header-toggle';
btn.setAttribute('role', 'button');
btn.setAttribute('aria-pressed', 'false');
btn.setAttribute('title', '折叠/展开页眉 (DeepSeek)');
btn.innerText = '▲';
btn.addEventListener('click', () => toggleHeader());
document.body.appendChild(btn);
return btn;
}
function getHeader() {
return document.querySelector('#page-header') || document.querySelector('header') || document.querySelector('.page-header');
}
function toggleHeader(forceState) {
const header = getHeader();
if (!header) return;
const shouldCollapse = typeof forceState === 'boolean' ? forceState : !header.classList.contains('collapsed');
header.classList.toggle('collapsed', shouldCollapse);
try { localStorage.setItem(CONFIG.storageKey, shouldCollapse ? '1' : '0'); } catch (e) {}
const btn = ensureToggle();
btn.innerText = shouldCollapse ? '▼' : '▲';
btn.setAttribute('aria-pressed', shouldCollapse ? 'true' : 'false');
}
function restoreState() {
try {
const v = localStorage.getItem(CONFIG.storageKey);
if (v === '1') toggleHeader(true);
} catch (e) {}
}
let debounceTimer = null;
function onDomChanged() {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
replaceLogo();
ensureToggle();
restoreState();
}, 250);
}
const observer = new MutationObserver(onDomChanged);
observer.observe(document.body, { childList: true, subtree: true });
replaceLogo();
ensureToggle();
restoreState();
window.addEventListener('load', () => setTimeout(onDomChanged, 300));
})();
| 1 | // ==UserScript== |
| 2 | // @name ChatGPT Logo 换成 DeepSeek |
| 3 | // @namespace https://gist.doracoin.cc/doracoin/39fd3ec77d3044fc95b408bdf6c0ac13 |
| 4 | // @version 2025.09.12 |
| 5 | // @license MIT |
| 6 | // @description 替换 ChatGPT 左上角 sidebar-header 的 SVG logo 为 DeepSeek logo,并实现 #page-header 向上收缩(垂直折叠),支持 SPA 重绘、状态记忆,并增加可配置项与主题适配按钮。 |
| 7 | // @author Doracoin |
| 8 | // @match https://chatgpt.com/* |
| 9 | // @icon https://deepseek.com/favicon.ico |
| 10 | // @run-at document-body |
| 11 | // @grant GM_addStyle |
| 12 | // ==/UserScript== |
| 13 | |
| 14 | (function () { |
| 15 | 'use strict'; |
| 16 | |
| 17 | // ====== 配置区 ====== |
| 18 | const CONFIG = { |
| 19 | deepseekLogoUrl: 'https://commons.wikimedia.org/wiki/Special:Redirect/file/DeepSeek_logo.svg', |
| 20 | storageKey: 'deepseek_header_collapsed_v2', |
| 21 | headerMaxHeight: 82, // header 默认高度 |
| 22 | buttonSize: 36, // 按钮尺寸(px) |
| 23 | buttonPosition: { top: 10, right: 12 }, // 按钮固定位置 |
| 24 | enableLogs: false // 是否启用 console.log 调试 |
| 25 | }; |
| 26 | // ==================== |
| 27 | |
| 28 | // 动态样式,支持深色/浅色主题自动适配 |
| 29 | GM_addStyle(` |
| 30 | #page-header { |
| 31 | --ds-header-max-height: ${CONFIG.headerMaxHeight}px; |
| 32 | max-height: var(--ds-header-max-height); |
| 33 | overflow: hidden; |
| 34 | transition: max-height 260ms ease, padding 260ms ease; |
| 35 | } |
| 36 | #page-header.collapsed { |
| 37 | max-height: 0 !important; |
| 38 | padding-top: 0 !important; |
| 39 | padding-bottom: 0 !important; |
| 40 | } |
| 41 | #page-header.collapsed * { |
| 42 | visibility: hidden; |
| 43 | pointer-events: none; |
| 44 | } |
| 45 | |
| 46 | #ds-page-header-toggle { |
| 47 | position: fixed; |
| 48 | top: ${CONFIG.buttonPosition.top}px; |
| 49 | right: ${CONFIG.buttonPosition.right}px; |
| 50 | z-index: 2147483647; |
| 51 | display: inline-flex; |
| 52 | align-items: center; |
| 53 | justify-content: center; |
| 54 | width: ${CONFIG.buttonSize}px; |
| 55 | height: ${CONFIG.buttonSize}px; |
| 56 | border-radius: 6px; |
| 57 | font-size: 16px; |
| 58 | line-height: 1; |
| 59 | background: var(--ds-btn-bg, rgba(0,0,0,0.6)); |
| 60 | color: var(--ds-btn-fg, #fff); |
| 61 | cursor: pointer; |
| 62 | user-select: none; |
| 63 | transition: transform 140ms ease, opacity 140ms ease, background 200ms ease; |
| 64 | box-shadow: 0 6px 18px rgba(0,0,0,0.35); |
| 65 | margin-top: 38px; |
| 66 | } |
| 67 | #ds-page-header-toggle:hover { |
| 68 | transform: translateY(-2px); |
| 69 | } |
| 70 | |
| 71 | @media (prefers-color-scheme: light) { |
| 72 | #ds-page-header-toggle { |
| 73 | --ds-btn-bg: rgba(255,255,255,0.85); |
| 74 | --ds-btn-fg: #000; |
| 75 | } |
| 76 | } |
| 77 | @media (prefers-color-scheme: dark) { |
| 78 | #ds-page-header-toggle { |
| 79 | --ds-btn-bg: rgba(0,0,0,0.6); |
| 80 | --ds-btn-fg: #fff; |
| 81 | } |
| 82 | } |
| 83 | `); |
| 84 | |
| 85 | function log(...args) { |
| 86 | if (CONFIG.enableLogs) console.log('[DeepSeek]', ...args); |
| 87 | } |
| 88 | |
| 89 | // 替换 sidebar header logo(保留用户调整的宽高逻辑) |
| 90 | function replaceLogo() { |
| 91 | try { |
| 92 | const sidebarHeader = document.querySelector('.sidebar-header') || document.querySelector('.sidebar .header') || document.querySelector('nav[aria-label]'); |
| 93 | if (!sidebarHeader) return; |
| 94 | |
| 95 | const origSvg = sidebarHeader.querySelector('svg'); |
| 96 | const origImg = sidebarHeader.querySelector('img'); |
| 97 | |
| 98 | if (sidebarHeader.querySelector('img[data-deepseek-logo]')) return; |
| 99 | |
| 100 | const img = document.createElement('img'); |
| 101 | img.setAttribute('src', CONFIG.deepseekLogoUrl); |
| 102 | img.setAttribute('alt', 'DeepSeek'); |
| 103 | img.setAttribute('data-deepseek-logo', '1'); |
| 104 | img.style.height = '56px'; // 用户修改的高度 |
| 105 | img.style.width = 'auto'; |
| 106 | img.style.objectFit = 'contain'; |
| 107 | img.style.verticalAlign = 'middle'; |
| 108 | |
| 109 | const parent = origSvg?.parentNode || origImg?.parentNode || sidebarHeader.querySelector('a') || sidebarHeader; |
| 110 | parent.innerHTML = ''; |
| 111 | parent.appendChild(img); |
| 112 | |
| 113 | // 调整父容器以适配新 logo 宽高 |
| 114 | if (parent.classList.contains("w-9")) { |
| 115 | parent.classList.remove("w-9"); |
| 116 | parent.classList.add("w-21"); |
| 117 | } |
| 118 | |
| 119 | log('logo replaced'); |
| 120 | } catch (e) { |
| 121 | log('replaceLogo error', e); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | function ensureToggle() { |
| 126 | let btn = document.getElementById('ds-page-header-toggle'); |
| 127 | if (btn) return btn; |
| 128 | |
| 129 | btn = document.createElement('div'); |
| 130 | btn.id = 'ds-page-header-toggle'; |
| 131 | btn.setAttribute('role', 'button'); |
| 132 | btn.setAttribute('aria-pressed', 'false'); |
| 133 | btn.setAttribute('title', '折叠/展开页眉 (DeepSeek)'); |
| 134 | btn.innerText = '▲'; |
| 135 | btn.addEventListener('click', () => toggleHeader()); |
| 136 | document.body.appendChild(btn); |
| 137 | return btn; |
| 138 | } |
| 139 | |
| 140 | function getHeader() { |
| 141 | return document.querySelector('#page-header') || document.querySelector('header') || document.querySelector('.page-header'); |
| 142 | } |
| 143 | |
| 144 | function toggleHeader(forceState) { |
| 145 | const header = getHeader(); |
| 146 | if (!header) return; |
| 147 | |
| 148 | const shouldCollapse = typeof forceState === 'boolean' ? forceState : !header.classList.contains('collapsed'); |
| 149 | header.classList.toggle('collapsed', shouldCollapse); |
| 150 | |
| 151 | try { localStorage.setItem(CONFIG.storageKey, shouldCollapse ? '1' : '0'); } catch (e) {} |
| 152 | |
| 153 | const btn = ensureToggle(); |
| 154 | btn.innerText = shouldCollapse ? '▼' : '▲'; |
| 155 | btn.setAttribute('aria-pressed', shouldCollapse ? 'true' : 'false'); |
| 156 | } |
| 157 | |
| 158 | function restoreState() { |
| 159 | try { |
| 160 | const v = localStorage.getItem(CONFIG.storageKey); |
| 161 | if (v === '1') toggleHeader(true); |
| 162 | } catch (e) {} |
| 163 | } |
| 164 | |
| 165 | let debounceTimer = null; |
| 166 | function onDomChanged() { |
| 167 | if (debounceTimer) clearTimeout(debounceTimer); |
| 168 | debounceTimer = setTimeout(() => { |
| 169 | replaceLogo(); |
| 170 | ensureToggle(); |
| 171 | restoreState(); |
| 172 | }, 250); |
| 173 | } |
| 174 | |
| 175 | const observer = new MutationObserver(onDomChanged); |
| 176 | observer.observe(document.body, { childList: true, subtree: true }); |
| 177 | |
| 178 | replaceLogo(); |
| 179 | ensureToggle(); |
| 180 | restoreState(); |
| 181 | window.addEventListener('load', () => setTimeout(onDomChanged, 300)); |
| 182 | })(); |
| 183 |