Hide Web Elements Pro

Powerful all-in-one webpage customization tool with a sleek glass UI. Instantly hide unwanted elements, block popup/redirect domains, freeze navigation, manage persistent hide rules, and enjoy a fast, battery-friendly experience on both desktop and Android browsers.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Hide Web Elements Pro 
// @version      3.65
// @description  Powerful all-in-one webpage customization tool with a sleek glass UI. Instantly hide unwanted elements, block popup/redirect domains, freeze navigation, manage persistent hide rules, and enjoy a fast, battery-friendly experience on both desktop and Android browsers.
// @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(), isFrozen: 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, logSaveTimer = null;
    let collapseTimer = null;

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

    function syncCache() {
        CACHE.blockedDomainsList = gv('hider_blocked_domains', []);
        CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
        CACHE.isFrozen = gv('hider_freeze_global', false);
        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();

    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 => 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 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
    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');
            lp.assign = function(u) { if (isDomainBlocked(u)) { logBlockedAttempt(u, 'Blocked Redirect'); showToast('⛔ Blocked redirect'); simulateAdWindowSuccess(); return; } return origA.apply(this, arguments); };
            lp.replace = function(u) { if (isDomainBlocked(u)) { logBlockedAttempt(u, 'Blocked Redirect'); showToast('⛔ Blocked redirect'); simulateAdWindowSuccess(); return; } return origR.apply(this, arguments); };
            if (hrefDesc?.set) {
                Object.defineProperty(lp, 'href', { set(u) { if (isDomainBlocked(u)) { logBlockedAttempt(u, 'Blocked Redirect'); showToast('⛔ Blocked redirect'); simulateAdWindowSuccess(); return; } hrefDesc.set.call(this, u); } });
            }
        }
        HTMLAnchorElement.prototype.click = function() { if (isDomainBlocked(this.href)) { logBlockedAttempt(this.href, 'Blocked Click'); showToast('⛔ Blocked link click'); 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);
            by('btn-select')?.classList.toggle('active', isSelecting);
            by('btn-scope')?.classList.toggle('active', currentScope === 'link');
            by('btn-freeze')?.classList.toggle('active', 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 combined = [...new Set([...gv('hider_site_' + location.hostname, []), ...gv('hider_link_' + cleanUrl(), [])])];
        const css = combined.filter(Boolean).map(s => `${s}{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important}`).join('\n');
        if (cachedCssString !== css) { cachedCssString = css; el.textContent = css; }
    }
    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 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.style.setProperty('display', 'none', 'important');
        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); }, 1800);
    }

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

        promptEl.innerHTML = `<div style="font-weight:700;color:#f43f5e;font-size:11px;text-transform:uppercase">❄️ Navigation Blocked</div><div style="font-size:12px;color:#f8fafc">Proceed to page?</div><div class="h-url-box">${displayUrl}</div><select id="hider-freeze-modal-memory" class="h-select"><option value="ask" ${CACHE.freezeMemory==='ask'?'selected':''}>Ask Every Time</option><option value="block_all" ${CACHE.freezeMemory==='block_all'?'selected':''}>Auto-Block All</option><option value="allow_same" ${CACHE.freezeMemory==='allow_same'?'selected':''}>Allow Same Domain</option><option value="allow_all" ${CACHE.freezeMemory==='allow_all'?'selected':''}>Allow All Navigations</option></select><div style="display:flex;gap:6px;width:100%"><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>`;

        (doc.body || doc.documentElement).appendChild(promptEl);
        const saveMem = () => { const m = by('hider-freeze-modal-memory'); if (m && m.value !== CACHE.freezeMemory) { CACHE.freezeMemory = m.value; sv('hider_freeze_memory', m.value); } };
        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(); };
    }

    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,.92);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}.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}.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:6px 16px;font-size:11px;font-weight:600;z-index:2147483647;pointer-events:none;transition:all .25s ease}.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:300px;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:4px 6px;font-size:10px;outline:0}#hider-panel{position:fixed;right:50px;top:75px;z-index:2147483647;width:300px;max-height:75vh;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:8px}.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}.section-title{font-size:9px;color:#94a3b8;font-weight:700;text-transform:uppercase;letter-spacing:.5px;display:flex;justify-content:space-between;align-items:center;margin-top:4px}.list-item{font-size:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);border-radius:6px;padding:4px 6px;display:flex;justify-content:space-between;align-items:center}.list-item span{word-break:break-all;padding-right:6px;color:#cbd5e1;font-family:monospace;font-size:9px}.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}`;
        (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 Dashboard">⚙️<span>Menu</span></button></div></div><div id="hider-panel" class="h-glass"><div class="panel-header"><span>🛡️ Control Center</span><button class="hider-btn-small" id="close-p" style="background:rgba(255,255,255,0.1)">✖</button></div><div class="section-title">Block Domain</div><div style="display:flex;gap:4px"><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 class="section-title">Blocked Domains</div><div id="list-blocked-domains"></div><div class="section-title">Freeze Mode Behavior</div><select id="freeze-memory-select" class="h-select"><option value="ask">Ask Every Time</option><option value="block_all">Auto-Block All</option><option value="allow_same">Allow Same Domain</option><option value="allow_all">Allow All Navigations</option></select><div class="section-title"><span>Blocked Log</span><button class="hider-btn-small" id="clear-log-btn" style="background:#475569">Clear</button></div><div id="list-blocked-log" style="max-height:100px;overflow-y:auto;background:rgba(0,0,0,.25);border-radius:6px;padding:4px"></div><div class="section-title">Hidden Site-Wide (${location.hostname})</div><div id="list-site"></div><div class="section-title">Hidden This Page Only</div><div id="list-link"></div></div>`;

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

        // Reset manual-hidden on mouse leave so hover works again naturally
        dockEl.addEventListener('mouseleave', () => {
            dockEl.classList.remove('manual-hidden');
        });

        // Draggable FAB Dock Logic (Y-Axis Only with GPU acceleration)
        let dockStartY, dockInitY, isDraggingDock = false, 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); // Save position
            }
        };

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

        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 => {
            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 = () => {
            currentScope = currentScope === 'site' ? 'link' : 'site';
            btnScope.querySelector('span').textContent = currentScope.toUpperCase();
            btnScope.classList.toggle('scope-link', currentScope === 'link');
            showToast(`Scope: ${currentScope.toUpperCase()}`); broadcastState();
        };

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

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

        const closePanelAndTriggerCollapse = () => {
            by('hider-panel').style.display = 'none';
            by('btn-manage').classList.remove('active');
            
            const menu = by('hider-dock-menu');
            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);
        };

        by('close-p').onclick = closePanelAndTriggerCollapse;
        by('clear-log-btn').onclick = () => { 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 cDomains = by('list-blocked-domains'), cSite = by('list-site'), cLink = by('list-link'), cLog = by('list-blocked-log'), memSelect = by('freeze-memory-select');
        if (!cSite || !cLink) return;

        if (memSelect) {
            memSelect.value = CACHE.freezeMemory || 'ask';
            memSelect.onchange = e => { CACHE.freezeMemory = e.target.value; sv('hider_freeze_memory', e.target.value); showToast(`Memory updated`); };
        }

        if (cDomains) {
            cDomains.innerHTML = CACHE.blockedDomainsList.length ? '' : `<div style="font-size:9px;color:#64748b">None</div>`;
            CACHE.blockedDomainsList.forEach((d, i) => {
                const div = doc.createElement('div'); div.className = 'list-item';
                div.innerHTML = `<span>${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;text-align:center">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 renderItems = (data, key, container) => {
            container.innerHTML = data.length ? '' : `<div style="font-size:9px;color:#64748b">None</div>`;
            data.forEach((sel, i) => {
                const div = doc.createElement('div'); div.className = 'list-item';
                div.innerHTML = `<span>${sel.substring(0, 20)}...</span><div style="display:flex;gap:2px"><button class="hider-btn-small" style="background:#8b5cf6" title="Convert Wildcard">🪄</button><button class="hider-btn-small btn-del">✖</button></div>`;
                div.querySelector('button[title]').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 => ['display','visibility','opacity','pointer-events'].forEach(p => el.style.removeProperty(p))); } catch {}
                    updateStyles(); renderList();
                };
                container.appendChild(div);
            });
        };
        renderItems(siteData, siteKey, cSite); renderItems(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);
        }
    }

    window.addEventListener('click', e => {
        if (e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID)) 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();
})();