// ==UserScript== // @name X/Twitter Comments Blurrifier // @namespace http://tampermonkey.net/ // @version 7.1 // @description Automagically blur comments on X (Twitter) and adds a button to toggle between visible/blurred. // @author nereids // @license MIT // @match https://twitter.com/* // @match https://x.com/* // @icon https://icons.duckduckgo.com/ip3/x.com.ico // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/554304/XTwitter%20Comments%20Blurrifier.user.js // @updateURL https://update.greasyfork.icu/scripts/554304/XTwitter%20Comments%20Blurrifier.meta.js // ==/UserScript== (function() { 'use strict'; // --- CROSS-BROWSER CSS INJECTION FIX --- function addStyle(css) { const style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); } // --- END FIX --- const CONVERSATION_SELECTOR = 'div[aria-label="Timeline: Conversation"]'; const ACTION_BAR_SELECTOR = 'article div[role="group"]'; const ICON_ID = 'toggleBlurIcon'; const ACTIVE_CLASS = 'blur-disabled'; // --- Custom Colors --- const X_BLUE = '#1d9bf0'; // For Active/Unblurred state const X_PINK = '#f91880';  // For Inactive/Blurred state (your custom color) // Initial state: Comments are blurred let isBlurred = true; // --- SVG Definitions (Using your provided sizes and colors) --- const blurredSvg = ` `; const unblurredSvg = ` `; // 2. Inject Base CSS for Blurring and Hover State addStyle(` /* Blur and Alignment Styles */ ${CONVERSATION_SELECTOR} div[data-testid="cellInnerDiv"]:not(:first-child) { filter: blur(4px) !important; transition: filter 0.2s ease-in-out; pointer-events: none !important; user-select: none !important; } body.${ACTIVE_CLASS} ${CONVERSATION_SELECTOR} div[data-testid="cellInnerDiv"]:not(:first-child) { filter: none !important; pointer-events: auto !important; user-select: auto !important; } /* ICON WRAPPER STYLING */ #${ICON_ID} { cursor: pointer; display: flex; align-items: center; justify-content: center; height: 100%; width: 46px; margin: 0 0 0 15px !important; padding: 0 !important; transition: background-color 0.2s ease-in-out; } /* Pseudo-element for circular background */ div[id="${ICON_ID}"]::before { content: ''; position: absolute; width: 38.5px; height: 38.5px; border-radius: 50%; transition: background-color 0.2s ease-in-out; z-index: 1; } /* The SVG is a direct child of the icon container */ #${ICON_ID} svg { z-index: 2; } /* --- HOVER STATE LOGIC --- */ /* 1. Default (Blurred/Inactive, Icon Blue) State Hover: Apply BLUE hover */ body:not(.${ACTIVE_CLASS}) div[id="${ICON_ID}"]:hover { color: ${X_BLUE}; /* Changes the icon color to blue */ } body:not(.${ACTIVE_CLASS}) div[id="${ICON_ID}"]:hover::before { background-color: rgba(29, 161, 242, 0.1); /* Low-opacity X blue background */ } /* 2. Active (Unblurred, Icon Pink) State Hover: Apply PINK hover */ body.${ACTIVE_CLASS} div[id="${ICON_ID}"]:hover { color: ${X_PINK}; /* Changes the icon color to pink */ } body.${ACTIVE_CLASS} div[id="${ICON_ID}"]:hover::before { background-color: rgba(249, 24, 128, 0.1); /* Low-opacity X pink background */ } /* The cloned button wrapper needs fixed dimensions for consistent alignment */ #${ICON_ID}:not(:first-child) { width: 38.5px; height: 38.5px; } `); // 3. Logic to inject the icon and attach the event handler (SPA logic) function injectToggleIcon() { const actionBar = document.querySelector(ACTION_BAR_SELECTOR); if (actionBar && !document.getElementById(ICON_ID)) { const iconContainer = document.createElement('div'); iconContainer.id = ICON_ID; isBlurred = !document.body.classList.contains(ACTIVE_CLASS); iconContainer.innerHTML = isBlurred ? blurredSvg : unblurredSvg; const existingButton = actionBar.children[0]; if (!existingButton) return; const targetButton = existingButton.querySelector('svg') ? existingButton : actionBar.children[1]; const buttonWrapper = targetButton.cloneNode(false); buttonWrapper.innerHTML = ''; buttonWrapper.appendChild(iconContainer); actionBar.appendChild(buttonWrapper); // Click Handler: Toggle state and update icon iconContainer.addEventListener('click', function(e) { e.stopPropagation(); isBlurred = !isBlurred; document.body.classList.toggle(ACTIVE_CLASS); // Update SVG iconContainer.innerHTML = isBlurred ? blurredSvg : unblurredSvg; }); } } // --- MutationObserver Setup (SPA logic) --- const observerCallback = (mutations) => { for (const mutation of mutations) { if (mutation.addedNodes.length) { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && (node.matches(CONVERSATION_SELECTOR) || node.querySelector(CONVERSATION_SELECTOR))) { setTimeout(injectToggleIcon, 50); return; } } } } }; const observer = new MutationObserver(observerCallback); const targetNode = document.body; const config = { childList: true, subtree: true }; observer.observe(targetNode, config); // Initial call injectToggleIcon(); })();