Stealth element hider, persistent global freeze mode, custom non-native dropdowns, editable hidden rules, domain & global rule editor, soft snow freeze mode, quick parent domain blocker, anti-detection engine.
// ==UserScript==
// @name Hide Web Elements Pro
// @version 4.8
// @description Stealth element hider, persistent global freeze mode, custom non-native dropdowns, editable hidden rules, domain & global rule editor, soft snow freeze mode, quick parent domain blocker, anti-detection engine.
// @author KTZ
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @run-at document-start
// @license MIT
// @namespace https://greasyfork.org/users/1620673
// ==/UserScript==
(function() {
'use strict';
const doc = document, win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window, gv = GM_getValue, sv = GM_setValue;
const UI_HOST_ID = 'hider-ui-root', STEPPER_BAR_ID = 'hider-stepper-bar', isTop = (window.self === window.top);
const by = id => doc.getElementById(id);
let initialMem = gv('hider_freeze_memory', null) || (gv('hider_autono_global', false) ? 'block_all' : 'ask');
const CACHE = {
blockedDomainsList: [],
blockedDomainsSet: new Set(),
customRules: [],
isFrozen: gv('hider_freeze_global', false),
freezeMemory: initialMem,
logs: null
};
let isSelecting = false, isFrozen = CACHE.isFrozen, currentScope = 'site', previewElement = null;
let stepperStack = [], userApprovedNavigation = false, cachedCssString = null, lastUrl = location.href;
let stepperPos = { x: null, y: null }, isDraggingStepper = false, isDraggingDock = false, logSaveTimer = null;
let collapseTimer = null, editingRuleId = null;
const FREEZE_LABELS = {
'ask': '❓ Ask Every Time',
'block_all': '⛔ Auto-Block All Navigations',
'allow_same': '🔗 Allow Same Domain Only',
'allow_all': '🟢 Allow All Navigations'
};
const cleanUrl = () => location.origin + location.pathname;
const cleanDomain = url => {
try {
if (!url) return '';
if (url === '*') return '*';
return new URL(url.includes('://') ? url : 'http://' + url).hostname.replace(/^www\./, '');
} catch {
return url.trim().toLowerCase();
}
};
// Extracts the root/parent domain instead of long subdomains
function getParentDomain(url) {
if (!url) return '';
try {
let host = new URL(url.includes('://') ? url : 'http://' + url).hostname.replace(/^www\./, '').toLowerCase();
if (!host) return '';
let parts = host.split('.');
if (parts.length <= 2) return host;
// Common multi-part TLD suffixes
const multiPartTlds = ['co.uk', 'com.au', 'org.uk', 'gov.uk', 'co.jp', 'com.br', 'co.id', 'or.id', 'ac.uk', 'net.au', 'com.tw', 'co.nz', 'com.sg', 'com.mx', 'co.kr', 'com.tr'];
let lastTwo = parts.slice(-2).join('.');
if (multiPartTlds.includes(lastTwo) && parts.length > 2) {
return parts.slice(-3).join('.');
}
return parts.slice(-2).join('.');
} catch {
return cleanDomain(url) || '';
}
}
function syncCache() {
CACHE.blockedDomainsList = gv('hider_blocked_domains', []);
CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
CACHE.customRules = gv('hider_custom_rules_v4', []);
CACHE.isFrozen = gv('hider_freeze_global', false);
isFrozen = CACHE.isFrozen;
CACHE.freezeMemory = gv('hider_freeze_memory', CACHE.freezeMemory);
if (gv('hider_last_log_clear_day', '') !== new Date().toDateString()) {
CACHE.logs = []; sv('hider_global_logs', []); sv('hider_last_log_clear_day', new Date().toDateString());
}
}
syncCache();
// Stealth Engine: Anti-Anti-Adblock
try {
const origGetComputedStyle = win.getComputedStyle;
win.getComputedStyle = function(el, pseudo) {
const style = origGetComputedStyle.apply(this, arguments);
if (el && el.nodeType === 1 && el.classList.contains('hider-stealth-target')) {
return new Proxy(style, {
get(target, prop) {
if (prop === 'display') return 'block';
if (prop === 'visibility') return 'visible';
if (prop === 'opacity') return '1';
if (prop === 'pointerEvents') return 'auto';
const val = target[prop];
return typeof val === 'function' ? val.bind(target) : val;
}
});
}
return style;
};
} catch {}
const isSameDomain = u => cleanDomain(u) && cleanDomain(u) === cleanDomain(location.href);
const isDomainBlocked = u => {
if (!u || !CACHE.blockedDomainsSet.size) return false;
const d = cleanDomain(u), lower = u.toLowerCase();
return d && (CACHE.blockedDomainsSet.has(d) || [...CACHE.blockedDomainsSet].some(b => b !== '*' && (d.endsWith('.' + b) || lower.includes(b))));
};
function convertToWildcardSelector(sel) {
return (sel || '').replace(/(#|\.)([a-zA-Z0-9_-]+)/g, (m, p, name) => {
if (name.startsWith('hider-')) return '';
let base = (name.match(/^([a-zA-Z_-]+?)[0-9a-fA-F_-]{3,}$/)?.[1] || name.replace(/[0-9a-fA-F]{4,}$/, '')).replace(/[-_]+$/, '');
return p === '#' ? `[id*="${base || name}"]` : `[class*="${base || name}"]`;
}).replace(/\s*>\s*/g, ' ');
}
function logBlockedAttempt(url, triggerType) {
if (!CACHE.logs) CACHE.logs = gv('hider_global_logs', []);
CACHE.logs.unshift({ url: url || 'about:blank', time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }), type: triggerType || 'Popup' });
if (CACHE.logs.length > 50) CACHE.logs.pop();
if (!logSaveTimer) logSaveTimer = setTimeout(() => { logSaveTimer = null; sv('hider_global_logs', CACHE.logs); }, 1000);
}
function triggerFreezeFx() {
const fx = doc.createElement('div');
fx.className = 'hider-freeze-snow-overlay';
for (let i = 0; i < 14; i++) {
const flake = doc.createElement('div');
flake.className = 'hider-snow-flake';
flake.style.left = `${Math.random() * 100}vw`;
flake.style.top = `${Math.random() * 70}vh`;
flake.style.animationDelay = `${Math.random() * 0.3}s`;
flake.style.transform = `scale(${0.4 + Math.random() * 0.8})`;
fx.appendChild(flake);
}
(doc.body || doc.documentElement).appendChild(fx);
setTimeout(() => fx.remove(), 1300);
}
function forceExposeAndPlayVideos() {
doc.querySelectorAll('video').forEach(v => {
['display', 'visibility', 'opacity', 'pointer-events'].forEach(p => v.style.setProperty(p, p === 'display' ? 'block' : p === 'pointer-events' ? 'auto' : '1', 'important'));
v.play()?.catch?.(() => {});
});
}
function triggerVideoResumeChain() {
forceExposeAndPlayVideos(); [150, 400, 800].forEach(t => setTimeout(forceExposeAndPlayVideos, t));
broadcastToFrames(window, { type: 'HIDER_RESUME_VIDEOS' });
}
function simulateAdWindowSuccess() {
try {
win.dispatchEvent(new Event('blur')); doc.dispatchEvent(new Event('visibilitychange'));
win.onblur?.();
setTimeout(() => { win.dispatchEvent(new Event('focus')); win.onfocus?.(); }, 60);
triggerVideoResumeChain();
} catch {}
}
function createDummyWindow() {
const dummy = {
closed: false, focus(){}, blur(){}, close(){ this.closed = true; }, postMessage(){},
location: { href: 'about:blank', replace(u){ if(isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Replace'); }, assign(u){ if(isDomainBlocked(u)) logBlockedAttempt(u, 'Blocked Dummy Assign'); } },
document: { write(){}, close(){}, body:{} }, opener: win
};
return new Proxy(dummy, {
get: (t, p) => p in t ? t[p] : () => {},
set: (t, p, v) => { if (p === 'location' && isDomainBlocked(v)) logBlockedAttempt(v, 'Blocked Dummy Location Set'); else t[p] = v; return true; }
});
}
function handleFreezeNavigation(url, triggerType, onConfirm, onDeny) {
const mem = CACHE.freezeMemory || 'ask';
if (mem === 'block_all') { logBlockedAttempt(url, triggerType + ' (Auto-Block)'); onDeny?.(); simulateAdWindowSuccess(); return; }
if (mem === 'allow_all' || (mem === 'allow_same' && isSameDomain(url))) {
userApprovedNavigation = true; onConfirm?.(); setTimeout(() => userApprovedNavigation = false, 300); return;
}
showFreezePrompt(url, triggerType, onConfirm, onDeny);
}
// Interceptors for Location & Navigation Interception
try {
const lp = win.Location ? win.Location.prototype : Object.getPrototypeOf(win.location);
if (lp) {
const origA = lp.assign, origR = lp.replace, hrefDesc = Object.getOwnPropertyDescriptor(lp, 'href');
const checkAndInterceptNav = (u, origFn, contextThis, triggerName) => {
if (isDomainBlocked(u)) {
logBlockedAttempt(u, `Blocked ${triggerName}`);
showToast(`⛔ Blocked ${triggerName}`);
simulateAdWindowSuccess();
return;
}
if (isFrozen && !userApprovedNavigation) {
handleFreezeNavigation(u, triggerName, () => {
userApprovedNavigation = true;
origFn.call(contextThis, u);
setTimeout(() => userApprovedNavigation = false, 300);
}, simulateAdWindowSuccess);
return;
}
return origFn.call(contextThis, u);
};
lp.assign = function(u) { return checkAndInterceptNav(u, origA, this, 'Redirect (assign)'); };
lp.replace = function(u) { return checkAndInterceptNav(u, origR, this, 'Redirect (replace)'); };
if (hrefDesc?.set) {
Object.defineProperty(lp, 'href', {
set(u) { checkAndInterceptNav(u, hrefDesc.set, this, 'Redirect (href)'); },
get() { return hrefDesc.get.call(this); }
});
}
}
HTMLAnchorElement.prototype.click = function() {
if (isDomainBlocked(this.href)) { logBlockedAttempt(this.href, 'Blocked Click'); showToast('⛔ Blocked link click'); simulateAdWindowSuccess(); return; }
if (isFrozen && !userApprovedNavigation) {
handleFreezeNavigation(this.href, 'Anchor Click', () => {
userApprovedNavigation = true;
HTMLElement.prototype.click.apply(this, arguments);
setTimeout(() => userApprovedNavigation = false, 300);
}, simulateAdWindowSuccess);
return;
}
return HTMLElement.prototype.click.apply(this, arguments);
};
} catch {}
try {
const origOpen = win.open;
win.open = function(url, target, features) {
if (isDomainBlocked(url)) { logBlockedAttempt(url || 'about:blank', 'Blocked Popup'); simulateAdWindowSuccess(); showToast('⛔ Blocked Popup'); return createDummyWindow(); }
if (!url || url === 'about:blank') {
const realWin = origOpen.apply(win, arguments);
return realWin ? new Proxy(realWin, {
get: (t, p) => p === 'location' ? new Proxy(t.location, { set: (l, lp, lv) => (lp==='href'||lp==='assign') && isDomainBlocked(lv) ? (logBlockedAttempt(lv, 'Blocked Popup Nav'), showToast('⛔ Blocked popup nav'), t.close(), true) : (l[lp]=lv, true) }) : (typeof t[p]==='function'?t[p].bind(t):t[p]),
set: (t, p, v) => p === 'location' && isDomainBlocked(v) ? (logBlockedAttempt(v, 'Blocked Popup Location'), showToast('⛔ Blocked popup nav'), t.close(), true) : (t[p]=v, true)
}) : createDummyWindow();
}
if (isFrozen && !userApprovedNavigation) { handleFreezeNavigation(url, 'win.open()', () => origOpen.call(win, url, target, features), () => {}); return createDummyWindow(); }
return origOpen.apply(win, arguments);
};
} catch {}
function broadcastToFrames(w, msg) { try { for (let i = 0; i < w.frames.length; i++) { w.frames[i].postMessage(msg, '*'); broadcastToFrames(w.frames[i], msg); } } catch {} }
function broadcastState() { if (isTop) broadcastToFrames(window, { type: 'HIDER_SYNC_STATE', isSelecting, currentScope, isFrozen }); }
window.addEventListener('message', e => {
if (!e.data) return;
if (e.data.type === 'HIDER_REQUEST_STATE' && isTop) broadcastState();
if (e.data.type === 'HIDER_SYNC_STATE') {
({ isSelecting, currentScope, isFrozen } = e.data);
CACHE.isFrozen = isFrozen;
by('btn-select')?.classList.toggle('active', isSelecting);
by('btn-scope')?.classList.toggle('active', currentScope === 'link');
by('btn-freeze')?.classList.toggle('is-frozen', isFrozen);
if (!isSelecting) clearSelectionState();
}
if (e.data.type === 'HIDER_RESUME_VIDEOS') triggerVideoResumeChain();
});
if (!isTop) try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch {}
function updateStyles() {
let el = by('hider-dynamic-styles') || Object.assign(doc.createElement('style'), { id: 'hider-dynamic-styles' });
if (!el.parentNode) (doc.head || doc.documentElement).appendChild(el);
const host = location.hostname;
const siteRules = gv('hider_site_' + host, []);
const linkRules = gv('hider_link_' + cleanUrl(), []);
const customActiveRules = (CACHE.customRules || []).filter(r => {
if (!r || !r.selector) return false;
const t = cleanDomain(r.target);
return t === '*' || t === host || (t && host.endsWith('.' + t));
}).map(r => r.selector);
const combined = [...new Set([...siteRules, ...linkRules, ...customActiveRules])].filter(Boolean);
const css = combined.map(s => `${s}{opacity:0!important;pointer-events:none!important;position:absolute!important;top:-99999px!important;left:-99999px!important;width:0!important;height:0!important;max-width:0!important;max-height:0!important;overflow:hidden!important;visibility:hidden!important;clip:rect(0,0,0,0)!important}`).join('\n');
if (cachedCssString !== css) {
cachedCssString = css;
el.textContent = css;
combined.forEach(s => {
try {
doc.querySelectorAll(s).forEach(targetEl => targetEl.classList.add('hider-stealth-target'));
} catch {}
});
}
}
updateStyles();
function getExactSelector(el) {
if (!el || el.nodeType !== 1) return '';
const path = []; let curr = el;
while (curr && curr.nodeType === 1 && curr.tagName.toLowerCase() !== 'html') {
let tag = curr.tagName.toLowerCase();
if (tag === 'body') { path.unshift('body'); break; }
if (curr.id && !curr.id.startsWith('hider-') && !/^\d+$/.test(curr.id) && curr.id.length < 15) { path.unshift(`#${CSS.escape(curr.id)}`); break; }
let rawC = typeof curr.className === 'string' ? curr.className : curr.getAttribute('class') || '';
let classes = rawC.trim().split(/\s+/).filter(c => c.length > 1 && !c.startsWith('hider-') && !/^[a-zA-Z0-9]{10,}$/.test(c) && !/^hover:/i.test(c));
if (classes.length) { tag += `.${CSS.escape(classes[0])}` + (classes[1] ? `.${CSS.escape(classes[1])}` : ''); }
else {
let idx = 1, sib = curr.previousElementSibling;
while (sib) { if (sib.tagName === curr.tagName) idx++; sib = sib.previousElementSibling; }
tag += `:nth-of-type(${idx})`;
}
path.unshift(tag); curr = curr.parentElement;
}
return path.join(' > ');
}
function attachDragListeners(barEl, handleEl) {
let startX, startY, initialX, initialY;
const onStart = e => {
if (e.target.tagName === 'BUTTON') return;
isDraggingStepper = true;
const p = e.touches ? e.touches[0] : e, r = barEl.getBoundingClientRect();
startX = p.clientX; startY = p.clientY; initialX = r.left; initialY = r.top;
barEl.style.transition = 'none';
};
const onMove = e => {
if (!isDraggingStepper) return; e.cancelable && e.preventDefault();
const p = e.touches ? e.touches[0] : e;
stepperPos = { x: initialX + (p.clientX - startX), y: initialY + (p.clientY - startY) };
Object.assign(barEl.style, { left: `${stepperPos.x}px`, top: `${stepperPos.y}px`, bottom: 'auto', transform: 'none' });
};
const onEnd = () => { if (isDraggingStepper) { isDraggingStepper = false; barEl.style.transition = 'all 0.2s cubic-bezier(0.16, 1, 0.3, 1)'; } };
handleEl.addEventListener('mousedown', onStart); window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onEnd);
handleEl.addEventListener('touchstart', onStart, { passive: false }); window.addEventListener('touchmove', onMove, { passive: false }); window.addEventListener('touchend', onEnd);
}
function clearSelectionState() {
previewElement?.classList.remove('hider-preview-highlight'); previewElement = null; stepperStack = [];
by(STEPPER_BAR_ID)?.remove();
}
function turnOffHideMode() {
if (isSelecting) {
isSelecting = false;
by('btn-select')?.classList.remove('active');
clearSelectionState();
broadcastState();
}
}
function closeAllMenus(e, force = false) {
if (!force) {
if (isSelecting) return;
if (isDraggingDock || isDraggingStepper) return;
const active = doc.activeElement;
if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')) {
if (active.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID)) return;
}
if (e && e.target && e.target.closest) {
if (e.target.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID)) return;
}
if (by('hider-panel')?.querySelector('.is-editing')) return;
}
const panel = by('hider-panel');
const menu = by('hider-dock-menu');
const mainBtn = by('btn-toggle-dock');
const dockEl = by('hider-main-dock');
if (panel && panel.style.display === 'flex') {
panel.style.display = 'none';
by('btn-manage')?.classList.remove('active');
}
if (menu && menu.style.display === 'flex') {
menu.style.display = 'none';
mainBtn?.classList.remove('expanded');
dockEl?.classList.remove('expanded');
dockEl?.classList.add('manual-hidden');
if (collapseTimer) clearTimeout(collapseTimer);
collapseTimer = setTimeout(() => {
dockEl?.classList.add('is-collapsed');
collapseTimer = null;
}, 1000);
}
}
function renderTouchStepperUI() {
let stepper = by(STEPPER_BAR_ID);
if (!previewElement) { stepper?.remove(); return; }
if (!stepper) { stepper = doc.createElement('div'); stepper.id = STEPPER_BAR_ID; (doc.body || doc.documentElement).appendChild(stepper); }
stepper.className = 'h-glass h-stepper-pill';
if (stepperPos.x !== null) Object.assign(stepper.style, { left: `${stepperPos.x}px`, top: `${stepperPos.y}px`, bottom: 'auto', transform: 'none' });
const tag = previewElement.tagName.toLowerCase();
const c = typeof previewElement.className === 'string' ? previewElement.className.trim().split(/\s+/)[0] : '';
const classStr = c && !c.startsWith('hider-') ? `.${c}` : '';
const idStr = previewElement.id && !previewElement.id.startsWith('hider-') ? `#${previewElement.id}` : '';
stepper.innerHTML = `<div id="hider-drag-handle" class="h-drag-dots" title="Drag">⋮⋮</div><button id="hider-step-up" class="h-btn-icon" title="Parent Level">▲</button><button id="hider-step-down" class="h-btn-icon" title="Child Level">▼</button><div class="h-tag-badge" title="${tag}${idStr}${classStr}">${tag}${idStr}${classStr}</div><button id="hider-step-confirm" class="h-btn-pill h-btn-save">Hide</button><button id="hider-step-cancel" class="h-btn-icon h-btn-close">✖</button>`;
attachDragListeners(stepper, stepper.querySelector('#hider-drag-handle'));
stepper.querySelector('#hider-step-up').onclick = e => {
e.stopPropagation(); const p = previewElement.parentElement;
if (p && p !== doc.body && p !== doc.documentElement && p.id !== UI_HOST_ID) {
previewElement.classList.remove('hider-preview-highlight'); stepperStack.push(previewElement);
previewElement = p; previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
} else showToast('Top parent reached');
};
stepper.querySelector('#hider-step-down').onclick = e => {
e.stopPropagation();
if (stepperStack.length || previewElement.firstElementChild) {
previewElement.classList.remove('hider-preview-highlight');
previewElement = stepperStack.length ? stepperStack.pop() : previewElement.firstElementChild;
previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
}
};
stepper.querySelector('#hider-step-confirm').onclick = e => { e.stopPropagation(); confirmHideSelectedElement(); };
stepper.querySelector('#hider-step-cancel').onclick = e => { e.stopPropagation(); clearSelectionState(); };
}
function confirmHideSelectedElement() {
if (!previewElement) return;
const el = previewElement;
el.classList.remove('hider-preview-highlight'); el.classList.add('hider-stealth-target');
const sel = getExactSelector(el);
if (sel) {
const key = currentScope === 'site' ? 'hider_site_' + location.hostname : 'hider_link_' + cleanUrl();
const s = gv(key, []); if (!s.includes(sel)) { s.push(sel); sv(key, s); }
}
updateStyles(); showToast('🙈 Element Hidden!'); clearSelectionState();
}
function showToast(msg) {
by('hider-toast')?.remove();
const toast = Object.assign(doc.createElement('div'), { id: 'hider-toast', className: 'h-glass h-toast', textContent: msg });
(doc.body || doc.documentElement).appendChild(toast);
setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(-50%) translateY(8px)'; setTimeout(() => toast.remove(), 250); }, 2200);
}
function setupCustomDropdown(container, initialValue, onChangeCallback) {
if (!container) return;
const trigger = container.querySelector('.h-custom-trigger');
const textSpan = container.querySelector('.h-custom-value-text');
const options = container.querySelectorAll('.h-custom-opt');
let currentVal = initialValue || 'ask';
textSpan.textContent = FREEZE_LABELS[currentVal] || FREEZE_LABELS['ask'];
options.forEach(opt => {
opt.classList.toggle('is-selected', opt.getAttribute('data-val') === currentVal);
opt.onclick = e => {
e.stopPropagation();
currentVal = opt.getAttribute('data-val');
textSpan.textContent = FREEZE_LABELS[currentVal] || opt.textContent.trim();
options.forEach(o => o.classList.toggle('is-selected', o === opt));
container.classList.remove('is-open');
onChangeCallback?.(currentVal);
};
});
trigger.onclick = e => {
e.stopPropagation();
const isOpen = container.classList.contains('is-open');
doc.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
if (!isOpen) container.classList.add('is-open');
};
}
// Helper to perform domain blocking directly
function executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl) {
if (parentDomain && !CACHE.blockedDomainsSet.has(parentDomain)) {
CACHE.blockedDomainsList.push(parentDomain);
CACHE.blockedDomainsSet.add(parentDomain);
sv('hider_blocked_domains', CACHE.blockedDomainsList);
}
logBlockedAttempt(parentDomain, triggerType + ' (User Domain Block)');
promptEl?.remove();
showToast(`🚫 Blocked ${parentDomain}! You can remove it anytime in Main Menu.`);
onDeny?.();
simulateAdWindowSuccess();
}
function showFreezePrompt(url, triggerType, onConfirm, onDeny) {
by('hider-freeze-prompt')?.remove();
const promptEl = Object.assign(doc.createElement('div'), { id: 'hider-freeze-prompt', className: 'h-glass h-prompt' });
const displayUrl = url ? (url.length > 38 ? url.substring(0, 35) + '...' : url) : 'another page';
const parentDomain = getParentDomain(url) || 'unknown domain';
promptEl.innerHTML = `
<div style="font-weight:700;color:#38bdf8;font-size:11px;text-transform:uppercase">❄️ Soft Freeze Triggered</div>
<div style="font-size:12px;color:#f8fafc">Proceed to page?</div>
<div class="h-url-box">${displayUrl}</div>
<div class="h-custom-select" id="hider-modal-custom-dropdown">
<div class="h-custom-trigger">
<span class="h-custom-value-text">${FREEZE_LABELS[CACHE.freezeMemory] || FREEZE_LABELS['ask']}</span>
<span class="h-custom-arrow">▼</span>
</div>
<div class="h-custom-options">
<div class="h-custom-opt" data-val="ask">❓ Ask Every Time</div>
<div class="h-custom-opt" data-val="block_all">⛔ Auto-Block All Navigations</div>
<div class="h-custom-opt" data-val="allow_same">🔗 Allow Same Domain Only</div>
<div class="h-custom-opt" data-val="allow_all">🟢 Allow All Navigations</div>
</div>
</div>
<div style="display:flex;gap:6px;width:100%;margin-top:4px">
<button id="hider-freeze-yes" class="h-btn-pill h-btn-save" style="flex:1">Yes</button>
<button id="hider-freeze-no" class="h-btn-pill h-btn-close" style="flex:1">No</button>
</div>
<button id="hider-freeze-block-btn" class="h-btn-pill" style="width:100%;margin-top:3px;background:rgba(239,68,68,0.22);border:1px solid rgba(239,68,68,0.45);color:#fca5a5">🚫 Block Domain (${parentDomain})</button>
`;
(doc.body || doc.documentElement).appendChild(promptEl);
let selectedMem = CACHE.freezeMemory || 'ask';
setupCustomDropdown(promptEl.querySelector('#hider-modal-custom-dropdown'), selectedMem, v => {
selectedMem = v;
});
const saveMem = () => {
if (selectedMem !== CACHE.freezeMemory) {
CACHE.freezeMemory = selectedMem;
sv('hider_freeze_memory', selectedMem);
}
};
promptEl.querySelector('#hider-freeze-yes').onclick = () => { saveMem(); promptEl.remove(); onConfirm?.(); };
promptEl.querySelector('#hider-freeze-no').onclick = () => { saveMem(); logBlockedAttempt(url, triggerType + ' (User)'); promptEl.remove(); onDeny?.(); simulateAdWindowSuccess(); };
// Handle Block Domain Option with optional Don't Ask Again confirmation
promptEl.querySelector('#hider-freeze-block-btn').onclick = () => {
saveMem();
const skipConfirm = gv('hider_skip_block_confirm', false);
if (skipConfirm) {
executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl);
return;
}
// Show Confirmation View inside Modal
promptEl.innerHTML = `
<div style="font-weight:700;color:#ef4444;font-size:11px;text-transform:uppercase">🚫 Confirm Block Domain</div>
<div style="font-size:11px;color:#f8fafc;text-align:center;margin:2px 0">Block all future requests to:<br><strong style="color:#38bdf8;font-size:12px">${parentDomain}</strong>?</div>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;margin:4px 0;user-select:none">
<input type="checkbox" id="hider-dont-ask-block" style="cursor:pointer;accent-color:#38bdf8"> Don't ask confirmation again
</label>
<div style="display:flex;gap:6px;width:100%;margin-top:4px">
<button id="hider-confirm-block-yes" class="h-btn-pill h-btn-close" style="flex:1">Yes, Block</button>
<button id="hider-confirm-block-no" class="h-btn-pill" style="flex:1;background:rgba(255,255,255,0.1);color:#f1f5f9;border:1px solid rgba(255,255,255,0.15)">Cancel</button>
</div>
`;
promptEl.querySelector('#hider-confirm-block-yes').onclick = () => {
const dontAskAgain = promptEl.querySelector('#hider-dont-ask-block')?.checked;
if (dontAskAgain) sv('hider_skip_block_confirm', true);
executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl);
};
promptEl.querySelector('#hider-confirm-block-no').onclick = () => {
// Return to original prompt dialog
showFreezePrompt(url, triggerType, onConfirm, onDeny);
};
};
}
function injectStylesAndUI() {
if (by('hider-core-styles')) return;
const s = doc.createElement('style'); s.id = 'hider-core-styles';
s.textContent = `
.h-glass{background:rgba(15,23,42,.93);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.12);color:#f8fafc;font-family:system-ui,-apple-system,sans-serif;box-sizing:border-box;border-radius:12px}
.h-dock{position:fixed;right:0;top:35%;z-index:2147483646;display:flex;flex-direction:column;align-items:flex-end;gap:5px;padding:4px;box-shadow:0 10px 25px rgba(0,0,0,.5);pointer-events:auto;transition:transform .25s ease,opacity .25s ease;will-change:transform,opacity}
.h-dock.is-collapsed{transform:translateX(50%);opacity:.4}
.h-dock.manual-hidden{transform:translateX(50%!important);opacity:.4!important}
.h-dock.is-collapsed:not(.manual-hidden):hover,.h-dock.expanded{transform:translateX(0);opacity:1}
.h-dock-main{width:36px;height:36px;font-size:16px!important;background:rgba(59,130,246,.3)!important;border-color:rgba(96,165,250,.5)!important;cursor:grab!important}
.h-dock-main:active{cursor:grabbing!important}
.h-dock-main.expanded{background:#3b82f6!important;color:#fff!important}
.h-dock-menu{display:none;flex-direction:column;gap:5px;margin-top:2px}
.h-dock-btn{width:36px;height:36px;border-radius:9px;border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.05);color:#cbd5e1;font-size:10px;font-weight:700;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;transition:all .15s ease}
.h-dock-btn:hover{background:rgba(255,255,255,.15);color:#fff}
.h-dock-btn.active{background:#3b82f6!important;color:#fff!important;border-color:#60a5fa!important}
.h-dock-btn.scope-link{background:#10b981!important;color:#fff!important}
.h-dock-btn.is-frozen{background:#8b5cf6!important;color:#fff!important;border-color:#a78bfa!important}
.h-stepper-pill{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:2147483647;padding:5px 8px;display:flex;align-items:center;gap:6px;box-shadow:0 10px 30px rgba(0,0,0,.6);pointer-events:auto}
.h-drag-dots{cursor:grab;color:#64748b;font-size:12px;padding:0 3px;user-select:none}
.h-btn-icon{width:28px;height:28px;border-radius:7px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.06);color:#e2e8f0;font-size:11px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center}
.h-btn-icon:hover{background:rgba(255,255,255,.18)}
.h-tag-badge{max-width:130px;height:26px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:0 6px;color:#38bdf8;font-size:10px;font-family:monospace;line-height:24px}
.h-btn-pill{height:28px;padding:0 10px;border-radius:7px;font-size:11px;font-weight:700;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s ease}
.h-btn-save{background:#10b981;color:#fff}
.h-btn-close{background:rgba(239,68,68,.2);border:1px solid rgba(239,68,68,.4);color:#f87171}
.h-toast{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);padding:7px 16px;font-size:11px;font-weight:600;z-index:2147483647;pointer-events:none;transition:all .25s ease;text-align:center;box-shadow:0 10px 25px rgba(0,0,0,.6);border:1px solid rgba(56,189,248,.3);color:#e0f2fe}
.h-prompt{position:fixed;top:30px;left:50%;transform:translateX(-50%);padding:14px 16px;z-index:2147483647;display:flex;flex-direction:column;align-items:center;gap:8px;box-shadow:0 15px 35px rgba(0,0,0,.6);pointer-events:auto;width:310px;max-width:90vw}
.h-url-box{font-size:10px;color:#9ca3af;word-break:break-all;max-height:32px;overflow:hidden;background:rgba(0,0,0,.3);padding:4px 6px;border-radius:6px;width:100%;border:1px solid rgba(255,255,255,.05)}
.h-select{width:100%;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#fff;padding:5px 8px;font-size:10px;outline:0;box-sizing:border-box}
/* Pure Custom Inline Dropdown */
.h-custom-select { position: relative; width: 100%; user-select: none; margin-top: 2px; }
.h-custom-trigger {
background: rgba(30,41,59,.85); border: 1px solid rgba(96,165,250,.3); border-radius: 8px;
color: #f1f5f9; padding: 7px 10px; font-size: 10px; font-weight: 600; cursor: pointer;
display: flex; justify-content: space-between; align-items: center; transition: all .2s ease;
}
.h-custom-trigger:hover { border-color: #38bdf8; background: rgba(30,41,59,.95); }
.h-custom-arrow { font-size: 8px; color: #38bdf8; transition: transform .2s ease; }
.h-custom-select.is-open .h-custom-arrow { transform: rotate(180deg); }
.h-custom-options {
display: none; flex-direction: column; background: #0f172a; border: 1px solid rgba(255,255,255,.15);
border-radius: 8px; margin-top: 4px; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,.6);
}
.h-custom-select.is-open .h-custom-options { display: flex; }
.h-custom-opt {
padding: 8px 10px; color: #cbd5e1; font-size: 10px; cursor: pointer; transition: all .15s ease;
border-bottom: 1px solid rgba(255,255,255,.04); display: flex; align-items: center; gap: 6px;
}
.h-custom-opt:last-child { border-bottom: none; }
.h-custom-opt:hover, .h-custom-opt.is-selected { background: rgba(56,189,248,.18); color: #38bdf8; font-weight: 700; }
#hider-panel{position:fixed;right:50px;top:75px;z-index:2147483647;width:320px;max-height:80vh;border-radius:14px;padding:12px;display:none;flex-direction:column;overflow-y:auto;pointer-events:auto;box-shadow:0 15px 35px rgba(0,0,0,.6);gap:6px}
.panel-header{font-weight:700;font-size:12px;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:6px;display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}
.h-acc-header{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.4px;display:flex;justify-content:space-between;align-items:center;padding:7px 9px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;cursor:pointer;user-select:none;transition:all .2s ease}
.h-acc-header:hover{background:rgba(255,255,255,.09);color:#f8fafc;border-color:rgba(255,255,255,.15)}
.h-acc-header.is-open{background:rgba(59,130,246,.15);border-color:rgba(96,165,250,.4);color:#38bdf8}
.h-acc-arrow{font-size:9px;transition:transform .25s cubic-bezier(0.16, 1, 0.3, 1);color:#38bdf8}
.h-acc-header.is-open .h-acc-arrow{transform:rotate(180deg)}
.h-acc-content{max-height:0;overflow:hidden;opacity:0;transition:max-height .28s cubic-bezier(0.16, 1, 0.3, 1), opacity .2s ease, padding .2s ease;padding:0 4px;display:flex;flex-direction:column;gap:4px}
.h-acc-content.is-open{max-height:350px;overflow-y:auto;opacity:1;padding:6px 2px}
.list-item{font-size:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);border-radius:6px;padding:5px 7px;display:flex;justify-content:space-between;align-items:center;gap:6px}
.list-item span.rule-text{word-break:break-all;padding-right:4px;color:#cbd5e1;font-family:monospace;font-size:9px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hider-btn-small{cursor:pointer;border:none;border-radius:4px;padding:2px 6px;color:#fff;font-weight:600;background:#ef4444;font-size:9px}
@keyframes hider-pulse{0%,100%{outline:2px stroke #ef4444}50%{outline:2px dashed #ef4444;box-shadow:inset 0 0 10px rgba(239,68,68,.3)}}
.hider-preview-highlight{animation:hider-pulse .8s infinite!important;cursor:pointer!important}
@keyframes hider-snow-freeze {
0% { opacity: 0; backdrop-filter: blur(0px); background: rgba(224, 242, 254, 0); }
35% { opacity: 1; backdrop-filter: blur(8px) saturate(130%); background: radial-gradient(circle at center, rgba(186, 230, 253, 0.22) 0%, rgba(56, 189, 248, 0.12) 60%, rgba(15, 23, 42, 0.35) 100%); box-shadow: inset 0 0 120px rgba(186, 230, 253, 0.35); }
100% { opacity: 0; backdrop-filter: blur(0px); background: rgba(224, 242, 254, 0); }
}
@keyframes hider-flake-drift {
0% { transform: translateY(0) scale(0.6); opacity: 0; }
40% { opacity: 0.95; }
100% { transform: translateY(40px) scale(1.1); opacity: 0; }
}
.hider-freeze-snow-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 2147483647; pointer-events: none; animation: hider-snow-freeze 1.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.hider-snow-flake { position: absolute; width: 6px; height: 6px; background: #e0f2fe; border-radius: 50%; box-shadow: 0 0 8px #38bdf8, 0 0 12px #ffffff; animation: hider-flake-drift 1.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
`;
(doc.head || doc.documentElement).appendChild(s);
if (!isTop || by(UI_HOST_ID)) return;
const host = doc.createElement('div'); host.id = UI_HOST_ID;
host.style.cssText = 'position:fixed;top:0;right:0;bottom:0;width:0;z-index:2147483647;pointer-events:none;';
(doc.body || doc.documentElement).appendChild(host);
host.innerHTML = `
<div class="h-glass h-dock is-collapsed manual-hidden" id="hider-main-dock">
<button class="h-dock-btn h-dock-main" id="btn-toggle-dock" title="Toggle Hider Menu">🛡️</button>
<div class="h-dock-menu" id="hider-dock-menu">
<button class="h-dock-btn" id="btn-select" title="Target Element to Hide">🎯<span>Hide</span></button>
<button class="h-dock-btn" id="btn-scope" title="Toggle Scope (Site/Page)">🌐<span>SITE</span></button>
<button class="h-dock-btn ${isFrozen?'is-frozen':''}" id="btn-freeze" title="Toggle Navigation Freeze">❄️<span>Freeze</span></button>
<button class="h-dock-btn" id="btn-manage" title="Open Control Panel">⚙️<span>Menu</span></button>
</div>
</div>
<div id="hider-panel" class="h-glass">
<div class="panel-header"><span>🛡️ Hide Web Elements Pro <span style="font-size:9px;color:#60a5fa">v4.8</span></span><button class="hider-btn-small" id="close-p" style="background:rgba(255,255,255,0.15);cursor:pointer;padding:3px 8px;">✖</button></div>
<div class="h-acc-header" data-target="acc-builder"><span>✏️ Rule Builder</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-builder">
<input type="text" id="manual-rule-input" placeholder="Rule CSS Selector (e.g. .ad-banner)" class="h-select">
<div style="display:flex;gap:4px">
<input type="text" id="manual-target-input" placeholder="Target Domain (* for Global)" class="h-select" style="flex:1">
<button class="hider-btn-small" id="save-rule-btn" style="background:#3b82f6;padding:4px 10px">Save</button>
</div>
</div>
<div class="h-acc-header" data-target="acc-custom"><span>⚙️ Custom Rules (<span id="cnt-custom">0</span>)</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-custom">
<div id="list-custom-rules" style="display:flex;flex-direction:column;gap:4px"></div>
</div>
<div class="h-acc-header" data-target="acc-domains"><span>🚫 Blocked Domains (<span id="cnt-domains">0</span>)</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-domains">
<div style="display:flex;gap:4px;margin-bottom:2px">
<input type="text" id="manual-domain-input" placeholder="e.g. badsite.com" class="h-select" style="flex:1">
<button class="hider-btn-small" id="add-domain-btn" style="background:#3b82f6">Add</button>
</div>
<div id="list-blocked-domains" style="display:flex;flex-direction:column;gap:4px"></div>
</div>
<div class="h-acc-header" data-target="acc-freeze"><span>❄️ Freeze Behavior</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-freeze">
<div class="h-custom-select" id="freeze-custom-panel-dropdown">
<div class="h-custom-trigger">
<span class="h-custom-value-text">${FREEZE_LABELS[CACHE.freezeMemory] || FREEZE_LABELS['ask']}</span>
<span class="h-custom-arrow">▼</span>
</div>
<div class="h-custom-options">
<div class="h-custom-opt" data-val="ask">❓ Ask Every Time</div>
<div class="h-custom-opt" data-val="block_all">⛔ Auto-Block All Navigations</div>
<div class="h-custom-opt" data-val="allow_same">🔗 Allow Same Domain Only</div>
<div class="h-custom-opt" data-val="allow_all">🟢 Allow All Navigations</div>
</div>
</div>
<button id="btn-reset-block-confirm" class="hider-btn-small" style="background:rgba(255,255,255,0.08);color:#94a3b8;margin-top:6px;width:100%;padding:5px;border:1px solid rgba(255,255,255,0.1)">Reset "Don't Ask Block Domain" Prompt</button>
</div>
<div class="h-acc-header" data-target="acc-logs">
<div style="display:flex;justify-content:space-between;align-items:center;width:100%;padding-right:6px">
<span>📋 Blocked Logs (<span id="cnt-logs">0</span>)</span>
<button class="hider-btn-small" id="clear-log-btn" style="background:#475569;font-size:8px;padding:1px 5px">Clear</button>
</div>
<span class="h-acc-arrow">▼</span>
</div>
<div class="h-acc-content" id="acc-logs">
<div id="list-blocked-log" style="display:flex;flex-direction:column;gap:3px"></div>
</div>
<div class="h-acc-header" data-target="acc-site"><span>🌐 Hidden Site-Wide (<span id="cnt-site">0</span>)</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-site">
<div id="list-site" style="display:flex;flex-direction:column;gap:4px"></div>
</div>
<div class="h-acc-header" data-target="acc-link"><span>📄 Hidden Page Only (<span id="cnt-link">0</span>)</span><span class="h-acc-arrow">▼</span></div>
<div class="h-acc-content" id="acc-link">
<div id="list-link" style="display:flex;flex-direction:column;gap:4px"></div>
</div>
</div>`;
doc.querySelectorAll('.h-acc-header').forEach(header => {
header.onclick = e => {
if (e.target.closest('button')) return;
const targetId = header.getAttribute('data-target');
const content = by(targetId);
if (!content) return;
const isOpen = content.classList.contains('is-open');
header.classList.toggle('is-open', !isOpen);
content.classList.toggle('is-open', !isOpen);
};
});
setupCustomDropdown(by('freeze-custom-panel-dropdown'), CACHE.freezeMemory, val => {
CACHE.freezeMemory = val;
sv('hider_freeze_memory', val);
showToast('Memory updated');
});
by('btn-reset-block-confirm').onclick = () => {
sv('hider_skip_block_confirm', false);
showToast('Reset Block Domain confirmation!');
};
const dockEl = by('hider-main-dock'), mainBtn = by('btn-toggle-dock');
const savedY = gv('hider_dock_y', null);
if (savedY) dockEl.style.top = savedY;
dockEl.addEventListener('mouseleave', () => dockEl.classList.remove('manual-hidden'));
by('btn-select')?.addEventListener('mousedown', e => e.preventDefault());
mainBtn?.addEventListener('mousedown', e => e.preventDefault());
let dockStartY, dockInitY, dockMoved = false;
const onDockStart = e => {
const p = e.touches ? e.touches[0] : e;
dockStartY = p.clientY; dockInitY = dockEl.getBoundingClientRect().top;
isDraggingDock = true; dockMoved = false;
dockEl.style.transition = 'none';
};
const onDockMove = e => {
if (!isDraggingDock) return;
const p = e.touches ? e.touches[0] : e;
const dy = p.clientY - dockStartY;
if (Math.abs(dy) > 4) dockMoved = true;
if (dockMoved) {
e.cancelable && e.preventDefault();
dockEl.style.top = `${dockInitY + dy}px`;
}
};
const onDockEnd = () => {
if (isDraggingDock) {
isDraggingDock = false;
dockEl.style.transition = '';
if (dockMoved) sv('hider_dock_y', dockEl.style.top);
}
};
mainBtn.addEventListener('mousedown', onDockStart);
window.addEventListener('mousemove', onDockMove, { passive: true });
window.addEventListener('mouseup', onDockEnd);
mainBtn.addEventListener('touchstart', onDockStart, { passive: false });
window.addEventListener('touchmove', onDockMove, { passive: false });
window.addEventListener('touchend', onDockEnd);
mainBtn.onclick = e => {
e.stopPropagation();
if (dockMoved) return;
if (collapseTimer) { clearTimeout(collapseTimer); collapseTimer = null; }
const menu = by('hider-dock-menu');
const isOpening = menu.style.display !== 'flex';
menu.style.display = isOpening ? 'flex' : 'none';
mainBtn.classList.toggle('expanded', isOpening);
dockEl.classList.toggle('expanded', isOpening);
dockEl.classList.toggle('is-collapsed', !isOpening);
if (!isOpening) {
turnOffHideMode();
by('hider-panel').style.display = 'none';
by('btn-manage')?.classList.remove('active');
dockEl.classList.add('manual-hidden');
collapseTimer = setTimeout(() => {
dockEl.classList.add('is-collapsed');
collapseTimer = null;
}, 1000);
} else {
dockEl.classList.remove('manual-hidden');
}
};
by('save-rule-btn').onclick = () => {
const ruleInput = by('manual-rule-input');
const targetInput = by('manual-target-input');
const sel = (ruleInput.value || '').trim();
let target = cleanDomain(targetInput.value) || '*';
if (!sel) { showToast('⚠️ Enter a selector'); return; }
if (editingRuleId) {
const idx = CACHE.customRules.findIndex(r => r.id === editingRuleId);
if (idx !== -1) {
CACHE.customRules[idx] = { id: editingRuleId, selector: sel, target };
showToast('✏️ Rule updated');
}
editingRuleId = null;
by('save-rule-btn').textContent = 'Save';
} else {
const newRule = { id: 'rule_' + Date.now(), selector: sel, target };
CACHE.customRules.push(newRule);
showToast('✨ Custom Rule Added!');
}
sv('hider_custom_rules_v4', CACHE.customRules);
ruleInput.value = ''; targetInput.value = '';
updateStyles(); renderList();
};
const addDomain = () => {
const input = by('manual-domain-input'), d = cleanDomain(input.value);
if (d && !CACHE.blockedDomainsSet.has(d)) {
CACHE.blockedDomainsList.push(d); CACHE.blockedDomainsSet.add(d);
sv('hider_blocked_domains', CACHE.blockedDomainsList); showToast(`🚫 Blocked: ${d}`);
}
input.value = ''; renderList();
};
by('add-domain-btn').onclick = addDomain;
by('manual-domain-input').onkeypress = e => e.key === 'Enter' && addDomain();
by('btn-select').onclick = e => {
e.stopPropagation();
isSelecting = !isSelecting;
e.currentTarget.classList.toggle('active', isSelecting);
if (isSelecting) { by('hider-panel').style.display = 'none'; by('btn-manage').classList.remove('active'); }
else clearSelectionState();
broadcastState();
};
const btnScope = by('btn-scope');
btnScope.onclick = e => {
e.stopPropagation();
currentScope = currentScope === 'site' ? 'link' : 'site';
btnScope.querySelector('span').textContent = currentScope.toUpperCase();
btnScope.classList.toggle('scope-link', currentScope === 'link');
showToast(`Scope: ${currentScope.toUpperCase()}`); broadcastState();
};
// Manual Freeze Button Toggle
const btnFreeze = by('btn-freeze');
btnFreeze.onclick = e => {
e.stopPropagation();
isFrozen = !isFrozen;
CACHE.isFrozen = isFrozen;
sv('hider_freeze_global', isFrozen);
btnFreeze.classList.toggle('is-frozen', isFrozen);
if (isFrozen) triggerFreezeFx();
showToast(isFrozen ? '❄️ Freeze ON' : '🔥 Freeze OFF');
broadcastState();
};
by('btn-manage').onclick = e => {
e.stopPropagation();
const p = by('hider-panel'), show = p.style.display !== 'flex';
p.style.display = show ? 'flex' : 'none';
e.currentTarget.classList.toggle('active', show);
if (show) { turnOffHideMode(); renderList(); }
};
by('close-p').onclick = e => {
if (e) { e.stopPropagation(); e.preventDefault(); }
turnOffHideMode();
closeAllMenus(e, true);
};
by('clear-log-btn').onclick = (e) => {
if (e) e.stopPropagation();
CACHE.logs = []; sv('hider_global_logs', []); renderList();
};
}
function renderList() {
if (!CACHE.logs) CACHE.logs = gv('hider_global_logs', []);
const siteKey = 'hider_site_' + location.hostname, linkKey = 'hider_link_' + cleanUrl();
const siteData = gv(siteKey, []), linkData = gv(linkKey, []);
const cCustom = by('list-custom-rules'), cDomains = by('list-blocked-domains'), cSite = by('list-site'), cLink = by('list-link'), cLog = by('list-blocked-log');
if (!cSite || !cLink) return;
if (by('cnt-custom')) by('cnt-custom').textContent = CACHE.customRules.length;
if (by('cnt-domains')) by('cnt-domains').textContent = CACHE.blockedDomainsList.length;
if (by('cnt-logs')) by('cnt-logs').textContent = CACHE.logs.length;
if (by('cnt-site')) by('cnt-site').textContent = siteData.length;
if (by('cnt-link')) by('cnt-link').textContent = linkData.length;
if (cCustom) {
cCustom.innerHTML = CACHE.customRules.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No custom rules</div>`;
CACHE.customRules.forEach(rule => {
const div = doc.createElement('div'); div.className = 'list-item';
const targetTag = rule.target === '*' ? '<span style="color:#38bdf8">[Global]</span>' : `<span style="color:#10b981">[${rule.target}]</span>`;
div.innerHTML = `<span class="rule-text" title="${rule.selector}">${rule.selector} ${targetTag}</span><div style="display:flex;gap:2px"><button class="hider-btn-small btn-edit" style="background:#3b82f6">✏️</button><button class="hider-btn-small btn-del">✖</button></div>`;
div.querySelector('.btn-edit').onclick = () => {
editingRuleId = rule.id;
by('manual-rule-input').value = rule.selector;
by('manual-target-input').value = rule.target;
by('save-rule-btn').textContent = 'Update';
const bHeader = doc.querySelector('[data-target="acc-builder"]');
const bContent = by('acc-builder');
if (bHeader && bContent && !bContent.classList.contains('is-open')) {
bHeader.classList.add('is-open'); bContent.classList.add('is-open');
}
};
div.querySelector('.btn-del').onclick = () => {
CACHE.customRules = CACHE.customRules.filter(r => r.id !== rule.id);
sv('hider_custom_rules_v4', CACHE.customRules); updateStyles(); renderList();
};
cCustom.appendChild(div);
});
}
if (cDomains) {
cDomains.innerHTML = CACHE.blockedDomainsList.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No blocked domains</div>`;
CACHE.blockedDomainsList.forEach((d, i) => {
const div = doc.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span class="rule-text" title="${d}">${d}</span><button class="hider-btn-small">✖</button>`;
div.querySelector('button').onclick = () => { CACHE.blockedDomainsList.splice(i, 1); CACHE.blockedDomainsSet.delete(cleanDomain(d)); sv('hider_blocked_domains', CACHE.blockedDomainsList); renderList(); };
cDomains.appendChild(div);
});
}
if (cLog) {
cLog.innerHTML = CACHE.logs.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">No logs today</div>`;
CACHE.logs.forEach(item => {
const row = doc.createElement('div'); row.style.cssText = 'font-size:9px;border-bottom:1px solid rgba(255,255,255,0.06);padding:3px 0;';
row.innerHTML = `<div style="display:flex;justify-content:space-between;color:#a7f3d0;font-weight:bold"><span>${item.type}</span><span style="color:#64748b">${item.time}</span></div><div style="color:#94a3b8;word-break:break-all;font-family:monospace">${item.url}</div>`;
cLog.appendChild(row);
});
}
const renderEditableHiddenItems = (data, key, container) => {
container.innerHTML = data.length ? '' : `<div style="font-size:9px;color:#64748b;padding:4px">None</div>`;
data.forEach((sel, i) => {
const div = doc.createElement('div'); div.className = 'list-item';
div.innerHTML = `
<span class="rule-text" title="${sel}">${sel}</span>
<div style="display:flex;gap:2px;align-items:center">
<button class="hider-btn-small btn-edit" style="background:#3b82f6" title="Edit Rule Inline">✏️</button>
<button class="hider-btn-small btn-wild" style="background:#8b5cf6" title="Convert Wildcard">🪄</button>
<button class="hider-btn-small btn-del" style="background:#ef4444" title="Delete Rule">✖</button>
</div>`;
const textSpan = div.querySelector('.rule-text');
const editBtn = div.querySelector('.btn-edit');
editBtn.onclick = () => {
if (div.classList.contains('is-editing')) return;
div.classList.add('is-editing');
const input = doc.createElement('input');
input.type = 'text';
input.value = data[i];
input.className = 'h-select';
input.style.cssText = 'font-size:9px;padding:2px 4px;height:22px;flex:1;margin-right:4px;';
const saveBtn = doc.createElement('button');
saveBtn.className = 'hider-btn-small';
saveBtn.style.cssText = 'background:#10b981;padding:2px 6px;';
saveBtn.textContent = '✓';
textSpan.replaceWith(input);
editBtn.replaceWith(saveBtn);
input.focus();
const saveHandler = () => {
const newVal = input.value.trim();
if (newVal) {
data[i] = newVal;
sv(key, data);
updateStyles();
showToast('✏️ Hidden rule updated');
}
renderList();
};
saveBtn.onclick = saveHandler;
input.onkeypress = e => e.key === 'Enter' && saveHandler();
};
div.querySelector('.btn-wild').onclick = () => {
data[i] = convertToWildcardSelector(data[i]);
sv(key, data); updateStyles(); renderList(); showToast(`🪄 Wildcard applied`);
};
div.querySelector('.btn-del').onclick = () => {
const removed = data.splice(i, 1)[0]; sv(key, data);
try { doc.querySelectorAll(removed).forEach(el => el.classList.remove('hider-stealth-target')); } catch {}
updateStyles(); renderList();
};
container.appendChild(div);
});
};
renderEditableHiddenItems(siteData, siteKey, cSite);
renderEditableHiddenItems(linkData, linkKey, cLink);
}
function handleNavigationClick(e) {
if (isSelecting || e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID)) return;
const link = e.target.closest('a[href], area[href]');
if (link?.href && !link.getAttribute('href').startsWith('#') && !link.getAttribute('href').startsWith('javascript:')) {
const targetUrl = link.href;
if (isDomainBlocked(targetUrl)) {
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
logBlockedAttempt(targetUrl, 'Blocked Link Click'); simulateAdWindowSuccess(); showToast('⛔ Force Blocked'); return;
}
if (!isFrozen) return;
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
const isNewTab = link.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1;
handleFreezeNavigation(targetUrl, 'Link Click', () => {
userApprovedNavigation = true;
if (isNewTab) win.open(targetUrl, '_blank'); else win.location.href = targetUrl;
setTimeout(() => userApprovedNavigation = false, 300);
}, simulateAdWindowSuccess);
}
}
['mousedown', 'pointerdown', 'mouseup'].forEach(evtType => {
window.addEventListener(evtType, e => {
if (!isSelecting) return;
if (e.target.closest('#' + UI_HOST_ID + ', #' + STEPPER_BAR_ID + ', #hider-freeze-prompt')) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}, true);
});
window.addEventListener('scroll', closeAllMenus, { passive: true });
window.addEventListener('click', e => {
const isUI = e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID);
if (!isUI) {
closeAllMenus(e);
doc.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
}
if (isUI) return;
handleNavigationClick(e);
if (!isSelecting) return;
e.preventDefault(); e.stopPropagation();
if (previewElement === e.target) confirmHideSelectedElement();
else {
previewElement?.classList.remove('hider-preview-highlight');
stepperStack = []; previewElement = e.target; previewElement.classList.add('hider-preview-highlight'); renderTouchStepperUI();
}
}, true);
window.addEventListener('auxclick', handleNavigationClick, true);
window.addEventListener('submit', e => {
if (e.target.closest('#' + UI_HOST_ID + ', #hider-freeze-prompt, #' + STEPPER_BAR_ID)) return;
const form = e.target, action = form.action || location.href;
if (isDomainBlocked(action)) {
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
logBlockedAttempt(action, 'Blocked Form Submit'); simulateAdWindowSuccess(); showToast('⛔ Blocked Form'); return;
}
if (!isFrozen) return;
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
handleFreezeNavigation(action, 'Form Submit', () => { userApprovedNavigation = true; form.submit(); setTimeout(() => userApprovedNavigation = false, 300); }, simulateAdWindowSuccess);
}, true);
win.addEventListener('beforeunload', e => {
if (isFrozen && !userApprovedNavigation && (CACHE.freezeMemory === 'ask' || CACHE.freezeMemory === 'block_all')) {
e.preventDefault(); return (e.returnValue = 'Page navigation is currently frozen site-wide.');
}
}, true);
const checkUrlChange = () => {
if (location.href !== lastUrl) {
lastUrl = location.href; updateStyles();
if (by('hider-panel')?.style.display === 'flex') renderList();
}
};
['pushState', 'replaceState'].forEach(fn => {
const orig = history[fn];
if (orig) history[fn] = function() { orig.apply(this, arguments); checkUrlChange(); };
});
window.addEventListener('popstate', checkUrlChange, { passive: true });
window.addEventListener('hashchange', checkUrlChange, { passive: true });
function init() {
['contextmenu', 'selectstart', 'copy', 'paste', 'dragstart'].forEach(evt => doc.addEventListener(evt, e => e.stopPropagation(), true));
injectStylesAndUI(); updateStyles();
let timer = null;
new MutationObserver(() => {
if (timer) return;
timer = setTimeout(() => {
timer = null;
if (isTop && !by(UI_HOST_ID)) injectStylesAndUI();
if (!by('hider-dynamic-styles')) updateStyles();
}, 1000);
}).observe(doc.documentElement || doc.body, { childList: true, subtree: true });
}
if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', init); else init();
})();