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.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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();
            });
        });
    }
})();