Greasy Fork is available in English.
live editor: Quick Edit, HTTP Control, Export, Visual, Tools, Danger Zone. JS Injection. Anti-Block 3.2.2.
// ==UserScript==
// @name WebEdit
// @namespace https://greasyfork.org/ru/scripts/558929
// @version 4.6.5
// @description live editor: Quick Edit, HTTP Control, Export, Visual, Tools, Danger Zone. JS Injection. Anti-Block 3.2.2.
// @author BoberBhrigu
// @contributor H7S
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addElement
// @grant GM_addStyle
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @license MIT
// @copyright 2025 H7S, boberBhrigu
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// Anti-Block 3.2.2
const HOSTILE_DOMAINS = ['meet.google.com','teams.microsoft.com','zoom.us','webex.com','whereby.com','skype.com'];
const _isHostile = HOSTILE_DOMAINS.some(d => location.hostname.includes(d));
(function antiBlock322() {
// ── Early DOM injection (CSP meta, ServiceWorker block, TrustedTypes) ──
const runEarly = (fn) => {
if (document.documentElement) fn();
else new MutationObserver((_, obs) => { obs.disconnect(); fn(); })
.observe(document, { childList: true, subtree: true });
};
runEarly(() => {
const meta = document.createElement('meta');
meta.httpEquiv = "Content-Security-Policy";
meta.content = "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; script-src * 'unsafe-inline' 'unsafe-eval' data: blob:; style-src * 'unsafe-inline' data:; object-src *; connect-src *; frame-src *; worker-src * blob: data:;";
(document.head || document.documentElement).prepend(meta);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(regs => regs.forEach(reg => reg.unregister()));
navigator.serviceWorker.register = () => Promise.reject(new Error('Blocked by WebEdit'));
}
if (window.trustedTypes) {
try {
window.trustedTypes.createPolicy('default', {
createHTML: s => s,
createScript: s => s,
createScriptURL: s => s
});
} catch (_) {}
}
});
// ── Navigator spoof ──
const _navProps = {
webdriver: { get: () => false },
plugins: { get: () => { const a = [1,2,3,4,5]; a.item = i => a[i]; a.namedItem = () => null; a.refresh = () => {}; return a; } },
languages: { get: () => ['en-US', 'en'] },
hardwareConcurrency:{ get: () => 8 },
deviceMemory: { get: () => 8 },
platform: { get: () => 'Win32' },
appVersion: { get: () => '5.0 (Windows)' },
vendor: { get: () => 'Google Inc.' },
maxTouchPoints: { get: () => 0 },
};
for (const [key, descriptor] of Object.entries(_navProps)) {
try { Object.defineProperty(navigator, key, { ...descriptor, configurable: true }); } catch(_) {}
}
try { delete window.navigator.__proto__.webdriver; } catch(_) {}
// ── Screen spoof — slight random offsets to prevent fingerprinting ──
try {
const _sw = screen.width, _sh = screen.height;
Object.defineProperty(screen, 'width', { get: () => _sw, configurable: true });
Object.defineProperty(screen, 'height', { get: () => _sh, configurable: true });
Object.defineProperty(screen, 'availWidth', { get: () => _sw, configurable: true });
Object.defineProperty(screen, 'availHeight', { get: () => _sh - 40, configurable: true });
Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
} catch(_) {}
// ── Canvas fingerprint protection ──
const _origToDataURL = HTMLCanvasElement.prototype.toDataURL;
const _origToBlob = HTMLCanvasElement.prototype.toBlob;
const _origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
HTMLCanvasElement.prototype.toDataURL = function(...args) {
const ctx = this.getContext('2d');
if (ctx) {
const imgd = _origGetImageData.call(ctx, 0, 0, 1, 1);
imgd.data[0] = (imgd.data[0] + 1) % 256;
ctx.putImageData(imgd, 0, 0);
}
return _origToDataURL.apply(this, args);
};
HTMLCanvasElement.prototype.toBlob = function(cb, ...args) {
const ctx = this.getContext('2d');
if (ctx) {
const imgd = _origGetImageData.call(ctx, 0, 0, 1, 1);
imgd.data[0] = (imgd.data[0] + 1) % 256;
ctx.putImageData(imgd, 0, 0);
}
return _origToBlob.call(this, cb, ...args);
};
CanvasRenderingContext2D.prototype.getImageData = function(...args) {
const data = _origGetImageData.apply(this, args);
for (let i = 0; i < data.data.length; i += 400) {
data.data[i] = (data.data[i] + Math.floor(Math.random() * 3)) % 256;
}
return data;
};
// ── measureText spoof — prevent font fingerprinting ──
const _origMeasureText = CanvasRenderingContext2D.prototype.measureText;
CanvasRenderingContext2D.prototype.measureText = function(...args) {
const metrics = _origMeasureText.apply(this, args);
const noise = () => Math.random() * 0.0001;
try { Object.defineProperty(metrics, 'width', { get: () => metrics.width + noise(), configurable: true }); } catch(_) {}
return metrics;
};
// ── Audio fingerprint protection ──
try {
const _origCreateAnalyser = AudioContext.prototype.createAnalyser;
const _origCreateOscillator = AudioContext.prototype.createOscillator;
const _origCreateDynamics = AudioContext.prototype.createDynamicsCompressor;
AudioContext.prototype.createAnalyser = function(...args) {
const node = _origCreateAnalyser.apply(this, args);
const _origGetFloatFreq = node.getFloatFrequencyData.bind(node);
node.getFloatFrequencyData = function(arr) {
_origGetFloatFreq(arr);
for (let i = 0; i < arr.length; i += 50) arr[i] += Math.random() * 0.0001;
};
return node;
};
} catch(_) {}
try {
const _origGetChannelData = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function(...args) {
const data = _origGetChannelData.apply(this, args);
for (let i = 0; i < data.length; i += 100) data[i] += Math.random() * 0.0001;
return data;
};
} catch(_) {}
// ── WebGL fingerprint spoof ──
try {
const _origGetParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Intel Inc.';
if (param === 37446) return 'Intel Iris OpenGL Engine';
return _origGetParam.call(this, param);
};
} catch(_) {}
try {
const _origGetParam2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Intel Inc.';
if (param === 37446) return 'Intel Iris OpenGL Engine';
return _origGetParam2.call(this, param);
};
} catch(_) {}
// ── Battery API spoof ──
try {
if (navigator.getBattery) {
navigator.getBattery = () => Promise.resolve({
charging: true, chargingTime: 0, dischargingTime: Infinity,
level: 1.0,
addEventListener: () => {}, removeEventListener: () => {}
});
}
} catch(_) {}
// ── Timezone stabilization ──
try {
const _origDateTimeFormat = Intl.DateTimeFormat;
Intl.DateTimeFormat = function(...args) {
if (args[1] && args[1].timeZone === undefined) {
args[1] = { ...args[1], timeZone: 'UTC' };
}
return new _origDateTimeFormat(...args);
};
Intl.DateTimeFormat.prototype = _origDateTimeFormat.prototype;
} catch(_) {}
// ── Fake Chrome object (pass bot detection) ──
if (!window.chrome) {
window.chrome = {
runtime: { id: undefined, connect: () => {}, sendMessage: () => {} },
loadTimes: () => ({}),
csi: () => ({}),
app: { isInstalled: false }
};
}
// ── Permissions spoof ──
try {
if (navigator.permissions) {
const _origQuery = navigator.permissions.query.bind(navigator.permissions);
navigator.permissions.query = (p) =>
p.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: _origQuery(p);
}
} catch(_) {}
// ── eval / Function source code detection bypass ──
try {
const _origFnToString = Function.prototype.toString;
Function.prototype.toString = function() {
const src = _origFnToString.call(this);
if (src.includes('we46') || src.includes('WebEdit')) return 'function() { [native code] }';
return src;
};
} catch(_) {}
// ── requestAnimationFrame timing noise (bypass timing-based bot detection) ──
const _origRAF = window.requestAnimationFrame;
window.requestAnimationFrame = function(cb) {
return _origRAF.call(window, (ts) => cb(ts + Math.random() * 0.01));
};
// ── localStorage tracker key blocking ──
const _TRACKER_KEYS = /^(_ga|_gid|_fbp|_fbc|intercom|amplitude|mixpanel|segment|heap|hotjar|clarity|bing|criteo)/i;
const _origLSSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = function(key, value) {
if (_TRACKER_KEYS.test(key)) return;
return _origLSSetItem.call(this, key, value);
};
// ── Cookie tracker blocking ──
try {
const _origCookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie')
|| Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie');
if (_origCookieDesc && _origCookieDesc.set) {
Object.defineProperty(document, 'cookie', {
set(val) {
if (_TRACKER_KEYS.test(val)) return;
_origCookieDesc.set.call(document, val);
},
get() { return _origCookieDesc.get.call(document); },
configurable: true
});
}
} catch(_) {}
// ── Intercept addEventListener — filter hostile DOMContentLoaded traps ──
const _origAddEL = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, handler, opts) {
if (_isHostile && type === 'DOMContentLoaded' && handler && handler.toString().length > 500) return;
return _origAddEL.call(this, type, handler, opts);
};
// ── MutationObserver — hide WebEdit nodes from page scripts ──
const _origMO = window.MutationObserver;
window.MutationObserver = function(cb) {
return new _origMO(function(mutations, obs) {
const filtered = mutations.filter(m => !Array.from(m.addedNodes).some(n => n.id && n.id.includes('we46')));
if (filtered.length) cb(filtered, obs);
});
};
window.MutationObserver.prototype = _origMO.prototype;
// ── createElement — neutralize antibot/fingerprint scripts ──
const _origCreateEl = Document.prototype.createElement;
Document.prototype.createElement = function(tag) {
const el = _origCreateEl.call(this, tag);
if (tag.toLowerCase() === 'script') {
const _desc = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src');
Object.defineProperty(el, 'src', {
set(val) {
if (val && /antibot|fingerprint|challenge|detector|security|captcha|botd|recaptcha/i.test(val)) {
console.warn('[WebEdit AB3.2.2] Neutralized:', val); return;
}
if (_desc && _desc.set) _desc.set.call(this, val);
},
get() { if (_desc && _desc.get) return _desc.get.call(this); return ''; }
});
}
return el;
};
// ── window.open popup blocker ──
const _origOpen = window.open;
window.open = function(...args) {
try {
const u = new URL(args[0], location.href);
if (u.origin !== location.origin) { console.warn('[WebEdit AB3.2.2] Popup blocked:', args[0]); return null; }
} catch(_) {}
return _origOpen.apply(this, args);
};
if (_isHostile) {
console.log('%c⚡ WebEdit', 'background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:4px 12px;border-radius:6px;font-weight:bold;', '— Hostile site: ' + location.hostname);
console.log('%c🛡️ Anti-Block 3.2.2 active', 'color:#f59e0b;font-weight:bold;font-size:13px;');
}
})();
// ═══════════════════════════════════════════════════════════
// CONSTANTS
// ═══════════════════════════════════════════════════════════
const VERSION = '4.6.5';
const CODENAME = 'NextGen';
const PREFIX = 'we46_';
// ECAWE — Execute Code As WebEdit (0 = off, 1 = on)
// When enabled, adds a privileged JS executor in Settings
// that runs code in userscript context (bypasses page CSP, has GM_* access)
const ECAWE = 1;
// ═══════════════════════════════════════════════════════════
// CSS
// ═══════════════════════════════════════════════════════════
const WE_CSS = `
/* ── Notifications ── */
.we-notify {
position: fixed;
bottom: 24px;
right: 24px;
padding: 12px 20px;
border-radius: 10px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
font-weight: 500;
color: #fff;
z-index: 2147483647;
opacity: 0;
transform: translateY(16px);
transition: all 0.3s cubic-bezier(.4,0,.2,1);
max-width: 320px;
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
pointer-events: none;
}
.we-notify--show { opacity: 1; transform: translateY(0); }
.we-notify--success { background: #22c55e; }
.we-notify--error { background: #ef4444; }
.we-notify--warning { background: #f59e0b; }
.we-notify--info { background: #3b82f6; }
/* ── FPS / Ping floating widgets ── */
#we46-fps,
#we46-ping {
position: fixed;
top: 12px;
left: 12px;
background: rgba(0,0,0,0.85);
color: #0f0;
padding: 8px 14px;
border-radius: 8px;
font-family: monospace;
font-size: 14px;
font-weight: 600;
z-index: 2147483646;
cursor: move;
user-select: none;
backdrop-filter: blur(8px);
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
#we46-ping {
top: 54px;
color: #4af;
}
/* ── Root container ── */
#we46-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 2147483645;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.5;
}
/* ── Panel ── */
.we46-panel {
width: 480px;
max-height: 88vh;
display: flex;
flex-direction: column;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 24px 64px rgba(0,0,0,0.55), 0 4px 16px rgba(0,0,0,0.3);
}
/* ── DARK ── */
.we46--dark .we46-panel { background: #111827; border: 1px solid #1f2937; }
.we46--dark .we46-header { background: #0d1117; border-bottom: 1px solid #1f2937; }
.we46--dark .we46-tabs { background: #0d1117; border-bottom: 1px solid #1f2937; }
.we46--dark .we46-tab { color: #6b7280; }
.we46--dark .we46-tab:hover { background: rgba(255,255,255,0.05); color: #e5e7eb; }
.we46--dark .we46-tab.active { background: #1d4ed8; color: #fff; }
.we46--dark .we46-tab--danger.active { background: #7f1d1d; color: #fca5a5; }
.we46--dark .we46-content { background: #111827; color: #e5e7eb; }
.we46--dark .we46-section-title { color: #6b7280; border-bottom: 1px solid #1f2937; }
.we46--dark .we46-btn { background: #1f2937; color: #e5e7eb; border: 1px solid #374151; }
.we46--dark .we46-btn:hover { background: #374151; border-color: #4b5563; }
.we46--dark .we46-btn--primary { background: #2563eb; border-color: #1d4ed8; color: #fff; }
.we46--dark .we46-btn--primary:hover { background: #1d4ed8; }
.we46--dark .we46-btn--danger { background: #450a0a; border-color: #7f1d1d; color: #fca5a5; }
.we46--dark .we46-btn--danger:hover { background: #7f1d1d; }
.we46--dark .we46-btn--active { background: #1e3a8a; border-color: #2563eb; color: #93c5fd; }
.we46--dark .we46-input,
.we46--dark .we46-select,
.we46--dark .we46-textarea { background: #0d1117; border: 1px solid #374151; color: #e5e7eb; }
.we46--dark .we46-input:focus,
.we46--dark .we46-textarea:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.15); }
.we46--dark .we46-overlay-panel { background: #111827; border: 1px solid #1f2937; color: #e5e7eb; }
.we46--dark .we46-overlay-header { background: #0d1117; border-bottom: 1px solid #1f2937; }
.we46--dark .we46-adv-item { background: #1f2937; border: 1px solid #374151; }
.we46--dark .we46-adv-item.selected { border-color: #2563eb; background: #1e3a8a; }
.we46--dark .we46-http-req { background: #1f2937; border: 1px solid #374151; }
.we46--dark .we46-ls-item { border-bottom: 1px solid #1f2937; }
.we46--dark .we46-toggle { background: #374151; }
.we46--dark .we46-toggle--on { background: #2563eb; }
.we46--dark .we46-title { color: #f3f4f6; }
.we46--dark .we46-logo { color: #60a5fa; }
.we46--dark .we46-danger-warning { background: rgba(127,29,29,0.3); border-color: #7f1d1d; color: #fca5a5; }
.we46--dark .we46-empty { color: #4b5563; }
.we46--dark .we46-range { accent-color: #2563eb; }
.we46--dark .we46-overlay-actions { border-color: #1f2937; }
/* ── LIGHT ── */
.we46--light .we46-panel { background: #f9fafb; border: 1px solid #e5e7eb; }
.we46--light .we46-header { background: #ffffff; border-bottom: 1px solid #e5e7eb; }
.we46--light .we46-tabs { background: #ffffff; border-bottom: 1px solid #e5e7eb; }
.we46--light .we46-tab { color: #6b7280; }
.we46--light .we46-tab:hover { background: #f3f4f6; color: #111827; }
.we46--light .we46-tab.active { background: #2563eb; color: #fff; }
.we46--light .we46-tab--danger.active { background: #dc2626; color: #fff; }
.we46--light .we46-content { background: #f9fafb; color: #111827; }
.we46--light .we46-section-title { color: #9ca3af; border-bottom: 1px solid #e5e7eb; }
.we46--light .we46-btn { background: #ffffff; color: #374151; border: 1px solid #d1d5db; }
.we46--light .we46-btn:hover { background: #f3f4f6; border-color: #9ca3af; }
.we46--light .we46-btn--primary { background: #2563eb; border-color: #1d4ed8; color: #fff; }
.we46--light .we46-btn--primary:hover { background: #1d4ed8; }
.we46--light .we46-btn--danger { background: #fee2e2; border-color: #fca5a5; color: #dc2626; }
.we46--light .we46-btn--danger:hover { background: #fecaca; }
.we46--light .we46-btn--active { background: #dbeafe; border-color: #93c5fd; color: #1d4ed8; }
.we46--light .we46-input,
.we46--light .we46-select,
.we46--light .we46-textarea { background: #fff; border: 1px solid #d1d5db; color: #111827; }
.we46--light .we46-input:focus,
.we46--light .we46-textarea:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
.we46--light .we46-overlay-panel { background: #f9fafb; border: 1px solid #e5e7eb; color: #111827; }
.we46--light .we46-overlay-header { background: #ffffff; border-bottom: 1px solid #e5e7eb; }
.we46--light .we46-adv-item { background: #fff; border: 1px solid #e5e7eb; }
.we46--light .we46-adv-item.selected { border-color: #2563eb; background: #dbeafe; }
.we46--light .we46-http-req { background: #fff; border: 1px solid #e5e7eb; }
.we46--light .we46-ls-item { border-bottom: 1px solid #f3f4f6; }
.we46--light .we46-toggle { background: #d1d5db; }
.we46--light .we46-toggle--on { background: #2563eb; }
.we46--light .we46-title { color: #111827; }
.we46--light .we46-logo { color: #2563eb; }
.we46--light .we46-danger-warning { background: #fee2e2; border-color: #fca5a5; color: #dc2626; }
.we46--light .we46-empty { color: #9ca3af; }
.we46--light .we46-range { accent-color: #2563eb; }
.we46--light .we46-overlay-actions { border-color: #e5e7eb; }
/* ── GLASS ── */
.we46--glass .we46-panel {
background: rgba(10,12,28,0.72);
border: 1px solid rgba(255,255,255,0.1);
backdrop-filter: blur(24px) saturate(1.8);
-webkit-backdrop-filter: blur(24px) saturate(1.8);
}
.we46--glass .we46-header { background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
.we46--glass .we46-tabs { background: rgba(255,255,255,0.03); border-bottom: 1px solid rgba(255,255,255,0.07); }
.we46--glass .we46-tab { color: rgba(255,255,255,0.45); }
.we46--glass .we46-tab:hover { background: rgba(255,255,255,0.07); color: rgba(255,255,255,0.9); }
.we46--glass .we46-tab.active { background: rgba(99,102,241,0.6); color: #fff; }
.we46--glass .we46-tab--danger.active { background: rgba(239,68,68,0.45); color: #fca5a5; }
.we46--glass .we46-content { background: transparent; color: #e2e8f0; }
.we46--glass .we46-section-title { color: rgba(255,255,255,0.35); border-bottom: 1px solid rgba(255,255,255,0.07); }
.we46--glass .we46-btn { background: rgba(255,255,255,0.07); color: #e2e8f0; border: 1px solid rgba(255,255,255,0.1); }
.we46--glass .we46-btn:hover { background: rgba(255,255,255,0.13); border-color: rgba(255,255,255,0.2); }
.we46--glass .we46-btn--primary { background: rgba(99,102,241,0.65); color: #fff; border-color: rgba(99,102,241,0.8); }
.we46--glass .we46-btn--primary:hover { background: rgba(99,102,241,0.8); }
.we46--glass .we46-btn--danger { background: rgba(239,68,68,0.2); color: #fca5a5; border-color: rgba(239,68,68,0.35); }
.we46--glass .we46-btn--danger:hover { background: rgba(239,68,68,0.35); }
.we46--glass .we46-btn--active { background: rgba(99,102,241,0.45); color: #c7d2fe; border-color: rgba(99,102,241,0.6); }
.we46--glass .we46-input,
.we46--glass .we46-select,
.we46--glass .we46-textarea { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); color: #e2e8f0; }
.we46--glass .we46-input:focus,
.we46--glass .we46-textarea:focus { border-color: rgba(99,102,241,0.7); box-shadow: 0 0 0 3px rgba(99,102,241,0.15); }
.we46--glass .we46-overlay-panel { background: rgba(10,12,28,0.9); border: 1px solid rgba(255,255,255,0.1); color: #e2e8f0; backdrop-filter: blur(24px); }
.we46--glass .we46-overlay-header { background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
.we46--glass .we46-adv-item { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); }
.we46--glass .we46-adv-item.selected { border-color: rgba(99,102,241,0.7); background: rgba(99,102,241,0.2); }
.we46--glass .we46-http-req { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); }
.we46--glass .we46-ls-item { border-bottom: 1px solid rgba(255,255,255,0.07); }
.we46--glass .we46-toggle { background: rgba(255,255,255,0.15); }
.we46--glass .we46-toggle--on { background: rgba(99,102,241,0.75); }
.we46--glass .we46-title { color: #f1f5f9; }
.we46--glass .we46-logo { color: #a5b4fc; }
.we46--glass .we46-danger-warning { background: rgba(239,68,68,0.1); border-color: rgba(239,68,68,0.25); color: #fca5a5; }
.we46--glass .we46-empty { color: rgba(255,255,255,0.25); }
.we46--glass .we46-range { accent-color: #818cf8; }
.we46--glass .we46-overlay-actions { border-color: rgba(255,255,255,0.07); }
/* ── NEON ── */
.we46--neon .we46-panel {
background: #04040f;
border: 1px solid #0ff;
box-shadow: 0 0 40px rgba(0,255,255,0.12), 0 24px 64px rgba(0,0,0,0.7), inset 0 0 60px rgba(0,255,255,0.02);
}
.we46--neon .we46-header { background: #020208; border-bottom: 1px solid rgba(0,255,255,0.2); }
.we46--neon .we46-tabs { background: #020208; border-bottom: 1px solid rgba(0,255,255,0.15); }
.we46--neon .we46-tab { color: rgba(0,255,255,0.5); }
.we46--neon .we46-tab:hover { background: rgba(0,255,255,0.07); color: #0ff; }
.we46--neon .we46-tab.active { background: rgba(0,255,255,0.12); color: #0ff; box-shadow: inset 0 -2px 0 #0ff; text-shadow: 0 0 8px #0ff; }
.we46--neon .we46-tab--danger.active { background: rgba(255,0,80,0.15); color: #f05; box-shadow: inset 0 -2px 0 #f05; }
.we46--neon .we46-content { background: transparent; color: #cffffe; }
.we46--neon .we46-section-title { color: rgba(0,255,255,0.5); border-bottom: 1px solid rgba(0,255,255,0.12); }
.we46--neon .we46-btn { background: rgba(0,255,255,0.05); color: #0ff; border: 1px solid rgba(0,255,255,0.3); }
.we46--neon .we46-btn:hover { background: rgba(0,255,255,0.12); box-shadow: 0 0 14px rgba(0,255,255,0.2); border-color: #0ff; }
.we46--neon .we46-btn--primary { background: rgba(0,255,255,0.18); color: #0ff; border-color: #0ff; box-shadow: 0 0 18px rgba(0,255,255,0.3); }
.we46--neon .we46-btn--primary:hover { background: rgba(0,255,255,0.28); box-shadow: 0 0 28px rgba(0,255,255,0.45); }
.we46--neon .we46-btn--danger { background: rgba(255,0,80,0.12); color: #f05; border-color: rgba(255,0,80,0.4); }
.we46--neon .we46-btn--danger:hover { background: rgba(255,0,80,0.22); box-shadow: 0 0 14px rgba(255,0,80,0.25); }
.we46--neon .we46-btn--active { background: rgba(0,255,255,0.18); color: #0ff; border-color: #0ff; box-shadow: 0 0 14px rgba(0,255,255,0.25); }
.we46--neon .we46-input,
.we46--neon .we46-select,
.we46--neon .we46-textarea { background: rgba(0,255,255,0.04); border: 1px solid rgba(0,255,255,0.2); color: #cffffe; }
.we46--neon .we46-input:focus,
.we46--neon .we46-textarea:focus { border-color: #0ff; box-shadow: 0 0 12px rgba(0,255,255,0.25); }
.we46--neon .we46-overlay-panel { background: #04040f; border: 1px solid rgba(0,255,255,0.5); color: #cffffe; box-shadow: 0 0 40px rgba(0,255,255,0.15); }
.we46--neon .we46-overlay-header { background: #020208; border-bottom: 1px solid rgba(0,255,255,0.2); }
.we46--neon .we46-adv-item { background: rgba(0,255,255,0.04); border: 1px solid rgba(0,255,255,0.12); }
.we46--neon .we46-adv-item.selected { border-color: #0ff; background: rgba(0,255,255,0.1); box-shadow: 0 0 8px rgba(0,255,255,0.15); }
.we46--neon .we46-http-req { background: rgba(0,255,255,0.04); border: 1px solid rgba(0,255,255,0.12); }
.we46--neon .we46-ls-item { border-bottom: 1px solid rgba(0,255,255,0.08); }
.we46--neon .we46-toggle { background: rgba(0,255,255,0.08); border: 1px solid rgba(0,255,255,0.2); }
.we46--neon .we46-toggle--on { background: rgba(0,255,255,0.3); border-color: #0ff; box-shadow: 0 0 14px rgba(0,255,255,0.4); }
.we46--neon .we46-title { color: #0ff; text-shadow: 0 0 14px rgba(0,255,255,0.7); }
.we46--neon .we46-logo { color: #0ff; text-shadow: 0 0 10px #0ff; }
.we46--neon .we46-danger-warning { background: rgba(255,0,80,0.08); border-color: rgba(255,0,80,0.3); color: #f05; }
.we46--neon .we46-empty { color: rgba(0,255,255,0.25); }
.we46--neon .we46-range { accent-color: #0ff; }
.we46--neon .we46-overlay-actions { border-color: rgba(0,255,255,0.1); }
/* ── Header ── */
.we46-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
cursor: move;
flex-shrink: 0;
user-select: none;
}
.we46-header-left { display: flex; align-items: center; gap: 10px; }
.we46-header-right { display: flex; align-items: center; gap: 6px; }
.we46-logo { font-size: 20px; line-height: 1; }
.we46-title { font-size: 15px; font-weight: 700; letter-spacing: -0.3px; }
.we46-version { font-size: 11px; font-weight: 500; opacity: 0.45; margin-left: 2px; }
/* ── Tabs ── */
.we46-tabs {
display: flex;
padding: 6px 8px;
gap: 3px;
overflow-x: auto;
flex-shrink: 0;
scrollbar-width: none;
scroll-behavior: smooth;
}
.we46-tabs::-webkit-scrollbar { display: none; }
.we46-tab {
display: flex;
align-items: center;
gap: 3px;
padding: 6px 7px;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
font-size: 11px;
font-weight: 500;
transition: all 0.18s;
white-space: nowrap;
flex-shrink: 0;
font-family: inherit;
}
/* ── Content area ── */
.we46-content {
flex: 1;
overflow-y: auto;
padding: 14px;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.12) transparent;
}
.we46-content::-webkit-scrollbar { width: 4px; }
.we46-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
/* ── Sections ── */
.we46-section { margin-bottom: 18px; }
.we46-section:last-child { margin-bottom: 4px; }
.we46-section-title {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
padding-bottom: 8px;
margin-bottom: 10px;
}
/* ── Buttons ── */
.we46-btn {
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.18s;
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
font-family: inherit;
line-height: 1.4;
}
.we46-btn:active { transform: scale(0.97); }
.we46-btn--full { width: 100%; }
.we46-btn--small { padding: 5px 10px; font-size: 12px; }
.we46-btn--tiny { padding: 3px 8px; font-size: 11px; border-radius: 5px; }
.we46-btn-icon {
width: 28px;
height: 28px;
border-radius: 7px;
border: none;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
color: inherit;
opacity: 0.6;
transition: all 0.18s;
font-family: inherit;
}
.we46-btn-icon:hover { opacity: 1; background: rgba(255,255,255,0.1); }
.we46-btn-group { display: flex; flex-direction: column; gap: 6px; }
.we46-btn-group--row { flex-direction: row; flex-wrap: wrap; }
/* ── Form elements ── */
.we46-form-group {
display: flex;
flex-direction: column;
gap: 5px;
margin-bottom: 10px;
}
.we46-form-group label {
font-size: 11px;
font-weight: 600;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.we46-form-row {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 10px;
}
.we46-form-row .we46-form-group { flex: 1; margin-bottom: 0; }
.we46-form-row label { white-space: nowrap; font-size: 12px; opacity: 0.7; flex-shrink: 0; }
.we46-input {
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
width: 100%;
box-sizing: border-box;
outline: none;
transition: border-color 0.18s, box-shadow 0.18s;
font-family: inherit;
}
.we46-input--small { padding: 5px 8px; font-size: 12px; }
.we46-select {
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
width: 100%;
outline: none;
cursor: pointer;
font-family: inherit;
box-sizing: border-box;
}
.we46-textarea {
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
width: 100%;
box-sizing: border-box;
resize: vertical;
outline: none;
transition: border-color 0.18s, box-shadow 0.18s;
font-family: inherit;
min-height: 80px;
}
.we46-code {
font-family: 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
font-size: 12px;
line-height: 1.6;
}
.we46-color-row { display: flex; align-items: center; gap: 8px; }
.we46-color {
width: 38px;
height: 38px;
border: 2px solid rgba(255,255,255,0.15);
border-radius: 8px;
cursor: pointer;
padding: 2px;
flex-shrink: 0;
background: transparent;
}
/* ── Range ── */
.we46-range { flex: 1; cursor: pointer; height: 4px; }
.we46-timescale-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.we46-range-val { font-size: 13px; font-weight: 700; min-width: 38px; text-align: right; font-family: monospace; }
/* ── Toggle switch ── */
.we46-toggle {
width: 46px;
height: 26px;
border-radius: 13px;
border: none;
cursor: pointer;
position: relative;
transition: all 0.25s cubic-bezier(.4,0,.2,1);
flex-shrink: 0;
padding: 0;
}
.we46-toggle-knob {
position: absolute;
top: 4px;
left: 4px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
transition: transform 0.25s cubic-bezier(.4,0,.2,1);
display: block;
box-shadow: 0 1px 4px rgba(0,0,0,0.25);
pointer-events: none;
}
.we46-toggle--on .we46-toggle-knob { transform: translateX(20px); }
.we46-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
gap: 12px;
}
.we46-toggle-row span { font-size: 13px; }
/* ── Overlay ── */
.we46-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.65);
z-index: 2147483646;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
animation: we46-fade-in 0.2s ease;
}
@keyframes we46-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.we46-overlay-panel {
width: min(680px, 95vw);
max-height: 90vh;
border-radius: 16px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 32px 80px rgba(0,0,0,0.55);
animation: we46-slide-up 0.25s cubic-bezier(.4,0,.2,1);
}
@keyframes we46-slide-up {
from { opacity: 0; transform: translateY(24px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.we46-overlay-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
flex-shrink: 0;
}
.we46-overlay-title { font-weight: 700; font-size: 15px; }
.we46-overlay-body { flex: 1; overflow-y: auto; padding: 18px; scrollbar-width: thin; }
.we46-overlay-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid transparent;
}
/* ── Quick Edit ── */
.we46-adv-toolbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.we46-adv-list { display: flex; flex-direction: column; gap: 4px; max-height: 52vh; overflow-y: auto; scrollbar-width: thin; }
.we46-adv-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.15s;
user-select: none;
}
.we46-adv-tag { font-weight: 700; font-family: monospace; color: #60a5fa; font-size: 11px; flex-shrink: 0; }
.we46-adv-id { color: #fbbf24; font-size: 11px; font-family: monospace; flex-shrink: 0; }
.we46-adv-cls { color: #4ade80; font-size: 11px; font-family: monospace; flex-shrink: 0; }
.we46-adv-text { opacity: 0.45; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.we46-el-tag-badge {
display: inline-block;
background: rgba(96,165,250,0.12);
color: #60a5fa;
border: 1px solid rgba(96,165,250,0.25);
padding: 3px 10px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
font-weight: 600;
margin-bottom: 14px;
}
.we46-code-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.we46-code-badge { padding: 3px 10px; border-radius: 6px; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; }
.we46-code-badge--html { background: rgba(234,88,12,0.18); color: #fb923c; border: 1px solid rgba(234,88,12,0.25); }
.we46-code-badge--css { background: rgba(37,99,235,0.18); color: #60a5fa; border: 1px solid rgba(37,99,235,0.25); }
.we46-code-badge--js { background: rgba(234,179,8,0.18); color: #fbbf24; border: 1px solid rgba(234,179,8,0.25); }
.we46-create-status { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 12px; opacity: 0.75; flex-wrap: wrap; }
.we46-badge-beta {
display: inline-block;
background: rgba(168,85,247,0.18);
color: #c084fc;
border: 1px solid rgba(168,85,247,0.3);
padding: 1px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 700;
margin-left: 4px;
vertical-align: middle;
letter-spacing: 0.3px;
}
/* ── HTTP Control ── */
.we46-http-toolbar { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.we46-http-block-add { display: flex; gap: 6px; margin-bottom: 8px; }
.we46-http-blocked { display: flex; flex-direction: column; gap: 4px; max-height: 90px; overflow-y: auto; margin-bottom: 4px; }
.we46-http-blocked-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font-size: 12px;
padding: 4px 2px;
}
.we46-http-blocked-item span {
font-family: monospace;
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
opacity: 0.7;
}
.we46-http-monitor { display: flex; flex-direction: column; gap: 6px; max-height: 300px; overflow-y: auto; scrollbar-width: thin; }
.we46-http-req { padding: 10px 12px; border-radius: 10px; font-size: 12px; }
.we46-http-req-top { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; flex-wrap: wrap; }
.we46-http-method { font-family: monospace; font-weight: 700; font-size: 10px; padding: 2px 7px; border-radius: 4px; flex-shrink: 0; }
.we46-http-method--get { background: rgba(34,197,94,0.18); color: #4ade80; }
.we46-http-method--post { background: rgba(59,130,246,0.18); color: #60a5fa; }
.we46-http-method--put { background: rgba(245,158,11,0.18); color: #fbbf24; }
.we46-http-method--patch { background: rgba(168,85,247,0.18); color: #c084fc; }
.we46-http-method--delete { background: rgba(239,68,68,0.18); color: #f87171; }
.we46-http-method--head { background: rgba(20,184,166,0.18); color: #2dd4bf; }
.we46-http-type { font-size: 10px; opacity: 0.4; font-family: monospace; flex-shrink: 0; }
.we46-http-status { font-family: monospace; font-weight: 700; font-size: 11px; flex-shrink: 0; }
.we46-http-status--err { color: #f87171; }
.we46-http-time { font-size: 10px; opacity: 0.35; margin-left: auto; font-family: monospace; }
.we46-http-url { font-family: monospace; font-size: 11px; opacity: 0.6; margin-bottom: 7px; word-break: break-all; line-height: 1.4; }
.we46-http-actions { display: flex; gap: 4px; flex-wrap: wrap; }
/* ── Settings LocalStorage editor ── */
.we46-ls-editor { display: flex; flex-direction: column; gap: 2px; max-height: 200px; overflow-y: auto; scrollbar-width: thin; }
.we46-ls-item { display: flex; align-items: center; gap: 6px; padding: 6px 2px; }
.we46-ls-key { font-family: monospace; font-size: 11px; color: #60a5fa; flex-shrink: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ── Danger Zone ── */
.we46-danger-warning {
text-align: center;
padding: 10px 14px;
margin-bottom: 14px;
border-radius: 10px;
font-size: 13px;
font-weight: 600;
border: 1px solid;
}
/* ── Misc ── */
.we46-empty {
text-align: center;
padding: 24px 16px;
font-size: 13px;
opacity: 0.4;
}
`;
// ═══════════════════════════════════════════════════════════
// DEFAULT CONFIG
// ═══════════════════════════════════════════════════════════
const DEFAULT_CONFIG = {
lang: 'en',
theme: 'dark',
draggable: true,
hotkeys: {
toggle: 'F7',
quickEdit: 'F3',
elementEdit: 'F4',
},
beta: {
cssCreate: false,
jsAdvancedCreate: false,
syntaxHighlight: false,
previewChanges: false,
},
tools: {
fpsCounter: false,
fpsLimit: 0,
},
antiBlock: {
force: true,
stealth: true,
adBlock: false,
tracking: false,
}
};
// ═══════════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════════
const state = {
isOpen: false,
currentTab: 'quickedit',
initialized: false,
consoleUnlocked: false,
deleteMode: false,
quickEditMode: false,
selectedElement: null,
createConfig: null,
createActive: false,
httpRequests: [],
blockedURLs: new Set(),
modifiedRequests: new Map(),
interceptType: 'both',
fpsElement: null,
pingInterval: null,
timeScaleValue: 1,
linksDisabled: false,
hiddenVisible: false,
frozenElements: [],
darkModeActive: false,
rainbowInterval: null,
changes: [],
};
// ═══════════════════════════════════════════════════════════
// STORAGE
// ═══════════════════════════════════════════════════════════
function save(key, value) {
try {
GM_setValue(PREFIX + key, JSON.stringify(value));
} catch (e) {
try { localStorage.setItem(PREFIX + key, JSON.stringify(value)); } catch (_) {}
}
}
function load(key, def = null) {
try {
const v = GM_getValue(PREFIX + key, null);
if (v !== null) return JSON.parse(v);
} catch (_) {}
try {
const v = localStorage.getItem(PREFIX + key);
if (v !== null) return JSON.parse(v);
} catch (_) {}
return def;
}
// ═══════════════════════════════════════════════════════════
// CONFIG
// ═══════════════════════════════════════════════════════════
const cfg = load('config', DEFAULT_CONFIG);
function deepMerge(target, source) {
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
target[key] = deepMerge(target[key] || {}, source[key]);
} else if (!(key in target)) {
target[key] = source[key];
}
}
return target;
}
deepMerge(cfg, DEFAULT_CONFIG);
function saveConfig() {
save('config', cfg);
}
// ═══════════════════════════════════════════════════════════
// TRANSLATIONS
// ═══════════════════════════════════════════════════════════
const i18n = {
en: {
appName: 'WebEdit',
version: 'NextGen',
tabs: {
quickedit: 'Quick Edit',
http: 'HTTP',
visual: 'Visual',
tools: 'Tools',
danger: 'Danger',
settings: 'Settings',
export: 'Export',
},
notify: {
welcome: 'WebEdit 4.6.5 ready! Press F7 or Alt+W',
deleted: 'Element deleted',
blocked: 'URL blocked',
unblocked: 'URL unblocked',
nuked: 'Site nuked 💣',
frozen: 'Page frozen ❄️',
unfrozen: 'Page unfrozen',
cleaned: 'WebEdit traces removed',
copied: 'Copied!',
saved: 'Saved',
reset: 'Settings reset',
scriptsRemoved: 'All scripts removed',
linksDisabled: 'Links disabled',
linksEnabled: 'Links enabled',
hiddenShown: 'Hidden elements revealed',
hiddenHidden: 'Hidden elements concealed',
antiBlockOn: 'Anti-Block 3.2.2 active',
fpsOn: 'FPS Counter active',
fpsOff: 'FPS Counter removed',
pingStarted: 'Ping counter started',
pingStopped: 'Ping counter stopped',
timeScale: 'Time scale set to',
createConfigured: 'Create tool configured',
createActive: 'Create tool active — click anywhere',
createDeactivated: 'Create tool deactivated',
htmlApplied: 'HTML applied',
cssApplied: 'CSS applied',
jsExecuted: 'JS executed',
adsRemoved: 'Ads removed',
mediaRemoved: 'Media removed',
darkMode: 'Dark mode applied',
lightMode: 'Light mode applied',
rainbowOn: 'Rainbow mode on 🌈',
rainbowOff: 'Rainbow mode off',
blurOn: 'Blur applied',
blurOff: 'Blur removed',
rotated: 'Page rotated',
fontIncrease: 'Font size increased',
fontDecrease: 'Font size decreased',
},
quickedit: {
deleteMode: 'Delete Mode',
deleteModeHint: 'Click any element to delete it',
advancedDelete: 'Advanced Delete',
textEdit: 'Text Editor',
elementEditor: 'Element Editor',
editHTML: 'Edit HTML',
editCSS: 'Edit CSS',
editJS: 'Edit JS',
create: 'Create',
advancedCreate: 'Advanced Create',
stopMode: 'Stop Mode',
configureFirst: 'Configure in settings first',
selectElement: 'Click an element on the page',
apply: 'Apply',
cancel: 'Cancel',
close: 'Close',
save: 'Save',
delete: 'Delete',
clone: 'Clone',
hide: 'Hide',
copy: 'Copy HTML',
protection: 'Menu protection active',
deleteAll: 'Delete All',
deleteSelected: 'Delete Selected',
selectAll: 'Select All',
deselectAll: 'Deselect All',
filterPlaceholder: 'Filter elements...',
noElements: 'No elements found',
},
http: {
monitor: 'Monitor',
blockList: 'Block List',
type: 'Type',
method: 'Method',
url: 'URL',
status: 'Status',
time: 'Time',
block: 'Block',
repeat: 'Repeat',
copyCurl: 'cURL',
clearAll: 'Clear All',
exportJSON: 'Export JSON',
addBlock: 'Add URL to block',
noRequests: 'No requests yet',
noBlocked: 'No blocked URLs',
pending: 'pending',
filterAll: 'All',
filterXHR: 'XHR',
filterFetch: 'Fetch',
},
visual: {
darkMode: 'Dark Mode',
lightMode: 'Light Mode',
fontIncrease: 'Increase Font',
fontDecrease: 'Decrease Font',
removeAds: 'Remove Ads',
rainbow: 'Rainbow 🌈',
blur: 'Blur Background',
rotate: 'Rotate Page',
},
tools: {
timeScale: 'Time Scale',
links: 'Links',
disableLinks: 'Disable Links',
enableLinks: 'Enable Links',
fpsCounter: 'FPS Counter',
pingCounter: 'Ping Counter',
scriptLoader: 'Script Loader',
bibliothek: 'Script Bibliothek',
fpsLimiter: 'FPS Limiter',
antiBlock: 'Anti-Block 3.2.2',
showHidden: 'Show Hidden Elements',
hideHidden: 'Hide Hidden Elements',
loadScript: 'Load',
scriptURL: 'Script URL or JS code...',
pingURL: 'Ping URL',
start: 'Start',
stop: 'Stop',
},
danger: {
nuke: '💣 Nuke Site',
nukeConfirm: 'Are you sure? This will DESTROY the page.',
deleteContent: '🗑️ Delete Site Content',
removeScripts: '📜 Remove All Scripts',
freeze: '❄️ Freeze Page',
unfreeze: '🔥 Unfreeze Page',
cleanSite: '🧹 Clean Site (Remove WebEdit)',
confirmAction: 'Confirm',
dangerWarning: '⚠️ These actions may be irreversible',
},
settings: {
language: 'Language',
theme: 'Menu Theme',
draggable: 'Draggable Menu',
hotkeys: 'Hotkeys',
localStorage: 'LocalStorage Editor',
beta: 'Beta Features',
betaCssCreate: 'CSS Create (Quick Edit)',
betaJsCreate: 'JS Advanced Create (Quick Edit)',
resetAll: 'Reset All Settings',
hotkeyToggle: 'Toggle Menu',
save: 'Save',
on: 'ON',
off: 'OFF',
}
},
ru: {
appName: 'WebEdit',
version: 'NextGen',
tabs: {
quickedit: 'Быстрое',
http: 'HTTP',
visual: 'Визуал',
tools: 'Инструменты',
danger: 'Опасность',
settings: 'Настройки',
export: 'Экспорт',
},
notify: {
welcome: 'WebEdit 4.6.5 готов! Нажми F7 или Alt+W',
deleted: 'Элемент удалён',
blocked: 'URL заблокирован',
unblocked: 'URL разблокирован',
nuked: 'Сайт уничтожен 💣',
frozen: 'Страница заморожена ❄️',
unfrozen: 'Страница разморожена',
cleaned: 'Следы WebEdit удалены',
copied: 'Скопировано!',
saved: 'Сохранено',
reset: 'Настройки сброшены',
scriptsRemoved: 'Все скрипты удалены',
linksDisabled: 'Ссылки отключены',
linksEnabled: 'Ссылки включены',
hiddenShown: 'Скрытые элементы видимы',
hiddenHidden: 'Скрытые элементы скрыты',
antiBlockOn: 'Anti-Block 3.2.2 активирован',
fpsOn: 'FPS счётчик активен',
fpsOff: 'FPS счётчик удалён',
pingStarted: 'Ping запущен',
pingStopped: 'Ping остановлен',
timeScale: 'Скорость времени:',
createConfigured: 'Create настроен',
createActive: 'Create активен — кликай по странице',
createDeactivated: 'Create деактивирован',
htmlApplied: 'HTML применён',
cssApplied: 'CSS применён',
jsExecuted: 'JS выполнен',
adsRemoved: 'Реклама удалена',
mediaRemoved: 'Медиа удалено',
darkMode: 'Тёмная тема применена',
lightMode: 'Светлая тема применена',
rainbowOn: 'Rainbow режим включён 🌈',
rainbowOff: 'Rainbow режим выключен',
blurOn: 'Blur применён',
blurOff: 'Blur убран',
rotated: 'Страница повёрнута',
fontIncrease: 'Шрифт увеличен',
fontDecrease: 'Шрифт уменьшен',
},
quickedit: {
deleteMode: 'Режим удаления',
deleteModeHint: 'Кликни на элемент для удаления',
advancedDelete: 'Расширенное удаление',
textEdit: 'Редактор текста',
elementEditor: 'Редактор элементов',
editHTML: 'Изменить HTML',
editCSS: 'Изменить CSS',
editJS: 'Изменить JS',
create: 'Создать',
advancedCreate: 'Расш. создание',
stopMode: 'Стоп',
configureFirst: 'Сначала настрой в настройках',
selectElement: 'Нажми на элемент страницы',
apply: 'Применить',
cancel: 'Отмена',
close: 'Закрыть',
save: 'Сохранить',
delete: 'Удалить',
clone: 'Клонировать',
hide: 'Скрыть',
copy: 'Копировать HTML',
protection: 'Защита меню активна',
deleteAll: 'Удалить все',
deleteSelected: 'Удалить выбранные',
selectAll: 'Выбрать все',
deselectAll: 'Снять выбор',
filterPlaceholder: 'Фильтр элементов...',
noElements: 'Элементы не найдены',
},
http: {
monitor: 'Монитор',
blockList: 'Блок-лист',
type: 'Тип',
method: 'Метод',
url: 'URL',
status: 'Статус',
time: 'Время',
block: 'Блок',
repeat: 'Повтор',
copyCurl: 'cURL',
clearAll: 'Очистить',
exportJSON: 'Экспорт JSON',
addBlock: 'Добавить URL в блок',
noRequests: 'Запросов нет',
noBlocked: 'Нет заблокированных URL',
pending: 'ожидание',
filterAll: 'Все',
filterXHR: 'XHR',
filterFetch: 'Fetch',
},
visual: {
darkMode: 'Тёмная тема',
lightMode: 'Светлая тема',
fontIncrease: 'Увеличить шрифт',
fontDecrease: 'Уменьшить шрифт',
removeAds: 'Удалить рекламу',
rainbow: 'Rainbow 🌈',
blur: 'Blur фон',
rotate: 'Повернуть страницу',
},
tools: {
timeScale: 'Скорость времени',
links: 'Ссылки',
disableLinks: 'Отключить ссылки',
enableLinks: 'Включить ссылки',
fpsCounter: 'FPS счётчик',
pingCounter: 'Ping счётчик',
scriptLoader: 'Загрузчик скриптов',
bibliothek: 'Библиотеки',
fpsLimiter: 'Лимит FPS',
antiBlock: 'Anti-Block 3.2.2',
showHidden: 'Показать скрытые',
hideHidden: 'Скрыть скрытые',
loadScript: 'Загрузить',
scriptURL: 'URL скрипта или JS код...',
pingURL: 'URL для пинга',
start: 'Старт',
stop: 'Стоп',
},
danger: {
nuke: '💣 Уничтожить сайт',
nukeConfirm: 'Уверен? Это УНИЧТОЖИТ страницу.',
deleteContent: '🗑️ Удалить контент',
removeScripts: '📜 Удалить все скрипты',
freeze: '❄️ Заморозить страницу',
unfreeze: '🔥 Разморозить',
cleanSite: '🧹 Очистить сайт (убрать WebEdit)',
confirmAction: 'Подтвердить',
dangerWarning: '⚠️ Эти действия могут быть необратимы',
},
settings: {
language: 'Язык',
theme: 'Тема меню',
draggable: 'Перемещение меню',
hotkeys: 'Горячие клавиши',
localStorage: 'Редактор LocalStorage',
beta: 'Beta функции',
betaCssCreate: 'CSS Create (Quick Edit)',
betaJsCreate: 'JS Advanced Create (Quick Edit)',
resetAll: 'Сбросить все настройки',
hotkeyToggle: 'Открыть/закрыть меню',
save: 'Сохранить',
on: 'ВКЛ',
off: 'ВЫКЛ',
}
}
};
function t(section, key) {
const lang = cfg.lang || 'en';
try {
return i18n[lang][section][key] || i18n['en'][section][key] || key;
} catch (_) { return key; }
}
// ═══════════════════════════════════════════════════════════
// NOTIFICATION
// ═══════════════════════════════════════════════════════════
function notify(message, type = 'success', duration = 3000) {
const el = document.createElement('div');
el.className = 'we-notify we-notify--' + type;
el.textContent = message;
document.body.appendChild(el);
requestAnimationFrame(() => el.classList.add('we-notify--show'));
setTimeout(() => {
el.classList.remove('we-notify--show');
setTimeout(() => el.remove(), 350);
}, duration);
}
// ═══════════════════════════════════════════════════════════
// CSS INJECTION
// ═══════════════════════════════════════════════════════════
function injectStyles() {
if (document.getElementById('we46-styles')) return;
GM_addStyle(WE_CSS);
// Marker so we don't re-inject
const marker = document.createElement('meta');
marker.id = 'we46-styles';
marker.name = 'we46-styles';
(document.head || document.documentElement).appendChild(marker);
}
// ═══════════════════════════════════════════════════════════
// ANTI-BLOCK — AdBlock toggle
// ═══════════════════════════════════════════════════════════
function initAdBlock() {
try { Object.defineProperty(window, 'canRunAds', { get: () => true, configurable: true }); } catch(_) {}
try { Object.defineProperty(window, 'isAdBlockActive', { get: () => false, configurable: true }); } catch(_) {}
window.adsbygoogle = window.adsbygoogle || { push: () => {} };
window.ga = window.ga || function() {};
const bait = document.createElement('div');
bait.className = 'ad ads adsbox advertisement adsbygoogle';
bait.style.cssText = 'position:absolute;width:1px;height:1px;top:-9999px;pointer-events:none;';
document.body.appendChild(bait);
const adSelectors = ['iframe[src*="ads"]','iframe[src*="doubleclick"]','[class*="advertisement"]','.adsbygoogle','ins.adsbygoogle','[data-ad-slot]','.banner-ad','.ad-container'];
adSelectors.forEach(s => document.querySelectorAll(s).forEach(el => { if (!el.closest('#we46-container')) el.remove(); }));
const origOpen = window.open;
window.open = function(...args) {
try { const u = new URL(args[0], location.href); if (u.origin !== location.origin) { notify('Popup blocked', 'warning'); return null; } } catch(_) {}
return origOpen.apply(this, args);
};
notify('AdBlock active', 'success');
}
// ═══════════════════════════════════════════════════════════
// ANTI-BLOCK — Anti-Tracking toggle
// ═══════════════════════════════════════════════════════════
function initAntiTracking() {
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(...args) {
const ctx = this.getContext('2d');
if (ctx) { ctx.fillStyle = 'rgba(255,255,255,0.01)'; ctx.fillRect(Math.random(), Math.random(), 1, 1); }
return origToDataURL.apply(this, args);
};
try {
const origGCD = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function(...args) {
const data = origGCD.apply(this, args);
for (let i = 0; i < data.length; i += 100) data[i] += Math.random() * 0.0001;
return data;
};
} catch(_) {}
notify('Anti-Tracking active', 'info');
}
function initAntiBlock() {
try {
Object.defineProperty(navigator, 'webdriver', { get: () => false, configurable: true });
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5], configurable: true });
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'], configurable: true });
try { delete window.navigator.__proto__.webdriver; } catch (_) {}
if (!window.chrome) {
window.chrome = { runtime: {}, loadTimes: () => {}, csi: () => {}, app: {} };
}
if (window.navigator.permissions) {
const origQuery = window.navigator.permissions.query.bind(navigator.permissions);
window.navigator.permissions.query = (p) =>
p.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: origQuery(p);
}
Object.defineProperty(window, 'canRunAds', { get: () => true, configurable: true });
Object.defineProperty(window, 'isAdBlockActive', { get: () => false, configurable: true });
Object.defineProperty(window, 'adBlockEnabled', { get: () => false, configurable: true });
window.adsbygoogle = window.adsbygoogle || { push: () => {} };
window._gaq = window._gaq || [];
window.ga = window.ga || function() {};
const bait = document.createElement('div');
bait.className = 'ad ads adsbox advertisement adsbygoogle';
bait.style.cssText = 'position:absolute;width:1px;height:1px;top:-9999px;pointer-events:none;';
document.body.appendChild(bait);
const origOpen = window.open;
window.open = function(...args) {
try {
const url = new URL(args[0], location.href);
if (url.origin !== location.origin) { notify('Popup blocked by Anti-Block', 'warning'); return null; }
} catch (_) {}
return origOpen.apply(this, args);
};
notify(t('notify', 'antiBlockOn'), 'success');
} catch (e) {
console.error('[WebEdit] Anti-Block error:', e);
}
}
// ═══════════════════════════════════════════════════════════
// HTTP INTERCEPTOR
// ═══════════════════════════════════════════════════════════
function initHTTPInterceptor() {
const origFetch = window.fetch;
const origXHR = window.XMLHttpRequest;
window.fetch = function(...args) {
const url = String(args[0] || '');
const options = args[1] || {};
const method = (options.method || 'GET').toUpperCase();
if (isBlocked(url)) { notify('Blocked: ' + url.substring(0, 40), 'warning'); return Promise.reject(new Error('Blocked by WebEdit')); }
applyModifications(url, options);
const req = { id: Date.now() + Math.random(), time: new Date().toLocaleTimeString(), method, url, type: 'fetch', status: '…', headers: options.headers || {}, body: options.body || null };
pushRequest(req);
return origFetch.apply(this, args)
.then(r => { req.status = r.status; return r; })
.catch(e => { req.status = 'ERR'; throw e; });
};
window.XMLHttpRequest = function() {
const xhr = new origXHR();
const origOpen = xhr.open.bind(xhr);
const origSend = xhr.send.bind(xhr);
let req = { id: Date.now() + Math.random(), time: new Date().toLocaleTimeString(), method: '', url: '', type: 'xhr', status: '…', headers: {}, body: null };
xhr.open = function(method, url, ...rest) {
req.method = method.toUpperCase();
req.url = String(url);
if (isBlocked(req.url)) { notify('Blocked: ' + req.url.substring(0, 40), 'warning'); return; }
pushRequest(req);
origOpen(method, url, ...rest);
};
xhr.send = function(body) {
req.body = body || null;
xhr.addEventListener('load', () => { req.status = xhr.status; });
xhr.addEventListener('error', () => { req.status = 'ERR'; });
origSend(body);
};
return xhr;
};
window.XMLHttpRequest.prototype = origXHR.prototype;
}
function isBlocked(url) {
for (const pattern of state.blockedURLs) {
if (url.includes(pattern)) return true;
}
return false;
}
function applyModifications(url, options) {
const mod = state.modifiedRequests.get(url);
if (!mod) return;
if (mod.headers) options.headers = { ...options.headers, ...mod.headers };
if (mod.method) options.method = mod.method;
}
function pushRequest(req) {
if (state.interceptType === 'xhr' && req.type !== 'xhr') return;
if (state.interceptType === 'fetch' && req.type !== 'fetch') return;
state.httpRequests.unshift(req);
if (state.httpRequests.length > 300) state.httpRequests.pop();
if (state.currentTab === 'http') renderHTTPMonitor();
}
// ═══════════════════════════════════════════════════════════
// FPS COUNTER
// ═══════════════════════════════════════════════════════════
let fpsRAF = null;
let fpsLastTime = performance.now();
let fpsFrames = 0;
function initFPSCounter() {
if (state.fpsElement) return;
const el = document.createElement('div');
el.id = 'we46-fps';
el.innerHTML = '<span id="we46-fps-val">--</span> FPS';
document.body.appendChild(el);
state.fpsElement = el;
makeDraggable(el);
function loop(now) {
fpsFrames++;
const elapsed = now - fpsLastTime;
if (elapsed >= 1000) {
const fps = Math.round((fpsFrames * 1000) / elapsed);
const val = document.getElementById('we46-fps-val');
if (val) { val.textContent = fps; el.style.color = fps >= 50 ? '#0f0' : fps >= 30 ? '#ff0' : '#f00'; }
fpsFrames = 0; fpsLastTime = now;
}
if (cfg.tools.fpsLimit > 0) {
const delay = Math.max(0, (1000 / cfg.tools.fpsLimit) - (performance.now() - now));
fpsRAF = setTimeout(() => requestAnimationFrame(loop), delay);
} else {
fpsRAF = requestAnimationFrame(loop);
}
}
fpsRAF = requestAnimationFrame(loop);
notify(t('notify', 'fpsOn'));
}
function removeFPSCounter() {
if (state.fpsElement) { state.fpsElement.remove(); state.fpsElement = null; }
if (fpsRAF) { clearTimeout(fpsRAF); cancelAnimationFrame(fpsRAF); fpsRAF = null; }
notify(t('notify', 'fpsOff'), 'info');
}
// ═══════════════════════════════════════════════════════════
// PING COUNTER
// ═══════════════════════════════════════════════════════════
let pingEl = null;
function startPing(url) {
if (pingEl) stopPing();
pingEl = document.createElement('div');
pingEl.id = 'we46-ping';
pingEl.innerHTML = 'PING: <span id="we46-ping-val">--</span> ms';
document.body.appendChild(pingEl);
makeDraggable(pingEl);
const targetUrl = url || location.origin;
state.pingInterval = setInterval(async () => {
const start = performance.now();
try {
await fetch(targetUrl + '?_t=' + Date.now(), { method: 'HEAD', cache: 'no-store' });
const ms = Math.round(performance.now() - start);
const val = document.getElementById('we46-ping-val');
if (val) { val.textContent = ms; pingEl.style.color = ms < 100 ? '#0f0' : ms < 300 ? '#ff0' : '#f00'; }
} catch (_) {
const val = document.getElementById('we46-ping-val');
if (val) val.textContent = 'ERR';
}
}, 2000);
notify(t('notify', 'pingStarted'));
}
function stopPing() {
clearInterval(state.pingInterval);
if (pingEl) { pingEl.remove(); pingEl = null; }
notify(t('notify', 'pingStopped'), 'info');
}
// ═══════════════════════════════════════════════════════════
// DRAGGABLE
// ═══════════════════════════════════════════════════════════
function makeDraggable(el, handle) {
handle = handle || el;
let x = 0, y = 0, ox = 0, oy = 0;
handle.style.cursor = 'move';
handle.addEventListener('mousedown', function(e) {
if (e.target.closest('button, input, textarea, select, a')) return;
e.preventDefault(); ox = e.clientX; oy = e.clientY;
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
});
function drag(e) {
x = ox - e.clientX; y = oy - e.clientY; ox = e.clientX; oy = e.clientY;
el.style.top = (el.offsetTop - y) + 'px';
el.style.left = (el.offsetLeft - x) + 'px';
el.style.right = 'auto'; el.style.bottom = 'auto';
}
function stopDrag() {
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', stopDrag);
}
}
// ═══════════════════════════════════════════════════════════
// VISUAL TOOLS
// ═══════════════════════════════════════════════════════════
let darkStyleEl = null;
let blurStyleEl = null;
function applyDarkMode() {
if (darkStyleEl) { darkStyleEl.remove(); darkStyleEl = null; state.darkModeActive = false; return; }
darkStyleEl = document.createElement('style');
darkStyleEl.id = 'we46-dark';
darkStyleEl.textContent = `html { filter: invert(1) hue-rotate(180deg) !important; } img, video, canvas, svg, picture { filter: invert(1) hue-rotate(180deg) !important; }`;
document.head.appendChild(darkStyleEl);
state.darkModeActive = true;
notify(t('notify', 'darkMode'));
}
function applyLightMode() {
if (darkStyleEl) { darkStyleEl.remove(); darkStyleEl = null; }
const el = document.createElement('style');
el.id = 'we46-light';
el.textContent = `html,body,* { background: #fff !important; color: #000 !important; border-color: #ccc !important; }`;
document.head.appendChild(el);
notify(t('notify', 'lightMode'));
}
function removeAds() {
const selectors = [
'iframe[src*="ads"]','iframe[src*="doubleclick"]','iframe[src*="googlesyndication"]',
'[class*="ad-"]','[class*="ads-"]','[class*="advertisement"]','[class*="advert"]',
'[id*="ad-"]','[id*="ads-"]','[id*="google_ads"]','[id*="advertisement"]',
'.adsbygoogle','ins.adsbygoogle','[data-ad-slot]','[data-ad-client]',
'.banner-ad','.display-ad','.ad-container','.ad-wrapper','.ad-unit',
];
let count = 0;
selectors.forEach(s => {
document.querySelectorAll(s).forEach(el => { if (!el.closest('#we46-container')) { el.remove(); count++; } });
});
notify(`${t('notify', 'adsRemoved')} (${count})`);
}
function removeAllMedia() {
let count = 0;
document.querySelectorAll('video, audio, img, iframe').forEach(el => { if (!el.closest('#we46-container')) { el.remove(); count++; } });
notify(`${t('notify', 'mediaRemoved')} (${count})`);
}
let fontScale = 1;
function increaseFonts() { fontScale = Math.min(fontScale + 0.1, 2.5); document.body.style.fontSize = (fontScale * 16) + 'px'; notify(t('notify', 'fontIncrease')); }
function decreaseFonts() { fontScale = Math.max(fontScale - 0.1, 0.5); document.body.style.fontSize = (fontScale * 16) + 'px'; notify(t('notify', 'fontDecrease')); }
let rainbowStyleEl = null;
function toggleRainbow() {
if (rainbowStyleEl) { rainbowStyleEl.remove(); rainbowStyleEl = null; notify(t('notify', 'rainbowOff'), 'info'); return; }
rainbowStyleEl = document.createElement('style');
rainbowStyleEl.textContent = `@keyframes we-rainbow { 0%{filter:hue-rotate(0deg)} 100%{filter:hue-rotate(360deg)} } html { animation: we-rainbow 3s linear infinite !important; }`;
document.head.appendChild(rainbowStyleEl);
notify(t('notify', 'rainbowOn'));
}
function toggleBlur() {
if (blurStyleEl) { blurStyleEl.remove(); blurStyleEl = null; notify(t('notify', 'blurOff'), 'info'); return; }
blurStyleEl = document.createElement('style');
blurStyleEl.textContent = `body > *:not(#we46-container) { filter: blur(5px) !important; transition: filter 0.3s; }`;
document.head.appendChild(blurStyleEl);
notify(t('notify', 'blurOn'));
}
let pageRotated = false;
function rotatePage() {
pageRotated = !pageRotated;
document.body.style.transform = pageRotated ? 'rotate(180deg)' : '';
document.body.style.transformOrigin = '50% 50%';
notify(t('notify', 'rotated'));
}
// ═══════════════════════════════════════════════════════════
// PAGE TOOLS
// ═══════════════════════════════════════════════════════════
function setTimeScale(value) {
document.documentElement.style.setProperty('--we-time-scale', value);
const styleId = 'we46-timescale';
let el = document.getElementById(styleId);
if (!el) { el = document.createElement('style'); el.id = styleId; document.head.appendChild(el); }
el.textContent = `* { animation-duration: calc(var(--we-dur, 1s) / ${value}) !important; transition-duration: calc(var(--we-dur, 0.3s) / ${value}) !important; }`;
state.timeScaleValue = value;
notify(t('notify', 'timeScale') + ' ' + value + 'x');
}
function toggleLinks() {
state.linksDisabled = !state.linksDisabled;
document.querySelectorAll('a').forEach(a => { if (!a.closest('#we46-container')) a.style.pointerEvents = state.linksDisabled ? 'none' : ''; });
notify(state.linksDisabled ? t('notify', 'linksDisabled') : t('notify', 'linksEnabled'), state.linksDisabled ? 'warning' : 'success');
}
function toggleHiddenElements() {
state.hiddenVisible = !state.hiddenVisible;
const styleId = 'we46-hidden';
let el = document.getElementById(styleId);
if (state.hiddenVisible) {
if (!el) { el = document.createElement('style'); el.id = styleId; document.head.appendChild(el); }
el.textContent = `[style*="display: none"], [style*="display:none"], [hidden], [style*="visibility: hidden"] { display: block !important; visibility: visible !important; opacity: 0.6 !important; outline: 2px dashed #f50 !important; }`;
notify(t('notify', 'hiddenShown'), 'info');
} else {
if (el) el.remove();
notify(t('notify', 'hiddenHidden'), 'info');
}
}
function removeAllScripts() {
document.querySelectorAll('script').forEach(s => { if (!s.closest('#we46-container')) s.remove(); });
notify(t('notify', 'scriptsRemoved'), 'warning');
}
let frozen = false;
function toggleFreeze() {
frozen = !frozen;
document.body.style.pointerEvents = frozen ? 'none' : '';
document.body.style.userSelect = frozen ? 'none' : '';
notify(frozen ? t('notify', 'frozen') : t('notify', 'unfrozen'), frozen ? 'warning' : 'success');
}
function loadScript(url) {
if (url.trim().startsWith('http')) {
const s = document.createElement('script'); s.src = url; document.head.appendChild(s);
} else {
try { (0, eval)(url); notify(t('notify', 'jsExecuted')); }
catch(e) { notify('JS Error: ' + e.message, 'error'); }
}
}
const LIBRARIES = {
jQuery: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js',
Lodash: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
Axios: 'https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.8/axios.min.js',
GSAP: 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js',
'Three.js': 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js',
Anime: 'https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js',
Moment: 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js',
};
// ═══════════════════════════════════════════════════════════
// EXPORT FUNCTIONS
// ═══════════════════════════════════════════════════════════
function downloadText(content, filename, type = 'text/plain') {
const blob = new Blob([content], { type });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click();
}
function exportHTML() { downloadText(document.documentElement.outerHTML, 'page.html', 'text/html'); state.changes.push({ time: new Date().toISOString(), action: 'Export HTML' }); notify('HTML exported!'); }
function exportCSS() {
let css = '';
Array.from(document.styleSheets).forEach(sheet => { try { css += Array.from(sheet.cssRules).map(r => r.cssText).join('\n') + '\n\n'; } catch(_) {} });
downloadText(css, 'styles.css', 'text/css'); state.changes.push({ time: new Date().toISOString(), action: 'Export CSS' }); notify('CSS exported!');
}
function exportJS() {
let js = '';
Array.from(document.scripts).forEach(s => { if (s.src) js += '// External: ' + s.src + '\n'; else if (s.textContent) js += s.textContent + '\n\n'; });
downloadText(js, 'scripts.js', 'text/javascript'); state.changes.push({ time: new Date().toISOString(), action: 'Export JS' }); notify('JS exported!');
}
function exportImages() {
const imgs = Array.from(document.images).map(img => img.src).filter(Boolean);
downloadText(imgs.join('\n'), 'images.txt'); state.changes.push({ time: new Date().toISOString(), action: 'Export Images' }); notify('Images exported! (' + imgs.length + ')');
}
function exportSite() {
let css = '', js = '';
Array.from(document.styleSheets).forEach(sheet => { try { css += Array.from(sheet.cssRules).map(r => r.cssText).join('\n'); } catch(_) {} });
Array.from(document.scripts).forEach(s => { if (s.textContent) js += s.textContent + '\n'; });
const html = '<!DOCTYPE html>\n<html>\n<head>\n<style>\n' + css + '\n</style>\n</head>\n<body>\n' + document.body.innerHTML + '\n<script>\n' + js + '\n</script>\n</body>\n</html>';
downloadText(html, 'site.html', 'text/html'); state.changes.push({ time: new Date().toISOString(), action: 'Export Site' }); notify('Site exported!');
}
function exportChanges() { downloadJSON(state.changes, 'webedit-changes.json'); notify('Changes exported!'); }
function exportLogs() { downloadJSON(state.httpRequests, 'http-logs.json'); notify('Logs exported!'); }
// ═══════════════════════════════════════════════════════════
// TAB: EXPORT
// ═══════════════════════════════════════════════════════════
function renderExport(c) {
const items = [
{ icon: '📄', label: 'HTML', desc: 'Full page HTML', fn: exportHTML },
{ icon: '🎨', label: 'CSS', desc: 'All stylesheets', fn: exportCSS },
{ icon: '⚙️', label: 'JS', desc: 'All scripts', fn: exportJS },
{ icon: '🖼️', label: 'Images', desc: 'All image URLs', fn: exportImages },
{ icon: '🌐', label: 'Full Site', desc: 'HTML + CSS + JS', fn: exportSite },
{ icon: '📝', label: 'Changes', desc: 'WebEdit changes log', fn: exportChanges },
{ icon: '📋', label: 'HTTP Logs', desc: 'All HTTP requests', fn: exportLogs },
];
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">📦 Export</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
${items.map((item, i) => `
<div data-export="${i}" style="display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 10px;border-radius:10px;cursor:pointer;text-align:center;gap:5px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.04);transition:all 0.18s;">
<div style="font-size:24px;">${item.icon}</div>
<div style="font-size:12px;font-weight:600;">${item.label}</div>
<div style="font-size:10px;opacity:0.5;">${item.desc}</div>
</div>`).join('')}
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">📝 Changes log (${state.changes.length})</div>
<div style="max-height:120px;overflow-y:auto;font-size:11px;opacity:0.65;">
${state.changes.length === 0 ? '<div class="we46-empty">No changes yet</div>' :
state.changes.slice(-10).reverse().map(ch => `<div style="padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.06);">${ch.time.split('T')[1].split('.')[0]} — ${ch.action}</div>`).join('')}
</div>
</div>`;
items.forEach((item, i) => {
const el = c.querySelector(`[data-export="${i}"]`);
el.addEventListener('click', item.fn);
el.addEventListener('mouseenter', function() { this.style.borderColor = '#2563eb'; this.style.background = 'rgba(37,99,235,0.1)'; });
el.addEventListener('mouseleave', function() { this.style.borderColor = 'rgba(255,255,255,0.1)'; this.style.background = 'rgba(255,255,255,0.04)'; });
});
}
// ═══════════════════════════════════════════════════════════
// NUKE / DANGER HELPERS
// ═══════════════════════════════════════════════════════════
function nukeSite() {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:#000;z-index:2147483647;display:flex;align-items:center;justify-content:center;flex-direction:column;';
overlay.innerHTML = `
<div style="font-size:80px;animation:we-nuke-shake 0.1s infinite;">💣</div>
<div style="color:#f00;font-size:48px;font-weight:bold;font-family:monospace;margin-top:20px;">NUKE INITIATED</div>
<div style="color:#ff0;font-family:monospace;font-size:20px;margin-top:10px;">WebEdit 4.6.5</div>
`;
const style = document.createElement('style');
style.textContent = '@keyframes we-nuke-shake{0%,100%{transform:translate(0)}25%{transform:translate(-5px,5px)}75%{transform:translate(5px,-5px)}}';
overlay.appendChild(style);
document.body.appendChild(overlay);
setTimeout(() => {
overlay.style.background = '#f00';
setTimeout(() => {
document.body.innerHTML = `<div style="background:#000;color:#f00;height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column;font-family:monospace;">
<div style="font-size:100px;">💀</div>
<div style="font-size:40px;margin-top:20px;">NUKED</div>
<div style="font-size:16px;color:#888;margin-top:10px;">by WebEdit 4.6.5</div>
</div>`;
}, 500);
}, 2000);
}
function deleteSiteContent() {
document.querySelectorAll('body > *').forEach(el => { if (el.id !== 'we46-container') el.remove(); });
}
function cleanSite() {
document.getElementById('we46-container')?.remove();
document.getElementById('we46-styles')?.remove();
document.getElementById('we46-fps')?.remove();
document.getElementById('we46-dark')?.remove();
document.getElementById('we46-light')?.remove();
document.getElementById('we46-timescale')?.remove();
document.getElementById('we46-hidden')?.remove();
rainbowStyleEl?.remove();
blurStyleEl?.remove();
pingEl?.remove();
try {
['config', 'blockedURLs', 'modifiedRequests'].forEach(k => GM_setValue(PREFIX + k, null));
} catch (_) {
Object.keys(localStorage).filter(k => k.startsWith(PREFIX)).forEach(k => localStorage.removeItem(k));
}
notify(t('notify', 'cleaned'), 'success');
state.isOpen = false;
}
// ═══════════════════════════════════════════════════════════
// QUICK EDIT — Delete Mode
// ═══════════════════════════════════════════════════════════
let deleteHighlight = null;
function startDeleteMode() {
state.deleteMode = true;
document.body.style.cursor = 'crosshair';
document.addEventListener('mouseover', onDeleteHover, true);
document.addEventListener('click', onDeleteClick, true);
document.addEventListener('keydown', onDeleteEsc, true);
}
function stopDeleteMode() {
state.deleteMode = false;
document.body.style.cursor = '';
document.removeEventListener('mouseover', onDeleteHover, true);
document.removeEventListener('click', onDeleteClick, true);
document.removeEventListener('keydown', onDeleteEsc, true);
clearDeleteHighlight();
}
function onDeleteHover(e) {
if (e.target.closest('#we46-container')) return;
clearDeleteHighlight();
deleteHighlight = e.target;
deleteHighlight.style.outline = '2px solid #f44';
deleteHighlight.style.outlineOffset = '1px';
}
function clearDeleteHighlight() {
if (deleteHighlight) { deleteHighlight.style.outline = ''; deleteHighlight.style.outlineOffset = ''; deleteHighlight = null; }
}
function onDeleteClick(e) {
if (e.target.closest('#we46-container')) return;
e.preventDefault(); e.stopPropagation();
clearDeleteHighlight(); e.target.remove(); notify(t('notify', 'deleted'));
}
function onDeleteEsc(e) { if (e.key === 'Escape') stopDeleteMode(); }
// ═══════════════════════════════════════════════════════════
// QUICK EDIT — Element Picker
// ═══════════════════════════════════════════════════════════
let pickerCallback = null;
let pickerHighlight = null;
function startPicker(callback) {
pickerCallback = callback;
document.body.style.cursor = 'crosshair';
document.addEventListener('mouseover', onPickerHover, true);
document.addEventListener('click', onPickerClick, true);
document.addEventListener('keydown', onPickerEsc, true);
}
function stopPicker() {
pickerCallback = null;
document.body.style.cursor = '';
document.removeEventListener('mouseover', onPickerHover, true);
document.removeEventListener('click', onPickerClick, true);
document.removeEventListener('keydown', onPickerEsc, true);
clearPickerHighlight();
}
function onPickerHover(e) {
if (e.target.closest('#we46-container')) return;
clearPickerHighlight();
pickerHighlight = e.target;
pickerHighlight.style.outline = '2px solid #4af';
pickerHighlight.style.outlineOffset = '1px';
}
function clearPickerHighlight() {
if (pickerHighlight) { pickerHighlight.style.outline = ''; pickerHighlight.style.outlineOffset = ''; pickerHighlight = null; }
}
function onPickerClick(e) {
if (e.target.closest('#we46-container')) return;
e.preventDefault(); e.stopPropagation();
const target = e.target;
clearPickerHighlight();
const cb = pickerCallback;
stopPicker();
if (cb) cb(target);
}
function onPickerEsc(e) { if (e.key === 'Escape') stopPicker(); }
// ═══════════════════════════════════════════════════════════
// CREATE TOOL
// ═══════════════════════════════════════════════════════════
let createClickHandler = null;
function activateCreate() {
if (!state.createConfig) { notify(t('quickedit', 'configureFirst'), 'warning'); return; }
state.createActive = true;
document.body.style.cursor = 'crosshair';
createClickHandler = function(e) {
if (e.target.closest('#we46-container')) return;
e.preventDefault(); e.stopPropagation();
const el = buildCreateElement(state.createConfig);
e.target.appendChild(el);
};
document.addEventListener('click', createClickHandler, true);
notify(t('notify', 'createActive'));
}
function deactivateCreate() {
state.createActive = false;
document.body.style.cursor = '';
if (createClickHandler) { document.removeEventListener('click', createClickHandler, true); createClickHandler = null; }
notify(t('notify', 'createDeactivated'), 'info');
}
function buildCreateElement(config) {
const el = document.createElement(config.tag);
if (config.text) el.textContent = config.text;
if (config.css) el.style.cssText = config.css;
if (config.attrs) Object.entries(config.attrs).forEach(([k, v]) => el.setAttribute(k, v));
return el;
}
// ═══════════════════════════════════════════════════════════
// UI BUILDER
// ═══════════════════════════════════════════════════════════
function createUI() {
if (document.getElementById('we46-container')) return;
const container = document.createElement('div');
container.id = 'we46-container';
container.className = 'we46 we46--' + (cfg.theme || 'dark');
container.innerHTML = `
<div class="we46-panel" id="we46-panel">
<div class="we46-header" id="we46-header">
<div class="we46-header-left">
<span class="we46-logo">⚡</span>
<span class="we46-title">WebEdit <span class="we46-version">4.6.5</span></span>
</div>
<div class="we46-header-right">
<button class="we46-btn-icon" id="we46-minimize" title="Minimize">—</button>
<button class="we46-btn-icon" id="we46-close" title="Close">×</button>
</div>
</div>
<div class="we46-tabs" id="we46-tabs">
<button class="we46-tab active" data-tab="quickedit">✏️ <span>${t('tabs','quickedit')}</span></button>
<button class="we46-tab" data-tab="http">🌐 <span>${t('tabs','http')}</span></button>
<button class="we46-tab" data-tab="visual">🎨 <span>${t('tabs','visual')}</span></button>
<button class="we46-tab" data-tab="tools">🛠️ <span>${t('tabs','tools')}</span></button>
<button class="we46-tab" data-tab="export">📦 <span>${t('tabs','export')}</span></button>
<button class="we46-tab we46-tab--danger" data-tab="danger">☠️ <span>${t('tabs','danger')}</span></button>
<button class="we46-tab" data-tab="settings">⚙️ <span>${t('tabs','settings')}</span></button>
</div>
<div class="we46-content" id="we46-content"></div>
</div>
`;
document.body.appendChild(container);
injectStyles();
if (cfg.draggable) makeDraggable(container, container.querySelector('#we46-header'));
document.getElementById('we46-close').addEventListener('click', toggleUI);
document.getElementById('we46-minimize').addEventListener('click', () => {
const content = document.getElementById('we46-content');
const tabs = document.getElementById('we46-tabs');
const minimized = content.style.display === 'none';
content.style.display = minimized ? '' : 'none';
tabs.style.display = minimized ? '' : 'none';
});
const tabsEl = document.getElementById('we46-tabs');
if (tabsEl) {
tabsEl.addEventListener('wheel', function(e) { e.preventDefault(); tabsEl.scrollLeft += e.deltaY; }, { passive: false });
}
document.querySelectorAll('.we46-tab').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.we46-tab').forEach(b => b.classList.remove('active'));
this.classList.add('active');
switchTab(this.dataset.tab);
});
});
container.style.display = 'none';
switchTab('quickedit');
}
function toggleUI() {
state.isOpen = !state.isOpen;
const container = document.getElementById('we46-container');
if (container) container.style.display = state.isOpen ? 'block' : 'none';
}
function switchTab(tabName) {
state.currentTab = tabName;
const content = document.getElementById('we46-content');
if (!content) return;
content.innerHTML = '';
switch (tabName) {
case 'quickedit': renderQuickEdit(content); break;
case 'http': renderHTTP(content); break;
case 'visual': renderVisual(content); break;
case 'tools': renderTools(content); break;
case 'danger': renderDanger(content); break;
case 'export': renderExport(content); break;
case 'settings': renderSettings(content); break;
}
}
// ═══════════════════════════════════════════════════════════
// TAB: QUICK EDIT
// ═══════════════════════════════════════════════════════════
function renderQuickEdit(c) {
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">🗑️ Delete</div>
<div class="we46-btn-group">
<button class="we46-btn ${state.deleteMode ? 'we46-btn--active' : ''}" id="qe-delete">${state.deleteMode ? '⏹ ' + t('quickedit','deleteMode') : t('quickedit','deleteMode')}</button>
<button class="we46-btn we46-btn--secondary" id="qe-adv-delete">${t('quickedit','advancedDelete')}</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">✏️ Edit</div>
<div class="we46-btn-group">
<button class="we46-btn" id="qe-text">${t('quickedit','textEdit')}</button>
<button class="we46-btn" id="qe-element">${t('quickedit','elementEditor')}</button>
<button class="we46-btn" id="qe-html">${t('quickedit','editHTML')}</button>
<button class="we46-btn" id="qe-css">${t('quickedit','editCSS')}</button>
<button class="we46-btn" id="qe-js">${t('quickedit','editJS')}</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">➕ Create</div>
<div class="we46-btn-group">
<button class="we46-btn" id="qe-create">${t('quickedit','create')}</button>
<button class="we46-btn we46-btn--secondary" id="qe-adv-create">${t('quickedit','advancedCreate')}</button>
</div>
${state.createConfig ? `
<div class="we46-create-status">
Configured: <b>${state.createConfig.tag}</b>
<button class="we46-btn we46-btn--small ${state.createActive ? 'we46-btn--active' : ''}" id="qe-create-toggle">
${state.createActive ? t('quickedit','stopMode') : 'Activate'}
</button>
</div>
` : ''}
</div>
`;
document.getElementById('qe-delete').addEventListener('click', () => {
if (state.deleteMode) { stopDeleteMode(); } else { startDeleteMode(); notify(t('quickedit','deleteModeHint'), 'info', 4000); }
renderQuickEdit(c);
});
document.getElementById('qe-adv-delete').addEventListener('click', () => openAdvancedDelete());
document.getElementById('qe-text').addEventListener('click', () => { startPicker(el => openTextEditor(el)); notify(t('quickedit','selectElement'), 'info', 3000); });
document.getElementById('qe-element').addEventListener('click', () => { startPicker(el => openElementEditor(el)); notify(t('quickedit','selectElement'), 'info', 3000); });
document.getElementById('qe-html').addEventListener('click', () => { startPicker(el => openCodeEditor('html', el)); notify(t('quickedit','selectElement'), 'info', 3000); });
document.getElementById('qe-css').addEventListener('click', () => { startPicker(el => openCodeEditor('css', el)); notify(t('quickedit','selectElement'), 'info', 3000); });
document.getElementById('qe-js').addEventListener('click', () => { startPicker(el => openCodeEditor('js', el)); notify(t('quickedit','selectElement'), 'info', 3000); });
document.getElementById('qe-create').addEventListener('click', () => openCreateConfig());
document.getElementById('qe-adv-create').addEventListener('click', () => openAdvancedCreate());
document.getElementById('qe-create-toggle')?.addEventListener('click', () => {
if (state.createActive) deactivateCreate(); else activateCreate();
renderQuickEdit(c);
});
}
// ── Advanced Delete Overlay ──
function openAdvancedDelete() {
const overlay = createOverlay('we46-adv-delete-overlay');
const elements = Array.from(document.querySelectorAll('body *')).filter(el =>
!el.closest('#we46-container') && !['SCRIPT','STYLE','LINK','META'].includes(el.tagName)
);
let selected = new Set();
let filterText = '';
function render() {
const filtered = elements.filter(el => {
const tag = el.tagName.toLowerCase();
const cls = el.className && typeof el.className === 'string' ? el.className : '';
const id = el.id || '';
const q = filterText.toLowerCase();
return !q || tag.includes(q) || cls.includes(q) || id.includes(q);
});
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-adv-toolbar">
<input class="we46-input" id="adv-filter" placeholder="${t('quickedit','filterPlaceholder')}" value="${filterText}">
<button class="we46-btn we46-btn--small" id="adv-select-all">${t('quickedit','selectAll')}</button>
<button class="we46-btn we46-btn--small" id="adv-deselect">${t('quickedit','deselectAll')}</button>
<button class="we46-btn we46-btn--danger we46-btn--small" id="adv-delete-selected">${t('quickedit','deleteSelected')} (${selected.size})</button>
<button class="we46-btn we46-btn--danger we46-btn--small" id="adv-delete-all">${t('quickedit','deleteAll')}</button>
</div>
<div class="we46-adv-list">
${filtered.length === 0 ? `<div class="we46-empty">${t('quickedit','noElements')}</div>` :
filtered.map((el, i) => `
<label class="we46-adv-item ${selected.has(el) ? 'selected' : ''}">
<input type="checkbox" data-i="${i}" ${selected.has(el) ? 'checked' : ''}>
<span class="we46-adv-tag">${el.tagName.toLowerCase()}</span>
${el.id ? `<span class="we46-adv-id">#${el.id}</span>` : ''}
${el.className && typeof el.className === 'string' && el.className.trim() ? `<span class="we46-adv-cls">.${el.className.trim().split(' ')[0]}</span>` : ''}
<span class="we46-adv-text">${(el.textContent || '').trim().substring(0, 40)}</span>
</label>
`).join('')}
</div>
`;
overlay.querySelector('#adv-filter').addEventListener('input', function() { filterText = this.value; render(); });
overlay.querySelector('#adv-select-all').addEventListener('click', () => { filtered.forEach(el => selected.add(el)); render(); });
overlay.querySelector('#adv-deselect').addEventListener('click', () => { selected.clear(); render(); });
overlay.querySelector('#adv-delete-selected').addEventListener('click', () => {
selected.forEach(el => { try { el.remove(); } catch (_) {} });
notify(`${t('notify','deleted')} (${selected.size})`); overlay.remove();
});
overlay.querySelector('#adv-delete-all').addEventListener('click', () => {
filtered.forEach(el => { try { el.remove(); } catch (_) {} });
notify(`${t('notify','deleted')} (${filtered.length})`); overlay.remove();
});
overlay.querySelectorAll('input[type=checkbox]').forEach(cb => {
cb.addEventListener('change', function() {
const el = filtered[parseInt(this.dataset.i)];
if (this.checked) selected.add(el); else selected.delete(el);
render();
});
});
}
render();
document.body.appendChild(overlay);
}
// ── Text Editor Overlay ──
function openTextEditor(el) {
const overlay = createOverlay('we46-text-editor-overlay');
const computedStyle = el ? window.getComputedStyle(el) : {};
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-form-group">
<label>Text</label>
<textarea class="we46-textarea" id="te-text" rows="3">${el ? el.textContent : ''}</textarea>
</div>
<div class="we46-form-row">
<div class="we46-form-group">
<label>Color</label>
<div class="we46-color-row">
<input type="color" class="we46-color" id="te-color" value="${rgbToHex(computedStyle.color) || '#000000'}">
<input class="we46-input" id="te-color-text" value="${computedStyle.color || ''}">
</div>
</div>
<div class="we46-form-group">
<label>Font Size</label>
<input class="we46-input" id="te-fontsize" value="${computedStyle.fontSize || ''}">
</div>
</div>
<div class="we46-form-row">
<div class="we46-form-group">
<label>Font Family</label>
<select class="we46-select" id="te-font">
${['inherit','Arial','Georgia','monospace','Courier New','Times New Roman','Verdana','Roboto'].map(f =>
`<option ${computedStyle.fontFamily?.includes(f) ? 'selected' : ''}>${f}</option>`
).join('')}
</select>
</div>
<div class="we46-form-group">
<label>Font Weight</label>
<select class="we46-select" id="te-weight">
${['normal','bold','100','200','300','400','500','600','700','800','900'].map(w =>
`<option ${computedStyle.fontWeight === w ? 'selected' : ''}>${w}</option>`
).join('')}
</select>
</div>
</div>
<div class="we46-form-row">
<div class="we46-form-group">
<label>Position</label>
<select class="we46-select" id="te-position">
${['','static','relative','absolute','fixed','sticky'].map(p =>
`<option ${computedStyle.position === p ? 'selected' : ''} value="${p}">${p || 'inherit'}</option>`
).join('')}
</select>
</div>
<div class="we46-form-group">
<label>Text Align</label>
<select class="we46-select" id="te-align">
${['left','center','right','justify'].map(a =>
`<option ${computedStyle.textAlign === a ? 'selected' : ''}>${a}</option>`
).join('')}
</select>
</div>
</div>
<div class="we46-form-group">
<label>Size (width × height)</label>
<div class="we46-form-row">
<input class="we46-input" id="te-width" placeholder="width" value="${computedStyle.width || ''}">
<input class="we46-input" id="te-height" placeholder="height" value="${computedStyle.height || ''}">
</div>
</div>
<div class="we46-overlay-actions">
<button class="we46-btn we46-btn--primary" id="te-apply">${t('quickedit','apply')}</button>
<button class="we46-btn we46-btn--danger" id="te-delete">${t('quickedit','delete')}</button>
<button class="we46-btn" id="te-close">${t('quickedit','close')}</button>
</div>
`;
overlay.querySelector('#te-color').addEventListener('input', function() { overlay.querySelector('#te-color-text').value = this.value; });
overlay.querySelector('#te-apply').addEventListener('click', () => {
if (el) {
el.textContent = overlay.querySelector('#te-text').value;
el.style.color = overlay.querySelector('#te-color').value;
el.style.fontSize = overlay.querySelector('#te-fontsize').value;
el.style.fontFamily = overlay.querySelector('#te-font').value;
el.style.fontWeight = overlay.querySelector('#te-weight').value;
el.style.position = overlay.querySelector('#te-position').value;
el.style.textAlign = overlay.querySelector('#te-align').value;
el.style.width = overlay.querySelector('#te-width').value;
el.style.height = overlay.querySelector('#te-height').value;
notify(t('notify','saved'));
}
});
overlay.querySelector('#te-delete').addEventListener('click', () => { if (el) { el.remove(); notify(t('notify','deleted')); } overlay.remove(); });
overlay.querySelector('#te-close').addEventListener('click', () => overlay.remove());
document.body.appendChild(overlay);
}
// ── Element Editor Overlay ──
function openElementEditor(el) {
const tag = el ? el.tagName.toLowerCase() : '';
const computed = el ? window.getComputedStyle(el) : {};
const isImg = tag === 'img';
const isInput = ['input','textarea','select'].includes(tag);
const isButton = tag === 'button';
const overlay = createOverlay('we46-element-editor-overlay');
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-el-tag-badge"><${tag}></div>
<div class="we46-form-row">
<div class="we46-form-group"><label>Width</label><input class="we46-input" id="ee-width" value="${computed.width || ''}"></div>
<div class="we46-form-group"><label>Height</label><input class="we46-input" id="ee-height" value="${computed.height || ''}"></div>
</div>
<div class="we46-form-row">
<div class="we46-form-group">
<label>Background</label>
<div class="we46-color-row">
<input type="color" class="we46-color" id="ee-bg" value="${rgbToHex(computed.backgroundColor) || '#ffffff'}">
<input class="we46-input" id="ee-bg-text" value="${computed.backgroundColor || ''}">
</div>
</div>
<div class="we46-form-group"><label>Opacity</label><input class="we46-input" type="number" min="0" max="1" step="0.1" id="ee-opacity" value="${computed.opacity || '1'}"></div>
</div>
<div class="we46-form-row">
<div class="we46-form-group">
<label>Display</label>
<select class="we46-select" id="ee-display">
${['block','inline','inline-block','flex','grid','none'].map(d => `<option ${computed.display === d ? 'selected' : ''}>${d}</option>`).join('')}
</select>
</div>
<div class="we46-form-group"><label>Border Radius</label><input class="we46-input" id="ee-radius" value="${computed.borderRadius || ''}"></div>
</div>
${isImg ? `
<div class="we46-form-group"><label>Image URL</label><input class="we46-input" id="ee-src" value="${el.src || ''}"></div>
<div class="we46-form-group"><label>Alt Text</label><input class="we46-input" id="ee-alt" value="${el.alt || ''}"></div>
` : ''}
${isInput ? `
<div class="we46-form-group"><label>Placeholder</label><input class="we46-input" id="ee-placeholder" value="${el.placeholder || ''}"></div>
<div class="we46-form-group"><label>Value</label><input class="we46-input" id="ee-value" value="${el.value || ''}"></div>
` : ''}
${isButton ? `<div class="we46-form-group"><label>Button Text</label><input class="we46-input" id="ee-btntext" value="${el.textContent || ''}"></div>` : ''}
<div class="we46-form-group">
<label>Custom CSS</label>
<textarea class="we46-textarea" id="ee-customcss" rows="3" placeholder="color: red; margin: 10px;">${el ? el.getAttribute('style') || '' : ''}</textarea>
</div>
<div class="we46-overlay-actions">
<button class="we46-btn we46-btn--primary" id="ee-apply">${t('quickedit','apply')}</button>
<button class="we46-btn" id="ee-clone">${t('quickedit','clone')}</button>
<button class="we46-btn" id="ee-hide">${t('quickedit','hide')}</button>
<button class="we46-btn" id="ee-copy">${t('quickedit','copy')}</button>
<button class="we46-btn we46-btn--danger" id="ee-delete">${t('quickedit','delete')}</button>
<button class="we46-btn" id="ee-close">${t('quickedit','close')}</button>
</div>
`;
overlay.querySelector('#ee-bg').addEventListener('input', function() { overlay.querySelector('#ee-bg-text').value = this.value; });
overlay.querySelector('#ee-apply').addEventListener('click', () => {
if (!el) return;
el.style.width = overlay.querySelector('#ee-width').value;
el.style.height = overlay.querySelector('#ee-height').value;
el.style.backgroundColor = overlay.querySelector('#ee-bg').value;
el.style.opacity = overlay.querySelector('#ee-opacity').value;
el.style.display = overlay.querySelector('#ee-display').value;
el.style.borderRadius = overlay.querySelector('#ee-radius').value;
const customCSS = overlay.querySelector('#ee-customcss').value;
if (customCSS) el.style.cssText += '; ' + customCSS;
if (isImg) { el.src = overlay.querySelector('#ee-src').value; el.alt = overlay.querySelector('#ee-alt').value; }
if (isInput) { el.placeholder = overlay.querySelector('#ee-placeholder').value; el.value = overlay.querySelector('#ee-value').value; }
if (isButton) el.textContent = overlay.querySelector('#ee-btntext').value;
notify(t('notify','saved'));
});
overlay.querySelector('#ee-clone').addEventListener('click', () => { if (el) { el.parentNode?.insertBefore(el.cloneNode(true), el.nextSibling); notify(t('notify','saved')); } });
overlay.querySelector('#ee-hide').addEventListener('click', () => { if (el) { el.style.display = 'none'; notify(t('notify','saved')); overlay.remove(); } });
overlay.querySelector('#ee-copy').addEventListener('click', () => { if (el) navigator.clipboard.writeText(el.outerHTML).then(() => notify(t('notify','copied'))); });
overlay.querySelector('#ee-delete').addEventListener('click', () => { if (el) { el.remove(); notify(t('notify','deleted')); } overlay.remove(); });
overlay.querySelector('#ee-close').addEventListener('click', () => overlay.remove());
document.body.appendChild(overlay);
}
// ── Code Editor Overlay (HTML/CSS/JS) ──
function openCodeEditor(type, el) {
const overlay = createOverlay('we46-code-editor-overlay');
let current = '';
if (type === 'html') current = el ? el.outerHTML : '';
if (type === 'css') current = el ? (el.getAttribute('style') || '') : '';
if (type === 'js') current = '';
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-code-header">
<span class="we46-code-badge we46-code-badge--${type}">${type.toUpperCase()} Editor</span>
${el ? `<span class="we46-el-tag-badge"><${el.tagName.toLowerCase()}></span>` : ''}
</div>
<textarea class="we46-textarea we46-code" id="ce-code" rows="12" spellcheck="false">${current}</textarea>
<div class="we46-overlay-actions">
<button class="we46-btn we46-btn--primary" id="ce-apply">${t('quickedit','apply')}</button>
<button class="we46-btn" id="ce-close">${t('quickedit','close')}</button>
</div>
`;
overlay.querySelector('#ce-apply').addEventListener('click', () => {
const code = overlay.querySelector('#ce-code').value;
if (type === 'html' && el) { el.outerHTML = code; notify(t('notify','htmlApplied')); }
else if (type === 'css' && el) { el.setAttribute('style', code); notify(t('notify','cssApplied')); }
else if (type === 'js') { try { (0, eval)(code); notify(t('notify','jsExecuted')); } catch(e) { notify('Error: ' + e.message, 'error'); } }
});
overlay.querySelector('#ce-close').addEventListener('click', () => overlay.remove());
document.body.appendChild(overlay);
}
// ── Create Config Overlay ──
function openCreateConfig() {
const overlay = createOverlay('we46-create-config-overlay');
const tags = ['div','button','input','a','h1','h2','h3','h4','h5','h6','p','span','img','section','article','nav'];
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-form-group">
<label>Element Type</label>
<select class="we46-select" id="cc-tag">${tags.map(t => `<option>${t}</option>`).join('')}</select>
</div>
<div class="we46-form-group">
<label>Text Content</label>
<input class="we46-input" id="cc-text" placeholder="Hello World">
</div>
<div class="we46-form-group">
<label>CSS Style</label>
<input class="we46-input" id="cc-css" placeholder="background: #4af; padding: 10px; border-radius: 6px;">
</div>
<div class="we46-form-group">
<label>Attributes (key=value, comma separated)</label>
<input class="we46-input" id="cc-attrs" placeholder="href=https://example.com, target=_blank">
</div>
<div class="we46-overlay-actions">
<button class="we46-btn we46-btn--primary" id="cc-save">${t('quickedit','save')}</button>
<button class="we46-btn" id="cc-cancel">${t('quickedit','cancel')}</button>
</div>
`;
overlay.querySelector('#cc-save').addEventListener('click', () => {
const attrsRaw = overlay.querySelector('#cc-attrs').value;
const attrs = {};
attrsRaw.split(',').forEach(pair => { const [k, v] = pair.split('=').map(s => s.trim()); if (k && v) attrs[k] = v; });
state.createConfig = {
tag: overlay.querySelector('#cc-tag').value,
text: overlay.querySelector('#cc-text').value,
css: overlay.querySelector('#cc-css').value,
attrs,
};
notify(t('notify','createConfigured')); overlay.remove();
renderQuickEdit(document.getElementById('we46-content'));
});
overlay.querySelector('#cc-cancel').addEventListener('click', () => overlay.remove());
document.body.appendChild(overlay);
}
// ── Advanced Create Overlay ──
function openAdvancedCreate() {
const overlay = createOverlay('we46-adv-create-overlay');
overlay.querySelector('.we46-overlay-body').innerHTML = `
<div class="we46-code-header">
<span class="we46-code-badge we46-code-badge--html">HTML</span>
<span class="we46-code-badge we46-code-badge--css">CSS</span>
${cfg.beta.jsAdvancedCreate ? '<span class="we46-code-badge we46-code-badge--js">JS (beta)</span>' : ''}
</div>
<div class="we46-form-group">
<label>HTML</label>
<textarea class="we46-textarea we46-code" id="ac-html" rows="6" spellcheck="false" placeholder="<div class='box'>Hello</div>"></textarea>
</div>
<div class="we46-form-group">
<label>CSS</label>
<textarea class="we46-textarea we46-code" id="ac-css" rows="4" spellcheck="false" placeholder=".box { background: #4af; padding: 20px; }"></textarea>
</div>
${cfg.beta.jsAdvancedCreate ? `
<div class="we46-form-group">
<label>JS <span class="we46-badge-beta">BETA</span></label>
<textarea class="we46-textarea we46-code" id="ac-js" rows="4" spellcheck="false" placeholder="document.querySelector('.box').addEventListener('click', () => alert('Hi!'))"></textarea>
</div>
` : ''}
<div class="we46-form-group">
<label>Insert into (CSS selector, leave empty for body)</label>
<input class="we46-input" id="ac-target" placeholder="body, #main, .container">
</div>
<div class="we46-overlay-actions">
<button class="we46-btn we46-btn--primary" id="ac-insert">Insert</button>
<button class="we46-btn" id="ac-close">${t('quickedit','close')}</button>
</div>
`;
overlay.querySelector('#ac-insert').addEventListener('click', () => {
const html = overlay.querySelector('#ac-html').value;
const css = overlay.querySelector('#ac-css').value;
const targetSel = overlay.querySelector('#ac-target').value || 'body';
const target = document.querySelector(targetSel) || document.body;
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
while (wrapper.firstChild) target.appendChild(wrapper.firstChild);
if (css) { const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); notify(t('notify','cssApplied')); }
if (cfg.beta.jsAdvancedCreate) {
const js = overlay.querySelector('#ac-js')?.value;
if (js) { try { (0, eval)(js); notify(t('notify','jsExecuted')); } catch(e) { notify('JS Error: ' + e.message, 'error'); } }
}
notify(t('notify','htmlApplied'));
});
overlay.querySelector('#ac-close').addEventListener('click', () => overlay.remove());
document.body.appendChild(overlay);
}
// ═══════════════════════════════════════════════════════════
// TAB: HTTP CONTROL
// ═══════════════════════════════════════════════════════════
function renderHTTP(c) {
c.innerHTML = `
<div class="we46-section">
<div class="we46-http-toolbar">
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn we46-btn--small ${state.interceptType === 'both' ? 'we46-btn--active' : ''}" data-type="both">${t('http','filterAll')}</button>
<button class="we46-btn we46-btn--small ${state.interceptType === 'xhr' ? 'we46-btn--active' : ''}" data-type="xhr">${t('http','filterXHR')}</button>
<button class="we46-btn we46-btn--small ${state.interceptType === 'fetch' ? 'we46-btn--active' : ''}" data-type="fetch">${t('http','filterFetch')}</button>
</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn we46-btn--small" id="http-clear">${t('http','clearAll')}</button>
<button class="we46-btn we46-btn--small" id="http-export">${t('http','exportJSON')}</button>
</div>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🔴 ${t('http','blockList')}</div>
<div class="we46-http-block-add">
<input class="we46-input" id="http-block-url" placeholder="${t('http','addBlock')}">
<button class="we46-btn we46-btn--danger we46-btn--small" id="http-block-add">${t('http','block')}</button>
</div>
<div class="we46-http-blocked" id="http-blocked-list">
${state.blockedURLs.size === 0 ? `<div class="we46-empty">${t('http','noBlocked')}</div>` :
Array.from(state.blockedURLs).map(url => `
<div class="we46-http-blocked-item">
<span>${url}</span>
<button class="we46-btn we46-btn--small we46-btn--danger" data-unblock="${url}">✕</button>
</div>
`).join('')}
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">📡 ${t('http','monitor')}</div>
<div class="we46-http-monitor" id="http-monitor">${renderHTTPList()}</div>
</div>
`;
c.querySelectorAll('[data-type]').forEach(btn => { btn.addEventListener('click', function() { state.interceptType = this.dataset.type; renderHTTP(c); }); });
c.querySelector('#http-clear').addEventListener('click', () => { state.httpRequests = []; renderHTTP(c); });
c.querySelector('#http-export').addEventListener('click', () => { downloadJSON({ requests: state.httpRequests, blocked: Array.from(state.blockedURLs) }, 'webedit-http.json'); });
c.querySelector('#http-block-add').addEventListener('click', () => {
const url = c.querySelector('#http-block-url').value.trim();
if (url) { state.blockedURLs.add(url); notify(t('notify','blocked')); renderHTTP(c); }
});
c.querySelectorAll('[data-unblock]').forEach(btn => {
btn.addEventListener('click', function() { state.blockedURLs.delete(this.dataset.unblock); notify(t('notify','unblocked'), 'info'); renderHTTP(c); });
});
c.querySelector('#http-monitor').addEventListener('click', function(e) {
const btn = e.target.closest('[data-req-action]');
if (!btn) return;
const idx = parseInt(btn.dataset.reqIdx);
const req = state.httpRequests[idx];
if (!req) return;
const action = btn.dataset.reqAction;
if (action === 'block') { state.blockedURLs.add(req.url); notify(t('notify','blocked')); renderHTTP(c); }
if (action === 'repeat') { fetch(req.url, { method: req.method, headers: req.headers, body: req.body }).then(() => notify('Repeated!')).catch(e => notify('Error: ' + e.message, 'error')); }
if (action === 'curl') { const curl = `curl -X ${req.method} "${req.url}"`; navigator.clipboard.writeText(curl).then(() => notify(t('notify','copied'))); }
});
}
function renderRequestSender(c) {
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">📤 Request Sender</div>
<div class="we46-form-group">
<label>Method</label>
<select class="we46-select" id="rs-method">
<option>GET</option><option>POST</option><option>PUT</option>
<option>DELETE</option><option>PATCH</option><option>HEAD</option>
</select>
</div>
<div class="we46-form-group">
<label>URL</label>
<input class="we46-input" id="rs-url" placeholder="https://example.com/api">
</div>
<div class="we46-form-group">
<label>Headers (JSON)</label>
<textarea class="we46-textarea" id="rs-headers" rows="2" placeholder='{"Content-Type":"application/json"}'></textarea>
</div>
<div class="we46-form-group">
<label>Body</label>
<textarea class="we46-textarea" id="rs-body" rows="3" placeholder='{"key":"value"}'></textarea>
</div>
<button class="we46-btn we46-btn--primary we46-btn--full" id="rs-send">📤 Send Request</button>
<div id="rs-response" style="margin-top:12px;"></div>
</div>`;
c.querySelector('#rs-send').addEventListener('click', async () => {
const method = c.querySelector('#rs-method').value;
const url = c.querySelector('#rs-url').value.trim();
if (!url) { notify('Enter URL', 'warning'); return; }
let headers = {};
try { headers = JSON.parse(c.querySelector('#rs-headers').value || '{}'); } catch(_) {}
const bodyVal = c.querySelector('#rs-body').value;
const respEl = c.querySelector('#rs-response');
respEl.innerHTML = '<div class="we46-empty">Sending...</div>';
try {
const resp = await fetch(url, { method, headers, body: ['GET','HEAD'].includes(method) ? undefined : bodyVal });
const text = await resp.text();
respEl.innerHTML = `<div class="we46-section-title">Response — ${resp.status} ${resp.statusText}</div>
<textarea class="we46-textarea" rows="6" readonly style="font-family:monospace;font-size:11px;">${text.substring(0,3000)}</textarea>`;
notify('Request sent!');
} catch(e) { respEl.innerHTML = `<div class="we46-empty" style="color:#f87171;">Error: ${e.message}</div>`; }
});
}
function renderHTTPList() {
const list = state.httpRequests.filter(r => {
if (state.interceptType === 'xhr') return r.type === 'xhr';
if (state.interceptType === 'fetch') return r.type === 'fetch';
return true;
}).slice(0, 100);
if (list.length === 0) return `<div class="we46-empty">${t('http','noRequests')}</div>`;
return list.map((req, i) => `
<div class="we46-http-req">
<div class="we46-http-req-top">
<span class="we46-http-method we46-http-method--${req.method.toLowerCase()}">${req.method}</span>
<span class="we46-http-type">${req.type.toUpperCase()}</span>
<span class="we46-http-status ${parseInt(req.status) >= 400 ? 'we46-http-status--err' : ''}">${req.status}</span>
<span class="we46-http-time">${req.time}</span>
</div>
<div class="we46-http-url" title="${req.url}">${req.url.substring(0, 60)}${req.url.length > 60 ? '…' : ''}</div>
<div class="we46-http-actions">
<button class="we46-btn we46-btn--tiny" data-req-action="block" data-req-idx="${i}">🔴 Block</button>
<button class="we46-btn we46-btn--tiny" data-req-action="repeat" data-req-idx="${i}">🔁 Repeat</button>
<button class="we46-btn we46-btn--tiny" data-req-action="curl" data-req-idx="${i}">📋 cURL</button>
</div>
</div>
`).join('');
}
function renderHTTPMonitor() {
const monitor = document.getElementById('http-monitor');
if (monitor) monitor.innerHTML = renderHTTPList();
}
// ═══════════════════════════════════════════════════════════
// TAB: VISUAL
// ═══════════════════════════════════════════════════════════
function renderVisual(c) {
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">🎨 Theme</div>
<div class="we46-btn-group">
<button class="we46-btn" id="v-dark">🌙 ${t('visual','darkMode')}</button>
<button class="we46-btn" id="v-light">☀️ ${t('visual','lightMode')}</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🔡 Font</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn" id="v-font-up">A+ ${t('visual','fontIncrease')}</button>
<button class="we46-btn" id="v-font-down">A- ${t('visual','fontDecrease')}</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🧹 Clean</div>
<button class="we46-btn we46-btn--full" id="v-remove-ads">🚫 ${t('visual','removeAds')}</button>
</div>
<div class="we46-section">
<div class="we46-section-title">✨ Effects</div>
<div class="we46-btn-group">
<button class="we46-btn ${rainbowStyleEl ? 'we46-btn--active' : ''}" id="v-rainbow">${t('visual','rainbow')}</button>
<button class="we46-btn ${blurStyleEl ? 'we46-btn--active' : ''}" id="v-blur">🌫️ ${t('visual','blur')}</button>
<button class="we46-btn" id="v-rotate">🔄 ${t('visual','rotate')}</button>
</div>
</div>
`;
c.querySelector('#v-dark').addEventListener('click', applyDarkMode);
c.querySelector('#v-light').addEventListener('click', applyLightMode);
c.querySelector('#v-font-up').addEventListener('click', increaseFonts);
c.querySelector('#v-font-down').addEventListener('click', decreaseFonts);
c.querySelector('#v-remove-ads').addEventListener('click', removeAds);
c.querySelector('#v-rainbow').addEventListener('click', () => { toggleRainbow(); renderVisual(c); });
c.querySelector('#v-blur').addEventListener('click', () => { toggleBlur(); renderVisual(c); });
c.querySelector('#v-rotate').addEventListener('click', rotatePage);
}
// ═══════════════════════════════════════════════════════════
// TAB: TOOLS
// ═══════════════════════════════════════════════════════════
function renderTools(c) {
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">⏱️ ${t('tools','timeScale')}</div>
<div class="we46-timescale-row">
<input type="range" class="we46-range" id="t-timescale" min="0.1" max="5" step="0.1" value="${state.timeScaleValue}">
<span class="we46-range-val" id="t-timescale-val">${state.timeScaleValue}x</span>
</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn we46-btn--small" data-ts="0.25">0.25x</button>
<button class="we46-btn we46-btn--small" data-ts="0.5">0.5x</button>
<button class="we46-btn we46-btn--small" data-ts="1">1x</button>
<button class="we46-btn we46-btn--small" data-ts="2">2x</button>
<button class="we46-btn we46-btn--small" data-ts="5">5x</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🔗 ${t('tools','links')}</div>
<button class="we46-btn ${state.linksDisabled ? 'we46-btn--active' : ''} we46-btn--full" id="t-links">
${state.linksDisabled ? t('tools','enableLinks') : t('tools','disableLinks')}
</button>
</div>
<div class="we46-section">
<div class="we46-section-title">👁️ Hidden Elements</div>
<button class="we46-btn ${state.hiddenVisible ? 'we46-btn--active' : ''} we46-btn--full" id="t-hidden">
${state.hiddenVisible ? t('tools','hideHidden') : t('tools','showHidden')}
</button>
</div>
<div class="we46-section">
<div class="we46-section-title">📊 ${t('tools','fpsCounter')} / ${t('tools','fpsLimiter')}</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn ${state.fpsElement ? 'we46-btn--active' : ''}" id="t-fps">
${state.fpsElement ? 'Hide FPS' : t('tools','fpsCounter')}
</button>
</div>
<div class="we46-form-row">
<label>FPS Limit</label>
<select class="we46-select" id="t-fpslimit">
${[0,30,60,90,120,144].map(v => `<option value="${v}" ${cfg.tools.fpsLimit === v ? 'selected' : ''}>${v === 0 ? 'Unlimited' : v}</option>`).join('')}
</select>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">📶 ${t('tools','pingCounter')}</div>
<div class="we46-form-row">
<input class="we46-input" id="t-pingurl" placeholder="${t('tools','pingURL')}">
<button class="we46-btn ${pingEl ? 'we46-btn--active' : ''}" id="t-ping">
${pingEl ? t('tools','stop') : t('tools','start')}
</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">📜 ${t('tools','scriptLoader')}</div>
<textarea class="we46-textarea" id="t-script" rows="3" placeholder="${t('tools','scriptURL')}"></textarea>
<button class="we46-btn we46-btn--full" id="t-script-load">${t('tools','loadScript')}</button>
</div>
<div class="we46-section">
<div class="we46-section-title">📚 ${t('tools','bibliothek')}</div>
<div class="we46-btn-group">
${Object.keys(LIBRARIES).map(lib => `<button class="we46-btn we46-btn--small" data-lib="${lib}">${lib}</button>`).join('')}
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🛡️ ${t('tools','antiBlock')}</div>
<div style="display:flex;flex-direction:column;gap:8px;">
<div style="border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px;">
<div class="we46-toggle-row">
<div><b style="font-size:13px;">Force Mode</b><div style="font-size:11px;opacity:0.55;">Bypass WebEdit blocking</div></div>
<button class="we46-toggle ${cfg.antiBlock && cfg.antiBlock.force ? 'we46-toggle--on' : ''}" id="t-ab-force"><span class="we46-toggle-knob"></span></button>
</div>
<div style="margin-top:8px;padding:10px;border:1px solid rgba(255,255,255,0.07);border-radius:8px;">
<div class="we46-toggle-row">
<div><b style="font-size:12px;">🥷 Stealth Mode</b><div style="font-size:11px;opacity:0.55;">Hide from page scripts</div></div>
<button class="we46-toggle ${cfg.antiBlock && cfg.antiBlock.stealth ? 'we46-toggle--on' : ''}" id="t-ab-stealth"><span class="we46-toggle-knob"></span></button>
</div>
</div>
</div>
<div style="border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px;">
<div class="we46-toggle-row">
<div><b style="font-size:13px;">🚫 AdBlock</b><div style="font-size:11px;opacity:0.55;">Remove ads, fake ad detectors</div></div>
<button class="we46-toggle ${cfg.antiBlock && cfg.antiBlock.adBlock ? 'we46-toggle--on' : ''}" id="t-ab-adblock"><span class="we46-toggle-knob"></span></button>
</div>
</div>
<div style="border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:12px;">
<div class="we46-toggle-row">
<div><b style="font-size:13px;">👁️ Anti-Tracking</b><div style="font-size:11px;opacity:0.55;">Block fingerprint & trackers</div></div>
<button class="we46-toggle ${cfg.antiBlock && cfg.antiBlock.tracking ? 'we46-toggle--on' : ''}" id="t-ab-tracking"><span class="we46-toggle-knob"></span></button>
</div>
</div>
${_isHostile ? '<div style="margin-top:4px;padding:8px 12px;background:rgba(245,158,11,0.15);border:1px solid rgba(245,158,11,0.3);border-radius:8px;font-size:12px;color:#fbbf24;">⚠️ Hostile site: ' + location.hostname + '</div>' : ''}
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">💉 JS Injection</div>
<div style="display:flex;flex-direction:column;gap:8px;">
<div class="we46-form-group">
<label>Target</label>
<select class="we46-select" id="inj-target">
<option value="page">Page context (eval)</option>
<option value="head">Inject <script> in <head></option>
<option value="body">Inject <script> in <body></option>
<option value="iframe">Active iFrame</option>
</select>
</div>
<div class="we46-form-group">
<label>Execution</label>
<select class="we46-select" id="inj-exec">
<option value="now">Now</option>
<option value="domready">DOMContentLoaded</option>
<option value="load">window.load</option>
<option value="interval">Interval (ms)</option>
</select>
</div>
<div class="we46-form-row" id="inj-interval-row" style="display:none;">
<label>Interval ms</label>
<input class="we46-input" id="inj-interval-ms" value="1000" style="width:90px;">
<button class="we46-btn we46-btn--danger we46-btn--small" id="inj-stop-interval">Stop</button>
</div>
<textarea class="we46-textarea we46-code" id="inj-code" rows="7" spellcheck="false" placeholder="// Your JS here"></textarea>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
<button class="we46-btn we46-btn--primary" id="inj-run">▶ Run</button>
<button class="we46-btn we46-btn--small" id="inj-clear-code">🗑 Clear</button>
<button class="we46-btn we46-btn--small" id="inj-save-snippet">💾 Save</button>
<button class="we46-btn we46-btn--small" id="inj-load-snippet">📂 Load</button>
</div>
<div id="inj-result" style="display:none;padding:10px;border-radius:8px;font-family:monospace;font-size:12px;border:1px solid rgba(255,255,255,0.1);margin-top:4px;word-break:break-all;max-height:100px;overflow-y:auto;"></div>
<div class="we46-section-title" style="margin-top:8px;">📌 Saved Snippets</div>
<div id="inj-snippets-list" style="display:flex;flex-direction:column;gap:4px;max-height:120px;overflow-y:auto;"></div>
</div>
</div>
`;
const slider = c.querySelector('#t-timescale');
const sliderVal = c.querySelector('#t-timescale-val');
slider.addEventListener('input', function() { sliderVal.textContent = this.value + 'x'; setTimeScale(parseFloat(this.value)); });
c.querySelectorAll('[data-ts]').forEach(btn => {
btn.addEventListener('click', function() { const v = parseFloat(this.dataset.ts); slider.value = v; sliderVal.textContent = v + 'x'; setTimeScale(v); });
});
c.querySelector('#t-links').addEventListener('click', () => { toggleLinks(); renderTools(c); });
c.querySelector('#t-hidden').addEventListener('click', () => { toggleHiddenElements(); renderTools(c); });
c.querySelector('#t-fps').addEventListener('click', () => { if (state.fpsElement) removeFPSCounter(); else initFPSCounter(); renderTools(c); });
c.querySelector('#t-fpslimit').addEventListener('change', function() {
cfg.tools.fpsLimit = parseInt(this.value); saveConfig();
if (state.fpsElement) { removeFPSCounter(); initFPSCounter(); }
});
c.querySelector('#t-ping').addEventListener('click', () => { if (pingEl) stopPing(); else startPing(c.querySelector('#t-pingurl').value.trim()); renderTools(c); });
c.querySelector('#t-script-load').addEventListener('click', () => { loadScript(c.querySelector('#t-script').value.trim()); });
c.querySelectorAll('[data-lib]').forEach(btn => { btn.addEventListener('click', function() { const url = LIBRARIES[this.dataset.lib]; if (url) { loadScript(url); notify(this.dataset.lib + ' loaded!'); } }); });
c.querySelector('#t-ab-force')?.addEventListener('click', function() {
cfg.antiBlock = cfg.antiBlock || {}; cfg.antiBlock.force = !cfg.antiBlock.force;
saveConfig(); this.classList.toggle('we46-toggle--on', cfg.antiBlock.force);
});
c.querySelector('#t-ab-stealth')?.addEventListener('click', function() {
cfg.antiBlock = cfg.antiBlock || {}; cfg.antiBlock.stealth = !cfg.antiBlock.stealth;
saveConfig(); this.classList.toggle('we46-toggle--on', cfg.antiBlock.stealth);
});
c.querySelector('#t-ab-adblock')?.addEventListener('click', function() {
cfg.antiBlock = cfg.antiBlock || {}; cfg.antiBlock.adBlock = !cfg.antiBlock.adBlock;
saveConfig(); if (cfg.antiBlock.adBlock) initAdBlock();
this.classList.toggle('we46-toggle--on', cfg.antiBlock.adBlock);
});
c.querySelector('#t-ab-tracking')?.addEventListener('click', function() {
cfg.antiBlock = cfg.antiBlock || {}; cfg.antiBlock.tracking = !cfg.antiBlock.tracking;
saveConfig(); if (cfg.antiBlock.tracking) initAntiTracking();
this.classList.toggle('we46-toggle--on', cfg.antiBlock.tracking);
});
// JS Injection handlers
let injInterval = null;
const injExecSel = c.querySelector('#inj-exec');
const injIntervalRow = c.querySelector('#inj-interval-row');
injExecSel.addEventListener('change', function() {
injIntervalRow.style.display = this.value === 'interval' ? 'flex' : 'none';
});
function runInjection(code) {
const target = c.querySelector('#inj-target').value;
const resultEl = c.querySelector('#inj-result');
resultEl.style.display = 'block';
try {
let result;
if (target === 'page') {
result = (0, eval)(code);
} else if (target === 'head' || target === 'body') {
const s = document.createElement('script');
s.textContent = code;
(target === 'head' ? document.head : document.body).appendChild(s);
result = 'Injected into <' + target + '>';
} else if (target === 'iframe') {
const frame = document.querySelector('iframe');
if (!frame) { notify('No iFrame found', 'warning'); return; }
try {
result = frame.contentWindow.eval(code);
} catch(e) {
const s = frame.contentDocument.createElement('script');
s.textContent = code;
frame.contentDocument.head.appendChild(s);
result = 'Injected into iFrame';
}
}
resultEl.style.background = 'rgba(34,197,94,0.1)';
resultEl.style.borderColor = 'rgba(34,197,94,0.3)';
resultEl.style.color = '#4ade80';
resultEl.innerHTML = '✅ ' + (result !== undefined ? String(result).substring(0, 500) : 'executed (no return value)');
notify('JS injected!', 'success');
state.changes.push({ time: new Date().toISOString(), action: 'JS Injection: ' + code.substring(0,40) });
} catch(e) {
resultEl.style.background = 'rgba(239,68,68,0.1)';
resultEl.style.borderColor = 'rgba(239,68,68,0.3)';
resultEl.style.color = '#f87171';
resultEl.innerHTML = '❌ ' + e.message;
notify('Error: ' + e.message, 'error');
}
}
c.querySelector('#inj-run').addEventListener('click', () => {
const code = c.querySelector('#inj-code').value.trim();
if (!code) { notify('Enter JS code', 'warning'); return; }
const exec = c.querySelector('#inj-exec').value;
if (injInterval) { clearInterval(injInterval); injInterval = null; }
if (exec === 'now') {
runInjection(code);
} else if (exec === 'domready') {
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => runInjection(code));
else runInjection(code);
} else if (exec === 'load') {
if (document.readyState === 'complete') runInjection(code);
else window.addEventListener('load', () => runInjection(code));
} else if (exec === 'interval') {
const ms = parseInt(c.querySelector('#inj-interval-ms').value) || 1000;
injInterval = setInterval(() => runInjection(code), ms);
notify('Interval injection started (' + ms + 'ms)', 'info');
}
});
c.querySelector('#inj-clear-code').addEventListener('click', () => {
c.querySelector('#inj-code').value = '';
const r = c.querySelector('#inj-result'); r.style.display = 'none'; r.innerHTML = '';
});
c.querySelector('#inj-stop-interval').addEventListener('click', () => {
if (injInterval) { clearInterval(injInterval); injInterval = null; notify('Interval stopped', 'info'); }
});
function renderSnippets() {
const snippets = load('inj_snippets', []);
const list = c.querySelector('#inj-snippets-list');
if (!list) return;
list.innerHTML = snippets.length === 0
? '<div class="we46-empty" style="padding:8px;">No saved snippets</div>'
: snippets.map((s, i) => `
<div style="display:flex;align-items:center;gap:6px;padding:5px 4px;border-bottom:1px solid rgba(255,255,255,0.06);">
<span style="font-size:12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:monospace;opacity:0.75;">${s.name}</span>
<button class="we46-btn we46-btn--tiny" data-snip-load="${i}">Load</button>
<button class="we46-btn we46-btn--tiny we46-btn--danger" data-snip-del="${i}">✕</button>
</div>`).join('');
list.querySelectorAll('[data-snip-load]').forEach(btn => {
btn.addEventListener('click', function() {
const s = snippets[parseInt(this.dataset.snipLoad)];
if (s) c.querySelector('#inj-code').value = s.code;
});
});
list.querySelectorAll('[data-snip-del]').forEach(btn => {
btn.addEventListener('click', function() {
snippets.splice(parseInt(this.dataset.snipDel), 1);
save('inj_snippets', snippets); renderSnippets();
});
});
}
c.querySelector('#inj-save-snippet').addEventListener('click', () => {
const code = c.querySelector('#inj-code').value.trim();
if (!code) { notify('Nothing to save', 'warning'); return; }
const name = prompt('Snippet name:', code.substring(0, 30) + '...');
if (!name) return;
const snippets = load('inj_snippets', []);
snippets.push({ name, code });
save('inj_snippets', snippets);
renderSnippets();
notify('Snippet saved!');
});
c.querySelector('#inj-load-snippet').addEventListener('click', renderSnippets);
renderSnippets();
}
// ═══════════════════════════════════════════════════════════
// TAB: DANGER ZONE
// ═══════════════════════════════════════════════════════════
function renderDanger(c) {
c.innerHTML = `
<div class="we46-danger-warning">${t('danger','dangerWarning')}</div>
<div class="we46-section">
<div class="we46-btn-group">
<button class="we46-btn we46-btn--danger we46-btn--full" id="d-nuke">${t('danger','nuke')}</button>
<button class="we46-btn we46-btn--danger we46-btn--full" id="d-delete">${t('danger','deleteContent')}</button>
<button class="we46-btn we46-btn--danger we46-btn--full" id="d-scripts">${t('danger','removeScripts')}</button>
<button class="we46-btn we46-btn--danger we46-btn--full ${frozen ? 'we46-btn--active' : ''}" id="d-freeze">
${frozen ? t('danger','unfreeze') : t('danger','freeze')}
</button>
<button class="we46-btn we46-btn--full" id="d-clean">${t('danger','cleanSite')}</button>
</div>
</div>
`;
c.querySelector('#d-nuke').addEventListener('click', function() {
if (this.dataset.confirm) { nukeSite(); notify(t('notify','nuked'), 'error'); }
else { this.textContent = t('danger','nukeConfirm'); this.dataset.confirm = '1'; setTimeout(() => { if (this) { this.textContent = t('danger','nuke'); delete this.dataset.confirm; } }, 3000); }
});
c.querySelector('#d-delete').addEventListener('click', function() {
if (this.dataset.confirm) { deleteSiteContent(); }
else { this.textContent = '⚠️ ' + t('danger','confirmAction') + '?'; this.dataset.confirm = '1'; setTimeout(() => { if (this) { this.textContent = t('danger','deleteContent'); delete this.dataset.confirm; } }, 3000); }
});
c.querySelector('#d-scripts').addEventListener('click', () => { removeAllScripts(); });
c.querySelector('#d-freeze').addEventListener('click', () => { toggleFreeze(); renderDanger(c); });
c.querySelector('#d-clean').addEventListener('click', () => { cleanSite(); });
}
// ═══════════════════════════════════════════════════════════
// TAB: SETTINGS
// ═══════════════════════════════════════════════════════════
function renderSettings(c) {
const lsKeys = Object.keys(localStorage).slice(0, 50);
c.innerHTML = `
<div class="we46-section">
<div class="we46-section-title">🌍 ${t('settings','language')}</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn ${cfg.lang === 'en' ? 'we46-btn--active' : ''}" data-lang="en">EN</button>
<button class="we46-btn ${cfg.lang === 'ru' ? 'we46-btn--active' : ''}" data-lang="ru">RU</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🎨 ${t('settings','theme')}</div>
<div class="we46-btn-group we46-btn-group--row">
<button class="we46-btn ${cfg.theme === 'dark' ? 'we46-btn--active' : ''}" data-theme="dark">🌙 Dark</button>
<button class="we46-btn ${cfg.theme === 'light' ? 'we46-btn--active' : ''}" data-theme="light">☀️ Light</button>
<button class="we46-btn ${cfg.theme === 'glass' ? 'we46-btn--active' : ''}" data-theme="glass">💎 Glass</button>
<button class="we46-btn ${cfg.theme === 'neon' ? 'we46-btn--active' : ''}" data-theme="neon">⚡ Neon</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🖱️ ${t('settings','draggable')}</div>
<div class="we46-toggle-row">
<span>${t('settings','draggable')}</span>
<button class="we46-toggle ${cfg.draggable ? 'we46-toggle--on' : ''}" id="s-draggable"><span class="we46-toggle-knob"></span></button>
</div>
<div class="we46-toggle-row">
<span>⛶ Fullscreen mode</span>
<button class="we46-toggle ${cfg.fullscreen ? 'we46-toggle--on' : ''}" id="s-fullscreen"><span class="we46-toggle-knob"></span></button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">⌨️ ${t('settings','hotkeys')}</div>
<div class="we46-form-group">
<label>${t('settings','hotkeyToggle')}</label>
<input class="we46-input" id="s-hk-toggle" value="${cfg.hotkeys.toggle}">
</div>
<button class="we46-btn we46-btn--full" id="s-hk-save">${t('settings','save')}</button>
</div>
<div class="we46-section">
<div class="we46-section-title">🧪 ${t('settings','beta')}</div>
<div class="we46-toggle-row">
<span>${t('settings','betaCssCreate')} <span class="we46-badge-beta">BETA</span></span>
<button class="we46-toggle ${cfg.beta.cssCreate ? 'we46-toggle--on' : ''}" id="s-beta-css"><span class="we46-toggle-knob"></span></button>
</div>
<div class="we46-toggle-row">
<span>${t('settings','betaJsCreate')} <span class="we46-badge-beta">BETA</span></span>
<button class="we46-toggle ${cfg.beta.jsAdvancedCreate ? 'we46-toggle--on' : ''}" id="s-beta-js"><span class="we46-toggle-knob"></span></button>
</div>
<div class="we46-toggle-row">
<span>Syntax highlight <span class="we46-badge-beta">BETA</span></span>
<button class="we46-toggle ${cfg.beta.syntaxHighlight ? 'we46-toggle--on' : ''}" id="s-beta-syn"><span class="we46-toggle-knob"></span></button>
</div>
<div class="we46-toggle-row">
<span>Preview changes <span class="we46-badge-beta">BETA</span></span>
<button class="we46-toggle ${cfg.beta.previewChanges ? 'we46-toggle--on' : ''}" id="s-beta-prev"><span class="we46-toggle-knob"></span></button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">💾 ${t('settings','localStorage')}</div>
<div class="we46-ls-editor" id="s-ls-editor">
${lsKeys.length === 0 ? '<div class="we46-empty">LocalStorage is empty</div>' :
lsKeys.map(k => `
<div class="we46-ls-item">
<span class="we46-ls-key">${k}</span>
<input class="we46-input we46-input--small" data-ls-key="${k}" value="${(localStorage.getItem(k) || '').substring(0, 80)}">
<button class="we46-btn we46-btn--tiny" data-ls-save="${k}">💾</button>
<button class="we46-btn we46-btn--tiny we46-btn--danger" data-ls-del="${k}">✕</button>
</div>
`).join('')}
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🍪 Cookie Editor</div>
<div style="display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap;">
<button class="we46-btn we46-btn--small" id="s-cookie-refresh">🔄 Refresh</button>
<button class="we46-btn we46-btn--small we46-btn--danger" id="s-cookie-clear-all">🗑 Clear All</button>
</div>
<div class="we46-ls-editor" id="s-cookie-list"></div>
<div class="we46-section-title" style="margin-top:12px;">➕ Add Cookie</div>
<div style="display:flex;flex-direction:column;gap:6px;">
<div class="we46-form-row">
<input class="we46-input we46-input--small" id="s-ck-name" placeholder="name">
<input class="we46-input we46-input--small" id="s-ck-value" placeholder="value">
</div>
<div class="we46-form-row">
<input class="we46-input we46-input--small" id="s-ck-path" placeholder="path" value="/">
<input class="we46-input we46-input--small" id="s-ck-expires" placeholder="days (0=session)" value="0">
</div>
<button class="we46-btn we46-btn--primary we46-btn--small" id="s-ck-add">Add Cookie</button>
</div>
</div>
<div class="we46-section">
<div class="we46-section-title">🛡️ Anti-Block Report</div>
<div id="s-ab-report"></div>
</div>
${ECAWE === 1 ? `
<div class="we46-section">
<div class="we46-section-title">⚡ ECAWE — Execute As WebEdit</div>
<div style="padding:10px 12px;border-radius:10px;border:1px solid rgba(99,102,241,0.35);background:rgba(99,102,241,0.07);margin-bottom:10px;font-size:12px;opacity:0.75;line-height:1.6;">
Runs in <b>userscript context</b> — bypasses page CSP, has access to GM_* APIs and unsafeWindow.
Use for code that normal eval() can't execute.
</div>
<textarea class="we46-textarea we46-code" id="ecawe-code" rows="8" spellcheck="false" placeholder="// Executed as WebEdit (userscript context) // GM_setValue, GM_getValue, unsafeWindow available GM_setValue('test', 'hello'); unsafeWindow.myVar = 42;"></textarea>
<div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;">
<button class="we46-btn we46-btn--primary" id="ecawe-run">⚡ Execute as WebEdit</button>
<button class="we46-btn we46-btn--small" id="ecawe-clear">🗑 Clear</button>
<button class="we46-btn we46-btn--small" id="ecawe-copy-result">📋 Copy result</button>
</div>
<div id="ecawe-result" style="display:none;margin-top:10px;padding:10px 12px;border-radius:8px;font-family:monospace;font-size:12px;border:1px solid rgba(255,255,255,0.1);word-break:break-all;max-height:150px;overflow-y:auto;white-space:pre-wrap;"></div>
<div class="we46-section-title" style="margin-top:12px;">📌 Quick snippets</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<button class="we46-btn we46-btn--small we46-btn--full" data-ecawe-snip="gm_list">📋 List all GM_* values</button>
<button class="we46-btn we46-btn--small we46-btn--full" data-ecawe-snip="unsafe_window">🪟 Dump unsafeWindow keys</button>
<button class="we46-btn we46-btn--small we46-btn--full" data-ecawe-snip="gm_clear">🗑 Clear all GM_* values</button>
<button class="we46-btn we46-btn--small we46-btn--full" data-ecawe-snip="page_title">📝 Get page title via unsafeWindow</button>
</div>
</div>
` : ''}
<div class="we46-section">
<button class="we46-btn we46-btn--danger we46-btn--full" id="s-reset">${t('settings','resetAll')}</button>
</div>
`;
c.querySelectorAll('[data-lang]').forEach(btn => { btn.addEventListener('click', function() { cfg.lang = this.dataset.lang; saveConfig(); switchTab('settings'); }); });
c.querySelectorAll('[data-theme]').forEach(btn => {
btn.addEventListener('click', function() {
cfg.theme = this.dataset.theme; saveConfig();
const panel = document.getElementById('we46-container');
if (panel) panel.className = 'we46 we46--' + cfg.theme;
renderSettings(c);
});
});
c.querySelector('#s-draggable').addEventListener('click', function() { cfg.draggable = !cfg.draggable; saveConfig(); this.classList.toggle('we46-toggle--on', cfg.draggable); });
c.querySelector('#s-fullscreen')?.addEventListener('click', function() {
cfg.fullscreen = !cfg.fullscreen; saveConfig();
const container = document.getElementById('we46-container');
if (container) {
if (cfg.fullscreen) {
container.style.cssText = 'position:fixed!important;top:0!important;left:0!important;right:0!important;bottom:0!important;width:100vw!important;height:100vh!important;display:flex!important;align-items:center!important;justify-content:center!important;background:rgba(0,0,0,0.6)!important;backdrop-filter:blur(4px)!important;z-index:2147483645!important;';
const panel = container.querySelector('.we46-panel');
if (panel) { panel.style.width = 'calc(100vw - 80px)'; panel.style.maxWidth = '1200px'; panel.style.height = 'calc(100vh - 80px)'; panel.style.maxHeight = 'none'; panel.style.borderRadius = '20px'; }
} else {
container.style.cssText = 'position:fixed;top:20px;right:20px;z-index:2147483645;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:14px;line-height:1.5;';
const panel = container.querySelector('.we46-panel');
if (panel) { panel.style.width = ''; panel.style.maxWidth = ''; panel.style.height = ''; panel.style.maxHeight = ''; panel.style.borderRadius = ''; }
}
}
this.classList.toggle('we46-toggle--on', cfg.fullscreen);
});
c.querySelector('#s-hk-save').addEventListener('click', () => { cfg.hotkeys.toggle = c.querySelector('#s-hk-toggle').value.trim() || 'F7'; saveConfig(); notify(t('notify','saved')); });
c.querySelector('#s-beta-css').addEventListener('click', function() { cfg.beta.cssCreate = !cfg.beta.cssCreate; saveConfig(); this.classList.toggle('we46-toggle--on', cfg.beta.cssCreate); });
c.querySelector('#s-beta-js').addEventListener('click', function() { cfg.beta.jsAdvancedCreate = !cfg.beta.jsAdvancedCreate; saveConfig(); this.classList.toggle('we46-toggle--on', cfg.beta.jsAdvancedCreate); });
c.querySelector('#s-beta-syn')?.addEventListener('click', function() { cfg.beta.syntaxHighlight = !cfg.beta.syntaxHighlight; saveConfig(); this.classList.toggle('we46-toggle--on', cfg.beta.syntaxHighlight); });
c.querySelector('#s-beta-prev')?.addEventListener('click', function() { cfg.beta.previewChanges = !cfg.beta.previewChanges; saveConfig(); this.classList.toggle('we46-toggle--on', cfg.beta.previewChanges); });
c.querySelectorAll('[data-ls-save]').forEach(btn => {
btn.addEventListener('click', function() { const key = this.dataset.lsSave; const val = c.querySelector(`[data-ls-key="${key}"]`).value; localStorage.setItem(key, val); notify(t('notify','saved')); });
});
c.querySelectorAll('[data-ls-del]').forEach(btn => { btn.addEventListener('click', function() { localStorage.removeItem(this.dataset.lsDel); renderSettings(c); }); });
// Cookie editor
function parseCookies() {
return document.cookie.split(';').map(c => {
const idx = c.indexOf('=');
return idx < 0 ? null : { name: c.substring(0, idx).trim(), value: c.substring(idx + 1).trim() };
}).filter(Boolean);
}
function setCookie(name, value, days, path) {
let expires = '';
if (days > 0) { const d = new Date(); d.setTime(d.getTime() + days * 86400000); expires = '; expires=' + d.toUTCString(); }
document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=' + (path || '/');
}
function deleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
}
function renderCookies() {
const list = c.querySelector('#s-cookie-list');
if (!list) return;
const cookies = parseCookies();
list.innerHTML = cookies.length === 0
? '<div class="we46-empty">No cookies</div>'
: cookies.map((ck, i) => `
<div class="we46-ls-item">
<span class="we46-ls-key" title="${ck.name}">${ck.name.substring(0,18)}</span>
<input class="we46-input we46-input--small" data-ck-key="${ck.name}" value="${decodeURIComponent(ck.value).substring(0, 60)}">
<button class="we46-btn we46-btn--tiny" data-ck-save="${ck.name}">💾</button>
<button class="we46-btn we46-btn--tiny we46-btn--danger" data-ck-del="${ck.name}">✕</button>
</div>`).join('');
list.querySelectorAll('[data-ck-save]').forEach(btn => {
btn.addEventListener('click', function() {
const val = list.querySelector('[data-ck-key="' + this.dataset.ckSave + '"]').value;
setCookie(this.dataset.ckSave, val, 30, '/');
notify('Cookie saved!');
});
});
list.querySelectorAll('[data-ck-del]').forEach(btn => {
btn.addEventListener('click', function() { deleteCookie(this.dataset.ckDel); renderCookies(); notify('Cookie deleted', 'warning'); });
});
}
renderCookies();
c.querySelector('#s-cookie-refresh').addEventListener('click', renderCookies);
c.querySelector('#s-cookie-clear-all').addEventListener('click', function() {
parseCookies().forEach(ck => deleteCookie(ck.name));
renderCookies(); notify('All cookies cleared', 'warning');
});
c.querySelector('#s-ck-add').addEventListener('click', () => {
const name = c.querySelector('#s-ck-name').value.trim();
const val = c.querySelector('#s-ck-value').value;
const path = c.querySelector('#s-ck-path').value || '/';
const days = parseInt(c.querySelector('#s-ck-expires').value) || 0;
if (!name) { notify('Enter cookie name', 'warning'); return; }
setCookie(name, val, days, path);
renderCookies(); notify('Cookie added!');
});
// Anti-Block report
(function renderABReport() {
const report = c.querySelector('#s-ab-report');
if (!report) return;
const checks = [
{ label: 'navigator.webdriver spoof', ok: () => navigator.webdriver === false },
{ label: 'navigator.plugins spoof', ok: () => navigator.plugins && navigator.plugins.length > 0 },
{ label: 'navigator.languages spoof', ok: () => Array.isArray(navigator.languages) && navigator.languages.includes('en-US') },
{ label: 'navigator.platform spoof', ok: () => navigator.platform === 'Win32' },
{ label: 'navigator.hardwareConcurrency',ok: () => navigator.hardwareConcurrency === 8 },
{ label: 'window.chrome present', ok: () => !!window.chrome },
{ label: 'window.open popup block', ok: () => window.open !== window.open.toString.call(window.open) || true },
{ label: 'ServiceWorker blocked', ok: () => { try { navigator.serviceWorker.register('/x').catch(()=>{}); return true; } catch(_){ return false; } } },
{ label: 'Canvas toDataURL patched', ok: () => HTMLCanvasElement.prototype.toDataURL.toString().indexOf('[native code]') < 0 },
{ label: 'Function.toString patched', ok: () => Function.prototype.toString.toString().indexOf('[native code]') < 0 },
{ label: 'rAF timing patched', ok: () => window.requestAnimationFrame.toString().indexOf('[native code]') < 0 },
{ label: 'MutationObserver patched', ok: () => window.MutationObserver.toString().indexOf('[native code]') < 0 },
{ label: 'localStorage tracker block', ok: () => Storage.prototype.setItem.toString().indexOf('[native code]') < 0 },
{ label: 'WebGL vendor spoof', ok: () => { try { const c2 = document.createElement('canvas'); const gl = c2.getContext('webgl'); return gl && gl.getParameter(37446) === 'Intel Iris OpenGL Engine'; } catch(_){ return false; } } },
{ label: 'Battery API spoofed', ok: () => typeof navigator.getBattery === 'function' },
];
const hostile = _isHostile ? '<div style="margin-bottom:10px;padding:8px 12px;background:rgba(245,158,11,0.15);border:1px solid rgba(245,158,11,0.3);border-radius:8px;font-size:12px;color:#fbbf24;">⚠️ Hostile site detected: ' + location.hostname + '</div>' : '';
report.innerHTML = hostile + checks.map(ch => {
let ok = false;
try { ok = ch.ok(); } catch(_) { ok = false; }
return '<div style="display:flex;align-items:center;gap:8px;padding:5px 2px;border-bottom:1px solid rgba(255,255,255,0.05);">'
+ '<span style="font-size:14px;">' + (ok ? '✅' : '❌') + '</span>'
+ '<span style="font-size:12px;flex:1;">' + ch.label + '</span>'
+ '<span style="font-size:11px;font-weight:600;' + (ok ? 'color:#4ade80' : 'color:#f87171') + '">' + (ok ? 'OK' : 'FAIL') + '</span>'
+ '</div>';
}).join('');
})();
// ECAWE handlers
if (ECAWE === 1) {
const ecaweResult = c.querySelector('#ecawe-result');
let ecaweLastResult = '';
const ECAWE_SNIPPETS = {
gm_list: `// List all GM_* stored values
const keys = ['config','inj_snippets','blockedURLs'];
const out = {};
keys.forEach(k => { try { out[k] = GM_getValue('we46_' + k, null); } catch(e) { out[k] = 'N/A'; } });
JSON.stringify(out, null, 2);`,
unsafe_window: `// Dump top-level unsafeWindow keys
Object.keys(unsafeWindow).filter(k => !['window','self','top','parent'].includes(k)).slice(0,40).join('\n');`,
gm_clear: `// Clear all WebEdit GM_* values
['config','inj_snippets','blockedURLs','modifiedRequests'].forEach(k => {
try { GM_setValue('we46_' + k, null); } catch(e) {}
});
'Cleared all WebEdit GM values';`,
page_title: `// Read/write page title via unsafeWindow
const old = unsafeWindow.document.title;
unsafeWindow.document.title = 'WebEdit was here';
'Was: ' + old + ' → Now: ' + unsafeWindow.document.title;`
};
c.querySelectorAll('[data-ecawe-snip]').forEach(btn => {
btn.addEventListener('click', function() {
const code = ECAWE_SNIPPETS[this.dataset.ecaweSnip];
if (code) c.querySelector('#ecawe-code').value = code;
});
});
c.querySelector('#ecawe-run').addEventListener('click', () => {
const code = c.querySelector('#ecawe-code').value.trim();
if (!code) { notify('Enter code', 'warning'); return; }
ecaweResult.style.display = 'block';
try {
// Execute in userscript context — GM_* and unsafeWindow are available
const fn = new Function(
'GM_setValue', 'GM_getValue', 'GM_xmlhttpRequest',
'GM_addStyle', 'GM_addElement', 'unsafeWindow',
code
);
const result = fn.call(null,
GM_setValue, GM_getValue, GM_xmlhttpRequest,
GM_addStyle, GM_addElement, unsafeWindow
);
ecaweLastResult = result !== undefined ? String(result) : '(no return value)';
ecaweResult.style.background = 'rgba(99,102,241,0.1)';
ecaweResult.style.borderColor = 'rgba(99,102,241,0.4)';
ecaweResult.style.color = '#a5b4fc';
ecaweResult.textContent = '⚡ ' + ecaweLastResult.substring(0, 2000);
notify('Executed as WebEdit!', 'success');
state.changes.push({ time: new Date().toISOString(), action: 'ECAWE: ' + code.substring(0, 40) });
} catch(e) {
ecaweLastResult = e.message;
ecaweResult.style.background = 'rgba(239,68,68,0.1)';
ecaweResult.style.borderColor = 'rgba(239,68,68,0.3)';
ecaweResult.style.color = '#f87171';
ecaweResult.textContent = '❌ ' + e.message;
notify('ECAWE Error: ' + e.message, 'error');
}
});
c.querySelector('#ecawe-clear').addEventListener('click', () => {
c.querySelector('#ecawe-code').value = '';
ecaweResult.style.display = 'none';
ecaweResult.textContent = '';
ecaweLastResult = '';
});
c.querySelector('#ecawe-copy-result').addEventListener('click', () => {
if (!ecaweLastResult) { notify('Nothing to copy', 'warning'); return; }
navigator.clipboard.writeText(ecaweLastResult).then(() => notify('Copied!'));
});
}
c.querySelector('#s-reset').addEventListener('click', function() {
if (this.dataset.confirm) {
Object.assign(cfg, JSON.parse(JSON.stringify(DEFAULT_CONFIG))); saveConfig(); notify(t('notify','reset')); renderSettings(c);
} else {
this.textContent = '⚠️ Confirm reset?'; this.dataset.confirm = '1';
setTimeout(() => { if (this) { this.textContent = t('settings','resetAll'); delete this.dataset.confirm; } }, 3000);
}
});
}
// ═══════════════════════════════════════════════════════════
// OVERLAY HELPER
// ═══════════════════════════════════════════════════════════
function createOverlay(id) {
const overlay = document.createElement('div');
overlay.className = 'we46-overlay we46--' + (cfg.theme || 'dark');
overlay.id = id;
overlay.innerHTML = `
<div class="we46-overlay-panel">
<div class="we46-overlay-header">
<span class="we46-overlay-title">WebEdit 4.6.5</span>
<button class="we46-btn-icon we46-overlay-close">×</button>
</div>
<div class="we46-overlay-body"></div>
</div>
`;
overlay.querySelector('.we46-overlay-close').addEventListener('click', () => overlay.remove());
overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); });
return overlay;
}
// ═══════════════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════════════
function downloadJSON(data, filename) {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click();
}
function rgbToHex(rgb) {
if (!rgb) return '#000000';
const match = rgb.match(/\d+/g);
if (!match || match.length < 3) return '#000000';
return '#' + match.slice(0, 3).map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
}
// ═══════════════════════════════════════════════════════════
// HOTKEYS
// ═══════════════════════════════════════════════════════════
function initHotkeys() {
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
const key = (e.altKey ? 'Alt+' : '') + (e.ctrlKey ? 'Ctrl+' : '') + (e.shiftKey ? 'Shift+' : '') + e.key;
if (key === cfg.hotkeys.toggle || e.key === 'F7') {
e.preventDefault();
if (!state.consoleUnlocked) { notify('Type WebEdit in console first!', 'warning'); return; }
toggleUI();
}
if (e.key === 'F7' && !state.consoleUnlocked) { e.preventDefault(); notify('Type WebEdit in console first!', 'warning'); }
if (key === 'Alt+w' || key === 'Alt+W') {
e.preventDefault();
if (!state.consoleUnlocked) { notify('Type WebEdit in console first!', 'warning'); return; }
toggleUI();
}
if (e.key === 'Escape') {
if (state.deleteMode) stopDeleteMode();
if (pickerCallback) stopPicker();
if (state.createActive) deactivateCreate();
}
});
}
// ═══════════════════════════════════════════════════════════
// CONSOLE UNLOCK
// ═══════════════════════════════════════════════════════════
function setupConsoleCommand() {
Object.defineProperty(unsafeWindow, 'WebEdit', {
get() {
state.consoleUnlocked = true;
const lang = cfg.lang === 'ru' ? 'ru' : 'en';
const messages = {
en: [
'%c⚡ WebEdit 4.6.5',
`%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
`%c✅ Loaded & Ready`,
`%c🎮 Press F7 or Alt+W to open the menu`,
`%c🛡️ Anti-Block 3.2.2 | 🌐 HTTP Control | ✏️ Quick Edit`,
`%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
],
ru: [
'%c⚡ WebEdit 4.6.5',
`%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
`%c✅ Загружен и готов к работе`,
`%c🎮 Нажми F7 или Alt+W для открытия меню`,
`%c🛡️ Anti-Block 3.2.2 | 🌐 HTTP Control | ✏️ Quick Edit`,
`%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
]
};
const styles = [
'background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:10px 24px;font-size:22px;font-weight:bold;border-radius:8px;',
'color:#555;font-size:12px;',
'color:#4caf50;font-size:14px;font-weight:bold;',
'color:#667eea;font-size:14px;',
'color:#888;font-size:12px;',
'color:#555;font-size:12px;',
];
messages[lang].forEach((msg, i) => console.log(msg, styles[i]));
return '✅ Unlocked! Press F7 or Alt+W';
},
configurable: true
});
}
// ═══════════════════════════════════════════════════════════
// INIT
// ═══════════════════════════════════════════════════════════
function init() {
injectStyles();
createUI();
initHTTPInterceptor();
initHotkeys();
setupConsoleCommand();
if (cfg.tools.fpsCounter) initFPSCounter();
initAntiBlock();
if (cfg.antiBlock && cfg.antiBlock.adBlock) initAdBlock();
if (cfg.antiBlock && cfg.antiBlock.tracking) initAntiTracking();
window.webEdit = { toggle: toggleUI, version: VERSION, state, cfg };
unsafeWindow.webEdit = window.webEdit;
console.log(
'%c⚡ WebEdit 4.6.5',
'background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:6px 16px;border-radius:6px;font-weight:bold;',
'— Type WebEdit in console to unlock | v4.6.5'
);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();