Greasy Fork

mydealz Manager

Deals gezielt ausblenden mittels X Button, Filtern nach Händlern und Wörtern im Titel. Teure und kalte Deals ausblenden.

当前为 2025-02-21 提交的版本,查看 最新版本

// ==UserScript==
// @name         mydealz Manager
// @namespace    http://tampermonkey.net/
// @version      1.12.7
// @description  Deals gezielt ausblenden mittels X Button, Filtern nach Händlern und Wörtern im Titel. Teure und kalte Deals ausblenden.
// @author       Moritz Baumeister (https://www.mydealz.de/profile/BobBaumeister) (https://github.com/grapefruit89) & Flo (https://www.mydealz.de/profile/Basics0119) (https://github.com/9jS2PL5T)
// @license      MIT
// @match        https://www.mydealz.de/*
// @match        https://www.preisjaeger.at/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=mydealz.de
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// ==/UserScript==

// Versions-Änderungen
// FIX: Maximalpreis - Deals mit Preis >999,99 wurden nicht gefiltert, wenn Maximalpreis niedriger war. Komma wurde nicht berücksichtigt (Bsp 999,99 -> 999999).
// FIX: Der Wortfilter hat Groß- und Kleinschreibung unterschieden, wodurch doppelte Einträge entstanden sind (z. B. 'geschirrspüler' und 'Geschirrspüler').
// FIX: Gespeicherte Daten wurden bei einem Script Update nicht übernommen, da sie seit 1.12.5 nur noch im GM Storage lagen. Von dort ließen sie sich aber nicht in eine neuere Version übernehmen. Daher werden Daten nun auch wieder im localstorage gespeichert.
// CHANGE: Backup - maxprice und hidecolddeals werden nun auch im Backup gesichert und wiederhergestellt.
// CHANGE: Design an mydealz Designänderung angepasst.
// REMOVE: Nicht benötigter Code wurde entfernt.

// Einbinden von Font Awesome für Icons
const fontAwesomeLink = document.createElement('link');
fontAwesomeLink.rel = 'stylesheet';
fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
document.head.appendChild(fontAwesomeLink);

// Add constant for touch detection
const IS_TOUCH_DEVICE = ('ontouchstart' in window) ||
      (navigator.maxTouchPoints > 0) ||
      (navigator.msMaxTouchPoints > 0);

// Dark mode color constants
const THEME_COLORS = {
    light: {
        background: '#f9f9f9',
        border: '#ccc',
        text: '#333',
        buttonBg: '#f0f0f0',
        buttonBorder: '#ccc',
        inputBg: '#fff',
        itemBg: '#f0f0f0',
        itemHoverBg: '#e8e8e8'
    },
    dark: {
        background: '#1f1f1f',
        border: '#2d2d2d',
        text: '#ffffff',
        buttonBg: '#2d2d2d',
        buttonBorder: '#3d3d3d',
        inputBg: '#2d2d2d',
        itemBg: '#2d2d2d',
        itemHoverBg: '#3d3d3d'
    }
};

const themeObserver = new MutationObserver((mutations) => {
    requestAnimationFrame(() => {
        const isLight = !isDarkMode();
        updateAllUIThemes(isLight);
    });
});

themeObserver.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['class', 'data-theme']
});

themeObserver.observe(document.body, {
    attributes: true,
    attributeFilter: ['class', 'data-theme']
});

// --- 1. Initial Setup ---
const EXCLUDE_WORDS_KEY = 'excludeWords';
const EXCLUDE_MERCHANTS_KEY = 'excludeMerchantIDs';
const HIDDEN_DEALS_KEY = 'hiddenDeals';
const MERCHANT_PAGE_SELECTOR = '.merchant-banner';
const HIDE_COLD_DEALS_KEY = 'hideColdDeals';
const MAX_PRICE_KEY = 'maxPrice';


// Load data immediately
let excludeWords = [];
let excludeMerchantIDs = [];
let hiddenDeals = [];
let suggestedWords = [];
let activeSubUI = null;
let dealThatOpenedSettings = null;
let settingsDiv = null;
let merchantListDiv = null;
let wordsListDiv = null;
let uiClickOutsideHandler = null;
let isSettingsOpen = false;
let hideColdDeals = localStorage.getItem(HIDE_COLD_DEALS_KEY) === 'true';
let maxPrice = parseFloat(localStorage.getItem(MAX_PRICE_KEY)) || 0;
let suggestionClickHandler = null;

// Sync-Funktion für beide Storage-Systeme
async function syncStorage() {

    // Prüfe ob Migration bereits durchgeführt wurde
    const migrationComplete = GM_getValue('migrationComplete', false);

    if (migrationComplete) {
        return;
    }

    // Prüfe ob GM_Storage Daten hat
    const gmExcludeWords = GM_getValue('excludeWords', null);
    const gmExcludeMerchants = GM_getValue('excludeMerchantsData', null);
    const gmHiddenDeals = GM_getValue('hiddenDeals', null);
    const gmHideColdDeals = GM_getValue('hideColdDeals', null);
    const gmMaxPrice = GM_getValue('maxPrice', null);

    // Prüfe ob localStorage Daten hat
    const lsExcludeWords = JSON.parse(localStorage.getItem('excludeWords') || 'null');
    const lsExcludeMerchants = JSON.parse(localStorage.getItem('excludeMerchantsData') || 'null');
    const lsHiddenDeals = JSON.parse(localStorage.getItem('hiddenDeals') || 'null');
    const lsHideColdDeals = localStorage.getItem('hideColdDeals') || 'null';
    const lsMaxPrice = localStorage.getItem('maxPrice') || 'null';

    // Migration von localStorage zu GM_storage wenn nötig
    let migrationPerformed = false;

    if (!gmExcludeWords && lsExcludeWords) {
        GM_setValue('excludeWords', lsExcludeWords);
        excludeWords = lsExcludeWords;
        migrationPerformed = true;
    }
    if (!gmExcludeMerchants && lsExcludeMerchants) {
        GM_setValue('excludeMerchantsData', lsExcludeMerchants);
        excludeMerchantIDs = lsExcludeMerchants.map(m => m.id);
        migrationPerformed = true;
    }
    if (!gmHiddenDeals && lsHiddenDeals) {
        GM_setValue('hiddenDeals', lsHiddenDeals);
        hiddenDeals = lsHiddenDeals;
        migrationPerformed = true;
    }
    if (!gmHideColdDeals && lsHideColdDeals !== 'null') {
        GM_setValue('hideColdDeals', lsHideColdDeals === 'true');
        hideColdDeals = lsHideColdDeals === 'true';
        migrationPerformed = true;
    }
    if (!gmMaxPrice && lsMaxPrice !== 'null') {
        GM_setValue('maxPrice', lsMaxPrice);
        maxPrice = parseFloat(lsMaxPrice);
        migrationPerformed = true;
    }

    if (migrationPerformed) {
        GM_setValue('migrationComplete', true);
    }

    // Synchronisiere localStorage mit GM_Storage
    localStorage.setItem('excludeWords', JSON.stringify(gmExcludeWords || []));
    localStorage.setItem('excludeMerchantsData', JSON.stringify(gmExcludeMerchants || []));
    localStorage.setItem('hiddenDeals', JSON.stringify(gmHiddenDeals || []));
    localStorage.setItem('hideColdDeals', (gmHideColdDeals || false).toString());
    localStorage.setItem('maxPrice', (gmMaxPrice || '0').toString());

}

function updateAllUIThemes(isLight) {
    // Update buttons
    document.querySelectorAll('.custom-hide-button').forEach(button => {
        if (button) {
            const bgColor = isLight ? '#ffffff' : '#1d1f20';
            button.style.setProperty('background', bgColor, 'important');
        }
    });

    // Update open UIs
    if (isSettingsOpen || activeSubUI) {
        updateUITheme();
    }

    // Update filter menu if open
    const filterMenu = document.querySelector('.subNavMenu-list');
    if (filterMenu) {
        const colors = getThemeColors();
        const inputs = filterMenu.querySelectorAll('input');
        inputs.forEach(input => {
            input.style.borderColor = colors.border;
            input.style.backgroundColor = colors.inputBg;
            input.style.color = colors.text;
        });
    }
}

function isDarkMode() {
    // Check system preference
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

    // Check document theme
    const htmlElement = document.documentElement;
    const bodyElement = document.body;

    // Check for dark theme indicators
    const isDark =
          htmlElement.classList.contains('dark') ||
          bodyElement.classList.contains('dark') ||
          htmlElement.getAttribute('data-theme') === 'dark' ||
          document.querySelector('html[data-theme="dark"]') !== null ||
          (prefersDark && !htmlElement.classList.contains('light')); // System dark + no explicit light

    return isDark;
}

const systemThemeObserver = window.matchMedia('(prefers-color-scheme: dark)');
systemThemeObserver.addListener((e) => {
    requestAnimationFrame(() => {
        const isLight = !isDarkMode();
        updateAllUIThemes(isLight);
    });
});

