Claude Menu Click Blocker with Compact Hidden Controls

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
})();