Greasy Fork is available in English.
Gemini 聊天页面轻量美化:更舒服的排版 + 发光代码块 & 表格(保持原生布局),主题自适应 + 可访问性增强。
// ==UserScript==
// @name Gemini Singularity (V0.9)
// @namespace http://tampermonkey.net/
// @version 0.9
// @description Gemini 聊天页面轻量美化:更舒服的排版 + 发光代码块 & 表格(保持原生布局),主题自适应 + 可访问性增强。
// @author GQLJ
// @match https://gemini.google.com/*
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+CiAgPGRlZnM+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxMTE4MjciLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMDIwNjE3Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPHJhZGlhbEdyYWRpZW50IGlkPSJnbG93IiBjeD0iNTAlIiBjeT0iMCUiIHI9IjcwJSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM2MGE1ZmEiIHN0b3Atb3BhY2l0eT0iMC45Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzFkNGVkOCIgc3RvcC1vcGFjaXR5PSIwLjAiLz4KICAgIDwvcmFkaWFsR3JhZGllbnQ+CiAgPC9kZWZzPgogIDxyZWN0IHg9IjQiIHk9IjQiIHdpZHRoPSI1NiIgaGVpZ2h0PSI1NiIgcng9IjE0IiBmaWxsPSJ1cmwoI2JnKSIvPgogIDxjaXJjbGUgY3g9IjMyIiBjeT0iMTgiIHI9IjE4IiBmaWxsPSJ1cmwoI2dsb3cpIiAvPgogIDx0ZXh0IHg9IjUwJSIgeT0iNTYlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjZTVlN2ViIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICdTZWdvZSBVSScsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMzAiIGZvbnQtd2VpZ2h0PSI3MDAiPgogICAgRwogIDwvdGV4dD4KPC9zdmc+
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false;
if (window.top !== window.self) return;
// =========================
// 可配置项(菜单开关)
// =========================
const DEFAULTS = {
maxWidth: 980,
fontSize: 17,
fontSizeMobile: 16,
enableRemoteFonts: true,
enableGlow: true,
enableEntryAnim: true,
justifyText: true,
roundImages: true,
tableMobileScroll: true,
};
const cfg = {
maxWidth: Number(GM_getValue('maxWidth', DEFAULTS.maxWidth)),
fontSize: Number(GM_getValue('fontSize', DEFAULTS.fontSize)),
fontSizeMobile: Number(GM_getValue('fontSizeMobile', DEFAULTS.fontSizeMobile)),
enableRemoteFonts: !!GM_getValue('enableRemoteFonts', DEFAULTS.enableRemoteFonts),
enableGlow: !!GM_getValue('enableGlow', DEFAULTS.enableGlow),
enableEntryAnim: !!GM_getValue('enableEntryAnim', DEFAULTS.enableEntryAnim),
justifyText: !!GM_getValue('justifyText', DEFAULTS.justifyText),
roundImages: !!GM_getValue('roundImages', DEFAULTS.roundImages),
tableMobileScroll: !!GM_getValue('tableMobileScroll', DEFAULTS.tableMobileScroll),
};
function toggle(key) {
GM_setValue(key, !cfg[key]);
window.location.reload();
}
try {
GM_registerMenuCommand(`远程字体: ${cfg.enableRemoteFonts ? '开' : '关'}`, () => toggle('enableRemoteFonts'));
GM_registerMenuCommand(`发光效果: ${cfg.enableGlow ? '开' : '关'}`, () => toggle('enableGlow'));
GM_registerMenuCommand(`入场动画: ${cfg.enableEntryAnim ? '开' : '关'}`, () => toggle('enableEntryAnim'));
GM_registerMenuCommand(`段落两端对齐: ${cfg.justifyText ? '开' : '关'}`, () => toggle('justifyText'));
GM_registerMenuCommand(`图片圆角: ${cfg.roundImages ? '开' : '关'}`, () => toggle('roundImages'));
GM_registerMenuCommand(`移动端表格横滑: ${cfg.tableMobileScroll ? '开' : '关'}`, () => toggle('tableMobileScroll'));
} catch (_) {}
// =========================
// 字体加载(可关)— 用 link 替代 @import
// =========================
function injectFonts() {
if (!cfg.enableRemoteFonts) return;
const head = document.head || document.documentElement;
const pre1 = document.createElement('link');
pre1.rel = 'preconnect';
pre1.href = 'https://fonts.googleapis.com';
head.appendChild(pre1);
const pre2 = document.createElement('link');
pre2.rel = 'preconnect';
pre2.href = 'https://fonts.gstatic.com';
pre2.crossOrigin = 'anonymous';
head.appendChild(pre2);
const link = document.createElement('link');
link.rel = 'stylesheet';
// ✅ 修复:URL 不能在单引号字符串里换行
link.href =
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;750&family=JetBrains+Mono:wght@400;500;600&display=swap';
head.appendChild(link);
}
injectFonts();
// =========================
// 阴影强度(可关)
// =========================
const glowShadows = cfg.enableGlow
? `
box-shadow:
0 0 0 1px rgba(255,255,255,0.04),
0 0 22px var(--singularity-glow-soft),
0 22px 55px -18px rgba(0,0,0,0.7) !important;
`
: `
box-shadow:
0 0 0 1px rgba(255,255,255,0.04),
0 18px 45px -22px rgba(0,0,0,0.55) !important;
`;
const glowShadowsHover = cfg.enableGlow
? `
box-shadow:
0 0 0 1px var(--singularity-glow-strong),
0 0 40px var(--singularity-glow-strong),
0 25px 65px -20px rgba(0,0,0,0.85) !important;
`
: `
box-shadow:
0 0 0 1px rgba(255,255,255,0.06),
0 22px 55px -24px rgba(0,0,0,0.75) !important;
`;
const tableShadows = cfg.enableGlow
? `
box-shadow:
0 0 0 1px rgba(255,255,255,0.03),
0 0 18px var(--singularity-glow-soft),
0 4px 20px rgba(0,0,0,0.05);
`
: `
box-shadow:
0 0 0 1px rgba(255,255,255,0.03),
0 4px 18px rgba(0,0,0,0.05);
`;
const tableShadowsHover = cfg.enableGlow
? `
box-shadow:
0 0 0 1px var(--singularity-glow-strong),
0 0 28px var(--singularity-glow-strong),
0 6px 28px rgba(0,0,0,0.12);
`
: `
box-shadow:
0 0 0 1px rgba(255,255,255,0.05),
0 6px 26px rgba(0,0,0,0.12);
`;
const entryAnimCSS = cfg.enableEntryAnim
? `
@keyframes singularFadeIn {
0% { opacity: 0; transform: translateY(8px); }
100% { opacity: 1; transform: translateY(0); }
}
.model-response-text > :is(p, h1, h2, h3, h4, blockquote, table, ul, ol, code-block, mjx-container[display="true"]),
markdown-renderer > :is(p, h1, h2, h3, h4, blockquote, table, ul, ol, code-block, mjx-container[display="true"]) {
animation: singularFadeIn 0.45s var(--ease-out-expo) both;
}
`
: `
.model-response-text > *,
markdown-renderer > * { animation: none !important; }
`;
const justifyCSS = cfg.justifyText
? `
:where(.model-response-text, markdown-renderer) p {
text-align: justify;
text-justify: inter-ideograph;
}
`
: `
:where(.model-response-text, markdown-renderer) p { text-align: left; }
`;
const imagesCSS = cfg.roundImages
? `
:where(.model-response-text, markdown-renderer) img {
max-width: 100%;
height: auto;
border-radius: 14px;
box-shadow: 0 8px 28px rgba(0,0,0,0.08);
}
`
: `
:where(.model-response-text, markdown-renderer) img {
max-width: 100%;
height: auto;
}
`;
const tableMobileCSS = cfg.tableMobileScroll
? `
@media (max-width: 768px) {
:where(.model-response-text, markdown-renderer) table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
}
`
: '';
// =========================
// CSS
// =========================
const css = `
:root {
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--glass-blur: blur(20px) saturate(180%);
--body-font: ${cfg.enableRemoteFonts ? `'Inter', ` : ''}system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--code-font: ${cfg.enableRemoteFonts ? `'JetBrains Mono', ` : ''}'Fira Code', 'Menlo', ui-monospace, SFMono-Regular, monospace;
--accent: #4285f4;
--accent-strong: #1a73e8;
--accent-soft: rgba(66, 133, 244, 0.08);
--singularity-glow-soft: rgba(138, 180, 248, 0.26);
--singularity-glow-strong: rgba(138, 180, 248, 0.55);
--singularity-max-width: ${cfg.maxWidth}px;
--singularity-font-size: ${cfg.fontSize}px;
--singularity-font-size-mobile: ${cfg.fontSizeMobile}px;
/* 代码块:主题自适应变量(默认按浅色) */
--sg-code-bg: var(--gm-surface-variant, #f6f7f8);
--sg-code-text: var(--gm-on-surface, #202124);
--sg-code-border: var(--gm-outline-variant, rgba(0,0,0,0.10));
--sg-code-header-bg: rgba(255,255,255,0.75);
--sg-code-header-sep: rgba(0,0,0,0.06);
scrollbar-width: thin;
scrollbar-color: var(--gm-outline-variant, rgba(128,128,128,0.2)) transparent;
}
/* 深色主题兜底 */
@media (prefers-color-scheme: dark) {
:root {
--sg-code-bg: #050608;
--sg-code-text: #e4e4e4;
--sg-code-border: var(--gm-outline-variant, rgba(255,255,255,0.12));
--sg-code-header-bg: rgba(30, 30, 30, 0.6);
--sg-code-header-sep: rgba(255,255,255,0.06);
}
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: var(--gm-outline-variant, rgba(128,128,128,0.2));
border-radius: 99px;
border: 2px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--gm-outline, rgba(128,128,128,0.5)); }
.conversation-container,
.bottom-container,
.input-area-container {
max-width: var(--singularity-max-width) !important;
margin: 0 auto !important;
padding-inline: 12px;
}
::selection { background: rgba(66, 133, 244, 0.25); color: inherit; }
${entryAnimCSS}
@media (prefers-reduced-motion: reduce) {
.model-response-text > *,
markdown-renderer > * { animation: none !important; }
code-block,
.model-response-text table,
markdown-renderer table { transition: none !important; }
}
@media (prefers-reduced-transparency: reduce) {
code-block, :where(.model-response-text, markdown-renderer) table { box-shadow: none !important; }
.code-block-decoration { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
}
:where(.model-response-text, markdown-renderer) {
font-family: var(--body-font) !important;
font-size: var(--singularity-font-size) !important;
line-height: 1.85 !important;
letter-spacing: 0.012em;
color: var(--gm-on-surface) !important;
hyphens: auto;
}
:where(.model-response-text, markdown-renderer) :is(h1, h2, h3, h4) {
font-weight: 750 !important;
letter-spacing: -0.02em;
margin-top: 1.8em !important;
margin-bottom: 0.8em !important;
color: var(--gm-on-surface);
position: relative;
}
:where(.model-response-text, markdown-renderer) h1 { font-size: 1.6em !important; }
:where(.model-response-text, markdown-renderer) h2 { font-size: 1.35em !important; }
:where(.model-response-text, markdown-renderer) h3 { font-size: 1.18em !important; }
:where(.model-response-text, markdown-renderer) h4 { font-size: 1.05em !important; }
:where(.model-response-text, markdown-renderer) :is(h1, h2, h3, h4)::after {
content: '';
position: absolute;
left: 0;
bottom: -6px;
width: 72px;
height: 2px;
border-radius: 99px;
background: linear-gradient(90deg, var(--accent), transparent);
opacity: 0.55;
}
:where(.model-response-text, markdown-renderer) p { margin-bottom: 1.8em !important; max-width: 100%; }
${justifyCSS}
@media (max-width: 768px) {
:where(.model-response-text, markdown-renderer) { font-size: var(--singularity-font-size-mobile) !important; }
:where(.model-response-text, markdown-renderer) p { text-align: left !important; }
}
:where(.model-response-text, markdown-renderer) ul li::marker { color: var(--accent); }
:where(.model-response-text, markdown-renderer) ol li::marker { color: var(--accent); font-weight: 600; font-variant-numeric: tabular-nums; }
:where(.model-response-text, markdown-renderer) li { padding-left: 4px; margin-bottom: 0.8em !important; }
:where(.model-response-text, markdown-renderer) a {
text-decoration: none !important;
color: var(--accent-strong) !important;
border-bottom: 1.5px solid rgba(26, 115, 232, 0.3);
transition: box-shadow 0.18s ease-out, background 0.18s ease-out, border-color 0.18s ease-out;
font-weight: 500;
border-radius: 4px;
}
:where(.model-response-text, markdown-renderer) a:hover {
border-bottom-color: var(--accent-strong);
background: var(--accent-soft);
box-shadow: 0 0 0 4px var(--accent-soft);
}
@media (prefers-color-scheme: dark) {
:where(.model-response-text, markdown-renderer) a { color: #8ab4f8 !important; border-color: rgba(138, 180, 248, 0.35); }
}
:where(.conversation-container, .bottom-container, .input-area-container)
:is(a, button, [role="button"], input, textarea, select):focus-visible {
outline: 2px solid var(--accent) !important;
outline-offset: 3px !important;
border-radius: 8px;
box-shadow: 0 0 0 4px var(--accent-soft) !important;
}
code-block {
display: block;
margin: 32px 0 !important;
border-radius: 16px !important;
border: 1px solid var(--sg-code-border) !important;
background: var(--sg-code-bg) !important;
${glowShadows}
overflow: hidden !important;
position: relative;
z-index: 1;
transition: box-shadow 0.22s var(--ease-out-expo), border-color 0.22s var(--ease-out-expo);
}
code-block:hover { border-color: var(--singularity-glow-strong); ${glowShadowsHover} }
.code-block-decoration {
height: 50px !important;
display: flex;
align-items: center;
padding: 0 22px !important;
background: var(--sg-code-header-bg) !important;
border-bottom: 1px solid var(--sg-code-header-sep);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
.code-block-decoration { backdrop-filter: var(--glass-blur) !important; -webkit-backdrop-filter: var(--glass-blur) !important; }
}
.code-block-decoration::before {
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
background: #ff5f56;
box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
margin-right: 20px;
opacity: 0.8;
transition: opacity 0.3s;
}
code-block:hover .code-block-decoration::before { opacity: 1; }
code-block pre {
background: transparent !important;
padding: 24px !important;
font-family: var(--code-font) !important;
font-size: 14.5px !important;
line-height: 1.7 !important;
color: var(--sg-code-text) !important;
text-shadow: none;
overflow-x: auto;
tab-size: 4;
}
@media (prefers-color-scheme: dark) { code-block pre { text-shadow: 0 1px 2px rgba(0,0,0,0.5); } }
:where(.model-response-text, markdown-renderer) :not(pre) > code {
font-family: var(--code-font) !important;
background: var(--gm-secondary-container, rgba(0,0,0,0.05)) !important;
color: var(--gm-on-secondary-container, #d93025) !important;
padding: 4px 8px !important;
border-radius: 8px !important;
font-size: 0.85em !important;
border: 1px solid var(--gm-outline-variant, rgba(0,0,0,0.05));
vertical-align: baseline;
font-weight: 600;
word-break: break-word;
}
:where(.model-response-text, markdown-renderer) blockquote {
border: none !important;
position: relative;
margin: 2.4em 0 !important;
padding: 1.6em 2em !important;
background: linear-gradient(135deg, var(--gm-surface-variant, rgba(66, 133, 244, 0.05)) 0%, rgba(66, 133, 244, 0.01) 100%);
border-radius: 16px;
color: var(--gm-on-surface-variant) !important;
}
:where(.model-response-text, markdown-renderer) blockquote::before {
content: '❝';
position: absolute;
top: 10px;
right: 14px;
font-size: 28px;
opacity: 0.18;
color: var(--accent);
pointer-events: none;
}
:where(.model-response-text, markdown-renderer) blockquote::after {
content: '';
position: absolute;
left: 0;
top: 12px;
bottom: 12px;
width: 3px;
background: var(--accent);
border-radius: 0 4px 4px 0;
box-shadow: 2px 0 8px rgba(66, 133, 244, 0.3);
}
:where(.model-response-text, markdown-renderer) table {
border-collapse: separate !important;
border-spacing: 0;
width: 100%;
margin: 2.4em 0 !important;
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--gm-outline-variant, rgba(128,128,128,0.2));
${tableShadows}
transition: box-shadow 0.22s var(--ease-out-expo), border-color 0.22s var(--ease-out-expo);
}
:where(.model-response-text, markdown-renderer) table:hover { border-color: var(--singularity-glow-strong); ${tableShadowsHover} }
:where(.model-response-text, markdown-renderer) th {
background: var(--gm-surface-variant, #f1f3f4);
color: var(--gm-on-surface);
padding: 16px 18px !important;
font-weight: 700;
font-size: 0.95em;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
}
:where(.model-response-text, markdown-renderer) td {
padding: 14px 18px !important;
border-top: 1px solid var(--gm-outline-variant, rgba(128,128,128,0.05));
background: var(--gm-surface, transparent);
transition: background 0.1s;
}
:where(.model-response-text, markdown-renderer) tr:hover td { background: var(--gm-secondary-container, rgba(66, 133, 244, 0.05)); }
${tableMobileCSS}
:where(.model-response-text, markdown-renderer) mjx-container[display="true"] {
margin: 2.5em 0 !important;
padding: 1.5em 2em !important;
background: var(--gm-surface-variant, rgba(128,128,128,0.03));
border-radius: 12px;
border: 1px solid var(--gm-outline-variant, transparent);
color: var(--gm-on-surface) !important;
overflow-x: auto;
}
${imagesCSS}
.input-area-container { position: relative; padding-bottom: 40px !important; }
/* 自定义拖放框:与输入区域宽度对齐 */
.sg-drop-overlay {
position: absolute;
left: 12px;
right: 12px;
bottom: 8px;
min-height: 64px;
border-radius: 18px;
border: 1.5px dashed var(--accent, #8ab4f8);
background: rgba(138, 180, 248, 0.06);
display: none;
align-items: center;
justify-content: center;
pointer-events: none;
box-sizing: border-box;
color: var(--gm-on-surface-variant, #8ab4f8);
font-size: 0.95em;
text-align: center;
}
.sg-drop-overlay--visible { display: flex; }
@media (forced-colors: active) {
code-block,
:where(.model-response-text, markdown-renderer) table,
:where(.model-response-text, markdown-renderer) blockquote,
:where(.model-response-text, markdown-renderer) mjx-container[display="true"] {
box-shadow: none !important;
background: Canvas !important;
color: CanvasText !important;
border: 1px solid CanvasText !important;
}
.code-block-decoration {
background: Canvas !important;
border-bottom: 1px solid CanvasText !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
:where(.model-response-text, markdown-renderer) :is(h1,h2,h3,h4)::after {
background: CanvasText !important;
opacity: 1 !important;
}
}
`;
if (typeof GM_addStyle !== 'undefined') {
GM_addStyle(css);
} else {
const style = document.createElement('style');
style.textContent = css;
document.documentElement.appendChild(style);
}
// =========================
// 上传文件:自定义拖放框,对齐输入框
// =========================
const DROP_HINT_RE = /(将文件拖放到此处|将文件拖放到这里|Drop (files?|file) here)/i;
function hideNativeDropOverlay() {
if (!document.body) return;
const nodes = document.querySelectorAll('div,span,p');
for (const el of nodes) {
const t = (el.textContent || '').trim();
if (!t || !DROP_HINT_RE.test(t)) continue;
const box = el.closest('div');
if (box && !box.dataset.sgDropHidden) {
box.dataset.sgDropHidden = '1';
box.style.setProperty('display', 'none', 'important');
box.style.setProperty('pointer-events', 'none', 'important');
}
}
}
let customDropOverlay = null;
function ensureCustomDropOverlay() {
const root =
document.querySelector('.input-area-container') ||
document.querySelector('.bottom-container') ||
document.body;
if (!root) return null;
if (!customDropOverlay) {
customDropOverlay = document.createElement('div');
customDropOverlay.className = 'sg-drop-overlay';
customDropOverlay.textContent = '将文件拖放到此处';
root.appendChild(customDropOverlay);
}
return customDropOverlay;
}
function setDropOverlayVisible(visible) {
const ov = ensureCustomDropOverlay();
if (!ov) return;
if (visible) ov.classList.add('sg-drop-overlay--visible');
else ov.classList.remove('sg-drop-overlay--visible');
hideNativeDropOverlay();
}
function isFileDrag(evt) {
const dt = evt.dataTransfer;
if (!dt) return false;
const types = dt.types;
if (!types) return false;
return types.includes
? types.includes('Files')
: Array.prototype.indexOf.call(types, 'Files') !== -1;
}
let dragDepth = 0;
window.addEventListener('dragenter', (e) => {
if (!isFileDrag(e)) return;
dragDepth++;
setDropOverlayVisible(true);
});
window.addEventListener('dragover', (e) => {
if (!isFileDrag(e)) return;
e.preventDefault();
});
window.addEventListener('dragleave', (e) => {
if (!isFileDrag(e)) return;
dragDepth = Math.max(0, dragDepth - 1);
if (dragDepth === 0) setDropOverlayVisible(false);
});
window.addEventListener('drop', (e) => {
if (!isFileDrag(e)) return;
dragDepth = 0;
setDropOverlayVisible(false);
});
if (DEBUG) console.log('Gemini Singularity (V0.9) Activated.', cfg);
})();