Hide Web Elements Pro

Double-Tap Hide, Stealth Engine, Site-Wide Freeze Mode, Hardened Blocker, Daily Auto-Clearing Logs, Auto-Sync Scope Button

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Hide Web Elements Pro 
// @version      3.28
// @description  Double-Tap Hide, Stealth Engine, Site-Wide Freeze Mode, Hardened Blocker, Daily Auto-Clearing Logs, Auto-Sync Scope Button
// @author       KTZ
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// @namespace    https://greasyfork.org/users/1620673
// ==/UserScript==

(function() {
    'use strict';

    const UI_HOST_ID = 'hider-ui-root';
    const isTop = (window.self === window.top);
    const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
    const btnSize = isMobile ? '50px' : '44px';
    const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;

    // Pre-compiled Regexes for high-performance DOM parsing
    const REG_NUMERIC = /^\d+$/;
    const REG_LONG_ID = /.{15,}/;
    const REG_HOVER = /^hover:/i;
    const REG_RANDOM_CLASS = /^[a-zA-Z0-9]{10,}$/;

    // In-Memory Fast Cache Engine (Avoids repeated GM_getValue IPC calls)
    const CACHE = {
        blockedDomainsList: [],
        blockedDomainsSet: new Set(),
        isFrozen: false,
        autoYes: false,
        autoNo: false,
        logs: null,
        siteData: null,
        linkData: null
    };

    // Auto-clears blocked logs every new calendar day
    function checkAndClearDailyLogs() {
        const today = new Date().toDateString();
        const lastClearDay = GM_getValue('hider_last_log_clear_day', '');

        if (lastClearDay !== today) {
            CACHE.logs = [];
            GM_setValue('hider_global_logs', []);
            GM_setValue('hider_last_log_clear_day', today);
        }
    }

    function syncCacheFromStorage() {
        CACHE.blockedDomainsList = GM_getValue('hider_blocked_domains', []);
        CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
        CACHE.isFrozen = GM_getValue('hider_freeze_global', false);
        CACHE.autoYes = GM_getValue('hider_autoyes_global', false);
        CACHE.autoNo = GM_getValue('hider_autono_global', false);
        checkAndClearDailyLogs();
    }
    syncCacheFromStorage();

    let isSelecting = false;
    let isFrozen = CACHE.isFrozen;
    let currentScope = 'site';
    let previewElement = null;
    let userApprovedNavigation = false;
    let cachedCssString = null;
    let lastUrl = window.location.href;

    // --- Helper: Fast Domain Cleaning & O(1) Set Lookups ---
    function cleanDomain(url) {
        if (!url || typeof url !== 'string') return '';
        try {
            const lower = url.trim().toLowerCase();
            const noProtocol = lower.includes('://') ? lower.split('://')[1] : lower;
            return noProtocol.split('/')[0].split('?')[0].split(':')[0].replace(/^www\./, '');
        } catch(e) {
            return '';
        }
    }

    function isDomainBlocked(targetUrl) {
        if (!targetUrl || CACHE.blockedDomainsSet.size === 0 || typeof targetUrl !== 'string') return false;

        const targetDomain = cleanDomain(targetUrl);
        if (!targetDomain) return false;

        // Fast O(1) Direct Set Match
        if (CACHE.blockedDomainsSet.has(targetDomain)) return true;

        // Fallback Subdomain & Query String Inclusion Check
        const lowerUrl = targetUrl.toLowerCase();
        for (const bDomain of CACHE.blockedDomainsSet) {
            if (targetDomain.endsWith('.' + bDomain) || lowerUrl.includes(bDomain)) {
                return true;
            }
        }
        return false;
    }

    // --- Optimized Log Engine (Daily Reset + Debounced Storage Sync) ---
    let logSaveTimer = null;
    function logBlockedAttempt(url, triggerType) {
        checkAndClearDailyLogs();

        if (!CACHE.logs) {
            const rawLogs = GM_getValue('hider_global_logs', []);
            CACHE.logs = Array.isArray(rawLogs) ? rawLogs : [];
        }

        CACHE.logs.unshift({
            url: url || 'about:blank',
            time: new Date().toLocaleTimeString(),
            type: triggerType || 'Popup'
        });

        if (CACHE.logs.length > 50) CACHE.logs.pop();

        if (!logSaveTimer) {
            logSaveTimer = setTimeout(() => {
                logSaveTimer = null;
                GM_setValue('hider_global_logs', CACHE.logs);
            }, 1000);
        }
    }

    // --- Layout-Thrash Free Video Exposing Engine ---
    function forceExposeAndPlayVideos() {
        try {
            const videos = document.getElementsByTagName('video');
            if (videos.length === 0) return;

            for (let i = 0; i < videos.length; i++) {
                const v = videos[i];
                v.style.setProperty('display', 'block', 'important');
                v.style.setProperty('visibility', 'visible', 'important');
                v.style.setProperty('opacity', '1', 'important');
                v.style.setProperty('pointer-events', 'auto', 'important');

                let parent = v.parentElement;
                let depth = 0;
                while (parent && depth < 3 && parent !== document.body) {
                    parent.style.setProperty('display', 'block', 'important');
                    parent.style.setProperty('visibility', 'visible', 'important');
                    parent.style.setProperty('opacity', '1', 'important');
                    parent = parent.parentElement;
                    depth++;
                }

                const p = v.play();
                if (p && typeof p.catch === 'function') p.catch(() => {});
            }
        } catch(e) {}
    }

    function triggerVideoResumeChain() {
        forceExposeAndPlayVideos();
        setTimeout(forceExposeAndPlayVideos, 150);
        setTimeout(forceExposeAndPlayVideos, 400);
        setTimeout(forceExposeAndPlayVideos, 800);
        broadcastToFrames(window, { type: 'HIDER_RESUME_VIDEOS' });
    }

    function simulateAdWindowSuccess() {
        try {
            window.dispatchEvent(new Event('blur'));
            document.dispatchEvent(new Event('visibilitychange'));

            if (typeof window.onblur === 'function') {
                try { window.onblur(); } catch(e) {}
            }

            setTimeout(() => {
                window.dispatchEvent(new Event('focus'));
                if (typeof window.onfocus === 'function') {
                    try { window.onfocus(); } catch(e) {}
                }
            }, 60);

            triggerVideoResumeChain();
        } catch(e) {}
    }

    function createDummyWindow() {
        const dummyTarget = {
            closed: false,
            focus: function() {},
            blur: function() {},
            close: function() { this.closed = true; },
            postMessage: function() {},
            location: {
                href: 'about:blank',
                replace: function(u) { if (isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Replace'); },
                assign: function(u) { if (isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Assign'); }
            },
            document: { write: function() {}, close: function() {}, body: {} },
            opener: win
        };

        return new Proxy(dummyTarget, {
            get(target, prop) {
                if (prop in target) return target[prop];
                return function() {};
            },
            set(target, prop, value) {
                if (prop === 'location' && typeof value === 'string' && isDomainBlocked(value)) {
                    logBlockedAttempt(value, 'Blocked Dummy Location Set');
                    return true;
                }
                target[prop] = value;
                return true;
            }
        });
    }

    // --- HARDENED INTERCEPTORS (PROTOTYPE LEVEL) ---

    // 1. Trap Location Redirects
    try {
        const locProto = win.Location ? win.Location.prototype : Object.getPrototypeOf(win.location);
        if (locProto) {
            const origAssign = locProto.assign;
            locProto.assign = function(url) {
                if (isDomainBlocked(url)) {
                    logBlockedAttempt(url, 'Hard Blocked Redirect (assign)');
                    showToast('⛔ Blocked redirect');
                    simulateAdWindowSuccess();
                    return;
                }
                return origAssign.apply(this, arguments);
            };

            const origReplace = locProto.replace;
            locProto.replace = function(url) {
                if (isDomainBlocked(url)) {
                    logBlockedAttempt(url, 'Hard Blocked Redirect (replace)');
                    showToast('⛔ Blocked redirect');
                    simulateAdWindowSuccess();
                    return;
                }
                return origReplace.apply(this, arguments);
            };

            const hrefDesc = Object.getOwnPropertyDescriptor(locProto, 'href');
            if (hrefDesc && hrefDesc.set) {
                const origHrefSet = hrefDesc.set;
                Object.defineProperty(locProto, 'href', {
                    set: function(url) {
                        if (isDomainBlocked(url)) {
                            logBlockedAttempt(url, 'Hard Blocked Redirect (href)');
                            showToast('⛔ Blocked redirect');
                            simulateAdWindowSuccess();
                            return;
                        }
                        origHrefSet.call(this, url);
                    }
                });
            }
        }
    } catch(e) {}

    // 2. Trap Programmatic Script Clicks
    try {
        const origAnchorClick = HTMLAnchorElement.prototype.click;
        HTMLAnchorElement.prototype.click = function() {
            if (isDomainBlocked(this.href)) {
                logBlockedAttempt(this.href, 'Hard Blocked Script Click');
                showToast('⛔ Blocked link click');
                simulateAdWindowSuccess();
                return;
            }
            return origAnchorClick.apply(this, arguments);
        };
    } catch(e) {}

    // 3. Trap window.open
    try {
        const origOpen = win.open;
        win.open = function(url, target, features) {
            if (isDomainBlocked(url)) {
                logBlockedAttempt(url || 'about:blank', 'Hard Blocked (win.open)');
                simulateAdWindowSuccess();
                showToast('⛔ Hard Blocked Popup');
                return createDummyWindow();
            }

            if (!url || url === 'about:blank' || url === '') {
                const realWin = origOpen.apply(win, arguments);
                if (!realWin) return createDummyWindow();

                try {
                    return new Proxy(realWin, {
                        get(t, p) {
                            if (p === 'location') {
                                return new Proxy(t.location, {
                                    set(locTarget, locProp, locVal) {
                                        if ((locProp === 'href' || locProp === 'assign') && isDomainBlocked(locVal)) {
                                            logBlockedAttempt(locVal, 'Hard Blocked Popup Navigation');
                                            showToast('⛔ Blocked popup navigation');
                                            try { t.close(); } catch(err){}
                                            return true;
                                        }
                                        locTarget[locProp] = locVal;
                                        return true;
                                    }
                                });
                            }
                            const val = t[p];
                            return typeof val === 'function' ? val.bind(t) : val;
                        },
                        set(t, p, v) {
                            if (p === 'location' && isDomainBlocked(v)) {
                                logBlockedAttempt(v, 'Hard Blocked Popup Location');
                                showToast('⛔ Blocked popup navigation');
                                try { t.close(); } catch(err){}
                                return true;
                            }
                            t[p] = v;
                            return true;
                        }
                    });
                } catch(e) {
                    return realWin;
                }
            }

            if (isFrozen && !userApprovedNavigation) {
                showFreezePrompt(url || 'about:blank', 'win.open()', () => {
                    userApprovedNavigation = true;
                    origOpen.call(win, url, target, features);
                    setTimeout(() => { userApprovedNavigation = false; }, 2000);
                }, () => {
                    simulateAdWindowSuccess();
                });
                return createDummyWindow();
            }
            return origOpen.apply(win, arguments);
        };
    } catch (err) {}

    // --- Cross-Frame Communication ---
    function broadcastToFrames(winObj, msg) {
        try {
            for (let i = 0; i < winObj.frames.length; i++) {
                winObj.frames[i].postMessage(msg, '*');
                broadcastToFrames(winObj.frames[i], msg);
            }
        } catch(e) {}
    }

    function broadcastState() {
        if (!isTop) return;
        broadcastToFrames(window, {
            type: 'HIDER_SYNC_STATE',
            isSelecting,
            currentScope,
            isFrozen
        });
    }

    window.addEventListener('message', (e) => {
        if (!e.data) return;

        if (e.data.type === 'HIDER_REQUEST_STATE' && isTop) {
            broadcastState();
            return;
        }

        if (e.data.type === 'HIDER_SYNC_STATE') {
            isSelecting = e.data.isSelecting;
            currentScope = e.data.currentScope;
            isFrozen = e.data.isFrozen;

            const btnSelect = document.getElementById('btn-select');
            const btnScope = document.getElementById('btn-scope');
            if (btnSelect) btnSelect.classList.toggle('active', isSelecting);
            if (btnScope) btnScope.classList.toggle('active', isSelecting);

            if (!isSelecting && previewElement) {
                previewElement.classList.remove('hider-preview-highlight');
                previewElement = null;
            }
        }

        if (e.data.type === 'HIDER_RESUME_VIDEOS') {
            forceExposeAndPlayVideos();
            setTimeout(forceExposeAndPlayVideos, 200);
            setTimeout(forceExposeAndPlayVideos, 500);
        }
    });

    if (!isTop) {
        try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch(e) {}
    }

    // --- Optimized Element Selector 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 && typeof current.id === 'string' && !REG_NUMERIC.test(current.id) && !REG_LONG_ID.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 && !REG_RANDOM_CLASS.test(c) && !REG_HOVER.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(' > ');
    }

    // --- UI Toast & Modal Prompts ---
    function showToast(msg) {
        const existing = document.getElementById('hider-toast');
        if (existing) existing.remove();

        const toast = document.createElement('div');
        toast.id = 'hider-toast';
        toast.style.cssText = `
            position: fixed; bottom: 25px; left: 50%; transform: translateX(-50%);
            background: rgba(15, 23, 42, 0.88); backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 20px;
            padding: 8px 18px; color: white; font-family: system-ui, -apple-system, sans-serif;
            font-size: 12px; font-weight: 600; z-index: 2147483647;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5); pointer-events: none;
            transition: opacity 0.3s ease; opacity: 1;
        `;
        toast.textContent = msg;
        (document.body || document.documentElement).appendChild(toast);

        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        }, 2000);
    }

    function showFreezePrompt(url, triggerType, onConfirm, onDeny) {
        if (CACHE.autoYes) { if (onConfirm) onConfirm(); return; }
        if (CACHE.autoNo) {
            logBlockedAttempt(url, triggerType + ' (Auto Block)');
            if (onDeny) onDeny();
            simulateAdWindowSuccess();
            return;
        }

        const existing = document.getElementById('hider-freeze-prompt');
        if (existing) existing.remove();

        const promptEl = document.createElement('div');
        promptEl.id = 'hider-freeze-prompt';
        promptEl.style.cssText = `
            position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(15, 23, 42, 0.92); backdrop-filter: blur(16px);
            border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 14px;
            padding: 14px 18px; color: white; font-family: system-ui, -apple-system, sans-serif;
            font-size: 13px; z-index: 2147483647; display: flex; flex-direction: column;
            align-items: center; gap: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
            pointer-events: auto; max-width: 90vw; width: 330px; text-align: center;
        `;

        const displayUrl = url ? (url.length > 45 ? url.substring(0, 42) + '...' : url) : 'another page';

        promptEl.innerHTML = `
            <div style="font-weight: 700; color: #f43f5e; font-size: 11px; letter-spacing: 0.5px; text-transform: uppercase;">❄️ Site-Wide Navigation Resistance</div>
            <div style="font-size: 12px; color: #f3f4f6;">Do you want to proceed to this link?</div>
            <div style="font-size: 11px; color: #9ca3af; word-break: break-all; max-height: 36px; overflow: hidden; opacity: 0.8;">${displayUrl}</div>

            <div style="display: flex; align-items: center; gap: 6px; margin: 2px 0;">
                <input type="checkbox" id="hider-freeze-remember" style="cursor: pointer; accent-color: #8b5cf6;">
                <label for="hider-freeze-remember" style="font-size: 11px; color: #cbd5e1; cursor: pointer; user-select: none;">Remember choice site-wide</label>
            </div>

            <div style="display: flex; gap: 8px; width: 100%; margin-top: 4px;">
                <button id="hider-freeze-yes" style="flex: 1; padding: 7px 12px; background: #10b981; border: none; border-radius: 8px; color: white; font-weight: bold; cursor: pointer; font-size: 12px;">Yes</button>
                <button id="hider-freeze-no" style="flex: 1; padding: 7px 12px; background: #4b5563; border: none; border-radius: 8px; color: white; font-weight: bold; cursor: pointer; font-size: 12px;">No</button>
            </div>
        `;

        (document.body || document.documentElement).appendChild(promptEl);

        document.getElementById('hider-freeze-yes').onclick = () => {
            if (document.getElementById('hider-freeze-remember').checked) {
                GM_setValue('hider_autoyes_global', true);
                GM_setValue('hider_autono_global', false);
                CACHE.autoYes = true; CACHE.autoNo = false;
            }
            promptEl.remove();
            if (onConfirm) onConfirm();
        };

        document.getElementById('hider-freeze-no').onclick = () => {
            logBlockedAttempt(url, triggerType + ' (User)');
            if (document.getElementById('hider-freeze-remember').checked) {
                GM_setValue('hider_autono_global', true);
                GM_setValue('hider_autoyes_global', false);
                CACHE.autoNo = true; CACHE.autoYes = false;
            }
            promptEl.remove();
            if (onDeny) onDeny();
            simulateAdWindowSuccess();
        };
    }

    function injectCoreStyles() {
        if (document.getElementById('hider-core-styles')) return;
        const s = document.createElement('style');
        s.id = 'hider-core-styles';
        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;
            }
        `;
        (document.head || document.documentElement).appendChild(s);
    }

    function injectUI() {
        if (!isTop || document.getElementById(UI_HOST_ID)) return;
        const mountPoint = document.body || document.documentElement;
        if (!mountPoint) 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;';
        mountPoint.appendChild(host);

        const s = document.createElement('style');
        s.textContent = `
            .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); }
            }

            #btn-select { top: 20px; }
            #btn-select.active { background: #3b82f6 !important; transform: translateX(0) !important; width: 70px !important; }

            #btn-scope { top: calc(20px + ${btnSize} + 10px); }
            #btn-scope.scope-link { background: #10b981 !important; }
            #btn-scope.active { transform: translateX(0) !important; width: 70px !important; }
            #btn-scope.temp-expand { transform: translateX(0) !important; width: 70px !important; }

            #btn-freeze { top: calc(20px + (${btnSize} * 2) + 20px); background: rgba(139, 92, 246, 0.4); }
            #btn-freeze.is-frozen { background: #8b5cf6 !important; }
            #btn-freeze.temp-expand { transform: translateX(0) !important; width: 70px !important; }

            #btn-manage { top: calc(20px + (${btnSize} * 3) + 30px); background: rgba(239, 68, 68, 0.4); }
            #btn-manage.active { background: rgba(239, 68, 68, 0.8) !important; transform: translateX(0) !important; width: 70px !important; }

            #hider-panel {
                position: fixed; right: 10px; top: 100px; width: 320px; max-height: 75vh;
                background: rgba(0,0,0,0.88); 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; display: flex; justify-content: space-between; align-items: center; }
            .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: 3px 7px; color: white; font-weight: bold; background: #ff4444; font-size: 10px; }
            .hider-btn-small:hover { background: #cc0000; }
            #close-p { background: #555; padding: 5px 9px; } #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 ${isFrozen ? 'is-frozen' : ''}" id="btn-freeze">FREEZE</div>
            <div class="hider-btn" id="btn-manage">List</div>

            <div id="hider-panel">
                <div class="panel-header">
                    <span>Site-Wide Hider & Blocker</span>
                    <button class="hider-btn-small" id="close-p">X</button>
                </div>

                <div class="section-title">Block Target Domain (Site-Wide)</div>
                <div style="display: flex; gap: 6px; margin-bottom: 8px;">
                    <input type="text" id="manual-domain-input" placeholder="e.g. badsite.com or adsserver.net" style="flex: 1; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; color: white; padding: 5px 8px; font-size: 11px; outline: none;">
                    <button class="hider-btn-small" id="add-domain-btn" style="background: #3b82f6; width: 30px; font-size: 14px; font-weight: bold;">+</button>
                </div>

                <div class="section-title" style="margin-top: 10px;">Manually Blocked Websites</div>
                <div id="list-blocked-domains"></div>

                <div class="section-title" style="margin-top: 10px;">Site-Wide Freeze Memory</div>
                <div id="freeze-memory-container" class="list-item"></div>

                <div class="section-title" style="margin-top: 10px;">
                    <span>Blocked Redirect Log (Daily)</span>
                    <button class="hider-btn-small" id="clear-log-btn" style="background:#6b7280;">Clear</button>
                </div>
                <div id="list-blocked-log" style="max-height: 120px; overflow-y: auto; background: rgba(255,255,255,0.05); border-radius: 6px; padding: 5px;"></div>

                <div class="section-title" style="margin-top: 10px;">Hidden Site-Wide (${window.location.hostname})</div>
                <div id="list-site"></div>

                <div class="section-title" style="margin-top: 10px;">Hidden This Page Only</div>
                <div id="list-link"></div>
            </div>
        `;

        const addBlockedDomain = () => {
            const input = document.getElementById('manual-domain-input');
            const domain = cleanDomain(input.value);
            if (!domain) return;

            if (!CACHE.blockedDomainsSet.has(domain)) {
                CACHE.blockedDomainsList.push(domain);
                CACHE.blockedDomainsSet.add(domain);
                GM_setValue('hider_blocked_domains', CACHE.blockedDomainsList);
                showToast(`🚫 Force-blocked website: ${domain}`);
            }

            input.value = '';
            renderList();
        };

        document.getElementById('add-domain-btn').onclick = addBlockedDomain;
        document.getElementById('manual-domain-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') addBlockedDomain();
        });

        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');
                if (btnScope) btnScope.classList.add('active');
                panel.style.display = 'none';
                btnManage.classList.remove('active');
            } else {
                e.target.classList.remove('active');
                if (btnScope) btnScope.classList.remove('active');

                if (previewElement) {
                    previewElement.classList.remove('hider-preview-highlight');
                    previewElement = null;
                }
            }
            broadcastState();
        };

        const btnScope = document.getElementById('btn-scope');
        btnScope.onclick = () => {
            currentScope = currentScope === 'site' ? 'link' : 'site';
            btnScope.textContent = currentScope === 'site' ? 'SITE' : 'LINK';
            btnScope.classList.toggle('scope-link', currentScope === 'link');

            btnScope.classList.add('temp-expand');
            showToast(`Scope set to: ${currentScope.toUpperCase()}`);
            broadcastState();

            setTimeout(() => {
                btnScope.classList.remove('temp-expand');
                btnScope.blur();
            }, 1000);
        };

        const btnFreeze = document.getElementById('btn-freeze');
        btnFreeze.onclick = () => {
            isFrozen = !isFrozen;
            CACHE.isFrozen = isFrozen;
            GM_setValue('hider_freeze_global', isFrozen);
            btnFreeze.classList.toggle('is-frozen', isFrozen);
            btnFreeze.classList.add('temp-expand');

            showToast(isFrozen ? '❄️ Global Freeze Mode: ON' : '🔥 Global Freeze Mode: OFF');
            broadcastState();

            setTimeout(() => {
                btnFreeze.classList.remove('temp-expand');
                btnFreeze.blur();
            }, 1000);
        };

        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');
                if (btnScope) btnScope.classList.remove('active');

                if (previewElement) {
                    previewElement.classList.remove('hider-preview-highlight');
                    previewElement = null;
                }

                broadcastState();
                renderList();
            }
        };

        document.getElementById('close-p').onclick = () => {
            document.getElementById('hider-panel').style.display = 'none';
            document.getElementById('btn-manage').classList.remove('active');
        };

        document.getElementById('clear-log-btn').onclick = () => {
            CACHE.logs = [];
            GM_setValue('hider_global_logs', []);
            renderList();
        };
    }

    // --- Dynamic CSS 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])];

        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');

        if (cachedCssString === hideRules && styleEl.textContent === hideRules) return;
        cachedCssString = hideRules;
        styleEl.textContent = hideRules;
    }

    function restoreSelector(selector) {
        if (!selector) return;
        try {
            const els = document.querySelectorAll(selector);
            const props = ['display', 'position', 'top', 'left', 'width', 'height', 'opacity', 'pointer-events', 'clip', 'clip-path', 'overflow', 'max-width', 'max-height', 'margin', 'padding'];
            els.forEach(el => {
                props.forEach(p => el.style.removeProperty(p));
            });
        } catch (err) {}
    }

    function renderList() {
        checkAndClearDailyLogs();

        const hostKey = window.location.hostname;
        const siteKey = 'hider_site_' + hostKey;
        const linkKey = 'hider_link_' + window.location.href;

        const siteData = GM_getValue(siteKey, []);
        const linkData = GM_getValue(linkKey, []);

        if (!CACHE.logs) {
            const raw = GM_getValue('hider_global_logs', []);
            CACHE.logs = Array.isArray(raw) ? raw : [];
        }

        const cMemory = document.getElementById('freeze-memory-container');
        const cDomains = document.getElementById('list-blocked-domains');
        const cSite = document.getElementById('list-site');
        const cLink = document.getElementById('list-link');
        const cLog = document.getElementById('list-blocked-log');

        if(!cSite || !cLink) return;

        if (cDomains) {
            cDomains.innerHTML = '';
            if (CACHE.blockedDomainsList.length === 0) {
                cDomains.innerHTML = `<div style="font-size:10px; color:#777;">None</div>`;
            } else {
                CACHE.blockedDomainsList.forEach((domain, i) => {
                    const div = document.createElement('div'); div.className = 'list-item';
                    div.innerHTML = `<span>${domain}</span> <button class="hider-btn-small" data-i="${i}">X</button>`;
                    div.querySelector('button').onclick = (e) => {
                        const removed = CACHE.blockedDomainsList.splice(e.target.dataset.i, 1)[0];
                        CACHE.blockedDomainsSet.delete(cleanDomain(removed));
                        GM_setValue('hider_blocked_domains', CACHE.blockedDomainsList);
                        renderList();
                    };
                    cDomains.appendChild(div);
                });
            }
        }

        if (cMemory) {
            if (CACHE.autoYes) {
                cMemory.innerHTML = `<span>Always Allow Popups</span> <button class="hider-btn-small" id="reset-mem">Reset</button>`;
            } else if (CACHE.autoNo) {
                cMemory.innerHTML = `<span style="color:#10b981;">Always Auto-Block Ads</span> <button class="hider-btn-small" id="reset-mem">Reset</button>`;
            } else {
                cMemory.innerHTML = `<span style="color:#777;">Ask Every Time</span>`;
            }
            const resetBtn = document.getElementById('reset-mem');
            if (resetBtn) {
                resetBtn.onclick = () => {
                    GM_setValue('hider_autoyes_global', false);
                    GM_setValue('hider_autono_global', false);
                    CACHE.autoYes = false; CACHE.autoNo = false;
                    renderList();
                };
            }
        }

        if (cLog) {
            cLog.innerHTML = '';
            if (CACHE.logs.length === 0) {
                cLog.innerHTML = `<div style="font-size:10px; color:#777; text-align:center;">No popups logged today</div>`;
            } else {
                CACHE.logs.forEach(item => {
                    const row = document.createElement('div');
                    row.style.cssText = 'font-size: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); padding: 3px 0; display: flex; flex-direction: column;';
                    row.innerHTML = `
                        <div style="display:flex; justify-content:space-between; color:#a7f3d0; font-weight:bold;">
                            <span>${item.type}</span>
                            <span style="color:#9ca3af;">${item.time}</span>
                        </div>
                        <div style="color:#d1d5db; word-break:break-all; opacity:0.8;">${item.url}</div>
                    `;
                    cLog.appendChild(row);
                });
            }
        }

        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);
        });
    }

    // --- Navigation Intercept Handlers ---
    function handleNavigationClick(e) {
        if (isSelecting) return;
        if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;

        const link = e.target.closest('a[href], area[href]');
        if (link) {
            const href = link.getAttribute('href');
            if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
                const targetUrl = link.href;

                if (isDomainBlocked(targetUrl)) {
                    e.preventDefault();
                    e.stopPropagation();
                    e.stopImmediatePropagation();
                    logBlockedAttempt(targetUrl, 'Hard Blocked Website Click');
                    simulateAdWindowSuccess();
                    showToast('⛔ Force Blocked: Website in List');
                    return;
                }

                if (!isFrozen) return;

                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                const isNewTab = link.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1;

                showFreezePrompt(targetUrl, 'Link Click', () => {
                    userApprovedNavigation = true;
                    if (isNewTab) {
                        win.open(targetUrl, '_blank');
                    } else {
                        win.location.href = targetUrl;
                    }
                    setTimeout(() => { userApprovedNavigation = false; }, 2000);
                }, () => {
                    simulateAdWindowSuccess();
                });
            }
        }
    }

    // Capture Listener for Navigation & Selection
    window.addEventListener('click', (e) => {
        if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;

        handleNavigationClick(e);

        if (!isSelecting) return;

        e.preventDefault();
        e.stopPropagation();

        if (previewElement === e.target) {
            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();
            previewElement = null;
        } else {
            if (previewElement) {
                previewElement.classList.remove('hider-preview-highlight');
            }
            previewElement = e.target;
            previewElement.classList.add('hider-preview-highlight');
        }

    }, true);

    window.addEventListener('auxclick', handleNavigationClick, true);

    window.addEventListener('submit', (e) => {
        if (e.target.closest('#' + UI_HOST_ID) || e.target.closest('#hider-freeze-prompt')) return;

        const form = e.target;
        const action = form.action || window.location.href;

        if (isDomainBlocked(action)) {
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            logBlockedAttempt(action, 'Hard Blocked Form Submission');
            simulateAdWindowSuccess();
            showToast('⛔ Force Blocked: Form Action');
            return;
        }

        if (!isFrozen) return;

        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();

        showFreezePrompt(action, 'Form Submit', () => {
            userApprovedNavigation = true;
            form.submit();
            setTimeout(() => { userApprovedNavigation = false; }, 2000);
        }, () => {
            simulateAdWindowSuccess();
        });
    }, true);

    win.addEventListener('beforeunload', (e) => {
        if (isFrozen && !userApprovedNavigation) {
            e.preventDefault();
            e.returnValue = 'Page navigation is currently frozen site-wide.';
            return e.returnValue;
        }
    }, true);

    // Context Menu Unlocker
    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);
    }

    // Lightweight Native Hook SPA Navigation Tracker
    const checkUrlChange = () => {
        if (window.location.href !== lastUrl) {
            lastUrl = window.location.href;
            updateStyles();
            const panel = document.getElementById('hider-panel');
            if (panel && panel.style.display === 'flex') renderList();
        }
    };

    const origPushState = history.pushState;
    if (origPushState) {
        history.pushState = function() {
            origPushState.apply(this, arguments);
            checkUrlChange();
        };
    }
    const origReplaceState = history.replaceState;
    if (origReplaceState) {
        history.replaceState = function() {
            origReplaceState.apply(this, arguments);
            checkUrlChange();
        };
    }
    window.addEventListener('popstate', checkUrlChange, { passive: true });
    window.addEventListener('hashchange', checkUrlChange, { passive: true });

    // Initialization Handler & Extremely Lightweight DOM Observer
    function initUI() {
        enableContextMenus();
        injectCoreStyles();
        injectUI();
        updateStyles();

        // Optimized MutationObserver (Debounced & Lightweight, No Deep Tree Traversal)
        let debouncedTimer = null;
        const observer = new MutationObserver(() => {
            if (debouncedTimer) return;
            debouncedTimer = setTimeout(() => {
                debouncedTimer = null;
                if (isTop && !document.getElementById(UI_HOST_ID)) injectUI();
                if (!document.getElementById('hider-dynamic-styles')) updateStyles();
                if (!document.getElementById('hider-core-styles')) injectCoreStyles();
            }, 1000);
        });

        if (document.documentElement) {
            observer.observe(document.documentElement, { childList: true });
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initUI);
    } else {
        initUI();
    }

})();