Claude Menu Click Blocker with Compact Hidden Controls

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

  1. // ==UserScript==
  2. // @name Claude Menu Click Blocker with Compact Hidden Controls
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.6
  5. // @description Blocks accidental clicks on Claude menu with space-saving hidden controls
  6. // @author Nirvash
  7. // @match https://claude.ai/chat/*
  8. // @match https://claude.ai/chats
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. let unlockTimer = null;
  17. const unlockDuration = 10000; // 10 seconds in milliseconds
  18. const blockerWidth = 250; // Width in pixels of the click-blocking area
  19. let isBlockerEnabled = true; // Default state is enabled
  20. let isTemporarilyUnblocked = false; // Track temporary unlock state
  21. let unlockEndTime = 0;
  22.  
  23. function createClickBlocker() {
  24. console.log('Creating Claude menu click blocker...');
  25.  
  26. // Remove existing blocker if any
  27. const existingBlocker = document.getElementById('claude-click-blocker');
  28. if (existingBlocker) {
  29. existingBlocker.remove();
  30. }
  31.  
  32. // Only create if enabled and not temporarily unblocked
  33. if (!isBlockerEnabled || isTemporarilyUnblocked) {
  34. return;
  35. }
  36.  
  37. // Create a transparent overlay div
  38. const blocker = document.createElement('div');
  39. blocker.id = 'claude-click-blocker';
  40.  
  41. // Style the blocker
  42. blocker.style.position = 'fixed';
  43. blocker.style.top = '0';
  44. blocker.style.left = '0';
  45. blocker.style.width = `${blockerWidth}px`; // Width of the click-blocking area
  46. blocker.style.height = '100%';
  47. blocker.style.zIndex = '9999'; // Set z-index below our buttons
  48. blocker.style.pointerEvents = 'all'; // Capture all pointer events
  49. blocker.style.cursor = 'default'; // Default cursor to not indicate it's clickable
  50.  
  51. // Add click handler
  52. blocker.addEventListener('click', function(e) {
  53. e.stopPropagation();
  54. e.preventDefault();
  55. console.log('Click blocked on Claude menu edge');
  56. return false;
  57. });
  58.  
  59. // Add the blocker to the document
  60. document.body.appendChild(blocker);
  61. console.log('Claude menu click blocker added to page');
  62. }
  63.  
  64. function createCompactControls() {
  65. // Remove existing controls if any
  66. const existingControls = document.getElementById('claude-blocker-controls');
  67. if (existingControls) {
  68. existingControls.remove();
  69. }
  70.  
  71. // Create main container
  72. const container = document.createElement('div');
  73. container.id = 'claude-blocker-controls';
  74.  
  75. // Style the container - initially collapsed
  76. container.style.position = 'fixed';
  77. container.style.top = '10px';
  78. container.style.left = '0';
  79. container.style.zIndex = '10001';
  80. container.style.display = 'flex';
  81. container.style.flexDirection = 'column';
  82. container.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
  83. container.style.borderRadius = '0 8px 8px 0';
  84. container.style.boxShadow = '2px 2px 10px rgba(0, 0, 0, 0.2)';
  85. container.style.transition = 'transform 0.3s ease';
  86. container.style.transform = 'translateX(-80%)'; // Initially mostly hidden
  87. container.style.overflow = 'hidden';
  88.  
  89. // Status indicator (always visible part)
  90. const statusIndicator = document.createElement('div');
  91. statusIndicator.id = 'claude-blocker-status';
  92. statusIndicator.style.width = '30px';
  93. statusIndicator.style.height = '30px';
  94. statusIndicator.style.borderRadius = '0 4px 4px 0';
  95. statusIndicator.style.position = 'absolute';
  96. statusIndicator.style.right = '0';
  97. statusIndicator.style.top = '0';
  98. statusIndicator.style.display = 'flex';
  99. statusIndicator.style.alignItems = 'center';
  100. statusIndicator.style.justifyContent = 'center';
  101. statusIndicator.style.fontWeight = 'bold';
  102. statusIndicator.style.fontSize = '16px';
  103. updateStatusIndicator(statusIndicator);
  104.  
  105. // Create buttons container
  106. const buttonsContainer = document.createElement('div');
  107. buttonsContainer.style.padding = '10px';
  108. buttonsContainer.style.display = 'flex';
  109. buttonsContainer.style.flexDirection = 'column';
  110. buttonsContainer.style.gap = '8px';
  111. buttonsContainer.style.minWidth = '120px'; // Ensure enough width for buttons
  112.  
  113. // Create temporary unlock button
  114. const tempUnlockButton = document.createElement('button');
  115. tempUnlockButton.id = 'claude-temp-unlock-button';
  116.  
  117. // Style the temporary unlock button - more compact
  118. tempUnlockButton.style.padding = '6px 10px';
  119. tempUnlockButton.style.color = 'white';
  120. tempUnlockButton.style.border = 'none';
  121. tempUnlockButton.style.borderRadius = '4px';
  122. tempUnlockButton.style.cursor = isBlockerEnabled ? 'pointer' : 'not-allowed';
  123. tempUnlockButton.style.fontSize = '12px';
  124. tempUnlockButton.style.fontWeight = 'bold';
  125. tempUnlockButton.style.width = '100%';
  126. tempUnlockButton.style.transition = 'background-color 0.2s';
  127.  
  128. // Create toggle button
  129. const toggleButton = document.createElement('button');
  130. toggleButton.id = 'claude-toggle-button';
  131.  
  132. // Style the toggle button - more compact
  133. toggleButton.style.padding = '6px 10px';
  134. toggleButton.style.color = 'white';
  135. toggleButton.style.border = 'none';
  136. toggleButton.style.borderRadius = '4px';
  137. toggleButton.style.cursor = 'pointer';
  138. toggleButton.style.fontSize = '12px';
  139. toggleButton.style.fontWeight = 'bold';
  140. toggleButton.style.width = '100%';
  141. toggleButton.style.transition = 'background-color 0.2s';
  142.  
  143. // Set button states
  144. updateTempButtonState(tempUnlockButton);
  145. updateToggleButtonState(toggleButton);
  146.  
  147. // Add button functionality
  148. tempUnlockButton.addEventListener('click', function() {
  149. toggleTemporaryUnlock();
  150. updateStatusIndicator(statusIndicator);
  151. });
  152.  
  153. toggleButton.addEventListener('click', function() {
  154. toggleBlocker();
  155. updateStatusIndicator(statusIndicator);
  156. });
  157.  
  158. // Add hover expand/collapse functionality
  159. container.addEventListener('mouseenter', function() {
  160. this.style.transform = 'translateX(0)'; // Fully expand
  161. });
  162.  
  163. container.addEventListener('mouseleave', function() {
  164. this.style.transform = 'translateX(-80%)'; // Mostly hide
  165. });
  166.  
  167. // Assemble the control elements
  168. container.appendChild(statusIndicator);
  169. buttonsContainer.appendChild(tempUnlockButton);
  170. buttonsContainer.appendChild(toggleButton);
  171. container.appendChild(buttonsContainer);
  172.  
  173. // Add to document
  174. document.body.appendChild(container);
  175. console.log('Compact control panel created');
  176. }
  177.  
  178. function updateStatusIndicator(indicator) {
  179. if (!indicator) {
  180. indicator = document.getElementById('claude-blocker-status');
  181. if (!indicator) return;
  182. }
  183.  
  184. // Determine status color and icon
  185. let color, icon;
  186.  
  187. if (!isBlockerEnabled) {
  188. color = '#2ecc71'; // Green for fully disabled
  189. icon = '🔓';
  190. } else if (isTemporarilyUnblocked) {
  191. color = '#f39c12'; // Orange for temporarily unblocked
  192. icon = '⏱️';
  193. } else {
  194. color = '#e74c3c'; // Red for active blocking
  195. icon = '🔒';
  196. }
  197.  
  198. indicator.style.backgroundColor = color;
  199. indicator.textContent = icon;
  200. }
  201.  
  202. function determineButtonColor(buttonType) {
  203. if (buttonType === 'temp') {
  204. // If blocker is disabled, temp button is disabled/gray
  205. if (!isBlockerEnabled) {
  206. return '#95a5a6'; // Gray for disabled state
  207. }
  208. // If temporarily unblocked, show orange
  209. return isTemporarilyUnblocked ? '#f39c12' : '#4a90e2';
  210. } else if (buttonType === 'toggle') {
  211. // Toggle button: red when enabled, green when disabled
  212. return isBlockerEnabled ? '#e74c3c' : '#2ecc71';
  213. }
  214. return '#4a90e2'; // Default blue
  215. }
  216.  
  217. function updateTempButtonState(button) {
  218. if (!button) {
  219. button = document.getElementById('claude-temp-unlock-button');
  220. if (!button) return;
  221. }
  222.  
  223. // Disable appearance if blocker is fully disabled
  224. if (!isBlockerEnabled) {
  225. button.textContent = '一時解除 (無効)';
  226. button.style.backgroundColor = '#95a5a6'; // Gray
  227. button.style.cursor = 'not-allowed';
  228. return;
  229. }
  230.  
  231. // Normal state (enabled/unlocked)
  232. button.style.cursor = 'pointer';
  233.  
  234. if (isTemporarilyUnblocked) {
  235. // Countdown state
  236. let secondsLeft = Math.ceil((unlockEndTime - Date.now()) / 1000);
  237. button.textContent = `⏱️ ${secondsLeft}秒後`;
  238. button.style.backgroundColor = '#f39c12'; // Orange
  239. } else {
  240. // Regular state
  241. button.textContent = '🔓 10秒間解除';
  242. button.style.backgroundColor = '#4a90e2'; // Blue
  243. }
  244. }
  245.  
  246. function updateToggleButtonState(button) {
  247. if (!button) {
  248. button = document.getElementById('claude-toggle-button');
  249. if (!button) return;
  250. }
  251.  
  252. button.textContent = isBlockerEnabled ? '🔒 ブロック中' : '🔓 解除中';
  253. button.style.backgroundColor = determineButtonColor('toggle');
  254. }
  255.  
  256. function toggleBlocker() {
  257. isBlockerEnabled = !isBlockerEnabled;
  258.  
  259. // Save state to localStorage
  260. localStorage.setItem('claudeBlockerEnabled', isBlockerEnabled.toString());
  261.  
  262. // If disabling, cancel any temporary unlock
  263. if (!isBlockerEnabled) {
  264. isTemporarilyUnblocked = false;
  265. if (unlockTimer) {
  266. clearTimeout(unlockTimer);
  267. unlockTimer = null;
  268. }
  269. }
  270.  
  271. // Update the UI
  272. updateToggleButtonState();
  273. updateTempButtonState();
  274. updateStatusIndicator();
  275.  
  276. // Update the blocker
  277. if (isBlockerEnabled && !isTemporarilyUnblocked) {
  278. createClickBlocker();
  279. } else {
  280. const blocker = document.getElementById('claude-click-blocker');
  281. if (blocker) {
  282. blocker.remove();
  283. }
  284. }
  285.  
  286. console.log(`Blocker ${isBlockerEnabled ? 'enabled' : 'disabled'}`);
  287. }
  288.  
  289. function toggleTemporaryUnlock() {
  290. // If blocker is fully disabled, do nothing on temp button click
  291. if (!isBlockerEnabled) {
  292. return;
  293. }
  294.  
  295. if (isTemporarilyUnblocked) {
  296. // Cancel temporary unlock early
  297. isTemporarilyUnblocked = false;
  298. if (unlockTimer) {
  299. clearTimeout(unlockTimer);
  300. unlockTimer = null;
  301. }
  302. createClickBlocker();
  303. } else {
  304. // Start temporary unlock
  305. isTemporarilyUnblocked = true;
  306. unlockEndTime = Date.now() + unlockDuration;
  307.  
  308. // Remove the blocker
  309. const blocker = document.getElementById('claude-click-blocker');
  310. if (blocker) {
  311. blocker.remove();
  312. }
  313.  
  314. // Clear any existing timer
  315. if (unlockTimer) {
  316. clearTimeout(unlockTimer);
  317. }
  318.  
  319. // Start the countdown UI update
  320. startCountdown();
  321.  
  322. // Set a timer to re-enable the blocker
  323. unlockTimer = setTimeout(() => {
  324. console.log('Temporary unlock period ended');
  325. unlockTimer = null;
  326. isTemporarilyUnblocked = false;
  327. if (isBlockerEnabled) { // Only recreate if globally enabled
  328. createClickBlocker();
  329. }
  330. updateTempButtonState();
  331. updateStatusIndicator();
  332. }, unlockDuration);
  333. }
  334.  
  335. // Update button states
  336. updateTempButtonState();
  337. }
  338.  
  339. function startCountdown() {
  340. // Update every second
  341. const updateInterval = setInterval(() => {
  342. if (!isTemporarilyUnblocked || !isBlockerEnabled) {
  343. clearInterval(updateInterval);
  344. return;
  345. }
  346.  
  347. const secondsLeft = Math.ceil((unlockEndTime - Date.now()) / 1000);
  348.  
  349. if (secondsLeft <= 0) {
  350. clearInterval(updateInterval);
  351. return;
  352. }
  353.  
  354. const tempButton = document.getElementById('claude-temp-unlock-button');
  355. if (tempButton) {
  356. tempButton.textContent = `⏱️ ${secondsLeft}秒後`;
  357. }
  358.  
  359. updateStatusIndicator();
  360. }, 1000);
  361. }
  362.  
  363. // Initialize the script
  364. function initialize() {
  365. console.log('Initializing Claude menu click blocker with compact controls');
  366.  
  367. // Load saved state
  368. const savedState = localStorage.getItem('claudeBlockerEnabled');
  369. if (savedState !== null) {
  370. isBlockerEnabled = savedState === 'true';
  371. }
  372.  
  373. // Reset temporary state on initialization
  374. isTemporarilyUnblocked = false;
  375.  
  376. // Create blocker if enabled
  377. if (isBlockerEnabled) {
  378. createClickBlocker();
  379. }
  380.  
  381. // Create control panel
  382. createCompactControls();
  383. }
  384.  
  385. // Run on page load
  386. if (document.readyState === 'loading') {
  387. document.addEventListener('DOMContentLoaded', initialize);
  388. } else {
  389. // DOM already loaded, run immediately
  390. initialize();
  391. }
  392.  
  393. // Also run after a short delay to catch late-loading elements
  394. setTimeout(initialize, 1000);
  395.  
  396. // Re-initialize on URL change (for SPA navigation)
  397. let lastUrl = location.href;
  398. new MutationObserver(() => {
  399. if (lastUrl !== location.href) {
  400. lastUrl = location.href;
  401. setTimeout(initialize, 500);
  402. }
  403. }).observe(document, {subtree: true, childList: true});
  404.  
  405. // Add a periodic checker to ensure controls are visible and synced
  406. setInterval(() => {
  407. const controls = document.getElementById('claude-blocker-controls');
  408. if (!controls) {
  409. console.log('Controls missing, recreating...');
  410. createCompactControls();
  411. } else {
  412. // Update button states to ensure they're in sync
  413. updateTempButtonState();
  414. updateToggleButtonState();
  415. updateStatusIndicator();
  416. }
  417. }, 5000);
  418. })();