Bazaars in Item Market powered by TornPal and IronNerd

Displays bazaar listings with sorting controls via TornPal & IronNerd

// ==UserScript==
// @name         Bazaars in Item Market powered by TornPal and IronNerd
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Displays bazaar listings with sorting controls via TornPal & IronNerd
// @author       Weav3r
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @match        https://www.torn.com/bazaar.php*   
// @grant        GM_xmlhttpRequest
// @connect      tornpal.com
// @connect      www.ironnerd.me
// @run-at       document-end

// ==/UserScript==


(function () {
    'use strict';

    const CACHE_DURATION_MS = 60000,
          CARD_WIDTH = 180;

    let currentSortKey = "price",
        currentSortOrder = "asc",
        allListings = [],
        currentDarkMode = document.body.classList.contains('dark-mode'),
        currentItemName = "",
        displayMode = "percentage";

    let scriptSettings = {
        defaultSort: "price",
        defaultOrder: "asc",
        apiKey: "",
        listingFee: parseFloat(localStorage.getItem("bazaarListingFee") || "0"),
        defaultDisplayMode: "percentage"
    };

    function loadSettings() {
        try {
            const saved = localStorage.getItem("bazaarsSettings");
            if (saved) {
                scriptSettings = { ...scriptSettings, ...JSON.parse(saved) };
                currentSortKey = scriptSettings.defaultSort;
                currentSortOrder = scriptSettings.defaultOrder;
                displayMode = scriptSettings.defaultDisplayMode || "percentage";
            }
        } catch (e) {
            console.error("Oops, settings failed to load:", e);
        }
    }

    function saveSettings() {
        try {
            localStorage.setItem("bazaarsSettings", JSON.stringify(scriptSettings));
            localStorage.setItem("bazaarApiKey", scriptSettings.apiKey || "");
            localStorage.setItem("bazaarDefaultSort", scriptSettings.defaultSort || "price");
            localStorage.setItem("bazaarDefaultOrder", scriptSettings.defaultOrder || "asc");
            localStorage.setItem("bazaarListingFee", scriptSettings.listingFee || 0);
            localStorage.setItem("bazaarDefaultDisplayMode", scriptSettings.defaultDisplayMode || "percentage");
        } catch (e) {
            console.error("Settings save hiccup:", e);
        }
    }

    loadSettings();

    const style = document.createElement("style");
    style.textContent = `
        .bazaar-button {
            padding: 3px 6px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background-color: #fff;
            color: #000;
            cursor: pointer;
            font-size: 12px;
            margin-left: 4px;
        }
        .dark-mode .bazaar-button {
            border: 1px solid #444;
            background-color: #1a1a1a;
            color: #fff;
        }
        .bazaar-modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.7);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10000;
        }

        .bazaar-info-container {
            font-size: 13px;
            border-radius: 4px;
            margin: 5px 0;
            padding: 10px;
            display: flex;
            flex-direction: column;
            gap: 8px;
            background-color: #f9f9f9;
            color: #000;
            border: 1px solid #ccc;
            box-sizing: border-box;
            width: 100%;
            overflow: hidden;
        }
        .dark-mode .bazaar-info-container {
            background-color: #2f2f2f;
            color: #ccc;
            border: 1px solid #444;
        }
        .bazaar-info-header {
            font-size: 16px;
            font-weight: bold;
            color: #000;
        }
        .dark-mode .bazaar-info-header {
            color: #fff;
        }
        .bazaar-sort-controls {
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 12px;
            padding: 5px;
            background-color: #eee;
            border-radius: 4px;
            border: 1px solid #ccc;
        }
        .dark-mode .bazaar-sort-controls {
            background-color: #333;
            border: 1px solid #444;
        }
        .bazaar-sort-select {
            padding: 3px 24px 3px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background: #fff url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iIzY2NiIvPjwvc3ZnPg==") no-repeat right 8px center;
            background-size: 10px 6px;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            cursor: pointer;
        }
        .bazaar-profit-tooltip {
            position: absolute;
            background: #fff;
            color: #333;
            border: 1px solid #ddd;
            padding: 8px 12px;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            z-index: 10000;
            max-width: 280px;
            pointer-events: none;
        }
        .dark-mode .bazaar-profit-tooltip {
            background: #333;
            color: #fff;
            border: 1px solid #555;
        }

        .dark-mode .bazaar-sort-select {
            border: 1px solid #444;
            background-color: #1a1a1a;
            color: #fff;
            background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iI2NjYyIvPjwvc3ZnPg==");
        }
        .bazaar-sort-select:focus {
            outline: none;
            border-color: #0078d7;
            box-shadow: 0 0 0 1px #0078d7;
        }
        .bazaar-scroll-container {
            position: relative;
            display: flex;
            align-items: stretch;
            width: 100%;
            box-sizing: border-box;
        }
        .bazaar-scroll-wrapper {
            flex: 1;
            overflow-x: auto;
            overflow-y: hidden;
            height: 100px;
            white-space: nowrap;
            padding-bottom: 3px;
            border-radius: 4px;
            border: 1px solid #ccc;
            margin: 0 auto;
            max-width: calc(100% - 30px);
            position: relative;
        }
        .dark-mode .bazaar-scroll-wrapper {
            border: 1px solid #444;
        }
        .bazaar-scroll-arrow {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 12px;
            flex-shrink: 0;
            flex-grow: 0;
            cursor: pointer;
            background-color: transparent;
            border: none;
            opacity: 0.5;
            transition: opacity 0.2s ease;
            margin: 0 1px;
            z-index: 2;
            position: relative;
        }
        .bazaar-scroll-arrow:hover {
            opacity: 0.9;
            background-color: transparent;
        }
        .dark-mode .bazaar-scroll-arrow {
            background-color: transparent;
            border: none;
        }
        .bazaar-scroll-arrow svg {
            width: 18px !important;
            height: 18px !important;
            color: #888;
        }
        .dark-mode .bazaar-scroll-arrow svg {
            color: #777;
        }
        .bazaar-scroll-arrow.left {
            padding-left: 10px;
            margin-left: -10px;
        }
        .bazaar-scroll-arrow.right {
            padding-right: 10px;
            margin-right: -10px;
        }
        .bazaar-scroll-wrapper::-webkit-scrollbar {
            height: 8px;
        }
        .bazaar-scroll-wrapper::-webkit-scrollbar-track {
            background: #f1f1f1;
        }
        .bazaar-scroll-wrapper::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 4px;
        }
        .bazaar-scroll-wrapper::-webkit-scrollbar-thumb:hover {
            background: #555;
        }
        .dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-track {
            background: #333;
        }
        .dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-thumb {
            background: #555;
        }
        .dark-mode .bazaar-scroll-wrapper::-webkit-scrollbar-thumb:hover {
            background: #777;
        }
        .bazaar-card-container {
            position: relative;
            height: 100%;
            display: flex;
            align-items: center;
        }
        .bazaar-listing-card {
            position: absolute;
            min-width: 140px;
            max-width: 200px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            border-radius: 4px;
            padding: 8px;
            font-size: clamp(12px, 1vw, 14px);
            box-sizing: border-box;
            overflow: hidden;
            background-color: #fff;
            color: #000;
            border: 1px solid #ccc;
            top: 50%;
            transform: translateY(-50%);
            word-break: break-word;
            height: auto;
        }
        .dark-mode .bazaar-listing-card {
            background-color: #1a1a1a;
            color: #fff;
            border: 1px solid #444;
        }
        .bazaar-listing-footnote {
            font-size: 11px;
            text-align: right;
            color: #555;
        }
        .dark-mode .bazaar-listing-footnote {
            color: #aaa;
        }
        .bazaar-listing-source {
            font-size: 10px;
            text-align: right;
            color: #555;
        }
        .dark-mode .bazaar-listing-source {
            color: #aaa;
        }
        .bazaar-footer-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 5px;
            font-size: 10px;
        }
        .bazaar-powered-by span {
            color: #999;
        }
        .dark-mode .bazaar-powered-by span {
            color: #666;
        }
        .bazaar-powered-by a {
            text-decoration: underline;
            color: #555;
        }
        .dark-mode .bazaar-powered-by a {
            color: #aaa;
        }
        @keyframes popAndFlash {
            0%   { transform: scale(1); background-color: rgba(0,255,0,0.6); }
            50%  { transform: scale(1.05); }
            100% { transform: scale(1); background-color: inherit; }
        }
        .pop-flash {
            animation: popAndFlash 0.8s ease-in-out forwards;
        }
        .green-outline {
            border: 3px solid green !important;
        }
        .bazaar-settings-modal {
            background-color: #fff;
            border-radius: 8px;
            padding: 24px;
            width: 500px;
            max-width: 95vw;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            position: relative;
            z-index: 10001;
            font-family: 'Arial', sans-serif;
        }
        .dark-mode .bazaar-settings-modal {
            background-color: #2a2a2a;
            color: #e0e0e0;
            border: 1px solid #444;
        }
        .bazaar-settings-title {
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #333;
        }
        .dark-mode .bazaar-settings-title {
            color: #fff;
        }
        .bazaar-tabs {
            display: flex;
            border-bottom: 1px solid #ddd;
            margin-bottom: 20px;
            padding-bottom: 0;
        }
        .dark-mode .bazaar-tabs {
            border-bottom: 1px solid #444;
        }
        .bazaar-tab {
            padding: 10px 16px;
            cursor: pointer;
            margin-right: 5px;
            border: 1px solid transparent;
            border-bottom: none;
            border-radius: 4px 4px 0 0;
            font-weight: normal;
            background-color: #f5f5f5;
            color: #555;
            position: relative;
            bottom: -1px;
        }
        .dark-mode .bazaar-tab {
            background-color: #333;
            color: #ccc;
        }
        .bazaar-tab.active {
            background-color: #fff;
            color: #333;
            border-color: #ddd;
            font-weight: bold;
            padding-bottom: 11px;
        }
        .dark-mode .bazaar-tab.active {
            background-color: #2a2a2a;
            color: #fff;
            border-color: #444;
        }
        .bazaar-tab-content {
            display: none;
        }
        .bazaar-tab-content.active {
            display: block;
        }
        .bazaar-settings-group {
            margin-bottom: 20px;
        }
        .bazaar-settings-item {
            margin-bottom: 18px;
        }
        .bazaar-settings-item label {
            display: block;
            margin-bottom: 8px;
            font-weight: bold;
            font-size: 14px;
        }
        .bazaar-settings-item input[type="text"],
        .bazaar-settings-item select,
        .bazaar-number-input {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
            background-color: #fff;
            color: #333;
            max-width: 250px;
        }
        .dark-mode .bazaar-settings-item input[type="text"],
        .dark-mode .bazaar-settings-item select,
        .dark-mode .bazaar-number-input {
            border: 1px solid #444;
            background-color: #222;
            color: #e0e0e0;
        }
        .bazaar-settings-item select {
            max-width: 250px;
        }
        .bazaar-number-input {
            -moz-appearance: textfield;
            appearance: textfield;
            width: 60px !important;
        }
        .bazaar-number-input::-webkit-outer-spin-button,
        .bazaar-number-input::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
        .bazaar-api-note {
            font-size: 12px;
            margin-top: 6px;
            color: #666;
            line-height: 1.4;
        }
        .dark-mode .bazaar-api-note {
            color: #aaa;
        }
        .bazaar-script-item {
            margin-bottom: 16px;
            padding-bottom: 16px;
            border-bottom: 1px solid #eee;
        }
        .dark-mode .bazaar-script-item {
            border-bottom: 1px solid #333;
        }
        .bazaar-script-item:last-child {
            border-bottom: none;
        }
        .bazaar-script-name {
            font-weight: bold;
            font-size: 16px;
            margin-bottom: 5px;
        }
        .bazaar-script-desc {
            margin-bottom: 8px;
            line-height: 1.4;
            color: #555;
        }
        .dark-mode .bazaar-script-desc {
            color: #bbb;
        }
        .bazaar-script-link {
            display: inline-block;
            margin-top: 5px;
            color: #2196F3;
            text-decoration: none;
        }
        .bazaar-script-link:hover {
            text-decoration: underline;
        }
        .bazaar-changelog {
            margin-bottom: 20px;
        }
        .bazaar-changelog-version {
            font-weight: bold;
            margin-bottom: 8px;
            font-size: 15px;
        }
        .bazaar-changelog-date {
            font-style: italic;
            color: #666;
            font-size: 13px;
            margin-bottom: 5px;
        }
        .dark-mode .bazaar-changelog-date {
            color: #aaa;
        }
        .bazaar-changelog-list {
            margin-left: 20px;
            margin-bottom: 15px;
        }
        .bazaar-changelog-item {
            margin-bottom: 5px;
            line-height: 1.4;
        }
        .bazaar-credits {
            margin-top: 20px;
            padding-top: 15px;
            border-top: 1px solid #eee;
        }
        .dark-mode .bazaar-credits {
            border-top: 1px solid #444;
        }
        .bazaar-credits h3 {
            font-size: 16px;
            margin-bottom: 10px;
        }
        .bazaar-credits p {
            line-height: 1.4;
            margin-bottom: 8px;
        }
        .bazaar-provider {
            font-weight: bold;
        }
        .bazaar-settings-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 12px;
            margin-top: 30px;
        }
        .bazaar-settings-save,
        .bazaar-settings-cancel {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            font-weight: bold;
        }
        .bazaar-settings-save {
            background-color: #4CAF50;
            color: white;
        }
        .bazaar-settings-save:hover {
            background-color: #45a049;
        }
        .bazaar-settings-cancel {
            background-color: #f5f5f5;
            color: #333;
            border: 1px solid #ddd;
        }
        .dark-mode .bazaar-settings-cancel {
            background-color: #333;
            color: #e0e0e0;
            border: 1px solid #444;
        }
        .bazaar-settings-cancel:hover {
            background-color: #e9e9e9;
        }
        .dark-mode .bazaar-settings-cancel:hover {
            background-color: #444 !important;
            border-color: #555 !important;
        }
        .bazaar-settings-footer {
            margin-top: 20px;
            font-size: 12px;
            color: #777;
            text-align: center;
            padding-top: 15px;
            border-top: 1px solid #eee;
        }
        .dark-mode .bazaar-settings-footer {
            color: #999;
            border-top: 1px solid #444;
        }
        .bazaar-settings-footer a {
            color: #2196F3;
            text-decoration: none;
        }
        .bazaar-settings-footer a:hover {
            text-decoration: underline;
        }
    `;
    document.head.appendChild(style);

    function fetchJSON(url, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url,
            onload: res => {
                try {
                    callback(JSON.parse(res.responseText));
                } catch (e) {
                    callback(null);
                }
            },
            onerror: () => callback(null)
        });
    }

    let cachedItemsData = null;
    function getStoredItems() {
        if (cachedItemsData === null) {
            try {
                cachedItemsData = JSON.parse(localStorage.getItem("tornItems") || "{}");
            } catch (e) {
                cachedItemsData = {};
                console.error("Stored items got funky:", e);
            }
        }
        return cachedItemsData;
    }

    function getCache(itemId) {
        try {
            const key = "tornBazaarCache_" + itemId,
                  cached = localStorage.getItem(key);
            if (cached) {
                const payload = JSON.parse(cached);
                if (Date.now() - payload.timestamp < CACHE_DURATION_MS) return payload.data;
            }
        } catch (e) {}
        return null;
    }

    function setCache(itemId, data) {
        try {
            localStorage.setItem("tornBazaarCache_" + itemId, JSON.stringify({ timestamp: Date.now(), data }));
        } catch (e) {}
    }

    function getRelativeTime(ts) {
        const diffSec = Math.floor((Date.now() - ts * 1000) / 1000);
        if (diffSec < 60) return diffSec + 's ago';
        if (diffSec < 3600) return Math.floor(diffSec / 60) + 'm ago';
        if (diffSec < 86400) return Math.floor(diffSec / 3600) + 'h ago';
        return Math.floor(diffSec / 86400) + 'd ago';
    }

    const svgTemplates = {
        rightArrow: `<svg viewBox="0 0 320 512"><path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg>`,
        leftArrow: `<svg viewBox="0 0 320 512"><path fill="currentColor" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/></svg>`,
        warningIcon: `<path fill="currentColor" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/>`,
        infoIcon: `<path fill="currentColor" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>`
    };

    function renderVirtualCards(infoContainer) {
        const cardContainer = infoContainer.querySelector('.bazaar-card-container'),
              scrollWrapper = infoContainer.querySelector('.bazaar-scroll-wrapper'),
              scrollLeft = scrollWrapper.scrollLeft,
              containerWidth = scrollWrapper.clientWidth;
        const visibleCards = Math.ceil(containerWidth / CARD_WIDTH),
              buffer = Math.max(2, Math.floor(visibleCards / 3)),
              totalItems = allListings.length;

        if (infoContainer.lastRenderScrollLeft !== undefined &&
            Math.abs(infoContainer.lastRenderScrollLeft - scrollLeft) < CARD_WIDTH * 0.3) {
            return;
        }
        infoContainer.lastRenderScrollLeft = scrollLeft;
        if (cardContainer.style.width !== (totalItems * CARD_WIDTH) + "px") {
            cardContainer.style.width = (totalItems * CARD_WIDTH) + "px";
        }

        let startIndex = Math.max(0, Math.floor(scrollLeft / CARD_WIDTH) - buffer),
            endIndex = Math.min(totalItems, Math.ceil((scrollLeft + containerWidth) / CARD_WIDTH) + buffer);
        const indicesToRender = new Set();
        for (let i = startIndex; i < endIndex; i++) indicesToRender.add(i);

        const existingCards = {};
        const cardsToRemove = [];
        Array.from(cardContainer.children).forEach(card => {
            if (card.className !== 'bazaar-listing-card') return;
            const index = parseInt(card.dataset.index, 10);
            if (isNaN(index)) {
                cardsToRemove.push(card);
            } else if (!indicesToRender.has(index)) {
                cardsToRemove.push(card);
            } else {
                existingCards[index] = card;
                indicesToRender.delete(index);
            }
        });
        cardsToRemove.forEach(card => card.remove());
        if (indicesToRender.size > 0) {
            const fragment = document.createDocumentFragment();
            indicesToRender.forEach(index => fragment.appendChild(createListingCard(allListings[index], index)));
            if (fragment.childElementCount > 0) cardContainer.appendChild(fragment);
        }

        const totalQuantity = allListings.reduce((sum, listing) => sum + listing.quantity, 0);

        const countElement = infoContainer.querySelector('.bazaar-listings-count');
        if (countElement) {
            countElement.textContent = `Showing bazaars ${startIndex + 1}-${endIndex} of ${totalItems} (${totalQuantity.toLocaleString()} items total)`;
        }
    }

    function createInfoContainer(itemName, itemId) {
        const container = document.createElement('div');
        container.className = 'bazaar-info-container';
        container.dataset.itemid = itemId;
        currentItemName = itemName;

        const header = document.createElement('div');
        header.className = 'bazaar-info-header';
        let marketValueText = "";
        try {
            const stored = getStoredItems();
            const match = Object.values(stored).find(item =>
                item.name && item.name.toLowerCase() === itemName.toLowerCase());
            if (match && match.market_value) {
                marketValueText = `Market Value: $${Number(match.market_value).toLocaleString()}`;
            }
        } catch (e) {
            console.error("Header market value error:", e);
        }
        header.textContent = `Bazaar Listings for ${itemName} (ID: ${itemId})`;
        if (marketValueText) {
            const span = document.createElement('span');
            span.style.marginLeft = '8px';
            span.style.fontSize = '14px';
            span.style.fontWeight = 'normal';
            span.style.color = currentDarkMode ? '#aaa' : '#666';
            span.textContent = `• ${marketValueText}`;
            header.appendChild(span);
        }
        container.appendChild(header);

        currentSortOrder = getSortOrderForKey(currentSortKey);
        const sortControls = document.createElement('div');
        sortControls.className = 'bazaar-sort-controls';
        sortControls.innerHTML = `
            <span>Sort by:</span>
            <select class="bazaar-sort-select">
                <option value="price" ${currentSortKey === "price" ? "selected" : ""}>Price</option>
                <option value="quantity" ${currentSortKey === "quantity" ? "selected" : ""}>Quantity</option>
                <option value="profit" ${currentSortKey === "profit" ? "selected" : ""}>Profit</option>
                <option value="updated" ${currentSortKey === "updated" ? "selected" : ""}>Last Updated</option>
            </select>
            <button class="bazaar-button bazaar-order-toggle">
                ${currentSortOrder === "asc" ? "Asc" : "Desc"}
            </button>
            <button class="bazaar-button bazaar-display-toggle" title="Toggle between percentage difference and total profit">
                ${displayMode === "percentage" ? "%" : "$"}
            </button>
        `;
        container.appendChild(sortControls);

        const scrollContainer = document.createElement('div');
        scrollContainer.className = 'bazaar-scroll-container';

        function createScrollArrow(direction) {
            const arrow = document.createElement('div');
            arrow.className = `bazaar-scroll-arrow ${direction}`;
            arrow.innerHTML = svgTemplates[direction === 'left' ? 'leftArrow' : 'rightArrow'];
            let isScrolling = false, scrollAnimationId = null, mouseDownTime = 0, isClickAction = false;
            const CLICK_THRESHOLD = 200;

            function smoothScroll() {
                if (!isScrolling) return;
                scrollWrapper.scrollLeft += (direction === 'left' ? -1.5 : 1.5);
                scrollAnimationId = requestAnimationFrame(smoothScroll);
            }
            arrow.addEventListener('mousedown', e => {
                e.preventDefault();
                mouseDownTime = Date.now();
                isClickAction = false;
                setTimeout(() => {
                    if (mouseDownTime && Date.now() - mouseDownTime >= CLICK_THRESHOLD) {
                        isScrolling = true;
                        smoothScroll();
                    }
                }, CLICK_THRESHOLD);
            });
            arrow.addEventListener('mouseup', () => {
                const holdDuration = Date.now() - mouseDownTime;
                isScrolling = false;
                if (scrollAnimationId) {
                    cancelAnimationFrame(scrollAnimationId);
                    scrollAnimationId = null;
                }
                if (holdDuration < CLICK_THRESHOLD && !isClickAction) {
                    isClickAction = true;
                    scrollWrapper.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });
                }
                mouseDownTime = 0;
            });
            arrow.addEventListener('mouseleave', () => {
                isScrolling = false;
                if (scrollAnimationId) {
                    cancelAnimationFrame(scrollAnimationId);
                    scrollAnimationId = null;
                }
                mouseDownTime = 0;
            });
            return arrow;
        }

        scrollContainer.appendChild(createScrollArrow('left'));
        const scrollWrapper = document.createElement('div');
        scrollWrapper.className = 'bazaar-scroll-wrapper';
        const cardContainer = document.createElement('div');
        cardContainer.className = 'bazaar-card-container';
        scrollWrapper.appendChild(cardContainer);
        scrollContainer.appendChild(scrollWrapper);
        scrollContainer.appendChild(createScrollArrow('right'));

        scrollWrapper.addEventListener('scroll', () => {
            if (!scrollWrapper.isScrolling) {
                scrollWrapper.isScrolling = true;
                requestAnimationFrame(function checkScroll() {
                    renderVirtualCards(container);
                    if (scrollWrapper.lastKnownScrollLeft === scrollWrapper.scrollLeft) {
                        renderVirtualCards(container);
                        scrollWrapper.isScrolling = false;
                    } else {
                        scrollWrapper.lastKnownScrollLeft = scrollWrapper.scrollLeft;
                        requestAnimationFrame(checkScroll);
                    }
                });
            }
        });
        container.appendChild(scrollContainer);

        const footerContainer = document.createElement('div');
        footerContainer.className = 'bazaar-footer-container';
        const listingsCount = document.createElement('div');
        listingsCount.className = 'bazaar-listings-count';
        listingsCount.textContent = 'Loading...';
        footerContainer.appendChild(listingsCount);

        const poweredBy = document.createElement('div');
        poweredBy.className = 'bazaar-powered-by';
        poweredBy.innerHTML = `
            <span>Powered by </span>
            <a href="https://tornpal.com/login?ref=1853324" target="_blank">TornPal</a>
            <span> &amp; </span>
            <a href="https://ironnerd.me/" target="_blank">IronNerd</a>
        `;
        const hasFiller = Boolean(localStorage.getItem("tornItems")),
              dismissed = localStorage.getItem('bazaarFillerPromptDismissed'),
              isDismissed = dismissed && (Date.now() - parseInt(dismissed)) < 259200000;
        if (!hasFiller && !isDismissed) {
            poweredBy.innerHTML += `<span> • </span>`;
            const fillerLink = document.createElement('a');
            fillerLink.href = "https://update.greasyfork.org/scripts/527925/Customizable%20Bazaar%20Filler.user.js";
            fillerLink.target = "_blank";
            fillerLink.textContent = "Install Bazaar Filler";
            fillerLink.className = "bazaar-filler-link";
            let hoverTimer = null, currentTooltip = null;
            fillerLink.addEventListener('mouseenter', e => {
                if (document.querySelector('.bazaar-filler-tooltip')) {
                    document.querySelector('.bazaar-filler-tooltip').remove();
                }
                hoverTimer = setTimeout(() => {
                    const tooltip = document.createElement('div');
                    tooltip.className = 'bazaar-filler-tooltip';
                    tooltip.textContent = 'Installing Bazaar Filler will show market value comparisons for each listing';
                    tooltip.style.position = 'absolute';
                    tooltip.style.backgroundColor = currentDarkMode ? '#444' : '#f9f9f9';
                    tooltip.style.color = currentDarkMode ? '#fff' : '#333';
                    tooltip.style.border = currentDarkMode ? '1px solid #666' : '1px solid #ccc';
                    tooltip.style.padding = '6px 10px';
                    tooltip.style.borderRadius = '4px';
                    tooltip.style.fontSize = '11px';
                    tooltip.style.maxWidth = '200px';
                    tooltip.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
                    tooltip.style.zIndex = '10000';
                    const rect = e.target.getBoundingClientRect();
                    tooltip.style.top = (rect.bottom + window.scrollY + 5) + 'px';
                    tooltip.style.left = (rect.left + window.scrollX) + 'px';
                    document.body.appendChild(tooltip);
                    currentTooltip = tooltip;
                }, 300);
            });
            fillerLink.addEventListener('mouseleave', () => {
                clearTimeout(hoverTimer);
                if (currentTooltip) {
                    currentTooltip.remove();
                    currentTooltip = null;
                }
            });
            poweredBy.appendChild(fillerLink);
            const hideSpan = document.createElement('span');
            hideSpan.className = 'dismiss-filler';
            hideSpan.textContent = " [hide]";
            hideSpan.style.cursor = 'pointer';
            hideSpan.style.fontSize = '10px';
            hideSpan.style.color = currentDarkMode ? '#999' : '#666';
            hideSpan.style.verticalAlign = 'super';
            hideSpan.addEventListener('click', e => {
                e.preventDefault();
                fillerLink.style.display = 'none';
                hideSpan.style.display = 'none';
                const bullet = poweredBy.querySelector('span:last-of-type');
                if (bullet && bullet.textContent.includes('•')) bullet.style.display = 'none';
                localStorage.setItem('bazaarFillerPromptDismissed', Date.now());
            });
            poweredBy.appendChild(hideSpan);
        }
        footerContainer.appendChild(poweredBy);
        container.appendChild(footerContainer);
        return container;
    }

    function createListingCard(listing, index) {
        const card = document.createElement('div');
        card.className = 'bazaar-listing-card';
        card.dataset.index = index;
        card.style.position = "absolute";
        card.style.left = (index * CARD_WIDTH) + "px";
        card.style.width = CARD_WIDTH + "px";

        let visitedColor = '#00aaff';
        try {
            const key = `visited_${listing.item_id}_${listing.player_id}`;
            const data = JSON.parse(localStorage.getItem(key));
            if (data && data.lastClickedUpdated >= listing.updated) {
                visitedColor = 'purple';
            }
        } catch (e) {}

        card.innerHTML = `
            <div>
                <div style="display:flex; align-items:center; gap:5px; margin-bottom:6px; flex-wrap:wrap">
                    <a href="https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/"
                       data-visited-key="visited_${listing.item_id}_${listing.player_id}"
                       data-updated="${listing.updated}"
                       style="font-weight:bold; color:${visitedColor}; text-decoration:underline;">
                       Player: ${listing.player_id}
                    </a>
                </div>
                <div>
                    <div style="margin-bottom:2px">
                        <strong>Price:</strong> <span style="word-break:break-all;">$${listing.price.toLocaleString()}</span>
                    </div>
                    <div style="display:flex; align-items:center">
                        <strong>Qty:</strong> <span style="margin-left:4px">${listing.quantity}</span>
                        <span style="margin-left:auto">${getPriceComparisonHtml(listing.price, listing.quantity)}</span>
                    </div>
                </div>
            </div>
            <div style="margin-top:6px">
                <div class="bazaar-listing-footnote">Updated: ${getRelativeTime(listing.updated)}</div>
                <div class="bazaar-listing-source">Source: ${listing.source === "ironnerd" ? "IronNerd" : (listing.source === "bazaar" ? "TornPal" : listing.source)}</div>
            </div>
        `;
        const playerLink = card.querySelector('a');
        playerLink.addEventListener('click', () => {
            localStorage.setItem(playerLink.dataset.visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';
        });

        const priceComparison = card.querySelector('.bazaar-price-comparison');
        if (priceComparison) {
            const tooltip = document.createElement('div');
            tooltip.className = 'bazaar-profit-tooltip';
            tooltip.style.display = 'none';
            tooltip.style.position = 'absolute';
            tooltip.style.zIndex = '10000';
            tooltip.style.padding = '8px 12px';
            tooltip.style.borderRadius = '4px';
            tooltip.style.fontSize = '12px';
            tooltip.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
            tooltip.style.maxWidth = '300px';
            tooltip.style.backgroundColor = currentDarkMode ? '#333' : '#fff';
            tooltip.style.color = currentDarkMode ? '#fff' : '#333';
            tooltip.style.border = currentDarkMode ? '1px solid #555' : '1px solid #ddd';
            tooltip.innerHTML = priceComparison.getAttribute('data-tooltip');

            priceComparison.addEventListener('mouseenter', e => {
                document.body.appendChild(tooltip);
                tooltip.style.display = 'block';
                const rect = e.target.getBoundingClientRect();
                tooltip.style.left = (rect.left + window.scrollX) + 'px';
                tooltip.style.top = (rect.bottom + window.scrollY + 5) + 'px';
                const tooltipRect = tooltip.getBoundingClientRect();
                if (tooltipRect.right > window.innerWidth) {
                    tooltip.style.left = (window.innerWidth - tooltipRect.width - 10) + 'px';
                }
            });

            priceComparison.addEventListener('mouseleave', () => {
                if (tooltip.parentNode) tooltip.parentNode.removeChild(tooltip);
            });
        }

        return card;
    }

    function getPriceComparisonHtml(listingPrice, quantity) {
        try {
            const stored = getStoredItems();
            const match = Object.values(stored).find(item =>
                item.name && item.name.toLowerCase() === currentItemName.toLowerCase());
            if (match && match.market_value) {
                const marketValue = Number(match.market_value),
                      priceDiff = listingPrice - marketValue,
                      percentDiff = ((listingPrice / marketValue) - 1) * 100,
                      listingFee = scriptSettings.listingFee || 0,
                      totalCost = listingPrice * quantity,
                      potentialRevenue = marketValue * quantity,
                      feeAmount = Math.ceil(potentialRevenue * (listingFee / 100)),
                      potentialProfit = potentialRevenue - totalCost - feeAmount,
                      minResellPrice = Math.ceil(listingPrice / (1 - (listingFee / 100)));

                let color, text;
                const absProfit = Math.abs(potentialProfit);
                let abbrevValue = potentialProfit < 0 ? '-' : '';
                if (absProfit >= 1000000) {
                    abbrevValue += '$' + (absProfit / 1000000).toFixed(1).replace(/\.0$/, '') + 'm';
                } else if (absProfit >= 1000) {
                    abbrevValue += '$' + (absProfit / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
                } else {
                    abbrevValue += '$' + absProfit;
                }

                if (potentialProfit > 0) {
                    color = currentDarkMode ? '#7fff7f' : '#006400';
                    text = displayMode === "percentage" ? `(${percentDiff.toFixed(1)}%)` : `(${abbrevValue})`;
                } else if (potentialProfit < 0) {
                    color = currentDarkMode ? '#ff7f7f' : '#8b0000';
                    text = displayMode === "percentage" ? `(+${percentDiff.toFixed(1)}%)` : `(${abbrevValue})`;
                } else {
                    color = currentDarkMode ? '#cccccc' : '#666666';
                    text = displayMode === "percentage" ? `(0%)` : `($0)`;
                }
                const tooltipContent = `
                    <div style="font-weight:bold; font-size:12px; margin-bottom:6px">
                        ${potentialProfit >= 0 ? 'PROFIT' : 'LOSS'}: ${potentialProfit >= 0 ? '$' : '-$'}${Math.abs(potentialProfit).toLocaleString()}
                    </div>
                    <hr style="margin: 4px 0; border-color: ${currentDarkMode ? '#444' : '#ddd'}">
                    <div>Buy: $${listingPrice.toLocaleString()} × ${quantity} item${quantity > 1 ? 's' : ''}</div>
                    <div>Market Value: $${marketValue.toLocaleString()}</div>
                    ${listingFee > 0 ? `<div>Resale Fee: ${listingFee}% ($${feeAmount.toLocaleString()})</div>` : ''}
                    ${listingFee > 0 ? `<div style="margin-top:6px"><strong>Min. Resell Price: $${minResellPrice.toLocaleString()}</strong></div>` : ''}
                `;
                const span = document.createElement('span');
                span.style.fontWeight = 'bold';
                span.style.fontSize = '10px';
                span.style.padding = '0 4px';
                span.style.borderRadius = '2px';
                span.style.color = color;
                span.style.cursor = 'help';
                span.style.whiteSpace = 'nowrap';
                span.textContent = text;
                span.className = 'bazaar-price-comparison';
                span.setAttribute('data-tooltip', tooltipContent);
                return span.outerHTML;
            }
        } catch (e) {
            console.error("Price comparison error:", e);
        }
        return '';
    }

    function sortListings(listings) {
        return listings.slice().sort((a, b) => {
            let diff;
            if (currentSortKey === "profit") {
                try {
                    const stored = getStoredItems();
                    const match = Object.values(stored).find(item =>
                        item.name && item.name.toLowerCase() === currentItemName.toLowerCase());
                    if (match && match.market_value) {
                        const marketValue = Number(match.market_value),
                              fee = scriptSettings.listingFee || 0,
                              aProfit = (marketValue * a.quantity) - (a.price * a.quantity) - Math.ceil((marketValue * a.quantity) * (fee / 100)),
                              bProfit = (marketValue * b.quantity) - (b.price * b.quantity) - Math.ceil((marketValue * b.quantity) * (fee / 100));
                        diff = aProfit - bProfit;
                    } else {
                        diff = a.price - b.price;
                    }
                } catch (e) {
                    console.error("Profit sort error:", e);
                    diff = a.price - b.price;
                }
            } else {
                diff = currentSortKey === "price" ? a.price - b.price :
                      currentSortKey === "quantity" ? a.quantity - b.quantity :
                      a.updated - b.updated;
            }
            return currentSortOrder === "asc" ? diff : -diff;
        });
    }

    function updateInfoContainer(wrapper, itemId, itemName) {
        let infoContainer = document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);
        if (!infoContainer) {
            infoContainer = createInfoContainer(itemName, itemId);
            wrapper.insertBefore(infoContainer, wrapper.firstChild);
        } else {
            const header = infoContainer.querySelector('.bazaar-info-header');
            if (header) {
                header.textContent = `Bazaar Listings for ${itemName} (ID: ${itemId})`;
            }
        }

        const cardContainer = infoContainer.querySelector('.bazaar-card-container');
        const countElement = infoContainer.querySelector('.bazaar-listings-count');

        const updateListingsCount = (text) => {
            if (countElement) {
                countElement.textContent = text;
            }
        };

        const showEmptyState = (isError) => {
            if (cardContainer) {
                cardContainer.innerHTML = '';
                renderMessageInContainer(cardContainer, isError);
            }
            updateListingsCount(isError ? 'API Error - Check back later' : 'No listings available');
        };

        if (cardContainer) {
            cardContainer.innerHTML = '<div style="padding:10px; text-align:center; width:100%;">Loading bazaar listings...</div>';
        }

        const cachedData = getCache(itemId);
        if (cachedData) {
            allListings = sortListings(cachedData.listings);
            if (allListings.length === 0) {
                showEmptyState(false);
            } else {
                renderVirtualCards(infoContainer);
            }
            return;
        }

        let listings = [], responses = 0, apiErrors = false;
        function processResponse(newListings, error) {
            if (error) {
                apiErrors = true;
            }
            if (Array.isArray(newListings)) {
                newListings.forEach(newItem => {
                    const normalized = newItem.user_id !== undefined ? {
                        item_id: newItem.item_id,
                        player_id: newItem.user_id,
                        quantity: newItem.quantity,
                        price: newItem.price,
                        updated: newItem.last_updated,
                        source: "ironnerd"
                    } : newItem;

                    const duplicate = listings.find(item =>
                        item.player_id === normalized.player_id &&
                        item.price === normalized.price &&
                        item.quantity === normalized.quantity
                    );
                    if (duplicate) {
                        duplicate.source = duplicate.source === normalized.source ?
                            duplicate.source : "TornPal & IronNerd";
                    } else {
                        listings.push(normalized);
                    }
                });
            }

            responses++;
            if (responses === 2) {
                setCache(itemId, { listings });
                if (listings.length === 0) {
                    showEmptyState(apiErrors);
                } else {
                    allListings = sortListings(listings);
                    renderVirtualCards(infoContainer);
                }
            }
        }

        fetchJSON(`https://tornpal.com/api/v1/markets/clist/${itemId}?comment=wBazaarMarket`, data => {
            processResponse(data && Array.isArray(data.listings) ? data.listings.filter(l => l.source === "bazaar") : [], data === null);
        });
        fetchJSON(`https://www.ironnerd.me/get_bazaar_items/${itemId}?comment=wBazaarMarket`, data => {
            processResponse(data && Array.isArray(data.bazaar_items) ? data.bazaar_items : [], data === null);
        });
    }

    function renderMessageInContainer(container, isApiError) {
        const messageContainer = document.createElement('div');
        messageContainer.style.cssText = 'display:flex; flex-direction:column; align-items:center; justify-content:center; padding:20px; text-align:center; width:100%; height:70px;';

        const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        iconSvg.setAttribute("viewBox", "0 0 512 512");
        iconSvg.setAttribute("width", "24");
        iconSvg.setAttribute("height", "24");
        iconSvg.style.marginBottom = "10px";

        const textDiv = document.createElement('div');

        if (isApiError) {
            iconSvg.innerHTML = svgTemplates.warningIcon;
            textDiv.textContent = "Unable to load bazaar listings. Please try again later.";
            textDiv.style.cssText = currentDarkMode ? 'color:#ff9999; font-weight:bold;' : 'color:#cc0000; font-weight:bold;';
        } else {
            iconSvg.innerHTML = svgTemplates.infoIcon;
            textDiv.textContent = "No bazaar listings available for this item.";
        }

        messageContainer.appendChild(iconSvg);
        messageContainer.appendChild(textDiv);
        container.appendChild(messageContainer);
    }

    function processSellerWrapper(wrapper) {
        if (!wrapper || wrapper.classList.contains('bazaar-info-container')) return;
        const itemTile = wrapper.previousElementSibling;
        if (!itemTile) return;
        const nameEl = itemTile.querySelector('.name___ukdHN'),
              btn = itemTile.querySelector('button[aria-controls^="wai-itemInfo-"]');
        if (nameEl && btn) {
            const itemName = nameEl.textContent.trim();
            const idParts = btn.getAttribute('aria-controls').split('-');
            const itemId = idParts[idParts.length - 1];
            updateInfoContainer(wrapper, itemId, itemName);
        }
    }

    function processMobileSellerList() {
        if (window.innerWidth >= 784) return;
        const sellerList = document.querySelector('ul.sellerList___e4C9_');
        if (!sellerList) {
            const existing = document.querySelector('.bazaar-info-container');
            if (existing) existing.remove();
            return;
        }
        const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT');
        const itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
        const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"]');
        let itemId = "unknown";
        if (btn) {
            const parts = btn.getAttribute('aria-controls').split('-');
            itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1];
        }
        if (document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`)) return;
        const infoContainer = createInfoContainer(itemName, itemId);
        sellerList.parentNode.insertBefore(infoContainer, sellerList);
        updateInfoContainer(infoContainer, itemId, itemName);
    }

    function processAllSellerWrappers(root = document.body) {
        if (window.innerWidth < 784) return;
        root.querySelectorAll('[class*="sellerListWrapper"]').forEach(wrapper => processSellerWrapper(wrapper));
    }

    processAllSellerWrappers();
    processMobileSellerList();

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (window.innerWidth < 784 && node.matches('ul.sellerList___e4C9_')) {
                        processMobileSellerList();
                    } else if (node.matches('[class*="sellerListWrapper"]')) {
                        processSellerWrapper(node);
                        processAllSellerWrappers(node);
                    } else {
                        processAllSellerWrappers(node);
                    }
                }
            });
            mutation.removedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE && node.matches('ul.sellerList___e4C9_') && window.innerWidth < 784) {
                    const container = document.querySelector('.bazaar-info-container');
                    if (container) container.remove();
                }
            });
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });

    const bodyObserver = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.attributeName === 'class') {
                currentDarkMode = document.body.classList.contains('dark-mode');
            }
        });
    });
    bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });

    if (window.location.href.includes("bazaar.php")) {
        function scrollToTargetItem() {
            const params = new URLSearchParams(window.location.search);
            const targetItemId = params.get("itemId"), highlight = params.get("highlight");
            if (!targetItemId || highlight !== "1") return;

            function removeHighlightParam() {
                params.delete("highlight");
                history.replaceState({}, "", window.location.pathname + "?" + params.toString() + window.location.hash);
            }
            function showToast(message) {
                const toast = document.createElement('div');
                toast.textContent = message;
                toast.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background-color:rgba(0,0,0,0.7); color:white; padding:10px 20px; border-radius:5px; z-index:100000; font-size:14px;';
                document.body.appendChild(toast);
                setTimeout(() => {
                    toast.remove();
                }, 3000);
            }
            function findItemCard() {
                const img = document.querySelector(`img[src*="/images/items/${targetItemId}/"]`);
                return img ? img.closest('.item___GYCYJ') : null;
            }

            const scrollInterval = setInterval(() => {
                const card = findItemCard();
                if (card) {
                    clearInterval(scrollInterval);
                    removeHighlightParam();
                    card.classList.add("green-outline", "pop-flash");
                    card.scrollIntoView({ behavior: "smooth", block: "center" });
                    setTimeout(() => {
                        card.classList.remove("pop-flash");
                    }, 800);
                } else {
                    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
                        showToast("Item not found on this page.");
                        removeHighlightParam();
                        clearInterval(scrollInterval);
                    } else {
                        window.scrollBy({ top: 300, behavior: 'auto' });
                    }
                }
            }, 50);
        }
        function waitForItems() {
            const container = document.querySelector('.ReactVirtualized__Grid__innerScrollContainer');
            if (container && container.childElementCount > 0) {
                scrollToTargetItem();
            } else {
                setTimeout(waitForItems, 500);
            }
        }
        waitForItems();
    }

    function dailyCleanup() {
        const lastCleanup = localStorage.getItem("lastDailyCleanup"),
              oneDay = 24 * 60 * 60 * 1000,
              now = Date.now();
        if (!lastCleanup || (now - parseInt(lastCleanup, 10)) > oneDay) {
            const sevenDays = 7 * 24 * 60 * 60 * 1000;
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key && (key.startsWith("visited_") || key.startsWith("tornBazaarCache_"))) {
                    try {
                        const val = JSON.parse(localStorage.getItem(key));
                        let ts = null;
                        if (key.startsWith("visited_") && val && val.lastClickedUpdated) {
                            // Use lastClickedUpdated consistently
                            ts = val.lastClickedUpdated;
                        } else if (key.startsWith("tornBazaarCache_") && val && val.timestamp) {
                            ts = val.timestamp;
                        } else {
                            localStorage.removeItem(key);
                        }
                        if (ts !== null && (now - ts) > sevenDays) {
                            localStorage.removeItem(key);
                        }
                    } catch (e) {
                        localStorage.removeItem(key);
                    }
                }
            }
            localStorage.setItem("lastDailyCleanup", now.toString());
        }
    }
    dailyCleanup();

    document.body.addEventListener('click', event => {
        const container = event.target.closest('.bazaar-info-container');
        if (!container) return;

        if (event.target.matches('.bazaar-order-toggle')) {
            currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
            event.target.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
            performSort(container);
        }
        if (event.target.matches('.bazaar-display-toggle')) {
            displayMode = displayMode === "percentage" ? "profit" : "percentage";
            event.target.textContent = displayMode === "percentage" ? "%" : "$";
            scriptSettings.defaultDisplayMode = displayMode;
            saveSettings();
            const cardContainer = container.querySelector('.bazaar-card-container');
            if (cardContainer) {
                cardContainer.innerHTML = '';
                container.lastRenderScrollLeft = undefined;
                renderVirtualCards(container);
                const scrollWrapper = container.querySelector('.bazaar-scroll-wrapper');
                if (scrollWrapper) {
                    const currentScroll = scrollWrapper.scrollLeft;
                    scrollWrapper.scrollLeft = currentScroll + 1;
                    setTimeout(() => {
                        scrollWrapper.scrollLeft = currentScroll;
                    }, 10);
                }
            }
        }
    });

    document.body.addEventListener('change', event => {
        const container = event.target.closest('.bazaar-info-container');
        if (!container) return;
        if (event.target.matches('.bazaar-sort-select')) {
            const newSortKey = event.target.value;
            if (newSortKey !== currentSortKey) {
                currentSortKey = newSortKey;
                currentSortOrder = getSortOrderForKey(currentSortKey);
                const orderToggle = container.querySelector('.bazaar-order-toggle');
                if (orderToggle) {
                    orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
                }
            } else {
                currentSortKey = newSortKey;
            }
            performSort(container);
        }
    });

    function performSort(container) {
        allListings = sortListings(allListings);
        const cardContainer = container.querySelector('.bazaar-card-container');
        if (cardContainer) {
            cardContainer.innerHTML = '';
            const scrollWrapper = container.querySelector('.bazaar-scroll-wrapper');
            if (scrollWrapper) scrollWrapper.scrollLeft = 0;
            container.lastRenderScrollLeft = undefined;
            void cardContainer.offsetWidth;
            setTimeout(() => {
                renderVirtualCards(container);
            }, 10);
        }
    }

    function addSettingsMenuItem() {
        const menu = document.querySelector('.settings-menu');
        if (!menu || document.querySelector('.bazaar-settings-button')) return;
        const li = document.createElement('li');
        li.className = 'link bazaar-settings-button';
        const a = document.createElement('a');
        a.href = '#';
        const iconDiv = document.createElement('div');
        iconDiv.className = 'icon-wrapper';
        const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svgIcon.setAttribute('class', 'default');
        svgIcon.setAttribute('fill', '#fff');
        svgIcon.setAttribute('stroke', 'transparent');
        svgIcon.setAttribute('stroke-width', '0');
        svgIcon.setAttribute('width', '16');
        svgIcon.setAttribute('height', '16');
        svgIcon.setAttribute('viewBox', '0 0 640 512');
        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', 'M36.8 192l566.3 0c20.3 0 36.8-16.5 36.8-36.8c0-7.3-2.2-14.4-6.2-20.4L558.2 21.4C549.3 8 534.4 0 518.3 0L121.7 0c-16 0-31 8-39.9 21.4L6.2 134.7c-4 6.1-6.2 13.2-6.2 20.4C0 175.5 16.5 192 36.8 192zM64 224l0 160 0 80c0 26.5 21.5 48 48 48l224 0c26.5 0 48-21.5 48-48l0-80 0-160-64 0 0 160-192 0 0-160-64 0zm448 0l0 256c0 17.7 14.3 32 32 32s32-14.3 32-32l0-256-64 0z');
        const span = document.createElement('span');
        span.textContent = 'Bazaar Settings';

        svgIcon.appendChild(path);
        iconDiv.appendChild(svgIcon);
        a.appendChild(iconDiv);
        a.appendChild(span);
        li.appendChild(a);
        a.addEventListener('click', e => {
            e.preventDefault();
            document.body.click();
            openSettingsModal();
        });
        const logoutButton = menu.querySelector('li.logout');
        if (logoutButton) {
            menu.insertBefore(li, logoutButton);
        } else {
            menu.appendChild(li);
        }
    }

    function openSettingsModal() {
        const overlay = document.createElement("div");
        overlay.className = "bazaar-modal-overlay";
        const modal = document.createElement("div");
        modal.className = "bazaar-settings-modal";
        modal.innerHTML = `
            <div class="bazaar-settings-title">Bazaar Listings Settings</div>
            <div class="bazaar-tabs">
                <div class="bazaar-tab active" data-tab="settings">Settings</div>
                <div class="bazaar-tab" data-tab="scripts">Other Scripts</div>
            </div>
            <div class="bazaar-tab-content active" id="tab-settings">
                <div class="bazaar-settings-group">
                    <div class="bazaar-settings-item">
                        <label for="bazaar-api-key">Torn API Key (Optional)</label>
                        <input type="text" id="bazaar-api-key" value="${scriptSettings.apiKey || ''}" placeholder="Enter your API key here">
                        <div class="bazaar-api-note">
                            Providing an API key enables market value comparison. Your key stays local.<br>
                            Alternatively, install <a href="https://greasyfork.org/en/scripts/527925-customizable-bazaar-filler" target="_blank">Bazaar Filler</a>, which works seamlessly with this script (Only ONE API call is made each day!)
                        </div>
                    </div>
                    <div class="bazaar-settings-item">
                        <label for="bazaar-default-sort">Default Sort</label>
                        <select id="bazaar-default-sort">
                            <option value="price" ${scriptSettings.defaultSort === 'price' ? 'selected' : ''}>Price</option>
                            <option value="quantity" ${scriptSettings.defaultSort === 'quantity' ? 'selected' : ''}>Quantity</option>
                            <option value="profit" ${scriptSettings.defaultSort === 'profit' ? 'selected' : ''}>Profit</option>
                            <option value="updated" ${scriptSettings.defaultSort === 'updated' ? 'selected' : ''}>Last Updated</option>
                        </select>
                        <div class="bazaar-api-note">
                            Choose how listings are sorted: Price, Quantity, Profit, or Last Updated.
                        </div>
                    </div>
                    <div class="bazaar-settings-item">
                        <label for="bazaar-default-order">Default Order</label>
                        <select id="bazaar-default-order">
                            <option value="asc" ${scriptSettings.defaultOrder === 'asc' ? 'selected' : ''}>Ascending</option>
                            <option value="desc" ${scriptSettings.defaultOrder === 'desc' ? 'selected' : ''}>Descending</option>
                        </select>
                        <div class="bazaar-api-note">
                            Choose the sorting direction.
                        </div>
                    </div>
                    <div class="bazaar-settings-item">
                        <label for="bazaar-listing-fee">Listing Fee (%)</label>
                        <input type="number" id="bazaar-listing-fee" class="bazaar-number-input" value="${scriptSettings.listingFee || 0}" min="0" max="100" step="1">
                        <div class="bazaar-api-note">
                            Set the fee percentage when listing items. (e.g., 10% fee means $10,000 on $100,000)
                        </div>
                    </div>
                    <div class="bazaar-settings-item">
                        <label for="bazaar-default-display">Default Display Mode</label>
                        <select id="bazaar-default-display">
                            <option value="percentage" ${scriptSettings.defaultDisplayMode === 'percentage' ? 'selected' : ''}>Percentage Difference</option>
                            <option value="profit" ${scriptSettings.defaultDisplayMode === 'profit' ? 'selected' : ''}>Potential Profit</option>
                        </select>
                        <div class="bazaar-api-note">
                            Choose whether to display price comparisons as a percentage or in dollars.
                        </div>
                    </div>
                </div>
            </div>
            <div class="bazaar-tab-content" id="tab-scripts" style="max-height: 350px; overflow-y: auto;">
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Customizable Bazaar Filler</div>
                    <div class="bazaar-script-desc">Auto-fills bazaar item quantities and prices.</div>
                    <a href="https://greasyfork.org/en/scripts/527925-customizable-bazaar-filler" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Torn Item Market Highlighter</div>
                    <div class="bazaar-script-desc">Highlights items based on rules and prices.</div>
                    <a href="https://greasyfork.org/en/scripts/471848-torn-item-market-highlighter-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Torn Item Market Max Quantity Calculator</div>
                    <div class="bazaar-script-desc">Calculates the max quantity you can buy.</div>
                    <a href="https://greasyfork.org/en/scripts/472073-torn-item-market-max-quantity-calculator-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Enhanced Chat Buttons V2</div>
                    <div class="bazaar-script-desc">Improves chat with extra buttons.</div>
                    <a href="https://greasyfork.org/en/scripts/458616-torn-com-enhanced-chat-buttons-v2-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Market Item Locker</div>
                    <div class="bazaar-script-desc">Lock items when listing to avoid accidental sales.</div>
                    <a href="https://greasyfork.org/en/scripts/475626-torn-market-item-locker-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Market Quick Remove</div>
                    <div class="bazaar-script-desc">Quickly remove items from your listings.</div>
                    <a href="https://greasyfork.org/en/scripts/472078-torn-market-quick-remove-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
                <div class="bazaar-script-item">
                    <div class="bazaar-script-name">Trade Chat Timer on Button</div>
                    <div class="bazaar-script-desc">Adds a timer to the trade chat button.</div>
                    <a href="https://greasyfork.org/en/scripts/459550-torn-trade-chat-timer-on-button-js" target="_blank" class="bazaar-script-link">Install from Greasy Fork</a>
                </div>
            </div>
            <div class="bazaar-settings-buttons">
                <button class="bazaar-settings-save">Save</button>
                <button class="bazaar-settings-cancel">Cancel</button>
            </div>
            <div class="bazaar-settings-footer">
                <p>This script uses data from <a href="https://tornpal.com" target="_blank">TornPal</a> and <a href="https://www.ironnerd.me/torn/" target="_blank">IronNerd</a>.</p>
                <p>Created by <a href="https://www.torn.com/profiles.php?XID=1853324" target="_blank">Weav3r [1853324]</a></p>
            </div>
        `;
        overlay.appendChild(modal);

        const tabs = modal.querySelectorAll('.bazaar-tab');
        tabs.forEach(tab => {
            tab.addEventListener('click', function () {
                tabs.forEach(t => t.classList.remove('active'));
                this.classList.add('active');
                modal.querySelectorAll('.bazaar-tab-content').forEach(content => content.classList.remove('active'));
                document.getElementById(`tab-${this.getAttribute('data-tab')}`).classList.add('active');
            });
        });

        modal.querySelector('.bazaar-settings-save').addEventListener('click', () => {
            saveSettingsFromModal(modal);
            overlay.remove();
        });
        modal.querySelector('.bazaar-settings-cancel').addEventListener('click', () => {
            overlay.remove();
        });
        overlay.addEventListener('click', e => {
            if (e.target === overlay) overlay.remove();
        });
        document.body.appendChild(overlay);
    }

    function saveSettingsFromModal(modal) {
        scriptSettings = {
            apiKey: modal.querySelector('#bazaar-api-key').value.trim(),
            defaultSort: modal.querySelector('#bazaar-default-sort').value,
            defaultOrder: modal.querySelector('#bazaar-default-order').value,
            listingFee: Math.round(parseFloat(modal.querySelector('#bazaar-listing-fee').value) || 0),
            defaultDisplayMode: modal.querySelector('#bazaar-default-display').value
        };
        if (scriptSettings.listingFee < 0) scriptSettings.listingFee = 0;
        if (scriptSettings.listingFee > 100) scriptSettings.listingFee = 100;
        currentSortKey = scriptSettings.defaultSort;
        currentSortOrder = scriptSettings.defaultOrder;
        displayMode = scriptSettings.defaultDisplayMode;
        saveSettings();

        document.querySelectorAll('.bazaar-info-container').forEach(container => {
            const sortSelect = container.querySelector('.bazaar-sort-select');
            if (sortSelect) sortSelect.value = currentSortKey;
            const orderToggle = container.querySelector('.bazaar-order-toggle');
            if (orderToggle) orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
            const displayToggle = container.querySelector('.bazaar-display-toggle');
            if (displayToggle) displayToggle.textContent = displayMode === "percentage" ? "%" : "$";
            performSort(container);
        });

        if (scriptSettings.apiKey) {
            fetchTornItems();
        }
    }

    function fetchTornItems() {
        const stored = localStorage.getItem("tornItems"),
              lastUpdated = localStorage.getItem("lastTornItemsUpdate") || 0,
              now = Date.now(),
              oneDayMs = 24 * 60 * 60 * 1000,
              lastUTC = new Date(parseInt(lastUpdated)).toISOString().split('T')[0],
              todayUTC = new Date().toISOString().split('T')[0];
        if (scriptSettings.apiKey && (!stored || lastUTC < todayUTC || (now - lastUpdated) >= oneDayMs)) {
            fetch(`https://api.torn.com/torn/?key=${scriptSettings.apiKey}&selections=items&comment=wBazaars`)
                .then(r => r.json())
                .then(data => {
                    if (!data.items) {
                        console.error("Failed to fetch Torn items. Check your API key or rate limit.");
                        return;
                    }
                    const filtered = {};
                    for (let [id, item] of Object.entries(data.items)) {
                        if (item.tradeable) {
                            filtered[id] = { name: item.name, market_value: item.market_value };
                        }
                    }
                    localStorage.setItem("tornItems", JSON.stringify(filtered));
                    localStorage.setItem("lastTornItemsUpdate", now.toString());
                })
                .catch(err => console.error("Error fetching Torn items:", err));
        }
    }

    function observeUserMenu() {
        const menuObserver = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                if (mutation.addedNodes.length > 0) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('settings-menu')) {
                            addSettingsMenuItem();
                            break;
                        }
                    }
                }
            });
        });
        menuObserver.observe(document.body, { childList: true, subtree: true });
        if (document.querySelector('.settings-menu')) {
            addSettingsMenuItem();
        }
    }

    function getSortOrderForKey(key) {
        return key === "price" ? "asc" : "desc";
    }

    observeUserMenu();
})();