Bazaars in Item Market powered by TornPal and IronNerd

Displays bazaar listings with sorting controls via TornPal & IronNerd

Pada tanggal 04 Maret 2025. Lihat %(latest_version_link).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==/UserScript==

// ==UserScript==
// @name         Bazaars in Item Market powered by TornPal and IronNerd
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Displays bazaar listings with sorting controls via TornPal & IronNerd
// @author       Weav3r
// @match        https://www.torn.com/*
// @grant        GM_xmlhttpRequest
// @connect      tornpal.com
// @connect      www.ironnerd.me
// @updateURL    https://greatest.deepsurf.us/en/scripts/527616/code/Bazaars%20in%20Item%20Market%20powered%20by%20TornPal%20and%20IronNerd.user.js
// @downloadURL  https://greatest.deepsurf.us/en/scripts/527616/code/Bazaars%20in%20Item%20Market%20powered%20by%20TornPal%20and%20IronNerd.user.js
// @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",
        isMobileView = false;

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

    function checkMobileView() {
        isMobileView = window.innerWidth < 784 ||
                       /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
        return isMobileView;
    }

    checkMobileView();

    window.addEventListener('resize', function() {
        checkMobileView();
        processMobileSellerList();
    });

    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");
            localStorage.setItem("bazaarLinkBehavior", scriptSettings.linkBehavior || "new_tab");
        } 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: 99999;
        }

        .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;
            max-height: 90vh;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            position: relative;
            z-index: 100000;
            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;
            flex-wrap: wrap;
        }
        .dark-mode .bazaar-tabs {
            border-bottom: 1px solid #444;
        }
        .bazaar-tab {
            padding: 10px 16px;
            cursor: pointer;
            margin-right: 5px;
            margin-bottom: 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;
        }
        @media (max-width: 600px) {
            .bazaar-settings-modal {
                padding: 16px;
                width: 100%;
                max-width: 100%;
                border-radius: 0;
                max-height: 100vh;
            }
            .bazaar-settings-title {
                font-size: 18px;
                margin-bottom: 16px;
            }
            .bazaar-tab {
                padding: 8px 12px;
                font-size: 14px;
            }
            .bazaar-settings-item label {
                font-size: 13px;
            }
            .bazaar-settings-item input[type="text"],
            .bazaar-settings-item select,
            .bazaar-number-input {
                padding: 6px 10px;
                font-size: 13px;
                max-width: 100%;
            }
            .bazaar-settings-item {
                margin-bottom: 14px;
            }
            .bazaar-settings-save,
            .bazaar-settings-cancel {
                padding: 6px 12px;
                font-size: 13px;
            }
            .bazaar-api-note {
                font-size: 11px;
            }
            .bazaar-settings-buttons {
                margin-top: 20px;
            }
            .bazaar-settings-footer {
                font-size: 11px;
            }
        }
    `;
    document.head.appendChild(style);

    function fetchJSON(url, callback) {
        let retryCount = 0;
        const MAX_RETRIES = 2;
        const TIMEOUT_MS = 10000;
        const RETRY_DELAY_MS = 2000;

        function attemptFetch() {
            let timeoutId = setTimeout(() => {
                console.warn(`Request to ${url} timed out, ${retryCount < MAX_RETRIES ? 'retrying...' : 'giving up.'}`);
                if (retryCount < MAX_RETRIES) {
                    retryCount++;
                    setTimeout(attemptFetch, RETRY_DELAY_MS);
                } else {
                    callback(null);
                }
            }, TIMEOUT_MS);

            GM_xmlhttpRequest({
                method: 'GET',
                url,
                timeout: TIMEOUT_MS,
                onload: res => {
                    clearTimeout(timeoutId);
                    try {
                        if (res.status >= 200 && res.status < 300) {
                            callback(JSON.parse(res.responseText));
                        } else {
                            console.warn(`Request to ${url} failed with status ${res.status}`);
                            if (retryCount < MAX_RETRIES) {
                                retryCount++;
                                setTimeout(attemptFetch, RETRY_DELAY_MS);
                            } else {
                                callback(null);
                            }
                        }
                    } catch (e) {
                        console.error(`Error parsing response from ${url}:`, e);
                        callback(null);
                    }
                },
                onerror: (error) => {
                    clearTimeout(timeoutId);
                    console.warn(`Request to ${url} failed:`, error);
                    if (retryCount < MAX_RETRIES) {
                        retryCount++;
                        setTimeout(attemptFetch, RETRY_DELAY_MS);
                    } else {
                        callback(null);
                    }
                },
                ontimeout: () => {
                    clearTimeout(timeoutId);
                    console.warn(`Request to ${url} timed out natively`);
                    if (retryCount < MAX_RETRIES) {
                        retryCount++;
                        setTimeout(attemptFetch, RETRY_DELAY_MS);
                    } else {
                        callback(null);
                    }
                }
            });
        }

        attemptFetch();
    }

    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');

        if (!cardContainer || !scrollWrapper || !infoContainer.isConnected) return;

        try {
            const 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)`;
            }
        } catch (error) {
            console.error("Error rendering virtual cards:", error);
        }
    }

    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,
                startTime = 0,
                isClickAction = false;
            const ACTION_THRESHOLD = 200;

            function smoothScroll() {
                if (!isScrolling) return;
                scrollWrapper.scrollLeft += (direction === 'left' ? -1.5 : 1.5);
                scrollAnimationId = requestAnimationFrame(smoothScroll);
            }

            function startScrolling(e) {
                e.preventDefault();
                startTime = Date.now();
                isClickAction = false;
                setTimeout(() => {
                    if (startTime && Date.now() - startTime >= ACTION_THRESHOLD) {
                        isScrolling = true;
                        smoothScroll();
                    }
                }, ACTION_THRESHOLD);
            }

            function stopScrolling() {
                const holdDuration = Date.now() - startTime;
                isScrolling = false;
                if (scrollAnimationId) {
                    cancelAnimationFrame(scrollAnimationId);
                    scrollAnimationId = null;
                }
                if (holdDuration < ACTION_THRESHOLD && !isClickAction) {
                    isClickAction = true;
                    scrollWrapper.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });
                }
                startTime = 0;
            }

            arrow.addEventListener('mousedown', startScrolling);
            arrow.addEventListener('mouseup', stopScrolling);
            arrow.addEventListener('mouseleave', stopScrolling);

            arrow.addEventListener('touchstart', startScrolling, { passive: false });
            arrow.addEventListener('touchend', stopScrolling);
            arrow.addEventListener('touchcancel', stopScrolling);

            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>
        `;
        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}"
                       ${scriptSettings.linkBehavior === 'new_tab' ? 'target="_blank" rel="noopener noreferrer"' : ''}
                       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', (e) => {
            localStorage.setItem(playerLink.dataset.visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';

            const behavior = scriptSettings.linkBehavior || 'new_tab';
            if (behavior !== 'same_tab') {
                e.preventDefault();
                if (behavior === 'new_window') {
                    window.open(playerLink.href, '_blank', 'noopener,noreferrer,width=1200,height=800');
                } else {
                    window.open(playerLink.href, '_blank', 'noopener,noreferrer');
                }
            }
        });

        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) {
        if (wrapper.hasAttribute('data-has-bazaar-info')) return;

        let infoContainer = document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);

        if (!infoContainer) {
            infoContainer = createInfoContainer(itemName, itemId);
            wrapper.insertBefore(infoContainer, wrapper.firstChild);
            wrapper.setAttribute('data-has-bazaar-info', 'true');
        } else if (!wrapper.contains(infoContainer)) {
            const oldContainer = infoContainer;
            infoContainer = createInfoContainer(itemName, itemId);
            wrapper.insertBefore(infoContainer, wrapper.firstChild);
            wrapper.setAttribute('data-has-bazaar-info', 'true');
        } 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;
        let requestTimeout = setTimeout(() => {
            console.warn('Bazaar listings request timed out');
            if (responses < 2) {
                showEmptyState(true);
                responses = 2; // Prevent further updates
            }
        }, 15000);

        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) {
                clearTimeout(requestTimeout);
                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') || wrapper.hasAttribute('data-bazaar-processed')) return;

        const existingContainer = wrapper.querySelector(':scope > .bazaar-info-container');
        if (existingContainer) 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];

            wrapper.setAttribute('data-bazaar-processed', 'true');

            updateInfoContainer(wrapper, itemId, itemName);
        }
    }

    function processMobileSellerList() {
        if (!checkMobileView()) return;

        const sellerList = document.querySelector('ul.sellerList___e4C9_, ul[class*="sellerList"]');
        if (!sellerList) {
            const existing = document.querySelector('.bazaar-info-container');
            if (existing && !document.contains(existing.parentNode)) {
                existing.remove();
            }
            return;
        }

        if (sellerList.hasAttribute('data-has-bazaar-container')) {
            return;
        }

        const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT, [class*="itemsHeader"] [class*="title"]');
        const itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
        const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"], [class*="itemsHeader"] 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];
        }

        const existingContainer = document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);
        if (existingContainer) {
            if (existingContainer.parentNode !== sellerList.parentNode ||
                existingContainer.nextSibling !== sellerList) {
                sellerList.parentNode.insertBefore(existingContainer, sellerList);
            }
            return;
        }

        const infoContainer = createInfoContainer(itemName, itemId);
        sellerList.parentNode.insertBefore(infoContainer, sellerList);
        sellerList.setAttribute('data-has-bazaar-container', 'true');
        updateInfoContainer(infoContainer, itemId, itemName);
    }

    function processAllSellerWrappers(root = document.body) {
        if (checkMobileView()) return;

        const sellerWrappers = root.querySelectorAll('[class*="sellerListWrapper"]');
        sellerWrappers.forEach(wrapper => processSellerWrapper(wrapper));
    }

    processAllSellerWrappers();
    processMobileSellerList();

    const observeTarget = document.querySelector('#root') || document.body;

    let isProcessing = false;

    const observer = new MutationObserver(mutations => {
        if (isProcessing) return;

        let needsProcessing = false;

        mutations.forEach(mutation => {
            const isOurMutation = Array.from(mutation.addedNodes).some(node =>
                node.nodeType === Node.ELEMENT_NODE &&
                (node.classList.contains('bazaar-info-container') ||
                 node.querySelector('.bazaar-info-container'))
            );

            if (isOurMutation) return;

            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    needsProcessing = true;
                }
            });
            mutation.removedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE &&
                    (node.matches('ul.sellerList___e4C9_') || node.matches('ul[class*="sellerList"]')) &&
                    checkMobileView()) {
                    const container = document.querySelector('.bazaar-info-container');
                    if (container) container.remove();
                }
            });
        });

        if (needsProcessing) {
            if (observer.processingTimeout) {
                clearTimeout(observer.processingTimeout);
            }

            observer.processingTimeout = setTimeout(() => {
                try {
                    isProcessing = true;
                    if (checkMobileView()) {
                        processMobileSellerList();
                    } else {
                        processAllSellerWrappers();
                    }
                } finally {
                    isProcessing = false;
                    observer.processingTimeout = null;
                }
            }, 100);
        }
    });

    observer.observe(observeTarget, { 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) {
                            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://greatest.deepsurf.us/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 class="bazaar-settings-item">
                        <label for="bazaar-link-behavior">Bazaar Link Click Behavior</label>
                        <select id="bazaar-link-behavior">
                            <option value="new_tab" ${scriptSettings.linkBehavior === 'new_tab' ? 'selected' : ''}>Open in New Tab</option>
                            <option value="new_window" ${scriptSettings.linkBehavior === 'new_window' ? 'selected' : ''}>Open in New Window</option>
                            <option value="same_tab" ${scriptSettings.linkBehavior === 'same_tab' ? 'selected' : ''}>Open in Same Tab</option>
                        </select>
                        <div class="bazaar-api-note">
                            Choose how bazaar links open when clicked.
                        </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://greatest.deepsurf.us/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://greatest.deepsurf.us/en/scripts/513617-torn-item-market-highlighter" 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://greatest.deepsurf.us/en/scripts/513790-torn-item-market-max-quantity-calculator" 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://greatest.deepsurf.us/en/scripts/488294-torn-com-enhanced-chat-buttons-v2" 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://greatest.deepsurf.us/en/scripts/513784-torn-market-item-locker" 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://greatest.deepsurf.us/en/scripts/515870-torn-market-quick-remove" 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://greatest.deepsurf.us/en/scripts/496284-trade-chat-timer-on-button" 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) {
        const oldLinkBehavior = scriptSettings.linkBehavior;

        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,
            linkBehavior: modal.querySelector('#bazaar-link-behavior').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" ? "%" : "$";
            if (oldLinkBehavior !== scriptSettings.linkBehavior) {
                const cardContainer = container.querySelector('.bazaar-card-container');
                if (cardContainer) {
                    cardContainer.innerHTML = '';
                    container.lastRenderScrollLeft = undefined;
                    renderVirtualCards(container);
                }
            } else {
                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();

    function cleanupResources() {
        if (observer) {
            observer.disconnect();
        }
        if (bodyObserver) {
            bodyObserver.disconnect();
        }


        document.querySelectorAll('.bazaar-scroll-container').forEach(container => {
            const scrollWrapper = container.querySelector('.bazaar-scroll-wrapper');
            if (scrollWrapper && scrollWrapper.isScrolling) {
                cancelAnimationFrame(scrollWrapper.scrollAnimationId);
            }
        });
    }

    window.addEventListener('beforeunload', cleanupResources);
})();