Stealth element hider, Shadow DOM UI, persistent freeze, link extractor, time skipper, enhanced reveal, dock button visibility, stable Reveal & Unblock Engine, plus media downloader with preview. Automatic Protection. Now with Anti-Paywall, Auto-Close Modals (smart skip for essential UI incl. side menus), Auto-Accept Age Verification, Auto-Close Logins, Cookie Bypass, Bulk Filter Lists, and improved UI.
// ==UserScript==
// @name Hide Web Elements Pro
// @version 14.1
// @description Stealth element hider, Shadow DOM UI, persistent freeze, link extractor, time skipper, enhanced reveal, dock button visibility, stable Reveal & Unblock Engine, plus media downloader with preview. Automatic Protection. Now with Anti-Paywall, Auto-Close Modals (smart skip for essential UI incl. side menus), Auto-Accept Age Verification, Auto-Close Logins, Cookie Bypass, Bulk Filter Lists, and improved UI.
// @author KTZ
// @match *://*/*
// @icon https://cdn.corenexis.com/f/v9leABxGUbJ.png
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant unsafeWindow
// @connect *
// @run-at document-start
// @license MIT
// @namespace https://greasyfork.org/users/1620673
// ==/UserScript==
(function() {
'use strict';
// ---------- Safe Storage ----------
const gv = (key, def) => {
try {
if (typeof GM_getValue !== 'undefined') return GM_getValue(key, def);
const item = localStorage.getItem(key);
return item !== null ? JSON.parse(item) : def;
} catch {
return def;
}
};
let isSyncingStorage = false;
const sv = (key, val) => {
try {
if (typeof GM_setValue !== 'undefined') {
GM_setValue(key, val);
} else {
localStorage.setItem(key, JSON.stringify(val));
}
} catch {}
if (!isSyncingStorage) {
isSyncingStorage = true;
try {
if (typeof syncCache === 'function') syncCache();
if (typeof requestUpdateStyles === 'function') requestUpdateStyles();
if (isTop && typeof broadcastState === 'function') broadcastState();
} finally {
isSyncingStorage = false;
}
}
};
const doc = document, win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const UI_HOST_ID = 'hider-ui-root', STEPPER_BAR_ID = 'hider-stepper-bar';
let isTop = false;
try { isTop = (window.self === window.top); } catch { isTop = false; }
let shadowRoot = null;
const shadowBy = id => shadowRoot ? shadowRoot.getElementById(id) : null;
// ---------- Persistent Settings ----------
let initialMem = gv('hider_freeze_memory', null) || (gv('hider_autono_global', false) ? 'block_all' : 'ask');
const CACHE = {
blockedDomainsList: [], blockedDomainsSet: new Set(),
allowedDomainsList: [], allowedDomainsSet: new Set(),
customRules: [], isFrozen: gv('hider_freeze_global', false),
freezeMemory: initialMem, logs: null,
autoTimeSkipper: gv('hider_auto_time_skipper', false),
autoScroll: gv('hider_auto_scroll', true),
enableContextMenu: gv('hider_enable_contextmenu', true),
autoRemoveBlur: gv('hider_auto_remove_blur', false),
universalProtect: true,
antiPaywall: gv('hider_anti_paywall', false),
autoCloseModals: gv('hider_auto_close_modals', false),
cookieConsentMode: gv('hider_cookie_consent_mode', 'ask'),
autoAcceptAge: gv('hider_auto_accept_age', false),
autoCloseLogins: gv('hider_auto_close_logins', false),
filterLists: gv('hider_filter_lists', [])
};
// ---------- Dock Button Visibility ----------
const DOCK_BUTTONS = [
{ id: 'btn-select', label: '🎯 Hide' },
{ id: 'btn-scope', label: '🌐 Scope' },
{ id: 'btn-reveal-quick', label: '👁️ Reveal' },
{ id: 'btn-links', label: '🔗 Links' },
{ id: 'btn-skip-30', label: '⏩ +30s' },
{ id: 'btn-freeze', label: '❄️ Freeze' }
];
function getHiddenDockButtons() {
return gv('hider_hidden_dock_buttons', []);
}
function setHiddenDockButtons(arr) {
sv('hider_hidden_dock_buttons', arr);
}
function applyDockButtonVisibility() {
if (!shadowRoot) return;
const hidden = new Set(getHiddenDockButtons());
DOCK_BUTTONS.forEach(btn => {
const el = shadowRoot.getElementById(btn.id);
if (el) {
el.style.setProperty('display', hidden.has(btn.id) ? 'none' : 'flex', 'important');
}
});
const menuBtn = shadowRoot.getElementById('btn-manage');
if (menuBtn) menuBtn.style.setProperty('display', 'flex', 'important');
}
function renderDockButtonOptions() {
if (!shadowRoot) return;
const container = shadowRoot.getElementById('dock-buttons-list');
if (!container) return;
const hidden = new Set(getHiddenDockButtons());
container.innerHTML = '';
container.style.cssText = 'display:grid!important;grid-template-columns:repeat(auto-fill,minmax(80px,1fr))!important;gap:4px!important;';
DOCK_BUTTONS.forEach(btn => {
const label = document.createElement('label');
label.style.cssText = 'font-size:9px!important;color:#cbd5e1!important;display:flex!important;align-items:center!important;gap:4px!important;cursor:pointer!important;user-select:none!important;background:rgba(255,255,255,0.05)!important;padding:2px 6px!important;border-radius:4px!important;border:1px solid rgba(255,255,255,0.06)!important;';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.dataset.btnId = btn.id;
checkbox.checked = !hidden.has(btn.id);
checkbox.style.cssText = 'accent-color:#38bdf8;cursor:pointer;width:12px!important;height:12px!important;flex-shrink:0!important;';
checkbox.addEventListener('change', function() {
const id = this.dataset.btnId;
let hiddenArr = getHiddenDockButtons();
if (this.checked) {
hiddenArr = hiddenArr.filter(h => h !== id);
} else {
if (!hiddenArr.includes(id)) hiddenArr.push(id);
}
setHiddenDockButtons(hiddenArr);
applyDockButtonVisibility();
});
label.appendChild(checkbox);
const span = document.createElement('span');
span.textContent = btn.label;
span.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
label.appendChild(span);
container.appendChild(label);
});
}
// ---------- Core Variables ----------
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, scrollAnimationFrame = null, styleUpdateRAF = null;
let linkPanelEl = null;
let linkDisplayMode = 'text';
let isProtected = false;
let featuresEnabled = true;
let CURRENT_DOMAIN = '', CURRENT_URL = '';
let previewOutsideListener = null;
let lastHiddenSelector = null;
let globalScopeTemp = false;
// === SMART IMPROVEMENT: Cache for rule signature ===
let lastRuleSignature = '';
let activeHiddenSelector = ''; // Combined selector for the proxy
// ---------- Auto Time Skipper State ----------
let autoSkipInterval = null;
let autoSkipEmptyCount = 0;
const AUTO_SKIP_INTERVAL_MS = 1000;
const AUTO_SKIP_MAX_EMPTY = 4;
// ---------- Cleanup ----------
const timers = {
scrollInterval: null,
blurInterval: null,
protectionPoller: null,
logSaveTimer: null,
collapseTimer: null,
paywallObserverTimer: null,
modalObserverTimer: null,
cookieObserverTimer: null
};
const observers = {
protectionObserver: null,
blurObserver: null,
urlChangeObserver: null,
paywallObserver: null,
modalObserver: null,
cookieObserver: null,
adSkipObserver: null,
headMutationObserver: null
};
const styleElements = {
scrollStyleEl: null,
contextMenuStyleEl: null,
blurGlobalStyle: null
};
function dispose() {
Object.keys(timers).forEach(key => {
if (timers[key]) {
clearInterval(timers[key]);
clearTimeout(timers[key]);
timers[key] = null;
}
});
Object.keys(observers).forEach(key => {
if (observers[key]) {
observers[key].disconnect();
observers[key] = null;
}
});
Object.values(styleElements).forEach(el => {
if (el && el.parentNode) el.remove();
});
const host = doc.getElementById(UI_HOST_ID);
if (host) host.remove();
const stepper = shadowBy(STEPPER_BAR_ID);
if (stepper) stepper.remove();
if (linkPanelEl && linkPanelEl.parentNode) linkPanelEl.remove();
const prompt = shadowBy('hider-freeze-prompt');
if (prompt) prompt.remove();
const toast = doc.getElementById('hider-toast');
if (toast) toast.remove();
const dyn = doc.getElementById('hider-dynamic-styles');
if (dyn) dyn.remove();
stopAutoSkipMonitoring();
}
window.addEventListener('beforeunload', dispose);
// ---------- Context Menu Override ----------
const contextMenuHandler = function(e) {
if (!CACHE.enableContextMenu) return;
const path = e.composedPath ? e.composedPath() : [];
if (path.some(el => el.id === UI_HOST_ID || (el.closest && el.closest('#' + UI_HOST_ID)))) {
e.stopPropagation();
return;
}
};
document.addEventListener('contextmenu', contextMenuHandler, true);
// ---------- Freeze Mode ----------
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 resolveUrl = u => {
if (!u || typeof u !== 'string') return '';
const trimmed = u.trim();
if (trimmed === '*') return '*';
if (!/^[a-zA-Z][a-zA-Z0-9+-.]*:\/\//.test(trimmed)) {
if (trimmed.startsWith('//')) return 'http:' + trimmed;
if (!trimmed.startsWith('/') && !trimmed.startsWith('./') && !trimmed.startsWith('../')) {
return 'http://' + trimmed;
}
}
try { return new URL(trimmed, win.location.href).href; } catch { return trimmed; }
};
const cleanUrl = () => CURRENT_URL || (CURRENT_URL = location.origin + location.pathname);
const cleanDomain = url => {
try {
if (!url) return '';
if (url === '*') return '*';
const abs = resolveUrl(url);
return new URL(abs).hostname.replace(/^www\./, '').toLowerCase();
} catch {
return String(url).trim().toLowerCase();
}
};
const updateCurrentLocCache = () => {
CURRENT_URL = location.origin + location.pathname;
CURRENT_DOMAIN = cleanDomain(location.href);
updateProtectionFlag();
applyAllSettings();
};
function getParentDomain(url) {
if (!url) return '';
try {
const host = cleanDomain(url);
if (!host || host === '*') return host;
const parts = host.split('.');
if (parts.length <= 2) return host;
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'];
const lastTwo = parts.slice(-2).join('.');
return (multiPartTlds.includes(lastTwo) && parts.length > 2) ? parts.slice(-3).join('.') : lastTwo;
} catch {
return cleanDomain(url) || '';
}
}
const FEATURE_BLACKLIST = ['facebook.com', 'fb.com', 'facebook'];
let blacklistToastShown = false;
function isFeatureBlacklisted() {
if (!CURRENT_DOMAIN) return false;
return FEATURE_BLACKLIST.some(domain =>
CURRENT_DOMAIN.includes(domain) || CURRENT_DOMAIN.endsWith('.' + domain)
);
}
// ============ REVEAL & UNBLOCK ENGINE ============
function isProtectedPage() {
try {
if (win.__cf_chl_opt || win.__cfRLUnblockHandlers || win._cf) return true;
if (win.turnstile) {
if (doc.querySelector('.cf-turnstile, #turnstile-wrapper, .turnstile-container, #cf-challenge')) return true;
}
const challengeSelectors = [
'#challenge-running', '#cf-please-wait', '.cf-browser-verification',
'#cf-content', '#cf-stage', '#cf-challenge', '.cf-challenge',
'#cf-error-details', '#cf-waiting', '.cf-please-wait',
'#cf-content-wrapper', '#challenge-form', '.challenge-form', '#cf-intercept'
];
for (const sel of challengeSelectors) {
if (doc.querySelector(sel)) return true;
}
if (doc.querySelector('.ray-id, [data-ray-id], [data-cf-ray]')) return true;
const title = doc.title || '';
if (title.includes('Just a moment...') || title.includes('Attention Required!') ||
title.includes('Security Check') || title.includes('Verify you are human') ||
title.includes('Checking your browser')) {
return true;
}
const forms = doc.querySelectorAll('form[action*="__cf_chl"], form[action*="cf-challenge"]');
if (forms.length > 0) return true;
const scripts = doc.querySelectorAll('script[src*="challenges.cloudflare.com"], script[src*="/cdn-cgi/challenge-platform/"]');
if (scripts.length > 0) return true;
if (doc.querySelector('meta[name="cf-options"]')) return true;
} catch (e) { /* ignore */ }
return false;
}
function updateProtectionFlag() {
const was = isProtected;
isProtected = isProtectedPage();
featuresEnabled = isProtected ? !CACHE.universalProtect : true;
if (was !== isProtected) {
applyAllSettings();
requestUpdateStyles();
}
}
function setupProtectionObserver() {
if (observers.protectionObserver) return;
observers.protectionObserver = new MutationObserver(() => {
const was = isProtected;
updateProtectionFlag();
if (was !== isProtected) {
applyAllSettings();
requestUpdateStyles();
}
});
observers.protectionObserver.observe(doc.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: false
});
let checks = 0;
const rapidInterval = setInterval(() => {
checks++;
const was = isProtected;
updateProtectionFlag();
if (was !== isProtected) {
applyAllSettings();
requestUpdateStyles();
}
if (checks >= 10) clearInterval(rapidInterval);
}, 1000);
timers.protectionPoller = rapidInterval;
if (!window.__hider_protection_poller) {
window.__hider_protection_poller = setInterval(() => {
const was = isProtected;
updateProtectionFlag();
if (was !== isProtected) {
applyAllSettings();
requestUpdateStyles();
}
}, 5000);
}
}
// ---------- Scroll Defeater ----------
function forceEnableScroll() {
if (!CACHE.autoScroll) return;
if (isFeatureBlacklisted() || !featuresEnabled) return;
const html = doc.documentElement, body = doc.body;
if (!html && !body) return;
if (!styleElements.scrollStyleEl) {
styleElements.scrollStyleEl = doc.createElement('style');
styleElements.scrollStyleEl.id = 'hider-force-scroll-style';
(doc.head || doc.documentElement)?.appendChild(styleElements.scrollStyleEl);
}
styleElements.scrollStyleEl.textContent = `
html, body {
overflow: auto !important;
overflow-x: auto !important;
overflow-y: auto !important;
position: static !important;
height: auto !important;
max-height: none !important;
touch-action: auto !important;
-webkit-overflow-scrolling: touch !important;
}
`;
}
function checkAndAutoUnblockScroll() {
if (!CACHE.autoScroll || isFeatureBlacklisted() || !featuresEnabled) {
stopScrollDefeater();
return;
}
const html = doc.documentElement, body = doc.body;
if (!html || !body) return;
try {
const hStyle = win.getComputedStyle(html), bStyle = win.getComputedStyle(body);
if (
hStyle.overflow === 'hidden' || hStyle.overflowY === 'hidden' ||
bStyle.overflow === 'hidden' || bStyle.overflowY === 'hidden' ||
bStyle.position === 'fixed' || hStyle.position === 'fixed'
) {
forceEnableScroll();
}
} catch {}
}
function startScrollDefeater() {
if (isFeatureBlacklisted() || !featuresEnabled) {
stopScrollDefeater();
return;
}
if (timers.scrollInterval) clearInterval(timers.scrollInterval);
if (CACHE.autoScroll) {
forceEnableScroll();
timers.scrollInterval = setInterval(checkAndAutoUnblockScroll, 2000);
}
}
function stopScrollDefeater() {
if (timers.scrollInterval) {
clearInterval(timers.scrollInterval);
timers.scrollInterval = null;
}
if (styleElements.scrollStyleEl) {
styleElements.scrollStyleEl.remove();
styleElements.scrollStyleEl = null;
}
}
// ---------- Context Menu Styles ----------
function updateContextMenuStyles() {
if (isFeatureBlacklisted() || !featuresEnabled) {
if (styleElements.contextMenuStyleEl) {
styleElements.contextMenuStyleEl.remove();
styleElements.contextMenuStyleEl = null;
}
return;
}
if (!CACHE.enableContextMenu) {
if (styleElements.contextMenuStyleEl) {
styleElements.contextMenuStyleEl.remove();
styleElements.contextMenuStyleEl = null;
}
return;
}
if (!styleElements.contextMenuStyleEl) {
styleElements.contextMenuStyleEl = doc.createElement('style');
styleElements.contextMenuStyleEl.id = 'hider-contextmenu-style';
(doc.head || doc.documentElement)?.appendChild(styleElements.contextMenuStyleEl);
}
styleElements.contextMenuStyleEl.textContent = `
* {
-webkit-touch-callout: default !important;
-webkit-user-select: text !important;
user-select: text !important;
}
`;
}
// ---------- Blur Removal ----------
function removeBlurFromElements() {
if (!CACHE.autoRemoveBlur || isFeatureBlacklisted() || !featuresEnabled) {
stopBlurRemoval();
return;
}
const all = doc.querySelectorAll('*');
const uiHost = doc.getElementById(UI_HOST_ID);
for (const el of all) {
if (el === uiHost || (el.closest && el.closest('#' + UI_HOST_ID))) continue;
try {
const style = win.getComputedStyle(el);
if (style.filter && style.filter.includes('blur')) {
el.style.setProperty('filter', 'none', 'important');
el.style.setProperty('backdrop-filter', 'none', 'important');
el.style.setProperty('-webkit-backdrop-filter', 'none', 'important');
}
if (el.style.filter && el.style.filter.includes('blur')) {
el.style.setProperty('filter', 'none', 'important');
}
if (el.style.backdropFilter && el.style.backdropFilter.includes('blur')) {
el.style.setProperty('backdrop-filter', 'none', 'important');
el.style.setProperty('-webkit-backdrop-filter', 'none', 'important');
}
} catch {}
}
}
function scheduleBlurRemoval() {
if (isFeatureBlacklisted() || !featuresEnabled) {
stopBlurRemoval();
return;
}
if (CACHE.autoRemoveBlur && !styleElements.blurGlobalStyle) {
styleElements.blurGlobalStyle = doc.createElement('style');
styleElements.blurGlobalStyle.id = 'hider-blur-global-style';
styleElements.blurGlobalStyle.textContent = `
*:not(#hider-ui-root):not(#hider-ui-root *) {
filter: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
`;
(doc.head || doc.documentElement)?.appendChild(styleElements.blurGlobalStyle);
} else if (!CACHE.autoRemoveBlur && styleElements.blurGlobalStyle) {
styleElements.blurGlobalStyle.remove();
styleElements.blurGlobalStyle = null;
}
if (timers.blurInterval) clearInterval(timers.blurInterval);
if (CACHE.autoRemoveBlur) {
removeBlurFromElements();
timers.blurInterval = setInterval(removeBlurFromElements, 2000);
if (!observers.blurObserver) {
observers.blurObserver = new MutationObserver(() => {
if (CACHE.autoRemoveBlur) {
removeBlurFromElements();
}
});
observers.blurObserver.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
}
} else {
if (observers.blurObserver) {
observers.blurObserver.disconnect();
observers.blurObserver = null;
}
}
}
function stopBlurRemoval() {
if (timers.blurInterval) {
clearInterval(timers.blurInterval);
timers.blurInterval = null;
}
if (observers.blurObserver) {
observers.blurObserver.disconnect();
observers.blurObserver = null;
}
if (styleElements.blurGlobalStyle) {
styleElements.blurGlobalStyle.remove();
styleElements.blurGlobalStyle = null;
}
}
// ---------- SAFE HELPER: Check if element is main content ----------
function isMainContentElement(el) {
if (!el) return false;
if (el === doc.documentElement || el === doc.body) return true;
if (el.id === UI_HOST_ID || el.closest && el.closest('#' + UI_HOST_ID)) return true;
const text = el.textContent || '';
if (text.length > 500) {
const style = win.getComputedStyle(el);
if (style.position === 'fixed' || style.position === 'absolute') {
const z = parseInt(style.zIndex, 10);
if (z > 1000) return false;
}
return true;
}
if (el.matches && el.matches('article, main, section, div[role="main"]')) {
if (text.length > 100) return true;
}
return false;
}
// ================================================================
// NEW HELPER: Detect side panels (smart)
// ================================================================
function isSidePanel(el) {
if (!el || el === doc.documentElement || el === doc.body) return false;
if (el.closest && el.closest('#' + UI_HOST_ID)) return false;
const rect = el.getBoundingClientRect();
const vw = win.innerWidth, vh = win.innerHeight;
const widthRatio = rect.width / vw;
const heightRatio = rect.height / vh;
const isLeft = rect.left < 10;
const isRight = (vw - rect.right) < 10;
// 1. Position & size
if ((isLeft || isRight) && widthRatio < 0.4 && heightRatio > 0.3) return true;
// 2. Class/id/role keywords
const classId = (el.className + ' ' + el.id).toLowerCase();
const role = el.getAttribute('role') || '';
const combined = classId + ' ' + role;
const sideKeywords = ['sidebar', 'drawer', 'menu', 'navigation', 'nav', 'sidepanel', 'offcanvas', 'slide', 'panel', 'sidenav'];
if (sideKeywords.some(kw => combined.includes(kw))) return true;
// 3. ARIA roles that are typical for menus/navigation
const menuRoles = ['navigation', 'menu', 'menubar', 'listbox', 'tree', 'tablist'];
if (menuRoles.includes(role)) return true;
// 4. Many links and not full‑screen → likely a menu
const links = el.querySelectorAll('a');
if (links.length > 5 && widthRatio < 0.5 && heightRatio < 0.9) return true;
// 5. Contains a search input or hamburger‑like button
const hasSearch = el.querySelector('input[type="search"], input[placeholder*="search"], .search-input');
if (hasSearch && widthRatio < 0.5) return true;
// 6. Check for transform/transition that suggests sliding panel
const style = win.getComputedStyle(el);
if (style.transform && style.transform !== 'none' && (style.transform.includes('translateX') || style.transform.includes('translateY'))) {
return true;
}
return false;
}
// ================================================================
// REWRITTEN ANTI-PAYWALL / ANTI-ADBLOCK ENGINE (SMARTER)
// ================================================================
function setupPaywallBypass() {
if (observers.paywallObserver) {
observers.paywallObserver.disconnect();
observers.paywallObserver = null;
}
if (!CACHE.antiPaywall || !featuresEnabled || isFeatureBlacklisted()) return;
const overlaySelectors = [
'.paywall', '.adblock-overlay', '.premium-overlay', '.subscribe-overlay',
'.gate', '.wall', '.restricted', '.locked',
'[class*="paywall"]', '[id*="paywall"]', '[class*="adblock"]', '[id*="adblock"]',
'[class*="premium"]', '[id*="premium"]', '[class*="subscribe"]', '[id*="subscribe"]',
'[class*="overlay"]', '[id*="overlay"]', '[class*="modal"]', '[id*="modal"]'
];
const paywallKeywords = ['adblock', 'paywall', 'premium', 'subscribe', 'whitelist',
'disable adblock', 'ad blocker', 'subscription', 'unlock',
'membership', 'register', 'sign up', 'log in'];
function isPaywallElement(el) {
if (!el || el === doc.documentElement || el === doc.body) return false;
if (el.closest && el.closest('#' + UI_HOST_ID)) return false;
if (isMainContentElement(el)) return false;
if (isSidePanel(el)) return false;
const style = win.getComputedStyle(el);
const pos = style.position;
if (pos !== 'fixed' && pos !== 'absolute') return false;
const rect = el.getBoundingClientRect();
const vw = win.innerWidth, vh = win.innerHeight;
const area = rect.width * rect.height;
const vpArea = vw * vh;
const coverage = area / vpArea;
if (coverage < 0.05) return false;
let score = 0;
let z = parseInt(style.zIndex, 10) || 0;
if (z >= 100) score += 1;
const bg = style.backgroundColor || style.background;
const hasDarkBg = bg && (bg.includes('rgba(0,0,0') || bg.includes('#000') || bg.includes('black'));
if (hasDarkBg) score += 2;
const hasBlur = style.backdropFilter && style.backdropFilter.includes('blur');
if (hasBlur) score += 2;
const text = el.textContent.toLowerCase();
let keywordCount = 0;
paywallKeywords.forEach(kw => { if (text.includes(kw)) keywordCount++; });
if (keywordCount >= 2) score += 3;
else if (keywordCount === 1) score += 1;
const buttons = el.querySelectorAll('button, a[role="button"], input[type="button"], input[type="submit"]');
let actionBtn = false;
for (const btn of buttons) {
const btnText = btn.textContent.toLowerCase();
if (btnText.includes('accept') || btnText.includes('agree') || btnText.includes('continue') ||
btnText.includes('subscribe') || btnText.includes('unlock')) {
actionBtn = true;
break;
}
}
if (actionBtn) score += 2;
if (coverage > 0.6) score += 2;
const hasCloseBtn = el.querySelector('[aria-label*="close"], .close, .dismiss, [class*="close"]');
if (hasCloseBtn) score -= 1;
return score >= 5;
}
function removePaywallElement(el) {
if (!el || el === doc.documentElement || el === doc.body) return;
if (el.closest && el.closest('#' + UI_HOST_ID)) return;
if (isMainContentElement(el)) return;
try {
el.style.setProperty('display', 'none', 'important');
el.style.setProperty('visibility', 'hidden', 'important');
forceEnableScroll();
} catch (e) { /* ignore */ }
}
function scanForPaywalls(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (isPaywallElement(node)) {
removePaywallElement(node);
return;
}
const children = node.querySelectorAll ? node.querySelectorAll('*') : [];
for (const child of children) {
if (isPaywallElement(child)) {
removePaywallElement(child);
}
}
}
}
let paywallTimeout = null;
const paywallObserverCallback = (mutations) => {
if (paywallTimeout) return;
paywallTimeout = setTimeout(() => {
paywallTimeout = null;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => scanForPaywalls(node));
}
}
}, 200);
};
observers.paywallObserver = new MutationObserver(paywallObserverCallback);
observers.paywallObserver.observe(doc.documentElement, { childList: true, subtree: true });
const allElements = doc.querySelectorAll('*');
for (const el of allElements) {
if (isPaywallElement(el)) {
removePaywallElement(el);
}
}
forceEnableScroll();
setTimeout(() => {
const allElements2 = doc.querySelectorAll('*');
for (const el of allElements2) {
if (isPaywallElement(el)) {
removePaywallElement(el);
}
}
forceEnableScroll();
}, 2000);
}
// ================================================================
// UPGRADED AGE VERIFICATION DETECTION & AUTO-FILL
// ================================================================
function isAgeVerificationOrLogin(modal) {
if (!modal) return false;
const text = (modal.textContent || '').toLowerCase();
// Keywords specific to age verification
const ageKeywords = [
'age', 'verify', 'verification', '18', 'over 18', 'adult',
'are you 18', 'confirm age', 'age gate', 'age check',
'you must be 18', 'adult content', 'age restricted',
'birthday', 'date of birth', 'dob'
];
const aria = (modal.getAttribute('aria-label') || '').toLowerCase();
const title = (modal.getAttribute('title') || '').toLowerCase();
const role = modal.getAttribute('role') || '';
const classId = (modal.className + ' ' + modal.id).toLowerCase();
const combined = text + ' ' + aria + ' ' + title + ' ' + classId + ' ' + role;
// Check for specific class names
const ageClassPatterns = [
'age-gate', 'age-verification', 'age-check', 'age-verify',
'adult-verification', 'age-modal', 'age-overlay',
'birthday-verification', 'dob-verification'
];
for (const pattern of ageClassPatterns) {
if (combined.includes(pattern)) return true;
}
// Check for date input fields (day/month/year selects or date input)
const hasDateFields = modal.querySelector('select.day, select.month, select.year, input[type="date"]');
if (hasDateFields) {
// If there are date fields and age-related keywords, it's age verification
if (ageKeywords.some(kw => combined.includes(kw))) return true;
}
// Check for "Enter" / "Confirm" button with age keywords
const buttons = modal.querySelectorAll('button, a[role="button"], input[type="button"], input[type="submit"]');
let hasAgeButton = false;
for (const btn of buttons) {
const btnText = btn.textContent.toLowerCase();
const btnAria = (btn.getAttribute('aria-label') || '').toLowerCase();
const btnCombined = btnText + ' ' + btnAria;
if (btnCombined.includes('enter') || btnCombined.includes('confirm') ||
btnCombined.includes('verify') || btnCombined.includes('i am') ||
btnCombined.includes('proceed') || btnCombined.includes('continue')) {
if (ageKeywords.some(kw => combined.includes(kw))) {
hasAgeButton = true;
break;
}
}
}
if (hasAgeButton) return true;
// Fallback: if it looks like a centered modal with a dark backdrop and age keywords
const style = win.getComputedStyle(modal);
const pos = style.position;
if (pos === 'fixed' || pos === 'absolute') {
const rect = modal.getBoundingClientRect();
const vw = win.innerWidth, vh = win.innerHeight;
const widthRatio = rect.width / vw;
const heightRatio = rect.height / vh;
// Centered modal (between 30% and 80% width, 20% to 80% height)
if (widthRatio > 0.2 && widthRatio < 0.8 && heightRatio > 0.1 && heightRatio < 0.9) {
const bg = style.backgroundColor || style.background;
const hasDarkBg = bg && (bg.includes('rgba(0,0,0') || bg.includes('#000') || bg.includes('black'));
if (hasDarkBg && ageKeywords.some(kw => combined.includes(kw))) {
return true;
}
}
}
return false;
}
function fillAgeFields(container) {
if (!container) return false;
let filled = false;
// 1. Input[type="date"]
const dateInputs = container.querySelectorAll('input[type="date"]');
for (const input of dateInputs) {
if (!input.value) {
input.value = '2001-01-01';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
}
// 2. Select dropdowns for day/month/year
const selects = container.querySelectorAll('select');
let daySelect = null, monthSelect = null, yearSelect = null;
for (const sel of selects) {
const name = (sel.name || '').toLowerCase();
const id = (sel.id || '').toLowerCase();
const combined = name + ' ' + id;
if (combined.includes('day') || combined.includes('dd') || combined.includes('date')) {
daySelect = sel;
} else if (combined.includes('month') || combined.includes('mm')) {
monthSelect = sel;
} else if (combined.includes('year') || combined.includes('yyyy') || combined.includes('yy')) {
yearSelect = sel;
}
}
// Try to find selects by label/placeholder/aria-label
if (!daySelect || !monthSelect || !yearSelect) {
for (const sel of selects) {
const label = sel.getAttribute('aria-label') || sel.getAttribute('placeholder') || '';
const lower = label.toLowerCase();
if (!daySelect && (lower.includes('day') || lower.includes('date'))) daySelect = sel;
else if (!monthSelect && lower.includes('month')) monthSelect = sel;
else if (!yearSelect && (lower.includes('year') || lower.includes('yyyy'))) yearSelect = sel;
}
}
// Set day → 1
if (daySelect && daySelect.options.length > 1) {
let found = false;
for (let opt of daySelect.options) {
if (opt.value === '1' || opt.value === '01' || opt.text.trim() === '1' || opt.text.trim() === '01') {
daySelect.selectedIndex = opt.index;
found = true;
break;
}
}
if (!found) daySelect.selectedIndex = 1;
daySelect.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
// Set month → January (1)
if (monthSelect && monthSelect.options.length > 1) {
for (let opt of monthSelect.options) {
if (opt.value === '1' || opt.value === '01' || opt.text.toLowerCase().includes('jan')) {
monthSelect.selectedIndex = opt.index;
break;
}
}
monthSelect.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
// Set year → 2001 (or the earliest available if 2001 not present)
if (yearSelect && yearSelect.options.length > 1) {
let yearSet = false;
for (let opt of yearSelect.options) {
if (opt.value === '2001') {
yearSelect.selectedIndex = opt.index;
yearSet = true;
break;
}
}
if (!yearSet && yearSelect.options.length > 0) {
// Pick the first option that looks like a year (e.g., 1990-2010)
const years = [];
for (let opt of yearSelect.options) {
const val = parseInt(opt.value, 10);
if (!isNaN(val) && val > 1900 && val < 2030) {
years.push({ value: val, index: opt.index });
}
}
if (years.length > 0) {
// Pick the median year or the earliest that is at least 18 years ago
const now = new Date().getFullYear();
const minYear = now - 100; // allow up to 100 years ago
const maxYear = now - 18; // must be at least 18
let best = years[0];
for (const y of years) {
if (y.value >= maxYear && y.value <= now) {
best = y;
break;
}
}
yearSelect.selectedIndex = best.index;
} else {
yearSelect.selectedIndex = 1; // fallback
}
}
yearSelect.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
// 3. Text/number inputs for age
const ageInputs = container.querySelectorAll('input[type="text"], input[type="number"]');
for (const input of ageInputs) {
const name = (input.name || '').toLowerCase();
const placeholder = (input.placeholder || '').toLowerCase();
const combined = name + ' ' + placeholder;
if (combined.includes('age') || combined.includes('birth') || combined.includes('dob') || combined.includes('birthday')) {
if (!input.value) {
input.value = '18'; // or '2001-01-01' if they expect a date
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
filled = true;
}
}
}
return filled;
}
function findAcceptButton(modal) {
const buttons = modal.querySelectorAll('button, a[role="button"], input[type="button"], input[type="submit"]');
const acceptKeywords = ['accept', 'enter', 'yes', 'i am over 18', 'over 18', 'continue', 'proceed', 'verify', 'confirm', 'i am 18', 'enter site', 'go to site'];
for (const btn of buttons) {
const text = btn.textContent.trim().toLowerCase();
const aria = (btn.getAttribute('aria-label') || '').toLowerCase();
const combined = text + ' ' + aria;
if (acceptKeywords.some(kw => combined.includes(kw))) {
return btn;
}
}
// Look for any button that is not "close" or "cancel"
for (const btn of buttons) {
const text = btn.textContent.trim().toLowerCase();
if (text && !text.includes('close') && !text.includes('cancel') && !text.includes('dismiss')) {
return btn;
}
}
return null;
}
// ================================================================
// AUTO-CLOSE MODALS & OVERLAYS ENGINE (SMART & SITE‑AWARE)
// ================================================================
function setupModalAutoClose() {
if (observers.modalObserver) {
observers.modalObserver.disconnect();
observers.modalObserver = null;
}
if (!CACHE.autoCloseModals || !featuresEnabled || isFeatureBlacklisted()) return;
const modalSelectors = [
'[role="dialog"]', '[role="alertdialog"]', '.modal', '.popup', '.lightbox',
'[class*="modal"]', '[id*="modal"]', '[class*="popup"]', '[id*="popup"]',
'[class*="overlay"]', '[id*="overlay"]', '.modal-overlay', '.modal-backdrop'
];
const closeKeywords = ['close', 'dismiss', 'cancel', '×', 'x', 'got it', 'ok', 'continue'];
function isNavigationOrMenu(modal) {
const role = modal.getAttribute('role');
if (role && ['navigation', 'menu', 'listbox', 'tree', 'menubar', 'tablist'].includes(role)) {
return true;
}
const label = (modal.getAttribute('aria-label') || '').toLowerCase();
if (label.includes('navigation') || label.includes('menu') || label.includes('sidebar') || label.includes('drawer')) {
return true;
}
const classId = (modal.className + ' ' + modal.id).toLowerCase();
if (/(menu|sidebar|drawer|nav|navigation|sidepanel|offcanvas|slide[-_]in|slide[-_]out)/i.test(classId)) {
return true;
}
const links = modal.querySelectorAll('a');
if (links.length > 3) {
const style = win.getComputedStyle(modal);
const pos = style.position;
if (pos === 'fixed' || pos === 'absolute') {
const rect = modal.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
if ((rect.left < 50 || (vw - rect.right) < 50) && rect.width < vw * 0.5) {
return true;
}
}
}
const parent = modal.parentElement;
if (parent && parent.matches && parent.matches('.sidebar, .menu, .nav, .drawer, .offcanvas, [role="navigation"]')) {
return true;
}
return false;
}
function isEssentialUI(modal) {
if (isSidePanel(modal)) return true;
if (isNavigationOrMenu(modal)) return true;
const rect = modal.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const widthRatio = rect.width / vw;
const heightRatio = rect.height / vh;
const isLeft = rect.left < 10;
const isRight = (vw - rect.right) < 10;
if ((isLeft || isRight) && widthRatio < 0.5 && heightRatio > 0.3) return true;
if (modal.querySelector('input, textarea, select')) {
const text = (modal.textContent || '').toLowerCase();
if (text.includes('subscribe') || text.includes('paywall') || text.includes('unlock') || text.includes('membership')) {
return false;
}
if (CACHE.autoCloseLogins) return false;
return true;
}
if (widthRatio < 0.3 || heightRatio < 0.3) return true;
if (modal.matches && modal.matches('.menu, .dropdown, .panel, .sidebar, .drawer, [role="menu"], [role="listbox"], [role="navigation"]')) return true;
if (modal.querySelector('input[type="search"], input[placeholder*="search"], .search-input')) return true;
return false;
}
function findCloseButton(modal) {
const buttons = modal.querySelectorAll('button, a[role="button"], input[type="button"], input[type="submit"]');
for (const btn of buttons) {
const text = btn.textContent.trim().toLowerCase();
const aria = (btn.getAttribute('aria-label') || '').toLowerCase();
if (closeKeywords.some(kw => text.includes(kw) || aria.includes(kw))) {
return btn;
}
}
const closeEl = modal.querySelector('.close, .dismiss, [aria-label*="close"], [aria-label*="Close"]');
return closeEl || null;
}
function tryCloseModal(modal) {
if (!modal || modal === doc.documentElement || modal === doc.body) return false;
if (modal.closest && modal.closest('#' + UI_HOST_ID)) return false;
if (isMainContentElement(modal)) return false;
if (isSidePanel(modal)) return false;
// ---- AGE VERIFICATION HANDLING ----
if (isAgeVerificationOrLogin(modal)) {
if (CACHE.autoAcceptAge) {
// Fill date fields
fillAgeFields(modal);
const acceptBtn = findAcceptButton(modal);
if (acceptBtn) {
try { acceptBtn.click(); return true; } catch {}
}
}
return false; // don't close automatically unless we clicked
}
// ---- LOGIN HANDLING ----
if (CACHE.autoCloseLogins) {
// If it's a login modal, try to close it
const text = (modal.textContent || '').toLowerCase();
if (text.includes('login') || text.includes('sign in') || text.includes('log in') || text.includes('register')) {
const closeBtn = findCloseButton(modal);
if (closeBtn) {
try { closeBtn.click(); return true; } catch {}
}
}
}
// ---- REGULAR MODAL CLOSE ----
if (isNavigationOrMenu(modal) || isEssentialUI(modal)) return false;
if (modal.querySelector('input[type="text"], input[type="search"], textarea, select')) {
const text = modal.textContent.toLowerCase();
if (!text.includes('subscribe') && !text.includes('paywall') && !text.includes('unlock')) {
return false;
}
}
const rect = modal.getBoundingClientRect();
const vw = window.innerWidth, vh = window.innerHeight;
const areaRatio = (rect.width * rect.height) / (vw * vh);
if (areaRatio < 0.3) {
const style = win.getComputedStyle(modal);
const bg = style.backgroundColor || style.background;
const hasDarkBg = bg && (bg.includes('rgba(0,0,0') || bg.includes('#000') || bg.includes('black'));
const text = (modal.textContent || '').toLowerCase();
const isAdOrPaywall = text.includes('ad') || text.includes('subscribe') || text.includes('unlock');
if (!hasDarkBg && !isAdOrPaywall) return false;
}
const closeBtn = findCloseButton(modal);
if (closeBtn) {
try { closeBtn.click(); return true; } catch {}
}
if (modal.matches('.modal-backdrop, .modal-overlay, .overlay')) {
try { modal.style.setProperty('display', 'none', 'important'); return true; } catch {}
}
const parent = modal.parentElement;
if (parent && parent.matches && parent.matches('.modal-backdrop, .modal-overlay, .overlay')) {
try { parent.style.setProperty('display', 'none', 'important'); return true; } catch {}
}
return false;
}
function scanForModals() {
const modals = doc.querySelectorAll(modalSelectors.join(','));
for (const modal of modals) {
tryCloseModal(modal);
}
}
let modalTimeout = null;
const modalObserverCallback = () => {
if (modalTimeout) return;
modalTimeout = setTimeout(() => {
modalTimeout = null;
scanForModals();
}, 250);
};
observers.modalObserver = new MutationObserver(modalObserverCallback);
observers.modalObserver.observe(doc.documentElement, { childList: true, subtree: true });
setTimeout(scanForModals, 500);
setTimeout(scanForModals, 1500);
setTimeout(scanForModals, 3000);
}
// ---------- Cookie Consent ----------
function setupCookieConsent() {
if (observers.cookieObserver) {
observers.cookieObserver.disconnect();
observers.cookieObserver = null;
}
if (CACHE.cookieConsentMode === 'ask' || !featuresEnabled || isFeatureBlacklisted()) return;
const cookieSelectors = [
'#cookie-consent', '#cookie-banner', '.cookie-consent', '.cookie-banner',
'[class*="cookie"]', '[id*="cookie"]', '[aria-label*="cookie"]'
];
const acceptKeywords = ['accept', 'agree', 'allow', 'yes', 'ok', 'got it'];
const rejectKeywords = ['reject', 'decline', 'no', 'deny'];
function handleCookieBanner() {
const banners = doc.querySelectorAll(cookieSelectors.join(','));
for (const banner of banners) {
if (banner === doc.body || banner === doc.documentElement) continue;
if (banner.closest && banner.closest('#' + UI_HOST_ID)) continue;
const style = win.getComputedStyle(banner);
if (style.display === 'none' || style.visibility === 'hidden') continue;
if (isMainContentElement(banner)) continue;
const buttons = banner.querySelectorAll('button, a[role="button"], input[type="button"]');
const mode = CACHE.cookieConsentMode;
let targetTexts = mode === 'accept' ? acceptKeywords : rejectKeywords;
for (const btn of buttons) {
const text = btn.textContent.trim().toLowerCase();
if (targetTexts.some(kw => text.includes(kw))) {
try { btn.click(); } catch {}
return;
}
}
const closeBtn = banner.querySelector('[aria-label*="close"], [aria-label*="Close"], .close, .dismiss');
if (closeBtn) {
try { closeBtn.click(); } catch {}
}
}
}
observers.cookieObserver = new MutationObserver(() => {
handleCookieBanner();
});
observers.cookieObserver.observe(doc.documentElement, { childList: true, subtree: true });
setTimeout(handleCookieBanner, 500);
setTimeout(handleCookieBanner, 2000);
}
// ---------- Apply all settings ----------
function applyAllSettings() {
if (isFeatureBlacklisted() && !blacklistToastShown) {
showToast('⚠️ Some Reveal features disabled on this domain');
blacklistToastShown = true;
}
if (CACHE.autoScroll) startScrollDefeater();
else stopScrollDefeater();
updateContextMenuStyles();
if (CACHE.autoRemoveBlur) scheduleBlurRemoval();
else stopBlurRemoval();
updateProtectionFlag();
setupPaywallBypass();
setupModalAutoClose();
setupCookieConsent();
if (CACHE.autoTimeSkipper && featuresEnabled) {
startAutoSkipMonitoring();
} else {
stopAutoSkipMonitoring();
}
}
// ---------- Sync Cache ----------
function syncCache() {
CACHE.blockedDomainsList = gv('hider_blocked_domains', []);
CACHE.blockedDomainsSet = new Set(CACHE.blockedDomainsList.map(cleanDomain).filter(Boolean));
CACHE.allowedDomainsList = gv('hider_allowed_domains', []);
CACHE.allowedDomainsSet = new Set(CACHE.allowedDomainsList.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);
CACHE.autoTimeSkipper = gv('hider_auto_time_skipper', false);
CACHE.autoScroll = gv('hider_auto_scroll', true);
CACHE.enableContextMenu = gv('hider_enable_contextmenu', true);
CACHE.autoRemoveBlur = gv('hider_auto_remove_blur', false);
CACHE.universalProtect = true;
CACHE.antiPaywall = gv('hider_anti_paywall', false);
CACHE.autoCloseModals = gv('hider_auto_close_modals', false);
CACHE.cookieConsentMode = gv('hider_cookie_consent_mode', 'ask');
CACHE.autoAcceptAge = gv('hider_auto_accept_age', false);
CACHE.autoCloseLogins = gv('hider_auto_close_logins', false);
CACHE.filterLists = gv('hider_filter_lists', []);
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());
}
applyAllSettings();
}
syncCache();
function disableFreezeMode() {
isFrozen = false; CACHE.isFrozen = false;
sv('hider_freeze_global', false);
shadowBy('btn-freeze')?.classList.remove('is-frozen');
broadcastState();
}
// ---------- Stealth Engine (SMART: no class, just proxy) ----------
const styleProxyCache = new WeakMap();
try {
const origGetComputedStyle = win.getComputedStyle;
win.getComputedStyle = function(el, pseudo) {
const style = origGetComputedStyle.apply(this, arguments);
if (el && el instanceof Element && el.matches && activeHiddenSelector && el.matches(activeHiddenSelector)) {
let cachedProxy = styleProxyCache.get(style);
if (!cachedProxy) {
cachedProxy = new Proxy(style, {
get(target, prop) {
switch(prop) {
case 'display': return 'block';
case 'visibility': return 'visible';
case 'opacity': return '1';
case 'pointerEvents': return 'auto';
default:
const val = target[prop];
return typeof val === 'function' ? val.bind(target) : val;
}
}
});
styleProxyCache.set(style, cachedProxy);
}
return cachedProxy;
}
return style;
};
} catch {}
const isSameDomain = u => cleanDomain(u) && cleanDomain(u) === CURRENT_DOMAIN;
const isDomainBlocked = u => {
if (!u || !CACHE.blockedDomainsSet.size) return false;
const d = cleanDomain(u), lower = String(u).toLowerCase();
return d && (CACHE.blockedDomainsSet.has(d) || [...CACHE.blockedDomainsSet].some(b => b !== '*' && (d.endsWith('.' + b) || lower.includes(b))));
};
const isDomainAllowed = u => {
if (!u || !CACHE.allowedDomainsSet.size) return false;
const d = cleanDomain(u), lower = String(u).toLowerCase();
return d && (CACHE.allowedDomainsSet.has('*') || CACHE.allowedDomainsSet.has(d) || [...CACHE.allowedDomainsSet].some(a => a !== '*' && (d.endsWith('.' + a) || lower.includes(a))));
};
// ---------- WILDCARD CONVERSION ----------
function getBaseName(name) {
let base = name.replace(/[-_][0-9a-fA-F]+$/, '');
base = base.replace(/[0-9]+$/, '');
base = base.replace(/[-_]+$/, '');
return base || name;
}
function convertToWildcardSelector(sel) {
if (!sel || typeof sel !== 'string') return '';
return sel.replace(/(?<![#.])(#|\.)([a-zA-Z0-9_-]+)/g, (match, p, name) => {
if (name.startsWith('hider-')) return match;
const base = getBaseName(name);
if (!base) return match;
return p === '#' ? `[id*="${base}"]` : `[class*="${base}"]`;
});
}
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 (!timers.logSaveTimer) timers.logSaveTimer = setTimeout(() => { timers.logSaveTimer = null; sv('hider_global_logs', CACHE.logs); }, 1000);
}
// ---------- Freeze FX ----------
function triggerFreezeFx() {
const fx = doc.createElement('div');
fx.className = 'hider-freeze-snow-overlay';
const frag = doc.createDocumentFragment();
const flakeChars = ['❄', '❅', '❆', '✨', '⚡'];
for (let i = 0; i < 22; i++) {
const flake = doc.createElement('div');
flake.className = 'hider-snow-flake';
flake.textContent = flakeChars[Math.floor(Math.random() * flakeChars.length)];
flake.style.cssText = `left:${Math.random() * 98}vw; top:${Math.random() * 60}vh; animation-delay:${Math.random() * 0.35}s; font-size:${14 + Math.random() * 18}px;`;
frag.appendChild(flake);
}
fx.appendChild(frag);
(doc.body || doc.documentElement)?.appendChild(fx);
setTimeout(() => fx.remove(), 1450);
}
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) {
if (isDomainBlocked(url)) {
logBlockedAttempt(url, triggerType + ' (Auto-Block Domain)');
onDeny?.(); simulateAdWindowSuccess(); return;
}
if (isDomainAllowed(url)) {
userApprovedNavigation = true; onConfirm?.();
setTimeout(() => userApprovedNavigation = false, 300); return;
}
const mem = CACHE.freezeMemory || 'ask';
if (mem === 'allow_all') {
disableFreezeMode(); userApprovedNavigation = true; onConfirm?.();
setTimeout(() => userApprovedNavigation = false, 300); return;
}
if (mem === 'block_all') {
logBlockedAttempt(url, triggerType + ' (Auto-Block)');
onDeny?.(); simulateAdWindowSuccess(); return;
}
if (mem === 'allow_same' && isSameDomain(url)) {
const domain = getParentDomain(url) || cleanDomain(url);
if (domain) executeAddAllowedDomain(domain);
userApprovedNavigation = true; onConfirm?.();
setTimeout(() => userApprovedNavigation = false, 300); return;
}
showFreezePrompt(url, triggerType, onConfirm, onDeny);
}
// ---------- Interceptors ----------
let interceptorsInstalled = false;
function installInterceptors() {
if (interceptorsInstalled) return;
interceptorsInstalled = true;
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 (!featuresEnabled) {
return origFn.call(contextThis, u);
}
if (isDomainBlocked(u)) {
logBlockedAttempt(u, `Blocked ${triggerName}`);
showToast(`⛔ Blocked ${triggerName}`); simulateAdWindowSuccess(); return;
}
if (isFrozen && !userApprovedNavigation) {
if (isDomainAllowed(u)) {
userApprovedNavigation = true; const res = origFn.call(contextThis, u);
setTimeout(() => userApprovedNavigation = false, 300); return res;
}
handleFreezeNavigation(u, triggerName, () => {
userApprovedNavigation = true; origFn.call(contextThis, u);
setTimeout(() => userApprovedNavigation = false, 300);
}, simulateAdWindowSuccess);
return;
}
return origFn.call(contextThis, u);
};
if (origA) lp.assign = function(u) { return checkAndInterceptNav(u, origA, this, 'Redirect (assign)'); };
if (origR) lp.replace = function(u) { return checkAndInterceptNav(u, origR, this, 'Redirect (replace)'); };
if (hrefDesc?.set) {
try {
Object.defineProperty(lp, 'href', {
set(u) { checkAndInterceptNav(u, hrefDesc.set, this, 'Redirect (href)'); },
get() { return hrefDesc.get.call(this); }
});
} catch {}
}
}
const origClick = HTMLAnchorElement.prototype.click;
HTMLAnchorElement.prototype.click = function() {
if (!featuresEnabled) return origClick.apply(this, arguments);
if (isDomainBlocked(this.href)) { logBlockedAttempt(this.href, 'Blocked Click'); showToast('⛔ Blocked link click'); simulateAdWindowSuccess(); return; }
if (isFrozen && !userApprovedNavigation) {
if (isDomainAllowed(this.href)) {
userApprovedNavigation = true; const res = origClick.apply(this, arguments);
setTimeout(() => userApprovedNavigation = false, 300); return res;
}
handleFreezeNavigation(this.href, 'Anchor Click', () => {
userApprovedNavigation = true; origClick.apply(this, arguments);
setTimeout(() => userApprovedNavigation = false, 300);
}, simulateAdWindowSuccess);
return;
}
return origClick.apply(this, arguments);
};
const origOpen = win.open;
win.open = function(url, target, features) {
if (!featuresEnabled) return origOpen.apply(win, arguments);
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) {
if (isDomainAllowed(url)) {
userApprovedNavigation = true; const res = origOpen.apply(win, arguments);
setTimeout(() => userApprovedNavigation = false, 300); return res;
}
handleFreezeNavigation(url, 'win.open()', () => {
userApprovedNavigation = true; origOpen.call(win, url, target, features);
setTimeout(() => userApprovedNavigation = false, 300);
}, () => {});
return createDummyWindow();
}
return origOpen.apply(win, arguments);
};
} catch {}
}
// ---------- Broadcast / Sync ----------
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;
syncCache(); requestUpdateStyles();
shadowBy('btn-select')?.classList.toggle('active', isSelecting);
shadowBy('btn-scope')?.classList.toggle('active', currentScope === 'link');
shadowBy('btn-scope')?.classList.toggle('scope-global', currentScope === 'global');
shadowBy('btn-freeze')?.classList.toggle('is-frozen', isFrozen);
if (!isSelecting) clearSelectionState();
applyDockButtonVisibility();
}
if (e.data.type === 'HIDER_RESUME_VIDEOS') triggerVideoResumeChain();
if (e.data.type === 'HIDER_SKIP_30') skip30Seconds();
});
window.addEventListener('storage', e => {
if (e.key && e.key.startsWith('hider_')) {
syncCache(); requestUpdateStyles();
if (isTop) broadcastState();
applyDockButtonVisibility();
}
});
if (typeof GM_addValueChangeListener !== 'undefined') {
['hider_freeze_global', 'hider_freeze_memory', 'hider_blocked_domains', 'hider_allowed_domains', 'hider_custom_rules_v4', 'hider_auto_time_skipper',
'hider_auto_scroll', 'hider_enable_contextmenu', 'hider_auto_remove_blur', 'hider_hidden_dock_buttons',
'hider_anti_paywall', 'hider_auto_close_modals', 'hider_cookie_consent_mode', 'hider_auto_accept_age', 'hider_auto_close_logins', 'hider_filter_lists'
].forEach(key => {
try { GM_addValueChangeListener(key, () => { syncCache(); requestUpdateStyles(); if (isTop) broadcastState(); applyDockButtonVisibility(); }); } catch {}
});
}
if (!isTop) try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch {}
// ---------- Style Update (SMART: signature-based) ----------
function getRuleSignature() {
const host = location.hostname;
const siteRules = gv('hider_site_' + host, []);
const linkRules = gv('hider_link_' + cleanUrl(), []);
const customActiveRules = (CACHE.customRules || [])
.filter(r => {
if (!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);
return combined.sort().join('|');
}
function requestUpdateStyles(force = false) {
if (styleUpdateRAF) {
cancelAnimationFrame(styleUpdateRAF);
styleUpdateRAF = null;
}
styleUpdateRAF = requestAnimationFrame(() => {
styleUpdateRAF = null;
updateStyles(force);
});
}
function updateStyles(force = false) {
const currentSignature = getRuleSignature();
if (!force && lastRuleSignature === currentSignature) {
if (CACHE.autoScroll && featuresEnabled) checkAndAutoUnblockScroll();
return;
}
let el = doc.getElementById('hider-dynamic-styles');
if (!el) {
el = doc.createElement('style');
el.id = 'hider-dynamic-styles';
(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?.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 safeCombined = combined.filter(sel => !sel.match(/^document$/i));
activeHiddenSelector = safeCombined.join(',');
const hideCss = (featuresEnabled && safeCombined.length)
? `${activeHiddenSelector}{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}`
: '';
const baseAnimationCss = `
.hider-preview-highlight { outline: none !important; border: 2px solid #38bdf8 !important; background: rgba(56, 189, 248, 0.15) !important; box-shadow: 0 0 24px rgba(56, 189, 248, 0.6), inset 0 0 12px rgba(56, 189, 248, 0.3) !important; transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1) !important; position: relative !important; z-index: 2147483640 !important; cursor: crosshair !important; border-radius: 6px !important; }
.hider-freeze-snow-overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; pointer-events: none !important; z-index: 2147483647 !important; overflow: hidden !important; background: radial-gradient(circle at center, rgba(56, 189, 248, 0.18) 0%, transparent 70%) !important; animation: hiderSnowOverlayFade 1.4s cubic-bezier(0.16, 1, 0.3, 1) forwards !important; }
@keyframes hiderSnowOverlayFade { 0% { opacity: 0; backdrop-filter: blur(0px); } 30% { opacity: 1; backdrop-filter: blur(3px); } 80% { opacity: 1; backdrop-filter: blur(3px); } 100% { opacity: 0; backdrop-filter: blur(0px); } }
.hider-snow-flake { position: absolute !important; color: #e0f2fe !important; user-select: none !important; pointer-events: none !important; opacity: 0.95 !important; animation: hiderSnowFall 1.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards !important; filter: drop-shadow(0 0 8px rgba(56, 189, 248, 0.9)) !important; }
@keyframes hiderSnowFall { 0% { transform: translateY(-25px) scale(0.5) rotate(0deg); opacity: 0; } 20% { opacity: 1; } 100% { transform: translateY(140px) scale(1.15) rotate(180deg); opacity: 0; } }`;
const css = baseAnimationCss + '\n' + hideCss;
if (cachedCssString !== css) {
el.textContent = css;
cachedCssString = css;
}
lastRuleSignature = currentSignature;
if (CACHE.autoScroll && featuresEnabled) checkAndAutoUnblockScroll();
}
updateStyles(true);
// === MOBILE-FRIENDLY PERSISTENCE: Reapply on visibility/focus ===
function reapplyHiddenStyles() {
requestUpdateStyles(true);
}
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
reapplyHiddenStyles();
}
});
window.addEventListener('focus', reapplyHiddenStyles, { passive: true });
if (doc.head) {
observers.headMutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.removedNodes) {
if (node.id === 'hider-dynamic-styles') {
requestUpdateStyles(true);
return;
}
}
}
});
observers.headMutationObserver.observe(doc.head, { childList: true });
}
// ---------- Selector Helpers ----------
function safeCSSEscape(str) { return (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') ? CSS.escape(str) : str.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1'); }
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 < 18) { path.unshift(`#${safeCSSEscape(curr.id)}`); break; }
const rawC = typeof curr.className === 'string' ? curr.className : curr.getAttribute('class') || '';
const 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 += `.${safeCSSEscape(classes[0])}` + (classes[1] ? `.${safeCSSEscape(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(' > ');
}
// ---------- Drag Listeners ----------
function attachDragListeners(barEl, handleEl) {
if (!handleEl) return;
let startX, startY, initialX, initialY;
const onMove = e => {
if (!isDraggingStepper) return;
if (e.cancelable) e.preventDefault();
const p = e.touches ? e.touches[0] : e, maxX = win.innerWidth - barEl.offsetWidth, maxY = win.innerHeight - barEl.offsetHeight;
stepperPos = { x: Math.max(0, Math.min(maxX, initialX + (p.clientX - startX))), y: Math.max(0, Math.min(maxY, initialY + (p.clientY - startY))) };
barEl.style.setProperty('left', `${stepperPos.x}px`, 'important'); barEl.style.setProperty('top', `${stepperPos.y}px`, 'important');
barEl.style.setProperty('right', 'auto', 'important'); barEl.style.setProperty('bottom', 'auto', 'important'); barEl.style.setProperty('transform', 'none', 'important');
};
const onEnd = () => {
if (isDraggingStepper) { isDraggingStepper = false; }
win.removeEventListener('mousemove', onMove); win.removeEventListener('mouseup', onEnd);
win.removeEventListener('touchmove', onMove); win.removeEventListener('touchend', onEnd); win.removeEventListener('touchcancel', onEnd);
};
const onStart = e => {
if (e.target.tagName === 'BUTTON') return;
if (e.cancelable) e.preventDefault();
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.setProperty('right', 'auto', 'important');
barEl.style.setProperty('bottom', 'auto', 'important');
barEl.style.setProperty('transform', 'none', 'important');
barEl.style.setProperty('margin', '0', 'important');
barEl.style.setProperty('left', `${initialX}px`, 'important');
barEl.style.setProperty('top', `${initialY}px`, 'important');
win.addEventListener('mousemove', onMove, { passive: false }); win.addEventListener('mouseup', onEnd);
win.addEventListener('touchmove', onMove, { passive: false }); win.addEventListener('touchend', onEnd); win.addEventListener('touchcancel', onEnd);
};
handleEl.addEventListener('mousedown', onStart, { passive: false });
handleEl.addEventListener('touchstart', onStart, { passive: false });
}
function clearSelectionState() {
previewElement?.classList.remove('hider-preview-highlight'); previewElement = null; stepperStack = [];
const stepper = shadowBy(STEPPER_BAR_ID);
if (stepper) {
stepper.remove();
}
globalScopeTemp = false;
}
function turnOffHideMode() {
if (isSelecting) {
isSelecting = false; shadowBy('btn-select')?.classList.remove('active');
clearSelectionState(); broadcastState();
showToast('🎯 Selection mode OFF');
}
globalScopeTemp = false;
}
function closeAllMenus(e, force = false) {
if (!shadowRoot) return false;
if (!force) {
if (isSelecting || isDraggingDock || isDraggingStepper) return false;
const active = shadowRoot.activeElement || doc.activeElement;
if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA')) return false;
const path = e?.composedPath?.() || [];
if (path.length > 0 && path.some(el => el.id === UI_HOST_ID || el.id === STEPPER_BAR_ID)) return false;
if (shadowBy('hider-panel')?.querySelector('.is-editing')) return false;
}
const panel = shadowBy('hider-panel'), menu = shadowBy('hider-dock-menu'), mainBtn = shadowBy('btn-toggle-dock'), dockEl = shadowBy('hider-main-dock');
let closed = false;
shadowRoot.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
if (linkPanelEl && linkPanelEl.classList.contains('is-visible')) {
linkPanelEl.classList.remove('is-visible');
setTimeout(() => linkPanelEl.style.display = 'none', 300);
closed = true;
}
if (panel && panel.classList.contains('is-visible')) {
panel.classList.remove('is-visible');
setTimeout(()=> { if(!panel.classList.contains('is-visible')) panel.style.display='none'; }, 300);
shadowBy('btn-manage')?.classList.remove('active');
closed = true;
}
if (menu && menu.classList.contains('is-open')) {
menu.classList.remove('is-open'); mainBtn?.classList.remove('expanded'); dockEl?.classList.remove('expanded'); dockEl?.classList.add('manual-hidden');
if (timers.collapseTimer) clearTimeout(timers.collapseTimer);
timers.collapseTimer = setTimeout(() => { dockEl?.classList.add('is-collapsed'); timers.collapseTimer = null; }, 1000);
closed = true;
}
return closed;
}
// ---------- Stepper UI ----------
function renderTouchStepperUI() {
if (!shadowRoot) return;
let stepper = shadowBy(STEPPER_BAR_ID);
if (!previewElement) { stepper?.remove(); return; }
if (!stepper) {
stepper = doc.createElement('div'); stepper.id = STEPPER_BAR_ID;
shadowRoot.appendChild(stepper);
}
stepper.className = 'h-glass h-stepper-pill';
if (stepperPos.x !== null && stepperPos.y !== null) {
stepper.style.setProperty('left', `${stepperPos.x}px`, 'important');
stepper.style.setProperty('top', `${stepperPos.y}px`, 'important');
stepper.style.setProperty('bottom', 'auto', 'important');
stepper.style.setProperty('right', 'auto', 'important');
stepper.style.setProperty('transform', 'none', 'important');
stepper.style.setProperty('margin', '0', 'important');
} else {
stepper.style.setProperty('left', '50%', 'important');
stepper.style.setProperty('top', 'auto', 'important');
stepper.style.setProperty('bottom', '24px', 'important');
stepper.style.setProperty('right', 'auto', 'important');
stepper.style.setProperty('transform', 'translateX(-50%)', 'important');
stepper.style.setProperty('margin', '0', 'important');
}
const tag = previewElement.tagName.toLowerCase(), c = typeof previewElement.className === 'string' ? previewElement.className.trim().split(/\s+/)[0] : '';
const classStr = c && !c.startsWith('hider-') ? `.${c}` : '', idStr = previewElement.id && !previewElement.id.startsWith('hider-') ? `#${previewElement.id}` : '';
const fullText = `${tag}${idStr}${classStr}`;
const isLongText = fullText.length > 14;
stepper.innerHTML = `
<div id="hider-drag-handle" class="h-drag-dots" title="Drag Selector Bar">⠿</div>
<div class="h-stepper-row">
<button id="hider-step-up" class="h-btn-icon" title="Select Parent Element">▲</button>
<button id="hider-step-down" class="h-btn-icon" title="Select Child Element">▼</button>
</div>
<div class="h-tag-badge-box" title="${fullText}">
<span class="h-tag-badge-text ${isLongText ? 'is-animating' : ''}">${fullText}</span>
</div>
<div class="h-stepper-row" style="display:flex;flex-direction:column;gap:4px;width:100%;">
<button id="hider-step-confirm" class="h-btn-pill btn-blue" style="width:100%;">🙈 Hide</button>
<button id="hider-step-undo" class="h-btn-pill btn-gray" style="width:100%;">↩️ Undo</button>
<button id="hider-step-cancel" class="h-btn-pill btn-red" style="width:100%;">✖ Cancel</button>
</div>
`;
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-undo').onclick = e => { e.stopPropagation(); undoLastHide(); };
stepper.querySelector('#hider-step-cancel').onclick = e => {
e.stopPropagation();
clearSelectionState();
showToast('❌ Selection cancelled');
};
}
function confirmHideSelectedElement() {
if (!previewElement) return;
const el = previewElement;
el.classList.remove('hider-preview-highlight');
const sel = getExactSelector(el);
if (sel) {
if (/^(html|body)$/i.test(sel)) {
if (!confirm('⚠️ Warning: You are about to hide the entire page (html/body). Are you sure?')) {
clearSelectionState();
showToast('❌ Cancelled hiding entire page');
return;
}
}
lastHiddenSelector = sel;
if (currentScope === 'global') {
const rule = { id: 'rule_' + Date.now(), selector: sel, target: '*' };
CACHE.customRules.push(rule);
sv('hider_custom_rules_v4', CACHE.customRules);
showToast('🌐 Global hide rule added!');
} else if (currentScope === 'site') {
const key = 'hider_site_' + location.hostname;
const s = gv(key, []); if (!s.includes(sel)) { s.push(sel); sv(key, s); }
showToast('🌐 Site-wide hide rule added!');
} else { // link
const key = 'hider_link_' + cleanUrl();
const s = gv(key, []); if (!s.includes(sel)) { s.push(sel); sv(key, s); }
showToast('📄 Page-only hide rule added!');
}
}
requestUpdateStyles(); clearSelectionState();
}
// ---------- Undo Last Hide ----------
function undoLastHide() {
if (!lastHiddenSelector) {
showToast('⚠️ No hidden element to undo');
return;
}
let found = false;
for (let i = 0; i < CACHE.customRules.length; i++) {
if (CACHE.customRules[i].selector === lastHiddenSelector) {
CACHE.customRules.splice(i, 1);
sv('hider_custom_rules_v4', CACHE.customRules);
found = true;
break;
}
}
if (!found) {
const key = 'hider_site_' + location.hostname;
let rules = gv(key, []);
let idx = rules.indexOf(lastHiddenSelector);
if (idx !== -1) {
rules.splice(idx, 1);
sv(key, rules);
found = true;
} else {
const linkKey = 'hider_link_' + cleanUrl();
rules = gv(linkKey, []);
idx = rules.indexOf(lastHiddenSelector);
if (idx !== -1) {
rules.splice(idx, 1);
sv(linkKey, rules);
found = true;
}
}
}
if (found) {
requestUpdateStyles();
showToast('↩️ Undone last hide');
lastHiddenSelector = null;
} else {
showToast('⚠️ Selector not found in rules');
}
}
// ---------- Toast ----------
function showToast(msg) {
let targetRoot = shadowRoot;
if (!targetRoot) targetRoot = doc.body;
if (!targetRoot) return;
const existing = targetRoot.querySelector('#hider-toast');
if (existing) existing.remove();
const toast = doc.createElement('div');
toast.id = 'hider-toast';
toast.className = 'h-glass h-toast';
toast.textContent = msg;
Object.assign(toast.style, {
position: 'fixed',
bottom: '70px',
left: '50%',
transform: 'translateX(-50%) translateY(16px)',
opacity: '0',
padding: '6px 16px',
fontSize: '11px',
fontWeight: '700',
zIndex: '2147483647',
pointerEvents: 'none',
transition: 'all .3s ease',
textAlign: 'center',
borderRadius: '20px',
border: '1px solid rgba(56,189,248,.5)',
color: '#e0f2fe',
background: 'rgba(13,18,30,0.95)',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
boxShadow: '0 12px 30px rgba(0,0,0,0.6)',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif',
lineHeight: '1.3',
maxWidth: '90vw',
whiteSpace: 'nowrap'
});
targetRoot.appendChild(toast);
requestAnimationFrame(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateX(-50%) translateY(0)';
});
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(-50%) translateY(12px)';
setTimeout(() => {
if (toast.parentNode) toast.remove();
}, 400);
}, 2500);
}
// ---------- Clipboard ----------
function copyToClipboard(text) {
if (!text) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
showToast('📋 Copied!');
}).catch(() => {
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.left = '-9999px';
textarea.style.top = '-9999px';
textarea.style.width = '1px';
textarea.style.height = '1px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showToast('📋 Copied!');
} catch (err) {
showToast('❌ Copy failed');
}
document.body.removeChild(textarea);
}
// ---------- Custom Dropdown ----------
function setupCustomDropdown(container, initialValue, onChangeCallback) {
if (!container) return;
const trigger = container.querySelector('.h-custom-trigger'), textSpan = container.querySelector('.h-custom-value-text'), 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');
shadowRoot.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
if (!isOpen) container.classList.add('is-open');
};
}
// ---------- Domain Management ----------
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}! Managed in Control Panel.`); onDeny?.(); simulateAdWindowSuccess();
}
function executeAddAllowedDomain(parentDomain) {
if (parentDomain && !CACHE.allowedDomainsSet.has(parentDomain)) {
CACHE.allowedDomainsList.push(parentDomain); CACHE.allowedDomainsSet.add(parentDomain);
sv('hider_allowed_domains', CACHE.allowedDomainsList);
if (shadowBy('hider-panel')?.classList.contains('is-visible')) renderList();
}
}
function showFreezePrompt(url, triggerType, onConfirm, onDeny) {
if (!shadowRoot) return;
shadowBy('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:800;color:#38bdf8;font-size:11px!important;text-transform:uppercase!important;letter-spacing:0.5px">❄️ Navigation Intercepted</div>
<div style="font-size:11px!important;color:#f8fafc!important;font-weight:600">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 btn-green" style="flex:1">Allow Once</button><button id="hider-freeze-no" class="h-btn-pill btn-red" style="flex:1">Deny</button></div>
<button id="hider-freeze-allow-btn" class="h-btn-pill" style="width:100%;margin-top:4px;background:rgba(16,185,129,0.15)!important;border:1px solid rgba(16,185,129,0.4)!important;color:#34d399!important;">🟢 Always Allow (${parentDomain})</button>
<button id="hider-freeze-block-btn" class="h-btn-pill" style="width:100%;margin-top:4px;background:rgba(239,68,68,0.15)!important;border:1px solid rgba(239,68,68,0.4)!important;color:#fca5a5!important;">🚫 Always Block (${parentDomain})</button>
`;
shadowRoot.appendChild(promptEl); let selectedMem = CACHE.freezeMemory || 'ask';
setupCustomDropdown(promptEl.querySelector('#hider-modal-custom-dropdown'), selectedMem, v => { selectedMem = v; });
setTimeout(() => { promptEl.style.opacity = '1'; promptEl.style.transform = 'translateX(-50%) translateY(0)'; }, 10);
const saveMem = () => { if (selectedMem !== CACHE.freezeMemory) { CACHE.freezeMemory = selectedMem; sv('hider_freeze_memory', selectedMem); } };
promptEl.querySelector('#hider-freeze-yes').onclick = () => {
saveMem(); if (selectedMem === 'allow_all') disableFreezeMode(); else if (selectedMem === 'allow_same' && parentDomain !== 'unknown domain') executeAddAllowedDomain(parentDomain);
promptEl.remove(); onConfirm?.();
};
promptEl.querySelector('#hider-freeze-no').onclick = () => {
saveMem(); if (selectedMem === 'allow_all') disableFreezeMode();
logBlockedAttempt(url, triggerType + ' (User)'); promptEl.remove(); onDeny?.(); simulateAdWindowSuccess();
};
promptEl.querySelector('#hider-freeze-allow-btn').onclick = () => {
saveMem(); if (parentDomain !== 'unknown domain') executeAddAllowedDomain(parentDomain);
if (selectedMem === 'allow_all') disableFreezeMode();
promptEl.remove(); showToast(`🟢 Allowed ${parentDomain}!`); onConfirm?.();
};
promptEl.querySelector('#hider-freeze-block-btn').onclick = () => {
saveMem(); const skipConfirm = gv('hider_skip_block_confirm', false);
if (skipConfirm) { executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl); return; }
promptEl.innerHTML = `
<div style="font-weight:800;color:#ef4444;font-size:11px!important;text-transform:uppercase!important;">🚫 Confirm Domain Block</div>
<div style="font-size:11px!important;color:#f8fafc!important;text-align:center;margin:2px 0">Block all future requests to:<br><strong style="color:#38bdf8;font-size:12px!important">${parentDomain}</strong>?</div>
<label style="font-size:10px!important;color:#cbd5e1!important;display:flex;align-items:center;gap:4px;cursor:pointer;margin:4px 0;user-select:none"><input type="checkbox" id="hider-dont-ask-block" style="cursor:pointer;accent-color:#38bdf8;width:12px!important;height:12px!important"> 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 btn-red" 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)!important;color:#f1f5f9!important;border:1px solid rgba(255,255,255,0.15)!important;">Cancel</button></div>
`;
promptEl.querySelector('#hider-confirm-block-yes').onclick = () => {
if (promptEl.querySelector('#hider-dont-ask-block')?.checked) sv('hider_skip_block_confirm', true);
executeAddBlockDomain(parentDomain, triggerType, onDeny, promptEl);
};
promptEl.querySelector('#hider-confirm-block-no').onclick = () => { showFreezePrompt(url, triggerType, onConfirm, onDeny); };
};
}
// ---------- Enhanced Reveal ----------
function revealHiddenElements() {
if (!doc.body) return;
const targets = new Set();
const inlineHidden = doc.querySelectorAll([
'[style*="display:none"]',
'[style*="display: none"]',
'[style*="visibility:hidden"]',
'[style*="visibility: hidden"]',
'[style*="opacity:0"]',
'[style*="opacity: 0"]',
'[style*="filter:blur"]',
'[style*="filter: blur"]',
'[style*="backdrop-filter:blur"]',
'[style*="backdrop-filter: blur"]'
].join(','));
inlineHidden.forEach(el => targets.add(el));
const overlaySelector = [
'div[style*="position:fixed"]',
'div[style*="position: fixed"]',
'div[style*="position:absolute"]',
'div[style*="position: absolute"]',
'div[style*="z-index: 999"]',
'div[style*="z-index:999"]',
'div[style*="z-index: 9999"]',
'div[style*="z-index:9999"]',
'div[style*="z-index: 99999"]',
'div[style*="z-index:99999"]'
].join(',');
const overlayCandidates = doc.querySelectorAll(overlaySelector);
overlayCandidates.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width < 100 || rect.height < 100) return;
if (el.innerText.length > 200) return;
if (el.querySelector('article, main, p, h1, h2, h3, h4, h5, h6')) return;
targets.add(el);
});
doc.querySelectorAll([
'.modal', '.overlay', '.popup', '.lightbox', '.blocker',
'.paywall', '.gate', '.wall', '.restricted', '.locked',
'[data-overlay]', '[data-modal]', '[data-popup]', '[data-paywall]'
].join(',')).forEach(el => targets.add(el));
doc.querySelectorAll('*').forEach(el => {
const style = win.getComputedStyle(el);
if (style.filter && style.filter.includes('blur')) {
targets.add(el);
}
if (style.backdropFilter && style.backdropFilter.includes('blur')) {
targets.add(el);
}
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
targets.add(el);
}
});
let count = 0;
let blurCount = 0, hiddenCount = 0, overlayCount = 0;
const processBatch = (arr, start) => {
const end = Math.min(start + 50, arr.length);
for (let i = start; i < end; i++) {
const el = arr[i];
const style = win.getComputedStyle(el);
let changed = false;
if (style.filter && style.filter.includes('blur')) {
el.style.setProperty('filter', 'none', 'important');
el.style.setProperty('backdrop-filter', 'none', 'important');
el.style.setProperty('-webkit-backdrop-filter', 'none', 'important');
blurCount++;
changed = true;
}
if (style.display === 'none') {
el.style.setProperty('display', 'block', 'important');
hiddenCount++;
changed = true;
}
if (style.visibility === 'hidden') {
el.style.setProperty('visibility', 'visible', 'important');
hiddenCount++;
changed = true;
}
if (style.opacity === '0') {
el.style.setProperty('opacity', '1', 'important');
hiddenCount++;
changed = true;
}
if (style.pointerEvents === 'none') {
el.style.setProperty('pointer-events', 'auto', 'important');
}
if (style.userSelect === 'none') {
el.style.setProperty('user-select', 'text', 'important');
el.style.setProperty('-webkit-user-select', 'text', 'important');
}
if (style.position === 'fixed' || style.position === 'absolute') {
const z = parseInt(style.zIndex, 10);
if (z > 900) {
el.style.setProperty('position', 'static', 'important');
el.style.setProperty('z-index', 'auto', 'important');
overlayCount++;
changed = true;
}
}
if (changed) count++;
}
if (end < arr.length) {
requestAnimationFrame(() => processBatch(arr, end));
} else {
const parts = [];
if (hiddenCount) parts.push(`${hiddenCount} hidden`);
if (blurCount) parts.push(`${blurCount} blurred`);
if (overlayCount) parts.push(`${overlayCount} overlays`);
const summary = parts.length ? `👁️ Revealed: ${parts.join(', ')}` : '👁️ No hidden/blurred elements found.';
showToast(summary);
if (CACHE.autoScroll && featuresEnabled) forceEnableScroll();
}
};
const targetArray = Array.from(targets);
if (targetArray.length === 0) {
showToast('👁️ No hidden/blurred elements found.');
return;
}
requestAnimationFrame(() => processBatch(targetArray, 0));
}
// ---------- Time Skipper (Manual 30s) ----------
function skip30Seconds() {
let mediaCount = 0;
doc.querySelectorAll('video, audio').forEach(el => {
try {
if (!isNaN(el.duration) && isFinite(el.duration)) {
el.currentTime = Math.min(el.duration, el.currentTime + 30);
} else {
el.currentTime += 30;
}
mediaCount++;
} catch {}
});
let timerCount = 0;
const timerElements = doc.querySelectorAll(
'[class*="timer"], [class*="countdown"], [id*="timer"], [id*="countdown"], ' +
'[class*="time"], [id*="time"], [class*="remaining"], [id*="remaining"]'
);
timerElements.forEach(el => {
const text = el.textContent.trim();
if (/^\d{1,2}:\d{2}$/.test(text) || /^\d+\s*(s|sec|seconds?)$/i.test(text) || /^\d{1,2}:\d{2}\s*$/.test(text)) {
el.textContent = '0';
el.dispatchEvent(new Event('input', { bubbles: true }));
timerCount++;
}
});
doc.querySelectorAll('*:not([class*="timer"]):not([id*="timer"]):not([class*="countdown"]):not([id*="countdown"])')
.forEach(el => {
const text = el.textContent.trim();
if (/^(?:[0-9]{1,2}:[0-5][0-9]|[1-9][0-9]?s?)$/i.test(text)) {
el.textContent = '0';
el.dispatchEvent(new Event('input', { bubbles: true }));
timerCount++;
}
});
let clickCount = 0;
const skipBtns = doc.querySelectorAll('button, a[role="button"], [role="button"]');
skipBtns.forEach(btn => {
const text = btn.textContent.trim().toLowerCase();
if (text && (text.includes('skip') || text.includes('close') || text.includes('dismiss'))) {
const rect = btn.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
try { btn.click(); clickCount++; } catch {}
}
}
});
broadcastToFrames(window, { type: 'HIDER_SKIP_30' });
const msgParts = [];
if (mediaCount) msgParts.push(`${mediaCount} media`);
if (timerCount) msgParts.push(`${timerCount} timers`);
if (clickCount) msgParts.push(`${clickCount} buttons`);
const summary = msgParts.length ? `⏩ Skipped: ${msgParts.join(', ')}` : '⏩ Skipped +30s';
showToast(summary);
}
// ================================================================
// SMART AUTO TIME SKIPPER + VIDEO AD SKIPPER
// ================================================================
function isInsideMedia(el) {
let node = el;
while (node && node !== doc) {
if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') return true;
node = node.parentElement;
}
return false;
}
function checkAndSkipTimers() {
if (!CACHE.autoTimeSkipper || !featuresEnabled) return false;
let found = false;
const timerSelectors = [
'[class*="timer"]', '[class*="countdown"]', '[id*="timer"]', '[id*="countdown"]',
'[class*="time"]', '[id*="time"]', '[class*="remaining"]', '[id*="remaining"]',
'[data-timer]', '[data-countdown]', '[aria-label*="time"]', '[aria-label*="countdown"]'
];
const timerElements = doc.querySelectorAll(timerSelectors.join(','));
for (const el of timerElements) {
if (isInsideMedia(el)) continue;
const text = el.textContent.trim();
if (/^\d{1,2}:\d{2}$/.test(text) ||
/^\d+\s*(s|sec|seconds?)$/i.test(text) ||
/^\d{1,2}:\d{2}\s*$/.test(text) ||
/^\d+\s*second/i.test(text)) {
el.textContent = '0';
el.dispatchEvent(new Event('input', { bubbles: true }));
found = true;
}
}
const allElements = doc.querySelectorAll('*');
for (const el of allElements) {
if (isInsideMedia(el)) continue;
if (el.matches && el.matches(timerSelectors.join(','))) continue;
const text = el.textContent.trim();
if (/^\d{1,2}:\d{2}$/.test(text) || /^\d+\s*(s|sec|seconds?)$/i.test(text)) {
const parent = el.parentElement;
if (parent && (parent.matches && parent.matches('[class*="timer"], [class*="countdown"], [id*="timer"], [id*="countdown"]'))) {
el.textContent = '0';
el.dispatchEvent(new Event('input', { bubbles: true }));
found = true;
}
}
}
return found;
}
function skipVideoAds() {
if (!CACHE.autoTimeSkipper || !featuresEnabled) return false;
let skipped = false;
const adContainers = doc.querySelectorAll([
'.ad-container', '.video-ads', '.ad-player', '.ad-overlay',
'[id*="ad"]', '[id*="video-ad"]', '[id*="ad-container"]',
'.ytp-ad-overlay', '.ytp-ad-player-overlay', '.ytp-ad-image-overlay',
'.ytp-ad-text-overlay', '.ytp-ad-skip-button-container',
'.vjs-ad-overlay', '.vjs-ad-container',
'.jwplayer-ads', '.jwplayer-ads-container',
'.preroll-ads', '.ad-iframe', '.ad-banner'
].join(','));
for (const ad of adContainers) {
const skipBtns = ad.querySelectorAll('button, a[role="button"], [role="button"]');
for (const btn of skipBtns) {
const text = btn.textContent.trim().toLowerCase();
if (text && (text.includes('skip') || text.includes('close') || text.includes('dismiss') || text.includes('×'))) {
const rect = btn.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
try { btn.click(); skipped = true; } catch {}
}
}
}
const skipTexts = ad.querySelectorAll('*');
for (const el of skipTexts) {
const txt = el.textContent.trim().toLowerCase();
if (txt && (txt.includes('skip ad') || txt.includes('skip video') || txt.includes('skip in'))) {
const parentBtn = el.closest('button, a[role="button"], [role="button"]');
if (parentBtn) {
try { parentBtn.click(); skipped = true; } catch {}
}
}
}
}
const genericSkipBtns = doc.querySelectorAll('button, a[role="button"], [role="button"]');
for (const btn of genericSkipBtns) {
const text = btn.textContent.trim().toLowerCase();
if (text && (text.includes('skip ad') || text.includes('skip video') || text.includes('skip this ad'))) {
const rect = btn.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
try { btn.click(); skipped = true; } catch {}
}
}
}
return skipped;
}
function autoSkipLoop() {
let found = false;
if (checkAndSkipTimers()) found = true;
if (skipVideoAds()) found = true;
if (found) {
autoSkipEmptyCount = 0;
} else {
autoSkipEmptyCount++;
if (autoSkipEmptyCount >= AUTO_SKIP_MAX_EMPTY) {
stopAutoSkipMonitoring();
}
}
}
function startAutoSkipMonitoring() {
if (autoSkipInterval) {
clearInterval(autoSkipInterval);
autoSkipInterval = null;
}
autoSkipEmptyCount = 0;
autoSkipLoop();
autoSkipInterval = setInterval(autoSkipLoop, AUTO_SKIP_INTERVAL_MS);
}
function stopAutoSkipMonitoring() {
if (autoSkipInterval) {
clearInterval(autoSkipInterval);
autoSkipInterval = null;
}
autoSkipEmptyCount = 0;
if (observers.adSkipObserver) {
observers.adSkipObserver.disconnect();
observers.adSkipObserver = null;
}
}
// ========== AGGRESSIVE PAUSE ==========
function pauseAllVideos() {
function pauseMedia(el) {
try {
if (el.tagName === 'VIDEO' || el.tagName === 'AUDIO') {
el.pause();
el.currentTime = 0;
el.loop = false;
}
} catch(e) {}
}
function traverse(node) {
if (!node) return;
if (node.nodeType === Node.ELEMENT_NODE) {
pauseMedia(node);
if (node.shadowRoot) {
traverse(node.shadowRoot);
}
if (node.tagName === 'IFRAME' || node.tagName === 'FRAME') {
try {
if (node.contentDocument) {
traverse(node.contentDocument);
}
} catch(e) {}
}
if (node.childNodes) {
node.childNodes.forEach(child => traverse(child));
}
} else if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) {
if (node.childNodes) {
node.childNodes.forEach(child => traverse(child));
}
}
}
traverse(document);
document.querySelectorAll('video, audio').forEach(pauseMedia);
}
// ========== LINK EXTRACTOR ==========
function isDirectMedia(url) {
if (!url) return false;
const lower = url.toLowerCase();
if (lower.startsWith('blob:')) return true;
const mediaExts = [
'.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.tif', '.avif', '.heic', '.heif',
'.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv', '.ts', '.m4v', '.wmv', '.flv', '.m3u8',
'.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg', '.wma'
];
return mediaExts.some(ext => lower.includes(ext));
}
function getMediaType(url) {
if (!url) return null;
const lower = url.toLowerCase();
const imgExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.tif', '.avif', '.heic', '.heif'];
if (imgExts.some(ext => lower.includes(ext))) return 'image';
const vidExts = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv', '.ts', '.m4v', '.wmv', '.flv', '.m3u8'];
if (vidExts.some(ext => lower.includes(ext))) return 'video';
const audExts = ['.mp3', '.wav', '.flac', '.m4a', '.aac', '.ogg', '.wma'];
if (audExts.some(ext => lower.includes(ext))) return 'audio';
if (lower.startsWith('blob:')) return 'image';
return null;
}
function isMediaUrl(url) {
return getMediaType(url) !== null;
}
function getAllLinks() {
const linkMap = new Map();
function addLink(url, text, type = 'link') {
if (!url) return;
try {
const abs = new URL(url, location.href).href;
if (abs.startsWith('javascript:') || abs.startsWith('mailto:') || abs.startsWith('tel:') || abs === '#') return;
let finalType = type;
const mediaType = getMediaType(abs);
if (mediaType) finalType = 'media';
if (!linkMap.has(abs)) {
linkMap.set(abs, { url: abs, text: text || abs, type: finalType, mediaSubtype: mediaType });
} else {
const existing = linkMap.get(abs);
if (finalType === 'media' && existing.type === 'link') {
existing.type = 'media';
existing.mediaSubtype = mediaType;
}
if (text && text !== abs && (existing.text === existing.url || !existing.text)) {
existing.text = text;
}
}
} catch (e) { /* ignore */ }
}
function traverse(node) {
if (!node) return;
if (node.nodeType === Node.ELEMENT_NODE) {
const el = node;
if (el.tagName === 'VIDEO' || el.tagName === 'AUDIO') {
const src = el.getAttribute('src') || el.currentSrc;
if (src) {
const text = el.getAttribute('title') || el.getAttribute('aria-label') || el.getAttribute('alt') || el.textContent.trim() || src;
addLink(src, text, 'media');
}
el.querySelectorAll('source').forEach(source => {
const s = source.getAttribute('src');
if (s) {
const label = source.getAttribute('label') || source.getAttribute('title') || s;
addLink(s, label, 'media');
}
});
const poster = el.getAttribute('poster');
if (poster && isMediaUrl(poster)) {
addLink(poster, 'Poster image', 'media');
}
}
if (el.tagName === 'IMG') {
const src = el.getAttribute('src') || el.currentSrc;
if (src) {
const text = el.alt || el.title || el.getAttribute('aria-label') || src;
addLink(src, text, 'media');
}
const srcset = el.getAttribute('srcset');
if (srcset) {
const urls = srcset.split(',').map(s => s.trim().split(/\s+/)[0]).filter(Boolean);
urls.forEach(u => {
if (u) addLink(u, el.alt || el.title || u, 'media');
});
}
const lazyAttrs = ['data-src', 'data-original', 'data-lazy-src', 'data-srcset'];
lazyAttrs.forEach(attr => {
const val = el.getAttribute(attr);
if (val) {
if (attr === 'data-srcset') {
const urls = val.split(',').map(s => s.trim().split(/\s+/)[0]).filter(Boolean);
urls.forEach(u => addLink(u, el.alt || el.title || u, 'media'));
} else {
addLink(val, el.alt || el.title || val, 'media');
}
}
});
}
if (el.tagName === 'SOURCE' && el.closest('picture')) {
const s = el.getAttribute('src');
if (s) {
const label = el.getAttribute('label') || el.getAttribute('title') || s;
addLink(s, label, 'media');
}
const srcset = el.getAttribute('srcset');
if (srcset) {
const urls = srcset.split(',').map(s => s.trim().split(/\s+/)[0]).filter(Boolean);
urls.forEach(u => addLink(u, el.getAttribute('label') || u, 'media'));
}
}
const dataAttrs = ['src', 'url', 'link', 'video-src', 'audio-src', 'media-url', 'file', 'image', 'img', 'photo'];
for (const attr of dataAttrs) {
const val = el.getAttribute('data-' + attr);
if (val && isMediaUrl(val)) {
const text = el.textContent.trim() || el.title || el.getAttribute('aria-label') || val;
addLink(val, text, 'media');
}
}
if (el.matches('a[href], area[href]')) {
const href = el.getAttribute('href');
if (href && isMediaUrl(href)) {
const text = el.textContent.trim() || el.title || el.getAttribute('aria-label') || href;
addLink(href, text, 'media');
} else if (href) {
const text = el.textContent.trim() || el.title || el.getAttribute('aria-label') || href;
addLink(href, text, 'link');
}
}
const onclick = el.getAttribute('onclick');
if (onclick) {
const matches = onclick.match(/(?:location\.href|window\.open)\s*\(\s*['"]([^'"]+)['"]/gi);
if (matches) {
matches.forEach(m => {
const urlMatch = m.match(/['"]([^'"]+)['"]/);
if (urlMatch) {
const url = urlMatch[1];
if (isMediaUrl(url)) {
const text = el.textContent.trim() || el.title || el.getAttribute('aria-label') || url;
addLink(url, text, 'media');
}
}
});
}
}
if (el.matches('form[action]')) {
const action = el.getAttribute('action');
if (action && isMediaUrl(action)) {
const text = el.getAttribute('name') || el.id || 'Form action';
addLink(action, text, 'media');
}
}
if (el.tagName === 'IFRAME') {
const iframeSrc = el.getAttribute('src');
if (iframeSrc) {
const text = el.getAttribute('title') || el.getAttribute('aria-label') || 'iframe';
addLink(iframeSrc, text, 'link');
}
}
if (el.shadowRoot) {
traverseShadow(el.shadowRoot);
}
}
if (node.childNodes) {
for (const child of node.childNodes) {
traverse(child);
}
}
}
function traverseShadow(root) {
if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
for (const child of root.children) {
traverse(child);
}
}
}
traverse(document);
return Array.from(linkMap.values());
}
// ---------- Preview Modal ----------
function closePreviewModal(modalEl) {
const modal = modalEl || shadowBy('hider-link-preview-modal');
if (modal && modal.parentNode) {
modal.remove();
}
if (previewOutsideListener) {
document.removeEventListener('click', previewOutsideListener);
previewOutsideListener = null;
}
}
function showLinkPreview(url) {
const existing = shadowBy('hider-link-preview-modal');
if (existing) {
closePreviewModal(existing);
}
const mediaType = getMediaType(url);
const isDirect = isDirectMedia(url);
const modal = doc.createElement('div');
modal.id = 'hider-link-preview-modal';
modal.className = 'h-glass';
modal.style.cssText = `
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 2147483647 !important;
padding: 16px !important;
border-radius: 16px !important;
background: rgba(13, 18, 30, 0.96) !important;
backdrop-filter: blur(16px) !important;
border: 1px solid rgba(255,255,255,0.15) !important;
box-shadow: 0 24px 48px rgba(0,0,0,0.8) !important;
min-width: 280px !important;
max-width: 90vw !important;
max-height: 90vh !important;
pointer-events: auto !important;
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
color: #f8fafc !important;
overflow-y: auto !important;
`;
let typeIcon = '🔗';
if (mediaType === 'image') typeIcon = '🖼️';
else if (mediaType === 'video') typeIcon = '🎬';
else if (mediaType === 'audio') typeIcon = '🔊';
const title = doc.createElement('div');
title.style.cssText = 'font-weight:800; font-size:12px; color:#38bdf8; text-transform:uppercase; letter-spacing:0.5px;';
title.textContent = `${typeIcon} ${mediaType ? mediaType.toUpperCase() : 'Link'} Preview`;
modal.appendChild(title);
if (mediaType) {
const mediaWrapper = doc.createElement('div');
mediaWrapper.style.cssText = `
display:flex; justify-content:center; align-items:center;
background: rgba(0,0,0,0.4); border-radius:8px; padding:4px;
min-height: 80px; max-height: 55vh; overflow:hidden;
`;
if (mediaType === 'image') {
const img = doc.createElement('img');
img.src = url;
img.style.cssText = 'max-width:100%; max-height:55vh; object-fit:contain; border-radius:4px;';
img.onerror = () => {
img.alt = '❌ Failed to load image';
img.style.cssText += 'height:60px; width:auto;';
};
mediaWrapper.appendChild(img);
} else if (mediaType === 'video') {
if (isDirect) {
const video = doc.createElement('video');
video.src = url;
video.controls = true;
video.preload = 'metadata';
video.style.cssText = 'max-width:100%; max-height:55vh; border-radius:4px; background:#000;';
video.onerror = () => {
video.innerHTML = '<div style="padding:20px;color:#f87171;font-size:12px;">❌ Failed to load video</div>';
};
mediaWrapper.appendChild(video);
} else {
mediaWrapper.textContent = '🎬 Video – click "Open" to watch in new tab.';
mediaWrapper.style.cssText += 'padding:20px;color:#94a3b8;font-size:12px;';
}
} else if (mediaType === 'audio') {
const audio = doc.createElement('audio');
audio.src = url;
audio.controls = true;
audio.preload = 'metadata';
audio.style.cssText = 'width:100%;';
audio.onerror = () => {
audio.innerHTML = '<div style="padding:20px;color:#f87171;font-size:12px;">❌ Failed to load audio</div>';
};
mediaWrapper.appendChild(audio);
} else {
mediaWrapper.textContent = 'Media preview not available';
mediaWrapper.style.cssText += 'padding:20px;color:#94a3b8;font-size:12px;';
}
modal.appendChild(mediaWrapper);
} else {
const linkPreview = doc.createElement('div');
linkPreview.textContent = '🔗 Link Preview';
linkPreview.style.cssText = 'font-size:12px;color:#94a3b8;padding:10px;';
modal.appendChild(linkPreview);
}
const urlDisplay = doc.createElement('div');
urlDisplay.style.cssText = 'font-size:10px; word-break:break-all; background:rgba(0,0,0,0.3); padding:6px 8px; border-radius:6px; border:1px solid rgba(255,255,255,0.1); font-family:monospace; max-height:80px; overflow-y:auto;';
urlDisplay.textContent = url;
modal.appendChild(urlDisplay);
const btnGroup = doc.createElement('div');
btnGroup.style.cssText = 'display:flex; gap:6px; justify-content:flex-end; flex-wrap:wrap;';
const copyBtn = doc.createElement('button');
copyBtn.className = 'hider-btn-small btn-blue';
copyBtn.textContent = '📋 Copy';
copyBtn.onclick = () => {
copyToClipboard(url);
showToast('📋 URL copied!');
};
btnGroup.appendChild(copyBtn);
if (isDirect && (mediaType === 'image' || mediaType === 'video' || mediaType === 'audio')) {
const downloadBtn = doc.createElement('button');
downloadBtn.className = 'hider-btn-small btn-green';
downloadBtn.textContent = '⬇️ Download';
downloadBtn.onclick = function(e) {
e.stopPropagation();
let filename = url.split('/').pop().split(/[?#]/)[0] || 'media_file';
if (!filename.includes('.')) {
if (mediaType === 'image') filename += '.jpg';
else if (mediaType === 'video') filename += '.mp4';
else if (mediaType === 'audio') filename += '.mp3';
else filename += '.bin';
}
try {
GM_download({
url: url,
name: filename,
onerror: function(err) {
console.error('GM_download failed, trying fetch fallback', err);
fetch(url).then(res => {
if (!res.ok) throw new Error('Network error');
return res.blob();
}).then(blob => {
const a = document.createElement('a');
const objectUrl = URL.createObjectURL(blob);
a.href = objectUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(objectUrl);
showToast('⬇️ Downloaded using fallback');
}).catch(() => {
showToast('❌ Download failed – opening in new tab');
window.open(url, '_blank');
});
}
});
showToast(`⬇️ Downloading: ${filename}`);
} catch (err) {
console.error('GM_download error:', err);
showToast('❌ Download error – opening in new tab');
window.open(url, '_blank');
}
};
btnGroup.appendChild(downloadBtn);
}
const openBtn = doc.createElement('button');
openBtn.className = 'hider-btn-small btn-purple';
openBtn.textContent = '↗ Open';
openBtn.onclick = () => {
win.open(url, '_blank');
};
btnGroup.appendChild(openBtn);
const closeBtn = doc.createElement('button');
closeBtn.className = 'hider-btn-small btn-gray';
closeBtn.textContent = '✖ Close';
closeBtn.onclick = () => closePreviewModal(modal);
btnGroup.appendChild(closeBtn);
modal.appendChild(btnGroup);
shadowRoot.appendChild(modal);
const closeModalHandler = (e) => {
if (!modal.parentNode) {
document.removeEventListener('click', closeModalHandler);
return;
}
const path = e.composedPath ? e.composedPath() : [];
if (path.some(el => el === modal || (el && el.contains && el.contains(modal)))) {
return;
}
if (path.some(el => el.id === UI_HOST_ID || el.id === STEPPER_BAR_ID)) {
return;
}
closePreviewModal(modal);
};
if (previewOutsideListener) {
document.removeEventListener('click', previewOutsideListener);
}
previewOutsideListener = closeModalHandler;
setTimeout(() => {
document.addEventListener('click', previewOutsideListener);
}, 10);
}
// ----- Grid item for Media Mode -----
function buildGridItem(link) {
const div = doc.createElement('div');
div.className = 'hider-grid-item';
div.style.cssText = `
position: relative;
background: rgba(255,255,255,0.04);
border-radius: 8px;
overflow: hidden;
aspect-ratio: 1 / 1;
cursor: pointer;
border: 1px solid rgba(255,255,255,0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
`;
div.onmouseover = () => { div.style.transform = 'scale(1.03)'; div.style.boxShadow = '0 4px 12px rgba(0,0,0,0.6)'; };
div.onmouseout = () => { div.style.transform = 'scale(1)'; div.style.boxShadow = 'none'; };
const mediaType = link.mediaSubtype || getMediaType(link.url);
const isDirect = isDirectMedia(link.url);
let icon = '📎';
if (mediaType === 'image') icon = '🖼️';
else if (mediaType === 'video') icon = '🎬';
else if (mediaType === 'audio') icon = '🔊';
let thumbnail = '';
if (mediaType === 'image') {
thumbnail = `<img src="${link.url}" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';">`;
} else if (mediaType === 'video' && isDirect) {
thumbnail = `<video src="${link.url}" muted preload="metadata" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';"></video>`;
} else {
thumbnail = `<div style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:48px;color:#94a3b8;background:rgba(0,0,0,0.3);">${icon}</div>`;
}
div.innerHTML = thumbnail;
const overlay = doc.createElement('div');
overlay.style.cssText = `
position: absolute;
bottom: 4px;
left: 4px;
background: rgba(0,0,0,0.7);
padding: 2px 6px;
border-radius: 4px;
font-size: 8px;
color: #e0f2fe;
backdrop-filter: blur(4px);
pointer-events: none;
font-weight: 600;
letter-spacing: 0.3px;
text-transform: uppercase;
`;
overlay.textContent = mediaType || 'media';
div.appendChild(overlay);
if (isDirect && (mediaType === 'image' || mediaType === 'video' || mediaType === 'audio')) {
const downloadBtn = doc.createElement('button');
downloadBtn.className = 'hider-btn-small';
downloadBtn.textContent = '⬇️';
downloadBtn.title = 'Download this media';
downloadBtn.style.cssText = `
position: absolute;
bottom: 4px;
right: 4px;
background: rgba(0,0,0,0.7) !important;
backdrop-filter: blur(4px);
border: none;
border-radius: 4px;
color: #fff;
padding: 2px 6px;
font-size: 10px;
cursor: pointer;
z-index: 2;
pointer-events: auto;
transition: background 0.2s;
`;
downloadBtn.onmouseover = () => { downloadBtn.style.background = 'rgba(56,189,248,0.8) !important'; };
downloadBtn.onmouseout = () => { downloadBtn.style.background = 'rgba(0,0,0,0.7) !important'; };
downloadBtn.addEventListener('click', function(e) {
e.stopPropagation();
const url = link.url;
if (!url) {
showToast('❌ No media URL');
return;
}
let filename = url.split('/').pop().split(/[?#]/)[0] || 'media_file';
if (!filename.includes('.')) {
if (mediaType === 'image') filename += '.jpg';
else if (mediaType === 'video') filename += '.mp4';
else if (mediaType === 'audio') filename += '.mp3';
else filename += '.bin';
}
try {
GM_download({
url: url,
name: filename,
onerror: function(err) {
console.error('GM_download failed, trying fetch fallback', err);
fetch(url).then(res => {
if (!res.ok) throw new Error('Network error');
return res.blob();
}).then(blob => {
const a = document.createElement('a');
const objectUrl = URL.createObjectURL(blob);
a.href = objectUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(objectUrl);
showToast('⬇️ Downloaded using fallback');
}).catch(() => {
showToast('❌ Download failed – opening in new tab');
window.open(url, '_blank');
});
}
});
showToast(`⬇️ Downloading: ${filename}`);
} catch (err) {
console.error('GM_download error:', err);
showToast('❌ Download error – opening in new tab');
window.open(url, '_blank');
}
});
div.appendChild(downloadBtn);
}
div.addEventListener('click', (e) => {
if (e.target.closest('button')) return;
showLinkPreview(link.url);
});
return div;
}
// ----- List item for normal mode -----
function buildListItem(link, filterText, hideInternal, mediaOnly) {
const currentHost = location.hostname;
if (hideInternal) {
try {
const urlObj = new URL(link.url);
if (urlObj.hostname === currentHost) return null;
} catch { /* ignore */ }
}
if (mediaOnly && link.type !== 'media') return null;
if (filterText) {
const lower = filterText.toLowerCase();
if (!link.text.toLowerCase().includes(lower) && !link.url.toLowerCase().includes(lower)) {
return null;
}
}
const div = doc.createElement('div');
div.className = 'list-item';
div.style.cssText = 'display:flex; justify-content:space-between; align-items:center; gap:4px;';
let icon = '🔗';
if (link.type === 'media') {
const subtype = link.mediaSubtype || getMediaType(link.url);
if (subtype === 'image') icon = '🖼️';
else if (subtype === 'video') icon = '🎬';
else if (subtype === 'audio') icon = '🔊';
else icon = '🎬';
}
let displayText = link.text;
if (link.type === 'media') {
displayText = icon + ' ' + displayText;
}
if (linkDisplayMode === 'url') {
displayText = link.url + (link.type === 'media' ? ' ' + icon : '');
}
const textSpan = doc.createElement('span');
textSpan.className = 'rule-text';
textSpan.textContent = displayText;
textSpan.title = link.url;
textSpan.style.cssText = 'flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;';
const btnGroup = doc.createElement('div');
btnGroup.style.cssText = 'display:flex; gap:2px; flex-shrink:0;';
const previewBtn = doc.createElement('button');
previewBtn.className = 'hider-btn-small btn-purple';
previewBtn.textContent = '🔍';
previewBtn.title = 'Preview full link (media preview if applicable)';
previewBtn.onclick = (e) => {
e.stopPropagation();
showLinkPreview(link.url);
};
btnGroup.appendChild(previewBtn);
const copyBtn = doc.createElement('button');
copyBtn.className = 'hider-btn-small btn-blue';
copyBtn.textContent = '📋';
copyBtn.title = 'Copy URL';
copyBtn.onclick = (e) => {
e.stopPropagation();
copyToClipboard(link.url);
showToast('📋 Copied!');
};
btnGroup.appendChild(copyBtn);
const openBtn = doc.createElement('button');
openBtn.className = 'hider-btn-small btn-green';
openBtn.textContent = '↗';
openBtn.title = 'Open in new tab';
openBtn.onclick = (e) => {
e.stopPropagation();
win.open(link.url, '_blank');
};
btnGroup.appendChild(openBtn);
div.appendChild(textSpan);
div.appendChild(btnGroup);
return div;
}
function populateLinkList() {
const listContainer = shadowBy('hider-link-list');
if (!listContainer) return;
const searchInput = shadowBy('hider-link-search');
const hideInternalCheck = shadowBy('hider-link-hide-internal');
const mediaModeCheck = shadowBy('hider-link-media-mode');
const filterText = searchInput ? searchInput.value.toLowerCase() : '';
const hideInternal = hideInternalCheck ? hideInternalCheck.checked : false;
const mediaMode = mediaModeCheck ? mediaModeCheck.checked : false;
const links = getAllLinks();
const title = shadowBy('link-panel-title');
if (title) title.textContent = `🔗 Extracted Links (${links.length})`;
listContainer.innerHTML = '';
let displayed = 0;
const frag = doc.createDocumentFragment();
if (mediaMode) {
const grid = doc.createElement('div');
grid.style.cssText = 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;';
links.forEach(link => {
if (link.type !== 'media') return;
if (filterText) {
const lower = filterText;
if (!link.text.toLowerCase().includes(lower) && !link.url.toLowerCase().includes(lower)) return;
}
if (hideInternal) {
try {
const urlObj = new URL(link.url);
if (urlObj.hostname === location.hostname) return;
} catch {}
}
const item = buildGridItem(link);
grid.appendChild(item);
displayed++;
});
if (displayed === 0) {
const empty = doc.createElement('div');
empty.style.cssText = 'font-size:9px!important;color:#94a3b8!important;padding:8px!important;text-align:center!important;border:1px dashed rgba(255,255,255,0.12)!important;border-radius:6px!important;grid-column:1/4;';
empty.textContent = 'No media items.';
grid.appendChild(empty);
}
frag.appendChild(grid);
} else {
links.forEach(link => {
const item = buildListItem(link, filterText, hideInternal, false);
if (item) {
frag.appendChild(item);
displayed++;
}
});
if (displayed === 0) {
const empty = doc.createElement('div');
empty.style.cssText = 'font-size:9px!important;color:#94a3b8!important;padding:8px!important;text-align:center!important;border:1px dashed rgba(255,255,255,0.12)!important;border-radius:6px!important;';
empty.textContent = 'No matching links.';
frag.appendChild(empty);
}
}
listContainer.appendChild(frag);
if (title) title.textContent = `🔗 Extracted Links (${displayed} shown)`;
}
// --- Link panel with Refresh button ---
function createLinkPanel() {
if (linkPanelEl) return linkPanelEl;
const panel = doc.createElement('div');
panel.id = 'hider-link-panel';
panel.className = 'h-glass';
panel.style.cssText = `
position: fixed !important;
right: 14px !important;
top: 55px !important;
z-index: 2147483645 !important;
width: min(310px, 90vw) !important;
max-height: min(580px, 82vh) !important;
border-radius: 16px !important;
padding: 10px !important;
display: none;
flex-direction: column !important;
overflow-y: auto !important;
overscroll-behavior: contain !important;
touch-action: pan-y !important;
pointer-events: auto !important;
gap: 6px !important;
opacity: 0;
transform: scale(0.96) translateY(-8px);
transition: opacity 0.2s ease, transform 0.2s ease !important;
`;
const header = doc.createElement('div');
header.className = 'panel-header';
header.style.cssText = 'display:flex; justify-content:space-between; align-items:center;';
const title = doc.createElement('span');
title.id = 'link-panel-title';
title.textContent = '🔗 Extracted Links';
const headerRight = doc.createElement('div');
headerRight.style.cssText = 'display:flex; gap:4px;';
const modeBtn = doc.createElement('button');
modeBtn.className = 'hider-btn-small btn-gray';
modeBtn.textContent = '📝';
modeBtn.title = 'Toggle display: text / URL';
modeBtn.onclick = (e) => {
e.stopPropagation();
linkDisplayMode = linkDisplayMode === 'text' ? 'url' : 'text';
modeBtn.textContent = linkDisplayMode === 'text' ? '📝' : '🔗';
populateLinkList();
};
headerRight.appendChild(modeBtn);
const refreshBtn = doc.createElement('button');
refreshBtn.className = 'hider-btn-small btn-purple';
refreshBtn.textContent = '🔄';
refreshBtn.title = 'Refresh links & pause media';
refreshBtn.onclick = (e) => {
e.stopPropagation();
pauseAllVideos();
populateLinkList();
showToast('🔄 Links refreshed');
};
headerRight.appendChild(refreshBtn);
const closeBtn = doc.createElement('button');
closeBtn.className = 'hider-btn-small';
closeBtn.style.cssText = 'background:rgba(255,255,255,0.12)!important; padding:2px 6px!important;';
closeBtn.textContent = '✖';
closeBtn.onclick = () => { toggleLinkPanel(); };
headerRight.appendChild(closeBtn);
header.appendChild(title);
header.appendChild(headerRight);
panel.appendChild(header);
const searchWrapper = doc.createElement('div');
searchWrapper.style.cssText = 'display:flex; gap:4px; align-items:center; margin-bottom:2px; flex-wrap:wrap;';
const searchInput = doc.createElement('input');
searchInput.id = 'hider-link-search';
searchInput.className = 'h-select';
searchInput.placeholder = '🔍 Filter links...';
searchInput.style.cssText = 'flex:1; height:26px; font-size:10px; min-width:80px;';
searchInput.oninput = () => populateLinkList();
searchWrapper.appendChild(searchInput);
const hideCheck = doc.createElement('label');
hideCheck.style.cssText = 'font-size:9px; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer; user-select:none; white-space:nowrap;';
const checkBox = doc.createElement('input');
checkBox.id = 'hider-link-hide-internal';
checkBox.type = 'checkbox';
checkBox.style.cssText = 'accent-color:#38bdf8; width:12px; height:12px; cursor:pointer;';
checkBox.onchange = () => populateLinkList();
hideCheck.appendChild(checkBox);
hideCheck.appendChild(doc.createTextNode('Hide internal'));
searchWrapper.appendChild(hideCheck);
const mediaCheck = doc.createElement('label');
mediaCheck.style.cssText = 'font-size:9px; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer; user-select:none; white-space:nowrap;';
const mediaBox = doc.createElement('input');
mediaBox.id = 'hider-link-media-mode';
mediaBox.type = 'checkbox';
mediaBox.style.cssText = 'accent-color:#f59e0b; width:12px; height:12px; cursor:pointer;';
mediaBox.onchange = () => populateLinkList();
mediaCheck.appendChild(mediaBox);
mediaCheck.appendChild(doc.createTextNode('📺 Media mode'));
searchWrapper.appendChild(mediaCheck);
panel.appendChild(searchWrapper);
const listContainer = doc.createElement('div');
listContainer.id = 'hider-link-list';
listContainer.style.cssText = 'display:flex; flex-direction:column; gap:4px; overflow-y:auto; flex:1; overscroll-behavior:contain;';
panel.appendChild(listContainer);
shadowRoot.appendChild(panel);
linkPanelEl = panel;
return panel;
}
function toggleLinkPanel() {
if (!shadowRoot) return;
if (!linkPanelEl || !linkPanelEl.parentNode) {
linkPanelEl = createLinkPanel();
}
const isVisible = linkPanelEl.classList.contains('is-visible');
if (isVisible) {
linkPanelEl.classList.remove('is-visible');
setTimeout(() => linkPanelEl.style.display = 'none', 300);
} else {
pauseAllVideos();
populateLinkList();
linkPanelEl.style.display = 'flex';
void linkPanelEl.offsetWidth;
linkPanelEl.classList.add('is-visible');
}
}
// ================================================================
// RULE OVERLAY (ADD / EDIT) – ENLARGED HEIGHT, TARGET FIELD
// ================================================================
let currentRuleOverlayMode = 'add';
let currentRuleEditInfo = null;
function showRuleOverlay(options) {
const existing = shadowRoot?.getElementById('hider-rule-overlay');
if (existing) existing.remove();
const mode = options.mode || 'add';
const initialSelector = options.selector || '';
const initialScope = options.scope || 'global';
const initialTarget = options.target || '';
currentRuleOverlayMode = mode;
currentRuleEditInfo = options.editInfo || null;
const overlay = doc.createElement('div');
overlay.id = 'hider-rule-overlay';
overlay.className = 'h-glass';
overlay.style.cssText = `
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 2147483647 !important;
padding: 22px !important;
border-radius: 16px !important;
min-width: 360px !important;
max-width: 92vw !important;
width: 420px !important;
min-height: 280px !important;
display: flex !important;
flex-direction: column !important;
gap: 14px !important;
pointer-events: auto !important;
background: rgba(13,18,30,0.96) !important;
backdrop-filter: blur(16px) !important;
border: 1px solid rgba(255,255,255,0.15) !important;
box-shadow: 0 24px 48px rgba(0,0,0,0.8) !important;
`;
const title = doc.createElement('div');
title.style.cssText = 'font-weight:800;font-size:14px;color:#38bdf8;margin-bottom:4px;';
title.textContent = mode === 'add' ? '➕ Add Rule' : '✏️ Edit Rule';
overlay.appendChild(title);
const scopeContainer = doc.createElement('div');
scopeContainer.className = 'h-custom-select';
scopeContainer.id = 'rule-scope-select';
scopeContainer.innerHTML = `
<div class="h-custom-trigger"><span class="h-custom-value-text">Global</span><span class="h-custom-arrow">▼</span></div>
<div class="h-custom-options">
<div class="h-custom-opt" data-val="global">🌍 Global</div>
<div class="h-custom-opt" data-val="site">🌐 Site‑Wide</div>
<div class="h-custom-opt" data-val="link">📄 Page‑Only</div>
</div>
`;
overlay.appendChild(scopeContainer);
const targetContainer = doc.createElement('div');
targetContainer.id = 'rule-target-container';
targetContainer.style.cssText = 'display:none; flex-direction:column; gap:4px;';
const targetLabel = doc.createElement('div');
targetLabel.style.cssText = 'font-size:10px;color:#94a3b8;font-weight:600;text-transform:uppercase;letter-spacing:0.3px;';
targetLabel.textContent = '🎯 Target (domain or URL)';
targetContainer.appendChild(targetLabel);
const targetInput = doc.createElement('input');
targetInput.id = 'rule-target-input';
targetInput.className = 'h-select';
targetInput.placeholder = 'example.com or /path';
targetInput.value = initialTarget || '';
targetInput.style.cssText = 'width:100%;height:34px;font-size:11px;padding:4px 10px;';
targetContainer.appendChild(targetInput);
overlay.appendChild(targetContainer);
const input = doc.createElement('input');
input.id = 'rule-selector-input';
input.className = 'h-select';
input.placeholder = 'CSS Selector (e.g. .ad, #banner)';
input.value = initialSelector;
input.style.cssText = 'width:100%;height:34px;font-size:11px;padding:4px 10px;';
overlay.appendChild(input);
const btnGroup = doc.createElement('div');
btnGroup.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;margin-top:6px;';
const saveBtn = doc.createElement('button');
saveBtn.className = 'hider-btn-small btn-green';
saveBtn.textContent = '💾 Save';
saveBtn.style.cssText = 'height:32px;padding:0 16px;font-size:11px;';
const cancelBtn = doc.createElement('button');
cancelBtn.className = 'hider-btn-small btn-gray';
cancelBtn.textContent = '✖ Cancel';
cancelBtn.style.cssText = 'height:32px;padding:0 16px;font-size:11px;';
btnGroup.appendChild(saveBtn);
btnGroup.appendChild(cancelBtn);
overlay.appendChild(btnGroup);
shadowRoot.appendChild(overlay);
let selectedScope = initialScope;
const trigger = scopeContainer.querySelector('.h-custom-trigger');
const valueSpan = trigger.querySelector('.h-custom-value-text');
const optionsList = scopeContainer.querySelectorAll('.h-custom-opt');
const updateScopeLabel = (val) => {
const labels = { global: '🌍 Global', site: '🌐 Site‑Wide', link: '📄 Page‑Only' };
valueSpan.textContent = labels[val] || 'Global';
optionsList.forEach(opt => opt.classList.toggle('is-selected', opt.dataset.val === val));
selectedScope = val;
if (val === 'global') {
targetContainer.style.display = 'none';
} else {
targetContainer.style.display = 'flex';
if (!targetInput.value) {
if (val === 'site') targetInput.value = location.hostname;
else if (val === 'link') targetInput.value = cleanUrl();
}
}
};
optionsList.forEach(opt => {
opt.addEventListener('click', (e) => {
e.stopPropagation();
updateScopeLabel(opt.dataset.val);
scopeContainer.classList.remove('is-open');
});
});
trigger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = scopeContainer.classList.contains('is-open');
shadowRoot.querySelectorAll('.h-custom-select').forEach(c => c.classList.remove('is-open'));
if (!isOpen) scopeContainer.classList.add('is-open');
});
document.addEventListener('click', function closeDropdown(e) {
if (scopeContainer && !scopeContainer.contains(e.target)) {
scopeContainer.classList.remove('is-open');
document.removeEventListener('click', closeDropdown);
}
});
updateScopeLabel(initialScope);
const saveRule = () => {
const selector = input.value.trim();
if (!selector) {
showToast('⚠️ Please enter a CSS selector');
return;
}
let newScope = selectedScope;
let targetValue = targetInput.value.trim();
if (newScope === 'global') {
targetValue = '*';
} else {
if (!targetValue) {
showToast(`⚠️ Please enter a target ${newScope === 'site' ? 'domain' : 'URL'}`);
return;
}
if (newScope === 'site') {
targetValue = cleanDomain(targetValue);
if (!targetValue) {
showToast('⚠️ Invalid domain');
return;
}
} else if (newScope === 'link') {
try {
const abs = resolveUrl(targetValue);
if (!abs) throw new Error();
targetValue = abs;
} catch {
showToast('⚠️ Invalid URL');
return;
}
}
}
if (mode === 'edit' && currentRuleEditInfo) {
const info = currentRuleEditInfo;
if (info.listType === 'custom') {
CACHE.customRules = CACHE.customRules.filter(r => r.id !== info.id);
sv('hider_custom_rules_v4', CACHE.customRules);
} else if (info.listType === 'site') {
const key = 'hider_site_' + location.hostname;
let rules = gv(key, []);
if (info.index >= 0 && info.index < rules.length) {
rules.splice(info.index, 1);
sv(key, rules);
}
} else if (info.listType === 'link') {
const key = 'hider_link_' + cleanUrl();
let rules = gv(key, []);
if (info.index >= 0 && info.index < rules.length) {
rules.splice(info.index, 1);
sv(key, rules);
}
}
}
if (newScope === 'global') {
const newRule = { id: 'rule_' + Date.now() + '_' + Math.random().toString(36).substr(2,4), selector: selector, target: '*' };
CACHE.customRules.push(newRule);
sv('hider_custom_rules_v4', CACHE.customRules);
showToast(`🌍 Added global rule: ${selector}`);
} else if (newScope === 'site') {
const key = 'hider_site_' + targetValue;
let rules = gv(key, []);
if (!rules.includes(selector)) {
rules.push(selector);
sv(key, rules);
showToast(`🌐 Added site-wide rule for ${targetValue}: ${selector}`);
} else {
showToast('⚠️ Rule already exists for this site');
}
} else if (newScope === 'link') {
const key = 'hider_link_' + targetValue;
let rules = gv(key, []);
if (!rules.includes(selector)) {
rules.push(selector);
sv(key, rules);
showToast(`📄 Added page-only rule for ${targetValue}: ${selector}`);
} else {
showToast('⚠️ Rule already exists for this page');
}
}
overlay.remove();
requestUpdateStyles();
renderList();
currentRuleEditInfo = null;
};
const cancel = () => {
overlay.remove();
currentRuleEditInfo = null;
};
saveBtn.addEventListener('click', saveRule);
cancelBtn.addEventListener('click', cancel);
}
// ---------- UI Creation ----------
function createShadowUI() {
if (!isTop) return;
const parentEl = doc.documentElement || doc.body;
if (!parentEl) return;
let hostEl = doc.getElementById(UI_HOST_ID);
if (!hostEl) {
hostEl = doc.createElement('div'); hostEl.id = UI_HOST_ID;
hostEl.style.cssText = 'position:fixed!important;top:0!important;left:0!important;width:0!important;height:0!important;z-index:2147483647!important;pointer-events:none!important;display:block!important;visibility:visible!important;opacity:1!important;overflow:visible!important;';
parentEl.appendChild(hostEl);
}
shadowRoot = hostEl.shadowRoot || hostEl.attachShadow({ mode: 'open' });
const style = doc.createElement('style');
style.textContent = `
* { box-sizing: border-box !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif !important; line-height: 1.3 !important; }
.h-glass {
background: rgba(13, 18, 30, 0.92) !important;
backdrop-filter: blur(12px) !important;
-webkit-backdrop-filter: blur(12px) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
color: #f8fafc !important;
border-radius: 14px !important;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6) !important;
}
::-webkit-scrollbar { width: 4px !important; height: 4px !important; }
::-webkit-scrollbar-track { background: transparent !important; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2) !important; border-radius: 8px !important; }
#hider-panel {
position: fixed !important;
right: 14px !important;
top: 55px !important;
z-index: 2147483645 !important;
width: min(480px, 92vw) !important;
max-height: min(700px, 85vh) !important;
border-radius: 16px !important;
padding: 0 !important;
display: none;
flex-direction: column !important;
overflow: hidden !important;
pointer-events: auto !important;
opacity: 0;
transform: scale(0.96) translateY(-8px);
transition: opacity 0.2s ease, transform 0.2s ease !important;
}
#hider-panel.is-visible {
opacity: 1 !important;
transform: scale(1) translateY(0) !important;
display: flex !important;
}
#hider-link-panel.is-visible {
opacity: 1 !important;
transform: scale(1) translateY(0) !important;
display: flex !important;
}
.panel-header {
padding: 8px 12px;
border-bottom: 1px solid rgba(255,255,255,0.08);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.panel-header .title {
font-weight: 800;
font-size: 12px;
color: #f8fafc;
}
.panel-header .close-btn {
background: rgba(255,255,255,0.08);
border: none;
border-radius: 6px;
color: #cbd5e1;
padding: 2px 8px;
cursor: pointer;
font-size: 11px;
}
.panel-header .close-btn:hover { background: rgba(255,255,255,0.16); }
.panel-body {
display: flex;
flex: 1;
overflow: hidden;
}
.panel-sidebar {
flex: 0 0 110px;
background: rgba(0,0,0,0.2);
padding: 6px 4px;
overflow-y: auto;
border-right: 1px solid rgba(255,255,255,0.06);
}
.panel-sidebar .tab-btn {
display: block;
width: 100%;
text-align: left;
background: transparent;
border: none;
border-radius: 6px;
padding: 5px 8px;
color: #94a3b8;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.panel-sidebar .tab-btn:hover {
background: rgba(255,255,255,0.08);
color: #e2e8f0;
}
.panel-sidebar .tab-btn.active {
background: rgba(56,189,248,0.15);
color: #38bdf8;
border-left: 2px solid #38bdf8;
}
.panel-content {
flex: 1;
padding: 8px 10px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 6px;
}
.panel-content .tab-content {
display: none;
flex-direction: column;
gap: 6px;
}
.panel-content .tab-content.active {
display: flex;
}
.h-dock {
position: fixed !important; right: 10px !important; top: 35%; z-index: 2147483643 !important;
display: flex !important; flex-direction: column !important; align-items: center !important; gap: 5px !important;
padding: 5px !important; pointer-events: auto !important; transition: transform 0.3s ease, opacity 0.3s ease !important;
user-select: none !important; border-radius: 20px !important;
}
.h-dock.is-collapsed { transform: translateX(65%) !important; opacity: 0.5 !important; }
.h-dock.manual-hidden { transform: translateX(65%) !important; opacity: 0.5 !important; }
.h-dock.is-collapsed:not(.manual-hidden):hover, .h-dock.expanded { transform: translateX(0) !important; opacity: 1 !important; }
.h-dock-main {
width: 34px !important; height: 34px !important; font-size: 16px !important;
background: linear-gradient(135deg, rgba(56, 189, 248, 0.3), rgba(37, 99, 235, 0.4)) !important;
border: 1px solid rgba(56, 189, 248, 0.4) !important; cursor: grab !important; border-radius: 50% !important;
flex-shrink: 0 !important; min-height: 34px !important; min-width: 34px !important; display: flex !important; align-items: center !important; justify-content: center !important;
}
.h-dock-main.expanded {
background: linear-gradient(135deg, #0ea5e9, #2563eb) !important; color: #fff !important;
box-shadow: 0 0 12px rgba(14, 165, 233, 0.5) !important; border-color: transparent !important;
}
.h-dock-menu { display: flex; flex-direction: column !important; gap: 5px !important; overflow: hidden !important; max-height: 0; opacity: 0; transition: max-height 0.3s ease, opacity 0.2s ease !important; }
.h-dock-menu.is-open { max-height: 450px; opacity: 1; }
.h-dock-btn {
width: 32px !important; height: 32px !important; border-radius: 10px !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important; background: rgba(255, 255, 255, 0.06) !important;
color: #cbd5e1 !important; font-size: 11px !important; font-weight: 700 !important; display: flex !important;
flex-direction: column !important; align-items: center !important; justify-content: center !important;
cursor: pointer !important; transition: all 0.2s ease !important; margin: 0 !important; padding: 0 !important;
flex-shrink: 0 !important; min-height: 32px !important; min-width: 32px !important;
}
.h-dock-btn:hover { background: rgba(255, 255, 255, 0.18) !important; color: #fff !important; transform: scale(1.05) !important; }
.h-dock-btn.active { background: linear-gradient(135deg, #0ea5e9, #2563eb) !important; color: #fff !important; border-color: transparent !important; }
.h-dock-btn.scope-link { background: linear-gradient(135deg, #10b981, #059669) !important; color: #fff !important; border-color: transparent !important; }
.h-dock-btn.scope-global { background: linear-gradient(135deg, #a855f7, #7e22ce) !important; color: #fff !important; border-color: transparent !important; }
.h-dock-btn.is-frozen { background: linear-gradient(135deg, #8b5cf6, #6d28d9) !important; color: #fff !important; border-color: transparent !important; }
.h-dock-btn span { font-size: 7px !important; font-weight: 800 !important; margin-top: 1px !important; letter-spacing: 0.3px !important; text-transform: uppercase !important; }
.btn-blue { background: linear-gradient(135deg, #0ea5e9, #0284c7) !important; color: #fff !important; border: 1px solid rgba(255,255,255,0.2) !important; }
.btn-green { background: linear-gradient(135deg, #10b981, #059669) !important; color: #fff !important; border: 1px solid rgba(255,255,255,0.2) !important; }
.btn-red { background: linear-gradient(135deg, #f43f5e, #e11d48) !important; color: #fff !important; border: 1px solid rgba(255,255,255,0.2) !important; }
.btn-purple { background: linear-gradient(135deg, #a855f7, #7e22ce) !important; color: #fff !important; border: 1px solid rgba(255,255,255,0.2) !important; }
.btn-gray { background: linear-gradient(135deg, #475569, #334155) !important; color: #fff !important; border: 1px solid rgba(255,255,255,0.15) !important; }
.h-stepper-pill {
position: fixed !important; bottom: 20px !important; left: 50% !important; transform: translateX(-50%) !important;
z-index: 2147483644 !important; padding: 6px 8px !important; display: flex !important; flex-direction: column !important;
align-items: center !important; gap: 4px !important; pointer-events: auto !important; width: 130px !important;
user-select: none !important; border-radius: 12px !important;
}
.h-stepper-row { display: flex !important; gap: 4px !important; width: 100% !important; justify-content: center !important; }
.h-stepper-row .h-btn-icon { flex: 1 !important; }
.h-drag-dots { cursor: grab !important; color: #64748b !important; font-size: 11px !important; width: 100% !important; text-align: center !important; line-height: 1 !important; letter-spacing: 2px !important; }
.h-btn-icon { width: 100% !important; height: 22px !important; border-radius: 6px !important; border: 1px solid rgba(255,255,255,.12) !important; background: rgba(255,255,255,.08) !important; color: #f1f5f9 !important; font-size: 9px !important; font-weight: 700 !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; }
.h-btn-icon:hover { background: rgba(255,255,255,.2) !important; }
.h-tag-badge-box { width: 100% !important; height: 22px !important; overflow: hidden !important; position: relative !important; background: rgba(0, 0, 0, 0.5) !important; border: 1px solid rgba(56, 189, 248, 0.3) !important; border-radius: 6px !important; display: flex !important; align-items: center !important; flex-shrink: 0 !important; }
.h-tag-badge-text { color: #38bdf8 !important; font-size: 9px !important; font-family: monospace !important; font-weight: 700 !important; display: block !important; width: 100% !important; text-align: center !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; line-height: 22px !important; }
.h-tag-badge-text.is-animating { position: absolute !important; left: 0 !important; width: auto !important; padding-left: 100% !important; text-align: left !important; animation: hiderMarqueeR2L 8s linear infinite !important; }
@keyframes hiderMarqueeR2L { 0% { transform: translate3d(0,0,0); } 100% { transform: translate3d(-100%,0,0); } }
.h-btn-pill { height: 22px !important; min-height: 22px !important; width: 100% !important; padding: 0 6px !important; border-radius: 6px !important; font-size: 9px !important; font-weight: 800 !important; border: none !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; }
.h-btn-pill:hover { filter: brightness(1.15) !important; }
.h-toast { /* overridden by inline styles */ }
.h-prompt { position: fixed !important; top: 25px !important; left: 50% !important; transform: translateX(-50%) translateY(-15px) !important; opacity: 0; padding: 12px 14px !important; z-index: 2147483646 !important; display: flex !important; flex-direction: column !important; align-items: center !important; gap: 6px !important; pointer-events: auto !important; width: min(290px, 88vw) !important; max-height: 85vh !important; transition: all 0.3s ease !important; }
.h-url-box { font-size: 10px !important; color: #94a3b8 !important; word-break: break-all !important; max-height: 32px !important; overflow: hidden !important; background: rgba(0,0,0,.4) !important; padding: 4px 8px !important; border-radius: 6px !important; width: 100% !important; border: 1px solid rgba(255,255,255,.1) !important; font-family: monospace !important; }
.h-select { width: 100% !important; background: rgba(255,255,255,.06) !important; border: 1px solid rgba(255,255,255,.14) !important; border-radius: 6px !important; color: #fff !important; padding: 4px 8px !important; font-size: 10px !important; outline: 0 !important; height: 28px !important; }
.h-custom-select { position: relative !important; width: 100% !important; user-select: none !important; margin-top: 2px !important; z-index: 10 !important; }
.h-custom-trigger { background: rgba(30,41,59,.85) !important; border: 1px solid rgba(96,165,250,.4) !important; border-radius: 8px !important; color: #f1f5f9 !important; padding: 6px 10px !important; font-size: 10px !important; font-weight: 700 !important; cursor: pointer !important; display: flex !important; justify-content: space-between !important; align-items: center !important; height: 30px !important; }
.h-custom-arrow { font-size: 9px !important; color: #38bdf8 !important; transition: transform .2s ease !important; }
.h-custom-select.is-open .h-custom-arrow { transform: rotate(-180deg) !important; }
.h-custom-options { display: none; position: relative !important; z-index: 100 !important; flex-direction: column !important; background: rgba(15,23,42,0.98) !important; border: 1px solid rgba(255,255,255,.2) !important; border-radius: 8px !important; max-height: 160px !important; overflow-y: auto !important; margin-top: 4px !important; }
.h-custom-select.is-open .h-custom-options { display: flex !important; }
.h-custom-opt { padding: 6px 10px !important; color: #cbd5e1 !important; font-size: 10px !important; cursor: pointer !important; border-bottom: 1px solid rgba(255,255,255,.05) !important; }
.h-custom-opt:hover, .h-custom-opt.is-selected { background: rgba(56,189,248,.2) !important; color: #38bdf8 !important; font-weight: 700 !important; }
.panel-header { font-weight: 800 !important; font-size: 11px !important; border-bottom: 1px solid rgba(255,255,255,.12) !important; padding-bottom: 6px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 2px !important; color: #f8fafc !important; }
.list-item { font-size: 10px !important; background: rgba(255,255,255,.04) !important; border: 1px solid rgba(255,255,255,.08) !important; border-radius: 6px !important; padding: 4px 6px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; gap: 6px !important; }
.list-item span.rule-text { word-break: break-all !important; color: #e2e8f0 !important; font-family: monospace !important; font-size: 9px !important; flex: 1 !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }
.hider-btn-small { cursor: pointer !important; border: none !important; border-radius: 4px !important; padding: 2px 6px !important; color: #fff !important; font-weight: 700 !important; font-size: 9px !important; height: 20px !important; display: flex; align-items: center; justify-content: center; }
`;
const container = doc.createElement('div');
container.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 Control Dock">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%" style="display:block;">
<defs>
<radialGradient id="bgGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#1e293b" />
<stop offset="60%" stop-color="#0d121e" />
<stop offset="100%" stop-color="#050816" />
</radialGradient>
<linearGradient id="neonCyan" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#7dd3fc" />
<stop offset="50%" stop-color="#38bdf8" />
<stop offset="100%" stop-color="#0284c7" />
</linearGradient>
<linearGradient id="proGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="100%" stop-color="#f59e0b" />
</linearGradient>
<linearGradient id="glassBorder" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#38bdf8" stop-opacity="0.9"/>
<stop offset="50%" stop-color="#1e293b" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#38bdf8" stop-opacity="0.7"/>
</linearGradient>
<filter id="neonGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="proGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<circle cx="256" cy="256" r="230" fill="url(#bgGradient)" stroke="url(#glassBorder)" stroke-width="6" filter="url(#neonGlow)"/>
<g stroke="#38bdf8" stroke-opacity="0.12" stroke-width="2">
<line x1="126" y1="180" x2="386" y2="180"/>
<line x1="126" y1="256" x2="386" y2="256"/>
<line x1="126" y1="332" x2="386" y2="332"/>
<line x1="180" y1="126" x2="180" y2="386"/>
<line x1="256" y1="126" x2="256" y2="386"/>
<line x1="332" y1="126" x2="332" y2="386"/>
</g>
<circle cx="256" cy="256" r="145" fill="none" stroke="#38bdf8" stroke-width="2.5" stroke-dasharray="8 8" opacity="0.45" />
<g filter="url(#neonGlow)">
<path d="M 136 256 C 180 176, 332 176, 376 256 C 332 336, 180 336, 136 256 Z"
fill="none" stroke="url(#neonCyan)" stroke-width="12" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="256" cy="256" r="46" fill="#0d121e" stroke="url(#neonCyan)" stroke-width="8"/>
<circle cx="256" cy="256" r="18" fill="#e0f2fe"/>
<line x1="150" y1="362" x2="362" y2="150" stroke="#f8fafc" stroke-width="12" stroke-linecap="round"/>
<line x1="150" y1="362" x2="362" y2="150" stroke="#38bdf8" stroke-width="6" stroke-linecap="round"/>
</g>
<g stroke="#e0f2fe" stroke-width="2.5" opacity="0.8">
<path d="M 360 130 L 360 154 M 348 142 L 372 142 M 351 133 L 369 151 M 351 151 L 369 133" />
</g>
<g transform="translate(85, 335)" filter="url(#proGlow)">
<rect x="0" y="0" width="112" height="50" rx="14" fill="#0d121e" stroke="url(#proGradient)" stroke-width="3.5"/>
<text x="56" y="34" font-family="system-ui, -apple-system, sans-serif" font-weight="900" font-size="25" fill="url(#proGradient)" text-anchor="middle" letter-spacing="3.5">PRO</text>
</g>
</svg>
</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/Link/Global)">🌐<span>SITE</span></button>
<button class="h-dock-btn" id="btn-reveal-quick" title="Reveal hidden/blurred elements">👁️<span>Reveal</span></button>
<button class="h-dock-btn" id="btn-links" title="Extract all links from the page">🔗<span>Links</span></button>
<button class="h-dock-btn" id="btn-skip-30" title="Skip 30 seconds forward">⏩<span>+30s</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 class="title">Hide Web Elements Pro</span>
<button class="close-btn" id="close-p">✖</button>
</div>
<div class="panel-body">
<div class="panel-sidebar" id="panel-sidebar"></div>
<div class="panel-content" id="panel-content"></div>
</div>
</div>`;
shadowRoot.innerHTML = ''; shadowRoot.appendChild(style); shadowRoot.appendChild(container);
buildPanelTabs();
setupShadowUIEvents();
applyDockButtonVisibility();
renderDockButtonOptions();
const panel = shadowRoot.getElementById('hider-panel');
if (panel) {
const stopProp = (e) => e.stopPropagation();
panel.addEventListener('wheel', stopProp, { passive: true, capture: true });
panel.addEventListener('touchmove', stopProp, { passive: true, capture: true });
panel.addEventListener('scroll', stopProp, { passive: true, capture: true });
panel.addEventListener('pointerdown', stopProp, { capture: true });
panel.addEventListener('mousedown', stopProp, { capture: true });
}
const linkPanel = shadowRoot.getElementById('hider-link-panel');
if (linkPanel) {
const stopProp = (e) => e.stopPropagation();
linkPanel.addEventListener('wheel', stopProp, { passive: true, capture: true });
linkPanel.addEventListener('touchmove', stopProp, { passive: true, capture: true });
linkPanel.addEventListener('scroll', stopProp, { passive: true, capture: true });
linkPanel.addEventListener('pointerdown', stopProp, { capture: true });
linkPanel.addEventListener('mousedown', stopProp, { capture: true });
}
const stepper = shadowRoot.getElementById(STEPPER_BAR_ID);
if (stepper) {
const stopProp = (e) => e.stopPropagation();
stepper.addEventListener('wheel', stopProp, { passive: true, capture: true });
stepper.addEventListener('touchmove', stopProp, { passive: true, capture: true });
stepper.addEventListener('pointerdown', stopProp, { capture: true });
}
}
// ---------- Build Panel Tabs ----------
function buildPanelTabs() {
const sidebar = shadowBy('panel-sidebar');
const content = shadowBy('panel-content');
if (!sidebar || !content) return;
const tabs = [
{ id: 'tab-tools', label: '🛠️ Tools', html: `
<div style="display:flex;flex-direction:column;gap:6px;">
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;">Basic</div>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;">
<input type="checkbox" id="chk-auto-scroll" ${CACHE.autoScroll?'checked':''} style="accent-color:#38bdf8;"> Auto Anti‑Scroll Lock
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;">
<input type="checkbox" id="chk-enable-contextmenu" ${CACHE.enableContextMenu?'checked':''} style="accent-color:#38bdf8;"> Auto Right‑Click / Long‑Press
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;">
<input type="checkbox" id="chk-auto-remove-blur" ${CACHE.autoRemoveBlur?'checked':''} style="accent-color:#38bdf8;"> Auto Remove Blur
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;margin-top:2px;">
<input type="checkbox" id="chk-auto-time-skipper" ${CACHE.autoTimeSkipper?'checked':''} style="accent-color:#f59e0b;"> Auto Time Skipper (Smart)
</label>
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;margin-top:6px;border-top:1px solid rgba(255,255,255,0.06);padding-top:6px;">Overlays & Paywalls</div>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;">
<input type="checkbox" id="chk-anti-paywall" ${CACHE.antiPaywall?'checked':''} style="accent-color:#f59e0b;"> Anti‑Paywall / Anti‑Adblock
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;">
<input type="checkbox" id="chk-auto-close-modals" ${CACHE.autoCloseModals?'checked':''} style="accent-color:#f59e0b;"> Auto‑Close Modals & Overlays
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;padding-left:16px;border-left:2px solid rgba(56,189,248,0.3);">
<input type="checkbox" id="chk-auto-accept-age" ${CACHE.autoAcceptAge?'checked':''} style="accent-color:#f59e0b;"> Auto‑Accept Age Verification
</label>
<label style="font-size:10px;color:#cbd5e1;display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;padding-left:16px;border-left:2px solid rgba(56,189,248,0.3);">
<input type="checkbox" id="chk-auto-close-logins" ${CACHE.autoCloseLogins?'checked':''} style="accent-color:#f59e0b;"> Auto‑Close Logins
</label>
<div style="display:flex;align-items:center;gap:6px;margin-top:2px;">
<span style="font-size:10px;color:#94a3b8;">🍪 Cookie Consent:</span>
<select id="cookie-consent-mode" style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.14);border-radius:6px;color:#fff;padding:2px 6px;font-size:9px;height:22px;flex:1;">
<option value="ask" ${CACHE.cookieConsentMode === 'ask' ? 'selected' : ''}>Ask</option>
<option value="accept" ${CACHE.cookieConsentMode === 'accept' ? 'selected' : ''}>Accept</option>
<option value="reject" ${CACHE.cookieConsentMode === 'reject' ? 'selected' : ''}>Reject</option>
</select>
</div>
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;margin-top:6px;border-top:1px solid rgba(255,255,255,0.06);padding-top:6px;">Dock Buttons</div>
<div id="dock-buttons-list" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:4px;"></div>
</div>
` },
{ id: 'tab-rules', label: '✏️ Rules', html: `
<div style="display:flex;flex-direction:column;gap:4px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px;">
<span style="font-size:10px;color:#94a3b8;font-weight:700;">Custom Rules (<span id="cnt-custom">0</span>)</span>
<button class="hider-btn-small btn-blue" id="add-rule-btn" style="height:24px;padding:0 8px;">➕ Add Rule</button>
</div>
<div id="list-custom-rules" style="display:flex;flex-direction:column;gap:3px;"></div>
<div style="border-top:1px solid rgba(255,255,255,0.06);padding-top:4px;margin-top:4px;">
<div style="display:flex;justify-content:space-between;font-size:10px;color:#94a3b8;">
<span>🌐 Site‑Wide (<span id="cnt-site">0</span>)</span>
<button class="hider-btn-small btn-red" id="clear-site-rules" style="font-size:8px;padding:1px 5px;height:18px;">Clear All</button>
</div>
<div id="list-site" style="display:flex;flex-direction:column;gap:3px;"></div>
</div>
<div style="border-top:1px solid rgba(255,255,255,0.06);padding-top:4px;">
<div style="display:flex;justify-content:space-between;font-size:10px;color:#94a3b8;">
<span>📄 Page‑Only (<span id="cnt-link">0</span>)</span>
<button class="hider-btn-small btn-red" id="clear-page-rules" style="font-size:8px;padding:1px 5px;height:18px;">Clear All</button>
</div>
<div id="list-link" style="display:flex;flex-direction:column;gap:3px;"></div>
</div>
<div style="margin-top:4px;text-align:right;">
<button class="hider-btn-small btn-red" id="clear-custom-rules" style="font-size:8px;padding:1px 5px;height:18px;">Clear All Custom Rules</button>
</div>
</div>
` },
{ id: 'tab-freeze', label: '❄️ Freeze', html: `
<div style="display:flex;flex-direction:column;gap:6px;">
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;">Freeze Behaviour</div>
<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</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</div>
</div>
</div>
<button id="btn-reset-block-confirm" class="hider-btn-small" style="background:rgba(255,255,255,0.06);color:#cbd5e1;width:100%;padding:4px;height:auto;font-size:9px;">Reset "Don't Ask Block" Prompt</button>
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;margin-top:8px;border-top:1px solid rgba(255,255,255,0.06);padding-top:6px;">🚫 Blocked Domains</div>
<div style="display:flex;gap:4px;margin-bottom:4px;">
<input type="text" id="manual-domain-input" placeholder="Block domain (e.g. bad.com)" class="h-select" style="flex:1;">
<button class="hider-btn-small btn-red" id="add-domain-btn" style="height:28px;padding:0 8px;">Block</button>
</div>
<div id="list-blocked-domains" style="display:flex;flex-direction:column;gap:3px;"></div>
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;font-weight:700;margin-top:8px;border-top:1px solid rgba(255,255,255,0.06);padding-top:6px;">🟢 Allowed Domains</div>
<div style="display:flex;gap:4px;margin-bottom:4px;">
<input type="text" id="manual-allowed-domain-input" placeholder="Allow domain (e.g. trusted.com)" class="h-select" style="flex:1;">
<button class="hider-btn-small btn-green" id="add-allowed-domain-btn" style="height:28px;padding:0 8px;">Allow</button>
</div>
<div id="list-allowed-domains" style="display:flex;flex-direction:column;gap:3px;"></div>
</div>
` },
{ id: 'tab-logs', label: '📋 Logs', html: `
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:10px;color:#94a3b8;">Blocked (<span id="cnt-logs">0</span>)</span>
<button class="hider-btn-small btn-gray" id="clear-log-btn" style="font-size:8px;padding:1px 5px;height:18px;">Clear</button>
</div>
<div id="list-blocked-log" style="display:flex;flex-direction:column;gap:3px;margin-top:4px;"></div>
` },
{ id: 'tab-export', label: '💾 Export', html: `
<div style="display:flex;gap:4px;flex-wrap:wrap;align-items:center;margin-bottom:8px;">
<button class="hider-btn-small btn-blue" id="btn-export-settings" style="flex:1;min-width:70px;height:26px;">📤 Export All</button>
<button class="hider-btn-small btn-green" id="btn-import-settings" style="flex:1;min-width:70px;height:26px;">📥 Import All</button>
<input type="file" id="import-file-input" accept=".json" style="display:none;">
</div>
<div style="font-size:9px;color:#94a3b8;margin-bottom:8px;">Export/Import all settings (rules, domains, toggles, logs).</div>
<!-- ========== COOKIE MANAGER (without edit) ========== -->
<div style="border-top:1px solid rgba(255,255,255,0.06);padding-top:8px;margin-top:4px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
<span style="font-size:11px;font-weight:700;color:#f8fafc;">🍪 Cookies (<span id="cookie-count">0</span>)</span>
<input type="text" id="cookie-search" placeholder="🔍 Filter by name..."
style="background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:6px;color:#fff;padding:2px 6px;font-size:9px;height:22px;width:140px;">
</div>
<div id="cookie-list" style="display:flex;flex-direction:column;gap:3px;max-height:160px;overflow-y:auto;margin-bottom:6px;"></div>
<!-- Add cookie row -->
<div style="display:flex;gap:4px;flex-wrap:wrap;align-items:center;background:rgba(255,255,255,0.04);padding:4px;border-radius:6px;border:1px solid rgba(255,255,255,0.08);">
<input type="text" id="cookie-new-name" placeholder="name" style="flex:1;min-width:60px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:6px;color:#fff;padding:2px 6px;font-size:9px;height:24px;">
<input type="text" id="cookie-new-value" placeholder="value" style="flex:2;min-width:80px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:6px;color:#fff;padding:2px 6px;font-size:9px;height:24px;">
<input type="number" id="cookie-new-expiry" placeholder="days (opt)" min="0" step="1" style="width:60px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:6px;color:#fff;padding:2px 6px;font-size:9px;height:24px;">
<button class="hider-btn-small btn-green" id="cookie-add" style="height:24px;padding:0 10px;">➕ Add</button>
</div>
<!-- Action buttons -->
<div style="display:flex;gap:4px;margin-top:4px;flex-wrap:wrap;">
<button class="hider-btn-small btn-red" id="cookie-clear-all" style="height:24px;padding:0 10px;">🗑️ Clear All</button>
<button class="hider-btn-small btn-blue" id="cookie-export" style="height:24px;padding:0 10px;">📤 Export</button>
<button class="hider-btn-small btn-purple" id="cookie-import" style="height:24px;padding:0 10px;">📥 Import</button>
<input type="file" id="cookie-import-file" accept=".json" style="display:none;">
</div>
</div>
` },
{ id: 'tab-about', label: 'ℹ️ About', html: `
<div style="font-size:10px;color:#cbd5e1;line-height:1.6;padding:4px 0;">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
<div style="width:48px;height:48px;flex-shrink:0;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="48" height="48">
<defs>
<radialGradient id="aboutBg" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#0d121e" />
</radialGradient>
<linearGradient id="aboutNeon" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#7dd3fc" />
<stop offset="100%" stop-color="#0284c7" />
</linearGradient>
<linearGradient id="aboutPro" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="100%" stop-color="#f59e0b" />
</linearGradient>
</defs>
<circle cx="256" cy="256" r="220" fill="url(#aboutBg)" stroke="url(#aboutNeon)" stroke-width="4"/>
<path d="M 136 256 C 180 176, 332 176, 376 256 C 332 336, 180 336, 136 256 Z"
fill="none" stroke="url(#aboutNeon)" stroke-width="10"/>
<circle cx="256" cy="256" r="40" fill="#0d121e" stroke="url(#aboutNeon)" stroke-width="6"/>
<circle cx="256" cy="256" r="14" fill="#e0f2fe"/>
<line x1="150" y1="362" x2="362" y2="150" stroke="#f8fafc" stroke-width="10"/>
<line x1="150" y1="362" x2="362" y2="150" stroke="#38bdf8" stroke-width="4"/>
<text x="256" y="425" font-family="system-ui, -apple-system, sans-serif" font-weight="900" font-size="28" fill="url(#aboutPro)" text-anchor="middle" letter-spacing="4">PRO</text>
</svg>
</div>
<div>
<h3 style="font-size:14px;font-weight:800;color:#f8fafc;margin:0;">Hide Web Elements</h3>
<p style="font-size:10px;color:#94a3b8;margin:2px 0 0;"><strong style="color:gold;">Pro</strong> version • by KTZ</p>
</div>
</div>
<p style="font-size:10px;color:#94a3b8;margin:0 0 8px;">A powerful element hider with advanced navigation control, media extraction, and automatic challenge protection.</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin:8px 0;">
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#38bdf8;">🎯 Hide</div>
<div style="font-size:9px;color:#94a3b8;">Site, page, or global hiding</div>
</div>
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#34d399;">❄️ Freeze</div>
<div style="font-size:9px;color:#94a3b8;">Block/allow navigation</div>
</div>
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#a855f7;">🔗 Links</div>
<div style="font-size:9px;color:#94a3b8;">Extract & download media</div>
</div>
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#f59e0b;">⏩ Time Skipper</div>
<div style="font-size:9px;color:#94a3b8;">Jump forward 30s</div>
</div>
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#f43f5e;">👁️ Reveal</div>
<div style="font-size:9px;color:#94a3b8;">Unhide overlays & blur</div>
</div>
<div style="background:rgba(255,255,255,0.04);border-radius:6px;padding:6px 8px;border:1px solid rgba(255,255,255,0.06);">
<div style="font-size:11px;font-weight:700;color:#8b5cf6;">🛡️ Protection</div>
<div style="font-size:9px;color:#94a3b8;">Cloudflare/anti‑bot</div>
</div>
</div>
<div style="margin:8px 0;border-top:1px solid rgba(255,255,255,0.06);padding-top:8px;">
<div style="display:flex;gap:12px;flex-wrap:wrap;">
<div><span style="font-weight:700;color:#38bdf8;">Anti‑Paywall</span> <span style="color:#94a3b8;">•</span> <span style="color:#94a3b8;">Removes overlays</span></div>
<div><span style="font-weight:700;color:#38bdf8;">Auto‑Close Modals</span> <span style="color:#94a3b8;">•</span> <span style="color:#94a3b8;">Smart skip</span></div>
<div><span style="font-weight:700;color:#38bdf8;">Cookie Bypass</span> <span style="color:#94a3b8;">•</span> <span style="color:#94a3b8;">Accept/Reject</span></div>
<div><span style="font-weight:700;color:#38bdf8;">Media Downloader</span> <span style="color:#94a3b8;">•</span> <span style="color:#94a3b8;">Preview & save</span></div>
</div>
</div>
<div style="margin:8px 0;border-top:1px solid rgba(255,255,255,0.06);padding-top:8px;">
<div style="font-size:9px;color:#94a3b8;">
<span style="font-weight:700;color:#e2e8f0;">🔒 Protection:</span> Pauses features on Cloudflare challenges, resumes automatically.
</div>
<div style="font-size:9px;color:#94a3b8;margin-top:2px;">
<span style="font-weight:700;color:#e2e8f0;">💾 Storage:</span> All settings persist across restarts (GM_setValue/localStorage).
</div>
<div style="font-size:9px;color:#94a3b8;margin-top:2px;">
<span style="font-weight:700;color:#e2e8f0;">⌨️ Shortcuts:</span> <kbd style="background:rgba(255,255,255,0.08);padding:0 4px;border-radius:3px;">Esc</kbd> cancel selection/close panels • <kbd style="background:rgba(255,255,255,0.08);padding:0 4px;border-radius:3px;">Ctrl+Click</kbd> bypass freeze
</div>
</div>
<div style="margin-top:8px;font-size:8px;color:#64748b;text-align:center;border-top:1px solid rgba(255,255,255,0.06);padding-top:6px;">
Open source under MIT • Made with ❤️
</div>
</div>
` }
];
tabs.forEach((tab, index) => {
const btn = document.createElement('button');
btn.className = 'tab-btn' + (index === 0 ? ' active' : '');
btn.dataset.tab = tab.id;
btn.textContent = tab.label;
btn.title = tab.label;
sidebar.appendChild(btn);
const contentDiv = document.createElement('div');
contentDiv.className = 'tab-content' + (index === 0 ? ' active' : '');
contentDiv.id = tab.id;
contentDiv.innerHTML = tab.html;
content.appendChild(contentDiv);
});
sidebar.addEventListener('click', (e) => {
const btn = e.target.closest('.tab-btn');
if (!btn) return;
const tabId = btn.dataset.tab;
sidebar.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
content.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
const contentDiv = content.querySelector('#' + tabId);
if (contentDiv) contentDiv.classList.add('active');
});
}
// ---------- Shadow UI Events ----------
function setupShadowUIEvents() {
shadowBy('close-p').onclick = e => { if (e) { e.stopPropagation(); e.preventDefault(); } turnOffHideMode(); closeAllMenus(e, true); };
setupCustomDropdown(shadowBy('freeze-custom-panel-dropdown'), CACHE.freezeMemory, val => {
CACHE.freezeMemory = val; sv('hider_freeze_memory', val);
const label = FREEZE_LABELS[val] || val;
if (val === 'allow_all') { disableFreezeMode(); showToast(`🟢 Freeze Mode Disabled (Allow All)`); }
else showToast(`❄️ Memory updated: ${label}`);
});
shadowBy('btn-reset-block-confirm').onclick = () => { sv('hider_skip_block_confirm', false); showToast('🔄 Reset Block Domain confirmation!'); };
const chkPaywall = shadowBy('chk-anti-paywall');
if (chkPaywall) {
chkPaywall.onchange = e => {
CACHE.antiPaywall = e.target.checked;
sv('hider_anti_paywall', CACHE.antiPaywall);
applyAllSettings();
showToast(`🚫 Anti-Paywall: ${CACHE.antiPaywall ? 'ON' : 'OFF'}`);
};
}
const chkModal = shadowBy('chk-auto-close-modals');
if (chkModal) {
chkModal.onchange = e => {
CACHE.autoCloseModals = e.target.checked;
sv('hider_auto_close_modals', CACHE.autoCloseModals);
applyAllSettings();
showToast(`🗑️ Auto-Close Modals: ${CACHE.autoCloseModals ? 'ON' : 'OFF'}`);
};
}
const chkAutoAcceptAge = shadowBy('chk-auto-accept-age');
if (chkAutoAcceptAge) {
chkAutoAcceptAge.onchange = e => {
CACHE.autoAcceptAge = e.target.checked;
sv('hider_auto_accept_age', CACHE.autoAcceptAge);
applyAllSettings();
showToast(`✅ Auto-Accept Age: ${CACHE.autoAcceptAge ? 'ON' : 'OFF'}`);
};
}
const chkAutoCloseLogins = shadowBy('chk-auto-close-logins');
if (chkAutoCloseLogins) {
chkAutoCloseLogins.onchange = e => {
CACHE.autoCloseLogins = e.target.checked;
sv('hider_auto_close_logins', CACHE.autoCloseLogins);
applyAllSettings();
showToast(`🔓 Auto-Close Logins: ${CACHE.autoCloseLogins ? 'ON' : 'OFF'}`);
};
}
const cookieMode = shadowBy('cookie-consent-mode');
if (cookieMode) {
cookieMode.onchange = e => {
CACHE.cookieConsentMode = e.target.value;
sv('hider_cookie_consent_mode', CACHE.cookieConsentMode);
applyAllSettings();
showToast(`🍪 Cookie mode: ${CACHE.cookieConsentMode === 'accept' ? 'Accept' : CACHE.cookieConsentMode === 'reject' ? 'Reject' : 'Ask'}`);
};
}
const chkScroll = shadowBy('chk-auto-scroll');
if (chkScroll) {
chkScroll.onchange = e => {
CACHE.autoScroll = e.target.checked;
sv('hider_auto_scroll', CACHE.autoScroll);
if (CACHE.autoScroll) {
startScrollDefeater();
showToast('🔓 Auto Anti-Scroll Lock enabled');
} else {
stopScrollDefeater();
showToast('🔒 Auto Anti-Scroll Lock disabled');
}
};
}
const chkContext = shadowBy('chk-enable-contextmenu');
if (chkContext) {
chkContext.onchange = e => {
CACHE.enableContextMenu = e.target.checked;
sv('hider_enable_contextmenu', CACHE.enableContextMenu);
updateContextMenuStyles();
showToast(`🖱️ Auto Right-Click/Long-Press: ${CACHE.enableContextMenu ? 'ON' : 'OFF'}`);
};
}
const chkBlur = shadowBy('chk-auto-remove-blur');
if (chkBlur) {
chkBlur.onchange = e => {
CACHE.autoRemoveBlur = e.target.checked;
sv('hider_auto_remove_blur', CACHE.autoRemoveBlur);
if (CACHE.autoRemoveBlur) {
scheduleBlurRemoval();
showToast('👁️ Auto Remove Blur enabled (aggressive)');
} else {
stopBlurRemoval();
showToast('👁️ Auto Remove Blur disabled');
}
};
}
const autoSkipChk = shadowBy('chk-auto-time-skipper');
if (autoSkipChk) {
autoSkipChk.onchange = e => {
CACHE.autoTimeSkipper = e.target.checked;
sv('hider_auto_time_skipper', CACHE.autoTimeSkipper);
if (CACHE.autoTimeSkipper) {
startAutoSkipMonitoring();
showToast('⏩ Auto Time Skipper enabled (smart)');
} else {
stopAutoSkipMonitoring();
showToast('⏩ Auto Time Skipper disabled');
}
};
}
const dockEl = shadowBy('hider-main-dock'), mainBtn = shadowBy('btn-toggle-dock');
const savedY = gv('hider_dock_y', null); if (savedY) dockEl.style.top = savedY;
dockEl.addEventListener('mouseleave', () => dockEl.classList.remove('manual-hidden'));
let dockStartX, dockStartY, dockInitY, dockMoved = false;
const onDockMove = e => {
if (!isDraggingDock) return;
const p = e.touches ? e.touches[0] : e, dy = p.clientY - dockStartY;
if (Math.hypot(p.clientX - dockStartX, dy) > 8) {
dockMoved = true; if (e.cancelable) e.preventDefault();
dockEl.style.top = `${Math.max(0, Math.min(dockInitY + dy, win.innerHeight - dockEl.offsetHeight))}px`;
}
};
const onDockEnd = () => {
if (isDraggingDock) { isDraggingDock = false; dockEl.style.transition = ''; if (dockMoved) sv('hider_dock_y', dockEl.style.top); }
win.removeEventListener('mousemove', onDockMove); win.removeEventListener('mouseup', onDockEnd);
win.removeEventListener('touchmove', onDockMove); win.removeEventListener('touchend', onDockEnd); win.removeEventListener('touchcancel', onDockEnd);
};
const onDockStart = e => {
if (e.target.tagName === 'BUTTON' && e.target.id !== 'btn-toggle-dock') return;
const p = e.touches ? e.touches[0] : e;
dockStartX = p.clientX; dockStartY = p.clientY; dockInitY = dockEl.getBoundingClientRect().top;
isDraggingDock = true; dockMoved = false; dockEl.style.transition = 'none';
win.addEventListener('mousemove', onDockMove, { passive: false }); win.addEventListener('mouseup', onDockEnd);
win.addEventListener('touchmove', onDockMove, { passive: false }); win.addEventListener('touchend', onDockEnd); win.addEventListener('touchcancel', onDockEnd);
};
mainBtn.addEventListener('mousedown', onDockStart, { passive: false });
mainBtn.addEventListener('touchstart', onDockStart, { passive: false });
mainBtn.onclick = e => {
e.stopPropagation(); if (dockMoved) { dockMoved = false; return; }
if (timers.collapseTimer) { clearTimeout(timers.collapseTimer); timers.collapseTimer = null; }
const menu = shadowBy('hider-dock-menu'), isOpening = !menu.classList.contains('is-open');
menu.classList.toggle('is-open', isOpening);
mainBtn.classList.toggle('expanded', isOpening);
dockEl.classList.toggle('expanded', isOpening);
dockEl.classList.toggle('is-collapsed', !isOpening);
if (!isOpening) {
turnOffHideMode();
const panel = shadowBy('hider-panel');
if (panel.classList.contains('is-visible')) { panel.classList.remove('is-visible'); setTimeout(()=> panel.style.display='none', 300); }
shadowBy('btn-manage')?.classList.remove('active');
dockEl.classList.add('manual-hidden');
timers.collapseTimer = setTimeout(() => { dockEl.classList.add('is-collapsed'); timers.collapseTimer = null; }, 1000);
} else dockEl.classList.remove('manual-hidden');
};
const btnScope = shadowBy('btn-scope');
btnScope.onclick = e => {
e.stopPropagation();
const scopes = ['site', 'link', 'global'];
let idx = scopes.indexOf(currentScope);
idx = (idx + 1) % scopes.length;
currentScope = scopes[idx];
btnScope.querySelector('span').textContent = currentScope.toUpperCase();
btnScope.classList.toggle('scope-link', currentScope === 'link');
btnScope.classList.toggle('scope-global', currentScope === 'global');
const label = currentScope === 'site' ? '🌐 Site-wide' : currentScope === 'link' ? '📄 Page-only' : '🌍 Global';
showToast(`Scope: ${label}`);
broadcastState();
};
shadowBy('btn-reveal-quick').onclick = (e) => {
e.stopPropagation();
revealHiddenElements();
};
shadowBy('btn-links').onclick = (e) => {
e.stopPropagation();
toggleLinkPanel();
};
shadowBy('btn-skip-30').onclick = (e) => {
e.stopPropagation();
skip30Seconds();
};
const addRuleBtn = shadowBy('add-rule-btn');
if (addRuleBtn) {
addRuleBtn.onclick = () => {
showRuleOverlay({ mode: 'add', scope: 'global' });
};
}
const addAllowedDomain = () => {
const input = shadowBy('manual-allowed-domain-input'), d = cleanDomain(input.value);
if (d && !CACHE.allowedDomainsSet.has(d)) { CACHE.allowedDomainsList.push(d); CACHE.allowedDomainsSet.add(d); sv('hider_allowed_domains', CACHE.allowedDomainsList); showToast(`🟢 Allowed: ${d}`); }
input.value = ''; renderList();
};
shadowBy('add-allowed-domain-btn').onclick = addAllowedDomain; shadowBy('manual-allowed-domain-input').onkeypress = e => e.key === 'Enter' && addAllowedDomain();
const addDomain = () => {
const input = shadowBy('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();
};
shadowBy('add-domain-btn').onclick = addDomain; shadowBy('manual-domain-input').onkeypress = e => e.key === 'Enter' && addDomain();
shadowBy('btn-select').onclick = e => {
e.stopPropagation(); isSelecting = !isSelecting; e.currentTarget.classList.toggle('active', isSelecting);
const p = shadowBy('hider-panel');
if (isSelecting) {
if(p.classList.contains('is-visible')) { p.classList.remove('is-visible'); setTimeout(()=> p.style.display='none', 300); } shadowBy('btn-manage').classList.remove('active');
showToast('🎯 Selection mode ON – Click element to hide');
} else {
clearSelectionState();
showToast('🎯 Selection mode OFF');
}
broadcastState();
};
const btnFreeze = shadowBy('btn-freeze');
btnFreeze.onclick = e => {
e.stopPropagation(); isFrozen = !isFrozen; CACHE.isFrozen = isFrozen; sv('hider_freeze_global', isFrozen);
if (isFrozen && CACHE.freezeMemory === 'allow_all') { CACHE.freezeMemory = 'ask'; sv('hider_freeze_memory', 'ask'); }
btnFreeze.classList.toggle('is-frozen', isFrozen); if (isFrozen) triggerFreezeFx();
showToast(isFrozen ? '🥶 Freeze ON' : '🔥 Freeze OFF'); broadcastState();
};
shadowBy('btn-manage').onclick = e => {
e.stopPropagation(); const p = shadowBy('hider-panel'), isHidden = p.style.display === 'none' || p.style.display === '';
if (isHidden) {
p.style.display = 'flex';
void p.offsetWidth;
p.classList.add('is-visible');
e.currentTarget.classList.add('active');
turnOffHideMode(); renderList();
} else {
p.classList.remove('is-visible');
setTimeout(() => p.style.display = 'none', 300);
e.currentTarget.classList.remove('active');
}
};
shadowBy('clear-log-btn').onclick = e => { if (e) e.stopPropagation(); CACHE.logs = []; sv('hider_global_logs', []); renderList(); showToast('🧹 Logs cleared'); };
shadowBy('clear-custom-rules').onclick = () => {
if (confirm('Delete all custom rules?')) {
CACHE.customRules = [];
sv('hider_custom_rules_v4', CACHE.customRules);
renderList(); requestUpdateStyles();
showToast('🧹 Custom rules cleared');
}
};
shadowBy('clear-site-rules').onclick = () => {
const key = 'hider_site_' + location.hostname;
if (confirm('Delete all site-wide rules?')) {
sv(key, []);
renderList(); requestUpdateStyles();
showToast('🧹 Site rules cleared');
}
};
shadowBy('clear-page-rules').onclick = () => {
const key = 'hider_link_' + cleanUrl();
if (confirm('Delete all page-only rules?')) {
sv(key, []);
renderList(); requestUpdateStyles();
showToast('🧹 Page rules cleared');
}
};
const exportBtn = shadowBy('btn-export-settings');
const importBtn = shadowBy('btn-import-settings');
const fileInput = shadowBy('import-file-input');
if (exportBtn) exportBtn.onclick = () => {
const data = getAllSettings();
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `hider_backup_${new Date().toISOString().slice(0,10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('📤 Settings exported');
};
if (importBtn) importBtn.onclick = () => fileInput?.click();
if (fileInput) {
fileInput.onchange = function() {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
let count = 0;
for (const [key, val] of Object.entries(data)) {
if (key.startsWith('hider_')) {
sv(key, val);
count++;
}
}
syncCache();
requestUpdateStyles();
renderList();
applyDockButtonVisibility();
showToast(`📥 Imported ${count} settings`);
} catch (err) {
showToast('❌ Invalid JSON file');
}
};
reader.readAsText(this.files[0]);
this.value = '';
}
};
}
// ---------- COOKIE MANAGER (without edit) ----------
const cookieListEl = shadowBy('cookie-list');
const cookieCountEl = shadowBy('cookie-count');
const cookieSearch = shadowBy('cookie-search');
const cookieAddBtn = shadowBy('cookie-add');
const cookieNameInput = shadowBy('cookie-new-name');
const cookieValueInput = shadowBy('cookie-new-value');
const cookieExpiryInput = shadowBy('cookie-new-expiry');
const cookieClearAll = shadowBy('cookie-clear-all');
const cookieExportBtn = shadowBy('cookie-export');
const cookieImportBtn = shadowBy('cookie-import');
const cookieImportFile = shadowBy('cookie-import-file');
function getCookies() {
return document.cookie.split(';').map(c => c.trim()).filter(Boolean).map(c => {
const eq = c.indexOf('=');
return { name: c.slice(0, eq), value: c.slice(eq + 1) };
});
}
function renderCookies(filter = '') {
if (!cookieListEl) return;
const cookies = getCookies();
const filtered = filter ? cookies.filter(c => c.name.toLowerCase().includes(filter.toLowerCase())) : cookies;
cookieCountEl.textContent = filtered.length;
if (filtered.length === 0) {
cookieListEl.innerHTML = `<div style="font-size:9px;color:#94a3b8;text-align:center;padding:4px;">${cookies.length ? 'No matching cookies' : 'No cookies for this domain.'}</div>`;
return;
}
cookieListEl.innerHTML = '';
filtered.forEach(c => {
const row = document.createElement('div');
row.style.cssText = 'display:flex;align-items:center;gap:4px;font-size:9px;padding:2px 4px;background:rgba(255,255,255,.04);border-radius:4px;border:1px solid rgba(255,255,255,0.06);';
const nameSpan = document.createElement('span');
nameSpan.textContent = c.name;
nameSpan.style.cssText = 'font-weight:700;color:#38bdf8;min-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
const valSpan = document.createElement('span');
valSpan.textContent = c.value;
valSpan.style.cssText = 'flex:1;color:#e2e8f0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
const btnGroup = document.createElement('div');
btnGroup.style.cssText = 'display:flex;gap:2px;flex-shrink:0;';
// Delete button only (no edit)
const delBtn = document.createElement('button');
delBtn.className = 'hider-btn-small btn-red';
delBtn.textContent = '✖';
delBtn.style.cssText = 'padding:0 4px;height:16px;font-size:7px;';
delBtn.onclick = (e) => {
e.stopPropagation();
if (confirm(`Delete cookie "${c.name}"?`)) {
// Try multiple deletion variants
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${location.hostname}`;
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
renderCookies(cookieSearch ? cookieSearch.value : '');
showToast(`🍪 Deleted cookie: ${c.name}`);
}
};
btnGroup.appendChild(delBtn);
row.appendChild(nameSpan);
row.appendChild(valSpan);
row.appendChild(btnGroup);
cookieListEl.appendChild(row);
});
}
// Add cookie
function addCookie() {
const name = cookieNameInput ? cookieNameInput.value.trim() : '';
const value = cookieValueInput ? cookieValueInput.value.trim() : '';
const days = cookieExpiryInput ? parseInt(cookieExpiryInput.value, 10) : 0;
if (!name) { showToast('⚠️ Enter cookie name'); return; }
let cookieStr = `${name}=${value}; path=/; domain=${location.hostname}`;
if (days > 0) {
const exp = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
cookieStr += `; expires=${exp.toUTCString()}`;
}
document.cookie = cookieStr;
if (cookieNameInput) cookieNameInput.value = '';
if (cookieValueInput) cookieValueInput.value = '';
if (cookieExpiryInput) cookieExpiryInput.value = '';
renderCookies(cookieSearch ? cookieSearch.value : '');
showToast(`🍪 Added cookie: ${name}`);
}
// Clear all cookies for current domain
function clearAllCookies() {
if (!confirm('Delete ALL cookies for this domain?')) return;
const cookies = getCookies();
cookies.forEach(c => {
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${location.hostname}`;
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
document.cookie = `${c.name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
});
renderCookies(cookieSearch ? cookieSearch.value : '');
showToast(`🗑️ Cleared ${cookies.length} cookies`);
}
// Export cookies
function exportCookies() {
const cookies = getCookies();
const json = JSON.stringify(cookies, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `cookies_${location.hostname}_${new Date().toISOString().slice(0,10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('🍪 Cookies exported');
}
// Import cookies
function importCookies(file) {
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
if (!Array.isArray(data)) throw new Error('Not an array');
data.forEach(c => {
if (c.name && c.value !== undefined) {
document.cookie = `${c.name}=${c.value}; path=/; domain=${location.hostname}`;
}
});
renderCookies(cookieSearch ? cookieSearch.value : '');
showToast(`🍪 Imported ${data.length} cookies`);
} catch (err) {
showToast('❌ Invalid cookie JSON');
}
};
reader.readAsText(file);
}
// Event listeners
if (cookieAddBtn) cookieAddBtn.onclick = addCookie;
if (cookieNameInput) cookieNameInput.onkeypress = e => e.key === 'Enter' && addCookie();
if (cookieValueInput) cookieValueInput.onkeypress = e => e.key === 'Enter' && addCookie();
if (cookieExpiryInput) cookieExpiryInput.onkeypress = e => e.key === 'Enter' && addCookie();
if (cookieClearAll) cookieClearAll.onclick = clearAllCookies;
if (cookieExportBtn) cookieExportBtn.onclick = exportCookies;
if (cookieImportBtn) cookieImportBtn.onclick = () => cookieImportFile?.click();
if (cookieImportFile) {
cookieImportFile.onchange = function() {
if (this.files && this.files[0]) {
importCookies(this.files[0]);
this.value = '';
}
};
}
if (cookieSearch) {
cookieSearch.oninput = function() {
renderCookies(this.value);
};
}
// Initial render
renderCookies();
// ---------- End of Cookie Manager ----------
function getAllSettings() {
const settings = {};
const knownKeys = [
'hider_freeze_memory', 'hider_freeze_global', 'hider_blocked_domains',
'hider_allowed_domains', 'hider_custom_rules_v4', 'hider_auto_time_skipper',
'hider_auto_scroll', 'hider_enable_contextmenu', 'hider_auto_remove_blur',
'hider_hidden_dock_buttons', 'hider_global_logs',
'hider_last_log_clear_day', 'hider_skip_block_confirm', 'hider_dock_y',
'hider_anti_paywall', 'hider_auto_close_modals', 'hider_cookie_consent_mode',
'hider_auto_accept_age', 'hider_auto_close_logins'
];
knownKeys.forEach(k => {
const val = gv(k, undefined);
if (val !== undefined) settings[k] = val;
});
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.startsWith('hider_site_') || key.startsWith('hider_link_'))) {
const val = localStorage.getItem(key);
try { settings[key] = JSON.parse(val); } catch { settings[key] = val; }
}
}
} catch {}
return settings;
}
}
// ---------- Render List ----------
function renderList() {
if (!shadowRoot) return;
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 = shadowBy('list-custom-rules'), cAllowed = shadowBy('list-allowed-domains'), cDomains = shadowBy('list-blocked-domains'), cSite = shadowBy('list-site'), cLink = shadowBy('list-link'), cLog = shadowBy('list-blocked-log');
if (!cSite || !cLink) return;
['cnt-custom', 'cnt-allowed', 'cnt-domains', 'cnt-logs', 'cnt-site', 'cnt-link'].forEach((id, i) => {
const el = shadowBy(id);
if(el) el.textContent = [CACHE.customRules.length, CACHE.allowedDomainsList.length, CACHE.blockedDomainsList.length, CACHE.logs.length, siteData.length, linkData.length][i];
});
const buildFrag = (arr, emptyMsg, renderItem) => {
const frag = doc.createDocumentFragment();
if (!arr.length) { const d = doc.createElement('div'); d.style.cssText = 'font-size:9px!important;color:#94a3b8!important;padding:4px!important;text-align:center!important;border:1px dashed rgba(255,255,255,0.12)!important;border-radius:6px!important;'; d.textContent = emptyMsg; frag.appendChild(d); return frag; }
arr.forEach((item, i) => frag.appendChild(renderItem(item, i))); return frag;
};
if (cCustom) {
cCustom.innerHTML = '';
cCustom.appendChild(buildFrag(CACHE.customRules, 'No custom rules', (rule, idx) => {
const div = doc.createElement('div'); div.className = 'list-item';
div.innerHTML = `<span class="rule-text" title="${rule.selector}">${rule.selector} <span style="color:${rule.target === '*' ? '#38bdf8' : '#34d399'}!important; font-weight:bold!important;">[${rule.target}]</span></span><div style="display:flex;gap:2px"><button class="hider-btn-small btn-blue btn-edit">✏️</button><button class="hider-btn-small btn-red btn-del">✖</button></div>`;
div.querySelector('.btn-edit').onclick = () => {
showRuleOverlay({
mode: 'edit',
selector: rule.selector,
scope: 'global',
target: rule.target,
editInfo: { listType: 'custom', id: rule.id, index: idx }
});
};
div.querySelector('.btn-del').onclick = () => {
CACHE.customRules = CACHE.customRules.filter(r => r.id !== rule.id);
sv('hider_custom_rules_v4', CACHE.customRules);
requestUpdateStyles(); renderList();
showToast('🗑️ Rule removed');
};
return div;
}));
}
if (cAllowed) {
cAllowed.innerHTML = '';
cAllowed.appendChild(buildFrag(CACHE.allowedDomainsList, 'No allowed domains', (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 btn-red">✖</button>`;
div.querySelector('button').onclick = () => {
CACHE.allowedDomainsList.splice(i, 1);
CACHE.allowedDomainsSet.delete(cleanDomain(d));
sv('hider_allowed_domains', CACHE.allowedDomainsList);
renderList();
showToast('🟢 Allowed domain removed');
};
return div;
}));
}
if (cDomains) {
cDomains.innerHTML = '';
cDomains.appendChild(buildFrag(CACHE.blockedDomainsList, 'No blocked domains', (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 btn-red">✖</button>`;
div.querySelector('button').onclick = () => {
CACHE.blockedDomainsList.splice(i, 1);
CACHE.blockedDomainsSet.delete(cleanDomain(d));
sv('hider_blocked_domains', CACHE.blockedDomainsList);
renderList();
showToast('🚫 Blocked domain removed');
};
return div;
}));
}
if (cLog) {
cLog.innerHTML = '';
cLog.appendChild(buildFrag(CACHE.logs, 'No logs today', item => {
const row = doc.createElement('div'); row.style.cssText = 'font-size:9px!important;border-bottom:1px solid rgba(255,255,255,0.08)!important;padding:2px 0!important;';
row.innerHTML = `<div style="display:flex;justify-content:space-between;color:#6ee7b7!important;font-weight:700!important;"><span>${item.type}</span><span style="color:#94a3b8!important;font-weight:normal!important;">${item.time}</span></div><div style="color:#cbd5e1!important;word-break:break-all!important;font-family:monospace!important;opacity:0.8;">${item.url}</div>`; return row;
}));
}
const renderEditableHiddenItems = (data, key, container) => {
container.innerHTML = '';
container.appendChild(buildFrag(data, 'None', (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-blue btn-edit" title="Edit Rule Inline">✏️</button><button class="hider-btn-small btn-purple btn-wild" title="Convert Wildcard">🪄</button><button class="hider-btn-small btn-red btn-del" title="Delete Rule">✖</button></div>`;
const textSpan = div.querySelector('.rule-text'), editBtn = div.querySelector('.btn-edit');
editBtn.onclick = () => {
let scope = 'site';
let target = location.hostname;
if (key === linkKey) {
scope = 'link';
target = cleanUrl();
}
const listType = (key === siteKey) ? 'site' : 'link';
showRuleOverlay({
mode: 'edit',
selector: sel,
scope: scope,
target: target,
editInfo: { listType: listType, index: i }
});
};
div.querySelector('.btn-wild').onclick = () => {
data[i] = convertToWildcardSelector(data[i]);
sv(key, data); requestUpdateStyles(); renderList();
showToast(`🪄 Wildcard applied`);
};
div.querySelector('.btn-del').onclick = () => {
const removed = data.splice(i, 1)[0]; sv(key, data);
requestUpdateStyles(); renderList();
showToast('🗑️ Hidden rule removed');
};
return div;
}));
};
renderEditableHiddenItems(siteData, siteKey, cSite); renderEditableHiddenItems(linkData, linkKey, cLink);
renderDockButtonOptions();
applyDockButtonVisibility();
}
// ---------- Navigation Click Handler ----------
function handleNavigationClick(e) {
const path = e.composedPath?.() || [];
if (isSelecting || path.some(el => el.id === UI_HOST_ID || el.id === STEPPER_BAR_ID)) return;
const link = e.target.closest('a[href], area[href]');
if (link?.href) {
const rawHref = link.getAttribute('href') || '';
if (rawHref.startsWith('#') || rawHref.toLowerCase().startsWith('javascript:')) return;
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 || isDomainAllowed(targetUrl)) 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);
}
}
// ---------- Scroll auto-close ----------
function setupScrollAutoClose() {
win.addEventListener('scroll', () => {
if (scrollAnimationFrame) return;
scrollAnimationFrame = win.requestAnimationFrame(() => {
scrollAnimationFrame = null;
closeAllMenus(null);
closePreviewModal();
});
}, { passive: true, capture: true });
}
// ---------- Keyboard shortcuts ----------
window.addEventListener('keydown', e => { if (e.key === 'Escape') { if (isSelecting) { turnOffHideMode(); showToast('🎯 Selection mode cancelled'); } else { closeAllMenus(null, true); closePreviewModal(); } } }, true);
// ---------- Selection mode blockers ----------
['mousedown', 'pointerdown', 'mouseup'].forEach(evtType => {
window.addEventListener(evtType, e => {
if (!isSelecting) return;
if (e.composedPath?.().some(el => el.id === UI_HOST_ID || el.id === STEPPER_BAR_ID)) return;
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
}, true);
});
// ---------- Click handling for selection ----------
window.addEventListener('click', e => {
const path = e.composedPath?.() || [];
const isUI = path.some(el => el.id === UI_HOST_ID || el.id === STEPPER_BAR_ID);
if (!isUI) { closeAllMenus(e); shadowRoot?.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 => {
const path = e.composedPath?.() || [];
if (path.some(el => el.id === UI_HOST_ID || el.id === 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 || isDomainAllowed(action)) return;
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
handleFreezeNavigation(action, 'Form Submit', () => { userApprovedNavigation = true; form.submit(); setTimeout(() => userApprovedNavigation = false, 300); }, simulateAdWindowSuccess);
}, true);
window.addEventListener('touchstart', e => {
if (!e.touches?.[0]) return;
const x = e.touches[0].clientX;
if (x <= 40 || x >= (win.innerWidth - 40)) { userApprovedNavigation = true; setTimeout(() => { userApprovedNavigation = false; }, 1500); }
}, { passive: true, capture: true });
// ---------- History navigation ----------
['back', 'forward', 'go'].forEach(method => {
const orig = history[method];
if (orig) { history[method] = function() { userApprovedNavigation = true; setTimeout(() => { userApprovedNavigation = false; }, 1000); return orig.apply(this, arguments); }; }
});
try { win.navigation?.addEventListener('navigate', e => { if (e.navigationType === 'traverse' || e.navigationType === 'reload') { userApprovedNavigation = true; setTimeout(() => { userApprovedNavigation = false; }, 1000); } }); } catch {}
win.addEventListener('beforeunload', e => {
if (userApprovedNavigation) return;
if (isFrozen && !isDomainAllowed(location.href) && (CACHE.freezeMemory === 'ask' || CACHE.freezeMemory === 'block_all')) { e.preventDefault(); return (e.returnValue = 'Page navigation is currently frozen site-wide.'); }
}, true);
// ---------- URL change detection (SMART) ----------
const checkUrlChange = () => {
if (location.href !== lastUrl) {
lastUrl = location.href;
updateCurrentLocCache();
requestUpdateStyles(false);
if (shadowBy('hider-panel')?.classList.contains('is-visible')) renderList();
}
};
window.addEventListener('pageshow', (e) => {
if (e.persisted || e.type === 'pageshow') {
requestUpdateStyles(true);
}
}, { passive: true });
['pushState', 'replaceState'].forEach(fn => {
const orig = history[fn];
if (orig) history[fn] = function() {
orig.apply(this, arguments);
checkUrlChange();
};
});
window.addEventListener('popstate', () => {
userApprovedNavigation = true;
checkUrlChange();
setTimeout(() => { userApprovedNavigation = false; }, 500);
}, { passive: true });
window.addEventListener('hashchange', checkUrlChange, { passive: true });
// ---------- Low-power heartbeat ----------
function setupLowPowerHeartbeat() {
const scheduleIdle = fn => (win.requestIdleCallback ? win.requestIdleCallback(fn, { timeout: 2000 }) : setTimeout(fn, 1000));
const performHealthCheck = () => {
if (isTop && !doc.getElementById(UI_HOST_ID)) createShadowUI();
if (!doc.getElementById('hider-dynamic-styles')) requestUpdateStyles();
if (!isTop) try { window.top.postMessage({ type: 'HIDER_REQUEST_STATE' }, '*'); } catch {}
scheduleIdle(performHealthCheck);
};
scheduleIdle(performHealthCheck);
}
// ---------- Init ----------
function init() {
updateCurrentLocCache();
installInterceptors();
createShadowUI();
requestUpdateStyles(true);
setupScrollAutoClose();
setupLowPowerHeartbeat();
setupProtectionObserver();
applyAllSettings();
if (CACHE.autoTimeSkipper && featuresEnabled) {
startAutoSkipMonitoring();
}
}
if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', init); else init();
})();