Bazaars in Item Market 2.0, powered by TornPal & IronNerd

Displays bazaar listings with sorting controls via TornPal & IronNerd

נכון ליום 25-02-2025. ראה הגרסה האחרונה.

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 2.0, powered by TornPal & IronNerd
// @namespace    http://tampermonkey.net/
// @version      1.221
// @description  Displays bazaar listings with sorting controls via TornPal & IronNerd
// @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';

    dailyCleanup();
    let allDollarToggles = [];
    const CACHE_DURATION_MS = 60000;
    let currentSortKey = "price", currentSortOrder = "asc";
    let showDollarItems = localStorage.getItem("showDollarItems") === "true";
    let currentDarkMode = false;
    const LISTINGS_PER_BATCH = 40; // Increased batch size for more efficient loading
    
    // Store all listings here
    let allListings = [];
    let visibleListings = 0;

    function setStyles(el, styles) {
        Object.assign(el.style, styles);
    }

    function isDarkMode() {
        return document.body.classList.contains('dark-mode');
    }

    function getButtonStyle() {
        return {
            padding: '2px 4px',
            border: isDarkMode() ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '2px',
            backgroundColor: isDarkMode() ? '#1a1a1a' : '#fff',
            color: isDarkMode() ? '#fff' : '#000',
            cursor: 'pointer'
        };
    }

    function updateThemeForAllElements() {
        const darkMode = isDarkMode();
        if (currentDarkMode === darkMode) return;
        currentDarkMode = darkMode;
        
        // Update all buttons to the new theme
        document.querySelectorAll('[id^="bazaar-modal-"] button, .sort-controls button, #item-info-container button').forEach(button => {
            const style = getButtonStyle();
            for (const [key, value] of Object.entries(style)) {
                button.style[key] = value;
            }
        });
        
        // Update all info containers
        document.querySelectorAll('#item-info-container').forEach(container => {
            container.style.backgroundColor = darkMode ? '#2f2f2f' : '#f9f9f9';
            container.style.color = darkMode ? '#ccc' : '#000';
            container.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
            
            // Update headers
            container.querySelectorAll('.info-header').forEach(header => {
                header.style.color = darkMode ? '#fff' : '#000';
            });
            
            // Update sort controls
            container.querySelectorAll('.sort-controls').forEach(sortControl => {
                sortControl.style.backgroundColor = darkMode ? '#333' : '#eee';
            });
            
            // Update selects
            container.querySelectorAll('select').forEach(select => {
                select.style.backgroundColor = darkMode ? '#1a1a1a' : '#fff';
                select.style.color = darkMode ? '#fff' : '#000';
                select.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
            });
            
            // Update cards
            container.querySelectorAll('.listing-card').forEach(card => {
                card.style.backgroundColor = darkMode ? '#1a1a1a' : '#fff';
                card.style.color = darkMode ? '#fff' : '#000';
                card.style.border = darkMode ? '1px solid #444' : '1px solid #ccc';
                
                // Maintain proper link colors based on visited status
                const playerLink = card.querySelector('a');
                if (playerLink) {
                    const visitedKey = playerLink.getAttribute('data-visited-key');
                    if (visitedKey) {
                        let visitedData = null;
                        try { visitedData = JSON.parse(localStorage.getItem(visitedKey)); } catch(e){}
                        const listing = JSON.parse(playerLink.getAttribute('data-listing') || '{}');
                        playerLink.style.color = (visitedData && visitedData.lastClickedUpdated >= listing.updated) ? 'purple' : '#00aaff';
                    }
                }
                
                // Update footnotes
                const footnote = card.querySelector('.listing-footnote');
                if (footnote) {
                    footnote.style.color = darkMode ? '#aaa' : '#555';
                }
                
                // Update source info
                const sourceInfo = card.querySelector('.listing-source');
                if (sourceInfo) {
                    sourceInfo.style.color = darkMode ? '#aaa' : '#555';
                }
            });
            
            // Update listings count
            container.querySelectorAll('.listings-count').forEach(count => {
                count.style.color = darkMode ? '#aaa' : '#666';
            });
            
            // Update powered by text
            container.querySelectorAll('.powered-by').forEach(poweredBy => {
                poweredBy.querySelectorAll('span').forEach(span => {
                    span.style.color = darkMode ? '#666' : '#999';
                });
                
                poweredBy.querySelectorAll('a').forEach(link => {
                    link.style.color = darkMode ? '#aaa' : '#555';
                });
            });
        });
        
        // Update modals
        document.querySelectorAll('#bazaar-modal-container').forEach(modal => {
            modal.style.backgroundColor = darkMode ? '#2f2f2f' : '#fff';
            modal.style.border = darkMode ? '8px solid #444' : '8px solid #000';
        });
    }
    
    // Initialize current dark mode state
    currentDarkMode = isDarkMode();

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

    const style = document.createElement("style");
    style.textContent = `
    @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; }
    `;
    document.head.appendChild(style);

    function getCache(itemId) {
        try {
            const key = "tornBazaarCache_" + itemId;
            const 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';
    }

    function openModal(url) {
        const originalOverflow = document.body.style.overflow;
        document.body.style.overflow = 'hidden';

        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'bazaar-modal-overlay';
        setStyles(modalOverlay, {
            position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
            backgroundColor: 'rgba(0, 0, 0, 0.7)', display: 'flex', justifyContent: 'center',
            alignItems: 'center', zIndex: '10000', overflow: 'visible'
        });

        const modalContainer = document.createElement('div');
        modalContainer.id = 'bazaar-modal-container';
        setStyles(modalContainer, {
            backgroundColor: '#fff', border: '8px solid #000',
            boxShadow: '0 0 10px rgba(0,0,0,0.5)', borderRadius: '8px',
            position: 'relative', resize: 'both'
        });

        const savedSize = localStorage.getItem('bazaarModalSize');
        if (savedSize) {
            try {
                const { width, height } = JSON.parse(savedSize);
                modalContainer.style.width = (width < 200 ? '80%' : width + 'px');
                modalContainer.style.height = (height < 200 ? '80%' : height + 'px');
            } catch(e) {
                modalContainer.style.width = modalContainer.style.height = '80%';
            }
        } else {
            modalContainer.style.width = modalContainer.style.height = '80%';
        }

        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        setStyles(closeButton, Object.assign({}, getButtonStyle(), {
            position: 'absolute', top: '-20px', right: '-20px',
            width: '40px', height: '40px', backgroundColor: '#ff0000',
            color: '#fff', border: 'none', borderRadius: '50%',
            fontSize: '24px', boxShadow: '0 0 5px rgba(0,0,0,0.5)'
        }));
        closeButton.addEventListener('click', () => {
            modalOverlay.remove();
            document.body.style.overflow = originalOverflow;
        });

        modalOverlay.addEventListener('click', e => {
            if (e.target === modalOverlay) {
                modalOverlay.remove();
                document.body.style.overflow = originalOverflow;
            }
        });

        const iframe = document.createElement('iframe');
        setStyles(iframe, { width: '100%', height: '100%', border: 'none' });
        iframe.src = url;

        modalContainer.append(closeButton, iframe);
        modalOverlay.appendChild(modalContainer);
        document.body.appendChild(modalOverlay);

        if (window.ResizeObserver) {
            const observer = new ResizeObserver(entries => {
                for (let entry of entries) {
                    const { width, height } = entry.contentRect;
                    localStorage.setItem('bazaarModalSize', JSON.stringify({
                        width: Math.round(width),
                        height: Math.round(height)
                    }));
                }
            });
            observer.observe(modalContainer);
        }
    }

    function createInfoContainer(itemName, itemId) {
        const dark = isDarkMode();
        const container = document.createElement('div');
        container.id = 'item-info-container';
        container.setAttribute('data-itemid', itemId);
        setStyles(container, {
            backgroundColor: dark ? '#2f2f2f' : '#f9f9f9',
            color: dark ? '#ccc' : '#000',
            fontSize: '13px',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '4px',
            margin: '5px 0',
            padding: '10px',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px'
        });

        const header = document.createElement('div');
        header.className = 'info-header';
        setStyles(header, { fontSize: '16px', fontWeight: 'bold', color: dark ? '#fff' : '#000' });
        header.textContent = `Item: ${itemName} (ID: ${itemId})`;
        container.appendChild(header);

        const sortControls = document.createElement('div');
        sortControls.className = 'sort-controls';
        setStyles(sortControls, {
            display: 'flex',
            alignItems: 'center',
            gap: '5px',
            fontSize: '12px',
            padding: '5px',
            backgroundColor: dark ? '#333' : '#eee',
            borderRadius: '4px'
        });
        const sortLabel = document.createElement('span');
        sortLabel.textContent = "Sort by:";
        sortControls.appendChild(sortLabel);

        const sortSelect = document.createElement('select');
        setStyles(sortSelect, {
            padding: '2px',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '2px',
            backgroundColor: dark ? '#1a1a1a' : '#fff',
            color: dark ? '#fff' : '#000'
        });
        [{ value: "price", text: "Price" },
         { value: "quantity", text: "Quantity" },
         { value: "updated", text: "Last Updated" }]
            .forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.value;
                option.textContent = opt.text;
                sortSelect.appendChild(option);
            });
        sortSelect.value = currentSortKey;
        sortControls.appendChild(sortSelect);

        const orderToggle = document.createElement('button');
        setStyles(orderToggle, getButtonStyle());
        orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
        sortControls.appendChild(orderToggle);

        const dollarToggle = document.createElement('button');
        setStyles(dollarToggle, getButtonStyle());
        // Use the persisted state for button text
        dollarToggle.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
        sortControls.appendChild(dollarToggle);
        allDollarToggles.push(dollarToggle);
        dollarToggle.addEventListener('click', () => {
            showDollarItems = !showDollarItems;
            // Persist the updated state in localStorage
            localStorage.setItem("showDollarItems", showDollarItems.toString());
            allDollarToggles.forEach(btn => {
                btn.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
            });
            if (container.filteredListings) {
                // Reset visible listings and render again
                visibleListings = 0;
                allListings = sortListings(container.filteredListings);
                renderCards(container, true);
            }
        });

        container.appendChild(sortControls);

        const scrollWrapper = document.createElement('div');
        setStyles(scrollWrapper, {
            overflowX: 'auto',
            overflowY: 'hidden',
            height: '120px',
            whiteSpace: 'nowrap',
            paddingBottom: '3px'
        });
        const cardContainer = document.createElement('div');
        cardContainer.className = 'card-container';
        setStyles(cardContainer, { display: 'flex', flexWrap: 'nowrap', gap: '10px' });
        scrollWrapper.appendChild(cardContainer);
        container.appendChild(scrollWrapper);
        
        // Create compact footer with count and powered by text
        const footerContainer = document.createElement('div');
        footerContainer.className = 'footer-container';
        setStyles(footerContainer, {
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginTop: '5px',
            fontSize: '10px'
        });
        
        // Create count element
        const countElement = document.createElement('div');
        countElement.className = 'listings-count';
        setStyles(countElement, {
            color: dark ? '#aaa' : '#666'
        });
        countElement.textContent = 'Loading...';
        
        // Create powered by element
        const poweredBy = document.createElement('div');
        poweredBy.className = 'powered-by';
        setStyles(poweredBy, { textAlign: 'right' });
        poweredBy.innerHTML = `
          <span style="color:${dark ? '#666' : '#999'};">Powered by </span>
          <a href="https://tornpal.com/login?ref=1853324" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">TornPal</a>
          <span style="color:${dark ? '#666' : '#999'};"> &amp; </span>
          <a href="https://ironnerd.me/" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">IronNerd</a>
        `;
        
        footerContainer.appendChild(countElement);
        footerContainer.appendChild(poweredBy);
        container.appendChild(footerContainer);

        sortSelect.addEventListener('change', () => {
            currentSortKey = sortSelect.value;
            if (container.filteredListings) {
                // Reset visible listings and sort again
                visibleListings = 0;
                allListings = sortListings(container.filteredListings);
                renderCards(container, true);
            }
        });
        orderToggle.addEventListener('click', () => {
            currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
            orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
            if (container.filteredListings) {
                // Reset visible listings and sort again
                visibleListings = 0;
                allListings = sortListings(container.filteredListings);
                renderCards(container, true);
            }
        });
        
        // Scroll event listener for lazy loading
        scrollWrapper.addEventListener('scroll', () => {
            const scrollRight = scrollWrapper.scrollWidth - (scrollWrapper.scrollLeft + scrollWrapper.clientWidth);
            if (scrollRight < 200) { // Increased threshold to load more when approaching the end
                renderMoreCards(container);
            }
        });

        return container;
    }
    
    function sortListings(listings) {
        const filtered = showDollarItems ? listings : listings.filter(l => l.price !== 1);
        return filtered.slice().sort((a, b) => {
            let diff = (currentSortKey === "price") ? a.price - b.price :
                       (currentSortKey === "quantity") ? a.quantity - b.quantity :
                       a.updated - b.updated;
            return currentSortOrder === "asc" ? diff : -diff;
        });
    }
    
    function renderMoreCards(container) {
        if (visibleListings >= allListings.length) return;
        
        const cardContainer = container.querySelector('.card-container');
        const end = Math.min(visibleListings + LISTINGS_PER_BATCH, allListings.length);
        
        for (let i = visibleListings; i < end; i++) {
            cardContainer.appendChild(createListingCard(allListings[i]));
        }
        
        // Update count display
        const countText = `Showing ${end} of ${allListings.length} listings`;
        const countElement = container.querySelector('.listings-count');
        if (countElement) {
            countElement.textContent = countText;
        }
        
        // Update visible count
        visibleListings = end;
        
        // No automatic loading - the scroll event will trigger more loads as needed
    }

    function renderCards(infoContainer, resetContainer) {
        if (resetContainer) {
            const cardContainer = infoContainer.querySelector('.card-container');
            cardContainer.innerHTML = '';
        }
        
        renderMoreCards(infoContainer);
    }

    function createListingCard(listing) {
        const dark = isDarkMode();
        const card = document.createElement('div');
        card.className = 'listing-card';
        setStyles(card, {
            position: 'relative',
            minWidth: '160px',
            maxWidth: '240px',
            display: 'inline-block',
            verticalAlign: 'top',
            backgroundColor: dark ? '#1a1a1a' : '#fff',
            color: dark ? '#fff' : '#000',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '4px',
            padding: '8px',
            fontSize: 'clamp(12px, 1vw, 16px)',
            boxSizing: 'border-box',
            overflow: 'hidden'
        });

        const linkContainer = document.createElement('div');
        setStyles(linkContainer, { display: 'flex', alignItems: 'center', gap: '5px', marginBottom: '6px', flexWrap: 'wrap' });
        const visitedKey = `visited_${listing.item_id}_${listing.player_id}`;
        let visitedData = null;
        try { visitedData = JSON.parse(localStorage.getItem(visitedKey)); } catch(e){}
        let linkColor = (visitedData && visitedData.lastClickedUpdated >= listing.updated) ? 'purple' : '#00aaff';

        const playerLink = document.createElement('a');
        playerLink.href = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`;
        playerLink.textContent = `Player: ${listing.player_id}`;
        // Store data attributes for theme switching
        playerLink.setAttribute('data-visited-key', visitedKey);
        playerLink.setAttribute('data-listing', JSON.stringify(listing));
        setStyles(playerLink, { fontWeight: 'bold', color: linkColor, textDecoration: 'underline' });
        playerLink.addEventListener('click', () => {
            localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';
        });
        linkContainer.appendChild(playerLink);

        const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        iconSvg.setAttribute("viewBox", "0 0 512 512");
        iconSvg.setAttribute("width", "16");
        iconSvg.setAttribute("height", "16");
        iconSvg.style.cursor = "pointer";
        iconSvg.style.color = dark ? '#ffa500' : '#cc6600';
        iconSvg.title = "Open in modal";
        iconSvg.innerHTML = `
            <path fill="currentColor" d="M432 64L208 64c-8.8 0-16 7.2-16 16l0 16-64 0 0-16c0-44.2 35.8-80 80-80L432 0c44.2 0 80 35.8 80 80l0 224
                c0 44.2-35.8 80-80 80l-16 0 0-64 16 0c8.8 0 16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zM0 192c0-35.3 28.7-64 64-64l256 0c35.3 0
                64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 192zm64 32c0 17.7 14.3 32 32 32l192 0c17.7 0 32-14.3
                32-32s-14.3-32-32-32L96 192c-17.7 0-32 14.3-32 32z"/>
        `;
        iconSvg.addEventListener('click', e => {
            e.preventDefault();
            localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';
            openModal(`https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`);
        });
        linkContainer.appendChild(iconSvg);
        card.appendChild(linkContainer);

        const details = document.createElement('div');
        details.innerHTML = `<div><strong>Price:</strong> $${listing.price.toLocaleString()}</div>
                             <div><strong>Qty:</strong> ${listing.quantity}</div>`;
        details.style.marginBottom = '6px';
        card.appendChild(details);

        const footnote = document.createElement('div');
        footnote.className = 'listing-footnote';
        setStyles(footnote, { fontSize: '11px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
        footnote.textContent = `Updated: ${getRelativeTime(listing.updated)}`;
        card.appendChild(footnote);

        const sourceInfo = document.createElement('div');
        sourceInfo.className = 'listing-source';
        setStyles(sourceInfo, { fontSize: '10px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
        let sourceDisplay = listing.source === "ironnerd" ? "IronNerd" : (listing.source === "bazaar" ? "TornPal" : listing.source);
        sourceInfo.textContent = "Source: " + sourceDisplay;
        card.appendChild(sourceInfo);

        return card;
    }

    function updateInfoContainer(wrapper, itemId, itemName) {
        let infoContainer = document.querySelector(`#item-info-container[data-itemid="${itemId}"]`);
        if (!infoContainer) {
            infoContainer = createInfoContainer(itemName, itemId);
            wrapper.insertBefore(infoContainer, wrapper.firstChild);
        } else {
            const header = infoContainer.querySelector('.info-header');
            if (header) header.textContent = `Item: ${itemName} (ID: ${itemId})`;
            const cardContainer = infoContainer.querySelector('.card-container');
            if (cardContainer) cardContainer.innerHTML = '';
        }
        const cachedData = getCache(itemId);
        if (cachedData) {
            infoContainer.filteredListings = cachedData.listings;
            // Reset visible listings count and sort listings
            visibleListings = 0;
            allListings = sortListings(cachedData.listings);
            renderCards(infoContainer, true);
            return;
        }
        let listings = [], responsesReceived = 0;
        let apiErrors = false;
        
        // Show loading state
        const cardContainer = infoContainer.querySelector('.card-container');
        const loadingEl = document.createElement('div');
        loadingEl.textContent = 'Loading bazaar listings...';
        setStyles(loadingEl, {
            padding: '10px',
            textAlign: 'center',
            width: '100%',
            color: isDarkMode() ? '#aaa' : '#666'
        });
        cardContainer.appendChild(loadingEl);
        
        function processResponse(newListings, error) {
            if (error) {
                apiErrors = true;
            }
            
            if (Array.isArray(newListings)) {
                newListings.forEach(newItem => {
                    let 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;
                    let duplicate = listings.find(item =>
                        item.player_id === normalized.player_id &&
                        item.price === normalized.price &&
                        item.quantity === normalized.quantity
                    );
                    if (duplicate) {
                        if (duplicate.source !== normalized.source)
                            duplicate.source = "TornPal & IronNerd";
                    } else { listings.push(normalized); }
                });
            }
            
            responsesReceived++;
            if (responsesReceived === 2) {
                // Both API calls have completed
                setCache(itemId, { listings });
                infoContainer.filteredListings = listings;
                
                // Clear loading state
                cardContainer.innerHTML = '';
                
                // Update the count element
                const countElement = infoContainer.querySelector('.listings-count');
                
                if (listings.length === 0) {
                    // No listings found
                    showNoListingsMessage(cardContainer, apiErrors);
                    if (countElement) {
                        countElement.textContent = apiErrors ? 'API Error - Check back later' : 'No listings available';
                    }
                } else {
                    // Reset visible listings count and sort listings
                    visibleListings = 0;
                    allListings = sortListings(listings);
                    renderCards(infoContainer, true);
                    
                    // Update count display
                    const countText = `Showing ${Math.min(LISTINGS_PER_BATCH, allListings.length)} of ${allListings.length} listings`;
                    if (countElement) {
                        countElement.textContent = countText;
                    }
                }
            }
        }
        
        fetchJSON(`https://tornpal.com/api/v1/markets/clist/${itemId}?comment=wBazaarMarket`, data => {
            const error = data === null;
            processResponse(data && data.listings && Array.isArray(data.listings) ? data.listings.filter(l => l.source === "bazaar") : [], error);
        });
        fetchJSON(`https://www.ironnerd.me/get_bazaar_items/${itemId}?comment=wBazaarMarket`, data => {
            const error = data === null;
            processResponse(data && data.bazaar_items && Array.isArray(data.bazaar_items) ? data.bazaar_items : [], error);
        });
    }

    function showNoListingsMessage(container, isApiError) {
        const dark = isDarkMode();
        const messageContainer = document.createElement('div');
        setStyles(messageContainer, {
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            padding: '20px',
            textAlign: '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";
        iconSvg.style.color = dark ? '#aaa' : '#666';
        
        if (isApiError) {
            // Show warning icon for API errors
            iconSvg.innerHTML = `
                <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"/>
            `;
            messageContainer.appendChild(iconSvg);
            
            const errorText = document.createElement('div');
            errorText.textContent = "Unable to load bazaar listings. Please try again later.";
            errorText.style.color = dark ? '#ff9999' : '#cc0000';
            errorText.style.fontWeight = 'bold';
            messageContainer.appendChild(errorText);
        } else {
            // Show info icon for no listings
            iconSvg.innerHTML = `
                <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 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
            `;
            messageContainer.appendChild(iconSvg);
            
            const noListingsText = document.createElement('div');
            noListingsText.textContent = "No bazaar listings available for this item.";
            noListingsText.style.color = dark ? '#ccc' : '#666';
            messageContainer.appendChild(noListingsText);
        }
        
        container.appendChild(messageContainer);
    }

    function processSellerWrapper(wrapper) {
        if (!wrapper || wrapper.id === 'item-info-container') return;
        const itemTile = wrapper.previousElementSibling;
        if (!itemTile) return;
        const nameEl = itemTile.querySelector('.name___ukdHN');
        const 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('#item-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(`#item-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);
                    }
                }
            });
            mutation.removedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE &&
                    node.matches('ul.sellerList___e4C9_') &&
                    window.innerWidth < 784) {
                    const container = document.querySelector('#item-info-container');
                    if (container) container.remove();
                }
            });
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });
    
    // Observe body class changes to detect theme switching
    const bodyObserver = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            if (mutation.attributeName === 'class') {
                updateThemeForAllElements();
            }
        });
    });
    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"), highlightParam = params.get("highlight");
            if (!targetItemId || highlightParam !== "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;
                setStyles(toast, {
                    position: 'fixed',
                    bottom: '20px',
                    left: '50%',
                    transform: 'translateX(-50%)',
                    backgroundColor: 'rgba(0,0,0,0.7)',
                    color: 'white',
                    padding: '10px 20px',
                    borderRadius: '5px',
                    zIndex: '100000',
                    fontSize: '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.lastClickedTime || (localStorage.removeItem(key), null);
                        } else if (key.startsWith("tornBazaarCache_") && val && val.timestamp) {
                            ts = val.timestamp;
                        }
                        if (ts !== null && (now - ts) > sevenDays) localStorage.removeItem(key);
                    } catch(e) { localStorage.removeItem(key); }
                }
            }
            localStorage.setItem("lastDailyCleanup", now.toString());
        }
    }
})();