Bazaars in Item Market powered by TornPal and IronNerd

Displays bazaar listings with sorting controls via TornPal & IronNerd

// ==UserScript==
// @name         Bazaars in Item Market powered by TornPal and IronNerd
// @namespace    http://tampermonkey.net/
// @version      1.301
// @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 = 10;
    
    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;
        
        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;
            }
        });
        
        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';
            
            container.querySelectorAll('.info-header').forEach(header => {
                header.style.color = darkMode ? '#fff' : '#000';
            });
            
            container.querySelectorAll('.sort-controls').forEach(sortControl => {
                sortControl.style.backgroundColor = darkMode ? '#333' : '#eee';
            });
            
            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';
            });
            
            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';
                
                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';
                    }
                }
                
                const footnote = card.querySelector('.listing-footnote');
                if (footnote) {
                    footnote.style.color = darkMode ? '#aaa' : '#555';
                }
                
                const sourceInfo = card.querySelector('.listing-source');
                if (sourceInfo) {
                    sourceInfo.style.color = darkMode ? '#aaa' : '#555';
                }
            });
            
            container.querySelectorAll('.listings-count').forEach(count => {
                count.style.color = darkMode ? '#aaa' : '#666';
            });
            

            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';
                });
            });
        });
        
        document.querySelectorAll('#bazaar-modal-container').forEach(modal => {
            modal.style.backgroundColor = darkMode ? '#2f2f2f' : '#fff';
            modal.style.border = darkMode ? '8px solid #444' : '8px solid #000';
        });
    }
    
    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());
        dollarToggle.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
        sortControls.appendChild(dollarToggle);
        allDollarToggles.push(dollarToggle);
        dollarToggle.addEventListener('click', () => {
            showDollarItems = !showDollarItems;
            localStorage.setItem("showDollarItems", showDollarItems.toString());
            allDollarToggles.forEach(btn => {
                btn.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
            });
            if (container.filteredListings) {
                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);
        
        const footerContainer = document.createElement('div');
        footerContainer.className = 'footer-container';
        setStyles(footerContainer, {
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginTop: '5px',
            fontSize: '10px'
        });
        
        const countElement = document.createElement('div');
        countElement.className = 'listings-count';
        setStyles(countElement, {
            color: dark ? '#aaa' : '#666'
        });
        countElement.textContent = 'Loading...';
        
        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) {
                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) {
                visibleListings = 0;
                allListings = sortListings(container.filteredListings);
                renderCards(container, true);
            }
        });
        
        scrollWrapper.addEventListener('scroll', () => {
            const scrollRight = scrollWrapper.scrollWidth - (scrollWrapper.scrollLeft + scrollWrapper.clientWidth);
            if (scrollRight < 200) {
                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]));
        }
        
        const countText = `Showing ${end} of ${allListings.length} listings`;
        const countElement = container.querySelector('.listings-count');
        if (countElement) {
            countElement.textContent = countText;
        }
        
        visibleListings = end;
    }

    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}`;
        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;
            visibleListings = 0;
            allListings = sortListings(cachedData.listings);
            renderCards(infoContainer, true);
            return;
        }
        let listings = [], responsesReceived = 0;
        let apiErrors = false;
        
        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) {
                setCache(itemId, { listings });
                infoContainer.filteredListings = listings;
                
                cardContainer.innerHTML = '';
                
                const countElement = infoContainer.querySelector('.listings-count');
                
                if (listings.length === 0) {
                    showNoListingsMessage(cardContainer, apiErrors);
                    if (countElement) {
                        countElement.textContent = apiErrors ? 'API Error - Check back later' : 'No listings available';
                    }
                } else {
                    visibleListings = 0;
                    allListings = sortListings(listings);
                    renderCards(infoContainer, true);
                    
                    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) {
            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 {
            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 });
    
    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());
        }
    }
})();