NHentai Search Enhancer

A single script that includes fuzzy search, library management (import/export), bulk edit, local storage, and a proper minimize fix for NHentai searches.

// ==UserScript==
// @name         NHentai Search Enhancer
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  A single script that includes fuzzy search, library management (import/export), bulk edit, local storage, and a proper minimize fix for NHentai searches.
// @author       FunkyJustin
// @match        https://nhentai.net/*
// @exclude      https://nhentai.net/g/*/*/
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    //----------------------------
    // 1) Local Storage + Defaults
    //----------------------------
    const STORAGE_KEY = 'nhentai_search_enhancer_state';
    const defaultState = {
        library: [
            'doujinshi', 'mature', 'romance', 'yaoi', 'action', 'comedy',
            'schoolgirl', 'tentacles', 'yuri', 'bondage', 'big breasts',
            'glasses', 'netorare', 'vanilla', 'monster girl'
        ],
        included: [],
        excluded: [],
        language: 'english'
    };

    function loadState() {
        try {
            const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
            if (!saved) return structuredClone(defaultState);
            // Merge saved with defaults in case new fields appear
            return {
                library: Array.isArray(saved.library) ? saved.library : [...defaultState.library],
                included: Array.isArray(saved.included) ? saved.included : [],
                excluded: Array.isArray(saved.excluded) ? saved.excluded : [],
                language: saved.language || 'english'
            };
        } catch(e) {
            return structuredClone(defaultState);
        }
    }

    function saveState() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(appState));
    }

    let appState = loadState();

    //----------------------------
    // 2) Styling Helpers
    //----------------------------
    function styleButton(btn) {
        btn.style.border = '1px solid #555';
        btn.style.borderRadius = '3px';
        btn.style.backgroundColor = '#555';
        btn.style.color = '#ddd';
        btn.style.padding = '3px 6px';
        btn.style.cursor = 'pointer';
        btn.style.transition = 'background-color 0.2s, color 0.2s';

        btn.addEventListener('mouseenter', () => {
            btn.style.backgroundColor = '#666';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.backgroundColor = '#555';
        });
        btn.addEventListener('mousedown', () => {
            btn.style.backgroundColor = '#777';
        });
        btn.addEventListener('mouseup', () => {
            btn.style.backgroundColor = '#666';
        });
    }

    // Basic fuzzy matching: returns true if all chars in query appear in tag in order
    function fuzzyMatch(query, tag) {
        let q = query.toLowerCase();
        let t = tag.toLowerCase();
        let i = 0, j = 0;
        while (i < q.length && j < t.length) {
            if (q[i] === t[j]) {
                i++; j++;
            } else {
                j++;
            }
        }
        return i === q.length;
    }

    //----------------------------
    // 3) Main Container & Header
    //----------------------------
    const container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.top = '10px';
    container.style.right = '10px';
    container.style.width = '320px';
    container.style.minWidth = '250px';
    container.style.minHeight = '200px';
    container.style.backgroundColor = '#2c2c2c';
    container.style.padding = '0';
    container.style.border = '1px solid #555';
    container.style.borderRadius = '5px';
    container.style.zIndex = '10000';
    container.style.fontSize = '14px';
    container.style.color = '#ddd';
    container.style.boxShadow = '0 2px 6px rgba(0,0,0,0.5)';
    container.style.userSelect = 'none';
    container.style.overflow = 'hidden';

    const header = document.createElement('div');
    header.style.backgroundColor = '#3a3a3a';
    header.style.padding = '8px';
    header.style.cursor = 'move';
    header.style.display = 'flex';
    header.style.justifyContent = 'space-between';
    header.style.alignItems = 'center';
    header.style.borderBottom = '1px solid #555';

    const title = document.createElement('span');
    title.textContent = 'NHentai Search Enhancer';
    title.style.fontWeight = 'bold';
    header.appendChild(title);

    // Minimize/Maximize button
    const toggleBtn = document.createElement('button');
    toggleBtn.textContent = '–';
    toggleBtn.style.cursor = 'pointer';
    toggleBtn.style.border = 'none';
    toggleBtn.style.background = 'transparent';
    toggleBtn.style.fontSize = '16px';
    toggleBtn.style.lineHeight = '16px';
    toggleBtn.style.padding = '0 5px';
    toggleBtn.style.color = '#ddd';
    header.appendChild(toggleBtn);

    container.appendChild(header);
    document.body.appendChild(container);

    // Draggable logic
    let isDragging = false;
    let offsetX = 0, offsetY = 0;
    header.addEventListener('mousedown', function(e) {
        if (e.target !== toggleBtn) {
            isDragging = true;
            offsetX = e.clientX - container.getBoundingClientRect().left;
            offsetY = e.clientY - container.getBoundingClientRect().top;
            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', stopDrag);
        }
    });

    function drag(e) {
        if (!isDragging) return;
        container.style.left = (e.clientX - offsetX) + 'px';
        container.style.top = (e.clientY - offsetY) + 'px';
        container.style.right = 'auto'; // remove "right"
    }

    function stopDrag() {
        isDragging = false;
        document.removeEventListener('mousemove', drag);
        document.removeEventListener('mouseup', stopDrag);
    }

    //----------------------------
    // 4) Content + Resizer
    //----------------------------
    const content = document.createElement('div');
    content.style.padding = '10px';
    content.style.position = 'relative'; // for auto-suggest alignment
    container.appendChild(content);

    // Resizer
    const resizer = document.createElement('div');
    resizer.style.width = '10px';
    resizer.style.height = '10px';
    resizer.style.background = 'transparent';
    resizer.style.position = 'absolute';
    resizer.style.right = '0';
    resizer.style.bottom = '0';
    resizer.style.cursor = 'se-resize';
    container.appendChild(resizer);

    let isResizing = false;
    resizer.addEventListener('mousedown', function(e) {
        isResizing = true;
        e.stopPropagation();
        document.addEventListener('mousemove', resize);
        document.addEventListener('mouseup', stopResize);
    });

    function resize(e) {
        if (!isResizing) return;
        const newWidth = e.clientX - container.getBoundingClientRect().left;
        const newHeight = e.clientY - container.getBoundingClientRect().top;
        if (newWidth > 250) container.style.width = newWidth + 'px';
        if (newHeight > 150) container.style.height = newHeight + 'px';
    }

    function stopResize() {
        isResizing = false;
        document.removeEventListener('mousemove', resize);
        document.removeEventListener('mouseup', stopResize);
    }

    //----------------------------
    // 5) Proper Minimize Fix
    //----------------------------
    let isMinimized = false;
    const originalMinHeight = container.style.minHeight || '200px';

    toggleBtn.addEventListener('click', function() {
        isMinimized = !isMinimized;
        if (isMinimized) {
            // Hide content & resizer
            content.style.display = 'none';
            resizer.style.display = 'none';
            container.style.minHeight = '0';
            container.style.height = header.offsetHeight + 'px';
            toggleBtn.textContent = '+';
        } else {
            // Show content & resizer
            content.style.display = 'block';
            resizer.style.display = 'block';
            container.style.minHeight = originalMinHeight;
            container.style.height = '';
            toggleBtn.textContent = '–';
        }
    });

    //---------------------------------------------------
    // 6) createAutoSuggestField (Include/Exclude Input)
    //---------------------------------------------------
    function createAutoSuggestField(labelText, placeholderText, onEnterTag) {
        const wrapper = document.createElement('div');
        wrapper.style.marginBottom = '10px';
        wrapper.style.position = 'relative';

        const label = document.createElement('label');
        label.textContent = labelText;
        label.style.display = 'block';
        label.style.marginBottom = '5px';
        wrapper.appendChild(label);

        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = placeholderText;
        input.style.width = '100%';
        input.style.padding = '3px';
        input.style.border = '1px solid #555';
        input.style.borderRadius = '3px';
        input.style.backgroundColor = '#444';
        input.style.color = '#ddd';
        wrapper.appendChild(input);

        const suggestBox = document.createElement('div');
        suggestBox.style.position = 'absolute';
        suggestBox.style.top = '100%';
        suggestBox.style.left = '0';
        suggestBox.style.width = '100%';
        suggestBox.style.backgroundColor = '#444';
        suggestBox.style.border = '1px solid #555';
        suggestBox.style.borderTop = 'none';
        suggestBox.style.display = 'none';
        suggestBox.style.maxHeight = '100px';
        suggestBox.style.overflowY = 'auto';
        suggestBox.style.zIndex = '9999';
        wrapper.appendChild(suggestBox);

        // Hide if click outside
        document.addEventListener('click', (e) => {
            if (!wrapper.contains(e.target)) {
                suggestBox.style.display = 'none';
            }
        });

        // Press Enter
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                const val = input.value.trim();
                if (val) {
                    onEnterTag(val);
                }
                suggestBox.style.display = 'none';
                input.value = '';
            }
        });

        // On input, fuzzy search
        input.addEventListener('input', function() {
            const query = input.value.trim().toLowerCase();
            if (!query) {
                suggestBox.innerHTML = '';
                suggestBox.style.display = 'none';
                return;
            }
            const filtered = appState.library.filter(t => fuzzyMatch(query, t));
            if (filtered.length === 0) {
                suggestBox.innerHTML = '';
                suggestBox.style.display = 'none';
                return;
            }
            suggestBox.innerHTML = '';
            filtered.forEach(tag => {
                const item = document.createElement('div');
                item.textContent = tag;
                item.style.padding = '5px';
                item.style.borderBottom = '1px solid #555';
                item.style.cursor = 'pointer';
                item.addEventListener('mouseover', () => {
                    item.style.backgroundColor = '#555';
                });
                item.addEventListener('mouseout', () => {
                    item.style.backgroundColor = '#444';
                });
                item.addEventListener('mousedown', () => {
                    onEnterTag(tag);
                    suggestBox.style.display = 'none';
                    input.value = '';
                });
                suggestBox.appendChild(item);
            });
            suggestBox.style.display = 'block';
        });

        return { wrapper, input };
    }

    //----------------------------
    // 7) Include & Exclude Fields
    //----------------------------
    const includeField = createAutoSuggestField(
        'Include Tag:',
        'Type to include tag...',
        (tag) => {
            appState.included.push(tag);
            saveState();
            updatePreview();
        }
    );
    content.appendChild(includeField.wrapper);

    const excludeField = createAutoSuggestField(
        'Exclude Tag:',
        'Type to exclude tag...',
        (tag) => {
            appState.excluded.push(tag);
            saveState();
            updatePreview();
        }
    );
    content.appendChild(excludeField.wrapper);

    //----------------------------
    // 8) Language Dropdown
    //----------------------------
    const languageWrapper = document.createElement('div');
    languageWrapper.style.marginBottom = '10px';

    const languageLabel = document.createElement('label');
    languageLabel.textContent = 'Language:';
    languageLabel.style.display = 'block';
    languageLabel.style.marginBottom = '5px';
    languageWrapper.appendChild(languageLabel);

    const languageSelect = document.createElement('select');
    languageSelect.style.width = '100%';
    languageSelect.style.padding = '3px';
    languageSelect.style.border = '1px solid #555';
    languageSelect.style.borderRadius = '3px';
    languageSelect.style.backgroundColor = '#444';
    languageSelect.style.color = '#ddd';

    ['none','english','japanese','chinese'].forEach(lang => {
        const opt = document.createElement('option');
        opt.value = lang;
        opt.textContent = lang;
        languageSelect.appendChild(opt);
    });
    languageSelect.value = appState.language || 'english';

    languageSelect.addEventListener('change', () => {
        appState.language = languageSelect.value;
        saveState();
        updatePreview();
    });

    languageWrapper.appendChild(languageSelect);
    content.appendChild(languageWrapper);

    //----------------------------
    // 9) Add Tag to Library
    //----------------------------
    const librarySection = document.createElement('div');
    librarySection.style.marginBottom = '10px';

    const addTagLabel = document.createElement('label');
    addTagLabel.textContent = 'Add Tag to Library:';
    addTagLabel.style.display = 'block';
    addTagLabel.style.marginBottom = '5px';
    librarySection.appendChild(addTagLabel);

    const addTagRow = document.createElement('div');
    addTagRow.style.display = 'flex';
    addTagRow.style.gap = '5px';

    const addTagInput = document.createElement('input');
    addTagInput.type = 'text';
    addTagInput.placeholder = 'Enter new tag...';
    addTagInput.style.flex = '1';
    addTagInput.style.padding = '3px';
    addTagInput.style.border = '1px solid #555';
    addTagInput.style.borderRadius = '3px';
    addTagInput.style.backgroundColor = '#444';
    addTagInput.style.color = '#ddd';

    const addTagButton = document.createElement('button');
    addTagButton.textContent = 'Add';
    styleButton(addTagButton);

    // Press Enter to add new tag
    addTagInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            addTagButton.click();
        }
    });

    addTagButton.addEventListener('click', () => {
        const newTag = addTagInput.value.trim();
        if (newTag && !appState.library.includes(newTag)) {
            appState.library.push(newTag);
            saveState();
        }
        addTagInput.value = '';
    });

    addTagRow.appendChild(addTagInput);
    addTagRow.appendChild(addTagButton);
    librarySection.appendChild(addTagRow);

    const btnSpacing = document.createElement('div');
    btnSpacing.style.height = '10px';
    librarySection.appendChild(btnSpacing);

    //----------------------------
    // 10) Manage Library & Bulk Edit
    //----------------------------
    const manageLibraryBtn = document.createElement('button');
    manageLibraryBtn.textContent = 'Manage Library';
    styleButton(manageLibraryBtn);

    const bulkEditBtn = document.createElement('button');
    bulkEditBtn.textContent = 'Bulk Edit';
    styleButton(bulkEditBtn);

    const libBtnRow = document.createElement('div');
    libBtnRow.style.display = 'flex';
    libBtnRow.style.gap = '5px';
    libBtnRow.appendChild(manageLibraryBtn);
    libBtnRow.appendChild(bulkEditBtn);

    librarySection.appendChild(libBtnRow);
    content.appendChild(librarySection);

    //-------------------------------------------------
    // 11) Manage Library Modal (Import/Export/Reset)
    //-------------------------------------------------
    const libraryModal = document.createElement('div');
    libraryModal.style.position = 'fixed';
    libraryModal.style.top = '0';
    libraryModal.style.left = '0';
    libraryModal.style.width = '100%';
    libraryModal.style.height = '100%';
    libraryModal.style.backgroundColor = 'rgba(0,0,0,0.5)';
    libraryModal.style.display = 'none';
    libraryModal.style.zIndex = '99999';

    const modalContent = document.createElement('div');
    modalContent.style.position = 'absolute';
    modalContent.style.top = '50%';
    modalContent.style.left = '50%';
    modalContent.style.transform = 'translate(-50%, -50%)';
    modalContent.style.backgroundColor = '#2c2c2c';
    modalContent.style.padding = '10px';
    modalContent.style.border = '1px solid #555';
    modalContent.style.borderRadius = '5px';
    modalContent.style.width = '300px';
    modalContent.style.maxHeight = '450px';
    modalContent.style.overflowY = 'auto';
    modalContent.style.color = '#ddd';
    modalContent.style.position = 'relative';

    const titleRow = document.createElement('div');
    titleRow.style.display = 'flex';
    titleRow.style.justifyContent = 'space-between';
    titleRow.style.alignItems = 'center';
    titleRow.style.marginBottom = '5px';
    titleRow.style.padding = '5px';
    titleRow.style.borderBottom = '1px solid #555';
    titleRow.style.backgroundColor = '#3a3a3a';

    const modalTitle = document.createElement('span');
    modalTitle.textContent = 'Manage Library';
    modalTitle.style.fontSize = '16px';
    modalTitle.style.fontWeight = 'bold';
    titleRow.appendChild(modalTitle);

    const closeModalX = document.createElement('button');
    closeModalX.textContent = '×';
    closeModalX.style.border = 'none';
    closeModalX.style.background = 'transparent';
    closeModalX.style.color = '#f66';
    closeModalX.style.cursor = 'pointer';
    closeModalX.style.fontSize = '24px';
    closeModalX.style.fontWeight = 'bold';
    closeModalX.style.padding = '0 5px';
    closeModalX.addEventListener('click', () => {
        libraryModal.style.display = 'none';
    });
    titleRow.appendChild(closeModalX);

    modalContent.appendChild(titleRow);

    // Buttons row: Remove All, Import, Export, Reset to Defaults
    const libraryBtnRow = document.createElement('div');
    libraryBtnRow.style.display = 'flex';
    libraryBtnRow.style.flexWrap = 'wrap';
    libraryBtnRow.style.gap = '5px';
    libraryBtnRow.style.marginBottom = '5px';

    // Remove All
    const removeAllBtn = document.createElement('button');
    removeAllBtn.textContent = 'Remove All';
    styleButton(removeAllBtn);
    removeAllBtn.addEventListener('click', () => {
        if (confirm('Remove ALL tags from the library?')) {
            appState.library = [];
            saveState();
            updateLibraryList(librarySearchInput.value.trim().toLowerCase());
        }
    });
    libraryBtnRow.appendChild(removeAllBtn);

    // Import
    const importBtn = document.createElement('button');
    importBtn.textContent = 'Import';
    styleButton(importBtn);
    libraryBtnRow.appendChild(importBtn);

    // Export
    const exportBtn = document.createElement('button');
    exportBtn.textContent = 'Export';
    styleButton(exportBtn);
    exportBtn.addEventListener('click', () => {
        const fileContent = appState.library.join('\n');
        const blob = new Blob([fileContent], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'nhentai_library.txt';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    });
    libraryBtnRow.appendChild(exportBtn);

    // Reset to Defaults
    const resetBtn = document.createElement('button');
    resetBtn.textContent = 'Reset to Defaults';
    styleButton(resetBtn);
    resetBtn.addEventListener('click', () => {
        if (confirm('Reset the library to default tags?')) {
            appState.library = structuredClone(defaultState.library);
            saveState();
            updateLibraryList(librarySearchInput.value.trim().toLowerCase());
        }
    });
    libraryBtnRow.appendChild(resetBtn);

    modalContent.appendChild(libraryBtnRow);

    // Import Section (hidden by default)
    const importSection = document.createElement('div');
    importSection.style.display = 'none';
    importSection.style.marginBottom = '10px';
    importSection.style.border = '1px dashed #555';
    importSection.style.padding = '5px';

    const importTitle = document.createElement('div');
    importTitle.textContent = 'Import Tags';
    importTitle.style.fontWeight = 'bold';
    importTitle.style.marginBottom = '5px';
    importSection.appendChild(importTitle);

    // 1) File input
    const fileLabel = document.createElement('label');
    fileLabel.textContent = 'Select .txt file:';
    fileLabel.style.display = 'block';
    fileLabel.style.marginBottom = '5px';
    importSection.appendChild(fileLabel);

    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = '.txt';
    fileLabel.appendChild(fileInput);

    // 2) Textarea
    const textLabel = document.createElement('label');
    textLabel.textContent = 'Or paste tags (one per line):';
    textLabel.style.display = 'block';
    textLabel.style.marginTop = '10px';
    importSection.appendChild(textLabel);

    const importTextarea = document.createElement('textarea');
    importTextarea.style.width = '100%';
    importTextarea.style.height = '80px';
    importTextarea.style.backgroundColor = '#444';
    importTextarea.style.color = '#ddd';
    importTextarea.style.border = '1px solid #555';
    importTextarea.style.borderRadius = '3px';
    importTextarea.style.resize = 'both';
    importTextarea.style.overflow = 'auto';
    importSection.appendChild(importTextarea);

    const processImportBtn = document.createElement('button');
    processImportBtn.textContent = 'Process Import';
    styleButton(processImportBtn);
    processImportBtn.style.marginTop = '10px';
    importSection.appendChild(processImportBtn);

    modalContent.appendChild(importSection);

    importBtn.addEventListener('click', () => {
        importSection.style.display =
            importSection.style.display === 'none' ? 'block' : 'none';
    });

    processImportBtn.addEventListener('click', () => {
        let linesFromFile = [];
        let linesFromTextarea = [];

        if (fileInput.files && fileInput.files[0]) {
            const file = fileInput.files[0];
            const reader = new FileReader();
            reader.onload = function(e) {
                const content = e.target.result;
                linesFromFile = content.split('\n').map(l => l.trim()).filter(Boolean);
                doImport();
            };
            reader.readAsText(file);
        } else {
            doImport();
        }

        function doImport() {
            linesFromTextarea = importTextarea.value
                .split('\n')
                .map(l => l.trim())
                .filter(Boolean);

            const allLines = [...linesFromFile, ...linesFromTextarea];
            let addedCount = 0;
            allLines.forEach(line => {
                if (!appState.library.includes(line)) {
                    appState.library.push(line);
                    addedCount++;
                }
            });

            importTextarea.value = '';
            fileInput.value = '';
            saveState();
            updateLibraryList(librarySearchInput.value.trim().toLowerCase());

            alert(`Imported ${addedCount} new tags.`);
        }
    });

    // Library Search
    const librarySearchInput = document.createElement('input');
    librarySearchInput.type = 'text';
    librarySearchInput.placeholder = 'Search library... (fuzzy)';
    librarySearchInput.style.width = '100%';
    librarySearchInput.style.padding = '3px';
    librarySearchInput.style.marginBottom = '10px';
    librarySearchInput.style.border = '1px solid #555';
    librarySearchInput.style.borderRadius = '3px';
    librarySearchInput.style.backgroundColor = '#444';
    librarySearchInput.style.color = '#ddd';
    modalContent.appendChild(librarySearchInput);

    const libraryList = document.createElement('div');
    modalContent.appendChild(libraryList);

    libraryModal.appendChild(modalContent);
    document.body.appendChild(libraryModal);

    manageLibraryBtn.addEventListener('click', () => {
        librarySearchInput.value = '';
        updateLibraryList('');
        libraryModal.style.display = 'block';

        // Hide import area
        importSection.style.display = 'none';
        importTextarea.value = '';
        fileInput.value = '';
    });

    // Keyboard nav
    let searchResults = [];
    let selectedIndex = -1;

    librarySearchInput.addEventListener('input', () => {
        const query = librarySearchInput.value.trim().toLowerCase();
        updateLibraryList(query);
    });

    librarySearchInput.addEventListener('keydown', (e) => {
        if (searchResults.length === 0) return;
        if (e.key === 'ArrowDown') {
            e.preventDefault();
            selectedIndex = Math.min(selectedIndex + 1, searchResults.length - 1);
            highlightSelected();
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            selectedIndex = Math.max(selectedIndex - 1, 0);
            highlightSelected();
        } else if (e.key === 'Enter' || e.key === 'Delete') {
            if (selectedIndex >= 0 && selectedIndex < searchResults.length) {
                const tagToRemove = searchResults[selectedIndex];
                const idx = appState.library.indexOf(tagToRemove);
                if (idx !== -1) {
                    appState.library.splice(idx, 1);
                    saveState();
                }
                updateLibraryList(librarySearchInput.value.trim().toLowerCase());
            }
        }
    });

    function updateLibraryList(filterQuery) {
        libraryList.innerHTML = '';
        let filteredLib = [...appState.library];
        if (filterQuery) {
            filteredLib = filteredLib.filter(t => fuzzyMatch(filterQuery, t));
        }
        searchResults = filteredLib;
        selectedIndex = -1;

        filteredLib.forEach((tag, idx) => {
            const row = document.createElement('div');
            row.style.display = 'flex';
            row.style.justifyContent = 'space-between';
            row.style.padding = '5px 0';
            row.style.transition = 'background-color 0.2s';

            const tagName = document.createElement('span');
            tagName.textContent = tag;
            row.appendChild(tagName);

            const removeBtn = document.createElement('button');
            removeBtn.textContent = '×';
            removeBtn.style.border = 'none';
            removeBtn.style.background = 'transparent';
            removeBtn.style.color = '#f66';
            removeBtn.style.cursor = 'pointer';
            removeBtn.style.fontWeight = 'bold';
            removeBtn.style.fontSize = '16px';
            removeBtn.addEventListener('click', () => {
                const actualIdx = appState.library.indexOf(tag);
                if (actualIdx !== -1) {
                    appState.library.splice(actualIdx, 1);
                    saveState();
                }
                updateLibraryList(librarySearchInput.value.trim().toLowerCase());
            });
            row.appendChild(removeBtn);

            libraryList.appendChild(row);
        });
    }

    function highlightSelected() {
        [...libraryList.children].forEach((child, idx) => {
            child.style.backgroundColor = (idx === selectedIndex) ? '#666' : 'transparent';
        });
    }

    //----------------------------------------
    // 12) Bulk Edit Modal (Clear All, Save)
    //----------------------------------------
    const bulkEditModal = document.createElement('div');
    bulkEditModal.style.position = 'fixed';
    bulkEditModal.style.top = '0';
    bulkEditModal.style.left = '0';
    bulkEditModal.style.width = '100%';
    bulkEditModal.style.height = '100%';
    bulkEditModal.style.backgroundColor = 'rgba(0,0,0,0.5)';
    bulkEditModal.style.display = 'none';
    bulkEditModal.style.zIndex = '99999';

    const bulkModalContent = document.createElement('div');
    bulkModalContent.style.position = 'absolute';
    bulkModalContent.style.top = '50%';
    bulkModalContent.style.left = '50%';
    bulkModalContent.style.transform = 'translate(-50%, -50%)';
    bulkModalContent.style.backgroundColor = '#2c2c2c';
    bulkModalContent.style.padding = '20px';
    bulkModalContent.style.border = '1px solid #555';
    bulkModalContent.style.borderRadius = '5px';
    bulkModalContent.style.width = '400px';
    bulkModalContent.style.maxHeight = '500px';
    bulkModalContent.style.overflowY = 'auto';
    bulkModalContent.style.color = '#ddd';
    bulkModalContent.style.position = 'relative';

    const bulkTitleRow = document.createElement('div');
    bulkTitleRow.style.display = 'flex';
    bulkTitleRow.style.justifyContent = 'space-between';
    bulkTitleRow.style.alignItems = 'center';
    bulkTitleRow.style.marginBottom = '10px';

    const bulkTitle = document.createElement('h2');
    bulkTitle.textContent = 'Bulk Edit Tags';
    bulkTitle.style.margin = '0';
    bulkTitleRow.appendChild(bulkTitle);

    const bulkCloseX = document.createElement('button');
    bulkCloseX.textContent = '×';
    bulkCloseX.style.border = 'none';
    bulkCloseX.style.background = 'transparent';
    bulkCloseX.style.color = '#f66';
    bulkCloseX.style.cursor = 'pointer';
    bulkCloseX.style.fontSize = '20px';
    bulkCloseX.style.fontWeight = 'bold';
    bulkCloseX.addEventListener('click', () => {
        bulkEditModal.style.display = 'none';
    });
    bulkTitleRow.appendChild(bulkCloseX);

    bulkModalContent.appendChild(bulkTitleRow);

    const incLabel = document.createElement('label');
    incLabel.textContent = 'Included Tags (one per line):';
    bulkModalContent.appendChild(incLabel);

    const incTextarea = document.createElement('textarea');
    incTextarea.style.width = '100%';
    incTextarea.style.height = '80px';
    incTextarea.style.marginBottom = '10px';
    incTextarea.style.backgroundColor = '#444';
    incTextarea.style.color = '#ddd';
    incTextarea.style.border = '1px solid #555';
    incTextarea.style.borderRadius = '3px';
    bulkModalContent.appendChild(incTextarea);

    const excLabel = document.createElement('label');
    excLabel.textContent = 'Excluded Tags (one per line):';
    bulkModalContent.appendChild(excLabel);

    const excTextarea = document.createElement('textarea');
    excTextarea.style.width = '100%';
    excTextarea.style.height = '80px';
    excTextarea.style.marginBottom = '10px';
    excTextarea.style.backgroundColor = '#444';
    excTextarea.style.color = '#ddd';
    excTextarea.style.border = '1px solid #555';
    excTextarea.style.borderRadius = '3px';
    bulkModalContent.appendChild(excTextarea);

    const bulkBtnRow = document.createElement('div');
    bulkBtnRow.style.display = 'flex';
    bulkBtnRow.style.gap = '5px';
    bulkBtnRow.style.marginTop = '10px';

    const clearAllBtn = document.createElement('button');
    clearAllBtn.textContent = 'Clear All Tags';
    styleButton(clearAllBtn);
    clearAllBtn.addEventListener('click', () => {
        appState.included = [];
        appState.excluded = [];
        saveState();
        updateBulkModal();
        updatePreview();
    });
    bulkBtnRow.appendChild(clearAllBtn);

    const bulkSaveBtn = document.createElement('button');
    bulkSaveBtn.textContent = 'Save';
    styleButton(bulkSaveBtn);
    bulkSaveBtn.addEventListener('click', () => {
        const incLines = incTextarea.value.split('\n').map(t => t.trim()).filter(Boolean);
        const excLines = excTextarea.value.split('\n').map(t => t.trim()).filter(Boolean);
        appState.included = incLines;
        appState.excluded = excLines;
        saveState();
        updatePreview();
        bulkEditModal.style.display = 'none';
    });
    bulkBtnRow.appendChild(bulkSaveBtn);

    bulkModalContent.appendChild(bulkBtnRow);
    bulkEditModal.appendChild(bulkModalContent);
    document.body.appendChild(bulkEditModal);

    bulkEditBtn.addEventListener('click', () => {
        updateBulkModal();
        bulkEditModal.style.display = 'block';
    });

    function updateBulkModal() {
        incTextarea.value = appState.included.join('\n');
        excTextarea.value = appState.excluded.join('\n');
    }

    //----------------------------
    // 13) Search Query Preview
    //----------------------------
    const queryPreview = document.createElement('div');
    queryPreview.style.marginTop = '10px';
    queryPreview.style.padding = '5px';
    queryPreview.style.backgroundColor = '#333';
    queryPreview.style.border = '1px dashed #555';
    queryPreview.style.minHeight = '20px';
    queryPreview.textContent = 'Search Query Preview: ';
    content.appendChild(queryPreview);

    const boxesContainer = document.createElement('div');
    boxesContainer.style.marginTop = '5px';
    content.appendChild(boxesContainer);

    function createTagBox(tag, isExclude) {
        const box = document.createElement('span');
        box.style.display = 'inline-block';
        box.style.backgroundColor = '#444';
        box.style.padding = '3px 6px';
        box.style.margin = '2px';
        box.style.borderRadius = '3px';
        box.style.border = '1px solid #555';
        box.style.transition = 'background-color 0.2s';

        const textNode = document.createElement('span');
        textNode.textContent = tag;
        box.appendChild(textNode);

        const removeBtn = document.createElement('button');
        removeBtn.textContent = ' ×';
        removeBtn.style.marginLeft = '5px';
        removeBtn.style.border = 'none';
        removeBtn.style.background = 'transparent';
        removeBtn.style.cursor = 'pointer';
        removeBtn.style.fontWeight = 'bold';
        removeBtn.style.color = '#f66';
        removeBtn.style.transition = 'color 0.2s';
        removeBtn.addEventListener('mouseenter', () => {
            removeBtn.style.color = '#faa';
        });
        removeBtn.addEventListener('mouseleave', () => {
            removeBtn.style.color = '#f66';
        });
        removeBtn.addEventListener('click', () => {
            if (!isExclude) {
                const idx = appState.included.indexOf(tag);
                if (idx !== -1) appState.included.splice(idx, 1);
            } else {
                const idx = appState.excluded.indexOf(tag);
                if (idx !== -1) appState.excluded.splice(idx, 1);
            }
            saveState();
            updatePreview();
        });
        box.appendChild(removeBtn);

        return box;
    }

    function updatePreview() {
        let queryParts = [];
        appState.included.forEach(t => queryParts.push(`tag:"${t}"`));
        appState.excluded.forEach(t => queryParts.push(`-tag:"${t}"`));
        if (appState.language !== 'none') {
            queryParts.push(`language:"${appState.language}"`);
        }
        const finalQuery = queryParts.join(' ');
        queryPreview.textContent = 'Search Query Preview: ' + finalQuery;

        boxesContainer.innerHTML = '';

        // Include
        if (appState.included.length > 0) {
            const incTitle = document.createElement('div');
            incTitle.textContent = 'Include Tags:';
            incTitle.style.marginTop = '5px';
            incTitle.style.fontWeight = 'bold';
            boxesContainer.appendChild(incTitle);

            const incBoxRow = document.createElement('div');
            appState.included.forEach(tag => {
                incBoxRow.appendChild(createTagBox(tag, false));
            });
            boxesContainer.appendChild(incBoxRow);
        }

        // Exclude
        if (appState.excluded.length > 0) {
            const excTitle = document.createElement('div');
            excTitle.textContent = 'Exclude Tags:';
            excTitle.style.marginTop = '5px';
            excTitle.style.fontWeight = 'bold';
            boxesContainer.appendChild(excTitle);

            const excBoxRow = document.createElement('div');
            appState.excluded.forEach(tag => {
                excBoxRow.appendChild(createTagBox(tag, true));
            });
            boxesContainer.appendChild(excBoxRow);
        }
    }

    //----------------------------
    // 14) Search Button
    //----------------------------
    const searchButton = document.createElement('button');
    searchButton.textContent = 'Search NHentai';
    styleButton(searchButton);
    searchButton.style.marginTop = '10px';
    searchButton.style.width = '100%';
    searchButton.style.padding = '6px';
    content.appendChild(searchButton);

    searchButton.addEventListener('click', () => {
        let queryParts = [];
        appState.included.forEach(t => queryParts.push(`tag:"${t}"`));
        appState.excluded.forEach(t => queryParts.push(`-tag:"${t}"`));
        if (appState.language !== 'none') {
            queryParts.push(`language:"${appState.language}"`);
        }
        const finalQuery = queryParts.join(' ');
        const encodedQuery = encodeURIComponent(finalQuery);
        window.location.href = `https://nhentai.net/search/?q=${encodedQuery}`;
    });

    //----------------------------
    // 15) Final Initialization
    //----------------------------
    // Restore language, update preview
    languageSelect.value = appState.language || 'english';
    updatePreview();

    // Re-check preview on blur
    includeField.input.addEventListener('blur', updatePreview);
    excludeField.input.addEventListener('blur', updatePreview);

})();