Greasy Fork is available in English.
Double-Tap Hide with Animated Preview, Undetectable Stealth Engine, Real-time Element Restoration
// ==UserScript==
// @name Hide Web Elements Pro
// @version 2.40
// @description Double-Tap Hide with Animated Preview, Undetectable Stealth Engine, Real-time Element Restoration
// @author KTZ
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @license MIT
// @namespace https://greasyfork.org/users/1620673
// ==/UserScript==
(function() {
'use strict';
const UI_HOST_ID = 'hider-ui-root';
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
const btnSize = isMobile ? '50px' : '44px';
let isSelecting = false;
let currentScope = 'site'; // 'site' or 'link'
let previewElement = null; // Tracks the currently highlighted element
// --- 1. Cosmetic Filtering Engine ---
function getExactSelector(el) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return '';
const path = [];
let current = el;
while (current && current.nodeType === Node.ELEMENT_NODE && current.tagName.toLowerCase() !== 'body' && current.tagName.toLowerCase() !== 'html') {
let selector = current.tagName.toLowerCase();
if (current.id && !/^\d+$/.test(current.id) && !/.{15,}/.test(current.id)) {
selector = `#${CSS.escape(current.id)}`;
path.unshift(selector);
break;
}
let hasGoodClass = false;
if (current.className && typeof current.className === 'string') {
const classes = current.className.trim().split(/\s+/).filter(c => {
return c.length > 2 && !/^[a-zA-Z0-9]{10,}$/.test(c) && !/^hover:/i.test(c);
});
if (classes.length > 0) {
selector += `.${CSS.escape(classes[0])}`;
if (classes[1]) selector += `.${CSS.escape(classes[1])}`;
hasGoodClass = true;
}
}
if (!hasGoodClass) {
let index = 1, sibling = current.previousElementSibling;
while (sibling) { index++; sibling = sibling.previousElementSibling; }
selector += `:nth-child(${index})`;
}
path.unshift(selector);
if (hasGoodClass && path.length >= 2) break;
current = current.parentElement;
}
if (path.length > 0 && !path[0].startsWith('#') && !path[0].includes('.')) {
path.unshift('body');
}
return path.join(' > ');
}
// --- 2. UI Injection ---
function injectUI() {
if (document.getElementById(UI_HOST_ID)) return;
if (!document.body) return;
const host = document.createElement('div');
host.id = UI_HOST_ID;
host.style.cssText = 'position:fixed; top:0; right:0; bottom:0; width:0; z-index:2147483647; pointer-events:none;';
document.body.appendChild(host);
const s = document.createElement('style');
s.textContent = `
@keyframes hider-preview-pulse {
0% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
50% { outline: 3px dotted #ef4444; outline-offset: 2px; box-shadow: inset 0 0 15px rgba(239, 68, 68, 0.4); }
100% { outline: 3px dotted #ef4444; outline-offset: -3px; box-shadow: inset 0 0 0 rgba(239, 68, 68, 0.1); }
}
.hider-preview-highlight {
animation: hider-preview-pulse 1s infinite !important;
cursor: pointer !important;
border-radius: 2px !important;
}
.hider-btn {
position: fixed; right: 0; width: ${btnSize}; height: ${btnSize};
background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(15px);
border: 1px solid rgba(255,255,255,0.3); border-radius: 12px 0 0 12px;
color: white; display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 10px; font-weight: 700; text-transform: uppercase;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: auto;
transform: translateX(calc(${btnSize} - 15px));
}
@media (hover: hover) {
.hider-btn:hover { transform: translateX(0); width: 70px; background: rgba(255,255,255,0.4); }
}
.hider-btn.active { transform: translateX(0) !important; width: 70px !important; }
#btn-select.active { background: #3b82f6 !important; }
#btn-scope.scope-link { background: #10b981 !important; }
#btn-manage.active { background: rgba(239, 68, 68, 0.8) !important; transform: translateX(0) !important; width: 70px !important; }
#btn-select { top: 20px; }
#btn-scope { top: calc(20px + ${btnSize} + 10px); }
#btn-manage { top: calc(20px + (${btnSize} * 2) + 20px); background: rgba(239, 68, 68, 0.4); }
#hider-panel {
position: fixed; right: 10px; top: 100px; width: 300px; max-height: 70vh;
background: rgba(0,0,0,0.85); backdrop-filter: blur(20px); border-radius: 16px;
padding: 15px; color: white; display: none; flex-direction: column; overflow-y: auto; pointer-events: auto;
}
.panel-header { font-weight:bold; margin-bottom:12px; border-bottom: 2px solid #555; padding-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
.section-title { font-size: 11px; color: #aaa; text-transform: uppercase; margin: 10px 0 5px 0; letter-spacing: 1px; }
.list-item { font-size: 11px; margin-bottom: 6px; border-bottom: 1px solid #333; padding-bottom: 4px; display: flex; justify-content: space-between; align-items: center; }
.list-item span { word-break: break-all; padding-right: 8px; }
.hider-btn-small { cursor: pointer; border: none; border-radius: 4px; padding: 4px 8px; color: white; font-weight: bold; background: #ff4444; }
.hider-btn-small:hover { background: #cc0000; }
#close-p { background: #555; padding: 6px 10px; } #close-p:hover { background: #777; }
`;
(document.head || document.documentElement).appendChild(s);
host.innerHTML = `
<div class="hider-btn" id="btn-select">Hide</div>
<div class="hider-btn" id="btn-scope">SITE</div>
<div class="hider-btn" id="btn-manage">List</div>
<div id="hider-panel">
<div class="panel-header">
<span>Manage Hidden Items</span>
<button class="hider-btn-small" id="close-p">X</button>
</div>
<div class="section-title">Hidden Everywhere (Site)</div>
<div id="list-site"></div>
<div class="section-title" style="margin-top: 15px;">Hidden Here Only (Link)</div>
<div id="list-link"></div>
</div>
`;
document.getElementById('btn-select').onclick = (e) => {
isSelecting = !isSelecting;
const btnScope = document.getElementById('btn-scope');
const btnManage = document.getElementById('btn-manage');
const panel = document.getElementById('hider-panel');
if (isSelecting) {
e.target.classList.add('active');
btnScope.classList.add('active');
panel.style.display = 'none';
btnManage.classList.remove('active');
} else {
e.target.classList.remove('active');
btnScope.classList.remove('active');
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = null;
}
}
};
const btnScope = document.getElementById('btn-scope');
btnScope.onclick = () => {
if (!isSelecting) return;
currentScope = currentScope === 'site' ? 'link' : 'site';
btnScope.textContent = currentScope === 'site' ? 'SITE' : 'LINK';
btnScope.classList.toggle('scope-link', currentScope === 'link');
};
document.getElementById('btn-manage').onclick = (e) => {
const panel = document.getElementById('hider-panel');
const btnSelect = document.getElementById('btn-select');
const btnScope = document.getElementById('btn-scope');
if (panel.style.display === 'flex') {
panel.style.display = 'none';
e.target.classList.remove('active');
} else {
panel.style.display = 'flex';
e.target.classList.add('active');
isSelecting = false;
btnSelect.classList.remove('active');
btnScope.classList.remove('active');
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = null;
}
renderList();
}
};
document.getElementById('close-p').onclick = () => {
document.getElementById('hider-panel').style.display = 'none';
document.getElementById('btn-manage').classList.remove('active');
};
}
// --- 3. Style & Storage (Undetectable Stealth Engine) ---
function updateStyles() {
let styleEl = document.getElementById('hider-dynamic-styles');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'hider-dynamic-styles';
(document.head || document.documentElement).appendChild(styleEl);
}
const siteData = GM_getValue('hider_site_' + window.location.hostname, []);
const linkData = GM_getValue('hider_link_' + window.location.href, []);
const combined = [...new Set([...siteData, ...linkData])];
// Undetectable stealth hiding rules bypass checks for getComputedStyle, offsetParent, and dimensions
const hideRules = combined
.filter(sel => sel && sel.trim() !== '')
.map(sel => `${sel} {
position: absolute !important;
top: -99999px !important;
left: -99999px !important;
width: 1px !important;
height: 1px !important;
opacity: 0.0001 !important;
pointer-events: none !important;
clip: rect(1px, 1px, 1px, 1px) !important;
clip-path: inset(50%) !important;
overflow: hidden !important;
max-width: 0px !important;
max-height: 0px !important;
margin: 0 !important;
padding: 0 !important;
}`)
.join('\n');
styleEl.textContent = hideRules;
}
function restoreSelector(selector) {
if (!selector) return;
try {
const els = document.querySelectorAll(selector);
els.forEach(el => {
el.style.removeProperty('display');
el.style.removeProperty('position');
el.style.removeProperty('top');
el.style.removeProperty('left');
el.style.removeProperty('width');
el.style.removeProperty('height');
el.style.removeProperty('opacity');
el.style.removeProperty('pointer-events');
});
} catch (err) {
// Ignore invalid selector queries safely
}
}
function renderList() {
const siteKey = 'hider_site_' + window.location.hostname;
const linkKey = 'hider_link_' + window.location.href;
const siteData = GM_getValue(siteKey, []);
const linkData = GM_getValue(linkKey, []);
const cSite = document.getElementById('list-site');
const cLink = document.getElementById('list-link');
if(!cSite || !cLink) return;
cSite.innerHTML = ''; cLink.innerHTML = '';
if (siteData.length === 0) cSite.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
siteData.forEach((sel, i) => {
const div = document.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
div.querySelector('button').onclick = (e) => {
const removed = siteData.splice(e.target.dataset.i, 1)[0];
GM_setValue(siteKey, siteData);
restoreSelector(removed);
updateStyles();
renderList();
};
cSite.appendChild(div);
});
if (linkData.length === 0) cLink.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
linkData.forEach((sel, i) => {
const div = document.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span>${sel.substring(0, 25)}...</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
div.querySelector('button').onclick = (e) => {
const removed = linkData.splice(e.target.dataset.i, 1)[0];
GM_setValue(linkKey, linkData);
restoreSelector(removed);
updateStyles();
renderList();
};
cLink.appendChild(div);
});
}
// --- 4. Global Capture Click (Double-Tap Preview) ---
document.addEventListener('click', (e) => {
if (!isSelecting) return;
if (e.target.closest('#' + UI_HOST_ID)) return;
e.preventDefault();
e.stopPropagation();
if (previewElement === e.target) {
// TAP 2: Commit and hide stealthily
previewElement.classList.remove('hider-preview-highlight');
const selector = getExactSelector(e.target);
if (!selector) {
previewElement = null;
return;
}
if (currentScope === 'site') {
const key = 'hider_site_' + window.location.hostname;
const s = GM_getValue(key, []);
if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
} else {
const key = 'hider_link_' + window.location.href;
const s = GM_getValue(key, []);
if (!s.includes(selector)) { s.push(selector); GM_setValue(key, s); }
}
updateStyles(); // Applies stealth hiding rules in real-time
previewElement = null;
} else {
// TAP 1: Highlight
if (previewElement) {
previewElement.classList.remove('hider-preview-highlight');
}
previewElement = e.target;
previewElement.classList.add('hider-preview-highlight');
}
}, true);
// --- 5. Permanent Long-Press & Right-Click Enabler ---
function enableContextMenus() {
const blockEvents = ['contextmenu', 'selectstart', 'copy', 'paste', 'dragstart'];
blockEvents.forEach(evt => {
document.addEventListener(evt, (e) => {
e.stopPropagation();
}, true);
});
const cssUnlock = document.createElement('style');
cssUnlock.textContent = `* { -webkit-touch-callout: default !important; }`;
(document.head || document.documentElement).appendChild(cssUnlock);
}
enableContextMenus();
// --- 6. MutationObserver Engine ---
let lastUrl = window.location.href;
const observer = new MutationObserver(() => {
if (!document.getElementById(UI_HOST_ID)) injectUI();
if (!document.getElementById('hider-dynamic-styles')) updateStyles();
if (window.location.href !== lastUrl) {
lastUrl = window.location.href;
updateStyles();
const panel = document.getElementById('hider-panel');
if (panel && panel.style.display === 'flex') renderList();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
injectUI();
updateStyles();
})();