Skip to content

Instantly share code, notes, and snippets.

@kiranwayne
Last active December 1, 2025 05:04
Show Gist options
  • Select an option

  • Save kiranwayne/f462acd88a1ef4dea525c6521c2deefa to your computer and use it in GitHub Desktop.

Select an option

Save kiranwayne/f462acd88a1ef4dea525c6521c2deefa to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Gemini Enhanced
// @namespace http://tampermonkey.net/
// @version 1.4.0
// @description Customize chat content width on Google Gemini with a slider. Dark mode settings panel.
// @author kiranwayne
// @match https://gemini.google.com/*
// @updateURL https://gist.github.com/kiranwayne/f462acd88a1ef4dea525c6521c2deefa/raw/gemini_enhanced.js
// @downloadURL https://gist.github.com/kiranwayne/f462acd88a1ef4dea525c6521c2deefa/raw/gemini_enhanced.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-end
// ==/UserScript==
(async () => {
'use strict';
// --- Configuration & Constants ---
const SCRIPT_NAME = 'Gemini Enhanced';
const SCRIPT_VERSION = '1.4.0';
const SCRIPT_AUTHOR = 'kiranwayne';
const CONFIG_PREFIX = 'geminiEnhancedWidth_v1_';
const MAX_WIDTH_PX_KEY = CONFIG_PREFIX + 'maxWidthPx';
const USE_DEFAULT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultWidth';
const UI_VISIBLE_KEY = CONFIG_PREFIX + 'uiVisible';
const WIDTH_STYLE_ID = 'gemini-enhanced-width-style';
const GLOBAL_STYLE_ID = 'gemini-enhanced-global-style';
const SETTINGS_PANEL_ID = 'gemini-userscript-settings-panel';
// Targets both the conversation container and the user bubbles
const WIDTH_TARGET_SELECTOR = '.conversation-container, .conversation-container user-query';
// --- Constraints ---
const SCRIPT_DEFAULT_CUSTOM_WIDTH_PX = 1380;
const MIN_WIDTH_PX = 800;
const MAX_WIDTH_PX = 2000;
const STEP_WIDTH_PX = 10;
let config = {
maxWidthPx: SCRIPT_DEFAULT_CUSTOM_WIDTH_PX,
useDefaultWidth: true,
uiVisible: false,
};
let settingsPanel = null, widthSlider = null, widthLabel = null, widthInput = null;
let defaultWidthCheckbox = null;
let menuCommandId_ToggleUI = null;
// --- Helper Functions ---
async function loadSettings() {
config.useDefaultWidth = await GM_getValue(USE_DEFAULT_WIDTH_KEY, true);
config.maxWidthPx = await GM_getValue(MAX_WIDTH_PX_KEY, SCRIPT_DEFAULT_CUSTOM_WIDTH_PX);
config.maxWidthPx = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, config.maxWidthPx));
config.uiVisible = await GM_getValue(UI_VISIBLE_KEY, false);
}
async function saveSetting(key, value) {
if (key === MAX_WIDTH_PX_KEY) {
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, numValue));
await GM_setValue(key, clampedValue);
config.maxWidthPx = clampedValue;
}
} else {
await GM_setValue(key, value);
if (key === USE_DEFAULT_WIDTH_KEY) config.useDefaultWidth = value;
else if (key === UI_VISIBLE_KEY) config.uiVisible = value;
}
}
// --- Style Generation Functions ---
function getCustomWidthConstraintCss() {
if (config.useDefaultWidth) return '';
return `${WIDTH_TARGET_SELECTOR} { max-width: ${config.maxWidthPx}px !important; width: 100% !important; }`;
}
function getGlobalPanelCss() {
return `
#${SETTINGS_PANEL_ID} {
font-family: "Google Sans", Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 1.4;
position: fixed;
top: 70px; /* Moved down to avoid header overlap */
right: 20px;
z-index: 2147483647; /* Max Z-Index to ensure visibility */
background-color: #1e1f20;
color: #e3e3e3;
border: 1px solid #444746;
border-radius: 8px;
padding: 14px;
box-shadow: 0 4px 24px rgba(0,0,0,0.8);
min-width: 250px;
display: flex !important;
flex-direction: column;
gap: 4px;
opacity: 1 !important;
visibility: visible !important;
}
#${SETTINGS_PANEL_ID} h4 {
margin: 0 0 8px 0;
font-size: 15px;
font-weight: 600;
color: #e3e3e3;
padding-bottom: 6px;
border-bottom: 1px solid #444746;
display: flex;
justify-content: space-between;
align-items: center;
}
#${SETTINGS_PANEL_ID} .close-btn {
cursor: pointer;
font-size: 20px;
color: #8e918f;
line-height: 1;
font-weight: normal;
}
#${SETTINGS_PANEL_ID} .close-btn:hover { color: #fff; }
#${SETTINGS_PANEL_ID} .meta-info {
margin: 0;
font-size: 11px;
color: #c4c7c5;
opacity: 0.8;
line-height: 1.3;
}
#${SETTINGS_PANEL_ID} .control-group { margin-top: 10px; }
#${SETTINGS_PANEL_ID} label {
cursor: pointer;
display: flex;
align-items: center;
color: #e3e3e3;
margin-bottom: 8px;
}
#${SETTINGS_PANEL_ID} input[type="checkbox"] {
margin-right: 8px;
accent-color: #8ab4f8;
width: 14px; height: 14px;
}
#${SETTINGS_PANEL_ID} .width-controls-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
#${SETTINGS_PANEL_ID} input[type="range"] {
flex-grow: 1;
accent-color: #8ab4f8;
background: #444746;
height: 4px;
border-radius: 2px;
cursor: pointer;
}
#${SETTINGS_PANEL_ID} input[type="number"] {
width: 55px;
padding: 4px 6px;
background-color: #2a2b2d;
color: #e3e3e3;
border: 1px solid #444746;
border-radius: 6px;
text-align: left;
font-family: monospace;
font-size: 12px;
}
#${SETTINGS_PANEL_ID} input[type="number"]:focus { outline: 2px solid #8ab4f8; border-color: transparent; }
#${SETTINGS_PANEL_ID} .width-value-label {
min-width: 45px;
text-align: left;
font-family: monospace;
color: #e3e3e3;
font-size: 12px;
}
#${SETTINGS_PANEL_ID} input:disabled,
#${SETTINGS_PANEL_ID} .width-value-label.disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
}
// --- Style Injection ---
function injectOrUpdateStyle(styleId, cssContent) {
let style = document.getElementById(styleId);
if (cssContent && cssContent.trim() !== '') {
if (!style) {
style = document.createElement('style');
style.id = styleId;
style.textContent = cssContent;
// Append to documentElement (html) if head is missing or problematic, otherwise head
(document.head || document.documentElement).appendChild(style);
} else if (style.textContent !== cssContent) {
style.textContent = cssContent;
}
} else {
if (style) style.remove();
}
}
function applyStyles() {
injectOrUpdateStyle(GLOBAL_STYLE_ID, getGlobalPanelCss());
injectOrUpdateStyle(WIDTH_STYLE_ID, getCustomWidthConstraintCss());
}
// --- UI Logic ---
function updateUIState() {
if (!settingsPanel) return;
defaultWidthCheckbox.checked = config.useDefaultWidth;
const isCustomWidthEnabled = !config.useDefaultWidth;
[widthSlider, widthInput].forEach(el => el.disabled = !isCustomWidthEnabled);
const opacityValue = isCustomWidthEnabled ? 1 : 0.5;
widthSlider.style.opacity = opacityValue;
widthInput.style.opacity = opacityValue;
widthLabel.style.opacity = opacityValue;
widthSlider.value = config.maxWidthPx;
widthInput.value = config.maxWidthPx;
widthLabel.textContent = `${config.maxWidthPx}px`;
}
async function closePanel() {
await saveSetting(UI_VISIBLE_KEY, false);
removeSettingsUI();
updateTampermonkeyMenu();
}
function removeSettingsUI() {
settingsPanel = document.getElementById(SETTINGS_PANEL_ID);
if (settingsPanel) settingsPanel.remove();
settingsPanel = null;
}
function createSettingsUI() {
if (document.getElementById(SETTINGS_PANEL_ID)) return;
if (!config.uiVisible) return;
// Use standard element creation to avoid TrustedHTML issues
settingsPanel = document.createElement('div');
settingsPanel.id = SETTINGS_PANEL_ID;
// Header
const header = document.createElement('h4');
const titleSpan = document.createElement('span');
titleSpan.textContent = SCRIPT_NAME;
const closeBtn = document.createElement('span');
closeBtn.className = 'close-btn';
closeBtn.textContent = '×';
closeBtn.onclick = closePanel;
header.appendChild(titleSpan);
header.appendChild(closeBtn);
settingsPanel.appendChild(header);
// Meta Info
const versionP = document.createElement('p');
versionP.className = 'meta-info';
versionP.textContent = `Version: ${SCRIPT_VERSION}`;
settingsPanel.appendChild(versionP);
const authorP = document.createElement('p');
authorP.className = 'meta-info';
authorP.textContent = `Author: ${SCRIPT_AUTHOR}`;
settingsPanel.appendChild(authorP);
// Controls
const controlContainer = document.createElement('div');
controlContainer.className = 'control-group';
// Checkbox
const checkboxWrapper = document.createElement('div');
defaultWidthCheckbox = document.createElement('input');
defaultWidthCheckbox.type = 'checkbox';
defaultWidthCheckbox.id = 'gemini-width-toggle';
const checkboxLabel = document.createElement('label');
checkboxLabel.htmlFor = 'gemini-width-toggle';
checkboxLabel.appendChild(defaultWidthCheckbox);
checkboxLabel.appendChild(document.createTextNode(' Use Gemini Default Width'));
checkboxWrapper.appendChild(checkboxLabel);
// Slider Row
const sliderWrapper = document.createElement('div');
sliderWrapper.className = 'width-controls-wrapper';
widthLabel = document.createElement('span');
widthLabel.className = 'width-value-label';
widthSlider = document.createElement('input');
widthSlider.type = 'range';
widthSlider.min = MIN_WIDTH_PX;
widthSlider.max = MAX_WIDTH_PX;
widthSlider.step = STEP_WIDTH_PX;
widthInput = document.createElement('input');
widthInput.type = 'number';
widthInput.min = MIN_WIDTH_PX;
widthInput.max = MAX_WIDTH_PX;
widthInput.step = STEP_WIDTH_PX;
sliderWrapper.appendChild(widthLabel);
sliderWrapper.appendChild(widthSlider);
sliderWrapper.appendChild(widthInput);
controlContainer.appendChild(checkboxWrapper);
controlContainer.appendChild(sliderWrapper);
settingsPanel.appendChild(controlContainer);
document.body.appendChild(settingsPanel);
// --- Event Listeners ---
defaultWidthCheckbox.addEventListener('change', async (e) => {
await saveSetting(USE_DEFAULT_WIDTH_KEY, e.target.checked);
applyStyles();
updateUIState();
});
const applyWidthChange = (val) => {
config.maxWidthPx = parseInt(val, 10);
widthLabel.textContent = `${config.maxWidthPx}px`;
if (!config.useDefaultWidth) injectOrUpdateStyle(WIDTH_STYLE_ID, getCustomWidthConstraintCss());
};
// Slider (Instant)
widthSlider.addEventListener('input', (e) => {
widthInput.value = e.target.value;
applyWidthChange(e.target.value);
});
widthSlider.addEventListener('change', async (e) => {
await saveSetting(MAX_WIDTH_PX_KEY, e.target.value);
});
// Input (Commit on change)
const commitInputValue = async () => {
let val = parseInt(widthInput.value, 10);
if (isNaN(val)) {
widthInput.value = config.maxWidthPx;
return;
}
val = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, val));
widthInput.value = val;
widthSlider.value = val;
await saveSetting(MAX_WIDTH_PX_KEY, val);
applyWidthChange(val);
};
widthInput.addEventListener('change', commitInputValue);
widthInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
widthInput.blur();
}
});
updateUIState();
}
function updateTampermonkeyMenu() {
if (menuCommandId_ToggleUI !== null) {
GM_unregisterMenuCommand(menuCommandId_ToggleUI);
}
const label = config.uiVisible ? 'Hide Gemini Width Settings' : 'Show Gemini Width Settings';
menuCommandId_ToggleUI = GM_registerMenuCommand(label, async () => {
config.uiVisible = !config.uiVisible;
await saveSetting(UI_VISIBLE_KEY, config.uiVisible);
if (config.uiVisible) createSettingsUI();
else removeSettingsUI();
updateTampermonkeyMenu();
});
}
// --- Initialization ---
await loadSettings();
applyStyles();
// Slight delay to ensure body is ready for panel
if (config.uiVisible) {
setTimeout(createSettingsUI, 500);
}
updateTampermonkeyMenu();
// Re-apply styles if page dynamically clears head
const headObserver = new MutationObserver(() => {
if (!document.getElementById(WIDTH_STYLE_ID) || !document.getElementById(GLOBAL_STYLE_ID)) {
applyStyles();
}
});
headObserver.observe(document.head || document.documentElement, { childList: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment