Hide Web Elements Pro

Right-edge FAB, Y-axis memory drag, Fixed auto-hide hover bug, High battery/performance optimization

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.

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

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

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

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

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

// ==UserScript==
// @name         Hide Web Elements Pro 
// @version      3.65
// @description  Right-edge FAB, Y-axis memory drag, Fixed auto-hide hover bug, High battery/performance optimization
// @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();
})();