StashDB Multi-Tag Searcher

Search StashDB with configurable columns, left-side adaptive images, fixed backend pagination, recent tag caches, maximize/minimize options, single-click tag page scraping, explicit subheadings, and fully clickable result cards.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         StashDB Multi-Tag Searcher
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Search StashDB with configurable columns, left-side adaptive images, fixed backend pagination, recent tag caches, maximize/minimize options, single-click tag page scraping, explicit subheadings, and fully clickable result cards.
// @author       You
// @match        https://stashdb.org/*
// @license      Custom / Free Redistribution for Modifications Only (No Exact Mirroring)
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // Global tracking configuration variables for pagination routines
    let currentActiveInputString = "";
    let currentActivePage = 1;
    let currentActiveResolvedLabels = ""; // Holds resolved string across pagination updates
    const ITEMS_PER_PAGE = 20;

    // Create main UI container - positioned ~1 inch from the top right
    const searchBox = document.createElement('div');
    searchBox.id = "stashdb-multi-tag-box";
    searchBox.style = "position: fixed; top: 96px; right: 20px; background: #191c1f; color: #f8f9fa; padding: 15px; border-radius: 6px; z-index: 9999; border: 1px solid #343a40; width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-family: sans-serif; transition: all 0.2s ease-in-out;";
    searchBox.innerHTML = `
        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
            <h4 style="margin:0; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; color: #00adb5;">Multi-Tag Search</h4>
            <div style="display: flex; gap: 8px; align-items: center;">
                <button id="btn-multi-config" style="background: none; border: none; color: #6c757d; cursor: pointer; font-size: 12px; transition: 0.2s;" onmouseover="this.style.color='#00adb5'" onmouseout="this.style.color='#6c757d'">⚙ Settings</button>
                <button id="btn-multi-toggle" style="background: none; border: none; color: #6c757d; cursor: pointer; font-size: 14px; font-weight: bold; transition: 0.2s; padding: 0 4px;" onmouseover="this.style.color='#00adb5'" onmouseout="this.style.color='#6c757d'">_</button>
            </div>
        </div>

        <div id="multi-search-content">
            <div id="multi-config-drawer" style="display: none; background: #212529; padding: 10px; border: 1px solid #343a40; border-radius: 4px; margin-bottom: 10px;">
                <label style="font-size: 11px; color: #a5b1c2; display: block; margin-bottom: 4px;">StashDB API Key (JWT Token):</label>
                <input type="password" id="cfg-api-key" placeholder="Paste Token..." style="width: 100%; background: #191c1f; color: #fff; border: 1px solid #495057; padding: 4px; border-radius: 4px; font-size: 11px; box-sizing: border-box; margin-bottom: 8px;">

                <label style="font-size: 11px; color: #a5b1c2; display: block; margin-bottom: 4px;">Max Grid Columns Layout:</label>
                <select id="cfg-grid-cols" style="width: 100%; background: #191c1f; color: #fff; border: 1px solid #495057; padding: 4px; border-radius: 4px; font-size: 11px; box-sizing: border-box; margin-bottom: 10px;">
                    <option value="max">Max (Auto-Responsive Layout)</option>
                    <option value="1">1 Column Layout</option>
                    <option value="2">2 Columns Layout</option>
                    <option value="3">3 Columns Layout</option>
                    <option value="4">4 Columns Layout</option>
                    <option value="5">5 Columns Layout</option>
                    <option value="6">6 Columns Layout</option>
                </select>

                <button id="btn-save-cfg" style="width: 100%; background: #28a745; color: white; border: none; padding: 5px; cursor: pointer; border-radius: 4px; font-size: 11px; font-weight: bold;">Save Configurations</button>
            </div>

            <input type="text" id="multi-tags" placeholder="Paste Tag UUIDs (comma separated)" style="width: 100%; margin-bottom: 6px; background: #212529; color: #fff; border: 1px solid #495057; padding: 6px; border-radius: 4px; box-sizing: border-box; font-size: 12px;">

            <button id="btn-grab-tag" style="width: 100%; margin-bottom: 10px; border: none; padding: 5px; border-radius: 4px; font-size: 11px; font-weight: bold; cursor: pointer; transition: 0.2s;">✚ Grab Active Tag</button>

            <div style="display: flex; gap: 8px; margin-bottom: 8px;">
                <button id="btn-multi-search" style="flex: 2; background: #00adb5; color: white; border: none; padding: 8px; cursor: pointer; border-radius: 4px; font-weight: bold; font-size: 13px;">Search Scenes</button>
                <button id="btn-multi-clear" style="flex: 1; background: #495057; color: white; border: none; padding: 8px; cursor: pointer; border-radius: 4px; font-size: 13px;">Clear</button>
            </div>

            <div id="multi-status" style="font-size: 11px; color: #6c757d; margin-bottom: 12px; min-height: 14px; line-height: 1.3;"></div>

            <div style="border-top: 1px solid #343a40; padding-top: 10px; margin-top: 5px; margin-bottom: 10px;">
                <h5 style="margin: 0 0 8px 0; font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px;">Recent Tags</h5>
                <div id="multi-recent-tags" style="display: flex; flex-wrap: wrap; gap: 4px; max-height: 85px; overflow-y: auto;"></div>
            </div>

            <div style="border-top: 1px solid #343a40; padding-top: 10px; margin-top: 5px;">
                <h5 style="margin: 0 0 8px 0; font-size: 12px; text-transform: uppercase; color: #6c757d; letter-spacing: 0.5px;">Last 5 Searches</h5>
                <div id="multi-history" style="display: flex; flex-direction: column; gap: 4px; max-height: 150px; overflow-y: auto;"></div>
            </div>
        </div>
    `;
    document.body.appendChild(searchBox);

    // Initializations
    renderHistoryList();
    renderRecentTagsList();
    checkApiKeyStatus();
    applyUIWindowState();
    evaluateGrabButtonContext();

    // Contextual Inspector: Watch URLs inside complex client-side Single Page App tracking frameworks
    let lastKnownUrlPath = window.location.href;
    setInterval(() => {
        if (window.location.href !== lastKnownUrlPath) {
            lastKnownUrlPath = window.location.href;
            evaluateGrabButtonContext();
        }
    }, 1000);

    // Contextual Evaluator Engine
    function getActiveTagIdFromUrl() {
        const matches = window.location.href.match(/stashdb\.org\/tags\/([a-f0-9\-]{36})/i);
        return matches ? matches[1] : null;
    }

    function evaluateGrabButtonContext() {
        const grabBtn = document.getElementById('btn-grab-tag');
        if (!grabBtn) return;

        const tagId = getActiveTagIdFromUrl();
        if (tagId) {
            grabBtn.disabled = false;
            grabBtn.style.background = "#17a2b8";
            grabBtn.style.color = "white";
            grabBtn.style.cursor = "pointer";
            grabBtn.style.opacity = "1";
            grabBtn.title = `Extract current Tag UUID: ${tagId}`;
        } else {
            grabBtn.disabled = true;
            grabBtn.style.background = "#2d3238";
            grabBtn.style.color = "#6c757d";
            grabBtn.style.cursor = "not-allowed";
            grabBtn.style.opacity = "0.6";
            grabBtn.title = "Navigate to an individual StashDB Tag page to activate this button.";
        }
    }

    // Event Listener: Contextual Scraper Routine
    document.getElementById('btn-grab-tag').addEventListener('click', async () => {
        const tagId = getActiveTagIdFromUrl();
        const userApiKey = checkApiKeyStatus();
        if (!tagId || !userApiKey) return;

        const statusDiv = document.getElementById('multi-status');
        statusDiv.innerHTML = "Scraping active tag information...";

        const inputField = document.getElementById('multi-tags');
        let currentVal = inputField.value.trim();

        if (currentVal === "") {
            inputField.value = tagId;
        } else {
            const existingArray = currentVal.split(',').map(x => x.trim());
            if (!existingArray.includes(tagId)) {
                inputField.value = currentVal + ", " + tagId;
            }
        }

        const graphqlQuery = {
            query: `query FindTag($id: ID!) { findTag(id: $id) { name } }`,
            variables: { id: tagId }
        };

        try {
            const response = await fetch("https://stashdb.org/graphql", {
                method: "POST",
                headers: { "Content-Type": "application/json", "Accept": "application/json", "ApiKey": userApiKey },
                body: JSON.stringify(graphqlQuery)
            });
            if (response.ok) {
                const data = await response.json();
                if (data?.data?.findTag?.name) {
                    const tagName = data.data.findTag.name;
                    saveToRecentTags([{ id: tagId, name: tagName }]);
                    statusDiv.innerHTML = `<span style='color:#28a745;'>Grabbed tag: <strong>${tagName}</strong></span>`;
                    return;
                }
            }
        } catch (e) { console.error(e); }

        saveToRecentTags([{ id: tagId, name: tagId.substring(0, 8) }]);
        statusDiv.innerHTML = "<span style='color:#28a745;'>Grabbed tag alphanumeric identity hash.</span>";
    });

    // Event Listener: Minimize / Maximize UI panel Toggle
    document.getElementById('btn-multi-toggle').addEventListener('click', () => {
        const isCollapsed = sessionStorage.getItem('stashdb_ui_collapsed') === 'true';
        sessionStorage.setItem('stashdb_ui_collapsed', !isCollapsed);
        applyUIWindowState();
    });

    function applyUIWindowState() {
        const content = document.getElementById('multi-search-content');
        const toggleBtn = document.getElementById('btn-multi-toggle');
        const isCollapsed = sessionStorage.getItem('stashdb_ui_collapsed') === 'true';

        if (isCollapsed) {
            content.style.display = 'none';
            searchBox.style.width = '180px';
            toggleBtn.innerText = '▢';
            toggleBtn.title = 'Maximize Panel';
        } else {
            content.style.display = 'block';
            searchBox.style.width = '300px';
            toggleBtn.innerText = '_';
            toggleBtn.title = 'Minimize Panel';
        }
    }

    // Event Listener: Toggle Settings Drawer Display
    document.getElementById('btn-multi-config').addEventListener('click', () => {
        const drawer = document.getElementById('multi-config-drawer');
        drawer.style.display = drawer.style.display === 'none' ? 'block' : 'none';
        document.getElementById('cfg-api-key').value = GM_getValue("stashdb_user_apikey", "");
        document.getElementById('cfg-grid-cols').value = GM_getValue("stashdb_grid_columns", "max");
    });

    // Event Listener: Save Options
    document.getElementById('btn-save-cfg').addEventListener('click', () => {
        const keyInput = document.getElementById('cfg-api-key').value.trim();
        const colInput = document.getElementById('cfg-grid-cols').value;

        GM_setValue("stashdb_user_apikey", keyInput);
        GM_setValue("stashdb_grid_columns", colInput);
        document.getElementById('multi-config-drawer').style.display = 'none';
        document.getElementById('multi-status').innerHTML = "<span style='color: #28a745;'>Configurations saved!</span>";
        evaluateGrabButtonContext();
    });

    // Event Listener: Execute Query
    document.getElementById('btn-multi-search').addEventListener('click', () => {
        const tagInputField = document.getElementById('multi-tags');
        if (!tagInputField.value.trim()) return;

        currentActiveInputString = tagInputField.value;
        currentActivePage = 1;
        currentActiveResolvedLabels = "";

        const mainContentArea = document.getElementById('page-content') || document.querySelector('.main') || document.querySelector('.container-fluid');
        if (mainContentArea) mainContentArea.innerHTML = "";
        tagInputField.value = "";

        executeMultiSearch();
    });

    // Event Listener: Clear field
    document.getElementById('btn-multi-clear').addEventListener('click', () => {
        document.getElementById('multi-tags').value = "";
        document.getElementById('multi-status').innerHTML = "Input cleared.";
    });

    function checkApiKeyStatus() {
        const activeKey = GM_getValue("stashdb_user_apikey", "");
        if (!activeKey) {
            document.getElementById('multi-status').innerHTML = "<span style='color: #ffc107; font-weight: 500;'>⚠️ API Key Required! Click 'Settings' above to paste your StashDB key before searching.</span>";
            return false;
        }
        return activeKey;
    }

    async function resolveTagNames(tagIds, userApiKey) {
        const resolvedObjects = [];
        const labelNames = [];

        for (const id of tagIds) {
            const graphqlQuery = {
                query: `query FindTag($id: ID!) { findTag(id: $id) { name } }`,
                variables: { id: id }
            };
            try {
                const response = await fetch("https://stashdb.org/graphql", {
                    method: "POST",
                    headers: { "Content-Type": "application/json", "Accept": "application/json", "ApiKey": userApiKey },
                    body: JSON.stringify(graphqlQuery)
                });
                if (response.ok) {
                    const data = await response.json();
                    if (data?.data?.findTag?.name) {
                        const tagName = data.data.findTag.name;
                        labelNames.push(tagName);
                        resolvedObjects.push({ id, name: tagName });
                        continue;
                    }
                }
            } catch (e) { console.error(e); }
            labelNames.push(id.substring(0, 8));
            resolvedObjects.push({ id, name: id.substring(0, 8) });
        }

        saveToRecentTags(resolvedObjects);
        return labelNames.join(" + ");
    }

    function switchMultiSearchPage(targetPageNum) {
        currentActivePage = targetPageNum;
        executeMultiSearch();
        window.scrollTo({ top: 0, behavior: 'smooth' });
    }

    // Core Logic Query Runner
    async function executeMultiSearch() {
        const userApiKey = checkApiKeyStatus();
        if (!userApiKey) return;

        const tagIds = currentActiveInputString.split(',').map(id => id.trim()).filter(id => id.length > 0);
        const statusDiv = document.getElementById('multi-status');
        if (tagIds.length === 0) {
            statusDiv.innerHTML = "<span style='color: #ffc107;'>Please enter at least one UUID.</span>";
            return;
        }

        statusDiv.innerHTML = `Loading Page ${currentActivePage}...`;

        if (currentActivePage === 1 && !currentActiveResolvedLabels) {
            currentActiveResolvedLabels = await resolveTagNames(tagIds, userApiKey);
            saveToSearchHistory(currentActiveInputString, currentActiveResolvedLabels);
        }

        const graphqlQuery = {
            query: `query QueryScenesByTags($input: SceneQueryInput!) {
                queryScenes(input: $input) {
                    count
                    scenes {
                        id
                        title
                        date
                        images {
                           url
                        }
                        studio {
                            name
                        }
                    }
                }
            }`,
            variables: {
                input: {
                    page: currentActivePage,
                    per_page: ITEMS_PER_PAGE,
                    tags: {
                        value: tagIds,
                        modifier: "INCLUDES_ALL"
                    }
                }
            }
        };
        try {
            const response = await fetch("https://stashdb.org/graphql", {
                method: "POST",
                headers: { "Content-Type": "application/json", "Accept": "application/json", "ApiKey": userApiKey },
                body: JSON.stringify(graphqlQuery)
            });
            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
            const data = await response.json();
            if (data.errors) throw new Error(data.errors[0].message);
            const scenes = data?.data?.queryScenes?.scenes || [];
            const count = data?.data?.queryScenes?.count || 0;
            const totalPages = Math.ceil(count / ITEMS_PER_PAGE);
            const mainContentArea = document.getElementById('page-content') || document.querySelector('.main') || document.querySelector('.container-fluid');
            if (!mainContentArea) throw new Error("Could not find layout container.");
            statusDiv.innerHTML = `Displaying page ${currentActivePage} matches.`;

            if (scenes.length === 0) {
                mainContentArea.innerHTML = `
                    <div style="padding: 40px; text-align: center; color: #dc3545;">
                        <h3>No Cross-Referenced Scenes Found</h3>
                        <p>No scenes in StashDB match all of those specific tag UUIDs simultaneously.</p>
                    </div>`;
                return;
            }

            const userColSetting = GM_getValue("stashdb_grid_columns", "max");
            const gridTemplateColumns = userColSetting === "max"
                ? "repeat(auto-fill, minmax(320px, 1fr))"
                : `repeat(${userColSetting}, minmax(0, 1fr))`;

            let paginationHTML = "";
            if (totalPages > 1) {
                paginationHTML = `
                    <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 30px; padding: 20px 0; border-top: 1px solid #343a40;">
                        <button id="p-nav-first" ${currentActivePage === 1 ? 'disabled style="opacity:0.3; cursor:not-allowed;"' : ''} style="background: #212529; border: 1px solid #495057; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size:12px;">« First</button>
                        <button id="p-nav-prev" ${currentActivePage === 1 ? 'disabled style="opacity:0.3; cursor:not-allowed;"' : ''} style="background: #212529; border: 1px solid #495057; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size:12px;">‹ Prev</button>
                        <span style="color: #a5b1c2; font-size: 13px; font-weight: 500;">Page <strong>${currentActivePage}</strong> of <strong>${totalPages}</strong></span>
                        <button id="p-nav-next" ${currentActivePage === totalPages ? 'disabled style="opacity:0.3; cursor:not-allowed;"' : ''} style="background: #212529; border: 1px solid #495057; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size:12px;">Next ›</button>
                        <button id="p-nav-last" ${currentActivePage === totalPages ? 'disabled style="opacity:0.3; cursor:not-allowed;"' : ''} style="background: #212529; border: 1px solid #495057; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size:12px;">Last »</button>
                    </div>
                `;
            }

            mainContentArea.innerHTML = `
                <div style="padding: 20px;">
                    <div style="border-bottom: 1px solid #343a40; padding-bottom: 15px; margin-bottom: 20px;">
                        <div style="display: flex; justify-content: space-between; align-items: center;">
                            <h2 style="margin: 0; font-size: 24px; color: #fff;">Multi-Tag Search Results <span style="font-size: 16px; color: #6c757d;">(Showing ${scenes.length} of ${count} total items)</span></h2>
                            <button onclick="window.location.reload()" style="background: #343a40; color: white; border: 1px solid #495057; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 13px;">Reset Page</button>
                        </div>
                        <div style="margin-top: 8px; font-size: 14px; color: #a5b1c2; font-weight: 500; word-break: break-word;">
                            <span style="color: #00adb5; font-weight: bold; text-transform: uppercase; font-size: 12px; letter-spacing: 0.5px; margin-right: 4px;">Tags Included:</span> ${currentActiveResolvedLabels}
                        </div>
                    </div>

                    <div style="display: grid; grid-template-columns: ${gridTemplateColumns}; gap: 20px;">
                        ${scenes.map(s => {
                            const studioName = s.studio ? s.studio.name : "Unknown Studio";
                            const releaseDate = s.date ? s.date : "Date Unknown";
                            const imageSrc = (s.images && s.images.length > 0) ? s.images[0].url : 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%232d3238"/></svg>';
                            return `
                                <a href="/scenes/${s.id}" target="_blank" class="search-result-card" style="text-decoration: none; display: block; color: inherit; transition: background-color 0.15s ease;">
                                    <div style="background: #1f2327; border: 1px solid #2d3238; border-radius: 6px; overflow: hidden; display: flex; box-shadow: 0 2px 5px rgba(0,0,0,0.2); min-height: 110px;" onmouseover="this.style.backgroundColor='#2d3238';" onmouseout="this.style.backgroundColor='#1f2327';">
                                        <div style="flex: 0 0 35%; max-width: 140px; background: #111; display: flex; align-items: center; justify-content: center; border-right: 1px solid #2d3238;">
                                            <img src="${imageSrc}" style="width: 100%; height: 100%; object-fit: cover;" alt="">
                                        </div>
                                        <div style="flex: 1; padding: 12px; display: flex; flex-direction: column; justify-content: space-between; min-width: 0;">
                                            <div>
                                                <div style="font-size: 10px; text-transform: uppercase; color: #00adb5; font-weight: bold; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${studioName}</div>
                                                <div style="color: #f8f9fa; font-size: 14px; font-weight: 600; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 4px;">
                                                    ${s.title || 'Untitled Scene'}
                                                </div>
                                            </div>
                                            <div style="font-size: 11px; color: #6c757d; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
                                                <span>Released: ${releaseDate}</span>
                                            </div>
                                        </div>
                                    </div>
                                </a>
                            `;
                        }).join('')}
                    </div>

                    ${paginationHTML}
                </div>
            `;

            if (totalPages > 1) {
                if (currentActivePage !== 1) {
                    document.getElementById('p-nav-first').addEventListener('click', () => switchMultiSearchPage(1));
                    document.getElementById('p-nav-prev').addEventListener('click', () => switchMultiSearchPage(currentActivePage - 1));
                }
                if (currentActivePage !== totalPages) {
                    document.getElementById('p-nav-next').addEventListener('click', () => switchMultiSearchPage(currentActivePage + 1));
                    document.getElementById('p-nav-last').addEventListener('click', () => switchMultiSearchPage(totalPages));
                }
            }

        } catch (error) {
            console.error(error);
            statusDiv.innerHTML = `<span style='color: #dc3545;'>Error loading.</span>`;
        }
    }

    // Engine: Manage Recent Individual Tags Caches inside localStorage
    function saveToRecentTags(newTagObjects) {
        let tags = [];
        try { tags = JSON.parse(localStorage.getItem('stashdb_recent_individual_tags')) || []; } catch(e) {}

        newTagObjects.forEach(newTag => {
            tags = tags.filter(t => t.id !== newTag.id);
            tags.unshift(newTag);
        });

        if (tags.length > 20) tags = tags.slice(0, 20);

        localStorage.setItem('stashdb_recent_individual_tags', JSON.stringify(tags));
        renderRecentTagsList();
    }

    function renderRecentTagsList() {
        const container = document.getElementById('multi-recent-tags');
        if (!container) return;

        let tags = [];
        try { tags = JSON.parse(localStorage.getItem('stashdb_recent_individual_tags')) || []; } catch(e) {}

        if (tags.length === 0) {
            container.innerHTML = "<p style='color: #495057; margin: 0; font-size: 11px; font-style: italic;'>No tags recorded yet.</p>";
            return;
        }

        container.innerHTML = tags.map(t => {
            return `
                <span class="recent-tag-pill" data-uuid="${t.id}" title="Click to add UUID: ${t.id}" style="display: inline-block; background: #212529; color: #a5b1c2; border: 1px solid #343a40; border-radius: 3px; padding: 2px 6px; font-size: 11px; cursor: pointer; transition: 0.15s; white-space: nowrap; max-width: 120px; overflow: hidden; text-overflow: ellipsis;" onmouseover="this.style.background='#00adb5'; this.style.color='#fff';" onmouseout="this.style.background='#212529'; this.style.color='#a5b1c2';">
                    ${t.name}
                </span>
            `;
        }).join('');

        container.querySelectorAll('.recent-tag-pill').forEach(pill => {
            pill.addEventListener('click', function() {
                const uuid = this.getAttribute('data-uuid');
                const inputField = document.getElementById('multi-tags');
                let currentVal = inputField.value.trim();

                if (currentVal === "") {
                    inputField.value = uuid;
                } else {
                    const existingArray = currentVal.split(',').map(x => x.trim());
                    if (!existingArray.includes(uuid)) {
                        inputField.value = currentVal + ", " + uuid;
                    }
                }
            });
        });
    }

    // Engine: History Stack Management
    function saveToSearchHistory(queryStr, labelStr) {
        let history = [];
        try { history = JSON.parse(localStorage.getItem('stashdb_tag_search_history_v2')) || []; } catch(e) {}

        history = history.filter(item => item.query !== queryStr);
        history.unshift({ query: queryStr, label: labelStr });
        if (history.length > 5) history.pop();

        localStorage.setItem('stashdb_tag_search_history_v2', JSON.stringify(history));
        renderHistoryList();
    }

    function renderHistoryList() {
        const container = document.getElementById('multi-history');
        let history = [];
        try { history = JSON.parse(localStorage.getItem('stashdb_tag_search_history_v2')) || []; } catch(e) {}

        if (history.length === 0) {
            container.innerHTML = "<p style='color: #495057; margin: 0; font-size: 11px; font-style: italic;'>No search history yet.</p>";
            return;
        }

        container.innerHTML = history.map((item, index) => {
            const shortDisplay = item.label.length > 38 ? item.label.substring(0, 35) + "..." : item.label;
            const borderStyle = index === 0
                ? "border: 1px solid #ffc107; box-shadow: 0 0 4px rgba(255,193,7,0.2);"
                : "border: 1px solid #2d3238;";

            return `
                <div class="history-item" data-query="${encodeURIComponent(item.query)}" data-label="${encodeURIComponent(item.label)}" title="UUIDs: ${item.query}" style="color: #a5b1c2; font-size: 11px; cursor: pointer; padding: 4px 6px; background: #212529; border-radius: 3px; ${borderStyle} overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: 0.2s;" onmouseover="this.style.background='#2d3238'; this.style.color='#00adb5';" onmouseout="this.style.background='#212529'; this.style.color='#a5b1c2';">
                    ${index + 1}. ${shortDisplay}
                </div>
            `;
        }).join('');

        const items = container.querySelectorAll('.history-item');
        items.forEach(el => {
            el.addEventListener('click', function() {
                const targets = decodeURIComponent(this.getAttribute('data-query'));
                const label = decodeURIComponent(this.getAttribute('data-label'));

                currentActiveInputString = targets;
                currentActivePage = 1;
                currentActiveResolvedLabels = label;

                const mainContentArea = document.getElementById('page-content') || document.querySelector('.main') || document.querySelector('.container-fluid');
                if (mainContentArea) mainContentArea.innerHTML = "";
                document.getElementById('multi-tags').value = "";

                executeMultiSearch();
            });
        });
    }
})();