const hideButtonThemeObserver = new MutationObserver(() => {
    const isLight = !isDarkMode();

    requestAnimationFrame(() => {
        document.querySelectorAll('.custom-hide-button').forEach(button => {
            if (button) {
                const bgColor = isLight ? '#ffffff' : '#1d1f20';
                const borderColor = isLight ? 'rgba(3,12,25,0.23)' : 'rgb(107, 109, 109)';
                button.style.cssText = `
                    position: absolute !important;
                    left: 50% !important;
                    top: 50% !important;
                    transform: translate(-50%, -50%) !important;
                    z-index: 10002 !important;
                    background: ${bgColor} !important;
                    border: 1px solid ${borderColor} !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    padding: 4px !important;
                    width: 28px !important;
                    height: 28px !important;
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    pointer-events: all !important;
                    box-shadow: none !important;
                    font-size: 12px !important;
                `;
            }
        });

        // Update settings UI wenn offen
        if (isSettingsOpen) {
            updateUITheme();
        }
    });
});

hideButtonThemeObserver.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['class']
});

function initUIContainers() {
    settingsDiv = document.createElement('div');
    merchantListDiv = document.createElement('div');
    wordsListDiv = document.createElement('div');
}

// --- 1. Core Functions ---
function processArticles() {
    // Cache für bereits verarbeitete Artikel
    const processedDeals = new Set();

    const deals = document.querySelectorAll('article.thread--deal, article.thread--voucher');
    deals.forEach(deal => {
        const dealId = deal.getAttribute('id');

        // Skip wenn bereits verarbeitet
        if (processedDeals.has(dealId)) return;
        processedDeals.add(dealId);

        if (hiddenDeals.includes(dealId)) {
            hideDeal(deal);
            return;
        }

        if (shouldExcludeArticle(deal)) {
            hideDeal(deal);
            return;
        }

        deal.style.display = 'block';
        deal.style.opacity = '1';
    });
}

function getThemeColors() {
    const isDark = isDarkMode();
    return isDark ? THEME_COLORS.dark : THEME_COLORS.light;
}

// Update word/merchant item styles in list creation
function updateItemStyles(item, colors) {
    item.style.cssText = `
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 5px;
        padding: 5px;
        background: ${colors.itemBg};
        color: ${colors.text};
        border: 1px solid ${colors.border};
        border-radius: 3px;
    `;
}

// Update createMerchantListUI and createExcludeWordsUI
function updateListStyles(listDiv, colors) {
    // Apply styles to list items
    listDiv.querySelectorAll('.merchant-item, .word-item').forEach(item => {
        updateItemStyles(item, colors);
    });

    // Update search input
    const searchInput = listDiv.querySelector('input[type="text"]');
    if (searchInput) {
        searchInput.style.cssText = `
            width: 100%;
            padding: 5px;
            margin-bottom: 10px;
            background: ${colors.inputBg};
            border: 1px solid ${colors.border};
            color: ${colors.text};
            border-radius: 3px;
        `;
    }

    // Update clear button
    const clearButton = listDiv.querySelector('[id*="clear"]');
    if (clearButton) {
        clearButton.style.cssText = `
            width: 100%;
            padding: 5px 10px;
            background: ${colors.buttonBg};
            border: 1px solid ${colors.buttonBorder};
            color: ${colors.text};
            border-radius: 3px;
            cursor: pointer;
            margin-top: 10px;
        `;
    }
}

function updateUITheme() {
    const colors = getThemeColors();

    [settingsDiv, merchantListDiv, wordsListDiv].forEach(div => {
        if (div?.parentNode) {
            div.style.background = colors.background;
            div.style.border = `1px solid ${colors.border}`;
            div.style.color = colors.text;

            // Update all buttons and inputs
            div.querySelectorAll('button:not([id*="close"])').forEach(btn => {
                btn.style.background = colors.buttonBg;
                btn.style.border = `1px solid ${colors.buttonBorder}`;
                btn.style.color = colors.text;
            });

            div.querySelectorAll('input').forEach(input => {
                input.style.background = colors.inputBg;
                input.style.border = `1px solid ${colors.border}`;
                input.style.color = colors.text;
            });
        }
    });
}

// Define observer
const observer = new MutationObserver(throttle((mutations) => {
    processArticles();
    addSettingsButton();
    addHideButtons();
}, 250));

function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => {
                inThrottle = false;
                return false;
            }, limit);
        }
    }
}

