Blocks accidental clicks on Claude menu with space-saving hidden controls
// ==UserScript==
// @name Claude Menu Click Blocker with Compact Hidden Controls
// @namespace http://tampermonkey.net/
// @version 1.6
// @description Blocks accidental clicks on Claude menu with space-saving hidden controls
// @author Nirvash
// @match https://claude.ai/chat/*
// @match https://claude.ai/chats
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let unlockTimer = null;
const unlockDuration = 10000; // 10 seconds in milliseconds
const blockerWidth = 250; // Width in pixels of the click-blocking area
let isBlockerEnabled = true; // Default state is enabled
let isTemporarilyUnblocked = false; // Track temporary unlock state
let unlockEndTime = 0;
function createClickBlocker() {
console.log('Creating Claude menu click blocker...');
// Remove existing blocker if any
const existingBlocker = document.getElementById('claude-click-blocker');
if (existingBlocker) {
existingBlocker.remove();
}
// Only create if enabled and not temporarily unblocked
if (!isBlockerEnabled || isTemporarilyUnblocked) {
return;
}
// Create a transparent overlay div
const blocker = document.createElement('div');
blocker.id = 'claude-click-blocker';
// Style the blocker
blocker.style.position = 'fixed';
blocker.style.top = '0';
blocker.style.left = '0';
blocker.style.width = `${blockerWidth}px`; // Width of the click-blocking area
blocker.style.height = '100%';
blocker.style.zIndex = '9999'; // Set z-index below our buttons
blocker.style.pointerEvents = 'all'; // Capture all pointer events
blocker.style.cursor = 'default'; // Default cursor to not indicate it's clickable
// Add click handler
blocker.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
console.log('Click blocked on Claude menu edge');
return false;
});
// Add the blocker to the document
document.body.appendChild(blocker);
console.log('Claude menu click blocker added to page');
}
function createCompactControls() {
// Remove existing controls if any
const existingControls = document.getElementById('claude-blocker-controls');
if (existingControls) {
existingControls.remove();
}
// Create main container
const container = document.createElement('div');
container.id = 'claude-blocker-controls';
// Style the container - initially collapsed
container.style.position = 'fixed';
container.style.top = '10px';
container.style.left = '0';
container.style.zIndex = '10001';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
container.style.borderRadius = '0 8px 8px 0';
container.style.boxShadow = '2px 2px 10px rgba(0, 0, 0, 0.2)';
container.style.transition = 'transform 0.3s ease';
container.style.transform = 'translateX(-80%)'; // Initially mostly hidden
container.style.overflow = 'hidden';
// Status indicator (always visible part)
const statusIndicator = document.createElement('div');
statusIndicator.id = 'claude-blocker-status';
statusIndicator.style.width = '30px';
statusIndicator.style.height = '30px';
statusIndicator.style.borderRadius = '0 4px 4px 0';
statusIndicator.style.position = 'absolute';
statusIndicator.style.right = '0';
statusIndicator.style.top = '0';
statusIndicator.style.display = 'flex';
statusIndicator.style.alignItems = 'center';
statusIndicator.style.justifyContent = 'center';
statusIndicator.style.fontWeight = 'bold';
statusIndicator.style.fontSize = '16px';
updateStatusIndicator(statusIndicator);
// Create buttons container
const buttonsContainer = document.createElement('div');
buttonsContainer.style.padding = '10px';
buttonsContainer.style.display = 'flex';
buttonsContainer.style.flexDirection = 'column';
buttonsContainer.style.gap = '8px';
buttonsContainer.style.minWidth = '120px'; // Ensure enough width for buttons
// Create temporary unlock button
const tempUnlockButton = document.createElement('button');
tempUnlockButton.id = 'claude-temp-unlock-button';
// Style the temporary unlock button - more compact
tempUnlockButton.style.padding = '6px 10px';
tempUnlockButton.style.color = 'white';
tempUnlockButton.style.border = 'none';
tempUnlockButton.style.borderRadius = '4px';
tempUnlockButton.style.cursor = isBlockerEnabled ? 'pointer' : 'not-allowed';
tempUnlockButton.style.fontSize = '12px';
tempUnlockButton.style.fontWeight = 'bold';
tempUnlockButton.style.width = '100%';
tempUnlockButton.style.transition = 'background-color 0.2s';
// Create toggle button
const toggleButton = document.createElement('button');
toggleButton.id = 'claude-toggle-button';
// Style the toggle button - more compact
toggleButton.style.padding = '6px 10px';
toggleButton.style.color = 'white';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '4px';
toggleButton.style.cursor = 'pointer';
toggleButton.style.fontSize = '12px';
toggleButton.style.fontWeight = 'bold';
toggleButton.style.width = '100%';
toggleButton.style.transition = 'background-color 0.2s';
// Set button states
updateTempButtonState(tempUnlockButton);
updateToggleButtonState(toggleButton);
// Add button functionality
tempUnlockButton.addEventListener('click', function() {
toggleTemporaryUnlock();
updateStatusIndicator(statusIndicator);
});
toggleButton.addEventListener('click', function() {
toggleBlocker();
updateStatusIndicator(statusIndicator);
});
// Add hover expand/collapse functionality
container.addEventListener('mouseenter', function() {
this.style.transform = 'translateX(0)'; // Fully expand
});
container.addEventListener('mouseleave', function() {
this.style.transform = 'translateX(-80%)'; // Mostly hide
});
// Assemble the control elements
container.appendChild(statusIndicator);
buttonsContainer.appendChild(tempUnlockButton);
buttonsContainer.appendChild(toggleButton);
container.appendChild(buttonsContainer);
// Add to document
document.body.appendChild(container);
console.log('Compact control panel created');
}
function updateStatusIndicator(indicator) {
if (!indicator) {
indicator = document.getElementById('claude-blocker-status');
if (!indicator) return;
}
// Determine status color and icon
let color, icon;
if (!isBlockerEnabled) {
color = '#2ecc71'; // Green for fully disabled
icon = '🔓';
} else if (isTemporarilyUnblocked) {
color = '#f39c12'; // Orange for temporarily unblocked
icon = '⏱️';
} else {
color = '#e74c3c'; // Red for active blocking
icon = '🔒';
}
indicator.style.backgroundColor = color;
indicator.textContent = icon;
}
function determineButtonColor(buttonType) {
if (buttonType === 'temp') {
// If blocker is disabled, temp button is disabled/gray
if (!isBlockerEnabled) {
return '#95a5a6'; // Gray for disabled state
}
// If temporarily unblocked, show orange
return isTemporarilyUnblocked ? '#f39c12' : '#4a90e2';
} else if (buttonType === 'toggle') {
// Toggle button: red when enabled, green when disabled
return isBlockerEnabled ? '#e74c3c' : '#2ecc71';
}
return '#4a90e2'; // Default blue
}
function updateTempButtonState(button) {
if (!button) {
button = document.getElementById('claude-temp-unlock-button');
if (!button) return;
}
// Disable appearance if blocker is fully disabled
if (!isBlockerEnabled) {
button.textContent = '一時解除 (無効)';
button.style.backgroundColor = '#95a5a6'; // Gray
button.style.cursor = 'not-allowed';
return;
}
// Normal state (enabled/unlocked)
button.style.cursor = 'pointer';
if (isTemporarilyUnblocked) {
// Countdown state
let secondsLeft = Math.ceil((unlockEndTime - Date.now()) / 1000);
button.textContent = `⏱️ ${secondsLeft}秒後`;
button.style.backgroundColor = '#f39c12'; // Orange
} else {
// Regular state
button.textContent = '🔓 10秒間解除';
button.style.backgroundColor = '#4a90e2'; // Blue
}
}
function updateToggleButtonState(button) {
if (!button) {
button = document.getElementById('claude-toggle-button');
if (!button) return;
}
button.textContent = isBlockerEnabled ? '🔒 ブロック中' : '🔓 解除中';
button.style.backgroundColor = determineButtonColor('toggle');
}
function toggleBlocker() {
isBlockerEnabled = !isBlockerEnabled;
// Save state to localStorage
localStorage.setItem('claudeBlockerEnabled', isBlockerEnabled.toString());
// If disabling, cancel any temporary unlock
if (!isBlockerEnabled) {
isTemporarilyUnblocked = false;
if (unlockTimer) {
clearTimeout(unlockTimer);
unlockTimer = null;
}
}
// Update the UI
updateToggleButtonState();
updateTempButtonState();
updateStatusIndicator();
// Update the blocker
if (isBlockerEnabled && !isTemporarilyUnblocked) {
createClickBlocker();
} else {
const blocker = document.getElementById('claude-click-blocker');
if (blocker) {
blocker.remove();
}
}
console.log(`Blocker ${isBlockerEnabled ? 'enabled' : 'disabled'}`);
}
function toggleTemporaryUnlock() {
// If blocker is fully disabled, do nothing on temp button click
if (!isBlockerEnabled) {
return;
}
if (isTemporarilyUnblocked) {
// Cancel temporary unlock early
isTemporarilyUnblocked = false;
if (unlockTimer) {
clearTimeout(unlockTimer);
unlockTimer = null;
}
createClickBlocker();
} else {
// Start temporary unlock
isTemporarilyUnblocked = true;
unlockEndTime = Date.now() + unlockDuration;
// Remove the blocker
const blocker = document.getElementById('claude-click-blocker');
if (blocker) {
blocker.remove();
}
// Clear any existing timer
if (unlockTimer) {
clearTimeout(unlockTimer);
}
// Start the countdown UI update
startCountdown();
// Set a timer to re-enable the blocker
unlockTimer = setTimeout(() => {
console.log('Temporary unlock period ended');
unlockTimer = null;
isTemporarilyUnblocked = false;
if (isBlockerEnabled) { // Only recreate if globally enabled
createClickBlocker();
}
updateTempButtonState();
updateStatusIndicator();
}, unlockDuration);
}
// Update button states
updateTempButtonState();
}
function startCountdown() {
// Update every second
const updateInterval = setInterval(() => {
if (!isTemporarilyUnblocked || !isBlockerEnabled) {
clearInterval(updateInterval);
return;
}
const secondsLeft = Math.ceil((unlockEndTime - Date.now()) / 1000);
if (secondsLeft <= 0) {
clearInterval(updateInterval);
return;
}
const tempButton = document.getElementById('claude-temp-unlock-button');
if (tempButton) {
tempButton.textContent = `⏱️ ${secondsLeft}秒後`;
}
updateStatusIndicator();
}, 1000);
}
// Initialize the script
function initialize() {
console.log('Initializing Claude menu click blocker with compact controls');
// Load saved state
const savedState = localStorage.getItem('claudeBlockerEnabled');
if (savedState !== null) {
isBlockerEnabled = savedState === 'true';
}
// Reset temporary state on initialization
isTemporarilyUnblocked = false;
// Create blocker if enabled
if (isBlockerEnabled) {
createClickBlocker();
}
// Create control panel
createCompactControls();
}
// Run on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
// DOM already loaded, run immediately
initialize();
}
// Also run after a short delay to catch late-loading elements
setTimeout(initialize, 1000);
// Re-initialize on URL change (for SPA navigation)
let lastUrl = location.href;
new MutationObserver(() => {
if (lastUrl !== location.href) {
lastUrl = location.href;
setTimeout(initialize, 500);
}
}).observe(document, {subtree: true, childList: true});
// Add a periodic checker to ensure controls are visible and synced
setInterval(() => {
const controls = document.getElementById('claude-blocker-controls');
if (!controls) {
console.log('Controls missing, recreating...');
createCompactControls();
} else {
// Update button states to ensure they're in sync
updateTempButtonState();
updateToggleButtonState();
updateStatusIndicator();
}
}, 5000);
})();