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