// Initialize everything
(function init() {
    syncStorage();
    processArticles();
    addSettingsButton();
    addHideButtons();

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

// --- 2. Hilfsfunktionen ---
function shouldExcludeArticle(article) {
    const titleElement = article.querySelector('.thread-title');
    if (!titleElement) return false;

    // 2. Quick checks (temperature & price)
    // Temperature check
    if (hideColdDeals) {
        const tempElement = article.querySelector('.cept-vote-temp .overflow--wrap-off');
        if (tempElement) {
            const temp = parseInt(tempElement.textContent);
            if (!isNaN(temp) && temp < 0) return true;
        }
    }

    // Price check
    if (maxPrice > 0) {
        const priceSelectors = ['.threadItemCard-price', '.thread-price', '[class*="price"]', '.cept-tp'];

        for (const selector of priceSelectors) {
            const priceElement = article.querySelector(selector);
            if (!priceElement) continue;

            try {
                const priceMatch = priceElement.textContent.trim().match(/(\d+(?:[,.]\d{1,2})?)\s*€/);
                if (priceMatch) {
                    const price = parseFloat(priceMatch[1].replace(',', '.'));
                    if (!isNaN(price) && price > maxPrice) return true;
                }
            } catch (error) {
                continue;
            }
        }
    }

    // 3. Complex checks
    // Word check
    const normalizedTitle = decodeHtml(titleElement.innerHTML)
    .toLowerCase()
    .replace(/&nbsp;/g, ' ')
    .replace(/[(){}»«„"\/\[\]]/g, ' ') // Don't replace hyphens
    .replace(/,/g, ' ') // Kommas durch Leerzeichen ersetzen
    .replace(/\s+/g, ' ')
    .trim();

    if (excludeWords.some(word => {
        const searchTerm = word.toLowerCase().trim();

        // If searchTerm contains hyphens, look for it as part of hyphenated words
        if (searchTerm.includes('-')) {
            // Split title into hyphenated word groups
            const hyphenatedWords = normalizedTitle.match(/\S+(?:-\S+)*/g) || [];
            return hyphenatedWords.some(titleWord =>
                                        // Check if any hyphenated word contains our search term
                                        titleWord.includes(searchTerm)
                                       );
        } else {
            // For non-hyphenated words, require exact matches
            return normalizedTitle.split(/\s+/).some(titleWord =>
                                                     titleWord === searchTerm || // Exact match
                                                     (searchTerm.includes(' ') && normalizedTitle.includes(searchTerm)) // Multi-word terms
                                                    );
        }
    })) {
        return true;
    }

    // Merchant check
    const merchantLink = article.querySelector('a[href*="merchant-id="]');
    if (merchantLink) {
        const merchantIDMatch = merchantLink.getAttribute('href').match(/merchant-id=(\d+)/);
        if (merchantIDMatch && excludeMerchantIDs.includes(merchantIDMatch[1])) {
            return true;
        }
    }
    return false;
}

function hideDeal(deal) {
    deal.style.display = 'none';
}

// Funktion zum Speichern der ausgeblendeten Deals
function saveHiddenDeals() {
    GM_setValue('hiddenDeals', hiddenDeals);
    localStorage.setItem('hiddenDeals', JSON.stringify(hiddenDeals));
}

// Speichern der `excludeWords` und `excludeMerchantIDs`
function saveExcludeWords(words) {

    // Normalisiere nur Groß-/Kleinschreibung und entferne Duplikate
    const normalizedWords = words.reduce((acc, word) => {
        // Konvertiere zum Vergleich in Kleinbuchstaben
        const lowerWord = word.toLowerCase();

        // Prüfe ob das Wort (unabhängig von Groß-/Kleinschreibung) bereits existiert
        const exists = acc.some(w => w.toLowerCase() === lowerWord);

        if (!exists) {
            // Wenn noch nicht vorhanden, füge das originale Wort hinzu
            acc.push(word);
        }

        return acc;
    }, []);

    // Speichere in beiden Systemen
    GM_setValue('excludeWords', normalizedWords);
    localStorage.setItem('excludeWords', JSON.stringify(normalizedWords));

}

function loadExcludeWords() {

    // Versuche zuerst GM Storage
    const gmWords = GM_getValue('excludeWords', []);

    // Dann localStorage
    let lsWords = [];
    try {
        lsWords = JSON.parse(localStorage.getItem('excludeWords') || '[]');
    } catch (e) {
        console.error('Error loading words from localStorage:', e);
    }

    // Kombiniere beide, GM hat Priorität
    return gmWords.length > 0 ? gmWords : lsWords;
}

function saveExcludeMerchants(merchantsData) {
    const validMerchants = merchantsData.filter(m =>
        m && typeof m.id !== 'undefined' && m.id !== null &&
        typeof m.name !== 'undefined' && m.name !== null
    );
    const ids = validMerchants.map(m => m.id);

    GM_setValue('excludeMerchantIDs', ids);
    GM_setValue('excludeMerchantsData', validMerchants);
    localStorage.setItem('excludeMerchantsData', JSON.stringify(validMerchants));

    excludeMerchantIDs = ids;
}

function loadExcludeMerchants() {
    const merchantsData = GM_getValue('excludeMerchantsData', []);
    const legacyIds = GM_getValue('excludeMerchantIDs', []);

    // Filter out invalid entries
    const validMerchants = merchantsData.filter(m =>
                                                m &&
                                                typeof m.id !== 'undefined' &&
                                                m.id !== null &&
                                                typeof m.name !== 'undefined' &&
                                                m.name !== null
                                               );

    // Convert legacy IDs if needed
    if (validMerchants.length === 0 && legacyIds.length > 0) {
        return legacyIds
            .filter(id => id && typeof id !== 'undefined')
            .map(id => ({ id, name: id }));
    }

    return validMerchants;
}

// Clean up existing data on script init
(function cleanupMerchantData() {
    const merchants = loadExcludeMerchants();
    saveExcludeMerchants(merchants);
})();

// Fügt Event Listener hinzu, um Auto-Speichern zu ermöglichen
function addAutoSaveListeners() {
    // Event Listener für Eingabefelder
    const excludeWordsInput = document.getElementById('excludeWordsInput');
    excludeWordsInput.addEventListener('input', () => {
        const newWords = excludeWordsInput.value.split('\n').map(w => w.trim()).filter(Boolean);
        saveExcludeWords(newWords);
        excludeWords = newWords;
        processArticles();
    });

    const excludeMerchantIDsInput = document.getElementById('excludeMerchantIDsInput');
    excludeMerchantIDsInput.addEventListener('input', () => {
        const newMerchantIDs = excludeMerchantIDsInput.value.split('\n').map(id => id.trim()).filter(Boolean);
        saveExcludeMerchants(newMerchantIDs);
        excludeMerchantIDs = newMerchantIDs;
        processArticles();
    });
}

function addSettingsButton() {
    const deals = document.querySelectorAll('article.thread--deal, article.thread--voucher');

    deals.forEach(deal => {
        if (deal.hasAttribute('data-settings-added')) return;

        const footer = deal.querySelector('.threadListCard-footer, .threadCardLayout-footer');
        if (!footer) return;

        // Create settings button
        const settingsBtn = document.createElement('button');
        settingsBtn.className = 'flex--shrink-0 button button--type-text button--mode-secondary button--square';
        settingsBtn.title = 'mydealz Manager Einstellungen';
        settingsBtn.setAttribute('data-t', 'mdmSettings');
        settingsBtn.style.cssText = `
            display: inline-flex !important;
            align-items: center !important;
            justify-content: center !important;
            padding: 6px !important;
            border: none !important;
            background: transparent !important;
            cursor: pointer !important;
            margin: 0 4px !important;
            min-width: 32px !important;
            min-height: 32px !important;
            position: relative !important;
            z-index: 2 !important;
        `;

        settingsBtn.innerHTML = `
            <span class="flex--inline boxAlign-ai--all-c">
                <svg width="20" height="20" class="icon icon--gear">
                    <use xlink:href="/assets/img/ico_707ed.svg#gear"></use>
                </svg>
            </span>
        `;

        // Insert at correct position (before comments button)
        const commentsBtn = footer.querySelector('[href*="comments"]');
        if (commentsBtn) {
            commentsBtn.parentNode.insertBefore(settingsBtn, commentsBtn);
        } else {
            footer.prepend(settingsBtn);
        }

        deal.setAttribute('data-settings-added', 'true');

        settingsBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();

            if (isSettingsOpen) {
                if (dealThatOpenedSettings === deal) {
                    cleanup();
                } else {
                    // Komplett neues UI erstellen statt nur den Button zu aktualisieren
                    cleanup();
                    dealThatOpenedSettings = deal;
                    createSettingsUI(); // Dies erstellt das UI in der korrekten Reihenfolge
                }
            } else {
                dealThatOpenedSettings = deal;
                createSettingsUI();
            }
            return false;
        };
    });
}

document.addEventListener('DOMContentLoaded', () => {
    // Bestehende Funktionen
    processArticles();
    addSettingsButton();
    addMerchantPageHideButton();
    initObserver();
    injectMaxPriceFilter();

    // Neue Theme-Erkennung
    const isLight = !isDarkMode();

    // Theme Observer starten
    hideButtonThemeObserver.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ['class']
    });

    // Initial Theme auf Buttons anwenden
    document.querySelectorAll('.custom-hide-button').forEach(button => {
        if (button) {
            const bgColor = isLight ? '#f2f5f7' : '#1d1f20';
            button.style.setProperty('background', bgColor, 'important');
            button.style.setProperty('border-radius', '50%', 'important');
        }
    });
});

function addHideButtons() {
    const deals = document.querySelectorAll('article:not([data-button-added])');

    deals.forEach(deal => {
        if (deal.hasAttribute('data-button-added')) return;

        // Check for expired status
        const isExpired = deal.querySelector('.color--text-TranslucentSecondary .size--all-s')?.textContent.includes('Abgelaufen');

        // Get temperature container
        const voteTemp = deal.querySelector('.cept-vote-temp');
        if (!voteTemp) return;

        // Remove popover
        const popover = voteTemp.querySelector('.popover-origin');
        if (popover) popover.remove();

        // Find temperature span for expired deals
        const tempSpan = isExpired ? voteTemp.querySelector('span') : null;
        const targetElement = isExpired ? tempSpan : voteTemp;

        if (!targetElement) return;

        const hideButtonContainer = document.createElement('div');
        hideButtonContainer.style.cssText = `
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            display: none;
            z-index: 10001;
            pointer-events: none;
        `;

        const hideButton = document.createElement('button');
        hideButton.innerHTML = '❌';
        hideButton.className = 'vote-button overflow--visible custom-hide-button';
        hideButton.title = 'Deal verbergen';
        hideButton.style.cssText = `
            position: absolute !important;
            left: 50% !important;
            top: 50% !important;
            transform: translate(-50%, -50%) !important;
            z-index: 10002 !important;
            background: ${isDarkMode() ? '#1d1f20' : '#ffffff'} !important;
            border: 1px solid ${isDarkMode() ? 'rgb(107, 109, 109)' : 'rgba(3,12,25,0.23)'} !important;
            border-radius: 50% !important;
            cursor: pointer !important;
            padding: 4px !important;
            width: 28px !important;
            height: 28px !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            pointer-events: all !important;
            box-shadow: none !important;
            font-size: 12px !important;
        `;

        // Position relative to container
        if (!targetElement.style.position) {
            targetElement.style.position = 'relative';
        }

        if (IS_TOUCH_DEVICE) {
            let buttonVisible = false;
            const dealId = deal.getAttribute('id');

            // Add scroll handler to hide button
            const scrollHandler = () => {
                if (buttonVisible) {
                    buttonVisible = false;
                    hideButtonContainer.style.display = 'none';
                } else if (hideButtonContainer.style.display === 'block') {
                }
            };

            // Add scroll listener
            window.addEventListener('scroll', scrollHandler, { passive: true });

            targetElement.addEventListener('touchstart', (e) => {
                e.preventDefault();
                e.stopPropagation();

                if (!buttonVisible) {
                    buttonVisible = true;
                    hideButtonContainer.style.display = 'block';
                } else {
                    hiddenDeals.push(dealId);
                    saveHiddenDeals();
                    hideDeal(deal);
                    window.removeEventListener('scroll', scrollHandler);
                }
            }, true);

            targetElement.addEventListener('touchend', () => {
                if (!buttonVisible) {
                    hideButtonContainer.style.display = 'none';
                }
            }, true);
        } else {
            targetElement.addEventListener('mouseenter', () => {
                hideButtonContainer.style.display = 'block';
            }, true);

            targetElement.addEventListener('mouseleave', () => {
                hideButtonContainer.style.display = 'none';
            }, true);

            hideButton.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                const dealId = deal.getAttribute('id');
                hiddenDeals.push(dealId);
                saveHiddenDeals();
                hideDeal(deal);
                return false;
            };
        }

        hideButtonContainer.appendChild(hideButton);
        targetElement.appendChild(hideButtonContainer);
        deal.setAttribute('data-button-added', 'true');
    });
}

// Verbesserte HTML Decoder Funktion
function decodeHtml(html) {
    const txt = document.createElement('textarea');
    txt.innerHTML = html;
    return txt.value;
}

