Claude Menu Click Blocker with Compact Hidden Controls

Blocks accidental clicks on Claude menu with space-saving hidden controls

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();