Hide Web Elements Pro

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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