// --- 3. Backup- und Restore-Funktionen ---
function backupData() {
    try {
        // Aktuelle Daten neu laden
        const currentWords = loadExcludeWords();
        const currentMerchants = loadExcludeMerchants();

        // Backup mit aktuellen Daten erstellen
        const backup = {
            excludeWords: currentWords,
            merchantsData: currentMerchants, // Nur merchantsData speichern
            maxPrice: maxPrice,
            hideColdDeals: hideColdDeals
        };

        const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        a.href = url;
        a.download = `mydealz_backup_${timestamp}.json`;
        a.click();
        URL.revokeObjectURL(url);

    } catch (error) {
        console.error('Backup failed:', error);
        alert('Fehler beim Erstellen des Backups: ' + error.message);
    }
}

// Restore-Funktion
function restoreData(event) {
    const file = event.target.files[0];
    if (!file || file.type !== 'application/json') {
        alert('Bitte wählen Sie eine gültige JSON-Datei aus.');
        return;
    }

    const reader = new FileReader();
    reader.onload = function(e) {
        try {
            const restoredData = JSON.parse(e.target.result);

            // Validiere Backup-Daten
            if (!restoredData.excludeWords || !Array.isArray(restoredData.excludeWords)) {
                throw new Error('Ungültiges Backup-Format: Keine Wörter-Liste gefunden');
            }

            // Restore excludeWords
            GM_setValue('excludeWords', restoredData.excludeWords);
            localStorage.setItem('excludeWords', JSON.stringify(restoredData.excludeWords));
            excludeWords = restoredData.excludeWords;

            // Restore merchantsData und extrahiere IDs
            if (restoredData.merchantsData) {
                saveExcludeMerchants(restoredData.merchantsData);
                // excludeMerchantIDs werden automatisch in saveExcludeMerchants gesetzt
            }

            // Restore maxPrice
            if (typeof restoredData.maxPrice === 'number') {
                saveMaxPrice(restoredData.maxPrice);
                const maxPriceInput = document.getElementById('maxPriceFilterInput');
                if (maxPriceInput) {
                    maxPriceInput.value = restoredData.maxPrice.toLocaleString('de-DE', {
                        minimumFractionDigits: 0,
                        maximumFractionDigits: 2
                    });
                }
            }

            // Restore hideColdDeals
            if (typeof restoredData.hideColdDeals === 'boolean') {
                hideColdDeals = restoredData.hideColdDeals;
                GM_setValue('hideColdDeals', hideColdDeals);
                localStorage.setItem('hideColdDeals', hideColdDeals);
                const coldDealsToggle = document.getElementById('hideColdDealsToggle');
                if (coldDealsToggle) {
                    coldDealsToggle.checked = hideColdDeals;
                }
            }

            if (isSettingsOpen) {
                updateUITheme();
            }
            processArticles();

            console.log('=== Restore Complete ===');
            alert('Backup wurde erfolgreich wiederhergestellt.');

        } catch (error) {
            console.error('Restore failed:', error);
            alert('Fehler beim Wiederherstellen des Backups: ' + error.message);
        }
    };

    reader.readAsText(file);
}

// --- 4. Benutzeroberfläche (UI) ---
function getSubUIPosition() {
    if (IS_TOUCH_DEVICE) {
        return `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        `;
    }
    return `
        position: fixed;
        top: 50%;
        left: calc(50% + 310px);
        transform: translate(-50%, -50%);
    `;
}

function createMerchantListUI() {
    const colors = getThemeColors();
    merchantListDiv.style.cssText = `
        ${getSubUIPosition()}
        padding: 15px;
        background: ${colors.background};
        border: 1px solid ${colors.border};
        border-radius: 5px;
        z-index: 1001;
        width: 300px;
        color: ${colors.text};
    `;

    const currentMerchants = loadExcludeMerchants();

    const merchantListHTML = currentMerchants.map(merchant => `
        <div class="merchant-item" style="
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
            padding: 5px;
            background: ${colors.itemBg};
            color: ${colors.text};
            border: 1px solid ${colors.border};
            border-radius: 3px;">
            <span>${merchant.name}</span>
            <button class="delete-merchant" data-id="${merchant.id}" style="
                background: none;
                border: none;
                cursor: pointer;
                color: ${colors.text};">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `).join('');

    merchantListDiv.innerHTML = `
        <h4 style="margin-bottom: 10px;">Ausgeblendete Händler (${currentMerchants.length})</h4>
        <input type="text" id="merchantSearch" placeholder="Händler suchen..."
            style="
                width: 100%;
                padding: 5px;
                margin-bottom: 10px;
                background: ${colors.inputBg};
                border: 1px solid ${colors.border};
                color: ${colors.text};
                border-radius: 3px;">
        <div style="margin-bottom: 15px;">
            <div id="merchantList" style="margin-bottom: 10px; max-height: 200px; overflow-y: auto; padding-right: 5px;">
                ${merchantListHTML}
            </div>
            <button id="clearMerchantListButton" style="
                width: 100%;
                padding: 5px 10px;
                background: ${colors.buttonBg};
                border: 1px solid ${colors.buttonBorder};
                color: ${colors.text};
                border-radius: 3px;
                cursor: pointer;
                margin-top: 10px;">
                <i class="fas fa-trash"></i> Alle Händler entfernen
            </button>
        </div>
        <div style="text-align: right;">
            <button id="closeMerchantListButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `;

    // Add the div to the document body
    document.body.appendChild(merchantListDiv);
    setupClickOutsideHandler();

    // Add search functionality
    const searchInput = document.getElementById('merchantSearch');
    searchInput.addEventListener('input', (e) => {
        const searchTerm = e.target.value.toLowerCase();
        document.querySelectorAll('.merchant-item').forEach(item => {
            const merchantName = item.querySelector('span').textContent.toLowerCase();
            item.style.display = merchantName.includes(searchTerm) ? 'flex' : 'none';
        });
    });

    // Add clear all button handler
    document.getElementById('clearMerchantListButton').addEventListener('click', () => {
        if (confirm('Möchten Sie wirklich alle Händler aus der Liste entfernen?')) {
            saveExcludeMerchants([]);
            document.getElementById('merchantList').innerHTML = '';
            processArticles();
        }
    });

    // Update delete button handlers
    document.querySelectorAll('.delete-merchant').forEach(button => {
        button.addEventListener('click', function(e) {
            // Prevent event bubbling
            e.preventDefault();
            e.stopPropagation();

            // Get merchant ID to delete
            const deleteButton = e.target.closest('.delete-merchant');
            if (!deleteButton) return;

            const idToDelete = deleteButton.dataset.id;

            // Update merchant data
            const merchantsData = loadExcludeMerchants();
            const updatedMerchants = merchantsData.filter(m => m.id !== idToDelete);

            // Save updated data
            saveExcludeMerchants(updatedMerchants);

            // Remove from UI
            deleteButton.closest('.merchant-item').remove();

            // Refresh deals
            processArticles();
        });
    });

    // Update close button handlers in createMerchantListUI
    document.getElementById('closeMerchantListButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        closeActiveSubUI();
    });
}

function createExcludeWordsUI() {
    const colors = getThemeColors();
    wordsListDiv.style.cssText = `
        ${getSubUIPosition()}
        padding: 15px;
        background: ${colors.background};
        border: 1px solid ${colors.border};
        border-radius: 5px;
        z-index: 1001;
        width: 300px;
        color: ${colors.text};
    `;

    const currentWords = loadExcludeWords();

    const wordsListHTML = currentWords.map(word => `
        <div class="word-item" style="
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
            padding: 5px;
            background: ${colors.itemBg};
            color: ${colors.text};
            border: 1px solid ${colors.border};
            border-radius: 3px;">
            <span style="word-break: break-word;">${word}</span>
            <button class="delete-word" data-word="${word}" style="
                background: none;
                border: none;
                cursor: pointer;
                color: ${colors.text};">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `).join('');

    wordsListDiv.innerHTML = `
        <h4 style="margin-bottom: 10px;">Ausgeblendete Wörter (${currentWords.length})</h4>
        <input type="text" id="wordSearch" placeholder="Wörter suchen..."
            style="
                width: 100%;
                padding: 5px;
                margin-bottom: 10px;
                background: ${colors.inputBg};
                border: 1px solid ${colors.border};
                color: ${colors.text};
                border-radius: 3px;">
        <div style="margin-bottom: 15px;">
            <div id="wordsList" style="margin-bottom: 10px; max-height: 200px; overflow-y: auto; padding-right: 5px;">
                ${wordsListHTML}
            </div>
            <button id="clearWordsListButton" style="
                width: 100%;
                padding: 5px 10px;
                background: ${colors.buttonBg};
                border: 1px solid ${colors.buttonBorder};
                color: ${colors.text};
                border-radius: 3px;
                cursor: pointer;
                margin-top: 10px;">
                <i class="fas fa-trash"></i> Alle Wörter entfernen
            </button>
        </div>
        <div style="text-align: right;">
            <button id="closeWordsListButton" style="padding: 8px 12px; background: none; border: none; cursor: pointer;" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>
    `;

    // Add the div to the document body
    document.body.appendChild(wordsListDiv);
    setupClickOutsideHandler();

    // Add search functionality
    const searchInput = document.getElementById('wordSearch');
    searchInput.addEventListener('input', (e) => {
        const searchTerm = e.target.value.toLowerCase();
        document.querySelectorAll('.word-item').forEach(item => {
            const word = item.querySelector('span').textContent.toLowerCase();
            item.style.display = word.includes(searchTerm) ? 'flex' : 'none';
        });
    });

    // Add clear all button handler
    document.getElementById('clearWordsListButton').addEventListener('click', () => {
        if (confirm('Möchten Sie wirklich alle Wörter aus der Liste entfernen?')) {
            saveExcludeWords([]);
            document.getElementById('wordsList').innerHTML = '';
            excludeWords = [];
            processArticles();
        }
    });

    // Add delete handlers
    document.querySelectorAll('.delete-word').forEach(button => {
        button.addEventListener('click', (e) => {
            // Prevent event bubbling
            e.preventDefault();
            e.stopPropagation();

            const deleteButton = e.target.closest('.delete-word');
            if (!deleteButton) return;

            const wordToDelete = deleteButton.dataset.word;
            excludeWords = excludeWords.filter(word => word !== wordToDelete);
            saveExcludeWords(excludeWords);

            // Remove only the specific word item
            deleteButton.closest('.word-item').remove();

            // Update deals without closing UI
            processArticles();
        });
    });

    // Update close button handlers in createExcludeWordsUI
    document.getElementById('closeWordsListButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        closeActiveSubUI();
    });
}

