Hide Web Elements Pro

Double-Tap Hide with Animated Preview, Undetectable Stealth Engine, Real-time Element Restoration

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();