// ==UserScript==
// @name ChatGPT/GROK/DEEPSEEK Scroll Navigator
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Adds a smart scroll button for easy navigation in AI chat interfaces
// @author Lepturus
// @match *://chatgpt.com/*
// @match *://chat.deepseek.com/*
// @match *://grok.com/*
// @icon https://chatgpt.com/favicon.ico
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration for different platforms
const platformConfig = {
chatgpt: {
container: '.flex.basis-auto.flex-col.grow.overflow-hidden div.relative.h-full div.overflow-y-auto',
name: 'ChatGPT'
},
grok: {
container: '.scrollbar-gutter-stable',
name: 'Grok'
},
deepseek: {
container: '.scrollable',
name: 'DeepSeek'
}
};
// Main initialization function
function initScrollNavigator() {
let currentPlatform = null;
// Determine current platform
if (/chatgpt/.test(location.hostname)) currentPlatform = 'chatgpt';
else if (/grok/.test(location.hostname)) currentPlatform = 'grok';
else if (/deepseek/.test(location.hostname)) currentPlatform = 'deepseek';
if (!currentPlatform) return;
const config = platformConfig[currentPlatform];
let scrollButton = null;
let chatContainer = null;
let isInitialized = false;
// Create scroll button
function createScrollButton() {
if (document.getElementById('scrollNavigatorBtn')) return;
scrollButton = document.createElement('div');
scrollButton.id = 'scrollNavigatorBtn';
scrollButton.innerHTML = '⬆';
scrollButton.title = `Scroll to bottom (${config.name})`;
// Apply styles
Object.assign(scrollButton.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
width: '45px',
height: '45px',
lineHeight: '45px',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
borderRadius: '50%',
fontSize: '24px',
cursor: 'pointer',
zIndex: '10000',
opacity: '0',
transition: 'opacity 0.3s, transform 0.2s',
transform: 'scale(0.8)',
pointerEvents: 'none',
boxShadow: '0 2px 10px rgba(0,0,0,0.3)'
});
document.body.appendChild(scrollButton);
// Button click handler
scrollButton.addEventListener('click', handleScrollClick);
// Mouse events for showing/hiding
scrollButton.addEventListener('mouseenter', () => {
scrollButton.style.opacity = '1';
scrollButton.style.transform = 'scale(1)';
});
scrollButton.addEventListener('mouseleave', () => {
if (!isMouseNearButton()) {
scrollButton.style.opacity = '0';
scrollButton.style.transform = 'scale(0.8)';
}
});
// Show button when mouse approaches screen edge
document.addEventListener('mousemove', (e) => {
const rect = scrollButton.getBoundingClientRect();
const buttonCenterX = rect.left + rect.width / 2;
const buttonCenterY = rect.top + rect.height / 2;
const distance = Math.sqrt(
Math.pow(e.clientX - buttonCenterX, 2) +
Math.pow(e.clientY - buttonCenterY, 2)
);
if (distance < 120) {
scrollButton.style.opacity = '1';
scrollButton.style.transform = 'scale(1)';
scrollButton.style.pointerEvents = 'auto';
} else if (distance > 150 && !scrollButton.matches(':hover')) {
scrollButton.style.opacity = '0';
scrollButton.style.transform = 'scale(0.8)';
scrollButton.style.pointerEvents = 'none';
}
});
}
// Check if mouse is near button
function isMouseNearButton() {
const rect = scrollButton.getBoundingClientRect();
const buttonCenterX = rect.left + rect.width / 2;
const buttonCenterY = rect.top + rect.height / 2;
return (event) => {
const distance = Math.sqrt(
Math.pow(event.clientX - buttonCenterX, 2) +
Math.pow(event.clientY - buttonCenterY, 2)
);
return distance < 100;
};
}
// Handle scroll button click
function handleScrollClick() {
if (!chatContainer) return;
const isNearTop = chatContainer.scrollTop < 100;
if (isNearTop) {
chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' });
scrollButton.innerHTML = '⬆';
scrollButton.title = 'Scroll to top';
} else {
chatContainer.scrollTo({ top: 0, behavior: 'smooth' });
scrollButton.innerHTML = '⬇';
scrollButton.title = 'Scroll to bottom';
}
}
// Find chat container
function findChatContainer() {
try {
if (currentPlatform === 'deepseek') {
const containers = document.querySelectorAll(config.container);
if (containers.length > 1) return containers[1];
return containers[0];
}
return document.querySelector(config.container);
} catch (error) {
console.error('Error finding chat container:', error);
return null;
}
}
// Update button state based on scroll position
function updateButtonState() {
if (!chatContainer || !scrollButton) return;
const scrollThreshold = 100;
const isNearTop = chatContainer.scrollTop < scrollThreshold;
const isNearBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight < scrollThreshold;
if (isNearTop) {
scrollButton.innerHTML = '⬇';
scrollButton.title = 'Scroll to bottom';
} else if (isNearBottom) {
scrollButton.innerHTML = '⬆';
scrollButton.title = 'Scroll to top';
} else {
scrollButton.innerHTML = '⬇';
scrollButton.title = 'Scroll to bottom';
}
}
// Initialize the script
function initialize() {
if (isInitialized) return;
chatContainer = findChatContainer();
if (!chatContainer) {
setTimeout(initialize, 1000);
return;
}
createScrollButton();
// Add scroll listener to update button state
chatContainer.addEventListener('scroll', updateButtonState);
// Initial update
updateButtonState();
isInitialized = true;
console.log(`Scroll Navigator initialized for ${config.name}`);
}
// Start initialization
initialize();
// Reinitialize if DOM changes (for SPA navigation)
const observer = new MutationObserver(() => {
if (!chatContainer || !document.contains(chatContainer)) {
isInitialized = false;
chatContainer = null;
initialize();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initScrollNavigator);
} else {
initScrollNavigator();
}
})();