function getWordsFromTitle(dealElement) {
    // Early return checks
    if (!dealElement) return [];
    const titleElement = dealElement.querySelector('.thread-title');
    if (!titleElement) return [];

    // 1. Basic text cleanup
    const titleText = decodeHtml(titleElement.textContent)
    .replace(/&nbsp;/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();

    // 2. Split title into potential words
    // Erweitere die Split-Pattern um das Pipe-Symbol
    const rawWords = titleText.split(/[\s,/»«\[\](){}|]/);

    // 3. Process each word
    const words = rawWords
    .map(word => {
        // Remove leading/trailing spaces and pipes
        word = word.trim().replace(/\|/g, '');

        // Handle hyphens:
        // If word starts with hyphen and has no other hyphens,
        // remove the leading hyphen (e.g. "-Tortilla" -> "Tortilla")
        if (word.startsWith('-') && !word.slice(1).includes('-')) {
            word = word.slice(1);
        }

        return word;
    })
    .filter(word =>
            word.length >= 2 && // Min length check
            !word.includes('=') &&
            !word.includes('»') &&
            !word.includes('«') &&
            !word.includes('|') && // Extra check für Pipe-Symbol
            !word.startsWith('class') &&
            !word.startsWith('title')
           );

    return [...new Set(words)]; // Entferne Duplikate
}

function createSettingsUI() {
    if (isSettingsOpen) return;
    isSettingsOpen = true;

    // Initialize containers
    initUIContainers();

    const colors = getThemeColors();

    // Get merchant info from current deal
    let merchantName = null;
    let showMerchantButton = false;

    settingsDiv.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        padding: 15px;
        background: ${colors.background};
        border: 1px solid ${colors.border};
        border-radius: 5px;
        z-index: 1000;
        width: 300px;
        max-height: 90vh;
        overflow: visible;
        color: ${colors.text};
    `;

    if (dealThatOpenedSettings) {
        const merchantLink = dealThatOpenedSettings.querySelector('a[data-t="merchantLink"]');
        if (merchantLink) {
            merchantName = merchantLink.textContent.trim();
            showMerchantButton = true;
        }
    }

    // Process articles when opening settings
    processArticles();

    const currentExcludeWords = JSON.parse(localStorage.getItem(EXCLUDE_WORDS_KEY)) || [];
    const currentExcludeMerchantIDs = JSON.parse(localStorage.getItem(EXCLUDE_MERCHANTS_KEY)) || [];

    const dealWords = dealThatOpenedSettings ? getWordsFromTitle(dealThatOpenedSettings) : [];

    // Conditional merchant button HTML - only show if merchant exists
    const merchantButtonHtml = showMerchantButton ? `
        <button id="hideMerchantButton" style="
            width: 100%;
            margin-top: 5px;
            padding: 5px 10px;
            background: ${colors.buttonBg};
            border: 1px solid ${colors.buttonBorder};
            border-radius: 3px;
            cursor: pointer;
            color: ${colors.text};
        ">
            <i class="fas fa-store-slash"></i> Alle Deals von <span style="font-weight: bold">${merchantName}</span> ausblenden
        </button>
    ` : '';

    const wordInputSection = `
        <div style="margin-bottom: 20px;">
            <div style="display: flex; align-items: center; gap: 4px;">
                <input id="newWordInput"
                    autocomplete="off"
                    ${IS_TOUCH_DEVICE ? 'readonly' : ''}
                    placeholder="Neues Wort..."
                    title="Deals mit hier eingetragenen Wörtern im Titel werden ausgeblendet."
                    style="
                        flex: 1;
                        min-width: 0; /* Verhindert Überbreite */
                        padding: 8px;
                        background: ${colors.inputBg};
                        border: 1px solid ${colors.border};
                        border-radius: 3px;
                        color: ${colors.text};
                    ">
                ${IS_TOUCH_DEVICE ? `
                    <button id="enableKeyboardButton"
                        style="
                            flex-shrink: 0;
                            width: 36px;
                            padding: 8px;
                            background: ${colors.buttonBg};
                            border: 1px solid ${colors.buttonBorder};
                            border-radius: 3px;
                            cursor: pointer;
                            color: ${colors.text};
                        ">
                        <i class="fas fa-keyboard"></i>
                    </button>
                ` : ''}
                <button id="addWordButton"
                    style="
                        flex-shrink: 0;
                        width: 36px;
                        padding: 8px;
                        background: ${colors.buttonBg};
                        border: 1px solid ${colors.buttonBorder};
                        border-radius: 3px;
                        cursor: pointer;
                        color: ${colors.text};
                    ">
                    <i class="fas fa-plus"></i>
                </button>
            </div>
        </div>`;

    settingsDiv.innerHTML = `
        <h4 style="margin-bottom: 15px; color: ${colors.text}">Einstellungen zum Ausblenden</h4>
        ${wordInputSection}
        ${merchantButtonHtml}

        <!-- List Management Section -->
        <div style="margin-top: 20px; display: flex; flex-direction: column; gap: 10px;">
            <button id="showWordsListButton" style="
                width: 100%;
                padding: 8px;
                background: ${colors.buttonBg};
                border: 1px solid ${colors.buttonBorder};
                border-radius: 3px;
                cursor: pointer;
                color: ${colors.text};">
                <i class="fas fa-list"></i> Wortfilter verwalten
            </button>
            <button id="showMerchantListButton" style="
                width: 100%;
                padding: 8px;
                background: ${colors.buttonBg};
                border: 1px solid ${colors.buttonBorder};
                border-radius: 3px;
                cursor: pointer;
                color: ${colors.text};">
                <i class="fas fa-store"></i> Händlerfilter verwalten
            </button>
        </div>

        <!-- Action Buttons -->
        <div style="margin-top: 20px; text-align: right; display: flex; justify-content: flex-end; gap: 5px;">
            <button id="createBackupButton" style="padding: 8px; background: none; border: none; cursor: pointer; color: ${colors.text};" title="Backup erstellen">
                <i class="fas fa-file-export"></i>
            </button>
            <button id="restoreBackupButton" style="padding: 8px; background: none; border: none; cursor: pointer; color: ${colors.text};" title="Wiederherstellen">
                <i class="fas fa-file-import"></i>
            </button>
            <input type="file" id="restoreFileInput" style="display: none;" />
            <button id="closeSettingsButton" style="padding: 8px; background: none; border: none; cursor: pointer; color: ${colors.text};" title="Schließen">
                <i class="fas fa-times"></i>
            </button>
        </div>`;

    // Explicitly add to DOM
    document.body.appendChild(settingsDiv);

    if (IS_TOUCH_DEVICE) {
        const input = document.getElementById('newWordInput');
        const keyboardButton = document.getElementById('enableKeyboardButton');

        if (input && keyboardButton) {
            let keyboardEnabled = false;
            let ignoreNextFocus = false;

            // Focus handler für Input
            input.addEventListener('focus', (e) => {
                if (ignoreNextFocus) {
                    ignoreNextFocus = false;
                    return;
                }

                if (!keyboardEnabled) {
                    // Verhindern dass die Tastatur erscheint wenn nicht explizit aktiviert
                    e.preventDefault();
                    input.blur();

                    // Zeige Wortvorschläge
                    if (suggestedWords.length === 0) {
                        suggestedWords = getWordsFromTitle(dealThatOpenedSettings);
                    }
                    if (suggestedWords.length > 0) {
                        updateSuggestionList();
                    }
                }
            });

            // Keyboard Button Handler
            keyboardButton.addEventListener('click', () => {
                const input = document.getElementById('newWordInput');
                if (!input) return;

                // Entferne readonly und aktiviere Tastatur
                input.removeAttribute('readonly');
                keyboardEnabled = true;

                // Verstecke Wortvorschläge
                const suggestionList = document.getElementById('wordSuggestionList');
                if (suggestionList) {
                    suggestionList.remove();
                }

                // Verhindern dass der nächste Focus die Wortvorschläge öffnet
                ignoreNextFocus = true;

                // Fokussiere Input und öffne Tastatur
                input.focus();

                // Setze einen Timer um keyboardEnabled zurückzusetzen
                setTimeout(() => {
                    keyboardEnabled = false;
                }, 100);
            });
        }
    }

    setupClickOutsideHandler();
    updateUITheme();

    const actionButtons = settingsDiv.querySelectorAll('#closeSettingsButton, #createBackupButton, #restoreBackupButton');
    actionButtons.forEach(btn => {
        btn.style.cssText = `
            padding: 8px;
            background: none;
            border: none;
            cursor: pointer;
            color: ${colors.text};
        `;
    });

    // Add word input handler
    const addWordButton = document.getElementById('addWordButton');
    if (addWordButton) {
        addWordButton.addEventListener('click', () => {
            const newWordInput = document.getElementById('newWordInput');
            const newWord = newWordInput.value.trim();

            // Lade aktuelle Wörter neu um sicherzustellen dass wir die komplette Liste haben
            excludeWords = loadExcludeWords();

            // Prüfe ob das Wort (unabhängig von Groß-/Kleinschreibung) bereits existiert
            const wordExists = excludeWords.some(word => word.toLowerCase() === newWord.toLowerCase());

            if (newWord && !wordExists) {
                excludeWords.unshift(newWord); // Füge neues Wort zur bestehenden Liste hinzu
                saveExcludeWords(excludeWords);
                newWordInput.value = '';
                processArticles();
                cleanup();

                suggestedWords = [];
                const suggestionList = document.getElementById('wordSuggestionList');
                if (suggestionList) {
                    suggestionList.remove();
                }
            } else if (wordExists) {
                // Erstelle und zeige Fehlermeldung
                const errorMsg = document.createElement('div');
                errorMsg.style.cssText = `
                    position: absolute;
                    top: 100%;
                    left: 0;
                    right: 0;
                    padding: 8px;
                    margin-top: 4px;
                    background: #ffebee;
                    color: #c62828;
                    border: 1px solid #ef9a9a;
                    border-radius: 3px;
                    font-size: 12px;
                    z-index: 1003;
                `;
                errorMsg.textContent = `"${newWord}" ist bereits in der Liste vorhanden.`;

                // Füge Fehlermeldung zum Input-Container hinzu
                const inputContainer = newWordInput.parentElement;
                inputContainer.style.position = 'relative';
                inputContainer.appendChild(errorMsg);

                // Entferne Fehlermeldung nach 3 Sekunden
                setTimeout(() => {
                    errorMsg.remove();
                }, 3000);

                // Selektiere den Text im Input für einfaches Überschreiben
                newWordInput.select();
            }
        });
    }

    // Add enter key handler for input
    document.getElementById('newWordInput').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('addWordButton').click();
        }
    });

    // Only add merchant button listener if button exists
    const hideMerchantButton = document.getElementById('hideMerchantButton');
    if (hideMerchantButton && showMerchantButton) {
        hideMerchantButton.addEventListener('click', () => {
            if (!dealThatOpenedSettings) return;

            const merchantLink = dealThatOpenedSettings.querySelector('a[href*="merchant-id="]');
            if (!merchantLink) return;

            const merchantIDMatch = merchantLink.getAttribute('href').match(/merchant-id=(\d+)/);
            if (!merchantIDMatch) return;

            const merchantID = merchantIDMatch[1];
            const merchantName = dealThatOpenedSettings.querySelector('a[data-t="merchantLink"]').textContent.trim();

            const merchantsData = loadExcludeMerchants();
            if (!merchantsData.some(m => m.id === merchantID)) {
                merchantsData.unshift({ id: merchantID, name: merchantName });
                saveExcludeMerchants(merchantsData);
                processArticles();
                cleanup(); // Close settings UI

                // Aktualisiere Listen wenn UI offen
                if (activeSubUI === 'merchant') {
                    updateActiveLists();
                }
            }
        });
    }

    // Add merchant list button listener
    document.getElementById('showMerchantListButton').addEventListener('click', () => {
        const btn = document.getElementById('showMerchantListButton');

        if (btn.hasAttribute('data-processing')) return;

        btn.setAttribute('data-processing', 'true');

        if (activeSubUI === 'merchant') {
            closeActiveSubUI();
            btn.innerHTML = '<i class="fas fa-store"></i> Händlerfilter verwalten';
            activeSubUI = null;
        } else {
            closeActiveSubUI();
            createMerchantListUI();
            activeSubUI = 'merchant';
            btn.innerHTML = '<i class="fas fa-times"></i> Händlerfilter ausblenden';
        }

        btn.removeAttribute('data-processing');
    });

    // Add words list button listener
    document.getElementById('showWordsListButton').addEventListener('click', () => {
        const btn = document.getElementById('showWordsListButton');

        if (activeSubUI === 'words') {
            closeActiveSubUI();
            btn.innerHTML = '<i class="fas fa-list"></i> Wortfilter verwalten';
            activeSubUI = null;
        } else {
            closeActiveSubUI();
            createExcludeWordsUI();
            activeSubUI = 'words';
            btn.innerHTML = '<i class="fas fa-times"></i> Wortfilter ausblenden';
        }
    });

    // Always ensure close button works
    document.getElementById('closeSettingsButton').addEventListener('click', (e) => {
        e.stopPropagation(); // Prevent event bubbling
        cleanup();
    });

    // Backup/Restore Event Listeners
    document.getElementById('createBackupButton').addEventListener('click', backupData);

    document.getElementById('restoreBackupButton').addEventListener('click', () => {
        document.getElementById('restoreFileInput').click();
    });

    document.getElementById('restoreFileInput').addEventListener('change', restoreData);

    // Add event listeners only if newWordInput exists
    const newWordInput = document.getElementById('newWordInput');
    if (newWordInput) {
        // Unified focus handler
        newWordInput.addEventListener('focus', () => {
            // Get fresh words from current deal if none exist
            if (suggestedWords.length === 0) {
                suggestedWords = getWordsFromTitle(dealThatOpenedSettings);
            }

            // Always show suggestion list if words exist
            if (suggestedWords.length > 0) {
                updateSuggestionList();
            }
        }, { once: false }); // Allow multiple focus events
    }

    // Click Outside Handler anpassen
    createSuggestionClickHandler();

    // Cleanup bei UI-Schließung
    document.getElementById('closeSettingsButton').addEventListener('click', () => {
        document.removeEventListener('click', suggestionClickHandler);
    });

    // Add cleanup to window unload
    window.addEventListener('unload', cleanup);

    const maxPriceInput = document.getElementById('maxPriceFilterInput'); // Note the correct ID
    if (maxPriceInput) {
        maxPriceInput.addEventListener('change', (e) => {
            const price = parseFloat(e.target.value);
            if (!isNaN(price) && price >= 0) {
                saveMaxPrice(price);
                processArticles();
            }
        });
    }

    // Get initial word suggestions
    suggestedWords = dealThatOpenedSettings ? getWordsFromTitle(dealThatOpenedSettings) : [];
}

function updateSuggestionList() {
    // Save scroll position if list exists
    const oldList = document.getElementById('wordSuggestionList');
    const scrollPosition = oldList?.scrollTop || 0;

    // Remove old list if exists
    if (oldList) oldList.remove();

    // Filter and check for words
    suggestedWords = suggestedWords.filter(word => !excludeWords.includes(word));
    if (!suggestedWords.length) return;

    const inputField = document.getElementById('newWordInput');
    const inputRect = inputField.getBoundingClientRect();
    const colors = getThemeColors();

    // Create suggestion list with fixed positioning
    const wordSuggestionList = document.createElement('div');
    wordSuggestionList.id = 'wordSuggestionList';
    wordSuggestionList.style.cssText = `
        position: fixed;
        top: ${inputRect.bottom}px;
        left: ${inputRect.left}px;
        width: ${inputRect.width}px;
        max-height: 200px;
        overflow-y: auto;
        background: ${colors.background};
        border: 1px solid ${colors.border};
        color: ${colors.text};
        border-radius: 3px;
        z-index: 1002;
        box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        display: block;
    `;

    // Add scroll event listener to update position
    const updatePosition = () => {
        const newRect = inputField.getBoundingClientRect();
        wordSuggestionList.style.top = `${newRect.bottom}px`;
        wordSuggestionList.style.left = `${newRect.left}px`;
    };

    window.addEventListener('scroll', updatePosition, true);
    window.addEventListener('resize', updatePosition);

    // Rest of the function stays the same
    wordSuggestionList.innerHTML = suggestedWords
        .map(word => `
            <div class="word-suggestion-item" style="padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; transition: background-color 0.2s;">
                ${word}
            </div>
        `).join('');

    document.body.appendChild(wordSuggestionList);
    wordSuggestionList.scrollTop = scrollPosition;

    // Add event listeners for items
    wordSuggestionList.querySelectorAll('.word-suggestion-item').forEach(item => {
        item.addEventListener('mouseenter', () => {
            item.style.backgroundColor = colors.itemBg;
        });
        item.addEventListener('mouseleave', () => {
            item.style.backgroundColor = colors.background;
        });
        item.addEventListener('click', handleWordSelection);
    });

    // Clean up event listeners when list is removed
    const cleanupListeners = () => {
        window.removeEventListener('scroll', updatePosition, true);
        window.removeEventListener('resize', updatePosition);
    };

    // Add to existing cleanup function
    const oldCleanup = cleanup;
    cleanup = () => {
        cleanupListeners();
        oldCleanup();
    };
}

function initObserver() {
    // Disconnect existing observer if it exists
    if (observer) {
        observer.disconnect();
    }

    // Reinitialize the observer
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Process any existing articles
    processArticles();
    addSettingsButton();
    addHideButtons();
}

// Update addMerchantToList function
function addMerchantToList(merchant, merchantList) {
    const div = document.createElement('div');
    div.className = 'merchant-item';
    div.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;';
    div.innerHTML = `
        <span style="font-weight: bold;">${merchant.name}</span>
        <button class="delete-merchant" data-id="${merchant.id}" style="background: none; border: none; cursor: pointer; color: #666;">
            <i class="fas fa-times"></i>
        </button>
    `;

    // Insert at beginning of list
    merchantList.insertBefore(div, merchantList.firstChild);
}

// Update word list UI - add new item at top
function addWordToList(word, wordsList) {
    const div = document.createElement('div');
    div.className = 'word-item';
    div.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;';
    div.innerHTML = `
        <span style="word-break: break-word;">${word}</span>
        <button class="delete-word" data-word="${word}" style="background: none; border: none; cursor: pointer; color: #666;">
            <i class="fas fa-times"></i>
        </button>
    `;

    // Insert at beginning of list
    wordsList.insertBefore(div, wordsList.firstChild);
}

function setupClickOutsideHandler() {
    if (uiClickOutsideHandler) {
        document.removeEventListener('click', uiClickOutsideHandler);
    }

    uiClickOutsideHandler = (e) => {

        // Early exit for clicks on UI controls
        if (e.target.closest('.settings-button') ||
            e.target.closest('#showMerchantListButton') ||
            e.target.closest('#showWordsListButton')) {
            return;
        }

        // Get current UI states
        const settingsOpen = settingsDiv?.parentNode;
        const merchantsOpen = merchantListDiv?.parentNode;
        const wordsOpen = wordsListDiv?.parentNode;

        // Check if click was outside all UIs
        const clickedOutside = (!settingsOpen || !settingsDiv.contains(e.target)) &&
              (!merchantsOpen || !merchantListDiv.contains(e.target)) &&
              (!wordsOpen || !wordsListDiv.contains(e.target));

        if (clickedOutside) {
            cleanup();

            // Explicit cleanup of UI elements
            if (settingsDiv?.parentNode) settingsDiv.remove();
            if (merchantListDiv?.parentNode) merchantListDiv.remove();
            if (wordsListDiv?.parentNode) wordsListDiv.remove();

            // Reset states
            isSettingsOpen = false;
            activeSubUI = null;

            // Remove handler
            document.removeEventListener('click', uiClickOutsideHandler);
            uiClickOutsideHandler = null;
        }
    };

    // Add with delay to prevent immediate trigger
    setTimeout(() => {
        document.addEventListener('click', uiClickOutsideHandler);
    }, 100);
}

// Add helper function to close sub-UIs
function closeActiveSubUI() {
    if (activeSubUI === 'merchant') {
        merchantListDiv?.remove();
        const btn = document.getElementById('showMerchantListButton');
        if (btn) {
            btn.innerHTML = '<i class="fas fa-store"></i> Händlerfilter verwalten';
            btn.removeAttribute('data-processing');
        }
    } else if (activeSubUI === 'words') {
        wordsListDiv?.remove();
        const btn = document.getElementById('showWordsListButton');
        if (btn) {
            btn.innerHTML = '<i class="fas fa-list"></i> Wortfilter verwalten';
        }
    }

    activeSubUI = null;
}

// Add new function to handle merchant pages
function addMerchantPageHideButton() {
    // Check if we're on a merchant page
    const urlParams = new URLSearchParams(window.location.search);
    const merchantId = urlParams.get('merchant-id');
    const merchantBanner = document.querySelector(MERCHANT_PAGE_SELECTOR);
    const merchantName = document.querySelector('.merchant-banner__title')?.textContent.trim();

    if (!merchantId || !merchantBanner || !merchantName) return;

    // Create hide button container
    const hideButtonContainer = document.createElement('div');
    hideButtonContainer.style.cssText = `
        display: inline-flex;
        align-items: center;
        margin-left: 10px;
    `;

    // Create hide button
    const hideButton = document.createElement('button');
    hideButton.innerHTML = '<i class="fas fa-store-slash"></i>';
    hideButton.title = `Alle Deals von ${merchantName} ausblenden`;
    hideButton.style.cssText = `
        padding: 8px;
        background: #f0f0f0;
        border: 1px solid #ccc;
        border-radius: 3px;
        cursor: pointer;
    `;

    // Add click handler
    hideButton.addEventListener('click', () => {
        const merchantsData = loadExcludeMerchants();

        // Check if ID already exists
        if (!merchantsData.some(m => m.id === merchantId)) {
            // Add new merchant at start of array
            merchantsData.unshift({ id: merchantId, name: merchantName });
            saveExcludeMerchants(merchantsData);
            processArticles();
        }
    });

    // Add button to page
    hideButtonContainer.appendChild(hideButton);
    merchantBanner.appendChild(hideButtonContainer);
}

// Funktion zum Aktualisieren der aktiven Listen
function updateActiveLists() {
    const colors = getThemeColors();

    if (activeSubUI === 'merchant' && merchantListDiv) {
        const merchantList = document.getElementById('merchantList');
        if (merchantList) {
            const currentMerchants = loadExcludeMerchants();
            merchantList.innerHTML = currentMerchants.map(merchant => `
                <div class="merchant-item" style="
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 5px;
                    padding: 5px;
                    background: ${colors.itemBg};
                    color: ${colors.text};
                    border: 1px solid ${colors.border};
                    border-radius: 3px;">
                    <div style="display: flex; flex-direction: column;">
                        <span>${merchant.name}</span>
                        <span style="color: ${colors.text}; opacity: 0.7; font-size: 0.8em;">ID: ${merchant.id}</span>
                    </div>
                    <button class="delete-merchant" data-id="${merchant.id}" style="
                        background: none;
                        border: none;
                        cursor: pointer;
                        color: ${colors.text};">
                        <i class="fas fa-times"></i>
                    </button>
                </div>
            `).join('');

            // Event Listener neu hinzufügen
            document.querySelectorAll('.delete-merchant').forEach(button => {
                button.addEventListener('click', handleMerchantDelete);
            });
        }
    } else if (activeSubUI === 'words' && wordsListDiv) {
        const wordsList = document.getElementById('wordsList');
        if (wordsList) {
            const currentWords = loadExcludeWords();
            wordsList.innerHTML = currentWords.map(word => `
                <div class="word-item" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; padding: 5px; background: #f0f0f0; border-radius: 3px;">
                    <span style="word-break: break-word;">${word}</span>
                    <button class="delete-word" data-word="${word}" style="background: none; border: none; cursor: pointer; color: #666;">
                        <i class="fas fa-times"></i>
                    </button>
                </div>
            `).join('');

            // Event Listener neu hinzufügen
            document.querySelectorAll('.delete-word').forEach(button => {
                button.addEventListener('click', handleWordDelete);
            });
        }
    }
}

// Handler Funktionen definieren
function handleMerchantDelete(e) {
    e.preventDefault();
    e.stopPropagation();

    const deleteButton = e.target.closest('.delete-merchant');
    if (!deleteButton) return;

    const idToDelete = deleteButton.dataset.id;
    const merchantsData = loadExcludeMerchants();
    const updatedMerchants = merchantsData.filter(m => m.id !== idToDelete);

    saveExcludeMerchants(updatedMerchants);
    deleteButton.closest('.merchant-item').remove();
    processArticles();
}

function handleWordDelete(e) {
    e.preventDefault();
    e.stopPropagation();

    const deleteButton = e.target.closest('.delete-word');
    if (!deleteButton) return;

    const wordToDelete = deleteButton.dataset.word;
    excludeWords = excludeWords.filter(word => word !== wordToDelete);
    saveExcludeWords(excludeWords);
    deleteButton.closest('.word-item').remove();
    processArticles();
}

// Add new save function
function saveMaxPrice(price) {
    GM_setValue('maxPrice', price.toString());
    localStorage.setItem('maxPrice', price.toString());
    maxPrice = price;
}

// Add after other utility functions
function handleWordSelection(e) {
    e.preventDefault();
    e.stopPropagation();

    const wordSuggestionList = document.getElementById('wordSuggestionList');
    const scrollPosition = wordSuggestionList.scrollTop; // Save scroll position

    const word = e.target.textContent.trim();
    const newWordInput = document.getElementById('newWordInput');
    const currentValue = newWordInput.value.trim();

    newWordInput.value = currentValue ? `${currentValue} ${word}` : word;
    suggestedWords = suggestedWords.filter(w => w !== word);

    updateSuggestionList();
    newWordInput.focus();

    // Restore scroll position after list update
    const updatedList = document.getElementById('wordSuggestionList');
    if (updatedList) {
        updatedList.scrollTop = scrollPosition;
    }
}

// Add after other global functions
function cleanup() {
    // Remove settings UI
    if (settingsDiv?.parentNode) {
        settingsDiv.remove();
        isSettingsOpen = false;
    }

    // Add word suggestion list cleanup
    const suggestionList = document.getElementById('wordSuggestionList');
    if (suggestionList) {
        suggestionList.remove();
    }

    // Close merchant & words lists
    if (merchantListDiv?.parentNode) merchantListDiv.remove();
    if (wordsListDiv?.parentNode) wordsListDiv.remove();

    // Reset UI states
    if (activeSubUI === 'merchant' || activeSubUI === 'words') {
        const btn = document.getElementById(`show${activeSubUI === 'merchant' ? 'Merchant' : 'Words'}ListButton`);
        if (btn) {
            btn.innerHTML = activeSubUI === 'merchant' ?
                '<i class="fas fa-store"></i> Händlerfilter verwalten' :
            '<i class="fas fa-list"></i> Wortfilter verwalten';
            btn.removeAttribute('data-processing');
        }
    }
    activeSubUI = null;

    // Clean up handlers
    document.removeEventListener('click', suggestionClickHandler);
    document.removeEventListener('click', uiClickOutsideHandler);
    window.removeEventListener('unload', cleanup);
    uiClickOutsideHandler = null;

    // Reset suggestion state
    suggestedWords = [];

    // Don't disconnect the main observer
    // Instead, reinitialize it to ensure it's working
    initObserver();
}

// 4. Complete UI state reset
function resetUIState() {
    isSettingsOpen = false;
    activeSubUI = null;
    dealThatOpenedSettings = null;
    suggestedWords = [];
    initialSuggestionListCreated = false;

    settingsDiv?.remove();
    merchantListDiv?.remove();
    wordsListDiv?.remove();
}

// Add after other global functions
function createSuggestionClickHandler() {
    // Remove old handler if exists
    if (suggestionClickHandler) {
        document.removeEventListener('click', suggestionClickHandler);
    }

    suggestionClickHandler = (e) => {
        const list = document.getElementById('wordSuggestionList');
        const input = document.getElementById('newWordInput');

        if (!list?.contains(e.target) && !input?.contains(e.target)) {
            list?.remove();
        }
    };

    document.addEventListener('click', suggestionClickHandler);
    return suggestionClickHandler;
}

// Add function to inject max price filter
function injectMaxPriceFilter() {
    const filterForm = document.querySelector('.subNavMenu-list form:first-of-type ul');
    if (!filterForm) return;

    // Get theme colors for logging
    const isDark = isDarkMode();
    const colors = getThemeColors();

    // Create filter items with optimized HTML
    const filterItems = document.createElement('div');
    filterItems.innerHTML = `
        <!-- Cold Deals Toggle -->
        <li class="flex boxAlign-jc--all-sb boxAlign-ai--all-c space--h-3 space--v-3 subNavMenu-item--separator">
            <span class="subNavMenu-text mute--text space--r-2 overflow--wrap-off">Kalte Deals ausblenden</span>
            <label class="checkbox checkbox--brand checkbox--mode-special">
                <input
                    class="input checkbox-input"
                    type="checkbox"
                    id="hideColdDealsToggle"
                    ${hideColdDeals ? 'checked' : ''}
                >
                <span class="tGrid-cell tGrid-cell--shrink">
                    <span class="checkbox-box flex--inline boxAlign-jc--all-c boxAlign-ai--all-c">
                        <svg width="18px" height="14px" class="icon icon--tick checkbox-tick">
                            <use xlink:href="/assets/img/ico_707ed.svg#tick"></use>
                        </svg>
                    </span>
                </span>
            </label>
        </li>

        <!-- Price Filter -->
        <li class="flex boxAlign-jc--all-sb boxAlign-ai--all-c space--h-3 space--v-3">
            <span class="subNavMenu-text mute--text space--r-2 overflow--wrap-off">
                Maximalpreis filtern
            </span>
            <input
                type="text"
                id="maxPriceFilterInput"
                value="${maxPrice.toLocaleString('de-DE', {
                    minimumFractionDigits: 0,
                    maximumFractionDigits: 2
                })}"
                placeholder="€"
                style="
                    width: 80px;
                    padding: 4px 8px;
                    border: 1px solid var(--border-color, ${isDarkMode() ? '#6b6d6d' : '#c5c7ca'});
                    border-radius: 4px;
                    margin-left: auto;
                    text-align: right;
                    background: ${colors.inputBg} !important;
                    color: ${colors.text} !important;
                    font-size: 14px;
                    line-height: 1.5;
                    transition: border-color .15s ease-in-out;
                    -moz-appearance: textfield;
                    &::-webkit-outer-spin-button,
                    &::-webkit-inner-spin-button {
                        -webkit-appearance: none;
                        margin: 0;
                    }
                "
            >
        </li>`;

    filterForm.appendChild(filterItems);

    // After input creation, get the element and check computed styles
    const maxPriceInput = document.getElementById('maxPriceFilterInput');
    if (maxPriceInput) {
        const computedStyle = window.getComputedStyle(maxPriceInput);
    }

    // Optimized input handling
    const priceInput = document.getElementById('maxPriceFilterInput');
    if (priceInput) {
        // Speichere den initialen Wert für Vergleiche
        const initialValue = maxPrice.toLocaleString('de-DE', {
            minimumFractionDigits: 0,
            maximumFractionDigits: 2
        });

        const formatPrice = (value) => {
            // Remove all non-numeric characters except comma and dot
            let cleaned = value.replace(/[^\d.,]/g, '');

            // Handle thousands separators and decimal
            const parts = cleaned.split(',');
            if (parts.length > 2) {
                cleaned = parts.slice(0, -1).join('') + ',' + parts.slice(-1);
            }

            // Format with thousand separators
            if (parts.length === 2) {
                const intPart = parts[0].replace(/\./g, '');
                const formatted = Number(intPart).toLocaleString('de-DE') + ',' + parts[1];
                return formatted;
            } else {
                const intPart = cleaned.replace(/\./g, '');
                return Number(intPart).toLocaleString('de-DE');
            }
        };

        // Event-Handler für Input-Formatierung
        priceInput.addEventListener('input', (e) => {
            e.stopPropagation();
            const formattedValue = formatPrice(e.target.value);
            e.target.value = formattedValue;
        }, { passive: true });

        // Event-Handler für Blur (wenn Input den Fokus verliert)
        priceInput.addEventListener('blur', (e) => {
            const value = e.target.value;
            const numStr = value.replace(/\./g, '').replace(',', '.');
            const numericValue = parseFloat(numStr);

            if (!isNaN(numericValue) && numericValue >= 0 && value !== initialValue) {
                saveMaxPrice(numericValue);
                location.reload();
            } else {
                // Wenn ungültig, setze auf den ursprünglichen Wert zurück
                e.target.value = initialValue;
            }
        });
    }

    // Cold deals toggle
    const coldDealsToggle = document.getElementById('hideColdDealsToggle');
    if (coldDealsToggle) {
        coldDealsToggle.addEventListener('change', (e) => {
            e.stopPropagation();
            hideColdDeals = e.target.checked;
            GM_setValue('hideColdDeals', hideColdDeals);
            localStorage.setItem(HIDE_COLD_DEALS_KEY, hideColdDeals);
            processArticles();
        });
    }
}

// Add observer for filter UI changes
const filterObserver = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        if (mutation.addedNodes.length) {
            const filterMenu = document.querySelector('.subNavMenu-list');
            if (filterMenu && !document.getElementById('maxPriceFilterInput')) {
                injectMaxPriceFilter();
            }
        }
    });
});

// Start observing filter area
filterObserver.observe(document.body, {
    childList: true,
    subtree: true
});