Hide Web Elements Pro

Stealth element hider, persistent global freeze mode, custom non-native dropdowns, editable hidden rules, domain & global rule editor, soft snow freeze mode, quick parent domain blocker, anti-detection engine.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Hide Web Elements Pro
// @version      4.8
// @description  Stealth element hider, persistent global freeze mode, custom non-native dropdowns, editable hidden rules, domain & global rule editor, soft snow freeze mode, quick parent domain blocker, anti-detection engine.
// @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 doc = document, win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window, gv = GM_getValue, sv = GM_setValue;
    const UI_HOST_ID = 'hider-ui-root', STEPPER_BAR_ID = 'hider-stepper-bar', isTop = (window.self === window.top);
    const by = id => doc.getElementById(id);

    let initialMem = gv('hider_freeze_memory', null) || (gv('hider_autono_global', false) ? 'block_all' : 'ask');
    const CACHE = { 
        blockedDomainsList: [], 
        blockedDomainsSet: new Set(), 
        customRules: [], 
        isFrozen: gv('hider_freeze_global', false), 
        freezeMemory: initialMem, 
        logs: null 
    };

    let isSelecting = false, isFrozen = CACHE.isFrozen, currentScope = 'site', previewElement = null;
    let stepperStack = [], userApprovedNavigation = false, cachedCssString = null, lastUrl = location.href;
    let stepperPos = { x: null, y: null }, isDraggingStepper = false, isDraggingDock = false, logSaveTimer = null;
    let collapseTimer = null, editingRuleId = null;

    const FREEZE_LABELS = {
        'ask': '❓ Ask Every Time',
        'block_all': '⛔ Auto-Block All Navigations',
        'allow_same': '🔗 Allow Same Domain Only',
        'allow_all': '🟢 Allow All Navigations'
    };

    const cleanUrl = () => location.origin + location.pathname;
    const cleanDomain = url => {
        try { 
            if (!url) return '';
            if (url === '*') return '*';
            return new URL(url.includes('://') ? url : 'http://' + url).hostname.replace(/^www\./, ''); 
        } catch { 
            return url.trim().toLowerCase(); 
        }
    };

    // Extracts the root/parent domain instead of long subdomains
    function getParentDomain(url) {
        if (!url) return '';
        try {
            let host = new URL(url.includes('://') ? url : 'http://' + url).hostname.replace(/^www\./, '').toLowerCase();
            if (!host) return '';
            let parts = host.split('.');
            if (parts.length <= 2) return host;
            
            // Common multi-part TLD suffixes
            const multiPartTlds = ['co.uk', 'com.au', 'org.uk', 'gov.uk', 'co.jp', 'com.br', 'co.id', 'or.id', 'ac.uk', 'net.au', 'com.tw', 'co.nz', 'com.sg', 'com.mx', 'co.kr', 'com.tr'];
            let lastTwo = parts.slice(-2).join('.');
            if (multiPartTlds.includes(lastTwo) && parts.length > 2) {
                return parts.slice(-3).join('.');
            }
            return parts.slice(-2).join('.');
        } catch {
            return cleanDomain(url) || '';
        }
    }

    function syncCache() {
        CACHE.blockedDomainsList = gv('hider_blocked_domains', []);
        CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
        CACHE.customRules = gv('hider_custom_rules_v4', []);
        CACHE.isFrozen = gv('hider_freeze_global', false);
        isFrozen = CACHE.isFrozen;
        CACHE.freezeMemory = gv('hider_freeze_memory', CACHE.freezeMemory);
        if (gv('hider_last_log_clear_day', '') !== new Date().toDateString()) {
            CACHE.logs = []; sv('hider_global_logs', []); sv('hider_last_log_clear_day', new Date().toDateString());
        }
    }
    syncCache();

    // Stealth Engine: Anti-Anti-Adblock
    try {
        const origGetComputedStyle = win.getComputedStyle;
        win.getComputedStyle = function(el, pseudo) {
            const style = origGetComputedStyle.apply(this, arguments);
            if (el && el.nodeType === 1 && el.classList.contains('hider-stealth-target')) {
                return new Proxy(style, {
                    get(target, prop) {
                        if (prop === 'display') return 'block';
                        if (prop === 'visibility') return 'visible';
                        if (prop === 'opacity') return '1';
                        if (prop === 'pointerEvents') return 'auto';
                        const val = target[prop];
                        return typeof val === 'function' ? val.bind(target) : val;
                    }
                });
            }
            return style;
        };
    } catch {}

    const isSameDomain = u => cleanDomain(u) && cleanDomain(u) === cleanDomain(location.href);
    const isDomainBlocked = u => {
        if (!u || !CACHE.blockedDomainsSet.size) return false;
        const d = cleanDomain(u), lower = u.toLowerCase();
        return d && (CACHE.blockedDomainsSet.has(d) || [...CACHE.blockedDomainsSet].some(b => b !== '*' && (d.endsWith('.' + b) || lower.includes(b))));
    };

    function convertToWildcardSelector(sel) {
        return (sel || '').replace(/(#|\.)([a-zA-Z0-9_-]+)/g, (m, p, name) => {
            if (name.startsWith('hider-')) return '';
            let base = (name.match(/^([a-zA-Z_-]+?)[0-9a-fA-F_-]{3,}$/)?.[1] || name.replace(/[0-9a-fA-F]{4,}$/, '')).replace(/[-_]+$/, '');
            return p === '#' ? `[id*="${base || name}"]` : `[class*="${base || name}"]`;
        }).replace(/\s*>\s*/g, ' ');
    }

    function logBlockedAttempt(url, triggerType) {
        if (!CACHE.logs) CACHE.logs = gv('hider_global_logs', []);
        CACHE.logs.unshift({ url: url || 'about:blank', time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }), type: triggerType || 'Popup' });
        if (CACHE.logs.length > 50) CACHE.logs.pop();
        if (!logSaveTimer) logSaveTimer = setTimeout(() => { logSaveTimer = null; sv('hider_global_logs', CACHE.logs); }, 1000);
    }

    function triggerFreezeFx() {
        const fx = doc.createElement('div');
        fx.className = 'hider-freeze-snow-overlay';
        for (let i = 0; i < 14; i++) {
            const flake = doc.createElement('div');
            flake.className = 'hider-snow-flake';
            flake.style.left = `${Math.random() * 100}vw`;
            flake.style.top = `${Math.random() * 70}vh`;
            flake.style.animationDelay = `${Math.random() * 0.3}s`;
            flake.style.transform = `scale(${0.4 + Math.random() * 0.8})`;
            fx.appendChild(flake);
        }
        (doc.body || doc.documentElement).appendChild(fx);
        setTimeout(() => fx.remove(), 1300);
    }

    function forceExposeAndPlayVideos() {
        doc.querySelectorAll('video').forEach(v => {
            ['display', 'visibility', 'opacity', 'pointer-events'].forEach(p => v.style.setProperty(p, p === 'display' ? 'block' : p === 'pointer-events' ? 'auto' : '1', 'important'));
            v.play()?.catch?.(() => {});
        });
    }

    function triggerVideoResumeChain() {
        forceExposeAndPlayVideos(); [150, 400, 800].forEach(t => setTimeout(forceExposeAndPlayVideos, t));
        broadcastToFrames(window, { type: 'HIDER_RESUME_VIDEOS' });
    }

    function simulateAdWindowSuccess() {
        try {
            win.dispatchEvent(new Event('blur')); doc.dispatchEvent(new Event('visibilitychange'));
            win.onblur?.();
            setTimeout(() => { win.dispatchEvent(new Event('focus')); win.onfocus?.(); }, 60);
            triggerVideoResumeChain();
        } catch {}
    }

    function createDummyWindow() {
        const dummy = {
            closed: false, focus(){}, blur(){}, close(){ this.closed = true; }, postMessage(){},
            location: { href: 'about:blank', replace(u){ if(isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Replace'); }, assign(u){ if(isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Assign'); } },
            document: { write(){}, close(){}, body:{} }, opener: win
        };
        return new Proxy(dummy, {
            get: (t, p) => p in t ? t[p] : () => {},
            set: (t, p, v) => { if (p === 'location' && isDomainBlocked(v)) logBlockedAttempt(v, 'Blocked Dummy Location Set'); else t[p] = v; return true; }
        });
    }

    function handleFreezeNavigation(url, triggerType, onConfirm, onDeny) {
        const mem = CACHE.freezeMemory || 'ask';
        if (mem === 'block_all') { logBlockedAttempt(url, triggerType + ' (Auto-Block)'); onDeny?.(); simulateAdWindowSuccess(); return; }
        if (mem === 'allow_all' || (mem === 'allow_same' && isSameDomain(url))) {
            userApprovedNavigation = true; onConfirm?.(); setTimeout(() => userApprovedNavigation = false, 300); return;
        }
        showFreezePrompt(url, triggerType, onConfirm, onDeny);
    }

    // Interceptors for Location & Navigation Interception
    try {
        const lp = win.Location ? win.Location.prototype : Object.getPrototypeOf(win.location);
        if (lp) {
            const origA = lp.assign, origR = lp.replace, hrefDesc = Object.getOwnPropertyDescriptor(lp, 'href');

            const checkAndInterceptNav = (u, origFn, contextThis, triggerName) => {
                if (isDomainBlocked(u)) {
                    logBlockedAttempt(u, `Blocked ${triggerName}`);
                    showToast(`⛔ Blocked ${triggerName}`);
                    simulateAdWindowSuccess();
                    return;
                }
                if (isFrozen && !userApprovedNavigation) {
                    handleFreezeNavigation(u, triggerName, () => {
                        userApprovedNavigation = true;
                        origFn.call(contextThis, u);
                        setTimeout(() => userApprovedNavigation = false, 300);
                    }, simulateAdWindowSuccess);
                    return;
                }
                return origFn.call(contextThis, u);
            };

            lp.assign = function(u) { return checkAndInterceptNav(u, origA, this, 'Redirect (assign)'); };
            lp.replace = function(u) { return checkAndInterceptNav(u, origR, this, 'Redirect (replace)'); };
            if (hrefDesc?.set) {
                Object.defineProperty(lp, 'href', {
                    set(u) { checkAndInterceptNav(u, hrefDesc.set, this, 'Redirect (href)'); },
                    get() { return hrefDesc.get.call(this); }
                });
            }
        }
        HTMLAnchorElement.prototype.click = function() {
            if (isDomainBlocked(this.href)) { logBlockedAttempt(this.href, 'Blocked Click'); showToast('⛔ Blocked link click'); simulateAdWindowSuccess(); return; }
            if (isFrozen && !userApprovedNavigation) {
                handleFreezeNavigation(this.href, 'Anchor Click', () => {
                    userApprovedNavigation = true;
                    HTMLElement.prototype.click.apply(this, arguments);
                    setTimeout(() => userApprovedNavigation = false, 300);
                }, simulateAdWindowSuccess);
                return;
            }
            return HTMLElement.prototype.click.apply(this, arguments);
        };
    } catch {}

    try {
        const origOpen = win.open;
        win.open = function(url, target, features) {
            if (isDomainBlocked(url)) { logBlockedAttempt(url || 'about:blank', 'Blocked Popup'); simulateAdWindowSuccess(); showToast('⛔ Blocked Popup'); return createDummyWindow(); }
            if (!url || url === 'about:blank') {
                const realWin = origOpen.apply(win, arguments);
                return realWin ? new Proxy(realWin, {
                    get: (t, p) => p === 'location' ? new Proxy(t.location, { set: (l, lp, lv) => (lp==='href'||lp==='assign') && isDomainBlocked(lv) ? (logBlockedAttempt(lv, 'Blocked Popup Nav'), showToast('⛔ Blocked popup nav'), t.close(), true) : (l[lp]=lv, true) }) : (typeof t[p]==='function'?t[p].bind(t):t[p]),
                    set: (t, p, v) => p === 'location' && isDomainBlocked(v) ? (logBlockedAttempt(v, 'Blocked Popup Location'), showToast('⛔ Blocked popup nav'), t.close(), true) : (t[p]=v, true)
                }) : createDummyWindow();
            }
            if (isFrozen && !userApprovedNavigation) { handleFreezeNavigation(url, 'win.open()', () => origOpen.call(win, url, target, features), () => {}); return createDummyWindow(); }
            return origOpen.apply(win, arguments);
        };
    } catch {}

    function broadcastToFrames(w, msg) { try { for (let i = 0; i < w.frames.length; i++) { w.frames[i].postMessage(msg, '*'); broadcastToFrames(w.frames[i], msg); } } catch {} }
    function broadcastState() { if (isTop) 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();
        if (e.data.type === 'HIDER_SYNC_STATE') {
            ({ isSelecting, currentScope, isFrozen } = e.data);
            CACHE.isFrozen = isFrozen;
            by('btn-select')?.classList.toggle('active', isSelecting);
            by('btn-scope')?.classList.toggle('active', currentScope === 'link');
            by('btn-freeze')?.classList.toggle('is-frozen', isFrozen);
            if (!isSelecting) clearSelectionState();
        }
        if (e.data.type === 'HIDER_RESUME_VIDEOS') triggerVideoResumeChain();
    });
    if (!isTop) try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch {}

    function updateStyles() {
        let el = by('hider-dynamic-styles') || Object.assign(doc.createElement('style'), { id: 'hider-dynamic-styles' });
        if (!el.parentNode) (doc.head || doc.documentElement).appendChild(el);
        
        const host = location.hostname;
        const siteRules = gv('hider_site_' + host, []);
        const linkRules = gv('hider_link_' + cleanUrl(), []);
        
        const customActiveRules = (CACHE.customRules || []).filter(r => {
            if (!r || !r.selector) return false;
            const t = cleanDomain(r.target);
            return t === '*' || t === host || (t && host.endsWith('.' + t));
        }).map(r => r.selector);

        const combined = [...new Set([...siteRules, ...linkRules, ...customActiveRules])].filter(Boolean);
        
        const css = combined.map(s => `${s}{opacity:0!important;pointer-events:none!important;position:absolute!important;top:-99999px!important;left:-99999px!important;width:0!important;height:0!important;max-width:0!important;max-height:0!important;overflow:hidden!important;visibility:hidden!important;clip:rect(0,0,0,0)!important}`).join('\n');
        
        if (cachedCssString !== css) { 
            cachedCssString = css; 
            el.textContent = css; 
            
            combined.forEach(s => {
                try {
                    doc.querySelectorAll(s).forEach(targetEl => targetEl.classList.add('hider-stealth-target'));
                } catch {}
            });
        }
    }
    updateStyles();

    function getExactSelector(el) {
        if (!el || el.nodeType !== 1) return '';
        const path = []; let curr = el;
        while (curr && curr.nodeType === 1 && curr.tagName.toLowerCase() !== 'html') {
            let tag = curr.tagName.toLowerCase();
            if (tag === 'body') { path.unshift('body'); break; }
            if (curr.id && !curr.id.startsWith('hider-') && !/^\d+$/.test(curr.id) && curr.id.length < 15) { path.unshift(`#${CSS.escape(curr.id)}`); break; }
            let rawC = typeof curr.className === 'string' ? curr.className : curr.getAttribute('class') || '';
            let classes = rawC.trim().split(/\s+/).filter(c => c.length > 1 && !c.startsWith('hider-') && !/^[a-zA-Z0-9]{10,}$/.test(c) && !/^hover:/i.test(c));
            if (classes.length) { tag += `.${CSS.escape(classes[0])}` + (classes[1] ? `.${CSS.escape(classes[1])}` : ''); }
            else {
                let idx = 1, sib = curr.previousElementSibling;
                while (sib) { if (sib.tagName === curr.tagName) idx++; sib = sib.previousElementSibling; }
                tag += `:nth-of-type(${idx})`;
            }
            path.unshift(tag); curr = curr.parentElement;
        }
        return path.join(' > ');
    }

    function attachDragListeners(barEl, handleEl) {
        let startX, startY, initialX, initialY;
        const onStart = e => {
            if (e.target.tagName === 'BUTTON') return;
            isDraggingStepper = true;
            const p = e.touches ? e.touches[0] : e, r = barEl.getBoundingClientRect();
            startX = p.clientX; startY = p.clientY; initialX = r.left; initialY = r.top;
            barEl.style.transition = 'none';
        };
        const onMove = e => {
            if (!isDraggingStepper) return; e.cancelable && e.preventDefault();
            const p = e.touches ? e.touches[0] : e;
            stepperPos = { x: initialX + (p.clientX - startX), y: initialY + (p.clientY - startY) };
            Object.assign(barEl.style, { left: `${stepperPos.x}px`, top: `${stepperPos.y}px`, bottom: 'auto', transform: 'none' });
        };
        const onEnd = () => { if (isDraggingStepper) { isDraggingStepper = false; barEl.style.transition = 'all 0.2s cubic-bezier(0.16, 1, 0.3, 1)'; } };
        handleEl.addEventListener('mousedown', onStart); window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onEnd);
        handleEl.addEventListener('touchstart', onStart, { passive: false }); window.addEventListener('touchmove', onMove, { passive: false }); window.addEventListener('touchend', onEnd);
    }

    function clearSelectionState() {
        previewElement?.classList.remove('hider-preview-highlight'); previewElement = null; stepperStack = [];
        by(STEPPER_BAR_ID)?.remove();
    }

    function turnOffHideMode() {
        if (isSelecting) {
            isSelecting = false;
            by('btn-select')?.classList.remove('active');
            clearSelectionState();
            broadcastState();
        }
    }

    function closeAllMenus(e, force = false) {
        if (!force) {
            if (isSelecting) return; 
            if (isDraggingDock || isDraggingStepper) return; 

            const active = doc.activeElement;
            if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')) {
                if (active.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID)) return;
            }

            if (e && e.target && e.target.closest) {
                if (e.target.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID)) return;
            }

            if (by('hider-panel')?.querySelector('.is-editing')) return;
        }

        const panel = by('hider-panel');
        const menu = by('hider-dock-menu');
        const mainBtn = by('btn-toggle-dock');
        const dockEl = by('hider-main-dock');

        if (panel && panel.style.display === 'flex') {
            panel.style.display = 'none';
            by('btn-manage')?.classList.remove('active');
        }

        if (menu && menu.style.display === 'flex') {
            menu.style.display = 'none';
            mainBtn?.classList.remove('expanded');
            dockEl?.classList.remove('expanded');
            dockEl?.classList.add('manual-hidden');
            if (collapseTimer) clearTimeout(collapseTimer);
            collapseTimer = setTimeout(() => {
                dockEl?.classList.add('is-collapsed');
                collapseTimer = null;
            }, 1000);
        }
    }

    function renderTouchStepperUI() {
        let stepper = by(STEPPER_BAR_ID);
        if (!previewElement) { stepper?.remove(); return; }
        if (!stepper) { stepper = doc.createElement('div'); stepper.id = STEPPER_BAR_ID; (doc.body || doc.documentElement).appendChild(stepper); }

        stepper.className = 'h-glass h-stepper-pill';
        if (stepperPos.x !== null) Object.assign(stepper.style, { left: `${stepperPos.x}px`, top: `${stepperPos.y}px`, bottom: 'auto', transform: 'none' });

        const tag = previewElement.tagName.toLowerCase();
        const c = typeof previewElement.className === 'string' ? previewElement.className.trim().split(/\s+/)[0] : '';
        const classStr = c && !c.startsWith('hider-') ? `.${c}` : '';
        const idStr = previewElement.id && !previewElement.id.startsWith('hider-') ? `#${previewElement.id}` : '';

        stepper.innerHTML = `<div id="hider-drag-handle" class="h-drag-dots" title="Drag">⋮⋮</div><button id="hider-step-up" class="h-btn-icon" title="Parent Level">▲</button><button id="hider-step-down" class="h-btn-icon" title="Child Level">▼</button><div class="h-tag-badge" title="${tag}${idStr}${classStr}">${tag}${idStr}${classStr}</div><button id="hider-step-confirm" class="h-btn-pill h-btn-save">Hide</button><button id="hider-step-cancel" class="h-btn-icon h-btn-close">✖</button>`;

        attachDragListeners(stepper, stepper.querySelector('#hider-drag-handle'));

        stepper.querySelector('#hider-step-up').onclick = e => {
            e.stopPropagation(); const p = previewElement.parentElement;
            if (p && p !== doc.body && p !== doc.documentElement && p.id !== UI_HOST_ID) {
                previewElement.classList.remove('hider-preview-highlight'); stepperStack.push(previewElement);
                previewElement = p; previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
            } else showToast('Top parent reached');
        };

        stepper.querySelector('#hider-step-down').onclick = e => {
            e.stopPropagation();
            if (stepperStack.length || previewElement.firstElementChild) {
                previewElement.classList.remove('hider-preview-highlight');
                previewElement = stepperStack.length ? stepperStack.pop() : previewElement.firstElementChild;
                previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
            }
        };

        stepper.querySelector('#hider-step-confirm').onclick = e => { e.stopPropagation(); confirmHideSelectedElement(); };
        stepper.querySelector('#hider-step-cancel').onclick = e => { e.stopPropagation(); clearSelectionState(); };
    }

    function confirmHideSelectedElement() {
        if (!previewElement) return;
        const el = previewElement;
        el.classList.remove('hider-preview-highlight'); el.classList.add('hider-stealth-target');
        const sel = getExactSelector(el);
        if (sel) {
            const key = currentScope === 'site' ? 'hider_site_' + location.hostname : 'hider_link_' + cleanUrl();
            const s = gv(key, []); if (!s.includes(sel)) { s.push(sel); sv(key, s); }
        }
        updateStyles(); showToast('🙈 Element Hidden!'); clearSelectionState();
    }

    function showToast(msg) {
        by('hider-toast')?.remove();
        const toast = Object.assign(doc.createElement('div'), { id: 'hider-toast', className: 'h-glass h-toast', textContent: msg });
        (doc.body || doc.documentElement).appendChild(toast);
        setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(-50%) translateY(8px)'; setTimeout(() => toast.remove(), 250); }, 2200);
    }

    function setupCustomDropdown(container, initialValue, onChangeCallback) {
        if (!container) return;
        const trigger = container.querySelector('.h-custom-trigger');
        const textSpan = container.querySelector('.h-custom-value-text');
        const options = container.querySelectorAll('.h-custom-opt');

        let currentVal = initialValue || 'ask';
        textSpan.textContent = FREEZE_LABELS[currentVal] || FREEZE_LABELS['ask'];

        options.forEach(opt => {
            opt.classList.toggle('is-selected', opt.getAttribute('data-val') === currentVal);
            opt.onclick = e => {
                e.stopPropagation();
                currentVal = opt.getAttribute('data-val');
                textSpan.textContent = FREEZE_LABELS[currentVal] || opt.textContent.trim();
                options.forEach(o => o.classList.toggle('is-selected', o === opt));
                container.classList.remove('is-open');
                onChangeCallback?.(currentVal);
            };
        });

        trigger.onclick = e => {
            e.stopPropagation();
            const isOpen = container.classList.contains('is-open');
            doc.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
            if (!isOpen) container.classList.add('is-open');
        };
    }

    // Helper to perform domain blocking directly
    function executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl) {
        if (parentDomain && !CACHE.blockedDomainsSet.has(parentDomain)) {
            CACHE.blockedDomainsList.push(parentDomain);
            CACHE.blockedDomainsSet.add(parentDomain);
            sv('hider_blocked_domains', CACHE.blockedDomainsList);
        }
        logBlockedAttempt(parentDomain, triggerType + ' (User Domain Block)');
        promptEl?.remove();
        showToast(`🚫 Blocked ${parentDomain}! You can remove it anytime in Main Menu.`);
        onDeny?.();
        simulateAdWindowSuccess();
    }

    function showFreezePrompt(url, triggerType, onConfirm, onDeny) {
        by('hider-freeze-prompt')?.remove();
        const promptEl = Object.assign(doc.createElement('div'), { id: 'hider-freeze-prompt', className: 'h-glass h-prompt' });
        const displayUrl = url ? (url.length > 38 ? url.substring(0, 35) + '...' : url) : 'another page';
        const parentDomain = getParentDomain(url) || 'unknown domain';

        promptEl.innerHTML = `
            <div style="font-weight:700;color:#38bdf8;font-size:11px;text-transform:uppercase">❄️ Soft Freeze Triggered</div>
            <div style="font-size:12px;color:#f8fafc">Proceed to page?</div>
            <div class="h-url-box">${displayUrl}</div>
            
            <div class="h-custom-select" id="hider-modal-custom-dropdown">
                <div class="h-custom-trigger">
                    <span class="h-custom-value-text">${FREEZE_LABELS[CACHE.freezeMemory] || FREEZE_LABELS['ask']}</span>
                    <span class="h-custom-arrow">▼</span>
                </div>
                <div class="h-custom-options">
                    <div class="h-custom-opt" data-val="ask">❓ Ask Every Time</div>
                    <div class="h-custom-opt" data-val="block_all">⛔ Auto-Block All Navigations</div>
                    <div class="h-custom-opt" data-val="allow_same">🔗 Allow Same Domain Only</div>
                    <div class="h-custom-opt" data-val="allow_all">🟢 Allow All Navigations</div>
                </div>
            </div>

            <div style="display:flex;gap:6px;width:100%;margin-top:4px">
                <button id="hider-freeze-yes" class="h-btn-pill h-btn-save" style="flex:1">Yes</button>
                <button id="hider-freeze-no" class="h-btn-pill h-btn-close" style="flex:1">No</button>
            </div>
            <button id="hider-freeze-block-btn" class="h-btn-pill" style="width:100%;margin-top:3px;background:rgba(239,68,68,0.22);border:1px solid rgba(239,68,68,0.45);color:#fca5a5">🚫 Block Domain (${parentDomain})</button>
        `;

        (doc.body || doc.documentElement).appendChild(promptEl);
        let selectedMem = CACHE.freezeMemory || 'ask';

        setupCustomDropdown(promptEl.querySelector('#hider-modal-custom-dropdown'), selectedMem, v => {
            selectedMem = v;
        });

        const saveMem = () => {
            if (selectedMem !== CACHE.freezeMemory) {
                CACHE.freezeMemory = selectedMem;
                sv('hider_freeze_memory', selectedMem);
            }
        };

        promptEl.querySelector('#hider-freeze-yes').onclick = () => { saveMem(); promptEl.remove(); onConfirm?.(); };
        promptEl.querySelector('#hider-freeze-no').onclick = () => { saveMem(); logBlockedAttempt(url, triggerType + ' (User)'); promptEl.remove(); onDeny?.(); simulateAdWindowSuccess(); };

        // Handle Block Domain Option with optional Don't Ask Again confirmation
        promptEl.querySelector('#hider-freeze-block-btn').onclick = () => {
            saveMem();
            const skipConfirm = gv('hider_skip_block_confirm', false);

            if (skipConfirm) {
                executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl);
                return;
            }

            // Show Confirmation View inside Modal
            promptEl.innerHTML = `
                <div style="font-weight:700;color:#ef4444;font-size:11px;text-transform:uppercase">🚫 Confirm Block Domain</div>
                <div style="font-size:11px;color:#f8fafc;text-align:center;margin:2px 0">Block all future requests to:<br><strong style="color:#38bdf8;font-size:12px">${parentDomain}</strong>?</div>
                
                <label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;margin:4px 0;user-select:none">
                    <input type="checkbox" id="hider-dont-ask-block" style="cursor:pointer;accent-color:#38bdf8"> Don't ask confirmation again
                </label>

                <div style="display:flex;gap:6px;width:100%;margin-top:4px">
                    <button id="hider-confirm-block-yes" class="h-btn-pill h-btn-close" style="flex:1">Yes, Block</button>
                    <button id="hider-confirm-block-no" class="h-btn-pill" style="flex:1;background:rgba(255,255,255,0.1);color:#f1f5f9;border:1px solid rgba(255,255,255,0.15)">Cancel</button>
                </div>
            `;

            promptEl.querySelector('#hider-confirm-block-yes').onclick = () => {
                const dontAskAgain = promptEl.querySelector('#hider-dont-ask-block')?.checked;
                if (dontAskAgain) sv('hider_skip_block_confirm', true);
                executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl);
            };

            promptEl.querySelector('#hider-confirm-block-no').onclick = () => {
                // Return to original prompt dialog
                showFreezePrompt(url, triggerType, onConfirm, onDeny);
            };
        };
    }

    function injectStylesAndUI() {
        if (by('hider-core-styles')) return;
        const s = doc.createElement('style'); s.id = 'hider-core-styles';
        s.textContent = `
        .h-glass{background:rgba(15,23,42,.93);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.12);color:#f8fafc;font-family:system-ui,-apple-system,sans-serif;box-sizing:border-box;border-radius:12px}
        .h-dock{position:fixed;right:0;top:35%;z-index:2147483646;display:flex;flex-direction:column;align-items:flex-end;gap:5px;padding:4px;box-shadow:0 10px 25px rgba(0,0,0,.5);pointer-events:auto;transition:transform .25s ease,opacity .25s ease;will-change:transform,opacity}
        .h-dock.is-collapsed{transform:translateX(50%);opacity:.4}
        .h-dock.manual-hidden{transform:translateX(50%!important);opacity:.4!important}
        .h-dock.is-collapsed:not(.manual-hidden):hover,.h-dock.expanded{transform:translateX(0);opacity:1}
        .h-dock-main{width:36px;height:36px;font-size:16px!important;background:rgba(59,130,246,.3)!important;border-color:rgba(96,165,250,.5)!important;cursor:grab!important}
        .h-dock-main:active{cursor:grabbing!important}
        .h-dock-main.expanded{background:#3b82f6!important;color:#fff!important}
        .h-dock-menu{display:none;flex-direction:column;gap:5px;margin-top:2px}
        .h-dock-btn{width:36px;height:36px;border-radius:9px;border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.05);color:#cbd5e1;font-size:10px;font-weight:700;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;transition:all .15s ease}
        .h-dock-btn:hover{background:rgba(255,255,255,.15);color:#fff}
        .h-dock-btn.active{background:#3b82f6!important;color:#fff!important;border-color:#60a5fa!important}
        .h-dock-btn.scope-link{background:#10b981!important;color:#fff!important}
        .h-dock-btn.is-frozen{background:#8b5cf6!important;color:#fff!important;border-color:#a78bfa!important}
        .h-stepper-pill{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:2147483647;padding:5px 8px;display:flex;align-items:center;gap:6px;box-shadow:0 10px 30px rgba(0,0,0,.6);pointer-events:auto}
        .h-drag-dots{cursor:grab;color:#64748b;font-size:12px;padding:0 3px;user-select:none}
        .h-btn-icon{width:28px;height:28px;border-radius:7px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.06);color:#e2e8f0;font-size:11px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center}
        .h-btn-icon:hover{background:rgba(255,255,255,.18)}
        .h-tag-badge{max-width:130px;height:26px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:0 6px;color:#38bdf8;font-size:10px;font-family:monospace;line-height:24px}
        .h-btn-pill{height:28px;padding:0 10px;border-radius:7px;font-size:11px;font-weight:700;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s ease}
        .h-btn-save{background:#10b981;color:#fff}
        .h-btn-close{background:rgba(239,68,68,.2);border:1px solid rgba(239,68,68,.4);color:#f87171}
        .h-toast{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);padding:7px 16px;font-size:11px;font-weight:600;z-index:2147483647;pointer-events:none;transition:all .25s ease;text-align:center;box-shadow:0 10px 25px rgba(0,0,0,.6);border:1px solid rgba(56,189,248,.3);color:#e0f2fe}
        .h-prompt{position:fixed;top:30px;left:50%;transform:translateX(-50%);padding:14px 16px;z-index:2147483647;display:flex;flex-direction:column;align-items:center;gap:8px;box-shadow:0 15px 35px rgba(0,0,0,.6);pointer-events:auto;width:310px;max-width:90vw}
        .h-url-box{font-size:10px;color:#9ca3af;word-break:break-all;max-height:32px;overflow:hidden;background:rgba(0,0,0,.3);padding:4px 6px;border-radius:6px;width:100%;border:1px solid rgba(255,255,255,.05)}
        .h-select{width:100%;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#fff;padding:5px 8px;font-size:10px;outline:0;box-sizing:border-box}

        /* Pure Custom Inline Dropdown */
        .h-custom-select { position: relative; width: 100%; user-select: none; margin-top: 2px; }
        .h-custom-trigger {
            background: rgba(30,41,59,.85); border: 1px solid rgba(96,165,250,.3); border-radius: 8px;
            color: #f1f5f9; padding: 7px 10px; font-size: 10px; font-weight: 600; cursor: pointer;
            display: flex; justify-content: space-between; align-items: center; transition: all .2s ease;
        }
        .h-custom-trigger:hover { border-color: #38bdf8; background: rgba(30,41,59,.95); }
        .h-custom-arrow { font-size: 8px; color: #38bdf8; transition: transform .2s ease; }
        .h-custom-select.is-open .h-custom-arrow { transform: rotate(180deg); }
        .h-custom-options {
            display: none; flex-direction: column; background: #0f172a; border: 1px solid rgba(255,255,255,.15);
            border-radius: 8px; margin-top: 4px; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,.6);
        }
        .h-custom-select.is-open .h-custom-options { display: flex; }
        .h-custom-opt {
            padding: 8px 10px; color: #cbd5e1; font-size: 10px; cursor: pointer; transition: all .15s ease;
            border-bottom: 1px solid rgba(255,255,255,.04); display: flex; align-items: center; gap: 6px;
        }
        .h-custom-opt:last-child { border-bottom: none; }
        .h-custom-opt:hover, .h-custom-opt.is-selected { background: rgba(56,189,248,.18); color: #38bdf8; font-weight: 700; }

        #hider-panel{position:fixed;right:50px;top:75px;z-index:2147483647;width:320px;max-height:80vh;border-radius:14px;padding:12px;display:none;flex-direction:column;overflow-y:auto;pointer-events:auto;box-shadow:0 15px 35px rgba(0,0,0,.6);gap:6px}
        .panel-header{font-weight:700;font-size:12px;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:6px;display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}
        
        .h-acc-header{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.4px;display:flex;justify-content:space-between;align-items:center;padding:7px 9px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;cursor:pointer;user-select:none;transition:all .2s ease}
        .h-acc-header:hover{background:rgba(255,255,255,.09);color:#f8fafc;border-color:rgba(255,255,255,.15)}
        .h-acc-header.is-open{background:rgba(59,130,246,.15);border-color:rgba(96,165,250,.4);color:#38bdf8}
        .h-acc-arrow{font-size:9px;transition:transform .25s cubic-bezier(0.16, 1, 0.3, 1);color:#38bdf8}
        .h-acc-header.is-open .h-acc-arrow{transform:rotate(180deg)}
        
        .h-acc-content{max-height:0;overflow:hidden;opacity:0;transition:max-height .28s cubic-bezier(0.16, 1, 0.3, 1), opacity .2s ease, padding .2s ease;padding:0 4px;display:flex;flex-direction:column;gap:4px}
        .h-acc-content.is-open{max-height:350px;overflow-y:auto;opacity:1;padding:6px 2px}

        .list-item{font-size:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);border-radius:6px;padding:5px 7px;display:flex;justify-content:space-between;align-items:center;gap:6px}
        .list-item span.rule-text{word-break:break-all;padding-right:4px;color:#cbd5e1;font-family:monospace;font-size:9px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
        .hider-btn-small{cursor:pointer;border:none;border-radius:4px;padding:2px 6px;color:#fff;font-weight:600;background:#ef4444;font-size:9px}
        @keyframes hider-pulse{0%,100%{outline:2px stroke #ef4444}50%{outline:2px dashed #ef4444;box-shadow:inset 0 0 10px rgba(239,68,68,.3)}}
        .hider-preview-highlight{animation:hider-pulse .8s infinite!important;cursor:pointer!important}
        
        @keyframes hider-snow-freeze {
            0% { opacity: 0; backdrop-filter: blur(0px); background: rgba(224, 242, 254, 0); }
            35% { opacity: 1; backdrop-filter: blur(8px) saturate(130%); background: radial-gradient(circle at center, rgba(186, 230, 253, 0.22) 0%, rgba(56, 189, 248, 0.12) 60%, rgba(15, 23, 42, 0.35) 100%); box-shadow: inset 0 0 120px rgba(186, 230, 253, 0.35); }
            100% { opacity: 0; backdrop-filter: blur(0px); background: rgba(224, 242, 254, 0); }
        }
        @keyframes hider-flake-drift {
            0% { transform: translateY(0) scale(0.6); opacity: 0; }
            40% { opacity: 0.95; }
            100% { transform: translateY(40px) scale(1.1); opacity: 0; }
        }
        .hider-freeze-snow-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 2147483647; pointer-events: none; animation: hider-snow-freeze 1.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
        .hider-snow-flake { position: absolute; width: 6px; height: 6px; background: #e0f2fe; border-radius: 50%; box-shadow: 0 0 8px #38bdf8, 0 0 12px #ffffff; animation: hider-flake-drift 1.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
        `;
        (doc.head || doc.documentElement).appendChild(s);

        if (!isTop || by(UI_HOST_ID)) return;
        const host = doc.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;';
        (doc.body || doc.documentElement).appendChild(host);

        host.innerHTML = `
        <div class="h-glass h-dock is-collapsed manual-hidden" id="hider-main-dock">
            <button class="h-dock-btn h-dock-main" id="btn-toggle-dock" title="Toggle Hider Menu">🛡️</button>
            <div class="h-dock-menu" id="hider-dock-menu">
                <button class="h-dock-btn" id="btn-select" title="Target Element to Hide">🎯<span>Hide</span></button>
                <button class="h-dock-btn" id="btn-scope" title="Toggle Scope (Site/Page)">🌐<span>SITE</span></button>
                <button class="h-dock-btn ${isFrozen?'is-frozen':''}" id="btn-freeze" title="Toggle Navigation Freeze">❄️<span>Freeze</span></button>
                <button class="h-dock-btn" id="btn-manage" title="Open Control Panel">⚙️<span>Menu</span></button>
            </div>
        </div>
        <div id="hider-panel" class="h-glass">
            <div class="panel-header"><span>🛡️ Hide Web Elements Pro <span style="font-size:9px;color:#60a5fa">v4.8</span></span><button class="hider-btn-small" id="close-p" style="background:rgba(255,255,255,0.15);cursor:pointer;padding:3px 8px;">✖</button></div>
            
            <div class="h-acc-header" data-target="acc-builder"><span>✏️ Rule Builder</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-builder">
                <input type="text" id="manual-rule-input" placeholder="Rule CSS Selector (e.g. .ad-banner)" class="h-select">
                <div style="display:flex;gap:4px">
                    <input type="text" id="manual-target-input" placeholder="Target Domain (* for Global)" class="h-select" style="flex:1">
                    <button class="hider-btn-small" id="save-rule-btn" style="background:#3b82f6;padding:4px 10px">Save</button>
                </div>
            </div>

            <div class="h-acc-header" data-target="acc-custom"><span>⚙️ Custom Rules (<span id="cnt-custom">0</span>)</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-custom">
                <div id="list-custom-rules" style="display:flex;flex-direction:column;gap:4px"></div>
            </div>

            <div class="h-acc-header" data-target="acc-domains"><span>🚫 Blocked Domains (<span id="cnt-domains">0</span>)</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-domains">
                <div style="display:flex;gap:4px;margin-bottom:2px">
                    <input type="text" id="manual-domain-input" placeholder="e.g. badsite.com" class="h-select" style="flex:1">
                    <button class="hider-btn-small" id="add-domain-btn" style="background:#3b82f6">Add</button>
                </div>
                <div id="list-blocked-domains" style="display:flex;flex-direction:column;gap:4px"></div>
            </div>

            <div class="h-acc-header" data-target="acc-freeze"><span>❄️ Freeze Behavior</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-freeze">
                <div class="h-custom-select" id="freeze-custom-panel-dropdown">
                    <div class="h-custom-trigger">
                        <span class="h-custom-value-text">${FREEZE_LABELS[CACHE.freezeMemory] || FREEZE_LABELS['ask']}</span>
                        <span class="h-custom-arrow">▼</span>
                    </div>
                    <div class="h-custom-options">
                        <div class="h-custom-opt" data-val="ask">❓ Ask Every Time</div>
                        <div class="h-custom-opt" data-val="block_all">⛔ Auto-Block All Navigations</div>
                        <div class="h-custom-opt" data-val="allow_same">🔗 Allow Same Domain Only</div>
                        <div class="h-custom-opt" data-val="allow_all">🟢 Allow All Navigations</div>
                    </div>
                </div>
                <button id="btn-reset-block-confirm" class="hider-btn-small" style="background:rgba(255,255,255,0.08);color:#94a3b8;margin-top:6px;width:100%;padding:5px;border:1px solid rgba(255,255,255,0.1)">Reset "Don't Ask Block Domain" Prompt</button>
            </div>

            <div class="h-acc-header" data-target="acc-logs">
                <div style="display:flex;justify-content:space-between;align-items:center;width:100%;padding-right:6px">
                    <span>📋 Blocked Logs (<span id="cnt-logs">0</span>)</span>
                    <button class="hider-btn-small" id="clear-log-btn" style="background:#475569;font-size:8px;padding:1px 5px">Clear</button>
                </div>
                <span class="h-acc-arrow">▼</span>
            </div>
            <div class="h-acc-content" id="acc-logs">
                <div id="list-blocked-log" style="display:flex;flex-direction:column;gap:3px"></div>
            </div>

            <div class="h-acc-header" data-target="acc-site"><span>🌐 Hidden Site-Wide (<span id="cnt-site">0</span>)</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-site">
                <div id="list-site" style="display:flex;flex-direction:column;gap:4px"></div>
            </div>

            <div class="h-acc-header" data-target="acc-link"><span>📄 Hidden Page Only (<span id="cnt-link">0</span>)</span><span class="h-acc-arrow">▼</span></div>
            <div class="h-acc-content" id="acc-link">
                <div id="list-link" style="display:flex;flex-direction:column;gap:4px"></div>
            </div>
        </div>`;

        doc.querySelectorAll('.h-acc-header').forEach(header => {
            header.onclick = e => {
                if (e.target.closest('button')) return;
                const targetId = header.getAttribute('data-target');
                const content = by(targetId);
                if (!content) return;
                const isOpen = content.classList.contains('is-open');
                header.classList.toggle('is-open', !isOpen);
                content.classList.toggle('is-open', !isOpen);
            };
        });

        setupCustomDropdown(by('freeze-custom-panel-dropdown'), CACHE.freezeMemory, val => {
            CACHE.freezeMemory = val;
            sv('hider_freeze_memory', val);
            showToast('Memory updated');
        });

        by('btn-reset-block-confirm').onclick = () => {
            sv('hider_skip_block_confirm', false);
            showToast('Reset Block Domain confirmation!');
        };

        const dockEl = by('hider-main-dock'), mainBtn = by('btn-toggle-dock');
        
        const savedY = gv('hider_dock_y', null);
        if (savedY) dockEl.style.top = savedY;

        dockEl.addEventListener('mouseleave', () => dockEl.classList.remove('manual-hidden'));

        by('btn-select')?.addEventListener('mousedown', e => e.preventDefault());
        mainBtn?.addEventListener('mousedown', e => e.preventDefault());

        let dockStartY, dockInitY, dockMoved = false;

        const onDockStart = e => {
            const p = e.touches ? e.touches[0] : e;
            dockStartY = p.clientY; dockInitY = dockEl.getBoundingClientRect().top;
            isDraggingDock = true; dockMoved = false;
            dockEl.style.transition = 'none';
        };
        const onDockMove = e => {
            if (!isDraggingDock) return;
            const p = e.touches ? e.touches[0] : e;
            const dy = p.clientY - dockStartY;
            if (Math.abs(dy) > 4) dockMoved = true;
            if (dockMoved) {
                e.cancelable && e.preventDefault();
                dockEl.style.top = `${dockInitY + dy}px`;
            }
        };
        const onDockEnd = () => {
            if (isDraggingDock) {
                isDraggingDock = false;
                dockEl.style.transition = '';
                if (dockMoved) sv('hider_dock_y', dockEl.style.top);
            }
        };

        mainBtn.addEventListener('mousedown', onDockStart);
        window.addEventListener('mousemove', onDockMove, { passive: true });
        window.addEventListener('mouseup', onDockEnd);
        mainBtn.addEventListener('touchstart', onDockStart, { passive: false });
        window.addEventListener('touchmove', onDockMove, { passive: false });
        window.addEventListener('touchend', onDockEnd);

        mainBtn.onclick = e => {
            e.stopPropagation();
            if (dockMoved) return;
            
            if (collapseTimer) { clearTimeout(collapseTimer); collapseTimer = null; }

            const menu = by('hider-dock-menu');
            const isOpening = menu.style.display !== 'flex';
            menu.style.display = isOpening ? 'flex' : 'none';
            mainBtn.classList.toggle('expanded', isOpening);
            dockEl.classList.toggle('expanded', isOpening);
            dockEl.classList.toggle('is-collapsed', !isOpening);

            if (!isOpening) {
                turnOffHideMode();
                by('hider-panel').style.display = 'none';
                by('btn-manage')?.classList.remove('active');

                dockEl.classList.add('manual-hidden');
                collapseTimer = setTimeout(() => {
                    dockEl.classList.add('is-collapsed');
                    collapseTimer = null;
                }, 1000);
            } else {
                dockEl.classList.remove('manual-hidden');
            }
        };

        by('save-rule-btn').onclick = () => {
            const ruleInput = by('manual-rule-input');
            const targetInput = by('manual-target-input');
            const sel = (ruleInput.value || '').trim();
            let target = cleanDomain(targetInput.value) || '*';

            if (!sel) { showToast('⚠️ Enter a selector'); return; }

            if (editingRuleId) {
                const idx = CACHE.customRules.findIndex(r => r.id === editingRuleId);
                if (idx !== -1) {
                    CACHE.customRules[idx] = { id: editingRuleId, selector: sel, target };
                    showToast('✏️ Rule updated');
                }
                editingRuleId = null;
                by('save-rule-btn').textContent = 'Save';
            } else {
                const newRule = { id: 'rule_' + Date.now(), selector: sel, target };
                CACHE.customRules.push(newRule);
                showToast('✨ Custom Rule Added!');
            }

            sv('hider_custom_rules_v4', CACHE.customRules);
            ruleInput.value = ''; targetInput.value = '';
            updateStyles(); renderList();
        };

        const addDomain = () => {
            const input = by('manual-domain-input'), d = cleanDomain(input.value);
            if (d && !CACHE.blockedDomainsSet.has(d)) {
                CACHE.blockedDomainsList.push(d); CACHE.blockedDomainsSet.add(d);
                sv('hider_blocked_domains', CACHE.blockedDomainsList); showToast(`🚫 Blocked: ${d}`);
            }
            input.value = ''; renderList();
        };

        by('add-domain-btn').onclick = addDomain;
        by('manual-domain-input').onkeypress = e => e.key === 'Enter' && addDomain();

        by('btn-select').onclick = e => {
            e.stopPropagation();
            isSelecting = !isSelecting;
            e.currentTarget.classList.toggle('active', isSelecting);
            if (isSelecting) { by('hider-panel').style.display = 'none'; by('btn-manage').classList.remove('active'); }
            else clearSelectionState();
            broadcastState();
        };

        const btnScope = by('btn-scope');
        btnScope.onclick = e => {
            e.stopPropagation();
            currentScope = currentScope === 'site' ? 'link' : 'site';
            btnScope.querySelector('span').textContent = currentScope.toUpperCase();
            btnScope.classList.toggle('scope-link', currentScope === 'link');
            showToast(`Scope: ${currentScope.toUpperCase()}`); broadcastState();
        };

        // Manual Freeze Button Toggle
        const btnFreeze = by('btn-freeze');
        btnFreeze.onclick = e => {
            e.stopPropagation();
            isFrozen = !isFrozen; 
            CACHE.isFrozen = isFrozen; 
            sv('hider_freeze_global', isFrozen);
            btnFreeze.classList.toggle('is-frozen', isFrozen);
            if (isFrozen) triggerFreezeFx();
            showToast(isFrozen ? '❄️ Freeze ON' : '🔥 Freeze OFF'); 
            broadcastState();
        };

        by('btn-manage').onclick = e => {
            e.stopPropagation();
            const p = by('hider-panel'), show = p.style.display !== 'flex';
            p.style.display = show ? 'flex' : 'none';
            e.currentTarget.classList.toggle('active', show);
            if (show) { turnOffHideMode(); renderList(); }
        };

        by('close-p').onclick = e => {
            if (e) { e.stopPropagation(); e.preventDefault(); }
            turnOffHideMode();
            closeAllMenus(e, true);
        };

        by('clear-log-btn').onclick = (e) => { 
            if (e) e.stopPropagation();
            CACHE.logs = []; sv('hider_global_logs', []); renderList(); 
        };
    }

    function renderList() {
        if (!CACHE.logs) CACHE.logs = gv('hider_global_logs', []);
        const siteKey = 'hider_site_' + location.hostname, linkKey = 'hider_link_' + cleanUrl();
        const siteData = gv(siteKey, []), linkData = gv(linkKey, []);

        const cCustom = by('list-custom-rules'), cDomains = by('list-blocked-domains'), cSite = by('list-site'), cLink = by('list-link'), cLog = by('list-blocked-log');
        if (!cSite || !cLink) return;

        if (by('cnt-custom')) by('cnt-custom').textContent = CACHE.customRules.length;
        if (by('cnt-domains')) by('cnt-domains').textContent = CACHE.blockedDomainsList.length;
        if (by('cnt-logs')) by('cnt-logs').textContent = CACHE.logs.length;
        if (by('cnt-site')) by('cnt-site').textContent = siteData.length;
        if (by('cnt-link')) by('cnt-link').textContent = linkData.length;

        if (cCustom) {
            cCustom.innerHTML = CACHE.customRules.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No custom rules</div>`;
            CACHE.customRules.forEach(rule => {
                const div = doc.createElement('div'); div.className = 'list-item';
                const targetTag = rule.target === '*' ? '<span style="color:#38bdf8">[Global]</span>' : `<span style="color:#10b981">[${rule.target}]</span>`;
                div.innerHTML = `<span class="rule-text" title="${rule.selector}">${rule.selector} ${targetTag}</span><div style="display:flex;gap:2px"><button class="hider-btn-small btn-edit" style="background:#3b82f6">✏️</button><button class="hider-btn-small btn-del">✖</button></div>`;
                
                div.querySelector('.btn-edit').onclick = () => {
                    editingRuleId = rule.id;
                    by('manual-rule-input').value = rule.selector;
                    by('manual-target-input').value = rule.target;
                    by('save-rule-btn').textContent = 'Update';
                    
                    const bHeader = doc.querySelector('[data-target="acc-builder"]');
                    const bContent = by('acc-builder');
                    if (bHeader && bContent && !bContent.classList.contains('is-open')) {
                        bHeader.classList.add('is-open'); bContent.classList.add('is-open');
                    }
                };
                div.querySelector('.btn-del').onclick = () => {
                    CACHE.customRules = CACHE.customRules.filter(r => r.id !== rule.id);
                    sv('hider_custom_rules_v4', CACHE.customRules); updateStyles(); renderList();
                };
                cCustom.appendChild(div);
            });
        }

        if (cDomains) {
            cDomains.innerHTML = CACHE.blockedDomainsList.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No blocked domains</div>`;
            CACHE.blockedDomainsList.forEach((d, i) => {
                const div = doc.createElement('div'); div.className = 'list-item';
                div.innerHTML = `<span class="rule-text" title="${d}">${d}</span><button class="hider-btn-small">✖</button>`;
                div.querySelector('button').onclick = () => { CACHE.blockedDomainsList.splice(i, 1); CACHE.blockedDomainsSet.delete(cleanDomain(d)); sv('hider_blocked_domains', CACHE.blockedDomainsList); renderList(); };
                cDomains.appendChild(div);
            });
        }

        if (cLog) {
            cLog.innerHTML = CACHE.logs.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No logs today</div>`;
            CACHE.logs.forEach(item => {
                const row = doc.createElement('div'); row.style.cssText = 'font-size:9px;border-bottom:1px solid rgba(255,255,255,0.06);padding:3px 0;';
                row.innerHTML = `<div style="display:flex;justify-content:space-between;color:#a7f3d0;font-weight:bold"><span>${item.type}</span><span style="color:#64748b">${item.time}</span></div><div style="color:#94a3b8;word-break:break-all;font-family:monospace">${item.url}</div>`;
                cLog.appendChild(row);
            });
        }

        const renderEditableHiddenItems = (data, key, container) => {
            container.innerHTML = data.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">None</div>`;
            data.forEach((sel, i) => {
                const div = doc.createElement('div'); div.className = 'list-item';
                div.innerHTML = `
                    <span class="rule-text" title="${sel}">${sel}</span>
                    <div style="display:flex;gap:2px;align-items:center">
                        <button class="hider-btn-small btn-edit" style="background:#3b82f6" title="Edit Rule Inline">✏️</button>
                        <button class="hider-btn-small btn-wild" style="background:#8b5cf6" title="Convert Wildcard">🪄</button>
                        <button class="hider-btn-small btn-del" style="background:#ef4444" title="Delete Rule">✖</button>
                    </div>`;

                const textSpan = div.querySelector('.rule-text');
                const editBtn = div.querySelector('.btn-edit');

                editBtn.onclick = () => {
                    if (div.classList.contains('is-editing')) return;
                    div.classList.add('is-editing');
                    
                    const input = doc.createElement('input');
                    input.type = 'text';
                    input.value = data[i];
                    input.className = 'h-select';
                    input.style.cssText = 'font-size:9px;padding:2px 4px;height:22px;flex:1;margin-right:4px;';
                    
                    const saveBtn = doc.createElement('button');
                    saveBtn.className = 'hider-btn-small';
                    saveBtn.style.cssText = 'background:#10b981;padding:2px 6px;';
                    saveBtn.textContent = '✓';
                    
                    textSpan.replaceWith(input);
                    editBtn.replaceWith(saveBtn);
                    input.focus();

                    const saveHandler = () => {
                        const newVal = input.value.trim();
                        if (newVal) {
                            data[i] = newVal;
                            sv(key, data);
                            updateStyles();
                            showToast('✏️ Hidden rule updated');
                        }
                        renderList();
                    };

                    saveBtn.onclick = saveHandler;
                    input.onkeypress = e => e.key === 'Enter' && saveHandler();
                };

                div.querySelector('.btn-wild').onclick = () => { 
                    data[i] = convertToWildcardSelector(data[i]); 
                    sv(key, data); updateStyles(); renderList(); showToast(`🪄 Wildcard applied`); 
                };

                div.querySelector('.btn-del').onclick = () => {
                    const removed = data.splice(i, 1)[0]; sv(key, data);
                    try { doc.querySelectorAll(removed).forEach(el => el.classList.remove('hider-stealth-target')); } catch {}
                    updateStyles(); renderList();
                };

                container.appendChild(div);
            });
        };

        renderEditableHiddenItems(siteData, siteKey, cSite); 
        renderEditableHiddenItems(linkData, linkKey, cLink);
    }

    function handleNavigationClick(e) {
        if (isSelecting || e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID)) return;
        const link = e.target.closest('a[href], area[href]');
        if (link?.href && !link.getAttribute('href').startsWith('#') && !link.getAttribute('href').startsWith('javascript:')) {
            const targetUrl = link.href;
            if (isDomainBlocked(targetUrl)) {
                e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                logBlockedAttempt(targetUrl, 'Blocked Link Click'); simulateAdWindowSuccess(); showToast('⛔ Force Blocked'); return;
            }
            if (!isFrozen) return;
            e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
            const isNewTab = link.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1;
            handleFreezeNavigation(targetUrl, 'Link Click', () => {
                userApprovedNavigation = true;
                if (isNewTab) win.open(targetUrl, '_blank'); else win.location.href = targetUrl;
                setTimeout(() => userApprovedNavigation = false, 300);
            }, simulateAdWindowSuccess);
        }
    }

    ['mousedown', 'pointerdown', 'mouseup'].forEach(evtType => {
        window.addEventListener(evtType, e => {
            if (!isSelecting) return;
            if (e.target.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID + ', #hider-freeze-prompt')) return;
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
        }, true);
    });

    window.addEventListener('scroll', closeAllMenus, { passive: true });

    window.addEventListener('click', e => {
        const isUI = e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID);
        
        if (!isUI) {
            closeAllMenus(e);
            doc.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
        }

        if (isUI) return;
        
        handleNavigationClick(e); 
        if (!isSelecting) return;
        
        e.preventDefault(); e.stopPropagation();
        if (previewElement === e.target) confirmHideSelectedElement();
        else {
            previewElement?.classList.remove('hider-preview-highlight');
            stepperStack = []; previewElement = e.target; previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
        }
    }, true);

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

    window.addEventListener('submit', e => {
        if (e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID)) return;
        const form = e.target, action = form.action || location.href;
        if (isDomainBlocked(action)) {
            e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
            logBlockedAttempt(action, 'Blocked Form Submit'); simulateAdWindowSuccess(); showToast('⛔ Blocked Form'); return;
        }
        if (!isFrozen) return;
        e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
        handleFreezeNavigation(action, 'Form Submit', () => { userApprovedNavigation = true; form.submit(); setTimeout(() => userApprovedNavigation = false, 300); }, simulateAdWindowSuccess);
    }, true);

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

    const checkUrlChange = () => {
        if (location.href !== lastUrl) {
            lastUrl = location.href; updateStyles();
            if (by('hider-panel')?.style.display === 'flex') renderList();
        }
    };

    ['pushState', 'replaceState'].forEach(fn => {
        const orig = history[fn];
        if (orig) history[fn] = function() { orig.apply(this, arguments); checkUrlChange(); };
    });
    window.addEventListener('popstate', checkUrlChange, { passive: true });
    window.addEventListener('hashchange', checkUrlChange, { passive: true });

    function init() {
        ['contextmenu', 'selectstart', 'copy', 'paste', 'dragstart'].forEach(evt => doc.addEventListener(evt, e => e.stopPropagation(), true));
        injectStylesAndUI(); updateStyles();
        let timer = null;
        new MutationObserver(() => {
            if (timer) return;
            timer = setTimeout(() => {
                timer = null;
                if (isTop && !by(UI_HOST_ID)) injectStylesAndUI();
                if (!by('hider-dynamic-styles')) updateStyles();
            }, 1000);
        }).observe(doc.documentElement || doc.body, { childList: true, subtree: true });
    }

    if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', init); else init();
})();