// ==UserScript==
// @name Wörter-Verwischen-Skript
// @namespace http://tampermonkey.net/
// @version 3.37
// @description Blurt Wörter auf Webseiten. Features: Mehrere Treffer, IP-Erkennung, Vollbild und Ausnahmen
// @author Sky95
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.ipify.org
// @run-at document-start
// @noframes
// ==/UserScript==
(function() {
'use strict';
let defaultFullPageBlurCheck = GM_getValue('defaultFullPageBlur', false);
let initialPageBlurStrength = GM_getValue('pageBlurStrength', 10);
let applyForceInitialBlur = defaultFullPageBlurCheck;
let exceptionListEarly = GM_getValue('exceptionList', []);
let earlyMatch = null;
try {
const currentUrl = window.location.href;
const currentHostname = new URL(currentUrl).hostname;
for (const exception of exceptionListEarly) {
if (!exception || typeof exception.pattern !== 'string' || typeof exception.mode !== 'string') continue;
const pattern = exception.pattern;
if (pattern.endsWith('/*')) {
const domain = pattern.slice(0, -2);
if (currentHostname === domain || currentHostname.endsWith('.' + domain)) {
earlyMatch = exception;
break;
}
} else {
const regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*'));
if (regex.test(currentUrl)) {
earlyMatch = exception;
break;
}
}
}
} catch(e) { }
if (earlyMatch && earlyMatch.mode === 'no_blur') {
applyForceInitialBlur = false;
}
if (applyForceInitialBlur) {
const style = document.createElement('style');
style.id = 'blur-force-initial-style';
style.textContent = `
html.force-initial-blur { filter: blur(${initialPageBlurStrength}px) !important; transition: none !important; }
html.force-initial-blur * { pointer-events: none !important; }
`;
if (document.documentElement) {
document.documentElement.appendChild(style);
document.documentElement.classList.add('force-initial-blur');
} else {
document.addEventListener('DOMContentLoaded', () => {
if(document.documentElement) {
document.documentElement.appendChild(style);
document.documentElement.classList.add('force-initial-blur');
}
}, { once: true });
}
}
GM_addStyle(`
.word-blur {
filter: blur(7.5px);
transition: filter 1.5s ease;
}
.word-blur:hover {
filter: blur(0px);
transition-delay: 1s;
}
html.full-page-blur {
filter: blur(10px) !important;
transition: none !important;
}
html.full-page-unblur {
filter: blur(0px) !important;
transition: filter 0.5s ease 0.5s !important;
}
html.full-page-blur * {
pointer-events: none !important;
}
body {
max-width: 100% !important;
overflow-x: hidden !important;
}
#blur-settings-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 550px;
background: #2b2b2b !important;
color: #fff !important;
border-radius: 8px !important;
box-shadow: 0 4px 20px rgba(0,0,0,0.8) !important;
z-index: 10000;
display: none;
font-family: 'Courier New', monospace !important;
pointer-events: auto !important;
opacity: 0;
transition: opacity 0.3s ease-in-out, transform 0.3s ease-out;
}
#blur-settings-panel.visible {
opacity: 0.98;
}
#blur-section-container {
max-height: 75vh !important;
overflow-y: auto !important;
scrollbar-width: thin !important;
scrollbar-color: #666 #2b2b2b !important;
}
#blur-settings-header {
padding: 15px 20px !important;
background: #363636 !important;
border-radius: 8px 8px 0 0;
cursor: move;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
}
#blur-settings-header h3 {
color: #fff !important;
font-size: 18px !important;
font-weight: normal !important;
font-family: Arial, sans-serif !important;
margin: 0 !important;
padding: 0 !important;
}
#blur-text-info, #blur-exception-info {
padding: 8px 15px !important;
margin-bottom: 10px !important;
background: #262626 !important;
border: 1px solid #406040 !important;
font-size: 12px !important;
color: #afa !important;
line-height: 1.4 !important;
border-radius: 4px;
}
#blur-exception-info {
border-color: #604060 !important;
color: #aaf !important;
}
#blur-ip-info {
padding: 5px 20px !important;
background: #262626 !important;
font-size: 14px !important;
color: #aaf !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
pointer-events: auto !important;
filter: blur(0) !important;
margin-top: 5px !important;
border-radius: 4px;
}
#blur-ip-display {
pointer-events: auto !important;
filter: blur(0) !important;
}
#blur-ip-button {
background: #4CAF50 !important;
color: white !important;
border: none !important;
border-radius: 4px !important;
padding: 5px 10px !important;
cursor: pointer !important;
font-size: 12px !important;
pointer-events: auto !important;
filter: blur(0) !important;
}
#blur-ip-button:hover {
background: #388E3C !important;
}
#blur-close-button {
background: none !important;
border: none !important;
color: #fff !important;
cursor: pointer !important;
font-family: Arial, sans-serif !important;
font-size: 24px !important;
padding: 0 !important;
width: 24px !important;
height: 24px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
line-height: 1 !important;
text-decoration: none !important;
outline: none !important;
}
#blur-close-button:hover {
background: rgba(255,255,255,0.1) !important;
border-radius: 4px !important;
}
.blur-section-content {
padding: 15px 20px !important;
display: none;
background: #2b2b2b !important;
}
.blur-button-container {
padding: 15px 20px !important;
background: #363636 !important;
border-top: 1px solid #404040 !important;
display: flex !important;
justify-content: space-between !important;
border-radius: 0 0 8px 8px !important;
gap: 10px !important;
}
.blur-button {
padding: 8px 16px !important;
border: none !important;
border-radius: 4px !important;
cursor: pointer !important;
font-family: 'Courier New', monospace !important;
font-size: 14px !important;
outline: none !important;
width: auto !important;
}
.blur-save-button {
background: #2196F3 !important;
color: white !important;
}
.blur-save-button:hover {
background: #1976D2 !important;
}
.blur-cancel-button {
background: #666 !important;
color: white !important;
}
.blur-cancel-button:hover {
background: #555 !important;
}
.blur-section {
margin: 5px 0 !important;
border-bottom: 1px solid #404040 !important;
background: #2b2b2b !important;
}
.blur-section:last-of-type {
border-bottom: none !important;
}
.blur-section-header {
padding: 10px 20px !important;
background: #363636 !important;
cursor: pointer !important;
user-select: none !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
color: #fff !important;
font-size: 14px !important;
font-weight: bold !important;
}
.blur-section-content {
padding: 15px 20px !important;
display: none;
background: #2b2b2b !important;
}
.blur-section.expanded .blur-section-content {
display: block;
}
.blur-section-arrow {
transition: transform 0.3s ease;
color: #fff !important;
font-size: 12px !important;
}
.blur-section.expanded .blur-section-arrow {
transform: rotate(180deg);
}
.blur-slider-container {
margin-top: 10px !important;
display: flex !important;
align-items: center !important;
gap: 5px !important;
}
.blur-slider-label {
flex: 1 !important;
color: #fff !important;
font-size: 14px !important;
min-width: 150px;
}
.blur-slider {
flex: 1 !important;
height: 4px !important;
max-width: 125px;
background: #666 !important;
outline: none !important;
-webkit-appearance: none !important;
appearance: none !important;
border-radius: 2px !important;
cursor: pointer;
}
.blur-slider::-webkit-slider-thumb {
-webkit-appearance: none !important;
appearance: none !important;
width: 16px !important;
height: 16px !important;
background: #2196F3 !important;
border-radius: 50% !important;
cursor: pointer !important;
}
.blur-slider::-moz-range-thumb {
width: 16px !important;
height: 16px !important;
background: #2196F3 !important;
border-radius: 50% !important;
cursor: pointer !important;
border: none !important;
}
.blur-slider-value {
min-width: 45px !important;
text-align: right !important;
color: #2196F3 !important;
font-size: 12px !important;
}
#blur-toggle-button {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 14px !important;
height: 14px !important;
max-width: 14px !important;
max-height: 14px !important;
min-width: 14px !important;
min-height: 14px !important;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACY0lEQVQ4jY3VP4gVVxQG8N+dN+tz1dVVWRMwiQkSEkTBSgurpEqXQkwsrSWQKo2NhYE06dJIugRRk0qIovgHBcHEpAmYgCYQ/LO7havo+nZ1n/v23hT3Ljs8H7t+cGcuw3zfnHPu+c6Ek5bFQRzFG+ji38S5IX6Z5b9DdEN+Ly0S6uX1HMaHZT+DscDWBUbX0j5P+plqHXGG3mfElQTfaexXY2Nga+KtyJOnpMTqx7xoM3WVZ639S4Tv8VWJ+gH24MuGYCjpLVTMY1NiJ3ZXvBl52aETSg0/x+kGea5E1ETCAqYxiQ7aGMJ44HLiSl2IP/SR+8XgOVoYCWxPxEAVUTEa6bR4WONY+dJK+Auj2JJYhzoh5DquD7wd+KDC/RWEvsGOwElcTNwJOd0oC86X9B9FntX4Tj6En7CqT+xbHCnEhcD2mOu4AWtKlE/xd+J64GZViGewb0B0Xy9uphnHP7ideIxeYD4whT9rfm9zp2qQ4wDBNODZQPTKfbEPP8UFrzqnxiUY5r0e7wf2BnZhs9wyQb50Mdfazxf4cYAYuQyrMIFPsCfktU2uYStk0bVop2K9d1fI5khZv4VsvbHECCpIWXAksDmyIZxguMon1X/C/ZiVG7sKuWTRUmM/ivwaOFvjBQ7JfbaIrlebfY1ivcRkoJNoB4YiExU3e9yqq/ypUxUfY4dswzOy8a80FUP2+L3EtcRdDMfskoeJP7o8CCcahGYPFdyzNMJ6cv/dwOmKW7GMr6Gc5dRYccpyaArO4Yk8WcYrJtczN9MYsB+9xoA9ji1ldYsrJhLTs3QP8LL/F/A/amq/lc3UweoAAAAASUVORK5CYII=') !important;
background-size: cover !important;
background-color: transparent !important;
background-repeat: no-repeat !important;
border: none !important;
cursor: pointer !important;
z-index: 9999 !important;
padding: 0 !important;
margin: 0 !important;
outline: none !important;
pointer-events: auto !important;
filter: blur(0) !important;
}
#blur-toggle-button:hover {
opacity: 0.8 !important;
}
#blur-word-list, #blur-exception-list {
display: flex !important;
flex-direction: column !important;
margin: 0 0 10px 0 !important;
padding: 0 !important;
gap: 8px;
}
.blur-list-entry {
display: flex !important;
gap: 5px !important;
align-items: center !important;
background-color: #333;
padding: 5px;
border-radius: 4px;
}
.blur-list-input {
flex-grow: 1 !important;
background: #1e1e1e !important;
border: 1px solid #404040 !important;
color: #ffffff !important;
padding: 8px !important;
border-radius: 4px !important;
font-family: 'Courier New', monospace !important;
font-size: 14px !important;
line-height: 1.4 !important;
height: auto !important;
width: auto !important;
margin: 0 !important;
outline: none !important;
box-shadow: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
transition: border-color 0.2s ease, filter 0.2s ease !important;
filter: blur(2px) !important;
}
.blur-list-input:focus {
border-color: #2196F3 !important;
filter: blur(0) !important;
}
.blur-delete-button {
background: #ff4444 !important;
color: white !important;
border: none !important;
border-radius: 4px !important;
width: 30px !important;
height: 30px !important;
cursor: pointer !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-family: Arial, sans-serif !important;
font-size: 20px !important;
line-height: 1 !important;
padding: 0 !important;
margin: 0 !important;
outline: none !important;
transition: background-color 0.2s ease !important;
flex-shrink: 0;
}
.blur-delete-button:hover {
background: #cc0000 !important;
}
.blur-exception-mode-select {
background: #444 !important;
color: #fff !important;
border: 1px solid #666 !important;
border-radius: 4px !important;
padding: 5px 8px !important;
font-family: Arial, sans-serif !important;
font-size: 12px !important;
height: 30px !important;
outline: none !important;
cursor: pointer;
flex-shrink: 0;
min-width: 100px;
}
.blur-add-button {
background: #4CAF50 !important;
color: white !important;
border: none !important;
border-radius: 4px !important;
padding: 8px !important;
height: 36px !important;
cursor: pointer !important;
width: 100% !important;
margin-top: 5px !important;
font-family: 'Courier New', monospace !important;
font-size: 14px !important;
text-align: center !important;
outline: none !important;
transition: background-color 0.2s ease !important;
}
.blur-add-current-button {
background: #007bff !important;
color: white !important;
margin-top: 10px !important;
margin-bottom: 5px !important;
}
.blur-add-button:hover, .blur-add-current-button:hover {
filter: brightness(0.9);
}
.blur-option-container {
padding: 10px !important;
background: #1e1e1e !important;
border-radius: 4px !important;
margin-top: 10px !important;
}
.blur-checkbox-label {
display: flex !important;
align-items: center !important;
gap: 10px !important;
color: #fff !important;
font-size: 14px !important;
cursor: pointer !important;
user-select: none !important;
padding: 5px;
border-radius: 3px;
transition: background-color 0.2s ease;
}
.blur-checkbox-label:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.blur-checkbox {
width: 16px !important;
height: 16px !important;
cursor: pointer !important;
margin: 0 !important;
flex-shrink: 0;
bottom: 0px !important;
}
`);
let blurWords = GM_getValue('blurWords', []);
let exceptionList = GM_getValue('exceptionList', []);
let userIP = GM_getValue('userIP', '');
let isFullPageBlurred = false;
let defaultFullPageBlur = GM_getValue('defaultFullPageBlur', false);
let autoUnblurAfterProcessing = GM_getValue('autoUnblurAfterProcessing', true);
let wordBlurStrength = GM_getValue('wordBlurStrength', 7.5);
let hoverDelay = GM_getValue('hoverDelay', 1.0);
let pageBlurStrength = GM_getValue('pageBlurStrength', 10);
let pageUnblurDelay = GM_getValue('pageUnblurDelay', 0.5);
let pageUnblurSpeed = GM_getValue('pageUnblurSpeed', 0.5);
let wordunblurTransitionSpeed = GM_getValue('wordunblurTransitionSpeed', 1.5);
let isIPBlurEnabled = GM_getValue('isIPBlurEnabled', false);
let clickTimeout = null;
const processedNodes = new WeakSet();
let pageMatchesException = null;
let shouldBlurWordsOnPage = true;
let shouldInitialBlur = false;
let manualUnblurListenerActive = false;
let panelElement = null;
let xOffset = 0;
let yOffset = 0;
const BATCH_SIZE = 20;
let initialVisibleScanComplete = false;
let deferredNodes = [];
let processingActive = false;
function findMatchingException(url) {
const currentHostname = new URL(url).hostname;
for (const exception of exceptionList) {
if (!exception || typeof exception.pattern !== 'string' || typeof exception.mode !== 'string') continue;
try {
const pattern = exception.pattern;
if (pattern.endsWith('/*')) {
const domain = pattern.slice(0, -2);
if (currentHostname === domain || currentHostname.endsWith('.' + domain)) {
return exception;
}
} else {
const regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*'));
if (regex.test(url)) {
return exception;
}
}
} catch (e) { }
}
return null;
}
function determineBlurBehavior() {
pageMatchesException = findMatchingException(window.location.href);
shouldBlurWordsOnPage = true;
shouldInitialBlur = defaultFullPageBlur;
if (pageMatchesException) {
switch (pageMatchesException.mode) {
case 'no_blur':
shouldBlurWordsOnPage = false;
shouldInitialBlur = false;
break;
case 'click_unblur':
shouldBlurWordsOnPage = true;
shouldInitialBlur = true;
break;
}
} else {
shouldBlurWordsOnPage = true;
shouldInitialBlur = defaultFullPageBlur;
}
}
function removeFullPageBlurClass(useTransition = true) {
document.documentElement.classList.remove('force-initial-blur');
if (useTransition) {
document.documentElement.classList.add('full-page-unblur');
}
document.documentElement.classList.remove('full-page-blur');
}
function applyFullPageBlurClass() {
document.documentElement.classList.remove('force-initial-blur');
document.documentElement.classList.remove('full-page-unblur');
document.documentElement.classList.add('full-page-blur');
}
function removeFullPageBlur() {
if (!isFullPageBlurred) return;
isFullPageBlurred = false;
setTimeout(() => {
removeFullPageBlurClass(true);
}, 50);
removeManualUnblurListener();
}
function applyFullPageBlur() {
if (isFullPageBlurred) return;
isFullPageBlurred = true;
applyFullPageBlurClass();
}
const manualUnblurHandler = () => {
removeFullPageBlur();
};
function addManualUnblurListener() {
if (manualUnblurListenerActive || !isFullPageBlurred) return;
manualUnblurListenerActive = true;
document.documentElement.addEventListener('click', manualUnblurHandler, { capture: true, once: true });
document.documentElement.addEventListener('keydown', manualUnblurHandler, { capture: true, once: true });
}
function removeManualUnblurListener() {
if (!manualUnblurListenerActive) return;
manualUnblurListenerActive = false;
document.documentElement.removeEventListener('click', manualUnblurHandler, { capture: true, once: true });
document.documentElement.removeEventListener('keydown', manualUnblurHandler, { capture: true, once: true });
}
function checkAndApplyInitialBlurState() {
if (shouldInitialBlur) {
applyFullPageBlurClass();
isFullPageBlurred = true;
} else {
removeFullPageBlurClass(false);
isFullPageBlurred = false;
}
}
function checkAndHandlePostProcessingBlurState() {
if (!isFullPageBlurred) return;
let requiresManualUnblur = false;
if (pageMatchesException) {
if (pageMatchesException.mode === 'click_unblur') {
requiresManualUnblur = true;
} else if (pageMatchesException.mode === 'no_blur'){
removeFullPageBlur();
return;
}
}
if (!requiresManualUnblur && autoUnblurAfterProcessing) {
removeFullPageBlur();
} else if (isFullPageBlurred) {
addManualUnblurListener();
}
}
function signalProcessingComplete() {
if (!initialVisibleScanComplete) {
initialVisibleScanComplete = true;
checkAndHandlePostProcessingBlurState();
if (deferredNodes.length > 0) {
const nodesToProcessNow = [...deferredNodes];
deferredNodes = [];
processNodes(document.body, false, nodesToProcessNow);
}
activateDelayedFunctionality();
}
}
function toggleFullPageBlur() {
if (isFullPageBlurred) {
removeFullPageBlur();
} else {
if (pageMatchesException && pageMatchesException.mode === 'no_blur') return;
applyFullPageBlur();
addManualUnblurListener();
}
}
function updateBlurStyles() {
GM_addStyle(`
.word-blur {
filter: blur(${wordBlurStrength}px) !important;
transition: filter ${wordunblurTransitionSpeed}s ease !important;
}
.word-blur:hover {
filter: blur(0px) !important;
transition-delay: ${hoverDelay}s !important;
}
html.full-page-blur {
filter: blur(${pageBlurStrength}px) !important;
transition: none !important;
}
html.full-page-unblur {
filter: blur(0px) !important;
transition: filter ${pageUnblurSpeed}s ease ${pageUnblurDelay}s !important;
}
`);
}
function fetchIP() {
const ipDisplay = document.getElementById('blur-ip-display');
if (ipDisplay) {
ipDisplay.style.filter = 'blur(0)';
ipDisplay.style.opacity = '0.5';
ipDisplay.textContent = 'Ermittle IP...';
}
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.ipify.org?format=json',
timeout: 5000,
onload: function(response) {
let newIP = null;
try {
const data = JSON.parse(response.responseText);
if (data.ip) {
newIP = data.ip;
}
} catch (e) { } finally {
if (newIP) {
userIP = newIP;
GM_setValue('userIP', userIP);
if (ipDisplay) {
ipDisplay.textContent = `Aktuelle IP: ${userIP}`;
ipDisplay.style.opacity = '1';
ipDisplay.style.color = '#aaf';
}
} else {
if (ipDisplay) {
ipDisplay.textContent = `IP-Ermittlung fehlgeschlagen. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`;
ipDisplay.style.opacity = '1';
ipDisplay.style.color = '#f88';
}
}
}
},
onerror: function(error) {
if (ipDisplay) {
ipDisplay.textContent = `IP-Anfrage fehlgeschlagen. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`;
ipDisplay.style.opacity = '1';
ipDisplay.style.color = '#f88';
}
},
ontimeout: function() {
if (ipDisplay) {
ipDisplay.textContent = `IP-Anfrage Zeitüberschreitung. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`;
ipDisplay.style.opacity = '1';
ipDisplay.style.color = '#f88';
}
}
});
}
function createWordListEntry(value = '') {
const entry = document.createElement('div');
entry.className = 'blur-list-entry';
const input = document.createElement('input');
input.type = 'text';
input.className = 'blur-list-input';
input.value = value;
input.placeholder = 'Wort oder Ausdruck';
const deleteBtn = document.createElement('button');
deleteBtn.className = 'blur-delete-button';
deleteBtn.innerHTML = '×';
deleteBtn.onclick = () => entry.remove();
entry.appendChild(input);
entry.appendChild(deleteBtn);
return entry;
}
function createExceptionListEntry(exception = { pattern: '', mode: 'no_blur' }) {
const entry = document.createElement('div');
entry.className = 'blur-list-entry';
const input = document.createElement('input');
input.type = 'text';
input.className = 'blur-list-input';
input.value = exception.pattern;
input.placeholder = 'z.B. domain.com/* oder https://seite.de/pfad*';
const select = document.createElement('select');
select.className = 'blur-exception-mode-select';
select.innerHTML = `
<option value="no_blur" ${exception.mode === 'no_blur' ? 'selected' : ''}>Nicht Bluren</option>
<option value="click_unblur" ${exception.mode === 'click_unblur' ? 'selected' : ''}>Geblurt lassen</option>
`;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'blur-delete-button';
deleteBtn.innerHTML = '×';
deleteBtn.onclick = () => entry.remove();
entry.appendChild(input);
entry.appendChild(select);
entry.appendChild(deleteBtn);
return entry;
}
function adjustPanelPosition() {
if (!panelElement) return;
const panelRect = panelElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const margin = 10;
let needsAdjustment = false;
let targetX = xOffset;
let targetY = yOffset;
if (panelRect.right > viewportWidth - margin) { targetX = xOffset - (panelRect.right - (viewportWidth - margin)); needsAdjustment = true; }
if (panelRect.left < margin) { targetX = xOffset + (margin - panelRect.left); needsAdjustment = true; }
if (panelRect.bottom > viewportHeight - margin) { targetY = yOffset - (panelRect.bottom - (viewportHeight - margin)); needsAdjustment = true; }
if (panelRect.top < margin) { targetY = yOffset + (margin - panelRect.top); needsAdjustment = true; }
if (panelRect.right < 0 || panelRect.left > viewportWidth || panelRect.bottom < 0 || panelRect.top > viewportHeight) {
targetX = 0;
targetY = 0;
needsAdjustment = true;
}
if (needsAdjustment) {
panelElement.style.transition = 'transform 0.3s ease-out';
xOffset = targetX;
yOffset = targetY;
panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`;
setTimeout(() => { if (panelElement) panelElement.style.transition = 'opacity 0.3s ease-in-out, transform 0.3s ease-out'; }, 300);
}
}
function createSettings() {
panelElement = document.createElement('div');
panelElement.id = 'blur-settings-panel';
const version = GM_info.script.version;
panelElement.innerHTML = `
<div id="blur-settings-header">
<h3 style="margin:0">Wörter-Verwischen Einstellungen (v${version})</h3>
<button id="blur-close-button">×</button>
</div>
<div id="blur-section-container">
<div id="blur-ip-info">
Tipp: Mache ein Doppelkick auf das Einstellungs-Symbol um die Seite zu Bluren. Mit Escape kannst du das Bluren der Seite und das UI wieder Schließen.
</div>
<div class="blur-section">
<div class="blur-section-header">
<span>Allgemeine Einstellungen</span>
<span class="blur-section-arrow">▼</span>
</div>
<div class="blur-section-content">
<div id="blur-text-info">
Passe hier die Stärke und das Verhalten des Blurs an.
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Wort Blur-Stärke (5px - 10px)</label>
<input type="range" min="5" max="10" step="0.5" value="${wordBlurStrength}" class="blur-slider" id="word-blur-slider">
<span class="blur-slider-value">${wordBlurStrength.toFixed(1)}px</span>
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Hover Verzögerung (0.5s - 1.5s)</label>
<input type="range" min="0.5" max="1.5" step="0.1" value="${hoverDelay}" class="blur-slider" id="hover-delay-slider">
<span class="blur-slider-value">${hoverDelay.toFixed(1)}s</span>
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Wort Unblur-Zeit (0.5s - 2.5s)</label>
<input type="range" min="0.5" max="2.5" step="0.1" value="${wordunblurTransitionSpeed}" class="blur-slider" id="transition-slider">
<span class="blur-slider-value">${wordunblurTransitionSpeed.toFixed(1)}s</span>
</div>
<div class="blur-option-container">
<label class="blur-checkbox-label">
<input type="checkbox" class="blur-checkbox" id="blur-default-setting" ${defaultFullPageBlur ? 'checked' : ''}>
Seite standardmäßig beim Start blurren
</label>
</div>
<div class="blur-option-container">
<label class="blur-checkbox-label">
<input type="checkbox" class="blur-checkbox" id="blur-auto-unblur-setting" ${autoUnblurAfterProcessing ? 'checked' : ''}>
Seite nach Verarbeiten automatisch entbluren
</label>
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Seiten Blur-Stärke (5px - 15px)</label>
<input type="range" min="5" max="15" step="0.5" value="${pageBlurStrength}" class="blur-slider" id="page-blur-slider">
<span class="blur-slider-value">${pageBlurStrength.toFixed(1)}px</span>
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Seiten Unblur-Verzögerung (0.0s - 1.0s)</label>
<input type="range" min="0.0" max="1.0" step="0.1" value="${pageUnblurDelay}" class="blur-slider" id="page-unblur-delay-slider">
<span class="blur-slider-value">${pageUnblurDelay.toFixed(1)}s</span>
</div>
<div class="blur-slider-container">
<label class="blur-slider-label">Seiten Unblur-Geschw. (0.0s - 1.0s)</label>
<input type="range" min="0.0" max="1.0" step="0.1" value="${pageUnblurSpeed}" class="blur-slider" id="page-unblur-speed-slider">
<span class="blur-slider-value">${pageUnblurSpeed.toFixed(1)}s</span>
</div>
</div>
</div>
<div class="blur-section">
<div class="blur-section-header">
<span>Ausnahme-Einstellungen</span>
<span class="blur-section-arrow">▼</span>
</div>
<div class="blur-section-content">
<div id="blur-exception-info">
Definiere Seiten (Domains oder URLs mit *), die speziell behandelt werden sollen. Wähle pro Eintrag, ob die Seite gar nicht oder nur initial geblurrt werden soll (Klick/Taste zum Aufheben).
</div>
<button class="blur-add-button blur-add-current-button" id="blur-add-current-site">Aktuelle Domain hinzufügen (domain.com/*)</button>
<div id="blur-exception-list"></div>
<button class="blur-add-button" id="blur-add-exception">+ Neue Ausnahme hinzufügen</button>
</div>
</div>
<div class="blur-section">
<div class="blur-section-header">
<span>IP-Einstellungen</span>
<span class="blur-section-arrow">▼</span>
</div>
<div class="blur-section-content">
<div id="blur-text-info">
Ermittel erst deine IP und aktiviere die Option, wenn du sie automatisch mitblurren möchtest.<br>Die Ermittlung muss manuell angestoßen werden!
</div>
<div id="blur-ip-info">
<span id="blur-ip-display">Aktuelle IP: ${userIP || 'Nicht ermittelt'}</span>
<button id="blur-ip-button">IP ermitteln</button>
</div>
<div class="blur-option-container">
<label class="blur-checkbox-label">
<input type="checkbox" class="blur-checkbox" id="ip-blur-setting" ${isIPBlurEnabled ? 'checked' : ''}>
Ermittelte IP automatisch mitblurren
</label>
</div>
</div>
</div>
<div class="blur-section">
<div class="blur-section-header">
<span>Wortliste</span>
<span class="blur-section-arrow">▼</span>
</div>
<div class="blur-section-content">
<div id="blur-text-info">
Füge hier Wörter oder Ausdrücke hinzu, die geblurrt werden sollen.<br>Nur ein Eintrag pro Feld! Groß-/Kleinschreibung wird ignoriert.
</div>
<div id="blur-word-list"></div>
<button class="blur-add-button" id="blur-add-word">+ Neues Wort hinzufügen</button>
</div>
</div>
</div>
<div class="blur-button-container">
<button class="blur-button blur-cancel-button" id="blur-cancel">Abbrechen</button>
<button class="blur-button blur-save-button" id="blur-save">Speichern & Neu laden</button>
</div>
`;
document.body.appendChild(panelElement);
const wordListContainer = panelElement.querySelector('#blur-word-list');
const exceptionListContainer = panelElement.querySelector('#blur-exception-list');
const addWordButton = panelElement.querySelector('#blur-add-word');
const addExceptionButton = panelElement.querySelector('#blur-add-exception');
const addCurrentSiteButton = panelElement.querySelector('#blur-add-current-site');
const ipButton = panelElement.querySelector('#blur-ip-button');
const sections = panelElement.querySelectorAll('.blur-section');
sections.forEach(section => {
const header = section.querySelector('.blur-section-header');
header.addEventListener('click', () => {
const wasExpanded = section.classList.contains('expanded');
if (!wasExpanded) {
section.classList.add('expanded');
} else {
section.classList.remove('expanded');
}
});
});
const sliders = panelElement.querySelectorAll('.blur-slider');
sliders.forEach(slider => {
const valueDisplay = slider.nextElementSibling;
const isTime = slider.id.includes('delay') || slider.id.includes('transition') || slider.id.includes('speed');
const unit = isTime ? 's' : 'px';
slider.addEventListener('input', () => {
valueDisplay.textContent = `${parseFloat(slider.value).toFixed(1)}${unit}`;
});
valueDisplay.textContent = `${parseFloat(slider.value).toFixed(1)}${unit}`;
});
addWordButton.onclick = () => {
wordListContainer.appendChild(createWordListEntry());
wordListContainer.lastElementChild?.querySelector('input')?.focus();
};
addExceptionButton.onclick = () => {
exceptionListContainer.appendChild(createExceptionListEntry());
exceptionListContainer.lastElementChild?.querySelector('input')?.focus();
};
addCurrentSiteButton.onclick = () => {
const hostname = window.location.hostname;
if (hostname) {
const pattern = `${hostname}/*`;
const inputs = exceptionListContainer.querySelectorAll('.blur-list-input');
const exists = Array.from(inputs).some(input => input.value.trim() === pattern);
if (!exists) {
exceptionListContainer.appendChild(createExceptionListEntry({ pattern: pattern, mode: 'no_blur' }));
exceptionListContainer.lastElementChild?.querySelector('input')?.focus();
} else { }
}
};
ipButton.onclick = fetchIP;
const toggleBtn = document.createElement('button');
toggleBtn.id = 'blur-toggle-button';
document.body.appendChild(toggleBtn);
let isDragging = false;
let currentX, currentY, initialX, initialY;
const dragStart = (e) => {
if (e.target.closest('#blur-settings-header')) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
isDragging = true;
if (panelElement) panelElement.style.transition = 'none';
}
};
const drag = (e) => {
if (isDragging && panelElement) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`;
}
};
const dragEnd = () => {
if (isDragging) {
isDragging = false;
if (panelElement) panelElement.style.transition = 'opacity 0.3s ease-in-out, transform 0.3s ease-out';
adjustPanelPosition();
}
};
window.addEventListener('resize', adjustPanelPosition);
document.addEventListener('mousedown', dragStart, false);
document.addEventListener('mousemove', drag, false);
document.addEventListener('mouseup', dragEnd, false);
document.addEventListener('mouseleave', dragEnd, false);
window.showPanel = function showPanel() {
if (!panelElement) return;
panelElement.style.display = 'block';
panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`;
setTimeout(() => panelElement.classList.add('visible'), 10);
wordListContainer.innerHTML = '';
blurWords.forEach(word => wordListContainer.appendChild(createWordListEntry(word)));
exceptionListContainer.innerHTML = '';
exceptionList.forEach(exception => exceptionListContainer.appendChild(createExceptionListEntry(exception)));
const ipDisplay = panelElement.querySelector('#blur-ip-display');
if (ipDisplay) ipDisplay.textContent = `Aktuelle IP: ${userIP || 'Nicht ermittelt'}`;
const autoUnblurCheckbox = panelElement.querySelector('#blur-auto-unblur-setting');
if (autoUnblurCheckbox) autoUnblurCheckbox.checked = autoUnblurAfterProcessing;
const defaultBlurCheckbox = panelElement.querySelector('#blur-default-setting');
if (defaultBlurCheckbox) defaultBlurCheckbox.checked = defaultFullPageBlur;
}
window.hidePanel = function hidePanel() {
if (!panelElement) return;
panelElement.classList.remove('visible');
setTimeout(() => panelElement.style.display = 'none', 300);
}
function handleClick() {
if (clickTimeout !== null) {
clearTimeout(clickTimeout);
clickTimeout = null;
toggleFullPageBlur();
} else {
clickTimeout = setTimeout(() => {
clickTimeout = null;
window.showPanel();
}, 250);
}
}
function saveSettings() {
if (!panelElement) return;
const wordInputs = panelElement.querySelectorAll('#blur-word-list .blur-list-input');
const newWords = [...new Set([...wordInputs].map(input => input.value.trim()).filter(w => w))];
const newExceptions = [];
const exceptionEntries = panelElement.querySelectorAll('#blur-exception-list .blur-list-entry');
exceptionEntries.forEach(entry => {
const patternInput = entry.querySelector('.blur-list-input');
const modeSelect = entry.querySelector('.blur-exception-mode-select');
const pattern = patternInput?.value.trim();
const mode = modeSelect?.value;
if (pattern && mode) {
newExceptions.push({ pattern: pattern, mode: mode });
}
});
const pageBlurSlider = panelElement.querySelector('#page-blur-slider');
const wordBlurSlider = panelElement.querySelector('#word-blur-slider');
const hoverDelaySlider = panelElement.querySelector('#hover-delay-slider');
const transitionSlider = panelElement.querySelector('#transition-slider');
const pageUnblurDelaySlider = panelElement.querySelector('#page-unblur-delay-slider');
const pageUnblurSpeedSlider = panelElement.querySelector('#page-unblur-speed-slider');
const defaultBlurCheckbox = panelElement.querySelector('#blur-default-setting');
const autoUnblurCheckbox = panelElement.querySelector('#blur-auto-unblur-setting');
const ipBlurCheckbox = panelElement.querySelector('#ip-blur-setting');
pageBlurStrength = parseFloat(pageBlurSlider.value);
wordBlurStrength = parseFloat(wordBlurSlider.value);
hoverDelay = parseFloat(hoverDelaySlider.value);
wordunblurTransitionSpeed = parseFloat(transitionSlider.value);
pageUnblurDelay = parseFloat(pageUnblurDelaySlider.value);
pageUnblurSpeed = parseFloat(pageUnblurSpeedSlider.value);
defaultFullPageBlur = defaultBlurCheckbox.checked;
autoUnblurAfterProcessing = autoUnblurCheckbox.checked;
isIPBlurEnabled = ipBlurCheckbox.checked;
GM_setValue('blurWords', newWords);
GM_setValue('exceptionList', newExceptions);
GM_setValue('hoverDelay', hoverDelay);
GM_setValue('pageBlurStrength', pageBlurStrength);
GM_setValue('pageUnblurDelay', pageUnblurDelay);
GM_setValue('pageUnblurSpeed', pageUnblurSpeed);
GM_setValue('wordBlurStrength', wordBlurStrength);
GM_setValue('wordunblurTransitionSpeed', wordunblurTransitionSpeed);
GM_setValue('defaultFullPageBlur', defaultFullPageBlur);
GM_setValue('autoUnblurAfterProcessing', autoUnblurAfterProcessing);
GM_setValue('isIPBlurEnabled', isIPBlurEnabled);
window.hidePanel();
location.reload();
}
function handleKeyPress(e) {
if (e.key === 'Escape') {
if (panelElement && panelElement.style.display === 'block' && panelElement.classList.contains('visible')) {
window.hidePanel();
} else if (isFullPageBlurred && !manualUnblurListenerActive) {
removeFullPageBlur();
}
}
}
toggleBtn.addEventListener('click', handleClick);
panelElement.querySelector('#blur-close-button').addEventListener('click', window.hidePanel);
panelElement.querySelector('#blur-cancel').addEventListener('click', window.hidePanel);
panelElement.querySelector('#blur-save').addEventListener('click', saveSettings);
document.addEventListener('keydown', handleKeyPress);
}
function findBlurPositions(text) {
const positions = [];
blurWords.forEach(word => {
const escapedWord = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(escapedWord, 'gi');
let match;
while ((match = regex.exec(text)) !== null) {
positions.push({
start: match.index,
end: match.index + match[0].length,
word: match[0]
});
}
});
if (isIPBlurEnabled && userIP) {
const ipRegex = new RegExp(userIP.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
let match;
while ((match = ipRegex.exec(text)) !== null) {
positions.push({
start: match.index,
end: match.index + match[0].length,
word: match[0]
});
}
}
positions.sort((a, b) => a.start - b.start);
const mergedPositions = [];
let current = null;
for (const pos of positions) {
if (!current) {
current = {...pos};
} else if (pos.start <= current.end) {
current.end = Math.max(current.end, pos.end);
current.word = text.slice(current.start, current.end);
} else {
mergedPositions.push(current);
current = {...pos};
}
}
if (current) {
mergedPositions.push(current);
}
return mergedPositions;
}
function isProcessed(node) {
if (!node) return true;
if (!node.parentElement) return false;
const isInput = node.parentElement.closest('input, textarea, [contenteditable="true"]');
if (isInput) return true;
const isBlurSettings = node.parentElement.closest('#blur-settings-panel, #blur-ip-display');
if (isBlurSettings) return true;
return processedNodes.has(node) ||
node.parentElement.classList.contains('word-blur') ||
node.parentElement.hasAttribute('data-blurred');
}
function blurText(node) {
if (!shouldBlurWordsOnPage || isProcessed(node)) return;
const text = node.textContent;
if (!text || !text.trim()) {
processedNodes.add(node);
return;
}
const positions = findBlurPositions(text);
if (positions.length === 0) {
processedNodes.add(node);
return;
}
const container = document.createElement('span');
container.setAttribute('data-blurred', 'true');
let lastIndex = 0;
positions.forEach(pos => {
if (pos.start > lastIndex) {
container.appendChild(document.createTextNode(
text.slice(lastIndex, pos.start)
));
}
const blurSpan = document.createElement('span');
blurSpan.className = 'word-blur';
blurSpan.textContent = pos.word;
container.appendChild(blurSpan);
lastIndex = pos.end;
});
if (lastIndex < text.length) {
container.appendChild(document.createTextNode(
text.slice(lastIndex)
));
}
try {
if (node.parentNode) {
node.parentNode.replaceChild(container, node);
processedNodes.add(container);
} else {
processedNodes.add(node);
}
} catch (e) {
processedNodes.add(node);
}
}
function isElementInViewport(el) {
if (!el || typeof el.getBoundingClientRect !== 'function') return false;
if (!el.offsetParent && el.offsetWidth === 0 && el.offsetHeight === 0 && !el.closest('svg')) {
return false;
}
const rect = el.getBoundingClientRect();
return (
rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
rect.bottom > 0 &&
rect.right > 0
);
}
function getBlurrableParent(node) {
if (!node || !node.parentElement) return null;
return node.parentElement.closest('p, div, span, li, td, th, h1, h2, h3, h4, h5, h6, a, pre, code, body') || node.parentElement;
}
function processBatch(nodes, startIndex, forRoot, isInitialVisibleScan) {
if (processingActive && !isInitialVisibleScan && forRoot !== document.body) return; // Avoid parallel processing for sub-elements if main is running
if (isInitialVisibleScan || forRoot === document.body && !initialVisibleScanComplete) {
processingActive = true;
}
const nodesToProcessThisBatch = [];
let currentIndex = startIndex;
while (currentIndex < nodes.length && nodesToProcessThisBatch.length < BATCH_SIZE) {
const node = nodes[currentIndex];
if (isInitialVisibleScan) {
const blurrableParent = getBlurrableParent(node);
if (blurrableParent && isElementInViewport(blurrableParent)) {
nodesToProcessThisBatch.push(node);
} else {
deferredNodes.push(node);
}
} else {
nodesToProcessThisBatch.push(node);
}
currentIndex++;
}
nodesToProcessThisBatch.forEach(node => {
try {
blurText(node);
} catch (e) { }
});
if (currentIndex < nodes.length) {
requestAnimationFrame(() => processBatch(nodes, currentIndex, forRoot, isInitialVisibleScan));
} else {
processingActive = false;
if (forRoot === document.body) {
if (isInitialVisibleScan && !initialVisibleScanComplete) {
signalProcessingComplete();
} else if (!initialVisibleScanComplete) {
signalProcessingComplete();
}
}
if (typeof forRoot.processingDoneCallback === 'function') {
forRoot.processingDoneCallback();
delete forRoot.processingDoneCallback;
}
}
}
function processNodes(root, isInitialVisibleScan = false, customNodes = null) {
if (!shouldBlurWordsOnPage) {
if (root === document.body && isInitialVisibleScan && !initialVisibleScanComplete) {
signalProcessingComplete();
}
return;
}
let nodesToWalk = [];
if (customNodes) {
nodesToWalk = customNodes;
} else {
const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
if (!node.textContent || node.textContent.trim() === '') return NodeFilter.FILTER_REJECT;
if (node.parentElement?.nodeName === 'SCRIPT' ||
node.parentElement?.nodeName === 'STYLE' ||
node.parentElement?.nodeName === 'NOSCRIPT') {
return NodeFilter.FILTER_REJECT;
}
if (node.parentElement?.closest('#blur-settings-panel, #blur-ip-display, #blur-toggle-button')) {
return NodeFilter.FILTER_REJECT;
}
if (node.parentElement?.closest('input, textarea, [contenteditable="true"]')) {
return NodeFilter.FILTER_REJECT;
}
if (isProcessed(node)) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
try {
let currentNode;
while (currentNode = walker.nextNode()) {
nodesToWalk.push(currentNode);
}
} catch (e) { }
}
if (nodesToWalk.length > 0) {
processBatch(nodesToWalk, 0, root, isInitialVisibleScan);
} else {
if (root === document.body) {
if (isInitialVisibleScan && !initialVisibleScanComplete) {
signalProcessingComplete();
} else if (!initialVisibleScanComplete) {
signalProcessingComplete();
}
}
}
}
function checkForMissedNodes(root) {
if (!initialVisibleScanComplete) return;
if (processingActive && root !== document.body) return;
const potentialDropdowns = root.querySelectorAll('div[style*="position"], div[style*="visibility"], div[style*="display"], div[class*="menu"], div[class*="dropdown"], ul, nav');
potentialDropdowns.forEach(element => {
if (!element.closest('#blur-settings-panel')) {
processNodes(element, false);
}
});
}
const observer = new MutationObserver((mutations) => {
if (!shouldBlurWordsOnPage) return;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.closest && node.closest('#blur-settings-panel')) return;
let nodesToProcessFromMutation = [];
if (node.nodeType === Node.ELEMENT_NODE) {
nodesToProcessFromMutation.push(node);
} else if (node.nodeType === Node.TEXT_NODE) {
if (!isProcessed(node)) {
if (!initialVisibleScanComplete) {
deferredNodes.push(node);
} else {
blurText(node);
}
}
return;
}
nodesToProcessFromMutation.forEach(n => {
if (!initialVisibleScanComplete) {
const walker = document.createTreeWalker(n, NodeFilter.SHOW_TEXT);
let current;
while(current = walker.nextNode()) {
if (current.textContent.trim() && !isProcessed(current) && !current.parentElement?.closest('input, textarea, [contenteditable="true"], #blur-settings-panel')) {
deferredNodes.push(current);
}
}
} else {
processNodes(n, false);
requestAnimationFrame(() => { if (document.contains(n)) checkForMissedNodes(n); });
}
});
});
} else if (mutation.type === 'characterData') {
if (mutation.target.parentElement?.closest('#blur-settings-panel')) return;
if (!isProcessed(mutation.target)) {
if (!initialVisibleScanComplete) {
deferredNodes.push(mutation.target);
} else {
blurText(mutation.target);
}
}
}
else if (mutation.type === 'attributes') {
const target = mutation.target;
if (target.nodeType === Node.ELEMENT_NODE &&
!target.closest('#blur-settings-panel') &&
(mutation.attributeName === 'style' ||
mutation.attributeName === 'class' ||
mutation.attributeName === 'aria-expanded')) {
if (initialVisibleScanComplete) {
requestAnimationFrame(() => {
if (document.contains(target)) {
processNodes(target, false);
}
});
}
}
}
}
});
function activateDelayedFunctionality() {
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ['style', 'class', 'aria-expanded', 'data-active', 'data-open', 'data-visible']
});
requestAnimationFrame(() => {
const dropdowns = document.querySelectorAll('div[style*="position:absolute"], div[style*="visibility"], div[class*="menu"], div[class*="dropdown"], div[role="menu"], div[aria-expanded="true"]');
dropdowns.forEach(dropdown => {
if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false);
});
});
document.addEventListener('click', (e) => {
if (!initialVisibleScanComplete) return;
requestAnimationFrame(() => {
const dropdowns = document.querySelectorAll('div[style*="position:absolute"], div[style*="visibility:visible"], div[role="menu"][aria-hidden="false"], div[role="menu"], div[aria-expanded="true"]');
dropdowns.forEach(dropdown => {
if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false);
});
});
}, true);
document.addEventListener('mouseover', () => {
if (!initialVisibleScanComplete) return;
requestAnimationFrame(() => {
const visibleDropdowns = document.querySelectorAll('div[style*="position:absolute"]:not([style*="display:none"]), div[style*="visibility:visible"]');
visibleDropdowns.forEach(dropdown => {
if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false);
});
});
}, true);
}
}
function init() {
determineBlurBehavior();
checkAndApplyInitialBlurState();
createSettings(); // Erstellt UI, fügt aber noch keine dynamischen Listener hinzu
if (shouldBlurWordsOnPage) {
if (document.body) {
processNodes(document.body, true); // Startet initialen Scan für sichtbare Bereiche
} else {
const bodyObserver = new MutationObserver(() => {
if (document.body) {
processNodes(document.body, true);
bodyObserver.disconnect();
}
});
bodyObserver.observe(document.documentElement, { childList: true });
}
} else {
signalProcessingComplete(); // Nichts zu tun, also "fertig"
}
updateBlurStyles();
GM_registerMenuCommand('Blur-Einstellungen öffnen', () => {
const panel = document.getElementById('blur-settings-panel');
if(panel && panel.style.display !== 'block') {
if (window.showPanel) window.showPanel();
} else if (panel) {
if (window.hidePanel) window.hidePanel();
}
});
}
if (document.readyState === 'loading' || document.readyState === 'interactive') {
document.addEventListener('DOMContentLoaded', () => requestAnimationFrame(init));
} else {
requestAnimationFrame(init);
}
})();