pokespy

Help with eBay listings with TCGdex integration and PriceCharting data extraction

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         pokespy
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Help with eBay listings with TCGdex integration and PriceCharting data extraction
// @author       bobjoepie
// @match        https://www.ebay.com/*
// @match        https://www.ebay.co.uk/*
// @match        https://www.pricecharting.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM.xmlHttpRequest
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ============================================================================
    // DEBUG MODE CONFIGURATION
    // ============================================================================
    const DEBUG_MODE = false; // Set to false for production/performance mode
    
    // Logging wrapper - only logs if DEBUG_MODE is true
    const debugLog = (...args) => {
        if (DEBUG_MODE) console.log(...args);
    };
    
    // Timing configuration based on mode
    const TIMING = {
        // PriceCharting data polling
        POLL_INTERVAL: DEBUG_MODE ? 500 : 200,        // How often to check for data (ms)
        POLL_MAX_ATTEMPTS: DEBUG_MODE ? 30 : 60,      // Max attempts before timeout
        
        // Button processing batches
        BATCH_PROCESSING_DELAY: DEBUG_MODE ? 100 : 50, // Delay between batches (ms)
        
        // Cache duration
        CACHE_DURATION: 60000, // 1 minute (same for both modes)
        
        // eBay Browse API polling interval (ms)
        API_POLL_INTERVAL: 18000, // 18 seconds - adjust this to change API polling frequency
    };
    
    debugLog(`🔧 Debug mode: ${DEBUG_MODE ? 'ENABLED' : 'DISABLED'}`);
    debugLog(`⏱️ Timing config:`, TIMING);

    // Detect which site we're on
    const currentSite = window.location.hostname;
    const isEbay = currentSite.includes('ebay.com') || currentSite.includes('ebay.co.uk');
    const isPriceCharting = currentSite.includes('pricecharting.com');

    debugLog(`🔍 Script running on: ${currentSite}`);

    if (isEbay) {
        initializeEbayFunctionality();
    } else if (isPriceCharting) {
        initializePriceChartingFunctionality();
    }

    // ============================================================================
    // SHARED DATA STORAGE FUNCTIONS (using Tampermonkey's cross-site storage)
    // ============================================================================

    // Store card search data for cross-site access
    function storePriceChartingRequest(cardData) {
        const timestamp = Date.now();
        const key = `pc_request_${timestamp}`;
        GM_setValue(key, {
            ...cardData,
            timestamp: timestamp,
            source: 'ebay'
        });
        debugLog(`💾 Stored PriceCharting request:`, cardData);
        return key;
    }

    // Store extracted PriceCharting data
    function storePriceChartingData(cardKey, priceData) {
        GM_setValue(`${cardKey}_data`, {
            ...priceData,
            timestamp: Date.now(),
            source: 'pricecharting'
        });
        debugLog(`💾 Stored PriceCharting data for ${cardKey}:`, priceData);
    }

    // Get stored data
    function getStoredData(key) {
        return GM_getValue(key, null);
    }

    // Store listing display cache (persists across page reloads)
    // Only store raw data, not HTML - we'll reconstruct the display each time
    function storeListingDisplayCache(listingUrl, displayData) {
        const cacheKey = `listing_cache_${btoa(listingUrl).substring(0, 50)}`;
        GM_setValue(cacheKey, {
            cardName: displayData.cardName,
            setName: displayData.setName,
            prices: displayData.prices,
            detectedGrade: displayData.detectedGrade,
            extractedCardName: displayData.extractedCardName,
            extractedSetName: displayData.extractedSetName,
            extractedCardNumber: displayData.extractedCardNumber,
            lastUpdated: displayData.lastUpdated,
            url: displayData.url,
            imageUrl: displayData.imageUrl,
            timestamp: Date.now(),
            originalUrl: listingUrl
        });
        debugLog(`💾 Cached listing data for: ${listingUrl}`);
    }

    // Get listing display cache
    function getListingDisplayCache(listingUrl) {
        const cacheKey = `listing_cache_${btoa(listingUrl).substring(0, 50)}`;
        const cached = GM_getValue(cacheKey, null);
        
        if (cached) {
            const age = Date.now() - cached.timestamp;
            const ageMinutes = (age / 1000 / 60).toFixed(1);
            debugLog(`🔍 Cache found for key: ${cacheKey.substring(0, 30)}... (age: ${ageMinutes} min)`);
            
            // Check if cache is still valid (30 minutes)
            if (age < (30 * 60 * 1000)) {
                debugLog(`✅ Cache is valid (< 30 min)`);
                return cached;
            } else {
                debugLog(`❌ Cache expired (> 30 min)`);
            }
        }
        
        return null;
    }

    // Clean up old data (older than 1 hour for requests, 30 minutes for listing caches)
    function cleanupOldData() {
        const keys = GM_listValues();
        const oneHourAgo = Date.now() - (60 * 60 * 1000);
        const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);

        keys.forEach(key => {
            if (key.startsWith('pc_request_')) {
                const data = GM_getValue(key);
                if (data && data.timestamp < oneHourAgo) {
                    GM_deleteValue(key);
                    GM_deleteValue(`${key}_data`);
                }
            } else if (key.startsWith('listing_cache_')) {
                const data = GM_getValue(key);
                if (data && data.timestamp < thirtyMinutesAgo) {
                    GM_deleteValue(key);
                }
            }
        });
    }

    // ============================================================================
    // TITLE CLEANING UTILITY
    // ============================================================================

    // Clean eBay item titles by removing "Picture X of Y" text that appears in image alt attributes
    function cleanEbayTitle(title) {
        if (!title) return title;
        
        // Remove "- Picture X of Y" or " - Picture X of Y" patterns (common in eBay image alt text)
        // Examples: "POKEMON CARD NAME - Picture 1 of 3" -> "POKEMON CARD NAME"
        const cleaned = title
            .replace(/\s*-\s*Picture\s+\d+\s+of\s+\d+\s*$/i, '')  // Remove from end
            .replace(/\s*-\s*Picture\s+\d+\s+of\s+\d+/i, '')      // Remove from middle
            .trim();
        
        if (cleaned !== title) {
            debugLog(`🧹 Cleaned eBay title: "${title}" -> "${cleaned}"`);
        }
        
        return cleaned;
    }

    // ============================================================================
    // POPUP PERMISSION HELPER
    // ============================================================================

    function checkPopupPermissions() {
        // Check if user has been notified before
        const hasBeenNotified = GM_getValue('popup_permission_notified', false);
        
        if (!hasBeenNotified) {
            // Show notification on first use
            showPopupPermissionNotification();
            GM_setValue('popup_permission_notified', true);
        }
    }

    function showPopupPermissionNotification() {
        const notification = document.createElement('div');
        notification.id = 'pokespy-popup-notification';
        notification.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 24px 32px;
            border-radius: 12px;
            z-index: 100000;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 14px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
            max-width: 500px;
            border: 2px solid #5a67d8;
        `;

        notification.innerHTML = `
            <div style="font-size: 18px; font-weight: bold; margin-bottom: 12px; text-align: center;">
                🚀 PokeSpy Setup Required
            </div>
            <div style="line-height: 1.6; margin-bottom: 16px;">
                <p style="margin: 0 0 12px 0;">
                    PokeSpy needs to open PriceCharting.com in popup windows to fetch card prices automatically.
                </p>
                <p style="margin: 0 0 12px 0; font-weight: bold;">
                    📌 Please allow popups for eBay in your browser settings.
                </p>
                <p style="margin: 0; font-size: 12px; opacity: 0.9;">
                    The popups will close automatically after fetching data (usually within 1-2 seconds).
                </p>
            </div>
            <div style="display: flex; gap: 12px; justify-content: center;">
                <button id="pokespy-popup-understood" style="
                    padding: 10px 24px;
                    background: #43b581;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: all 0.2s;
                ">Got it!</button>
                <button id="pokespy-popup-help" style="
                    padding: 10px 24px;
                    background: rgba(255,255,255,0.2);
                    color: white;
                    border: 1px solid white;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: all 0.2s;
                ">Show Me How</button>
            </div>
        `;

        document.body.appendChild(notification);

        // Got it button
        document.getElementById('pokespy-popup-understood').addEventListener('click', () => {
            notification.remove();
        });

        // Help button
        document.getElementById('pokespy-popup-help').addEventListener('click', () => {
            notification.innerHTML = `
                <div style="font-size: 18px; font-weight: bold; margin-bottom: 12px; text-align: center;">
                    📖 How to Allow Popups
                </div>
                <div style="line-height: 1.6; margin-bottom: 16px; text-align: left;">
                    <p style="margin: 0 0 8px 0; font-weight: bold;">Chrome / Edge:</p>
                    <ol style="margin: 0 0 12px 0; padding-left: 20px;">
                        <li>Click the popup blocked icon <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">🚫</span> in the address bar</li>
                        <li>Select "Always allow popups from [ebay.com]"</li>
                        <li>Click "Done"</li>
                    </ol>
                    
                    <p style="margin: 12px 0 8px 0; font-weight: bold;">Firefox:</p>
                    <ol style="margin: 0 0 12px 0; padding-left: 20px;">
                        <li>Click the popup blocked icon in the address bar</li>
                        <li>Click "Preferences" → "Allow popups for ebay.com"</li>
                    </ol>

                    <p style="margin: 12px 0 0 0; font-size: 12px; opacity: 0.9;">
                        💡 You only need to do this once!
                    </p>
                </div>
                <div style="text-align: center;">
                    <button id="pokespy-popup-close" style="
                        padding: 10px 24px;
                        background: #43b581;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Close</button>
                </div>
            `;

            document.getElementById('pokespy-popup-close').addEventListener('click', () => {
                notification.remove();
            });
        });

        // Add hover effects
        const buttons = notification.querySelectorAll('button');
        buttons.forEach(btn => {
            btn.addEventListener('mouseenter', () => {
                btn.style.transform = 'translateY(-2px)';
                btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
            });
            btn.addEventListener('mouseleave', () => {
                btn.style.transform = 'translateY(0)';
                btn.style.boxShadow = 'none';
            });
        });
    }

    function showPopupBlockedWarning(listingElement) {
        // Show a small warning badge on the button
        const pcButton = listingElement?.querySelector('.pricecharting-direct-btn');
        if (pcButton) {
            pcButton.style.background = '#e74c3c';
            pcButton.title = '❌ Popup blocked! Please allow popups for eBay to use this feature.';
            pcButton.textContent = '🚫 Blocked';
        }

        // Check if we should show the full notification (only once per session)
        const hasShownWarning = sessionStorage.getItem('pokespy_popup_warning_shown');
        if (!hasShownWarning) {
            sessionStorage.setItem('pokespy_popup_warning_shown', 'true');
            
            const warning = document.createElement('div');
            warning.style.cssText = `
                position: fixed;
                top: 80px;
                right: 20px;
                background: #e74c3c;
                color: white;
                padding: 16px 20px;
                border-radius: 8px;
                z-index: 99999;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 13px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                max-width: 350px;
                animation: slideIn 0.3s ease-out;
            `;

            warning.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 8px; font-size: 15px;">
                    🚫 Popup Blocked!
                </div>
                <div style="line-height: 1.4; margin-bottom: 12px;">
                    PokeSpy needs to open popups to fetch prices. Please allow popups for eBay.
                </div>
                <button id="pokespy-warning-ok" style="
                    padding: 6px 16px;
                    background: white;
                    color: #e74c3c;
                    border: none;
                    border-radius: 4px;
                    font-size: 12px;
                    font-weight: bold;
                    cursor: pointer;
                ">OK</button>
            `;

            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = `
                @keyframes slideIn {
                    from { transform: translateX(400px); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
            `;
            document.head.appendChild(style);

            document.body.appendChild(warning);

            document.getElementById('pokespy-warning-ok').addEventListener('click', () => {
                warning.style.animation = 'slideIn 0.3s ease-out reverse';
                setTimeout(() => warning.remove(), 300);
            });

            // Auto-remove after 10 seconds
            setTimeout(() => {
                if (warning.parentNode) {
                    warning.style.animation = 'slideIn 0.3s ease-out reverse';
                    setTimeout(() => warning.remove(), 300);
                }
            }, 10000);
        }
    }

    // ============================================================================
    // LISTING NOTES FUNCTIONALITY (SHARED BETWEEN SEARCH AND ITEM PAGES)
    // ============================================================================

    // Store and retrieve notes (using listing ID for portability)
    function storeListingNote(listingId, noteData) {
        const noteKey = `listing_note_${listingId}`;
        GM_setValue(noteKey, {
            rating: noteData.rating, // 'good', 'neutral', 'bad'
            description: noteData.description,
            timestamp: Date.now()
        });
        debugLog(`💾 Stored note for listing ID: ${listingId}`);
    }

    function getListingNote(listingId) {
        const noteKey = `listing_note_${listingId}`;
        return GM_getValue(noteKey, null);
    }

    // Helper functions for ratings
    function getRatingIcon(rating) {
        switch(rating) {
            case 'good': return '✓';
            case 'neutral': return '−';
            case 'bad': return '✕';
            default: return '📝';
        }
    }

    function getRatingColor(rating) {
        switch(rating) {
            case 'good': return '#27ae60';
            case 'neutral': return '#f39c12';
            case 'bad': return '#e74c3c';
            default: return 'rgba(255, 255, 255, 0.9)';
        }
    }

    // Wrapper for item page that refreshes the panel after note changes
    function openNoteModalWithRefresh(panel, listingId, rating, existingDescription = '') {
        // Remove any existing modal
        const existingModal = document.getElementById('pokespy-note-modal');
        if (existingModal) existingModal.remove();

        const modal = document.createElement('div');
        modal.id = 'pokespy-note-modal';
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.2s ease;
        `;

        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            background: #2f3136;
            color: white;
            padding: 24px;
            border-radius: 12px;
            max-width: 500px;
            width: 90%;
            box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            animation: slideIn 0.3s ease;
        `;

        const ratingColor = getRatingColor(rating);
        const ratingIcon = getRatingIcon(rating);

        modalContent.innerHTML = `
            <div style="display: flex; align-items: center; margin-bottom: 16px;">
                <div style="width: 40px; height: 40px; border-radius: 50%; background: ${ratingColor}; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; margin-right: 12px;">
                    ${ratingIcon}
                </div>
                <div>
                    <div style="font-size: 18px; font-weight: bold;">Edit Note</div>
                    <div style="font-size: 12px; opacity: 0.7;">${rating.charAt(0).toUpperCase() + rating.slice(1)} Rating</div>
                </div>
            </div>
            <textarea id="pokespy-note-textarea" placeholder="Why did you choose this rating?" style="
                width: 100%;
                min-height: 120px;
                padding: 12px;
                background: #40444b;
                border: 2px solid ${ratingColor};
                border-radius: 8px;
                color: white;
                font-family: inherit;
                font-size: 14px;
                resize: vertical;
                margin-bottom: 16px;
            ">${existingDescription}</textarea>
            <div style="display: flex; gap: 12px; justify-content: flex-end;">
                <button id="pokespy-note-cancel" style="
                    padding: 10px 20px;
                    background: #5865f2;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                ">Cancel</button>
                <button id="pokespy-note-delete" style="
                    padding: 10px 20px;
                    background: #ed4245;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    display: ${existingDescription ? 'block' : 'none'};
                ">Delete</button>
                <button id="pokespy-note-save" style="
                    padding: 10px 20px;
                    background: ${ratingColor};
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                ">Save Note</button>
            </div>
        `;

        modal.appendChild(modalContent);
        document.body.appendChild(modal);

        // Focus textarea
        const textarea = document.getElementById('pokespy-note-textarea');
        textarea.focus();

        // Event handlers
        document.getElementById('pokespy-note-cancel').addEventListener('click', () => {
            modal.remove();
        });

        document.getElementById('pokespy-note-delete').addEventListener('click', () => {
            const noteKey = `listing_note_${listingId}`;
            GM_deleteValue(noteKey);
            
            modal.remove();
            
            // Refresh the panel by reloading the page
            window.location.reload();
        });

        document.getElementById('pokespy-note-save').addEventListener('click', () => {
            const description = textarea.value.trim();
            
            storeListingNote(listingId, {
                rating: rating,
                description: description
            });

            modal.remove();
            
            // Refresh the panel by reloading the page
            window.location.reload();
        });

        // Close on background click
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
            }
        });

        // Close on escape key
        const escHandler = (e) => {
            if (e.key === 'Escape') {
                modal.remove();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    }

    // ============================================================================
    // EBAY FUNCTIONALITY
    // ============================================================================

    function initializeEbayFunctionality() {
        debugLog('🛒 Initializing eBay functionality...');

        // Clean up old data on startup
        cleanupOldData();

        // Check and notify about popup permissions
        checkPopupPermissions();

        // Cache for sets data
        let setsCache = null;
        let setsCacheLoaded = false;

        // Load and cache sets on startup
        async function loadSetsCache() {
            if (setsCacheLoaded) return setsCache;

            try {
                debugLog('Loading TCGdex sets cache...');
                const response = await fetch('https://api.tcgdex.net/v2/en/sets', {
                    method: 'GET',
                    headers: { 'Accept': 'application/json' }
                });

                if (response.ok) {
                    setsCache = await response.json();
                    setsCacheLoaded = true;
                    debugLog(`✓ Loaded ${Array.isArray(setsCache) ? setsCache.length : 'unknown number of'} sets into cache`);
                    return setsCache;
                } else {
                    console.warn(`Failed to load sets cache: ${response.status} ${response.statusText}`);
                    setsCache = [];
                    setsCacheLoaded = true;
                    return setsCache;
                }
            } catch (error) {
                console.warn('Error loading sets cache:', error);
                setsCache = [];
                setsCacheLoaded = true;
                return setsCache;
            }
        }

        // Find sets by card count from cache
        function findSetsByCardCount(cardCount) {
            if (!setsCache || !Array.isArray(setsCache)) {
                return [];
            }

            const matchingSets = [];
            // Convert to number to handle leading zeros (078 becomes 78)
            const targetCount = parseInt(cardCount, 10);

            for (let i = 0; i < setsCache.length; i++) {
                const set = setsCache[i];
                const official = set.cardCount?.official || set.cardCount?.total;
                if (official && parseInt(official, 10) === targetCount) {
                    matchingSets.push(set);
                }
            }

            return matchingSets;
        }

        // Card number patterns - easy to add new ones
        const CARD_PATTERNS = [
            {
                name: 'numeric',
                regex: /\b(\d{1,4})\/(\d{1,4})\b/,
                description: 'Numeric pattern like 108/106'
            },
            {
                name: 'number-letter',
                regex: /\b(\d{1,4}[a-zA-Z])\/(\d{1,4})\b/,
                description: 'Number-letter pattern like 177a/168'
            },
            {
                name: 'letter-number',
                regex: /\b([A-Za-z]{1,4}\d{1,4})\/([A-Za-z]{1,4}\d{1,4})\b/i,
                description: 'Letter-number pattern like GG69/GG70 or Tg20/Tg30'
            },
            {
                name: 'hash-number',
                regex: /\#(\d{1,4})\b/,
                description: 'Hash pattern like #238'
            },
            {
                name: 'single-letter-number',
                regex: /\b([A-Za-z]{1,4})(\d{1,3})\b/i,
                description: 'Single letter-number pattern like SV107, RC5, DP45, SM158, SWSH184, TG03, XY121'
            },
            {
                name: 'hash-letter-number',
                regex: /\#([A-Za-z]{1,4})(\d{1,3})\b/i,
                description: 'Hash letter-number pattern like #SV107, #RC5'
            },
            {
                name: 'standalone-number',
                regex: /\b(\d{3}|\d{2}|\d{1})\b/,
                description: 'Standalone number like "164" (last resort - prefers 3 digits, then 2, then 1)',
                exclude: /\b(?:PSA|BGS|CGC|SGC)\s+(\d{1,2}(?:\.\d)?)\b/i // Exclude grade numbers like "PSA 10", "BGS 9.5"
            }
        ];

        // Extract title and card numbers from eBay listings
        function extractListingInfo(listingElement) {
            const info = {
                title: null,
                cardNumber: null,
                setNumber: null,
                fullCardNumber: null,
                matchedPattern: null,
                setName: null, // Extracted set name from title
                pokemonName: null // Extracted Pokemon name (when no card number found)
            };

            // Try multiple selectors at once for better performance
            const titleElement = listingElement.querySelector('.s-card__title .su-styled-text, [role="heading"] span, .s-item__title span, h3 span, .x-item-title-label span');

            if (titleElement) {
                info.title = cleanEbayTitle(titleElement.textContent.trim());

                // Special case: Check for Black Star Promo patterns first
                // "Mew ex SVP 053 Pokemon TCG Scarlet Violet 151 Black Star Promo" -> SVP = Scarlet & Violet Promo
                // "SWSH BLACK STAR PROMO ARCEUS V #204" -> SWSH Black Star Promo
                // Check if title contains "BLACK STAR PROMO" or just "PROMO" anywhere
                const hasPromo = /BLACK\s+STAR\s+PROMO|PROMO/i.test(info.title);
                if (hasPromo) {
                    // Look for promo prefix: SVP, SWSH, SM, XY, BW, DP, HGSS (check most specific first, EX last)
                    // Order matters! Check SVP before SM, SWSH before SM, etc.
                    const prefixMatch = info.title.match(/\b(SVP|SWSH|HGSS|XY|SM|BW|DP)\b/i) || 
                                       info.title.match(/\b(EX)\s+(?:BLACK\s+STAR\s+)?PROMO/i); // Only match EX if followed by PROMO
                    if (prefixMatch) {
                        const promoPrefix = prefixMatch[1].toUpperCase();
                        const promoSetNames = {
                            'SVP': 'SVP Black Star Promos',
                            'SWSH': 'SWSH Black Star Promos',
                            'SM': 'SM Black Star Promos',
                            'XY': 'XY Black Star Promos',
                            'BW': 'BW Black Star Promos',
                            'DP': 'DP Black Star Promos',
                            'HGSS': 'HGSS Black Star Promos',
                            'EX': 'EX Black Star Promos'
                        };
                        info.setName = promoSetNames[promoPrefix] || `${promoPrefix} Black Star Promos`;
                        debugLog(`🔍 Promo detected: "${info.setName}" (found "${prefixMatch[1]}" + "PROMO" in title)`);
                    }
                }

                // Special case: Check if "151" appears as a set name (not as card number)
                // "POKEMON 151 MEW EX #163" -> 151 is set name, 163 is card number
                // "Scarlet & Violet 151 Pokémon TCG" -> 151 is set name
                // "Poliwhirl Illustration Rare 2023 Scarlet & Violet 151 Pokémon TCG PSA 10" -> 151 is set name
                // "POKEMON SCARLET VIOLET MEW 151/165" -> 151 is card number (will be caught by numeric pattern)
                const set151Match = info.title.match(/(?:Scarlet\s*&\s*Violet|Pokemon|Pokémon)\s+151(?:\W|$)/i);
                const has151AsSlash = info.title.match(/151\s*\/\s*\d+/);
                
                if (set151Match && !has151AsSlash) {
                    // "151" appears after "Scarlet & Violet" or "Pokemon" - treat as set name
                    debugLog(`🔍 Special case: "151" detected as set name (found after Scarlet & Violet/Pokemon): ${set151Match[0]}`);
                    info.setName = "151";
                    // Mark that 151 should NOT be used as a card number
                    info.skip151AsCardNumber = true;
                }

                // Try each pattern until we find a match
                for (const pattern of CARD_PATTERNS) {
                    const match = info.title.match(pattern.regex);
                    if (match) {
                        // Check if this match should be excluded (for standalone-number pattern)
                        if (pattern.exclude) {
                            const excludeMatch = info.title.match(pattern.exclude);
                            if (excludeMatch && excludeMatch[1] === match[1]) {
                                debugLog(`🔍 Skipping "${match[1]}" - matched exclude pattern (grade number)`);
                                continue; // Skip this pattern, try next one
                            }
                        }
                        
                        // Skip if this is "151" standalone number and we've already identified it as a set name
                        if (pattern.name === 'standalone-number' && match[1] === '151' && info.skip151AsCardNumber) {
                            debugLog(`🔍 Skipping "151" as card number - already identified as set name`);
                            continue; // Skip this pattern, try next one
                        }
                        
                        if (pattern.name === 'single-letter-number' || pattern.name === 'hash-letter-number') {
                            // Special handling for single letter-number patterns: complete localId format
                            info.cardNumber = match[1] + match[2]; // "SV" + "107" = "SV107", "RC" + "5" = "RC5", "SM" + "241" = "SM241", etc.
                            info.setNumber = null; // No set number for these patterns
                            info.fullCardNumber = match[1] + match[2]; // "SV107", "RC5", "DP45", "SM241", "SWSH184", etc.
                        } else if (pattern.name === 'hash-number') {
                            // Special handling for hash patterns: #238 format
                            info.cardNumber = match[1]; // Just the number: "238"
                            info.setNumber = null; // No set number for hash patterns
                            info.fullCardNumber = match[0]; // Full match: "#238"
                            debugLog(`🔍 Hash-number pattern detected: ${match[0]}`);
                            
                            // Try to extract set name from title - check if already extracted "151" as set name
                            if (!info.setName) {
                                // Try to extract set name - special handling for PROMO sets
                                // For promos: "SWSH BLACK STAR PROMO ARCEUS V #204" -> "SWSH BLACK STAR PROMO"
                                // For sets: "SHROUDED FABLE KINGDRA EX #131" -> "SHROUDED FABLE"
                                let setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*((?:SWSH|SM|XY|SV|BW|DP|HGSS|EX)?\s*BLACK\s+STAR\s+PROMO(?:S)?)/i);
                                if (!setNameMatch) {
                                    // Extract 1-3 words after POKEMON/TCG (typical set names are 1-3 words)
                                    // "POKEMON SHROUDED FABLE KINGDRA" -> match "SHROUDED FABLE" (2 words)
                                    setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*([A-Z][A-Za-z&-]+(?:\s+[A-Z&][A-Za-z&-]+){0,2})/i);
                                }
                                if (setNameMatch) {
                                    info.setName = setNameMatch[1].trim();
                                    debugLog(`🔍 Extracted set name from title: "${info.setName}"`);
                                }
                            } else {
                                debugLog(`🔍 Using pre-extracted set name: "${info.setName}"`);
                            }
                        } else if (pattern.name === 'standalone-number') {
                            // Special handling for standalone numbers: "164 Secret PSA" format
                            info.cardNumber = match[1]; // Just the number: "164"
                            info.setNumber = null; // No set number for standalone patterns
                            info.fullCardNumber = match[1]; // Just the number
                            debugLog(`🔍 Standalone number detected: ${match[1]}`);
                            
                            // Try to extract set name from title - check if already extracted "151" as set name
                            if (!info.setName) {
                                // Try to extract set name - special handling for PROMO sets
                                // For promos: "SWSH BLACK STAR PROMO ARCEUS V #204" -> "SWSH BLACK STAR PROMO"
                                // For sets: "SHROUDED FABLE KINGDRA EX #131" -> "SHROUDED FABLE"
                                let setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*((?:SWSH|SM|XY|SV|BW|DP|HGSS|EX)?\s*BLACK\s+STAR\s+PROMO(?:S)?)/i);
                                if (!setNameMatch) {
                                    // Extract 1-3 words after POKEMON/TCG (typical set names are 1-3 words)
                                    // "POKEMON SHROUDED FABLE KINGDRA" -> match "SHROUDED FABLE" (2 words)
                                    setNameMatch = info.title.match(/(?:Pokemon|Pokémon)?\s*(?:TCG)?\s*([A-Z][A-Za-z&-]+(?:\s+[A-Z&][A-Za-z&-]+){0,2})/i);
                                }
                                if (setNameMatch) {
                                    info.setName = setNameMatch[1].trim();
                                    debugLog(`🔍 Extracted set name from title: "${info.setName}"`);
                                }
                            } else {
                                debugLog(`🔍 Using pre-extracted set name: "${info.setName}"`);
                            }
                        } else if (pattern.name === 'letter-number') {
                            // Special handling for letter-number slash patterns: GG44/GG70, RC24/RC25 format
                            info.cardNumber = match[1]; // Full card identifier: "GG44", "RC24", "Tg20"
                            // Check if the second part is also a letter-number combination
                            if (/^[A-Za-z]+\d+$/i.test(match[2])) {
                                // Both parts are letter-number (like GG44/GG70, Tg20/Tg30) - treat as localId search
                                info.setNumber = null;
                                debugLog(`🔍 Both parts are letter-number format: ${match[1]}/${match[2]} - using localId search`);
                            } else {
                                // Second part is numeric (like RC24/25) - extract set number
                                const setTotalMatch = match[2].match(/(\d+)$/);
                                info.setNumber = setTotalMatch ? setTotalMatch[1] : match[2];
                            }
                            info.fullCardNumber = match[0]; // Full match: "GG44/GG70", "RC24/RC25", "Tg20/Tg30"
                        } else {
                            // Standard handling for slash patterns: 108/106 format
                            info.cardNumber = match[1]; // First capture group
                            info.setNumber = match[2];   // Second capture group
                            info.fullCardNumber = match[0]; // Full match
                        }
                        info.matchedPattern = pattern.name; // Track which pattern matched
                        break; // Stop after first match
                    }
                }
                
                // Always try to extract set name for potential fallback (even if card number was found)
                if (info.title && !info.setName) {
                    debugLog(`🔍 Extracting set name from title for potential fallback`);
                    
                    // Common set names that might appear in titles (most specific first)
                    const setPatterns = [
                        // XY Series sets (specific names to avoid matching "EX" in Pokemon names)
                        /\b(Phantom Forces|Ancient Origins|BREAKthrough|BREAKpoint|Roaring Skies|Primal Clash)\b/i,
                        /\b(Steam Siege|Fates Collide|Generations|Evolutions|Flashfire|Furious Fists)\b/i,
                        // Sun & Moon Series
                        /\b(Sun & Moon|Burning Shadows|Crimson Invasion|Ultra Prism|Forbidden Light)\b/i,
                        /\b(Celestial Storm|Lost Thunder|Team Up|Unbroken Bonds|Unified Minds|Guardians Rising)\b/i,
                        // Sword & Shield Series
                        /\b(Cosmic Eclipse|Sword & Shield|Rebel Clash|Darkness Ablaze|Vivid Voltage)\b/i,
                        /\b(Shining Fates|Battle Styles|Chilling Reign|Evolving Skies|Fusion Strike)\b/i,
                        /\b(Brilliant Stars|Astral Radiance|Lost Origin|Silver Tempest)\b/i,
                        // Scarlet & Violet Series
                        /\b(Prismatic Evolutions?|Phantasmal Flames|Paldea Evolved|Obsidian Flames|Paradox Rift|Paldean Fates|Temporal Forces)\b/i,
                        /\b(Twilight Masquerade|Shrouded Fable|Stellar Crown|Surging Sparks|Mega Evolutions?)\b/i,
                        /\b(151)\b/i,
                        // Older series (specific names only, no generic "EX")
                        /\b(XY|Black & White|HeartGold & SoulSilver|Diamond & Pearl)\b/i,
                        // EX series sets (must have "EX" followed by set name, not just "EX")
                        /\b(EX\s+(?:Deoxys|Emerald|Unseen Forces|Delta Species|Legend Maker|Holon Phantoms|Crystal Guardians|Dragon Frontiers|Power Keepers|Team Rocket Returns|FireRed & LeafGreen|Team Magma vs Team Aqua|Hidden Legends|Ruby & Sapphire|Sandstorm))\b/i,
                    ];
                    
                    // Try to find a set name
                    for (const pattern of setPatterns) {
                        const setMatch = info.title.match(pattern);
                        if (setMatch) {
                            info.setName = setMatch[1];
                            debugLog(`  Found set name: "${info.setName}"`);
                            debugLog(`  Will match full title against card names in this set`);
                            break;
                        }
                    }
                }
            }

            return info;
        }

        // Search for a card by matching eBay title against card names in a specific set
        async function searchByTitleInSet(setNameHint, ebayTitle) {
            try {
                debugLog(`\n🔍 Matching title against cards in set: "${setNameHint}"`);
                debugLog(`  eBay title: "${ebayTitle}"`);
                
                // Normalize set name and find matching set IDs
                await loadSetsCache();
                
                const normalizedSetHint = setNameHint.toUpperCase()
                    .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                    .replace(/PROMOS?$/, 'PROMO');
                
                const matchingSets = setsCache?.filter(set => {
                    const normalizedSetName = set.name.toUpperCase()
                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                        .replace(/PROMOS?$/, 'PROMO');
                    return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                }) || [];
                
                if (matchingSets.length === 0) {
                    debugLog(`  No matching sets found for hint: "${setNameHint}"`);
                    return null;
                }
                
                debugLog(`  Found ${matchingSets.length} matching set(s)`);
                
                // Normalize eBay title for comparison (remove common descriptors but keep Pokemon name)
                const normalizedTitle = ebayTitle.toUpperCase()
                    .replace(/POKÉMON|POKEMON|TCG/gi, '')
                    .replace(/\b\d{4}\b/g, '') // Remove years
                    .replace(/\b(?:PSA|BGS|CGC|SGC)\s+\d+(?:\.\d)?\b/gi, '') // Remove grades
                    .replace(/\b(?:ENGLISH|JAPANESE|KOREAN|GERMAN|FRENCH|ITALIAN|SPANISH)\b/gi, '') // Remove languages
                    .replace(/[^A-Z0-9\s]/g, ' ') // Replace special chars with spaces
                    .replace(/\s+/g, ' ')
                    .trim();
                
                debugLog(`  Normalized title: "${normalizedTitle}"`);
                
                // Try each matching set
                for (const set of matchingSets) {
                    debugLog(`\n  Fetching cards from set: ${set.name} (${set.id})`);
                    
                    const response = await fetch(`https://api.tcgdex.net/v2/en/sets/${set.id}`);
                    if (!response.ok) continue;
                    
                    const setData = await response.json();
                    const cards = setData.cards || [];
                    
                    debugLog(`    Got ${cards.length} cards from set`);
                    
                    // Score each card based on title similarity
                    const scoredCards = [];
                    
                    for (const card of cards) {
                        const cardName = (card.name || '').toUpperCase();
                        const normalizedCardName = cardName
                            .replace(/[^A-Z0-9\s]/g, ' ')
                            .replace(/\s+/g, ' ')
                            .trim();
                        
                        // Calculate how much of the card name appears in the title
                        const cardNameWords = normalizedCardName.split(' ').filter(w => w.length > 0);
                        const titleWords = normalizedTitle.split(' ').filter(w => w.length > 0);
                        
                        // Count matching words
                        let matchedWords = 0;
                        for (const cardWord of cardNameWords) {
                            if (titleWords.some(titleWord => 
                                titleWord === cardWord || 
                                titleWord.includes(cardWord) || 
                                cardWord.includes(titleWord)
                            )) {
                                matchedWords++;
                            }
                        }
                        
                        // Score is percentage of card name words found in title
                        const score = cardNameWords.length > 0 ? (matchedWords / cardNameWords.length) * 100 : 0;
                        
                        if (score >= 50) { // Only consider cards with at least 50% word match
                            scoredCards.push({ card, score, cardName });
                            debugLog(`      ${cardName} - Match: ${score.toFixed(0)}% (${matchedWords}/${cardNameWords.length} words)`);
                        }
                    }
                    
                    if (scoredCards.length === 0) {
                        debugLog(`    No good matches found in this set`);
                        continue;
                    }
                    
                    // Sort by score descending
                    scoredCards.sort((a, b) => b.score - a.score);
                    
                    // Get top matches (within 10% of best score)
                    const bestScore = scoredCards[0].score;
                    const topMatches = scoredCards.filter(sc => sc.score >= bestScore - 10);
                    
                    debugLog(`\n    Top ${topMatches.length} match(es):`);
                    topMatches.forEach((sc, i) => {
                        debugLog(`      ${i + 1}. ${sc.cardName} - ${sc.score.toFixed(0)}%`);
                    });
                    
                    // If multiple top matches, use rarity indicators and pricing to pick the best one
                    if (topMatches.length > 1) {
                        debugLog(`\n    Fetching full details for tie-breaking...`);
                        
                        // Check if title has rarity indicators - be specific to distinguish SIR from IR
                        const hasMUR = /\b(MEGA\s+ULTRA\s+RARE|MUR)\b/i.test(ebayTitle);
                        const hasSIR = /\b(SPECIAL\s+ILLUSTRATION\s+RARE|SIR)\b/i.test(ebayTitle);
                        const hasIR = /\b(ILLUSTRATION\s+RARE|IR)\b/i.test(ebayTitle);
                        const hasSpecialRarity = hasMUR || hasSIR || hasIR;
                        
                        // Determine priority - MUR > SIR > IR (if multiple mentioned)
                        // Default to SIR if no rarity specified
                        let targetRarity = 'SIR'; // Default to SIR
                        if (hasMUR) {
                            targetRarity = 'MUR';
                        } else if (hasSIR) {
                            targetRarity = 'SIR';
                        } else if (hasIR) {
                            targetRarity = 'IR';
                        }
                        
                        if (hasSpecialRarity) {
                            debugLog(`    🌟 Title indicates special rarity (${targetRarity}) - will prioritize matching card`);
                        } else {
                            debugLog(`    🌟 No rarity specified - defaulting to ${targetRarity}`);
                        }
                        
                        const fullCards = await Promise.all(
                            topMatches.map(async sc => {
                                const detailResponse = await fetch(`https://api.tcgdex.net/v2/en/cards/${sc.card.id}`);
                                if (!detailResponse.ok) return null;
                                const fullCard = await detailResponse.json();
                                const titleSimilarity = calculateTitleSimilarity(ebayTitle, fullCard.name);
                                
                                // Check if card rarity matches the specific type we're looking for
                                const cardRarity = (fullCard.rarity?.name || fullCard.rarity || '').toUpperCase();
                                debugLog(`      Fetched ${fullCard.name} #${fullCard.localId}`);
                                debugLog(`        Rarity object:`, fullCard.rarity);
                                debugLog(`        Rarity string: "${cardRarity}"`);
                                
                                // Determine card's rarity type and priority
                                let cardRarityType = null;
                                let cardRarityPriority = 0;
                                
                                if (cardRarity.includes('MEGA') && cardRarity.includes('ULTRA')) {
                                    cardRarityType = 'MUR';
                                    cardRarityPriority = 3; // Highest priority
                                } else if (cardRarity.includes('SPECIAL') && cardRarity.includes('ILLUSTRATION')) {
                                    cardRarityType = 'SIR';
                                    cardRarityPriority = 2;
                                } else if (cardRarity.includes('ILLUSTRATION') && !cardRarity.includes('SPECIAL')) {
                                    cardRarityType = 'IR';
                                    cardRarityPriority = 1;
                                }
                                
                                // Match rarity based on target (if specified) or use priority
                                let rarityMatch = false;
                                if (targetRarity) {
                                    // Specific rarity requested in title
                                    rarityMatch = (cardRarityType === targetRarity);
                                } else if (cardRarityType) {
                                    // No specific rarity in title, but card has special rarity
                                    rarityMatch = true; // All special rarities match when not specified
                                }
                                
                                debugLog(`        Rarity type: ${cardRarityType || 'none'} (priority: ${cardRarityPriority})`);
                                debugLog(`        Rarity match: ${rarityMatch} (target: ${targetRarity || 'any'})`);
                                
                                return { 
                                    card: fullCard, 
                                    similarity: titleSimilarity,
                                    rarity: cardRarity,
                                    rarityType: cardRarityType,
                                    rarityPriority: cardRarityPriority,
                                    rarityMatch: rarityMatch
                                };
                            })
                        );
                        
                        const validCards = fullCards.filter(fc => fc !== null);
                        if (validCards.length > 0) {
                            // Always prefer cards with matching rarity (including default SIR)
                            const specialCards = validCards.filter(fc => fc.rarityMatch);
                            if (specialCards.length > 0) {
                                debugLog(`    Found ${specialCards.length} card(s) matching target rarity (${targetRarity}):`);
                                specialCards.forEach(fc => {
                                    debugLog(`      ${fc.card.name} - ${fc.rarity} (priority ${fc.rarityPriority})`);
                                });
                                
                                // Sort by rarity priority first (MUR > SIR > IR), then by similarity
                                specialCards.sort((a, b) => 
                                    (b.rarityPriority - a.rarityPriority) || (b.similarity - a.similarity)
                                );
                                const best = specialCards[0];
                                debugLog(`    🎯 BEST MATCH (${best.rarityType || 'rarity'} priority): ${best.card.name} - ${best.rarity} (${best.similarity.toFixed(1)}%)`);
                                return best.card;
                            }
                            
                            // Otherwise, sort by similarity
                            validCards.sort((a, b) => b.similarity - a.similarity);
                            const best = validCards[0];
                            debugLog(`    🎯 BEST MATCH: ${best.card.name} - ${best.rarity} (${best.similarity.toFixed(1)}%)`);
                            return best.card;
                        }
                    }
                    
                    // Single best match - fetch full details
                    const bestMatch = topMatches[0];
                    debugLog(`    Fetching full details for: ${bestMatch.cardName}`);
                    const detailResponse = await fetch(`https://api.tcgdex.net/v2/en/cards/${bestMatch.card.id}`);
                    if (!detailResponse.ok) continue;
                    
                    const fullCard = await detailResponse.json();
                    debugLog(`    ✅ Found card: ${fullCard.name} #${fullCard.localId}`);
                    return fullCard;
                }
                
                debugLog(`  ⚠ No matching cards found in any set`);
                return null;
                
            } catch (error) {
                console.error('Error in searchByTitleInSet:', error);
                return null;
            }
        }

        // Try to fetch cards directly from a specific set
        async function tryDirectSetSearch(cardNumber, setNameHint, ebayTitle = '') {
            try {
                // Normalize set name and find matching set IDs
                await loadSetsCache();
                
                const normalizedSetHint = setNameHint.toUpperCase()
                    .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                    .replace(/PROMOS?$/, 'PROMO');
                
                const matchingSets = setsCache?.filter(set => {
                    const normalizedSetName = set.name.toUpperCase()
                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                        .replace(/PROMOS?$/, 'PROMO');
                    return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                }) || [];
                
                if (matchingSets.length === 0) {
                    debugLog(`  No matching sets found for hint: "${setNameHint}"`);
                    return null;
                }
                
                debugLog(`  🔍 Trying direct set search in: ${matchingSets.map(s => s.name).join(', ')}`);
                
                // Try each matching set
                for (const set of matchingSets) {
                    try {
                        const setUrl = `https://api.tcgdex.net/v2/en/sets/${set.id}`;
                        debugLog(`    Fetching all cards from set ${set.id}: ${setUrl}`);
                        
                        const response = await fetch(setUrl);
                        if (!response.ok) continue;
                        
                        const setData = await response.json();
                        const cards = setData.cards || [];
                        
                        debugLog(`    ✓ Loaded ${cards.length} cards from ${set.name}`);
                        
                        // Find card with matching localId
                        const matchingCards = cards.filter(card => 
                            card.localId === cardNumber || 
                            card.localId === cardNumber.toUpperCase() ||
                            parseInt(card.localId, 10) === parseInt(cardNumber, 10)
                        );
                        
                        if (matchingCards.length > 0) {
                            debugLog(`    ✓ Found ${matchingCards.length} card(s) with localId ${cardNumber} in ${set.name}`);
                            
                            // If multiple matches or we have eBay title, fetch full details for similarity matching
                            if (matchingCards.length > 1 && ebayTitle) {
                                // Fetch full details for each matching card
                                const fullCards = [];
                                for (const card of matchingCards) {
                                    try {
                                        const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${card.id}`;
                                        const detailResponse = await fetch(cardDetailUrl);
                                        if (detailResponse.ok) {
                                            const fullCard = await detailResponse.json();
                                            fullCards.push(fullCard);
                                        }
                                    } catch (err) {
                                        debugLog(`      Error fetching details for ${card.id}`);
                                    }
                                }
                                
                                // Use similarity matching to find best card
                                let bestMatch = fullCards[0];
                                let bestSimilarity = 0;
                                
                                fullCards.forEach(card => {
                                    const similarity = calculateTitleSimilarity(ebayTitle, card.name);
                                    debugLog(`      ${card.name}: ${(similarity * 100).toFixed(1)}% similarity`);
                                    if (similarity > bestSimilarity) {
                                        bestSimilarity = similarity;
                                        bestMatch = card;
                                    }
                                });
                                
                                debugLog(`    🎯 Best match: ${bestMatch.name} (${(bestSimilarity * 100).toFixed(1)}%)`);
                                return bestMatch;
                            } else {
                                // Single match or no title - fetch full details and return
                                const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${matchingCards[0].id}`;
                                const detailResponse = await fetch(cardDetailUrl);
                                if (detailResponse.ok) {
                                    const fullCard = await detailResponse.json();
                                    debugLog(`    ✓ ${fullCard.name} from ${fullCard.set?.name}`);
                                    return fullCard;
                                }
                            }
                        }
                    } catch (setError) {
                        debugLog(`    Error fetching set ${set.id}:`, setError.message);
                    }
                }
                
                return null; // No card found in any matching set
            } catch (error) {
                debugLog(`  Error in direct set search:`, error.message);
                return null;
            }
        }

        // Search TCGdex API by localId when no set number is available
        async function searchTCGdexByLocalId(cardNumber, ebayTitle = '', setNameHint = null, skipFallback = false) {
            try {
                if (setNameHint) {
                    debugLog(`🔍 Using set name hint for filtering: "${setNameHint}"`);
                    
                    // OPTIMIZATION: If we have a strong set hint, try to fetch directly from that set first
                    // This is much faster than searching by localId and filtering
                    const directSetResult = await tryDirectSetSearch(cardNumber, setNameHint, ebayTitle);
                    if (directSetResult) {
                        debugLog(`✅ Found card via direct set search!`);
                        return directSetResult;
                    }
                    debugLog(`⚠ Direct set search didn't find card, falling back to localId search`);
                }
                
                // Create variations of the card number to handle zero-padding issues
                let cardNumberVariations;

                // For promo cards (SWSH291, SM241, etc.), don't create variations - use exactly as-is
                if (/^(SWSH|SM)[0-9]+$/i.test(cardNumber)) {
                    debugLog(`  Promo card detected: ${cardNumber} - using exact match only`);
                    cardNumberVariations = [cardNumber.toUpperCase()]; // Normalize to uppercase for API
                } else if (/^[A-Za-z]+\d+$/i.test(cardNumber)) {
                    // For letter-number patterns (RC24, GG69, TG20, etc.), normalize to uppercase
                    const normalizedCardNumber = cardNumber.toUpperCase();
                    debugLog(`  Letter-number pattern detected: ${cardNumber} - normalizing to uppercase: ${normalizedCardNumber}`);
                    cardNumberVariations = [normalizedCardNumber];
                } else {
                    // For regular numeric cards, create variations
                    const baseCardNumber = cardNumber.replace(/[a-zA-Z]+$/g, ''); // Remove trailing letters
                    cardNumberVariations = [
                        baseCardNumber, // Original: "238"
                        parseInt(baseCardNumber, 10).toString(), // Remove leading zeros: "238"
                        baseCardNumber.padStart(3, '0') // Add leading zeros: "238" -> "238"
                    ].filter(variation => variation && !isNaN(variation) && variation !== 'NaN'); // Filter out invalid variations
                }

                // Remove duplicates and filter out invalid entries
                const uniqueCardNumbers = [...new Set(cardNumberVariations)].filter(num => num && num !== 'NaN');
                debugLog(`  Card number variations to try: ${uniqueCardNumbers.join(', ')}`);

                let allFoundCards = [];

                // Try each card number variation
                for (const cardNum of uniqueCardNumbers) {
                    try {
                        const localIdUrl = `https://api.tcgdex.net/v2/en/cards?localId=${cardNum}`;
                        debugLog(`  Fetching cards with localId ${cardNum}: ${localIdUrl}`);

                        const response = await fetch(localIdUrl, {
                            method: 'GET',
                            headers: {
                                'Accept': 'application/json',
                                'Content-Type': 'application/json'
                            }
                        });

                        if (response.ok) {
                            const cards = await response.json();

                            if (Array.isArray(cards) && cards.length > 0) {
                                debugLog(`    ✓ Found ${cards.length} card(s) with localId ${cardNum}`);

                                // If we have a set name hint, filter cards by set first to reduce API calls
                                let cardsToProcess = cards;
                                if (setNameHint && cards.length > 10) {
                                    // Normalize both hint and set names for matching
                                    const normalizedSetHint = setNameHint.toUpperCase()
                                        .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                        .replace(/PROMOS?$/, 'PROMO'); // Normalize "PROMO" vs "PROMOS"
                                    debugLog(`      Filtering by set hint: "${setNameHint}" (normalized: "${normalizedSetHint}")`);
                                    
                                    // Load sets cache to get set IDs
                                    await loadSetsCache();
                                    
                                    // Find matching set IDs
                                    const matchingSets = setsCache?.filter(set => {
                                        const normalizedSetName = set.name.toUpperCase()
                                            .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                            .replace(/PROMOS?$/, 'PROMO'); // Normalize "PROMO" vs "PROMOS"
                                        return normalizedSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedSetName);
                                    }) || [];
                                    
                                    if (matchingSets.length > 0) {
                                        const matchingSetIds = matchingSets.map(s => s.id);
                                        debugLog(`      Found matching sets: ${matchingSets.map(s => s.name).join(', ')}`);
                                        const filteredCards = cards.filter(card => matchingSetIds.some(setId => card.id.startsWith(setId + '-')));
                                        
                                        if (filteredCards.length > 0) {
                                            cardsToProcess = filteredCards;
                                            debugLog(`      Filtered to ${cardsToProcess.length} card(s) from matching sets`);
                                        } else {
                                            debugLog(`      ⚠ Filtering resulted in 0 cards - ignoring set hint and using all ${cards.length} cards`);
                                            cardsToProcess = cards;
                                        }
                                    }
                                }

                                // Add cards to list for similarity matching (use basic data for speed)
                                for (const card of cardsToProcess) {
                                    if (card && card.id) {
                                        allFoundCards.push(card);
                                    }
                                }
                                
                                if (cardsToProcess.length > 0 && cardsToProcess.length <= 10) {
                                    debugLog(`      Found ${cardsToProcess.length} card(s) to compare:${cardsToProcess.map(c => ' ' + c.name).join(',')}`);
                                }
                            } else {
                                debugLog(`    ✗ No cards found with localId ${cardNum}`);
                            }
                        } else {
                            debugLog(`    ✗ LocalId ${cardNum} not found: ${response.status} ${response.statusText}`);
                        }
                    } catch (error) {
                        debugLog(`    ✗ Error fetching localId ${cardNum}:`, error);
                    }
                }

                if (allFoundCards.length > 0) {
                    debugLog(`\n✓ Found ${allFoundCards.length} total card(s) with localId ${cardNumber}`);

                    // If multiple cards found and we have an eBay title, find the best match
                    let bestMatch = allFoundCards[0]; // Default to first card

                    if (allFoundCards.length > 1 && (ebayTitle || setNameHint)) {
                        debugLog(`\nComparing ${allFoundCards.length} cards against eBay title${setNameHint ? ' and set name' : ''}...`);

                        let bestSimilarity = -1;
                        let bestCard = null;
                        const MINIMUM_SIMILARITY_THRESHOLD = 0.25; // 25% minimum similarity

                        // First pass: Check for set identifier matches in eBay title
                        const ebayTitleUpper = ebayTitle ? ebayTitle.toUpperCase() : '';
                        const setNameHintUpper = setNameHint ? setNameHint.toUpperCase() : '';
                        let setMatchFound = false;

                        allFoundCards.forEach((card, index) => {
                            const similarity = ebayTitle ? calculateTitleSimilarity(ebayTitle, card.name) : 0;

                            // Extract set identifier from card ID (e.g., "svp-141" -> "SVP", "A4-141" -> "A4")
                            const setMatch = card.id.match(/^([^-]+)-/);
                            const setIdentifier = setMatch ? setMatch[1].toUpperCase() : null;
                            const cardSetName = (card.set?.name || '').toUpperCase();

                            // Normalize set names for comparison (remove prefixes and normalize PROMO/PROMOS)
                            const normalizedSetHint = setNameHintUpper
                                .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                .replace(/PROMOS?$/, 'PROMO');
                            const normalizedCardSetName = cardSetName
                                .replace(/^(EX|XY|SM|SWSH|SV|BW|DP|HGSS)\s+/, '')
                                .replace(/PROMOS?$/, 'PROMO');

                            // Check if the set identifier or set name matches
                            let setBonus = 0;
                            if (setIdentifier && ebayTitleUpper.includes(setIdentifier)) {
                                setBonus = 0.5; // Large bonus for set ID match in title
                                setMatchFound = true;
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}% + SET MATCH (${setIdentifier}) = ${((similarity + setBonus) * 100).toFixed(1)}%`);
                            } else if (setNameHint && (normalizedCardSetName.includes(normalizedSetHint) || normalizedSetHint.includes(normalizedCardSetName))) {
                                setBonus = 0.6; // Even larger bonus for set name match
                                setMatchFound = true;
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}% + SET NAME MATCH = ${((similarity + setBonus) * 100).toFixed(1)}%`);
                            } else {
                                debugLog(`  ${index + 1}. ${card.name} from ${card.set?.name || 'Unknown Set'} - Similarity: ${(similarity * 100).toFixed(1)}%`);
                            }

                            const totalScore = similarity + setBonus;
                            if (totalScore > bestSimilarity) {
                                bestSimilarity = totalScore;
                                bestCard = card;
                            }
                        });

                        if (bestCard) {
                            bestMatch = bestCard;
                        }

                        // Check if best match meets minimum threshold (or has set match)
                        if (bestSimilarity >= MINIMUM_SIMILARITY_THRESHOLD || setMatchFound) {
                            const matchType = setMatchFound ? "with SET MATCH" : "by similarity";
                            debugLog(`\n🎯 BEST MATCH: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'} (${(bestSimilarity * 100).toFixed(1)}% total score - ${matchType})`);
                        } else {
                            debugLog(`\n❌ NO GOOD MATCH: Best similarity was ${(bestSimilarity * 100).toFixed(1)}% (below ${(MINIMUM_SIMILARITY_THRESHOLD * 100).toFixed(1)}% threshold)`);
                            debugLog(`🔄 Falling back to highest similarity card: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'}`);
                            // bestMatch already contains the highest similarity card (bestCard)
                            bestSimilarity = -1; // Reset similarity to indicate poor match
                        }
                    } else if (allFoundCards.length === 1) {
                        debugLog(`\n🎯 SINGLE MATCH FOUND: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'}`);
                    } else {
                        debugLog(`\n🎯 USING FIRST MATCH: ${bestMatch.name} from ${bestMatch.set?.name || 'Unknown Set'} (no eBay title for comparison)`);
                    }

                    // Fetch full details for the best match only (optimization - 1 API call instead of potentially hundreds)
                    debugLog(`\n📡 Fetching full details for best match: ${bestMatch.name}`);
                    try {
                        const cardDetailUrl = `https://api.tcgdex.net/v2/en/cards/${bestMatch.id}`;
                        const cardDetailResponse = await fetch(cardDetailUrl);
                        
                        if (cardDetailResponse.ok) {
                            const fullCard = await cardDetailResponse.json();
                            bestMatch = fullCard; // Replace with full card data
                            debugLog(`✓ Loaded complete card data with set info: ${fullCard.set?.name || 'Unknown Set'}`);
                        } else {
                            debugLog(`⚠ Could not fetch full details (${cardDetailResponse.status}), using basic data`);
                        }
                    } catch (error) {
                        debugLog(`⚠ Error fetching full details:`, error.message, '- using basic data');
                    }

                    // Display the best match
                    debugLog(`\n🎉 FINAL RESULT FOR localId ${cardNumber}:`);
                    debugLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
                    debugLog(`Name: ${bestMatch.name}`);
                    debugLog(`Card Number: ${bestMatch.localId}/${bestMatch.set?.cardCount?.official || bestMatch.set?.cardCount?.total || '???'}`);
                    debugLog(`Set: ${bestMatch.set?.name || 'Unknown Set'} (${bestMatch.set?.id || 'unknown-id'})`);
                    debugLog(`Rarity: ${bestMatch.rarity || 'Unknown'}`);
                    debugLog(`HP: ${bestMatch.hp || 'N/A'}`);
                    debugLog(`Types: ${bestMatch.types?.join(', ') || 'Unknown'}`);

                    // Price information (simplified version)
                    if (bestMatch.pricing && bestMatch.pricing.tcgplayer) {
                        debugLog(`\n💰 TCGPLAYER PRICES:`);
                        const tcgPricing = bestMatch.pricing.tcgplayer;

                        debugLog(`  Last Updated: ${tcgPricing.updated || 'Unknown'}`);
                        debugLog(`  Currency: ${tcgPricing.unit || 'USD'}`);

                        if (tcgPricing['holofoil']) {
                            const holo = tcgPricing['holofoil'];
                            debugLog(`  Holofoil - Low: $${holo.lowPrice || 'N/A'}, Mid: $${holo.midPrice || 'N/A'}, High: $${holo.highPrice || 'N/A'}, Market: $${holo.marketPrice || 'N/A'}`);
                        }

                        if (tcgPricing['normal']) {
                            const normal = tcgPricing['normal'];
                            debugLog(`  Normal - Low: $${normal.lowPrice || 'N/A'}, Mid: $${normal.midPrice || 'N/A'}, High: $${normal.highPrice || 'N/A'}, Market: $${normal.marketPrice || 'N/A'}`);
                        }
                    } else {
                        debugLog(`💰 TCGPLAYER PRICES: Not available`);
                    }

                    // High quality image
                    const imageUrl = bestMatch.image?.high || bestMatch.image?.large || bestMatch.images?.large || bestMatch.imageUrl || bestMatch.image;
                    if (imageUrl) {
                        debugLog(`\n🖼️ High-Res Image: ${imageUrl}`);
                    }

                    // Market URLs
                    if (bestMatch.tcgPlayer?.url) {
                        debugLog(`🔗 TCGPlayer: ${bestMatch.tcgPlayer.url}`);
                    }

                    debugLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);

                    // Return the best match
                    return bestMatch;

                } else {
                    debugLog(`No cards found with localId ${cardNumber}`);
                    debugLog('This could mean:');
                    debugLog('1. The card number doesn\'t exist in the API');
                    debugLog('2. The number format is different than expected');
                    debugLog('3. API connectivity issues');
                    
                    // Only open fallback search if not skipped (e.g., when called from PriceCharting URL generation)
                    if (!skipFallback) {
                        debugLog('\nTrying fallback search...');
                        await searchTCGdexFallback(cardNumber);
                    } else {
                        debugLog('Skipping fallback search (silent fail for PriceCharting)');
                    }
                    return null;
                }

            } catch (error) {
                console.error('Error searching TCGdex by localId:', error);
                debugLog('API request failed. This could be due to:');
                debugLog('1. Network connectivity issues');
                debugLog('2. API being temporarily down');
                debugLog('3. Rate limiting');

                debugLog('\nOpening TCGdex website as fallback...');
                const searchWindow = window.open(`https://www.tcgdex.dev/cards?search=${encodeURIComponent(cardNumber)}`, '_blank');
                if (searchWindow) {
                    debugLog('Opened TCGdex website search in new tab');
                }
                return null;
            }
        }

        // Add Google PriceCharting search button
        function addGooglePriceChartingButton(listingElement, cardNumber) {
            if (listingElement.querySelector('.google-pricecharting-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement || !cardNumber) return;

            const button = document.createElement('a');
            button.className = 'google-pricecharting-btn';
            button.href = `https://www.google.com/search?q=PriceCharting+${encodeURIComponent(cardNumber)}`;
            button.target = '_blank';
            button.textContent = '🔍';
            button.title = `Google search PriceCharting for ${cardNumber}`;

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '8px',
                padding: '2px 6px',
                background: '#e74c3c',
                color: 'white',
                textDecoration: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#google-pc-button-styles')) {
                const style = document.createElement('style');
                style.id = 'google-pc-button-styles';
                style.textContent = '.google-pricecharting-btn:hover { background-color: #c0392b !important; }';
                document.head.appendChild(style);
            }

            priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
        }

        // Enhanced PriceCharting Direct button with data sharing
        function addPriceChartingDirectButton(listingElement, cardNumber, setNumber) {
            if (listingElement.querySelector('.pricecharting-direct-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement) return;

            const button = document.createElement('button');
            button.className = 'pricecharting-direct-btn';
            button.textContent = cardNumber ? 'PC✓' : '🔍 PC';
            button.title = cardNumber ? `Direct PriceCharting link for ${cardNumber}` : 'Search PriceCharting by card name';

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '4px',
                padding: '2px 6px',
                background: '#9b59b6',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                cursor: 'pointer',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#pricecharting-direct-button-styles')) {
                const style = document.createElement('style');
                style.id = 'pricecharting-direct-button-styles';
                style.textContent = '.pricecharting-direct-btn:hover { background-color: #8e44ad !important; }';
                document.head.appendChild(style);
            }

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();

                const originalText = button.textContent;
                button.textContent = '...';
                button.disabled = true;

                try {
                    await openPriceChartingDirectWithSharing(listingElement, cardNumber, setNumber);
                } finally {
                    button.textContent = originalText;
                    button.disabled = false;
                }
            });

            // Insert after the PC📊 (View) button
            const pcViewButton = listingElement.querySelector('.pricecharting-view-btn');
            if (pcViewButton && pcViewButton.parentNode) {
                pcViewButton.parentNode.insertBefore(button, pcViewButton.nextSibling);
            } else {
                // Fallback insertion logic
                const googlePcButton = listingElement.querySelector('.google-pricecharting-btn');
                if (googlePcButton) {
                    googlePcButton.parentNode.insertBefore(button, googlePcButton.nextSibling);
                } else {
                    priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
                }
            }
        }

        // Add PriceCharting View button to open the full pricing page (without #full-prices hash)
        function addPriceChartingViewButton(listingElement, cardNumber, setNumber) {
            if (listingElement.querySelector('.pricecharting-view-btn')) return;

            const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
            if (!priceElement || !cardNumber) return;

            const button = document.createElement('button');
            button.className = 'pricecharting-view-btn';
            button.textContent = 'PC📊';
            button.title = `View full PriceCharting page for ${cardNumber}`;

            Object.assign(button.style, {
                display: 'inline-block',
                marginLeft: '4px',
                padding: '2px 6px',
                background: '#e67e22',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                fontSize: '11px',
                fontWeight: 'bold',
                verticalAlign: 'middle',
                cursor: 'pointer',
                transition: 'background-color 0.2s'
            });

            if (!document.querySelector('#pricecharting-view-button-styles')) {
                const style = document.createElement('style');
                style.id = 'pricecharting-view-button-styles';
                style.textContent = '.pricecharting-view-btn:hover { background-color: #d35400 !important; }';
                document.head.appendChild(style);
            }

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                e.stopPropagation();

                const originalText = button.textContent;
                button.textContent = '...';
                button.disabled = true;

                try {
                    await openPriceChartingViewPage(listingElement, cardNumber, setNumber);
                } finally {
                    button.textContent = originalText;
                    button.disabled = false;
                }
            });

            // Insert after the PC✓ button
            const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
            if (pcButton && pcButton.parentNode) {
                pcButton.parentNode.insertBefore(button, pcButton.nextSibling);
            } else {
                // Fallback insertion logic
                const googlePcButton = listingElement.querySelector('.google-pricecharting-btn');
                if (googlePcButton) {
                    googlePcButton.parentNode.insertBefore(button, googlePcButton.nextSibling);
                } else {
                    priceElement.parentNode.insertBefore(button, priceElement.nextSibling);
                }
            }
        }

        // Calculate title similarity function (referenced in searchTCGdex)
        function calculateTitleSimilarity(ebayTitle, cardName) {
            if (!ebayTitle || !cardName) return 0;

            // Normalize "Mega" and "M " before comparison
            let title1 = ebayTitle.toLowerCase().replace(/\bmega[\s-]*/g, 'm ');
            let title2 = cardName.toLowerCase();

            // Extract Pokemon name from card name (before any modifiers like "ex", "V", "VSTAR", etc.)
            const pokemonNameMatch = title2.match(/^(.*?)\s*(?:ex|v|vstar|vmax|gx|tag team|&|break|prime|lv\.?x|δ|★|prism star)?\s*$/i);
            const pokemonName = pokemonNameMatch ? pokemonNameMatch[1].trim() : title2;

            // Check for exact Pokemon name match (case-insensitive)
            // Special handling for single-letter names (like "N") or trainer names
            let isMatch = false;
            if (pokemonName.length === 1) {
                // For single letter names, look for the letter as a standalone word
                const regex = new RegExp(`\\b${pokemonName}\\b`, 'i');
                isMatch = regex.test(title1);
                if (isMatch) {
                    debugLog(`🎯 Single-letter Pokemon match found: "${pokemonName}" as standalone word in "${title1}"`);
                }
            } else if (pokemonName.length >= 2) {
                // For multi-character names, use substring matching
                isMatch = title1.includes(pokemonName);
                if (isMatch) {
                    debugLog(`🎯 Direct Pokemon match found: "${pokemonName}" in "${title1}"`);
                }
            }

            if (isMatch) {
                let score = 0.85; // High base score for Pokemon name match

                // Bonus points for matching modifiers
                if (title2.includes('ex') && title1.includes('ex')) score += 0.1;
                if (title2.includes(' v') && title1.includes(' v')) score += 0.1;
                if (title2.includes('vstar') && title1.includes('vstar')) score += 0.1;
                if (title2.includes('vmax') && title1.includes('vmax')) score += 0.1;
                if (title2.includes('gx') && title1.includes('gx')) score += 0.1;

                return Math.min(score, 1.0);
            }

            // Fallback to word matching for non-Pokemon names or when Pokemon name doesn't match
            const words1 = title1.split(/\s+/);
            const words2 = title2.split(/\s+/);

            let matches = 0;
            let significantMatches = 0;

            words2.forEach(word => {
                if (word.length > 2) { // Only count significant words
                    if (words1.some(w => w === word)) {
                        matches++;
                        significantMatches++;
                    } else if (words1.some(w => w.includes(word) || word.includes(w))) {
                        matches += 0.5; // Partial match
                    }
                }
            });

            return significantMatches > 0 ? matches / Math.max(words1.length, words2.length) : 0;
        }

        // Fallback search function (referenced in searchTCGdex)
        async function searchTCGdexFallback(cardNumber) {
            debugLog(`🔄 Fallback: Opening TCGdex website search for ${cardNumber}`);
            const searchWindow = window.open(`https://www.tcgdex.dev/cards?search=${encodeURIComponent(cardNumber)}`, '_blank');
            if (searchWindow) {
                debugLog('Opened TCGdex website search in new tab');
            }
            return null;
        }

        // Function to open PriceCharting view page (without #full-prices hash)
        async function openPriceChartingViewPage(listingElement, cardNumber, setNumber) {
            try {
            // Store the initial card data for PriceCharting to access
            const listingInfo = extractListingInfo(listingElement);
            const cardData = {
                cardNumber: cardNumber,
                setNumber: setNumber,
                ebayTitle: listingInfo.title
            };

            const requestKey = storePriceChartingRequest(cardData);

            // Use the enhanced URL creation function that handles all the logic
            const finalUrl = await createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, false);

            if (finalUrl) {
                debugLog(`🔗 Opening PriceCharting view URL: ${finalUrl}`);

                // Open in new tab for viewing
                window.open(finalUrl, '_blank');
            } else {
                debugLog('⚠️ Card number pattern failed - trying title-based fallback for view page');
                
                // Try title-based search as fallback if we have a set name
                if (listingInfo.setName) {
                    debugLog(`🔄 Falling back to title-based search in set: ${listingInfo.setName}`);
                    
                    // Search by matching title against card names in the set
                    const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                    
                    if (foundCard) {
                        debugLog(`✅ Found card via title fallback: ${foundCard.name} #${foundCard.localId}`);
                        
                        // Build URL from the found card
                        const fallbackUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, requestKey, false, listingElement);
                        
                        if (fallbackUrl) {
                            debugLog(`🔗 Opening PriceCharting view URL (fallback): ${fallbackUrl}`);
                            window.open(fallbackUrl, '_blank');
                            return;
                        }
                    }
                }
                
                // If all fallbacks failed, use search page with title
                debugLog('⚠️ All search methods failed - opening PriceCharting search page');
                const searchQuery = listingInfo.title || cardNumber;
                const fallbackUrl = `https://www.pricecharting.com/search?q=${encodeURIComponent(searchQuery)}&type=prices`;
                window.open(fallbackUrl, '_blank');
            }

            } catch (error) {
            debugLog('Error constructing PriceCharting view URL:', error);
            const listingInfo = extractListingInfo(listingElement);
            const searchQuery = listingInfo.title || cardNumber;
            const fallbackUrl = `https://www.pricecharting.com/search?q=${encodeURIComponent(searchQuery)}&type=prices`;
            window.open(fallbackUrl, '_blank');
            }
        }

        // PriceCharting URL set name mapping - hardcoded for known differences
        const PRICECHARTING_SET_MAPPING = {
            // Sets with & characters (must preserve the & to avoid redirects)
            '151': 'pokemon-scarlet-&-violet-151',
            'Scarlet & Violet': 'pokemon-scarlet-&-violet',
            'Sun & Moon': 'pokemon-sun-&-moon',
            'Black & White': 'pokemon-black-&-white',
            'Ruby & Sapphire': 'pokemon-ruby-&-sapphire',
            'Diamond & Pearl': 'pokemon-diamond-&-pearl',
            'HeartGold SoulSilver': 'pokemon-heartgold-&-soulsilver',
            'Sword & Shield': 'pokemon-sword-&-shield',
            'FireRed & LeafGreen': 'pokemon-firered-&-leafgreen',

            // Base Set variations
            'Base Set': 'pokemon-base-set',
            'Base Set 2': 'pokemon-base-set-2',

            // POP Series
            'POP Series 1': 'pokemon-pop-series-1',
            'POP Series 2': 'pokemon-pop-series-2',
            'POP Series 3': 'pokemon-pop-series-3',
            'POP Series 4': 'pokemon-pop-series-4',
            'POP Series 5': 'pokemon-pop-series-5',
            'POP Series 6': 'pokemon-pop-series-6',
            'POP Series 7': 'pokemon-pop-series-7',
            'POP Series 8': 'pokemon-pop-series-8',
            'POP Series 9': 'pokemon-pop-series-9',

            // Gym Series
            'Gym Heroes': 'pokemon-gym-heroes',
            'Gym Challenge': 'pokemon-gym-challenge',

            // Neo Series
            'Neo Genesis': 'pokemon-neo-genesis',
            'Neo Discovery': 'pokemon-neo-discovery',
            'Neo Revelation': 'pokemon-neo-revelation',
            'Neo Destiny': 'pokemon-neo-destiny',

            // E-Card Series
            'Expedition Base Set': 'pokemon-expedition',
            'Aquapolis': 'pokemon-aquapolis',
            'Skyridge': 'pokemon-skyridge',

            // EX Series
            'Team Magma vs Team Aqua': 'pokemon-team-magma-vs-team-aqua',
            'Hidden Legends': 'pokemon-hidden-legends',
            'Team Rocket Returns': 'pokemon-team-rocket-returns',
            'Deoxys': 'pokemon-deoxys',
            'EX Deoxys': 'pokemon-deoxys',
            'Emerald': 'pokemon-emerald',
            'EX Emerald': 'pokemon-emerald',
            'Unseen Forces': 'pokemon-unseen-forces',
            'EX Unseen Forces': 'pokemon-unseen-forces',
            'Delta Species': 'pokemon-delta-species',
            'Legend Maker': 'pokemon-legend-maker',
            'Holon Phantoms': 'pokemon-holon-phantoms',
            'Crystal Guardians': 'pokemon-crystal-guardians',
            'Dragon Frontiers': 'pokemon-dragon-frontiers',
            'Power Keepers': 'pokemon-power-keepers',

            // Diamond & Pearl Series
            'Mysterious Treasures': 'pokemon-mysterious-treasures',
            'Secret Wonders': 'pokemon-secret-wonders',
            'Great Encounters': 'pokemon-great-encounters',
            'Majestic Dawn': 'pokemon-majestic-dawn',
            'Legends Awakened': 'pokemon-legends-awakened',
            'Stormfront': 'pokemon-stormfront',

            // Platinum Series
            'Platinum': 'pokemon-platinum',
            'Rising Rivals': 'pokemon-rising-rivals',
            'Supreme Victors': 'pokemon-supreme-victors',
            'Arceus': 'pokemon-arceus',

            // HGSS Series
            'Unleashed': 'pokemon-unleashed',
            'Undaunted': 'pokemon-undaunted',
            'Triumphant': 'pokemon-triumphant',
            'Call of Legends': 'pokemon-call-of-legends',

            // Black & White Series
            'Emerging Powers': 'pokemon-emerging-powers',
            'Noble Victories': 'pokemon-noble-victories',
            'Next Destinies': 'pokemon-next-destinies',
            'Dark Explorers': 'pokemon-dark-explorers',
            'Dragons Exalted': 'pokemon-dragons-exalted',
            'Boundaries Crossed': 'pokemon-boundaries-crossed',
            'Plasma Storm': 'pokemon-plasma-storm',
            'Plasma Freeze': 'pokemon-plasma-freeze',
            'Plasma Blast': 'pokemon-plasma-blast',
            'Legendary Treasures': 'pokemon-legendary-treasures',

            // XY Series
            'XY': 'pokemon-xy',
            'Flashfire': 'pokemon-flashfire',
            'Furious Fists': 'pokemon-furious-fists',
            'Phantom Forces': 'pokemon-phantom-forces',
            'Primal Clash': 'pokemon-primal-clash',
            'Roaring Skies': 'pokemon-roaring-skies',
            'Ancient Origins': 'pokemon-ancient-origins',
            'BREAKthrough': 'pokemon-breakthrough',
            'BREAKpoint': 'pokemon-breakpoint',
            'Generations': 'pokemon-generations',
            'Fates Collide': 'pokemon-fates-collide',
            'Steam Siege': 'pokemon-steam-siege',
            'Evolutions': 'pokemon-evolutions',

            // Sun & Moon Series
            'Guardians Rising': 'pokemon-guardians-rising',
            'Burning Shadows': 'pokemon-burning-shadows',
            'Crimson Invasion': 'pokemon-crimson-invasion',
            'Ultra Prism': 'pokemon-ultra-prism',
            'Forbidden Light': 'pokemon-forbidden-light',
            'Celestial Storm': 'pokemon-celestial-storm',
            'Lost Thunder': 'pokemon-lost-thunder',
            'Team Up': 'pokemon-team-up',
            'Unbroken Bonds': 'pokemon-unbroken-bonds',
            'Unified Minds': 'pokemon-unified-minds',
            'Cosmic Eclipse': 'pokemon-cosmic-eclipse',

            // Sword & Shield Series
            'Rebel Clash': 'pokemon-rebel-clash',
            'Darkness Ablaze': 'pokemon-darkness-ablaze',
            'Vivid Voltage': 'pokemon-vivid-voltage',
            'Battle Styles': 'pokemon-battle-styles',
            'Chilling Reign': 'pokemon-chilling-reign',
            'Evolving Skies': 'pokemon-evolving-skies',
            'Fusion Strike': 'pokemon-fusion-strike',
            'Brilliant Stars': 'pokemon-brilliant-stars',
            'Astral Radiance': 'pokemon-astral-radiance',
            'Lost Origin': 'pokemon-lost-origin',
            'Silver Tempest': 'pokemon-silver-tempest',

            // Scarlet & Violet Series
            'Paldea Evolved': 'pokemon-paldea-evolved',
            'Obsidian Flames': 'pokemon-obsidian-flames',
            'Paradox Rift': 'pokemon-paradox-rift',
            'Temporal Forces': 'pokemon-temporal-forces',
            'Twilight Masquerade': 'pokemon-twilight-masquerade',
            'Shrouded Fable': 'pokemon-shrouded-fable',
            'Stellar Crown': 'pokemon-stellar-crown',
            'Surging Sparks': 'pokemon-surging-sparks',

            // Special Sets
            'Shining Legends': 'pokemon-shining-legends',
            'Hidden Fates': 'pokemon-hidden-fates',
            'Champion\'s Path': 'pokemon-champions-path',
            'Shining Fates': 'pokemon-shining-fates',
            'Celebrations': 'pokemon-celebrations',
            'Pokémon GO': 'pokemon-go',
            'Crown Zenith': 'pokemon-crown-zenith',
            'Paldean Fates': 'pokemon-paldean-fates',

            // Promos
            'SVP Black Star Promos': 'pokemon-promo',
            'SWSH Black Star Promos': 'pokemon-promo',
            'SM Black Star Promos': 'pokemon-promo',
            'XY Black Star Promos': 'pokemon-promo',
            'BW Black Star Promos': 'pokemon-promo',
            'DP Black Star Promos': 'pokemon-promo',
            'HGSS Black Star Promos': 'pokemon-promo',
            'EX Black Star Promos': 'pokemon-promo',
            'Nintendo Black Star Promos': 'pokemon-promo',
            'Wizards Black Star Promos': 'pokemon-promo',
            'Promo Cards': 'pokemon-promo',
            'Promos': 'pokemon-promo',
        };

        // Cache for recently fetched PriceCharting data to avoid duplicate requests
        const priceChartingCache = new Map();

        // Enhanced function to open PriceCharting with the specific URL format - Returns Promise
        async function openPriceChartingDirectWithSharing(listingElement, cardNumber, setNumber) {
            try {
                const listingInfo = extractListingInfo(listingElement);

                // Check if this is a title-based search (no card number but has set name)
                if (!cardNumber && listingInfo.setName) {
                    debugLog(`🔍 Title-based search in set: ${listingInfo.setName}`);
                    
                    // Update button to show searching
                    const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                    if (pcButton) {
                        pcButton.textContent = '🔍 Searching...';
                        pcButton.disabled = true;
                    }
                    
                    // Search by matching title against card names in the set
                    const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                    
                    if (foundCard) {
                        debugLog(`✅ Found card via name search: ${foundCard.name} #${foundCard.localId}`);
                        
                        // Update button to show found
                        if (pcButton) {
                            pcButton.textContent = `💰 ${foundCard.name}`;
                            pcButton.title = `Found: ${foundCard.name} #${foundCard.localId} from ${foundCard.set?.name}`;
                        }
                        
                        // Store card data for PriceCharting to access
                        const cardData = {
                            cardNumber: foundCard.localId,
                            setNumber: foundCard.set?.cardCount?.total || null,
                            ebayTitle: listingInfo.title
                        };
                        
                        const requestKey = storePriceChartingRequest(cardData);
                        
                        // Build URL directly from the found card (bypass localId search)
                        // Params: foundCard, setName, requestKey, showPricePage, listingElement
                        const finalUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, requestKey, true, listingElement);
                        
                        if (finalUrl) {
                            // Continue with opening PriceCharting window
                            const detectedGrade = detectGradeFromTitle(listingInfo.title);
                            const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                            const baseUrl = finalUrl.split('?')[0];
                            const cacheKey = `${foundCard.localId}_${foundCard.set?.cardCount?.total}_${gradeKey}_${baseUrl}`;
                            
                            const cached = priceChartingCache.get(cacheKey);
                            if (cached && (Date.now() - cached.timestamp < TIMING.CACHE_DURATION)) {
                                debugLog(`💾 Using cached PriceCharting data for ${cacheKey}`);
                                updateListingWithPriceChartingData(listingElement, cached.data);
                                
                                if (pcButton) {
                                    pcButton.style.background = '#27ae60';
                                    pcButton.title = `✅ PriceCharting data loaded (cached) - ${Object.keys(cached.data.prices || {}).length} prices found`;
                                    pcButton.disabled = false;
                                }
                                return true;
                            }
                            
                        debugLog(`🔗 Opening PriceCharting URL: ${finalUrl}`);
                        const windowName = `pricecharting_${requestKey}`;
                        // Create minimal resource popup (1x1 pixel, off-screen, all features disabled)
                        const pcWindow = window.open(finalUrl, windowName, 
                            'width=1,height=1,left=9999,top=9999,' +
                            'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                            'resizable=no,directories=no');                            if (pcWindow) {
                                debugLog(`✅ PriceCharting window opened successfully (minimized off-screen)`);
                                window.focus();
                            } else {
                                debugLog(`⚠️ Failed to open PriceCharting window - popup may be blocked`);
                                showPopupBlockedWarning(listingElement);
                                if (pcButton) pcButton.disabled = false;
                                return false;
                            }
                            
                            await setupPriceChartingDataListener(requestKey, listingElement);
                            return true;
                        } else {
                            debugLog('⚠️ Could not create PriceCharting URL from found card');
                            if (pcButton) {
                                pcButton.style.background = '#95a5a6';
                                pcButton.textContent = '❓ URL Error';
                                pcButton.title = 'Could not create PriceCharting URL';
                                pcButton.disabled = false;
                            }
                            return false;
                        }
                    } else {
                        debugLog(`⚠️ Could not find card by name`);
                        if (pcButton) {
                            pcButton.style.background = '#95a5a6';
                            pcButton.textContent = '❓ Not Found';
                            pcButton.title = `Could not find "${listingInfo.pokemonName}" in ${listingInfo.setName}`;
                            pcButton.disabled = false;
                        }
                        return false;
                    }
                }

                // Store the initial card data for PriceCharting to access
                const cardData = {
                    cardNumber: cardNumber,
                    setNumber: setNumber,
                    ebayTitle: listingInfo.title
                };

                const requestKey = storePriceChartingRequest(cardData);

                // Use the enhanced URL creation function that handles all the logic
                const finalUrl = await createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, true);

                if (finalUrl) {
                    // Detect grade from title to make cache key unique per grade
                    const detectedGrade = detectGradeFromTitle(listingInfo.title);
                    const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                    
                    // Create a cache key based on the card AND grade (without the unique request key)
                    const baseUrl = finalUrl.split('?')[0]; // URL without query params
                    const cacheKey = `${cardNumber}_${setNumber}_${gradeKey}_${baseUrl}`;
                    
                    // Check if we have cached data for this card with this specific grade
                    const cached = priceChartingCache.get(cacheKey);
                    if (cached && (Date.now() - cached.timestamp < TIMING.CACHE_DURATION)) {
                        debugLog(`💾 Using cached PriceCharting data for ${cacheKey}`);
                        // Use cached data immediately
                        updateListingWithPriceChartingData(listingElement, cached.data);
                        
                        // Update button to show success
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.style.background = '#27ae60';
                            pcButton.title = `✅ PriceCharting data loaded (cached) - ${Object.keys(cached.data.prices || {}).length} prices found`;
                        }
                        return true;
                    }

                    debugLog(`🔗 Opening PriceCharting URL: ${finalUrl}`);
                    debugLog(`🔑 Request key: ${requestKey}`);

                    // Use unique window name for each request to avoid reusing same window
                    const windowName = `pricecharting_${requestKey}`;
                    
                    // Open in new window/tab (will auto-close quickly)
                    // Create minimal resource popup (1x1 pixel, off-screen, all features disabled)
                    const pcWindow = window.open(finalUrl, windowName, 
                        'width=1,height=1,left=9999,top=9999,' +
                        'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                        'resizable=no,directories=no');
                    if (pcWindow) {
                        debugLog(`✅ PriceCharting window opened successfully`);
                        window.focus(); // Try to keep focus on current eBay tab
                    } else {
                        debugLog(`⚠️ Failed to open PriceCharting window - popup may be blocked`);
                        // Show popup blocked warning
                        showPopupBlockedWarning(listingElement);
                        return false;
                    }

                    // Set up listener for returned data - now returns a Promise
                    await setupPriceChartingDataListener(requestKey, listingElement);
                    return true;
                } else {
                    debugLog('⚠️ Card number pattern failed to find card - trying title-based fallback');
                    
                    // Try title-based search as fallback if we have a set name
                    if (listingInfo.setName) {
                        debugLog(`🔄 Falling back to title-based search in set: ${listingInfo.setName}`);
                        
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.textContent = '🔍 Retry...';
                            pcButton.disabled = true;
                        }
                        
                        // Search by matching title against card names in the set
                        const foundCard = await searchByTitleInSet(listingInfo.setName, listingInfo.title);
                        
                        if (foundCard) {
                            debugLog(`✅ Found card via title fallback: ${foundCard.name} #${foundCard.localId}`);
                            
                            // Store new card data
                            const newCardData = {
                                cardNumber: foundCard.localId,
                                setNumber: foundCard.set?.cardCount?.total || null,
                                ebayTitle: listingInfo.title
                            };
                            const newRequestKey = storePriceChartingRequest(newCardData);
                            
                            // Build URL from the found card
                            const fallbackUrl = await buildPriceChartingUrlFromCard(foundCard, foundCard.set?.name, newRequestKey, true, listingElement);
                            
                            if (fallbackUrl) {
                                debugLog(`🔗 Opening PriceCharting URL (fallback): ${fallbackUrl}`);
                                const windowName = `pricecharting_${newRequestKey}`;
                                const pcWindow = window.open(fallbackUrl, windowName, 
                                    'width=1,height=1,left=9999,top=9999,' +
                                    'scrollbars=no,toolbar=no,menubar=no,location=no,status=no,' +
                                    'resizable=no,directories=no');
                                
                                if (pcWindow) {
                                    debugLog(`✅ PriceCharting window opened (fallback method)`);
                                    window.focus();
                                    await setupPriceChartingDataListener(newRequestKey, listingElement);
                                    return true;
                                }
                            }
                        }
                    }
                    
                    // If all fallbacks failed, show not found
                    debugLog('⚠️ All search methods failed - card not found');
                    const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                    if (pcButton) {
                        pcButton.style.background = '#95a5a6'; // Gray
                        pcButton.textContent = '❓ Not Found';
                        pcButton.title = 'Card not found in TCGdex database. Try manual search.';
                        pcButton.disabled = false;
                    }
                    
                    return false;
                }

            } catch (error) {
                debugLog('❌ Error opening PriceCharting:', error);
                
                // Update button to show error
                const pcButton = listingElement?.querySelector('.pricecharting-direct-btn');
                if (pcButton) {
                    pcButton.style.background = '#95a5a6'; // Gray
                    pcButton.textContent = '⚠️ Error';
                    pcButton.title = `Error: ${error.message}`;
                }
                
                return false;
            }
        }

        // --- Enhanced helper function for PriceCharting URL creation with all logic ---
        async function createPriceChartingUrl(cardNumber, setNumber, requestKey, listingElement, showPricePage) {
            try {
            // Load sets cache and find matching sets
            await loadSetsCache();
            const matchingSets = findSetsByCardCount(setNumber);

            let foundCard = null;
            let setName = null;
            let allFoundCards = [];

            // If no setNumber provided (hash patterns) OR no matching sets found, search by localId to get set info
            if (!setNumber || matchingSets.length === 0) {
                if (!setNumber) {
                    debugLog('🔍 No setNumber provided - using localId search to find set information for PriceCharting URL');
                } else {
                    debugLog(`🔍 No matching sets found for setNumber "${setNumber}" - falling back to localId search`);
                }

                try {
                    const listingInfo = extractListingInfo(listingElement);
                    const localIdResult = await searchTCGdexByLocalId(cardNumber, listingInfo.title, listingInfo.setName, true);

                    if (localIdResult && localIdResult.id) {
                        // Extract set ID from the card ID (e.g., "sv08-238" -> "sv08")
                        const setId = localIdResult.id.split('-')[0];
                        debugLog(`✅ Found card via localId: ${localIdResult.name} from set ID "${setId}"`);

                        // Find the set name from our cached sets using the set ID
                        await loadSetsCache();
                        const matchingSet = setsCache.find(set => set.id === setId);

                        if (matchingSet) {
                            foundCard = localIdResult;
                            setName = matchingSet.name;
                            debugLog(`✅ Found matching set: ${setName}`);

                            // Skip the normal set searching since we already have our card
                            // Jump directly to URL construction
                            return buildPriceChartingUrlFromCard(foundCard, setName, requestKey, showPricePage, listingElement);
                        } else {
                            debugLog(`❌ Could not find set with ID "${setId}" in cached sets`);
                            return null;
                        }
                    } else {
                        debugLog('❌ LocalId search failed - no card or ID found');
                        return null;
                    }
                } catch (error) {
                    debugLog('❌ Error in localId search for PriceCharting URL:', error);
                    return null;
                }
            }

            // Create variations of the card number to handle zero-padding issues
            // For alternate arts (like "177a"), strip the letter for API searches
            const baseCardNumber = cardNumber.replace(/[a-zA-Z]/g, ''); // Remove letters
            debugLog(`🔍 [PC URL] Original cardNumber: "${cardNumber}" (length: ${cardNumber.length})`);
            debugLog(`🔍 [PC URL] Base cardNumber after letter removal: "${baseCardNumber}"`);

            let cardNumberVariations = [];

            // Check if this is a letter-number pattern (like "RC24", "GG69") - letters at beginning
            if (/[A-Z]/.test(cardNumber)) {
                // For letter-number patterns, keep the original format
                cardNumberVariations = [cardNumber, baseCardNumber];
                debugLog(`🔍 Letter-number pattern detected: "${cardNumber}" - keeping original format`);
            } else {
                // For numeric patterns (like "177", "177a"), create variations
                cardNumberVariations = [
                    baseCardNumber, // Numeric part only: "177" from "177a"
                    parseInt(baseCardNumber, 10).toString(), // Remove leading zeros: "177"
                    baseCardNumber.padStart(3, '0') // Add leading zeros: "177" -> "177"
                ].filter(variation => variation && !isNaN(variation) && variation !== 'NaN'); // Filter out invalid variations
            }

            // Remove duplicates
            const uniqueCardNumbers = [...new Set(cardNumberVariations)];
            debugLog(`🔍 Card number variations to try: ${uniqueCardNumbers.join(', ')}`);
            if (cardNumber !== baseCardNumber && /[a-zA-Z]/.test(cardNumber)) {
                debugLog(`🔍 Alternate art detected: "${cardNumber}" -> using "${baseCardNumber}" for API search`);
            }

            // Search each target set to find the card and get set info
            for (const set of matchingSets) {
                // Try each card number variation
                for (const cardNum of uniqueCardNumbers) {
                    try {
                const fullCardUrl = `https://api.tcgdex.net/v2/en/cards/${set.id}-${cardNum}`;
                const response = await fetch(fullCardUrl, {
                    method: 'GET',
                    headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                    }
                });

                if (response.ok) {
                    const fullCard = await response.json();
                    if (fullCard && fullCard.name) {
                    allFoundCards.push({
                        card: fullCard,
                        setName: set.name
                    });
                    debugLog(`🎯 Found card: ${fullCard.name} in set ${set.name} with card number ${cardNum}`);
                    break; // Found card in this set, no need to try other variations
                    }
                }
                } catch (error) {
                // Continue to next variation
                }
                }
            }

            // Find the best match using title similarity
            if (allFoundCards.length > 0) {
                const listingInfo = extractListingInfo(listingElement);

                if (allFoundCards.length === 1) {
                foundCard = allFoundCards[0].card;
                setName = allFoundCards[0].setName;
                debugLog(`🎯 Single card found for PriceCharting: ${foundCard.name} from ${setName}`);
                } else if (listingInfo.title) {
                let bestSimilarity = -1;
                let bestMatch = allFoundCards[0];

                debugLog(`🔍 Comparing ${allFoundCards.length} cards for PriceCharting URL...`);

                allFoundCards.forEach((cardData, index) => {
                    const similarity = calculateTitleSimilarity(listingInfo.title, cardData.card.name);
                    debugLog(`  ${index + 1}. ${cardData.card.name} from ${cardData.setName} - Similarity: ${(similarity * 100).toFixed(1)}%`);

                    if (similarity > bestSimilarity) {
                    bestSimilarity = similarity;
                    bestMatch = cardData;
                    }
                });

                foundCard = bestMatch.card;
                setName = bestMatch.setName;
                debugLog(`🎯 Best match for PriceCharting: ${foundCard.name} from ${setName} (${(bestSimilarity * 100).toFixed(1)}% similarity)`);
                } else {
                foundCard = allFoundCards[0].card;
                setName = allFoundCards[0].setName;
                debugLog(`🎯 Using first card for PriceCharting (no title): ${foundCard.name} from ${setName}`);
                }
            }

            if (!foundCard || !setName) {
                debugLog('Card not found for PriceCharting URL construction');
                return null; // Let caller handle fallback
            }

            // Now construct the URL with the found card data
            let cleanSetName;

            // Check if we have a hardcoded mapping first
            if (PRICECHARTING_SET_MAPPING[setName]) {
                cleanSetName = PRICECHARTING_SET_MAPPING[setName];
                debugLog(`🔧 Using hardcoded mapping: "${setName}" -> "${cleanSetName}"`);
            } else {
                // Fall back to automatic cleaning
                cleanSetName = setName
                .toLowerCase()
                .replace(/pokémon/g, 'pokemon')
                .replace(/[^a-z0-9\s]/g, '')
                .replace(/\s+/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');
                debugLog(`🔧 Using automatic cleaning: "${setName}" -> "${cleanSetName}"`);
            }

            // Check if this is a Mega Evolution card (starts with "M ")
            const isMegaCard = /^m\s+/i.test(foundCard.name);
            
            const cleanCardName = foundCard.name
                .toLowerCase()
                .replace(/^m\s+/i, 'm-') // Replace "M " prefix with "m-" (e.g., "M Charizard EX" -> "m-charizard ex")
                .replace(/'/g, '') // Remove apostrophes
                .replace(/&/g, '-&-') // Replace & with -&- to preserve it in URL
                .replace(/[^a-z0-9\s&-]/g, '') // Keep letters, numbers, spaces, &, and hyphens
                .replace(/\s+/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');

            // For Mega cards, also create the "mega-" variant
            const megaVariantName = isMegaCard ? cleanCardName.replace(/^m-/, 'mega-') : null;

            // Check eBay title for special indicators and modifiers
            let finalCardName = cleanCardName;
            const listingInfo = extractListingInfo(listingElement);
            if (listingInfo.title) {
                const titleUpper = listingInfo.title.toUpperCase();

                // Check for Gold Star variants (highest priority for rare cards)
                if (titleUpper.includes('GOLD STAR') || titleUpper.includes('GOLDSTAR')) {
                    finalCardName = `${cleanCardName}-gold-star`;
                    debugLog(`🔧 Gold Star detected in title - adding "gold-star": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for Pokemon Center variants
                else if (['POKEMON CENTER', 'POKÉMON CENTER'].some(variant => titleUpper.includes(variant))) {
                    finalCardName = `${cleanCardName}-pokemon-center`;
                    debugLog(`🔧 Pokemon Center detected in title - adding "pokemon-center": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for standalone STAMPED (but not if part of "Stamped Reverse Holo")
                else if (titleUpper.includes('STAMPED') && !titleUpper.includes('REVERSE')) {
                    finalCardName = `${cleanCardName}-stamped`;
                    debugLog(`🔧 Stamped detected in title - adding "stamped": "${cleanCardName}" -> "${finalCardName}"`);
                }
                // Check for First Edition
                else if (['FIRST EDITION', '1ST EDITION'].some(variant => titleUpper.includes(variant))) {
                    finalCardName = `${cleanCardName}-1st-edition`;
                    debugLog(`🔧 First Edition detected in title - adding "1st-edition": "${cleanCardName}" -> "${finalCardName}"`);
                }
            }

            // Handle card number - preserve letters for alternate arts (like 177a) and promo cards (like SWSH291)
            let cleanCardNumber;
            if (/\d+[a-zA-Z]$/.test(cardNumber)) {
                // Card number ends with a letter (e.g., "177a") - preserve it for alternate arts
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Alternate art detected - preserving letter: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else if (/^(SWSH|SM)[0-9]+$/i.test(cardNumber)) {
                // Promo card (e.g., "SWSH291", "SM241") - preserve exactly as-is
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Promo card detected - preserving format: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else if (/^[A-Z]{1,4}\d+$/i.test(cardNumber)) {
                // Letter-number pattern (e.g., "TG29", "SV107", "RC5") - preserve exactly as-is
                cleanCardNumber = cardNumber.toLowerCase();
                debugLog(`🔧 Letter-number pattern detected - preserving format: "${cardNumber}" -> "${cleanCardNumber}"`);
            } else {
                // Standard numeric card number - normalize it
                cleanCardNumber = parseInt(cardNumber, 10).toString();
            }

            const setPrefix = cleanSetName.startsWith('pokemon-') ? '' : 'pokemon-';

            // Build base URL - don't add query parameters as they cause search redirects
            let finalUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${finalCardName}-${cleanCardNumber}`;
            
            // If this is a Mega card, store the alternative URL for retry
            let alternativeUrl = null;
            if (megaVariantName && showPricePage && requestKey) {
                alternativeUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${megaVariantName}-${cleanCardNumber}#full-prices&ebay_request=${requestKey}`;
                
                // Store the alternative URL in the request data
                const storedData = GM_getValue(requestKey);
                if (storedData) {
                    storedData.alternativeUrl = alternativeUrl;
                    storedData.triedAlternative = false;
                    GM_setValue(requestKey, storedData);
                    debugLog(`💾 Stored alternative URL for Mega card: ${alternativeUrl}`);
                }
            }
            
            debugLog(`🔍 [createPriceChartingUrl] showPricePage: ${showPricePage}`);
            debugLog(`🔍 [createPriceChartingUrl] requestKey: ${requestKey}`);
            
            if (showPricePage) {
                // Put ebay_request in the hash to survive redirects
                finalUrl += `#full-prices&ebay_request=${requestKey}`;
                debugLog(`✅ [createPriceChartingUrl] Added ebay_request to hash`);
            } else {
                debugLog(`ℹ️ [createPriceChartingUrl] Skipping ebay_request (view mode)`);
            }

            debugLog(`📋 Card: ${foundCard.name} from ${setName}`);
            debugLog(`🔗 Generated final URL: ${finalUrl}`);
            if (alternativeUrl) {
                debugLog(`🔗 Alternative URL available: ${alternativeUrl}`);
            }
            return finalUrl;

            } catch (error) {
                debugLog('Error in createPriceChartingUrl:', error);
                return null; // Let caller handle fallback
            }
        }

        // Helper function to build PriceCharting URL from a found card
        function buildPriceChartingUrlFromCard(foundCard, setName, requestKey, showPricePage, listingElement) {
            try {
                debugLog(`🔗 Building PriceCharting URL from found card: ${foundCard.name} from ${setName}`);

                // Now construct the URL with the found card data
                let cleanSetName;

                // Check if we have a hardcoded mapping first
                if (PRICECHARTING_SET_MAPPING[setName]) {
                    cleanSetName = PRICECHARTING_SET_MAPPING[setName];
                    debugLog(`🔧 Using hardcoded mapping: "${setName}" -> "${cleanSetName}"`);
                } else {
                    // Fall back to automatic cleaning
                    cleanSetName = setName
                    .toLowerCase()
                    .replace(/pokémon/g, 'pokemon')
                    .replace(/[^a-z0-9\s]/g, '')
                    .replace(/\s+/g, '-')
                    .replace(/-+/g, '-')
                    .replace(/^-|-$/g, '');
                    debugLog(`🔧 Using automatic cleaning: "${setName}" -> "${cleanSetName}"`);
                }

                // Check if this is a Mega Evolution card (starts with "M ")
                const isMegaCard = /^m\s+/i.test(foundCard.name);
                
                const cleanCardName = foundCard.name
                    .toLowerCase()
                    .replace(/^m\s+/i, 'm-') // Replace "M " prefix with "m-" (e.g., "M Charizard EX" -> "m-charizard ex")
                    .replace(/'/g, '') // Remove apostrophes
                    .replace(/&/g, '-&-') // Replace & with -&- to preserve it in URL
                    .replace(/[^a-z0-9\s&-]/g, '') // Keep letters, numbers, spaces, &, and hyphens
                    .replace(/\s+/g, '-')
                    .replace(/-+/g, '-')
                    .replace(/^-|-$/g, '');

                // For Mega cards, also create the "mega-" variant
                const megaVariantName = isMegaCard ? cleanCardName.replace(/^m-/, 'mega-') : null;

                // Check eBay title for special indicators and modifiers
                let finalCardName = cleanCardName;
                const listingInfo = extractListingInfo(listingElement);
                if (listingInfo.title) {
                    const titleUpper = listingInfo.title.toUpperCase();

                    // Check for Pokemon Center variants
                    if (titleUpper.includes('POKEMON CENTER') || titleUpper.includes('POKÉMON CENTER')) {
                        finalCardName = `${cleanCardName}-pokemon-center`;
                        debugLog(`🔧 Pokemon Center detected in title - adding "pokemon-center": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                    // Check for standalone STAMPED (but not if part of "Stamped Reverse Holo")
                    else if (titleUpper.includes('STAMPED') && !titleUpper.includes('REVERSE')) {
                        finalCardName = `${cleanCardName}-stamped`;
                        debugLog(`🔧 Stamped detected in title - adding "stamped": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                    // Check for First Edition
                    else if (titleUpper.includes('FIRST EDITION') || titleUpper.includes('1ST EDITION')) {
                        finalCardName = `${cleanCardName}-1st-edition`;
                        debugLog(`🔧 First Edition detected in title - adding "1st-edition": "${cleanCardName}" -> "${finalCardName}"`);
                    }
                }

                // Use the localId from the found card for the card number
                let cleanCardNumber = foundCard.localId;
                if (/\d+[a-zA-Z]$/.test(cleanCardNumber)) {
                    // Card number ends with a letter (e.g., "177a") - preserve it for alternate arts
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Alternate art detected - preserving letter: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else if (/^(SWSH|SM)[0-9]+$/i.test(cleanCardNumber)) {
                    // Promo card (e.g., "SWSH291", "SM241") - preserve exactly as-is
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Promo card detected - preserving format: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else if (/^[A-Z]{1,4}\d+$/i.test(cleanCardNumber)) {
                    // Letter-number pattern (e.g., "TG29", "SV107", "RC5") - preserve exactly as-is
                    cleanCardNumber = cleanCardNumber.toLowerCase();
                    debugLog(`🔧 Letter-number pattern detected - preserving format: "${foundCard.localId}" -> "${cleanCardNumber}"`);
                } else {
                    // Standard numeric card number - normalize it
                    cleanCardNumber = parseInt(foundCard.localId, 10).toString();
                }

                const setPrefix = cleanSetName.startsWith('pokemon-') ? '' : 'pokemon-';

                // Build base URL - don't add query parameters as they cause search redirects
                let finalUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${finalCardName}-${cleanCardNumber}`;
                
                // If this is a Mega card, store the alternative URL for retry
                let alternativeUrl = null;
                if (megaVariantName && showPricePage && requestKey) {
                    alternativeUrl = `https://www.pricecharting.com/game/${setPrefix}${cleanSetName}/${megaVariantName}-${cleanCardNumber}#full-prices&ebay_request=${requestKey}`;
                    
                    // Store the alternative URL in the request data
                    const storedData = GM_getValue(requestKey);
                    if (storedData) {
                        storedData.alternativeUrl = alternativeUrl;
                        storedData.triedAlternative = false;
                        GM_setValue(requestKey, storedData);
                        debugLog(`💾 Stored alternative URL for Mega card: ${alternativeUrl}`);
                    }
                }
                
                debugLog(`🔍 [buildPriceChartingUrlFromCard] showPricePage: ${showPricePage}`);
                debugLog(`🔍 [buildPriceChartingUrlFromCard] requestKey: ${requestKey}`);
                
                if (showPricePage) {
                    // Put ebay_request in the hash to survive redirects
                    finalUrl += `#full-prices&ebay_request=${requestKey}`;
                    debugLog(`✅ [buildPriceChartingUrlFromCard] Added ebay_request to hash`);
                } else {
                    debugLog(`ℹ️ [buildPriceChartingUrlFromCard] Skipping ebay_request (view mode)`);
                }

                debugLog(`📋 Card: ${foundCard.name} from ${setName}`);
                debugLog(`🔗 Generated PriceCharting URL: ${finalUrl}`);
                if (alternativeUrl) {
                    debugLog(`🔗 Alternative URL available: ${alternativeUrl}`);
                }
                return finalUrl;

            } catch (error) {
                debugLog('Error in buildPriceChartingUrlFromCard:', error);
                return null;
            }
        }

        // Track active listeners to prevent duplicates and allow cancellation
        const activeListeners = new Map();

        // Enhanced listener with Promise-based async/await for better control
        function setupPriceChartingDataListener(requestKey, listingElement) {
            // Cancel any existing listener for this request key
            if (activeListeners.has(requestKey)) {
                debugLog(`🛑 Cancelling existing listener for ${requestKey}`);
                const existingListener = activeListeners.get(requestKey);
                existingListener.cancelled = true;
                clearTimeout(existingListener.timeoutId);
            }

            return new Promise((resolve, reject) => {
                let attempts = 0;
                const maxAttempts = TIMING.POLL_MAX_ATTEMPTS;
                let timeoutId = null;
                const listenerControl = { cancelled: false, timeoutId: null };

                // Store this listener
                activeListeners.set(requestKey, listenerControl);

                debugLog(`🔄 Setting up listener for request key: ${requestKey}`);

                const checkForData = () => {
                    // Check if this listener was cancelled
                    if (listenerControl.cancelled) {
                        debugLog(`🛑 Listener for ${requestKey} was cancelled`);
                        activeListeners.delete(requestKey);
                        reject(new Error('Listener cancelled'));
                        return;
                    }

                    attempts++;
                    debugLog(`📡 Checking for PriceCharting data... attempt ${attempts}/${maxAttempts}`);

                    const data = getStoredData(`${requestKey}_data`);

                    if (data) {
                        debugLog(`📊 Received PriceCharting data after ${attempts} attempts:`, data);

                        // Clean up
                        activeListeners.delete(requestKey);

                        // Cache the data for future use (extract card info from stored request)
                        const storedRequest = getStoredData(requestKey);
                        if (storedRequest && data.url) {
                            // Detect grade from the original eBay title
                            const detectedGrade = detectGradeFromTitle(storedRequest.ebayTitle || '');
                            const gradeKey = detectedGrade ? detectedGrade.key : 'ungraded';
                            
                            const baseUrl = data.url.split('?')[0];
                            const cacheKey = `${storedRequest.cardNumber}_${storedRequest.setNumber}_${gradeKey}_${baseUrl}`;
                            priceChartingCache.set(cacheKey, {
                                data: data,
                                timestamp: Date.now()
                            });
                            debugLog(`💾 Cached data for key: ${cacheKey} (grade: ${gradeKey})`);
                        }

                        // Verify the listing element still exists
                        if (listingElement && listingElement.parentNode) {
                            updateListingWithPriceChartingData(listingElement, data);

                            // Add a success indicator to the PC button
                            const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                            if (pcButton) {
                                pcButton.style.background = '#27ae60'; // Green to indicate success
                                pcButton.title = `✅ PriceCharting data loaded - ${Object.keys(data.prices || {}).length} prices found`;
                            }
                        } else {
                            console.warn('⚠️ Listing element no longer exists in DOM');
                        }
                        resolve(data);
                        return;
                    }

                    if (attempts < maxAttempts) {
                        timeoutId = setTimeout(checkForData, TIMING.POLL_INTERVAL);
                        listenerControl.timeoutId = timeoutId;
                    } else {
                        debugLog('⏰ Timeout waiting for PriceCharting data');

                        // Clean up
                        activeListeners.delete(requestKey);

                        // Update button to show timeout
                        const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                        if (pcButton) {
                            pcButton.style.background = '#e67e22'; // Orange to indicate timeout
                            pcButton.title = '⏰ Timeout waiting for PriceCharting data';
                        }
                        reject(new Error('Timeout waiting for PriceCharting data'));
                    }
                };

                // Start checking after a short delay to allow PriceCharting page to load
                timeoutId = setTimeout(checkForData, 1000); // Wait 1 second before first check
                listenerControl.timeoutId = timeoutId;
            });
        }

        // Update listing with PriceCharting data - Enhanced version with grade detection
        function updateListingWithPriceChartingData(listingElement, pcData) {
            debugLog('📊 Updating eBay listing with PriceCharting data:', pcData);

            // Create or update a PriceCharting info display
            let pcInfoDisplay = listingElement.querySelector('.pc-info-display');

            if (!pcInfoDisplay) {
                pcInfoDisplay = document.createElement('div');
                pcInfoDisplay.className = 'pc-info-display';

                Object.assign(pcInfoDisplay.style, {
                    display: 'block',
                    marginTop: '4px',
                    padding: '6px 10px',
                    background: '#9b59b6',
                    color: 'white',
                    borderRadius: '4px',
                    fontSize: '12px',
                    fontWeight: 'bold',
                    textAlign: 'left',
                    lineHeight: '1.4',
                    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                    border: '1px solid #8e44ad'
                });

                // Find the best place to insert the display
                const pcButton = listingElement.querySelector('.pricecharting-direct-btn');
                const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');

                if (pcButton && pcButton.parentNode) {
                    pcButton.parentNode.insertBefore(pcInfoDisplay, pcButton.nextSibling);
                } else if (priceElement && priceElement.parentNode) {
                    priceElement.parentNode.insertBefore(pcInfoDisplay, priceElement.nextSibling);
                } else {
                    // Fallback: append to the listing element itself
                    listingElement.appendChild(pcInfoDisplay);
                }

                debugLog('✅ Created new PC info display element');
            }

            // Get eBay title to detect grade
            const listingInfo = extractListingInfo(listingElement);
            const detectedGrade = detectGradeFromTitle(listingInfo.title);

            debugLog('🔍 Detected grade from eBay title:', detectedGrade);
            debugLog('🔍 eBay title:', listingInfo.title);

            // Format the PriceCharting data to show only the relevant grade
            let displayLines = [];
            let hasValidPrices = false;

            if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                debugLog('Available price keys:', Object.keys(pcData.prices));

                // If grade detected from title, show that grade + ungraded as baseline
                if (detectedGrade) {
                    debugLog(`Looking for detected grade key: ${detectedGrade.key}`);

                    // Show the specific detected grade (highlighted)
                    if (pcData.prices[detectedGrade.key]) {
                        const gradePrice = pcData.prices[detectedGrade.key];
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: ${gradePrice.price}</span>`);
                        hasValidPrices = true;
                        debugLog(`Added detected grade: ${detectedGrade.displayName}: ${gradePrice.price}`);
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: Unpriced</span>`);
                        debugLog(`Detected grade ${detectedGrade.displayName} not found - showing Unpriced`);
                    }

                    // Show ungraded as baseline reference
                    if (pcData.prices['ungraded']) {
                        const ungradedPrice = pcData.prices['ungraded'];
                        displayLines.push(`Ungraded: ${ungradedPrice.price}`);
                        hasValidPrices = true;
                        debugLog(`Added ungraded baseline: ${ungradedPrice.price}`);
                    } else {
                        displayLines.push(`Ungraded: Unpriced`);
                        debugLog('Ungraded price not found for baseline');
                    }
                } else {
                    // No grade detected, show only ungraded price (highlighted)
                    if (pcData.prices['ungraded']) {
                        const ungradedPrice = pcData.prices['ungraded'];
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: ${ungradedPrice.price}</span>`);
                        hasValidPrices = true;
                        debugLog(`Added ungraded: ${ungradedPrice.price}`);
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: Unpriced</span>`);
                        debugLog('Ungraded price not found - showing Unpriced');
                    }
                }

                // Add total count if there are more prices available
                const totalPrices = Object.keys(pcData.prices).length;
                const shownPrices = displayLines.length;
                if (totalPrices > shownPrices) {
                    displayLines.push(`+${totalPrices - shownPrices} more grades available`);
                }
            } else {
                if (detectedGrade) {
                    displayLines.push(`<strong>${detectedGrade.displayName}: Unpriced</strong>`);
                } else {
                    displayLines.push('<strong>Ungraded: Unpriced</strong>');
                }
                debugLog('⚠️ No valid prices found in data');
            }

            // Add card name if extracted
            let headerText = 'PC:';
            const displayCardName = pcData.extractedCardName || pcData.cardName || 'Unknown Card';
            if (displayCardName && displayCardName !== 'Unknown' && displayCardName !== 'Unknown Card') {
                // Add detected grade to the name if available
                if (detectedGrade) {
                    if (detectedGrade.key.toUpperCase().includes('BGS') && parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (👑${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (💎${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.5) {
                        headerText = `${displayCardName} (⭐${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.0) {
                        headerText = `${displayCardName} (✨${detectedGrade.displayName})`;
                    } else {
                        headerText = `${displayCardName} (${detectedGrade.displayName})`;
                    }
                } else {
                    headerText = `${displayCardName}`;
                }
            }

            // Add set name if available
            let setNameLine = '';
            if (pcData.extractedSetName) {
                setNameLine = `<div style="font-size: 12px; opacity: 0.8; margin-top: 2px; font-weight: bold;">${pcData.extractedSetName}</div>`;
            }

            // Add card image if available (on the right side)
            let imageHtml = '';
            if (pcData.imageUrl) {
                imageHtml = `<img src="${pcData.imageUrl}" alt="Card Image" style="float: right; width: 60px; height: auto; margin-left: 8px; margin-top: -4px; border-radius: 3px; border: 1px solid rgba(255,255,255,0.3);">`;
            }

            // Update the display content
            pcInfoDisplay.innerHTML = `${imageHtml}<strong>${headerText}</strong>${setNameLine}<br>${displayLines.join('<br>')}`;

            // Create detailed tooltip with all prices
            let tooltipContent = `PriceCharting Data:\n`;
            tooltipContent += `Card: ${pcData.extractedCardName || pcData.cardName || 'Unknown'}\n`;
            if (pcData.extractedCardNumber) {
                tooltipContent += `Number: #${pcData.extractedCardNumber}\n`;
            }
            if (detectedGrade) {
                tooltipContent += `Detected Grade: ${detectedGrade.displayName}\n`;
            }
            tooltipContent += `URL: ${pcData.url}\n\n`;

            if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                tooltipContent += `All Available Prices:\n`;
                Object.entries(pcData.prices).forEach(([key, priceInfo]) => {
                    tooltipContent += `  ${priceInfo.grade}: ${priceInfo.price}\n`;
                });
            } else {
                tooltipContent += `No prices currently available\n`;
            }

            if (pcData.lastUpdated) {
                tooltipContent += `\nLast Updated: ${pcData.lastUpdated}`;
            }

            tooltipContent += `\nExtracted: ${new Date(pcData.timestamp).toLocaleString()}`;

            pcInfoDisplay.title = tooltipContent;

            // Make the display more visible with a subtle animation
            pcInfoDisplay.style.opacity = '0';
            setTimeout(() => {
                pcInfoDisplay.style.transition = 'opacity 0.3s ease-in-out';
                pcInfoDisplay.style.opacity = '1';
            }, 100);

            debugLog(`✅ Updated PC display with ${displayLines.length} lines:`, displayLines);

            // Cache the data (not HTML) for future page loads
            const listingUrl = getListingUrl(listingElement);
            debugLog(`🔑 Attempting to cache - listingUrl: ${listingUrl ? listingUrl.substring(0, 80) : 'NULL'}`);
            if (listingUrl) {
                debugLog(`💾 Storing cache with keys:`, {
                    cardName: pcData.cardName,
                    setName: pcData.setName,
                    hasPrices: !!pcData.prices,
                    hasGrade: !!detectedGrade
                });
                storeListingDisplayCache(listingUrl, {
                    cardName: pcData.cardName,
                    setName: pcData.setName,
                    prices: pcData.prices,
                    detectedGrade: detectedGrade,
                    extractedCardName: pcData.extractedCardName,
                    extractedSetName: pcData.extractedSetName,
                    extractedCardNumber: pcData.extractedCardNumber,
                    lastUpdated: pcData.lastUpdated,
                    url: pcData.url,
                    imageUrl: pcData.imageUrl
                });
                debugLog(`✅ Cache stored successfully!`);
            } else {
                debugLog(`❌ Cannot cache - listingUrl is null!`);
            }

            // Color the eBay price based on PriceCharting comparison
            colorEbayPriceBasedOnComparison(listingElement, pcData, detectedGrade);
        }

        // Helper function to build display HTML from cached data
        function buildDisplayFromCache(cachedData) {
            const displayLines = [];
            const detectedGrade = cachedData.detectedGrade;
            let hasValidPrices = false;

            if (cachedData.prices && Object.keys(cachedData.prices).length > 0) {
                if (detectedGrade) {
                    // Show detected grade price first (highlighted)
                    const gradePrice = cachedData.prices[detectedGrade.key];
                    if (gradePrice) {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: ${gradePrice.price}</span>`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">${detectedGrade.displayName}: Unpriced</span>`);
                    }

                    // Show ungraded as baseline reference
                    if (cachedData.prices['ungraded']) {
                        displayLines.push(`Ungraded: ${cachedData.prices['ungraded'].price}`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`Ungraded: Unpriced`);
                    }
                } else {
                    // No grade detected, show only ungraded price (highlighted)
                    if (cachedData.prices['ungraded']) {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: ${cachedData.prices['ungraded'].price}</span>`);
                        hasValidPrices = true;
                    } else {
                        displayLines.push(`<span style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 3px; font-weight: bold;">Ungraded: Unpriced</span>`);
                    }
                }

                // Add total count if there are more prices available
                const totalPrices = Object.keys(cachedData.prices).length;
                const shownPrices = displayLines.length;
                if (totalPrices > shownPrices) {
                    displayLines.push(`+${totalPrices - shownPrices} more grades available`);
                }
            } else {
                if (detectedGrade) {
                    displayLines.push(`<strong>${detectedGrade.displayName}: Unpriced</strong>`);
                } else {
                    displayLines.push('<strong>Ungraded: Unpriced</strong>');
                }
            }

            // Build header text
            let headerText = 'PC:';
            const displayCardName = cachedData.extractedCardName || cachedData.cardName || 'Unknown Card';
            if (displayCardName && displayCardName !== 'Unknown' && displayCardName !== 'Unknown Card') {
                if (detectedGrade) {
                    if (detectedGrade.key.toUpperCase().includes('BGS') && parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (👑${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 10.0) {
                        headerText = `${displayCardName} (💎${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.5) {
                        headerText = `${displayCardName} (⭐${detectedGrade.displayName})`;
                    } else if (parseFloat(detectedGrade.grade) >= 9.0) {
                        headerText = `${displayCardName} (✨${detectedGrade.displayName})`;
                    } else {
                        headerText = `${displayCardName} (${detectedGrade.displayName})`;
                    }
                } else {
                    headerText = `${displayCardName}`;
                }
            }

            // Add set name if available
            let setNameLine = '';
            if (cachedData.extractedSetName) {
                setNameLine = `<div style="font-size: 12px; opacity: 0.8; margin-top: 2px; font-weight: bold;">${cachedData.extractedSetName}</div>`;
            }

            // Add card image if available (on the right side)
            let imageHtml = '';
            if (cachedData.imageUrl) {
                imageHtml = `<img src="${cachedData.imageUrl}" alt="Card Image" style="float: right; width: 60px; height: auto; margin-left: 8px; margin-top: -4px; border-radius: 3px; border: 1px solid rgba(255,255,255,0.3);">`;
            }

            // Build tooltip
            let tooltipContent = `PriceCharting Data:\n`;
            tooltipContent += `Card: ${cachedData.extractedCardName || cachedData.cardName || 'Unknown'}\n`;
            if (cachedData.extractedCardNumber) {
                tooltipContent += `Number: #${cachedData.extractedCardNumber}\n`;
            }
            if (detectedGrade) {
                tooltipContent += `Detected Grade: ${detectedGrade.displayName}\n`;
            }
            tooltipContent += `URL: ${cachedData.url}\n\n`;

            if (cachedData.prices && Object.keys(cachedData.prices).length > 0) {
                tooltipContent += `All Available Prices:\n`;
                Object.entries(cachedData.prices).forEach(([key, priceInfo]) => {
                    tooltipContent += `  ${priceInfo.grade}: ${priceInfo.price}\n`;
                });
            } else {
                tooltipContent += `No prices currently available\n`;
            }

            if (cachedData.lastUpdated) {
                tooltipContent += `\nLast Updated: ${cachedData.lastUpdated}`;
            }

            tooltipContent += `\nExtracted: ${new Date(cachedData.timestamp).toLocaleString()}`;

            return {
                html: `${imageHtml}<strong>${headerText}</strong>${setNameLine}<br>${displayLines.join('<br>')}`,
                tooltip: tooltipContent
            };
        }

        // Function to color eBay price based on PriceCharting data comparison
        function colorEbayPriceBasedOnComparison(listingElement, pcData, detectedGrade) {
            try {
                // Find the eBay price element
                const priceElement = listingElement.querySelector('.s-card__price, .s-item__price, .su-styled-text.primary.bold.large-1.s-card__price');
                if (!priceElement) {
                    debugLog('🔍 eBay price element not found for comparison');
                    return;
                }
                
                // Always check and remove existing modifications before updating
                const existingPercentage = priceElement.querySelector('.price-percentage');
                if (existingPercentage) {
                    existingPercentage.remove();
                    debugLog('🧹 Removed existing .price-percentage');
                }
                
                // If the price element has been modified, reset it to get the raw price
                const styledPriceSpan = Array.from(priceElement.children).find(el => 
                    el.tagName === 'SPAN' && el.style.color && el.textContent.includes('$')
                );
                if (styledPriceSpan) {
                    // Extract just the price number
                    const priceMatch = priceElement.textContent.match(/\$[\d,]+\.?\d*/);
                    if (priceMatch) {
                        priceElement.textContent = priceMatch[0];
                        debugLog(`🧹 Reset price element to: ${priceMatch[0]}`);
                    }
                }

                // Extract the eBay price value
                const priceText = priceElement.textContent.trim();
                const priceMatch = priceText.match(/\$?([\d,]+\.?\d*)/);
                if (!priceMatch) {
                    debugLog('🔍 Could not parse eBay price:', priceText);
                    return;
                }

                const ebayPrice = parseFloat(priceMatch[1].replace(/,/g, ''));
                debugLog(`💰 eBay price: $${ebayPrice}`);

                // Determine which PriceCharting price to compare against
                let comparisonPrice = null;
                let comparisonGrade = 'Ungraded';

                if (pcData.prices && Object.keys(pcData.prices).length > 0) {
                    if (detectedGrade && pcData.prices[detectedGrade.key]) {
                        // Use detected grade price
                        const gradeData = pcData.prices[detectedGrade.key];
                        const gradePriceMatch = gradeData.price.match(/\$?([\d,]+\.?\d*)/);
                        if (gradePriceMatch) {
                            comparisonPrice = parseFloat(gradePriceMatch[1].replace(/,/g, ''));
                            comparisonGrade = detectedGrade.displayName;
                        }
                    } else if (pcData.prices['ungraded']) {
                        // Use ungraded price as fallback
                        const ungradedData = pcData.prices['ungraded'];
                        const ungradedPriceMatch = ungradedData.price.match(/\$?([\d,]+\.?\d*)/);
                        if (ungradedPriceMatch) {
                            comparisonPrice = parseFloat(ungradedPriceMatch[1].replace(/,/g, ''));
                            comparisonGrade = 'Ungraded';
                        }
                    } else {
                        // Use first available price
                        const firstPrice = Object.entries(pcData.prices)[0];
                        if (firstPrice) {
                            const [key, priceData] = firstPrice;
                            const firstPriceMatch = priceData.price.match(/\$?([\d,]+\.?\d*)/);
                            if (firstPriceMatch) {
                                comparisonPrice = parseFloat(firstPriceMatch[1].replace(/,/g, ''));
                                comparisonGrade = priceData.grade;
                            }
                        }
                    }
                }

                if (comparisonPrice === null) {
                    debugLog('🔍 No valid PriceCharting price found for comparison');
                    return;
                }

                debugLog(`📊 Comparing eBay $${ebayPrice} vs PriceCharting ${comparisonGrade} $${comparisonPrice}`);

                // Calculate percentage difference
                const percentageDifference = Math.abs((ebayPrice - comparisonPrice) / comparisonPrice) * 100;
                debugLog(`📊 Percentage difference: ${percentageDifference.toFixed(1)}%`);

                // Determine color based on comparison (within 5% = orange)
                let color;
                let comparison;
                if (percentageDifference <= 5) {
                    color = '#f39c12'; // Orange - within 5% (close to market value)
                    comparison = 'near';
                } else if (ebayPrice < comparisonPrice) {
                    color = '#27ae60'; // Green - good deal (more than 5% below)
                    comparison = 'below';
                } else {
                    color = '#e74c3c'; // Red - expensive (more than 5% above)
                    comparison = 'above';
                }

                // Calculate the price difference
                const priceDifference = ebayPrice - comparisonPrice;
                const percentageSign = priceDifference > 0 ? '+' : '-';
                const differenceText = priceDifference > 0 
                    ? `(+$${priceDifference.toFixed(2)})` 
                    : `(-$${Math.abs(priceDifference).toFixed(2)})`;

                // Update price element (always, since we cleaned it above if needed)
                const originalPriceText = priceElement.textContent.trim();
                
                // Clear the price element and rebuild it with styled components
                priceElement.textContent = '';
                
                // Add the original price with color
                const priceSpan = document.createElement('span');
                priceSpan.textContent = `${originalPriceText} ${differenceText}`;
                priceSpan.style.color = color;
                priceSpan.style.fontWeight = 'bold';
                priceSpan.style.textShadow = `0 0 2px ${color}`;
                
                // Add the percentage in smaller, black font
                const percentageSpan = document.createElement('span');
                percentageSpan.className = 'price-percentage';
                percentageSpan.textContent = ` ${percentageSign}${percentageDifference.toFixed(1)}%`;
                percentageSpan.style.color = 'black';
                percentageSpan.style.fontSize = '0.85em';
                percentageSpan.style.fontWeight = 'normal';
                
                priceElement.appendChild(priceSpan);
                priceElement.appendChild(percentageSpan);

                debugLog(`🎨 Colored eBay price ${comparison} PriceCharting ${comparisonGrade}: ${color} with differential: ${differenceText}`);

                // Update tooltip to include comparison info
                const originalTitle = priceElement.title || '';
                const newTitle = `${originalTitle}${originalTitle ? '\n' : ''}eBay: $${ebayPrice} (${comparison} PC ${comparisonGrade}: $${comparisonPrice})`;
                priceElement.title = newTitle;

            } catch (error) {
                debugLog('❌ Error in price comparison:', error);
            }
        }

        // Get unique identifier for a listing
        function getListingUrl(listingElement) {
            // Try to find the listing URL
            const linkElement = listingElement.querySelector('a[href*="/itm/"]');
            if (linkElement) {
                return linkElement.href.split('?')[0]; // Remove query params for consistent caching
            }
            
            // Fallback: use title as identifier
            const titleElement = listingElement.querySelector('.s-card__title .su-styled-text, [role="heading"] span, .s-item__title span, h3 span, .x-item-title-label span');
            if (titleElement) {
                return `title_${titleElement.textContent.trim()}`;
            }
            
            return null;
        }

        // Extract listing ID (item number) from listing element
        function getListingId(listingElement) {
            // Try to find the listing URL and extract ID
            const linkElement = listingElement.querySelector('a[href*="/itm/"]');
            if (linkElement) {
                const match = linkElement.href.match(/\/itm\/(\d+)/);
                if (match) {
                    return match[1]; // Return the item number
                }
            }
            
            // Try data attributes
            const itemId = listingElement.getAttribute('data-item-id') || 
                          listingElement.getAttribute('listingid');
            if (itemId) return itemId;
            
            // Fallback: use title as identifier
            const titleElement = listingElement.querySelector('.s-card__title .su-styled-text, [role="heading"] span, .s-item__title span, h3 span, .x-item-title-label span');
            if (titleElement) {
                return `title_${titleElement.textContent.trim()}`;
            }
            
            return null;
        }

        // Check if listing has already been processed
        function isListingProcessed(listingElement) {
            // Check if display already exists
            if (listingElement.querySelector('.pc-info-display')) {
                return true;
            }
            
            // Check if buttons already exist
            if (listingElement.querySelector('.pricecharting-direct-btn')) {
                return true;
            }
            
            return false;
        }

        // Add notes button to listing
        function addListingNotesButton(listingElement) {
            // Don't add if already exists
            if (listingElement.querySelector('.pokespy-notes-btn')) return;

            // Find the watch button container
            const watchContainer = listingElement.querySelector('.s-item__watchheart, .s-card__watchheart');
            if (!watchContainer) return;

            const listingId = getListingId(listingElement);
            if (!listingId) return;

            // Get existing note if any
            const existingNote = getListingNote(listingId);

            // Create notes button container
            const notesContainer = document.createElement('div');
            notesContainer.className = 'pokespy-notes-container';
            notesContainer.style.cssText = `
                position: absolute;
                top: 45px;
                right: 8px;
                z-index: 100;
            `;

            // Main notes button
            const mainButton = document.createElement('button');
            mainButton.className = 'pokespy-notes-btn';
            mainButton.innerHTML = existingNote ? getRatingIcon(existingNote.rating) : '📝';
            mainButton.title = existingNote ? `Note: ${existingNote.rating}\n${existingNote.description || 'No description'}` : 'Add note';
            mainButton.style.cssText = `
                width: 32px;
                height: 32px;
                border-radius: 50%;
                background: ${existingNote ? getRatingColor(existingNote.rating) : 'rgba(255, 255, 255, 0.9)'};
                border: 2px solid ${existingNote ? '#fff' : '#ddd'};
                cursor: pointer;
                font-size: 16px;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 2px 8px rgba(0,0,0,0.2);
                transition: all 0.3s ease;
                position: relative;
            `;

            // Rating options container (hidden by default)
            const optionsContainer = document.createElement('div');
            optionsContainer.className = 'pokespy-notes-options';
            optionsContainer.style.cssText = `
                position: absolute;
                top: 0;
                right: 0;
                display: none;
                pointer-events: auto;
                width: 180px;
                height: 180px;
            `;

            // Create rating buttons
            const ratings = [
                { type: 'good', icon: '✓', color: '#27ae60', label: 'Good' },
                { type: 'neutral', icon: '−', color: '#f39c12', label: 'Neutral' },
                { type: 'bad', icon: '✕', color: '#e74c3c', label: 'Bad' }
            ];

            ratings.forEach((rating, index) => {
                const ratingBtn = document.createElement('button');
                ratingBtn.className = `pokespy-rating-btn pokespy-rating-${rating.type}`;
                ratingBtn.innerHTML = rating.icon;
                ratingBtn.title = rating.label;
                ratingBtn.style.cssText = `
                    width: 32px;
                    height: 32px;
                    border-radius: 50%;
                    background: ${rating.color};
                    border: 2px solid #fff;
                    cursor: pointer;
                    font-size: 18px;
                    font-weight: bold;
                    color: white;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
                    position: absolute;
                    top: 0;
                    right: 0;
                    opacity: 0;
                    transform: translate(0, 0) scale(0);
                    transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.3s ease;
                    pointer-events: all;
                `;

                ratingBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    openNoteModal(listingElement, listingId, rating.type);
                    hideOptions();
                });

                optionsContainer.appendChild(ratingBtn);
            });

            notesContainer.appendChild(mainButton);
            notesContainer.appendChild(optionsContainer);

            // Fan out animation
            let isExpanded = false;
            let hoverTimeout;

            function showOptions() {
                isExpanded = true;
                optionsContainer.style.display = 'block';
                
                const positions = [
                    { x: -45, y: 45 },   // Good: down-left
                    { x: 0, y: 55 },     // Neutral: straight down
                    { x: 45, y: 45 }     // Bad: down-right
                ];
                
                const buttons = optionsContainer.querySelectorAll('.pokespy-rating-btn');
                buttons.forEach((btn, index) => {
                    setTimeout(() => {
                        const pos = positions[index];
                        btn.style.opacity = '1';
                        btn.style.transform = `translate(${pos.x}px, ${pos.y}px) scale(1)`;
                    }, index * 100);
                });
            }

            function hideOptions() {
                isExpanded = false;
                const buttons = optionsContainer.querySelectorAll('.pokespy-rating-btn');
                buttons.forEach(btn => {
                    btn.style.opacity = '0';
                    btn.style.transform = 'translate(0, 0) scale(0)';
                });
                
                setTimeout(() => {
                    optionsContainer.style.display = 'none';
                }, 300);
            }

            mainButton.addEventListener('mouseenter', () => {
                if (!isExpanded) showOptions();
            });

            notesContainer.addEventListener('mouseleave', () => {
                clearTimeout(hoverTimeout);
                if (isExpanded) {
                    setTimeout(hideOptions, 500);
                }
            });

            mainButton.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                
                if (existingNote) {
                    // If note exists, open modal to edit
                    openNoteModal(listingElement, listingId, existingNote.rating, existingNote.description);
                } else if (!isExpanded) {
                    // If no note and not expanded, show options
                    showOptions();
                }
            });

            // Insert after watch button
            watchContainer.parentNode.style.position = 'relative';
            watchContainer.parentNode.appendChild(notesContainer);
        }

        // Open modal for note description
        function openNoteModal(listingElement, listingId, rating, existingDescription = '') {
            // Remove any existing modal
            const existingModal = document.getElementById('pokespy-note-modal');
            if (existingModal) existingModal.remove();

            const modal = document.createElement('div');
            modal.id = 'pokespy-note-modal';
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.7);
                z-index: 999999;
                display: flex;
                align-items: center;
                justify-content: center;
                animation: fadeIn 0.2s ease;
            `;

            const modalContent = document.createElement('div');
            modalContent.style.cssText = `
                background: #2f3136;
                color: white;
                padding: 24px;
                border-radius: 12px;
                max-width: 500px;
                width: 90%;
                box-shadow: 0 10px 40px rgba(0,0,0,0.5);
                animation: slideIn 0.3s ease;
            `;

            const ratingColor = getRatingColor(rating);
            const ratingIcon = getRatingIcon(rating);

            modalContent.innerHTML = `
                <div style="display: flex; align-items: center; margin-bottom: 16px;">
                    <div style="width: 40px; height: 40px; border-radius: 50%; background: ${ratingColor}; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; margin-right: 12px;">
                        ${ratingIcon}
                    </div>
                    <div>
                        <div style="font-size: 18px; font-weight: bold;">Add Note</div>
                        <div style="font-size: 12px; opacity: 0.7;">${rating.charAt(0).toUpperCase() + rating.slice(1)} Rating</div>
                    </div>
                </div>
                <textarea id="pokespy-note-textarea" placeholder="Why did you choose this rating?" style="
                    width: 100%;
                    min-height: 120px;
                    padding: 12px;
                    background: #40444b;
                    border: 2px solid ${ratingColor};
                    border-radius: 8px;
                    color: white;
                    font-family: inherit;
                    font-size: 14px;
                    resize: vertical;
                    margin-bottom: 16px;
                ">${existingDescription}</textarea>
                <div style="display: flex; gap: 12px; justify-content: flex-end;">
                    <button id="pokespy-note-cancel" style="
                        padding: 10px 20px;
                        background: #5865f2;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Cancel</button>
                    <button id="pokespy-note-delete" style="
                        padding: 10px 20px;
                        background: #ed4245;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                        display: ${existingDescription ? 'block' : 'none'};
                    ">Delete</button>
                    <button id="pokespy-note-save" style="
                        padding: 10px 20px;
                        background: ${ratingColor};
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Save Note</button>
                </div>
            `;

            // Add animation styles
            const style = document.createElement('style');
            style.textContent = `
                @keyframes fadeIn {
                    from { opacity: 0; }
                    to { opacity: 1; }
                }
                @keyframes slideIn {
                    from { transform: translateY(-20px); opacity: 0; }
                    to { transform: translateY(0); opacity: 1; }
                }
            `;
            document.head.appendChild(style);

            modal.appendChild(modalContent);
            document.body.appendChild(modal);

            // Focus textarea
            const textarea = document.getElementById('pokespy-note-textarea');
            textarea.focus();

            // Event handlers
            document.getElementById('pokespy-note-cancel').addEventListener('click', () => {
                modal.remove();
            });

            document.getElementById('pokespy-note-delete').addEventListener('click', () => {
                const noteKey = `listing_note_${listingId}`;
                GM_deleteValue(noteKey);
                
                // Update button
                const notesBtn = listingElement.querySelector('.pokespy-notes-btn');
                if (notesBtn) {
                    notesBtn.innerHTML = '📝';
                    notesBtn.title = 'Add note';
                    notesBtn.style.background = 'rgba(255, 255, 255, 0.9)';
                    notesBtn.style.borderColor = '#ddd';
                }
                
                modal.remove();
            });

            document.getElementById('pokespy-note-save').addEventListener('click', () => {
                const description = textarea.value.trim();
                
                storeListingNote(listingId, {
                    rating: rating,
                    description: description
                });

                // Update button
                const notesBtn = listingElement.querySelector('.pokespy-notes-btn');
                if (notesBtn) {
                    notesBtn.innerHTML = getRatingIcon(rating);
                    notesBtn.title = `Note: ${rating}\n${description || 'No description'}`;
                    notesBtn.style.background = getRatingColor(rating);
                    notesBtn.style.borderColor = '#fff';
                }

                modal.remove();
            });

            // Close on background click
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    modal.remove();
                }
            });

            // Close on escape key
            const escHandler = (e) => {
                if (e.key === 'Escape') {
                    modal.remove();
                    document.removeEventListener('keydown', escHandler);
                }
            };
            document.addEventListener('keydown', escHandler);
        }

        // Update the addPriceChartingButtons function
        function addPriceChartingButtons() {
            const listings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card, [data-testid="listing-card"]');

            debugLog(`Found ${listings.length} listings to process`);

            if (listings.length === 0) return;

            // Process in smaller batches
            const batchSize = 5;
            let processed = 0;
            let skipped = 0;

            function processBatch() {
                const endIndex = Math.min(processed + batchSize, listings.length);

                for (let i = processed; i < endIndex; i++) {
                    const listing = listings[i];
                    
                    const listingUrl = getListingUrl(listing);
                    const info = extractListingInfo(listing);
                    
                    // Try to restore from cache first (even if buttons exist)
                    if (listingUrl) {
                        const cached = getListingDisplayCache(listingUrl);
                        if (cached && cached.prices) {
                            debugLog(`📦 Found cache for: ${listingUrl.substring(0, 80)}...`);
                            // Check if display already exists
                            if (!listing.querySelector('.pc-info-display')) {
                                debugLog(`📦 Restoring display from cached data for: ${listingUrl.substring(0, 80)}...`);
                                
                                // Build display from cached data
                                const displayContent = buildDisplayFromCache(cached);
                                
                                // Create display element
                                const pcInfoDisplay = document.createElement('div');
                                pcInfoDisplay.className = 'pc-info-display';
                                pcInfoDisplay.innerHTML = displayContent.html;
                                pcInfoDisplay.title = displayContent.tooltip;
                                
                                Object.assign(pcInfoDisplay.style, {
                                    display: 'block',
                                    marginTop: '4px',
                                    padding: '6px 10px',
                                    background: '#9b59b6',
                                    color: 'white',
                                    borderRadius: '4px',
                                    fontSize: '12px',
                                    fontWeight: 'bold',
                                    textAlign: 'left',
                                    lineHeight: '1.4',
                                    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
                                    border: '1px solid #8e44ad',
                                    opacity: '0',
                                    transition: 'opacity 0.3s ease-in-out'
                                });
                                
                                // Find the best place to insert the display
                                const priceElement = listing.querySelector('.s-card__price, .s-item__price, .s-item__price-range, .notranslate');
                                if (priceElement && priceElement.parentNode) {
                                    priceElement.parentNode.insertBefore(pcInfoDisplay, priceElement.nextSibling);
                                } else {
                                    listing.appendChild(pcInfoDisplay);
                                }
                                
                                // Animate in
                                setTimeout(() => {
                                    pcInfoDisplay.style.opacity = '1';
                                }, 100);
                                
                                // Update price comparison (since eBay price can change)
                                const pcData = {
                                    cardName: cached.cardName,
                                    setName: cached.setName,
                                    prices: cached.prices,
                                    extractedCardName: cached.extractedCardName,
                                    extractedSetName: cached.extractedSetName
                                };
                                colorEbayPriceBasedOnComparison(listing, pcData, cached.detectedGrade);
                                debugLog(`🔄 Updated price comparison from cached data`);
                                
                                // Still add buttons for functionality (if not already present)
                                if (info.fullCardNumber) {
                                    addGooglePriceChartingButton(listing, info.title);
                                    addPriceChartingViewButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                                    addPriceChartingDirectButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                                } else if (info.setName) {
                                    addGooglePriceChartingButton(listing, info.title);
                                    addPriceChartingDirectButton(listing, null, null, 'title-based');
                                } else {
                                    addGooglePriceChartingButton(listing, info.title);
                                }
                                
                                // Add notes button
                                addListingNotesButton(listing);
                            } else {
                                // Display exists, but update price comparison dynamically
                                const pcData = {
                                    cardName: cached.cardName,
                                    setName: cached.setName,
                                    prices: cached.prices,
                                    extractedCardName: cached.extractedCardName,
                                    extractedSetName: cached.extractedSetName
                                };
                                colorEbayPriceBasedOnComparison(listing, pcData, cached.detectedGrade);
                                debugLog(`🔄 Updated price comparison from cache (display already exists)`);
                            }
                            
                            skipped++;
                            continue;
                        } else {
                            debugLog(`⚠️ No cache found for: ${listingUrl.substring(0, 80)}...`);
                        }
                    }
                    
                    // Skip if already processed and no cache available
                    if (isListingProcessed(listing)) {
                        debugLog(`⏭️ Skipping already-processed listing (has buttons/display, no cache)`);
                        skipped++;
                        continue;
                    }

                    // Debug logging for button creation
                    if (!info.fullCardNumber && info.setName) {
                        debugLog(`📋 Card info - cardNumber: ${info.cardNumber}, setName: ${info.setName}`);
                    }

                    // Add buttons if we have card number OR set name (for title-based matching)
                    if (info.fullCardNumber) {
                        addGooglePriceChartingButton(listing, info.title);
                        addPriceChartingViewButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                        addPriceChartingDirectButton(listing, info.cardNumber, info.setNumber, info.matchedPattern);
                    } else if (info.setName) {
                        // No card number, but we have set name - add title-based search button
                        debugLog(`✅ Adding title-based search button for set: ${info.setName}`);
                        addGooglePriceChartingButton(listing, info.title);
                        addPriceChartingDirectButton(listing, null, null, 'title-based');
                    } else {
                        addGooglePriceChartingButton(listing, info.title);
                    }
                    
                    // Add notes button
                    addListingNotesButton(listing);
                }

                processed = endIndex;

                // Schedule next batch if there are more items
                if (processed < listings.length) {
                    setTimeout(() => requestAnimationFrame(processBatch), TIMING.BATCH_PROCESSING_DELAY);
                } else if (skipped > 0) {
                    debugLog(`✅ Processing complete! Skipped ${skipped} already-processed listing(s)`);
                }
            }

            // Start processing
            processBatch();
        }

        // Create floating control panel for PriceCharting functionality
        function createPCControlPanel() {
            // Don't add multiple panels
            if (document.getElementById('pokespy-pc-panel')) return;

            const panel = document.createElement('div');
            panel.id = 'pokespy-pc-panel';
            panel.style.cssText = `
                position: fixed;
                top: 10px;
                right: 10px;
                background: #2f3136;
                color: #ffffff;
                padding: 12px;
                border-radius: 8px;
                z-index: 10000;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 12px;
                border: 2px solid #9b59b6;
                min-width: 220px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            `;

            panel.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 10px; color: #9b59b6; font-size: 14px; display: flex; align-items: center; justify-content: space-between; gap: 6px;">
                    <div style="display: flex; align-items: center; gap: 6px;">
                        <span>💎</span>
                        <span>PokeSpy - Search Page</span>
                    </div>
                    <button id="pokespy-search-minimize-btn" style="
                        background: transparent;
                        border: none;
                        color: #b9bbbe;
                        font-size: 16px;
                        cursor: pointer;
                        padding: 4px;
                        line-height: 1;
                        transition: color 0.2s ease;
                    " title="Minimize panel">−</button>
                </div>
                <div id="pokespy-search-panel-content">
                    <div style="margin-bottom: 8px;">
                        <button id="pokespy-check-all-btn" style="
                        width: 100%;
                        padding: 8px 12px;
                        background: linear-gradient(45deg, #9b59b6, #8e44ad);
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 13px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🚀 Check All Prices</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-stop-check-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #e74c3c;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">⏹️ Stop</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-clear-cache-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #95a5a6;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🗑️ Clear Cache</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-hide-bad-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #e74c3c;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">✕ Hide Bad Listings</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-auto-refresh-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #3498db;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🔄 Auto-Refresh OFF</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-discord-settings-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #5865f2;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">⚙️ Discord Webhook</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-ebay-api-settings-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #e67e22;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">🔑 eBay API Key</button>
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-api-poll-btn" style="
                        width: 100%;
                        padding: 6px 10px;
                        background: #16a085;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">📡 API Poll OFF</button>
                </div>
                    <div style="font-size: 11px; opacity: 0.8; margin-top: 8px; padding-top: 8px; border-top: 1px solid #444;">
                        <div>Status: <span id="pokespy-status" style="color: #43b581; font-weight: bold;">Ready</span></div>
                        <div id="pokespy-progress" style="margin-top: 4px; display: none;">
                            Progress: <span id="pokespy-progress-text" style="font-weight: bold;">0/0</span>
                        </div>
                    </div>
                </div>
            `;

            document.body.appendChild(panel);

            // Add minimize functionality
            let isMinimized = false;
            const minimizeBtn = document.getElementById('pokespy-search-minimize-btn');
            const panelContent = document.getElementById('pokespy-search-panel-content');
            const panelHeader = minimizeBtn.parentElement;
            
            // Create minimized icon
            const minimizedIcon = document.createElement('div');
            minimizedIcon.innerHTML = '💎';
            minimizedIcon.style.cssText = `
                font-size: 20px;
                cursor: pointer;
                display: none;
                width: 100%;
                height: 100%;
                display: flex;
                align-items: center;
                justify-content: center;
            `;
            minimizedIcon.title = 'Expand PokeSpy panel';
            panel.appendChild(minimizedIcon);
            
            function toggleMinimize() {
                isMinimized = !isMinimized;
                if (isMinimized) {
                    panelContent.style.display = 'none';
                    panelHeader.style.display = 'none';
                    minimizedIcon.style.display = 'flex';
                    panel.style.minWidth = 'auto';
                    panel.style.width = '40px';
                    panel.style.height = '40px';
                    panel.style.padding = '0';
                } else {
                    panelContent.style.display = 'block';
                    panelHeader.style.display = 'flex';
                    minimizedIcon.style.display = 'none';
                    panel.style.minWidth = '220px';
                    panel.style.width = 'auto';
                    panel.style.height = 'auto';
                    panel.style.padding = '12px';
                }
            }
            
            minimizeBtn.addEventListener('click', toggleMinimize);
            minimizedIcon.addEventListener('click', toggleMinimize);
            
            minimizeBtn.addEventListener('mouseenter', () => {
                minimizeBtn.style.color = '#ffffff';
            });
            minimizeBtn.addEventListener('mouseleave', () => {
                minimizeBtn.style.color = '#b9bbbe';
            });

            // Add hover effect for check all button
            const checkAllButton = document.getElementById('pokespy-check-all-btn');
            checkAllButton.addEventListener('mouseenter', () => {
                checkAllButton.style.transform = 'translateY(-2px)';
                checkAllButton.style.boxShadow = '0 4px 8px rgba(155,89,182,0.4)';
            });

            checkAllButton.addEventListener('mouseleave', () => {
                checkAllButton.style.transform = 'translateY(0)';
                checkAllButton.style.boxShadow = 'none';
            });

            // Store stop flag
            let shouldStop = false;

            // Stop button handler
            document.getElementById('pokespy-stop-check-btn').addEventListener('click', () => {
                shouldStop = true;
                document.getElementById('pokespy-status').textContent = 'Stopping...';
                document.getElementById('pokespy-status').style.color = '#e67e22';
            });

            // Clear cache button handler
            document.getElementById('pokespy-clear-cache-btn').addEventListener('click', () => {
                const confirmClear = confirm('Clear all cached listing displays? This will remove saved price data from previous sessions.');
                if (confirmClear) {
                    const keys = GM_listValues();
                    let clearedCount = 0;
                    
                    keys.forEach(key => {
                        if (key.startsWith('listing_cache_')) {
                            GM_deleteValue(key);
                            clearedCount++;
                        }
                    });
                    
                    // Also remove all existing displays from current page
                    document.querySelectorAll('.pc-info-display').forEach(display => display.remove());
                    
                    alert(`✅ Cleared ${clearedCount} cached listing(s). Page will reload.`);
                    location.reload();
                }
            });

            // Discord webhook settings modal
            const discordSettingsBtn = document.getElementById('pokespy-discord-settings-btn');
            const webhookUrl = GM_getValue('discord_webhook_url', '');
            
            // Update button text based on webhook status
            if (webhookUrl) {
                discordSettingsBtn.innerHTML = '✅ Discord Webhook';
            }
            
            // eBay API key settings modal
            const ebayApiSettingsBtn = document.getElementById('pokespy-ebay-api-settings-btn');
            const ebayClientId = GM_getValue('ebay_client_id', '');
            const ebayClientSecret = GM_getValue('ebay_client_secret', '');
            
            // Update button text based on API key status
            if (ebayClientId && ebayClientSecret) {
                ebayApiSettingsBtn.innerHTML = '✅ eBay API Key';
            }
            
            ebayApiSettingsBtn.addEventListener('click', () => {
                const currentClientId = GM_getValue('ebay_client_id', '');
                const currentClientSecret = GM_getValue('ebay_client_secret', '');
                const currentEnvironment = GM_getValue('ebay_api_environment', 'sandbox');
                
                const modal = document.createElement('div');
                modal.style.cssText = `
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: rgba(0, 0, 0, 0.7);
                    z-index: 999999;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                `;
                
                const modalContent = document.createElement('div');
                modalContent.style.cssText = `
                    background: #2f3136;
                    color: white;
                    padding: 24px;
                    border-radius: 12px;
                    max-width: 650px;
                    width: 90%;
                    box-shadow: 0 10px 40px rgba(0,0,0,0.5);
                    max-height: 80vh;
                    overflow-y: auto;
                `;
                
                modalContent.innerHTML = `
                    <div style="font-size: 18px; font-weight: bold; margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
                        <span style="font-size: 24px;">🔑</span>
                        eBay Developer API Credentials
                    </div>
                    <div style="margin-bottom: 16px; font-size: 13px; opacity: 0.9; line-height: 1.5;">
                        <p style="margin: 0 0 12px 0;"><strong>⚠️ IMPORTANT: Use SANDBOX credentials for testing!</strong></p>
                        <p style="margin: 0 0 12px 0;">To use the eBay Browse API:</p>
                        <ol style="margin: 0; padding-left: 20px;">
                            <li>Go to <a href="https://developer.ebay.com/" target="_blank" style="color: #5865f2;">developer.ebay.com</a></li>
                            <li>Sign in with your eBay account</li>
                            <li>Go to <strong>My Account → Application Keys</strong></li>
                            <li>Under <strong>Sandbox Keys</strong> section, click "Create a keyset" if you don't have one</li>
                            <li>Find the <strong>OAuth credentials</strong> section</li>
                            <li>Copy <strong>App ID (Client ID)</strong> and <strong>Cert ID (Client Secret)</strong></li>
                        </ol>
                        <p style="margin: 12px 0 0 0; font-size: 12px; opacity: 0.7; background: #e67e22; padding: 8px; border-radius: 4px;">
                            💡 <strong>Tip:</strong> Production keys require approval and won't work for most apps. Start with Sandbox!
                        </p>
                    </div>
                    <div style="margin-bottom: 12px;">
                        <label style="display: block; margin-bottom: 4px; font-size: 12px; opacity: 0.8;">Environment:</label>
                        <select id="pokespy-environment-select" style="
                            width: 100%;
                            padding: 10px;
                            background: #40444b;
                            border: 2px solid #e67e22;
                            border-radius: 6px;
                            color: white;
                            font-size: 12px;
                        ">
                            <option value="sandbox" ${currentEnvironment === 'sandbox' ? 'selected' : ''}>Sandbox (for testing)</option>
                            <option value="production" ${currentEnvironment === 'production' ? 'selected' : ''}>Production (requires approval)</option>
                        </select>
                    </div>
                    <div style="margin-bottom: 12px;">
                        <label style="display: block; margin-bottom: 4px; font-size: 12px; opacity: 0.8;">App ID (Client ID):</label>
                        <input type="text" id="pokespy-client-id-input" placeholder="YourAppName-YourApp-SBX-... (for Sandbox)" value="${currentClientId}" style="
                            width: 100%;
                            padding: 10px;
                            background: #40444b;
                            border: 2px solid #e67e22;
                            border-radius: 6px;
                            color: white;
                            font-family: monospace;
                            font-size: 11px;
                        ">
                    </div>
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; margin-bottom: 4px; font-size: 12px; opacity: 0.8;">Cert ID (Client Secret):</label>
                        <input type="password" id="pokespy-client-secret-input" placeholder="SBX-... (for Sandbox)" value="${currentClientSecret}" style="
                            width: 100%;
                            padding: 10px;
                            background: #40444b;
                            border: 2px solid #e67e22;
                            border-radius: 6px;
                            color: white;
                            font-family: monospace;
                            font-size: 11px;
                        ">
                    </div>
                    <div style="display: flex; gap: 12px; justify-content: flex-end;">
                        <button id="pokespy-api-key-cancel" style="
                            padding: 10px 20px;
                            background: #95a5a6;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                        ">Cancel</button>
                        <button id="pokespy-api-key-clear" style="
                            padding: 10px 20px;
                            background: #e74c3c;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                            display: ${currentClientId || currentClientSecret ? 'block' : 'none'};
                        ">Clear</button>
                        <button id="pokespy-api-key-save" style="
                            padding: 10px 20px;
                            background: #e67e22;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                        ">Save</button>
                    </div>
                `;
                
                modal.appendChild(modalContent);
                document.body.appendChild(modal);
                
                const clientIdInput = document.getElementById('pokespy-client-id-input');
                const clientSecretInput = document.getElementById('pokespy-client-secret-input');
                const environmentSelect = document.getElementById('pokespy-environment-select');
                clientIdInput.focus();
                
                document.getElementById('pokespy-api-key-cancel').addEventListener('click', () => {
                    modal.remove();
                });
                
                document.getElementById('pokespy-api-key-clear').addEventListener('click', () => {
                    GM_setValue('ebay_client_id', '');
                    GM_setValue('ebay_client_secret', '');
                    GM_setValue('ebay_api_environment', 'sandbox');
                    GM_deleteValue('ebay_api_token');
                    GM_deleteValue('ebay_api_token_expires');
                    ebayApiSettingsBtn.innerHTML = '🔑 eBay API Key';
                    modal.remove();
                });
                
                document.getElementById('pokespy-api-key-save').addEventListener('click', () => {
                    const clientId = clientIdInput.value.trim();
                    const clientSecret = clientSecretInput.value.trim();
                    const environment = environmentSelect.value;
                    
                    if (!clientId || !clientSecret) {
                        alert('⚠️ Please enter both App ID (Client ID) and Cert ID (Client Secret)');
                        return;
                    }
                    
                    GM_setValue('ebay_client_id', clientId);
                    GM_setValue('ebay_client_secret', clientSecret);
                    GM_setValue('ebay_api_environment', environment);
                    // Clear cached token when credentials change
                    GM_deleteValue('ebay_api_token');
                    GM_deleteValue('ebay_api_token_expires');
                    ebayApiSettingsBtn.innerHTML = '✅ eBay API Key';
                    modal.remove();
                });
                
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) modal.remove();
                });
            });
            
            discordSettingsBtn.addEventListener('click', () => {
                const currentUrl = GM_getValue('discord_webhook_url', '');
                
                const modal = document.createElement('div');
                modal.style.cssText = `
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: rgba(0, 0, 0, 0.7);
                    z-index: 999999;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                `;
                
                const modalContent = document.createElement('div');
                modalContent.style.cssText = `
                    background: #2f3136;
                    color: white;
                    padding: 24px;
                    border-radius: 12px;
                    max-width: 500px;
                    width: 90%;
                    box-shadow: 0 10px 40px rgba(0,0,0,0.5);
                `;
                
                modalContent.innerHTML = `
                    <div style="font-size: 18px; font-weight: bold; margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
                        <span style="font-size: 24px;">🔗</span>
                        Discord Webhook Settings
                    </div>
                    <div style="margin-bottom: 12px; font-size: 13px; opacity: 0.8; line-height: 1.4;">
                        Enter your Discord webhook URL to receive notifications when new listings appear in the top 8.
                    </div>
                    <input type="text" id="pokespy-webhook-input" placeholder="https://discord.com/api/webhooks/..." value="${currentUrl}" style="
                        width: 100%;
                        padding: 10px;
                        background: #40444b;
                        border: 2px solid #5865f2;
                        border-radius: 6px;
                        color: white;
                        font-family: monospace;
                        font-size: 12px;
                        margin-bottom: 16px;
                    ">
                    <div style="display: flex; gap: 12px; justify-content: flex-end;">
                        <button id="pokespy-webhook-cancel" style="
                            padding: 10px 20px;
                            background: #95a5a6;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                        ">Cancel</button>
                        <button id="pokespy-webhook-clear" style="
                            padding: 10px 20px;
                            background: #e74c3c;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                            display: ${currentUrl ? 'block' : 'none'};
                        ">Clear</button>
                        <button id="pokespy-webhook-save" style="
                            padding: 10px 20px;
                            background: #5865f2;
                            color: white;
                            border: none;
                            border-radius: 6px;
                            font-size: 14px;
                            font-weight: bold;
                            cursor: pointer;
                        ">Save</button>
                    </div>
                `;
                
                modal.appendChild(modalContent);
                document.body.appendChild(modal);
                
                const input = document.getElementById('pokespy-webhook-input');
                input.focus();
                
                document.getElementById('pokespy-webhook-cancel').addEventListener('click', () => {
                    modal.remove();
                });
                
                document.getElementById('pokespy-webhook-clear').addEventListener('click', () => {
                    GM_setValue('discord_webhook_url', '');
                    discordSettingsBtn.innerHTML = '⚙️ Discord Webhook';
                    modal.remove();
                });
                
                document.getElementById('pokespy-webhook-save').addEventListener('click', () => {
                    const url = input.value.trim();
                    if (url && !url.startsWith('https://discord.com/api/webhooks/')) {
                        alert('Invalid Discord webhook URL. It should start with https://discord.com/api/webhooks/');
                        return;
                    }
                    GM_setValue('discord_webhook_url', url);
                    discordSettingsBtn.innerHTML = url ? '✅ Discord Webhook' : '⚙️ Discord Webhook';
                    modal.remove();
                });
                
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) modal.remove();
                });
            });
            
            // Function to send listing to Discord webhook
            async function sendToDiscord(listingData) {
                const webhookUrl = GM_getValue('discord_webhook_url', '');
                if (!webhookUrl) return;
                
                try {
                    const embed = {
                        title: listingData.title,
                        url: listingData.url,
                        color: 0x9b59b6, // Purple color
                        fields: [
                            {
                                name: 'Price',
                                value: listingData.price || 'N/A',
                                inline: true
                            },
                            {
                                name: 'Time Left',
                                value: listingData.timeLeft || 'N/A',
                                inline: true
                            }
                        ],
                        timestamp: new Date().toISOString()
                    };
                    
                    if (listingData.imageUrl) {
                        embed.thumbnail = { url: listingData.imageUrl };
                    }
                    
                    const payload = {
                        content: '🆕 **New Listing Detected!**',
                        embeds: [embed]
                    };
                    
                    const response = await fetch(webhookUrl, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify(payload)
                    });
                    
                    if (response.ok) {
                        debugLog('✅ Sent to Discord successfully');
                    } else {
                        debugLog('❌ Failed to send to Discord:', response.status, response.statusText);
                    }
                } catch (error) {
                    debugLog('❌ Error sending to Discord:', error);
                }
            }
            
            // eBay API OAuth token management
            async function getEbayApiToken() {
                const clientId = GM_getValue('ebay_client_id', '');
                const clientSecret = GM_getValue('ebay_client_secret', '');
                
                if (!clientId || !clientSecret) {
                    debugLog('❌ No eBay API credentials configured');
                    console.error('Missing credentials - Client ID:', clientId ? 'Set' : 'Missing', 'Client Secret:', clientSecret ? 'Set' : 'Missing');
                    return null;
                }
                
                // Check if we have a cached valid token
                const cachedToken = GM_getValue('ebay_api_token', '');
                const tokenExpires = GM_getValue('ebay_api_token_expires', 0);
                
                if (cachedToken && Date.now() < tokenExpires) {
                    debugLog('✅ Using cached eBay API token');
                    return cachedToken;
                }
                
                // Get new token using Client Credentials grant
                try {
                    debugLog('🔄 Requesting new eBay API token...');
                    const environment = GM_getValue('ebay_api_environment', 'sandbox');
                    console.log('Environment:', environment);
                    console.log('Client ID (first 10 chars):', clientId.substring(0, 10) + '...');
                    console.log('Client Secret (first 5 chars):', clientSecret.substring(0, 5) + '...');
                    
                    const credentials = btoa(`${clientId}:${clientSecret}`);
                    console.log('Base64 credentials (first 20 chars):', credentials.substring(0, 20) + '...');
                    
                    // Use sandbox or production endpoint based on environment
                    const tokenUrl = environment === 'sandbox' 
                        ? 'https://api.sandbox.ebay.com/identity/v1/oauth2/token'
                        : 'https://api.ebay.com/identity/v1/oauth2/token';
                    
                    console.log('Token URL:', tokenUrl);
                    
                    return await new Promise((resolve, reject) => {
                        GM.xmlHttpRequest({
                            method: 'POST',
                            url: tokenUrl,
                            headers: {
                                'Content-Type': 'application/x-www-form-urlencoded',
                                'Authorization': `Basic ${credentials}`
                            },
                            data: 'grant_type=client_credentials&scope=https://api.ebay.com/oauth/api_scope',
                            onload: (response) => {
                                console.log('eBay OAuth Response Status:', response.status);
                                console.log('eBay OAuth Response:', response.responseText);
                                
                                if (response.status >= 200 && response.status < 300) {
                                    try {
                                        const data = JSON.parse(response.responseText);
                                        const token = data.access_token;
                                        const expiresIn = data.expires_in || 7200; // Default 2 hours
                                        
                                        // Cache token (expires 5 minutes early to be safe)
                                        GM_setValue('ebay_api_token', token);
                                        GM_setValue('ebay_api_token_expires', Date.now() + ((expiresIn - 300) * 1000));
                                        
                                        debugLog('✅ eBay API token acquired');
                                        console.log('Token acquired, expires in:', expiresIn, 'seconds');
                                        resolve(token);
                                    } catch (err) {
                                        console.error('Failed to parse token response:', err);
                                        debugLog('❌ Failed to parse token response:', err);
                                        resolve(null);
                                    }
                                } else {
                                    console.error('Authentication failed:', response.status, response.responseText);
                                    debugLog('❌ Failed to get eBay API token:', response.status, response.responseText);
                                    
                                    let errorMsg = 'Authentication failed: ';
                                    try {
                                        const errorData = JSON.parse(response.responseText);
                                        errorMsg += errorData.error_description || errorData.error || response.responseText;
                                    } catch {
                                        errorMsg += response.responseText;
                                    }
                                    
                                    alert('❌ eBay API authentication failed.\n\n' + errorMsg + '\n\nPlease verify:\n1. Client ID is correct (from Production keyset)\n2. Client Secret is correct\n3. You\'re using Production credentials (not Sandbox)\n\nCheck browser console for more details.');
                                    resolve(null);
                                }
                            },
                            onerror: (error) => {
                                console.error('Network error getting eBay token:', error);
                                debugLog('❌ Error getting eBay API token:', error);
                                alert('❌ Cannot connect to eBay API. Network error.');
                                resolve(null);
                            }
                        });
                    });
                } catch (error) {
                    console.error('Exception in getEbayApiToken:', error);
                    debugLog('❌ Error getting eBay API token:', error);
                    return null;
                }
            }
            
            // Extract current search filters from URL
            function extractSearchFilters() {
                const url = new URL(window.location.href);
                const params = new URLSearchParams(url.search);
                
                console.log('📋 URL Parameters:', Object.fromEntries(params.entries()));
                
                // Map eBay _sop codes to Browse API sort values
                const sortMap = {
                    '1': 'price',              // Price + Shipping: lowest first
                    '10': 'newlyListed',       // Best Match -> use newly listed for monitoring
                    '12': '-price',            // Price + Shipping: highest first
                    '15': '-endingSoonest',    // Time: ending soonest
                    '16': 'price',             // Price: lowest first
                    '17': '-price'             // Price: highest first
                };
                
                const sopCode = params.get('_sop');
                const apiSort = sopCode ? (sortMap[sopCode] || 'newlyListed') : 'newlyListed';
                
                // Extract common eBay search parameters
                const filters = {
                    q: params.get('_nkw') || params.get('q') || '', // Search query
                    categoryId: params.get('_sacat') || params.get('_dcat') || params.get('categoryId'), // _dcat is also used
                    sort: apiSort, // Use mapped sort value, default to newlyListed for monitoring
                    buyingFormat: params.get('LH_BIN') === '1' ? 'FIXED_PRICE' : params.get('LH_Auction') === '1' ? 'AUCTION' : null,
                    filter: [],
                    limit: 40 // Get top 40 results to filter down
                };
                
                console.log(`🔄 Sort: eBay code ${sopCode} -> API sort '${apiSort}'`);
                
                // Add price filters
                const minPrice = params.get('_udlo');
                const maxPrice = params.get('_udhi');
                if (minPrice || maxPrice) {
                    // eBay Browse API format: price:[min..max],priceCurrency:USD
                    // Use {min} or {max} syntax for proper filtering
                    const min = minPrice || '0';
                    const max = maxPrice || '';
                    if (max) {
                        filters.filter.push(`price:[${min}..${max}],priceCurrency:USD`);
                    } else {
                        filters.filter.push(`price:[${min}..],priceCurrency:USD`);
                    }
                    console.log(`💰 Price filter: min=$${min}, max=$${max || 'none'}`);
                }
                
                // Add condition filter
                const condition = params.get('LH_ItemCondition');
                if (condition) {
                    filters.filter.push(`conditionIds:{${condition}}`);
                }
                
                // Add location filter - US only
                if (params.get('LH_PrefLoc') === '1' || params.get('LH_AV') === '1') {
                    filters.filter.push('itemLocationCountry:US');
                }
                
                const locatedIn = params.get('_fcid');
                if (locatedIn) {
                    filters.filter.push(`locatedIn:${locatedIn}`);
                }
                
                // Add authenticity guarantee to buying options if requested
                if (params.get('LH_AV') === '1') {
                    // Will be combined with buyingOptions filter below
                    filters.authenticityGuarantee = true;
                    console.log('🔍 Authenticity Guarantee filter will be applied');
                }
                
                // Add "New" filter if LH_Complete is set (completed listings)
                if (params.get('LH_Complete') === '1') {
                    filters.filter.push('listingStatus:COMPLETED');
                }
                
                // Add "Sold" filter
                if (params.get('LH_Sold') === '1') {
                    filters.filter.push('listingStatus:SOLD');
                }
                
                // Add shipping options
                if (params.get('LH_FS') === '1') { // Free shipping
                    filters.filter.push('deliveryOptions:{FREE_SHIPPING}');
                }
                
                // Add filter for items listed in the last hour (for API polling freshness)
                // This ensures we get recent items, not the same old "newly listed" items
                const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
                filters.filter.push(`listingStartDate:[${oneHourAgo}..`);
                console.log(`⏰ Filtering items listed since: ${oneHourAgo}`);
                
                // Check for custom "Graded" aspect filter
                const graded = params.get('Graded');
                const language = params.get('Language');
                
                // Build aspect_filter if we have aspect parameters
                const aspectFilters = [];
                
                if (graded) {
                    // Use the exact value from the URL parameter
                    aspectFilters.push(`Graded:{${graded}}`);
                }
                
                if (language) {
                    aspectFilters.push(`Language:{${language}}`);
                }
                
                // Store aspect filters separately - they need to be in aspect_filter parameter
                filters.aspectFilter = aspectFilters.length > 0 ? aspectFilters : null;
                filters.gradedFilter = graded; // Also store for backup client-side filtering
                
                console.log('🔍 Extracted filters:', filters);
                
                return filters;
            }
            
            // Call eBay Browse API
            async function searchEbayApi(filters) {
                const token = await getEbayApiToken();
                if (!token) {
                    debugLog('❌ Cannot search: no valid API token');
                    return null;
                }
                
                try {
                    // Build query parameters
                    const params = new URLSearchParams();
                    if (filters.q) params.append('q', filters.q);
                    if (filters.categoryId) params.append('category_ids', filters.categoryId);
                    if (filters.sort) params.append('sort', filters.sort);
                    if (filters.limit) params.append('limit', filters.limit.toString());
                    
                    // Combine all filters into a single comma-separated string
                    const allFilters = [...filters.filter];
                    
                    // Build buyingOptions filter (can have multiple values)
                    const buyingOptions = [];
                    if (filters.buyingFormat) {
                        buyingOptions.push(filters.buyingFormat);
                    }
                    if (filters.authenticityGuarantee) {
                        buyingOptions.push('AUTHENTICITY_GUARANTEE');
                    }
                    if (buyingOptions.length > 0) {
                        allFilters.push(`buyingOptions:{${buyingOptions.join('|')}}`);
                    }
                    
                    if (allFilters.length > 0) {
                        params.append('filter', allFilters.join(','));
                        console.log('🔧 Combined filters:', allFilters.join(','));
                    }
                    
                    // Add aspect_filter if present (Graded, Language, etc.)
                    if (filters.aspectFilter && filters.aspectFilter.length > 0) {
                        // aspect_filter format: categoryId:CATEGORY_ID,AspectName:{Value}
                        const aspectFilterStr = `categoryId:${filters.categoryId},${filters.aspectFilter.join(',')}`;
                        params.append('aspect_filter', aspectFilterStr);
                        console.log('🎯 Aspect filter:', aspectFilterStr);
                    }
                    
                    // Use sandbox or production endpoint based on environment
                    const environment = GM_getValue('ebay_api_environment', 'sandbox');
                    const baseUrl = environment === 'sandbox'
                        ? 'https://api.sandbox.ebay.com/buy/browse/v1/item_summary/search'
                        : 'https://api.ebay.com/buy/browse/v1/item_summary/search';
                    
                    const apiUrl = `${baseUrl}?${params.toString()}`;
                    debugLog('📡 eBay API Request:', apiUrl);
                    console.log('Using environment:', environment);
                    console.log('API URL:', apiUrl);
                    
                    return await new Promise((resolve, reject) => {
                        GM.xmlHttpRequest({
                            method: 'GET',
                            url: apiUrl,
                            headers: {
                                'Authorization': `Bearer ${token}`,
                                'X-EBAY-C-MARKETPLACE-ID': 'EBAY_US',
                                'Accept': 'application/json'
                            },
                            onload: (response) => {
                                if (response.status >= 200 && response.status < 300) {
                                    try {
                                        const data = JSON.parse(response.responseText);
                                        debugLog('✅ eBay API Response:', data);
                                        resolve(data);
                                    } catch (err) {
                                        debugLog('❌ Failed to parse API response:', err);
                                        resolve(null);
                                    }
                                } else {
                                    debugLog('❌ eBay API Error:', response.status, response.responseText);
                                    resolve(null);
                                }
                            },
                            onerror: (error) => {
                                debugLog('❌ Error calling eBay API:', error);
                                resolve(null);
                            }
                        });
                    });
                } catch (error) {
                    debugLog('❌ Error calling eBay API:', error);
                    return null;
                }
            }

            // Auto-refresh functionality
            let autoRefreshEnabled = false;
            let autoRefreshInterval = null;
            let seenListingIds = new Set(GM_getValue('seen_listing_ids', []));
            const autoRefreshBtn = document.getElementById('pokespy-auto-refresh-btn');
            
            // Function to update tab badge
            let originalTitle = document.title;
            let newListingCount = 0;
            
            function updateTabBadge(count) {
                newListingCount = count;
                if (count > 0) {
                    document.title = `(${count}) ${originalTitle}`;
                } else {
                    document.title = originalTitle;
                }
            }
            
            // Clear badge when page is focused
            window.addEventListener('focus', () => {
                if (newListingCount > 0) {
                    debugLog('🔍 Page focused - clearing badge');
                    updateTabBadge(0);
                }
            });
            
            // Function to play notification sound
            function playNotificationSound() {
                // Create a simple beep sound using Web Audio API
                const audioContext = new (window.AudioContext || window.webkitAudioContext)();
                const oscillator = audioContext.createOscillator();
                const gainNode = audioContext.createGain();
                
                oscillator.connect(gainNode);
                gainNode.connect(audioContext.destination);
                
                oscillator.frequency.value = 800; // Frequency in Hz
                oscillator.type = 'sine';
                
                gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
                gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
                
                oscillator.start(audioContext.currentTime);
                oscillator.stop(audioContext.currentTime + 0.5);
            }
            
            // Function to get top 8 listing IDs
            function getTop8ListingIds() {
                const allListings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card, [data-testid="listing-card"]');
                const listingIds = [];
                
                for (let i = 0; i < Math.min(8, allListings.length); i++) {
                    const listingId = getListingId(allListings[i]);
                    if (listingId) {
                        listingIds.push(listingId);
                    }
                }
                
                return listingIds;
            }
            
            // Function to check for new listings
            function checkForNewListings() {
                const allListings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card, [data-testid="listing-card"]');
                const currentTop8 = [];
                const currentTop8Data = [];
                
                // Get top 8 with their data
                for (let i = 0; i < Math.min(8, allListings.length); i++) {
                    const listing = allListings[i];
                    const listingId = getListingId(listing);
                    if (listingId) {
                        currentTop8.push(listingId);
                        
                        // Extract listing data for Discord
                        const titleElement = listing.querySelector('.s-item__title, h3 span, [role="heading"] span');
                        const priceElement = listing.querySelector('.s-item__price, .s-card__price');
                        const timeLeftElement = listing.querySelector('.s-item__time-left, .s-item__timeLeft, .s-item__time-end');
                        const imageElement = listing.querySelector('.s-item__image-img, img');
                        const linkElement = listing.querySelector('a.s-item__link, a[href*="/itm/"]');
                        
                        currentTop8Data.push({
                            id: listingId,
                            title: titleElement ? cleanEbayTitle(titleElement.textContent.trim()) : 'Unknown Item',
                            price: priceElement ? priceElement.textContent.trim() : null,
                            timeLeft: timeLeftElement ? timeLeftElement.textContent.trim() : null,
                            imageUrl: imageElement ? imageElement.src : null,
                            url: linkElement ? linkElement.href : `https://www.ebay.com/itm/${listingId}`
                        });
                    }
                }
                
                const newListings = currentTop8.filter(id => !seenListingIds.has(id));
                
                if (newListings.length > 0) {
                    debugLog(`🆕 Found ${newListings.length} new listing(s) in top 8!`);
                    playNotificationSound();
                    
                    // Update tab badge
                    updateTabBadge(newListings.length);
                    
                    // Send new listings to Discord
                    newListings.forEach(newId => {
                        const listingData = currentTop8Data.find(data => data.id === newId);
                        if (listingData) {
                            sendToDiscord(listingData);
                        }
                    });
                    
                    // Show notification
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = `🆕 ${newListings.length} new listing(s)!`;
                    statusElement.style.color = '#f39c12';
                    
                    // Flash the status a few times
                    let flashCount = 0;
                    const flashInterval = setInterval(() => {
                        statusElement.style.opacity = statusElement.style.opacity === '0' ? '1' : '0';
                        flashCount++;
                        if (flashCount >= 6) {
                            clearInterval(flashInterval);
                            statusElement.style.opacity = '1';
                            setTimeout(() => {
                                if (autoRefreshEnabled) {
                                    statusElement.textContent = 'Auto-refresh active';
                                    statusElement.style.color = '#3498db';
                                }
                            }, 2000);
                        }
                    }, 300);
                }
                
                // Add current top 8 to seen listings
                currentTop8.forEach(id => seenListingIds.add(id));
                
                // Save to persistent storage
                GM_setValue('seen_listing_ids', Array.from(seenListingIds));
                
                debugLog(`📊 Tracking ${seenListingIds.size} total seen listings (current top 8: ${currentTop8.length})`);
            }
            
            // Auto-refresh button handler
            autoRefreshBtn.addEventListener('click', () => {
                autoRefreshEnabled = !autoRefreshEnabled;
                
                if (autoRefreshEnabled) {
                    // Enable auto-refresh
                    autoRefreshBtn.innerHTML = '🔄 Auto-Refresh ON';
                    autoRefreshBtn.style.background = '#27ae60';
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = 'Auto-refresh active';
                    statusElement.style.color = '#3498db';
                    
                    // Check current top 8 immediately
                    checkForNewListings();
                    
                    // Set up interval to refresh page every 5 seconds
                    let countdown = 5;
                    autoRefreshInterval = setInterval(() => {
                        countdown--;
                        if (countdown <= 0) {
                            debugLog('🔄 Auto-refreshing page...');
                            location.reload();
                        } else {
                            statusElement.textContent = `Refresh in ${countdown}s`;
                        }
                    }, 1000);
                } else {
                    // Disable auto-refresh
                    autoRefreshBtn.innerHTML = '🔄 Auto-Refresh OFF';
                    autoRefreshBtn.style.background = '#3498db';
                    
                    if (autoRefreshInterval) {
                        clearInterval(autoRefreshInterval);
                        autoRefreshInterval = null;
                    }
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = 'Ready';
                    statusElement.style.color = '#43b581';
                }
            });
            
            // On page load, if auto-refresh was enabled, re-enable it
            const wasAutoRefreshEnabled = sessionStorage.getItem('pokespy_auto_refresh_enabled') === 'true';
            if (wasAutoRefreshEnabled) {
                // Small delay to ensure page is fully loaded
                setTimeout(() => {
                    autoRefreshBtn.click();
                }, 1000);
            }
            
            // Save auto-refresh state to session storage
            window.addEventListener('beforeunload', () => {
                sessionStorage.setItem('pokespy_auto_refresh_enabled', autoRefreshEnabled.toString());
            });
            
            // API Polling functionality
            let apiPollEnabled = false;
            let apiPollInterval = null;
            let apiSeenListingIds = new Set(GM_getValue('api_seen_listing_ids', []));
            const apiPollBtn = document.getElementById('pokespy-api-poll-btn');
            
            // Function to check API results for new listings
            async function checkApiForNewListings() {
                // Only run if API polling is enabled
                if (!apiPollEnabled) {
                    return;
                }
                
                const filters = extractSearchFilters();
                debugLog('🔍 Current search filters:', filters);
                
                const results = await searchEbayApi(filters);
                if (!results || !results.itemSummaries) {
                    debugLog('⚠️ No results from eBay API');
                    return;
                }
                
                console.log('📊 eBay API Results:', results);
                console.log(`📦 Found ${results.itemSummaries.length} items`);
                
                const newListings = [];
                let displayIndex = 1;
                
                results.itemSummaries.forEach((item, index) => {
                    const itemId = item.itemId;
                    const isNew = !apiSeenListingIds.has(itemId);
                    
                    // Client-side filtering for graded cards if "Graded: No" was requested
                    const filters = extractSearchFilters();
                    let shouldSkip = false;
                    
                    if (filters.gradedFilter && filters.gradedFilter.toLowerCase() === 'no') {
                        // Check if title contains grading company names
                        const title = item.title.toUpperCase();
                        const gradingKeywords = ['PSA', 'BGS', 'CGC', 'SGC', 'GRADED', 'BECKETT'];
                        if (gradingKeywords.some(keyword => title.includes(keyword))) {
                            shouldSkip = true;
                        }
                    }
                    
                    if (shouldSkip) return; // Skip this item
                    
                    console.log(`${displayIndex}. ${item.title}`);
                    console.log(`   ID: ${itemId} ${isNew ? '🆕 NEW' : '(seen)'}`);
                    console.log(`   Price: ${item.price?.value} ${item.price?.currency || ''}`);
                    console.log(`   URL: ${item.itemWebUrl}`);
                    
                    displayIndex++;
                    
                    if (isNew) {
                        newListings.push({
                            id: itemId,
                            title: item.title,
                            price: item.price ? `${item.price.value} ${item.price.currency}` : 'N/A',
                            timeLeft: item.itemEndDate ? new Date(item.itemEndDate).toLocaleString() : null,
                            imageUrl: item.image?.imageUrl || item.thumbnailImages?.[0]?.imageUrl,
                            url: item.itemWebUrl
                        });
                        apiSeenListingIds.add(itemId);
                    }
                });
                
                if (newListings.length > 0) {
                    console.log(`\n🆕 Found ${newListings.length} NEW listing(s) via API!`);
                    playNotificationSound();
                    updateTabBadge(newListings.length);
                    
                    // Send to Discord if webhook is configured
                    const webhookUrl = GM_getValue('discord_webhook_url', '');
                    if (webhookUrl) {
                        console.log('📤 Sending new listings to Discord...');
                        for (const listing of newListings) {
                            await sendToDiscord(listing);
                        }
                    }
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = `🆕 ${newListings.length} new via API!`;
                    statusElement.style.color = '#f39c12';
                    
                    setTimeout(() => {
                        if (apiPollEnabled) {
                            statusElement.textContent = 'API polling active';
                            statusElement.style.color = '#16a085';
                        }
                    }, 3000);
                }
                
                // Save seen IDs
                GM_setValue('api_seen_listing_ids', Array.from(apiSeenListingIds));
                console.log(`📊 Tracking ${apiSeenListingIds.size} total API listings\n`);
            }
            
            // API Poll button handler
            apiPollBtn.addEventListener('click', async () => {
                const clientId = GM_getValue('ebay_client_id', '');
                const clientSecret = GM_getValue('ebay_client_secret', '');
                
                if (!clientId || !clientSecret) {
                    alert('⚠️ Please configure your eBay API credentials first!\n\nYou need both Client ID and Client Secret from developer.ebay.com');
                    ebayApiSettingsBtn.click();
                    return;
                }
                
                apiPollEnabled = !apiPollEnabled;
                
                if (apiPollEnabled) {
                    apiPollBtn.innerHTML = '📡 API Poll ON';
                    apiPollBtn.style.background = '#27ae60';
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = 'API polling active';
                    statusElement.style.color = '#16a085';
                    
                    // Check immediately
                    await checkApiForNewListings();
                    
                    // Then check at configured interval
                    apiPollInterval = setInterval(async () => {
                        await checkApiForNewListings();
                    }, TIMING.API_POLL_INTERVAL);
                } else {
                    apiPollBtn.innerHTML = '📡 API Poll OFF';
                    apiPollBtn.style.background = '#16a085';
                    
                    if (apiPollInterval) {
                        clearInterval(apiPollInterval);
                        apiPollInterval = null;
                    }
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = 'Ready';
                    statusElement.style.color = '#43b581';
                }
            });

            // Hide bad listings button handler
            let badListingsHidden = false;
            const hideBadBtn = document.getElementById('pokespy-hide-bad-btn');
            
            hideBadBtn.addEventListener('click', () => {
                badListingsHidden = !badListingsHidden;
                
                if (badListingsHidden) {
                    // Hide all listings marked as bad
                    let hiddenCount = 0;
                    const allListings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card, [data-testid="listing-card"]');
                    
                    allListings.forEach(listing => {
                        const listingId = getListingId(listing);
                        if (listingId) {
                            const note = getListingNote(listingId);
                            if (note && note.rating === 'bad') {
                                listing.style.display = 'none';
                                listing.setAttribute('data-pokespy-hidden', 'true');
                                hiddenCount++;
                            }
                        }
                    });
                    
                    hideBadBtn.innerHTML = '👁️ Show Bad Listings';
                    hideBadBtn.style.background = '#27ae60';
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = `Hidden ${hiddenCount} bad listing(s)`;
                    statusElement.style.color = '#e67e22';
                    setTimeout(() => {
                        statusElement.textContent = 'Ready';
                        statusElement.style.color = '#43b581';
                    }, 3000);
                } else {
                    // Show all hidden listings
                    const hiddenListings = document.querySelectorAll('[data-pokespy-hidden="true"]');
                    hiddenListings.forEach(listing => {
                        listing.style.display = '';
                        listing.removeAttribute('data-pokespy-hidden');
                    });
                    
                    hideBadBtn.innerHTML = '✕ Hide Bad Listings';
                    hideBadBtn.style.background = '#e74c3c';
                    
                    const statusElement = document.getElementById('pokespy-status');
                    statusElement.textContent = 'Showing all listings';
                    statusElement.style.color = '#3498db';
                    setTimeout(() => {
                        statusElement.textContent = 'Ready';
                        statusElement.style.color = '#43b581';
                    }, 3000);
                }
            });

            // Check all button handler
            checkAllButton.addEventListener('click', async () => {
                shouldStop = false;
                const statusElement = document.getElementById('pokespy-status');
                const progressElement = document.getElementById('pokespy-progress');
                const progressText = document.getElementById('pokespy-progress-text');
                const originalText = checkAllButton.innerHTML;
                const pcButtons = document.querySelectorAll('.pricecharting-direct-btn');

                if (pcButtons.length === 0) {
                    statusElement.textContent = '⚠️ No PC buttons found';
                    statusElement.style.color = '#e67e22';
                    setTimeout(() => {
                        statusElement.textContent = 'Ready';
                        statusElement.style.color = '#43b581';
                    }, 3000);
                    return;
                }

                checkAllButton.disabled = true;
                checkAllButton.innerHTML = '⏳ Processing...';
                checkAllButton.style.opacity = '0.6';
                statusElement.textContent = 'Running';
                statusElement.style.color = '#3498db';
                progressElement.style.display = 'block';
                progressText.textContent = `0/${pcButtons.length}`;

                // Log the order of buttons for debugging
                debugLog(`Processing ${pcButtons.length} PC buttons in document order:`);
                pcButtons.forEach((btn, index) => {
                    const listingTitle = btn.closest('.s-item')?.querySelector('.s-item__title')?.textContent?.slice(0, 50) || 'Unknown';
                    debugLog(`${index + 1}. ${listingTitle}... (${btn.title})`);
                });

                let processed = 0;
                let successful = 0;

                // Process buttons one by one, waiting for each to show ✅
                for (const button of pcButtons) {
                    if (shouldStop) {
                        statusElement.textContent = 'Stopped by user';
                        statusElement.style.color = '#e74c3c';
                        break;
                    }

                    if (button.disabled) continue; // Skip already processed buttons

                    try {
                        const listingElement = button.closest('.s-item, .s-card');
                        // Try multiple selectors for the title
                        const titleElement = listingElement?.querySelector('.s-item__title span, .s-item__title, .s-card__title span, .s-card__title, [role="heading"] span');
                        const listingTitle = titleElement?.textContent?.trim()?.slice(0, 40) || 'Unknown';
                        progressText.textContent = `${processed + 1}/${pcButtons.length}`;
                        debugLog(`\n🎯 Processing ${processed + 1}/${pcButtons.length}: ${listingTitle}`);

                        // Skip if already processed (has green background or cached display exists)
                        const hasCachedDisplay = listingElement?.querySelector('.pc-info-display');
                        if (button.style.background === 'rgb(39, 174, 96)' || hasCachedDisplay) {
                            debugLog(`⏭️ Skipping - ${hasCachedDisplay ? 'has cached display' : 'already processed'}`);
                            successful++;
                            processed++;
                            continue;
                        }
                        
                        // Store the original button text and state
                        const originalText = button.textContent;
                        const originalDisabled = button.disabled;
                        
                        // Create a promise that resolves when the button click completes
                        const clickPromise = new Promise(async (resolve, reject) => {
                            try {
                                // Add a one-time event listener to the button to detect when processing completes
                                let completed = false;
                                const observer = new MutationObserver((mutations) => {
                                    // Check if button background turned green (success)
                                    if (button.style.background === 'rgb(39, 174, 96)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`✅ Button ${processed + 1} completed successfully`);
                                        resolve(true);
                                    }
                                    // Check if button background turned orange (timeout/error)
                                    else if (button.style.background === 'rgb(230, 126, 34)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⚠️ Button ${processed + 1} timed out`);
                                        resolve(false);
                                    }
                                    // Check if button background turned gray (not found/error) - skip and continue
                                    else if (button.style.background === 'rgb(149, 165, 166)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⏭️ Button ${processed + 1} - card not found, skipping`);
                                        resolve(false);
                                    }
                                    // Check if button shows red (popup blocked)
                                    else if (button.style.background === 'rgb(231, 76, 60)' && !completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`🚫 Button ${processed + 1} - popup blocked, skipping`);
                                        resolve(false);
                                    }
                                    // Check if button is re-enabled (finished processing)
                                    else if (!button.disabled && button.textContent === originalText && !completed) {
                                        const displayExists = listingElement?.querySelector('.pc-info-display');
                                        if (displayExists) {
                                            completed = true;
                                            observer.disconnect();
                                            debugLog(`✅ Button ${processed + 1} completed (display shown)`);
                                            resolve(true);
                                        }
                                    }
                                });

                                // Observe button style and state changes
                                observer.observe(button, {
                                    attributes: true,
                                    attributeFilter: ['style', 'disabled']
                                });

                                // Also observe the listing for PC display appearance
                                if (listingElement) {
                                    const listingObserver = new MutationObserver((mutations) => {
                                        const pcDisplay = listingElement.querySelector('.pc-info-display');
                                        if (pcDisplay && pcDisplay.style.opacity === '1' && !completed) {
                                            const displayText = pcDisplay.textContent || '';
                                            if (displayText.includes('$') || displayText.includes('Unpriced')) {
                                                completed = true;
                                                observer.disconnect();
                                                listingObserver.disconnect();
                                                debugLog(`✅ Button ${processed + 1} completed (PC display visible)`);
                                                resolve(true);
                                            }
                                        }
                                    });
                                    listingObserver.observe(listingElement, {
                                        childList: true,
                                        subtree: true,
                                        attributes: true,
                                        attributeFilter: ['style']
                                    });
                                }

                                // Set a timeout in case something goes wrong
                                setTimeout(() => {
                                    if (!completed) {
                                        completed = true;
                                        observer.disconnect();
                                        debugLog(`⏰ Button ${processed + 1} timed out after 20 seconds - skipping`);
                                        resolve(false); // Resolve instead of reject to continue processing
                                    }
                                }, 20000); // Reduced to 20 second timeout per item

                                // Now click the button
                                debugLog(`🖱️ Clicking button ${processed + 1}...`);
                                button.click();
                                
                            } catch (error) {
                                debugLog(`❌ Error in click promise for button ${processed + 1}:`, error);
                                resolve(false); // Resolve instead of reject to continue
                            }
                        });

                        // Wait for the click to complete (or fail)
                        try {
                            const success = await clickPromise;
                            if (success) {
                                successful++;
                            }
                        } catch (error) {
                            debugLog(`❌ Click promise rejected for button ${processed + 1}, continuing anyway:`, error);
                            // Continue processing even if one fails
                        }

                        // Progressive delay - longer after every few items to prevent rate limiting
                        let delay = 1000; // Base delay of 1 second
                        if (processed > 0 && processed % 5 === 0) {
                            // Every 5 items, add an extra delay
                            delay = 3000;
                            debugLog(`⏸️ Taking a 3-second break after ${processed} items to prevent rate limiting...`);
                        }
                        
                        await new Promise(resolve => setTimeout(resolve, delay));

                    } catch (error) {
                        debugLog(`❌ Error processing button ${processed + 1}:`, error);
                        // Continue processing even if one item fails
                    }

                    processed++;
                }

                // Show completion status
                checkAllButton.disabled = false;
                checkAllButton.innerHTML = originalText;
                checkAllButton.style.opacity = '1';
                
                if (shouldStop) {
                    statusElement.textContent = `Stopped (${successful}/${processed})`;
                    statusElement.style.color = '#e74c3c';
                } else {
                    statusElement.textContent = `✅ Complete! ${successful}/${processed}`;
                    statusElement.style.color = '#43b581';
                }

                // Reset status after 5 seconds
                setTimeout(() => {
                    statusElement.textContent = 'Ready';
                    statusElement.style.color = '#43b581';
                    progressElement.style.display = 'none';
                }, 5000);
            });

            debugLog('✅ PokeSpy PC Control Panel created');
        }

        function addPriceResearchTools() {
            // Try immediately first
            addPriceChartingButtons();

            // Add the control panel after buttons are added
            setTimeout(() => {
                createPCControlPanel();
            }, 1000);


            // Then try a few more times quickly if no results found initially
            let attempts = 0;
            const maxAttempts = 5;

            function tryAddButtons() {
                const existingButtons = document.querySelectorAll('.google-pricecharting-btn');
                const totalListings = document.querySelectorAll('#srp-river-results .s-item, .srp-river-results .s-item, .s-item, .s-card').length;

                if (existingButtons.length < totalListings && attempts < maxAttempts) {
                    attempts++;
                    addPriceChartingButtons();
                    createPCControlPanel(); // Also try to add the control panel
                    setTimeout(tryAddButtons, 200);
                }
            }

            setTimeout(tryAddButtons, 100);

            // Re-add buttons when new items load (infinite scroll, etc.)
            const observer = new MutationObserver((mutations) => {
                let shouldUpdate = false;
                mutations.forEach((mutation) => {
                    if (mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.nodeType === 1 && (node.classList?.contains('s-item') || node.classList?.contains('s-card'))) {
                                shouldUpdate = true;
                            }
                        });
                    }
                });

                if (shouldUpdate) {
                    setTimeout(addPriceChartingButtons, 50);
                }
            });

            const searchResults = document.querySelector('#srp-river-results, .srp-river-results, .s-results, body');
            if (searchResults) {
                observer.observe(searchResults, { childList: true, subtree: true });
                debugLog('Observer attached to search results container');
            }
        }

        // Add control panel to item page (simplified version for single items)
        function addItemPageControlPanel() {
            console.log('🎛️ Adding item page control panel');
            
            // Don't add multiple panels
            if (document.getElementById('pokespy-item-panel')) return;

            const panel = document.createElement('div');
            panel.id = 'pokespy-item-panel';
            panel.style.cssText = `
                position: fixed;
                top: 10px;
                right: 10px;
                background: #2f3136;
                color: #ffffff;
                padding: 12px;
                border-radius: 8px;
                z-index: 10000;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 12px;
                border: 2px solid #3498db;
                min-width: 220px;
                max-width: 300px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            `;

            // Extract item ID from URL
            const match = window.location.href.match(/\/itm\/(\d+)/);
            const listingId = match ? match[1] : 'Unknown';
            
            // Extract card info from page - try multiple selectors
            let titleElement = document.querySelector('.x-item-title__mainTitle .ux-textspans');
            if (!titleElement) {
                titleElement = document.querySelector('.x-item-title__mainTitle span');
            }
            if (!titleElement) {
                titleElement = document.querySelector('h1.x-item-title__mainTitle');
            }
            if (!titleElement) {
                titleElement = document.querySelector('.x-item-title h1');
            }
            if (!titleElement) {
                titleElement = document.querySelector('h1');
            }
            
            const cardTitle = titleElement ? cleanEbayTitle(titleElement.textContent.trim()) : 'Unknown';
            console.log('📝 Title element found:', !!titleElement);
            console.log('📝 Extracted card title:', cardTitle);
            
            // Try to extract card info using the existing function
            let cardInfo = { title: cardTitle, cardNumber: null, setNumber: null, fullCardNumber: null };
            if (cardTitle && cardTitle !== 'Unknown') {
                // Create a temporary wrapper to use extractListingInfo - use the same selector pattern
                const tempWrapper = document.createElement('div');
                const tempTitle = document.createElement('span');
                tempTitle.className = 's-card__title';
                const tempInnerSpan = document.createElement('span');
                tempInnerSpan.className = 'su-styled-text';
                tempInnerSpan.textContent = cardTitle;
                tempTitle.appendChild(tempInnerSpan);
                tempWrapper.appendChild(tempTitle);
                
                console.log('🔍 Created temp wrapper HTML:', tempWrapper.innerHTML);
                
                const extracted = extractListingInfo(tempWrapper);
                console.log('🔍 extractListingInfo returned:', extracted);
                
                if (extracted && extracted.title) {
                    cardInfo = extracted;
                    console.log('✅ Extracted card info:', cardInfo);
                    console.log('✅ Card number in info:', cardInfo.cardNumber);
                    console.log('✅ Full card number in info:', cardInfo.fullCardNumber);
                } else {
                    console.log('⚠️ Could not extract card info, using title only');
                }
            } else {
                console.log('❌ No title element found or title is Unknown');
            }
            
            console.log('🎯 Final cardInfo object:', cardInfo);
            console.log('🎯 Has card number?', !!(cardInfo.fullCardNumber || cardInfo.cardNumber));
            
            // Check if there's a note for this item
            const existingNote = getListingNote(listingId);
            
            // Build note status display with description
            let noteStatusHtml = '';
            if (existingNote) {
                const ratingColor = getRatingColor(existingNote.rating);
                noteStatusHtml = `
                    <div style="font-size: 12px; margin-bottom: 6px;">
                        <span style="color: ${ratingColor}; font-weight: bold;">● ${existingNote.rating.toUpperCase()}</span>
                    </div>
                    ${existingNote.description ? `
                        <div style="font-size: 11px; color: #dcddde; line-height: 1.4; padding: 6px; background: #36393f; border-radius: 3px; border-left: 3px solid ${ratingColor};">
                            ${existingNote.description}
                        </div>
                    ` : '<div style="font-size: 11px; color: #72767d; font-style: italic;">No description</div>'}
                `;
            } else {
                noteStatusHtml = '<span style="color: #95a5a6;">○ No note</span>';
            }

            const cardNumberDisplay = cardInfo.fullCardNumber || cardInfo.cardNumber || 'Not detected';
            
            panel.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 10px; color: #3498db; font-size: 14px; display: flex; align-items: center; justify-content: space-between; gap: 6px;">
                    <div style="display: flex; align-items: center; gap: 6px;">
                        <span>💎</span>
                        <span>PokeSpy - Item Page</span>
                    </div>
                    <button id="pokespy-minimize-btn" style="
                        background: transparent;
                        border: none;
                        color: #b9bbbe;
                        font-size: 16px;
                        cursor: pointer;
                        padding: 4px;
                        line-height: 1;
                        transition: color 0.2s ease;
                    " title="Minimize panel">−</button>
                </div>
                <div id="pokespy-panel-content">
                    <div style="margin-bottom: 8px; padding: 8px; background: #40444b; border-radius: 4px;">
                        <div style="font-size: 11px; color: #b9bbbe; margin-bottom: 4px;">Card Info</div>
                        <div style="font-size: 11px; color: #dcddde; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${cardTitle}">${cardTitle}</div>
                        ${cardNumberDisplay !== 'Not detected' ? `<div style="font-size: 10px; color: #7289da;">Card #: ${cardNumberDisplay}</div>` : '<div style="font-size: 10px; color: #95a5a6; font-style: italic;">No card number detected</div>'}
                    </div>
                <div style="margin-bottom: 8px; padding: 8px; background: #40444b; border-radius: 4px;">
                    <div style="font-size: 11px; color: #b9bbbe; margin-bottom: 4px;">Item ID</div>
                    <div style="font-size: 11px; font-family: monospace; color: #7289da;">#${listingId}</div>
                </div>
                <div style="margin-bottom: 8px; padding: 8px; background: #40444b; border-radius: 4px; max-height: 150px; overflow-y: auto;">
                    <div style="font-size: 11px; color: #b9bbbe; margin-bottom: 4px;">Note</div>
                    ${noteStatusHtml}
                </div>
                <div style="margin-bottom: 8px;">
                    <button id="pokespy-item-edit-note" style="
                        width: 100%;
                        padding: 8px 12px;
                        background: linear-gradient(45deg, #f39c12, #e67e22);
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                        margin-bottom: 8px;
                    ">${existingNote ? '✏️ Edit Note' : '📝 Add Note'}</button>
                    <button id="pokespy-item-check-price" style="
                        width: 100%;
                        padding: 8px 12px;
                        background: linear-gradient(45deg, #9b59b6, #8e44ad);
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                    ">� Check PriceCharting</button>
                    <button id="pokespy-item-google-search" style="
                        width: 100%;
                        padding: 8px 12px;
                        background: linear-gradient(45deg, #e74c3c, #c0392b);
                        color: white;
                        border: none;
                        border-radius: 5px;
                        font-size: 12px;
                        font-weight: bold;
                        cursor: pointer;
                        transition: all 0.2s ease;
                        margin-top: 8px;
                    ">🔍 Google Search PC</button>
                </div>
                    <div style="font-size: 10px; color: #72767d; text-align: center; margin-top: 8px;">
                        Notes button on image ↗️
                    </div>
                </div>
            `;

            document.body.appendChild(panel);

            // Add minimize functionality
            let isMinimized = false;
            const minimizeBtn = document.getElementById('pokespy-minimize-btn');
            const panelContent = document.getElementById('pokespy-panel-content');
            const panelHeader = minimizeBtn.parentElement;
            
            // Create minimized icon
            const minimizedIcon = document.createElement('div');
            minimizedIcon.innerHTML = '💎';
            minimizedIcon.style.cssText = `
                font-size: 20px;
                cursor: pointer;
                display: none;
                width: 100%;
                height: 100%;
                display: flex;
                align-items: center;
                justify-content: center;
            `;
            minimizedIcon.title = 'Expand PokeSpy panel';
            panel.appendChild(minimizedIcon);
            
            function toggleMinimize() {
                isMinimized = !isMinimized;
                if (isMinimized) {
                    panelContent.style.display = 'none';
                    panelHeader.style.display = 'none';
                    minimizedIcon.style.display = 'flex';
                    panel.style.minWidth = 'auto';
                    panel.style.width = '40px';
                    panel.style.height = '40px';
                    panel.style.padding = '0';
                } else {
                    panelContent.style.display = 'block';
                    panelHeader.style.display = 'flex';
                    minimizedIcon.style.display = 'none';
                    panel.style.minWidth = '220px';
                    panel.style.width = 'auto';
                    panel.style.height = 'auto';
                    panel.style.padding = '12px';
                }
            }
            
            minimizeBtn.addEventListener('click', toggleMinimize);
            minimizedIcon.addEventListener('click', toggleMinimize);
            
            minimizeBtn.addEventListener('mouseenter', () => {
                minimizeBtn.style.color = '#ffffff';
            });
            minimizeBtn.addEventListener('mouseleave', () => {
                minimizeBtn.style.color = '#b9bbbe';
            });

            // Add hover effect and functionality to Edit/Add Note button
            const editNoteButton = document.getElementById('pokespy-item-edit-note');
            editNoteButton.addEventListener('mouseenter', () => {
                editNoteButton.style.transform = 'scale(1.05)';
                editNoteButton.style.boxShadow = '0 4px 12px rgba(243, 156, 18, 0.4)';
            });
            editNoteButton.addEventListener('mouseleave', () => {
                editNoteButton.style.transform = 'scale(1)';
                editNoteButton.style.boxShadow = 'none';
            });
            editNoteButton.addEventListener('click', () => {
                if (existingNote) {
                    // Edit existing note - modal will update the panel on save/delete
                    openNoteModalWithRefresh(panel, listingId, existingNote.rating, existingNote.description);
                } else {
                    // Show fan-out rating selector for new note
                    showRatingSelector(panel, listingId);
                }
            });

            // Add hover effect to PriceCharting button
            const checkButton = document.getElementById('pokespy-item-check-price');
            checkButton.addEventListener('mouseenter', () => {
                checkButton.style.transform = 'scale(1.05)';
                checkButton.style.boxShadow = '0 4px 12px rgba(155, 89, 182, 0.4)';
            });
            checkButton.addEventListener('mouseleave', () => {
                checkButton.style.transform = 'scale(1)';
                checkButton.style.boxShadow = 'none';
            });
            
            // Add hover effect to Google search button
            const googleButton = document.getElementById('pokespy-item-google-search');
            googleButton.addEventListener('mouseenter', () => {
                googleButton.style.transform = 'scale(1.05)';
                googleButton.style.boxShadow = '0 4px 12px rgba(231, 76, 60, 0.4)';
            });
            googleButton.addEventListener('mouseleave', () => {
                googleButton.style.transform = 'scale(1)';
                googleButton.style.boxShadow = 'none';
            });
            
            // Add functionality to Google search button - same as search page (uses full title)
            googleButton.addEventListener('click', () => {
                // Use full title like search page does
                console.log('🔍 Google button clicked - cardTitle:', cardTitle);
                console.log('🔍 Google button clicked - cardInfo.title:', cardInfo.title);
                const googleUrl = `https://www.google.com/search?q=PriceCharting+${encodeURIComponent(cardTitle)}`;
                console.log('🔗 Opening Google URL:', googleUrl);
                window.open(googleUrl, '_blank');
                
                googleButton.textContent = '✅ Opened!';
                setTimeout(() => {
                    googleButton.textContent = '🔍 Google Search PC';
                }, 2000);
            });

            // Add functionality to check price button
            checkButton.addEventListener('click', async () => {
                console.log('🔍 Check Price button clicked on item page');
                console.log('📋 Card Title:', cardTitle);
                console.log('📋 Card Info Object:', cardInfo);
                console.log('📋 Full Card Number:', cardInfo.fullCardNumber);
                console.log('📋 Card Number:', cardInfo.cardNumber);
                console.log('📋 Set Number:', cardInfo.setNumber);
                console.log('📋 Set Name:', cardInfo.setName);
                
                checkButton.disabled = true;
                checkButton.textContent = '🔍 Searching...';
                checkButton.style.background = '#95a5a6';
                
                try {
                    // Create a fake listing element that extractListingInfo can work with (same as search page)
                    const fakeListing = document.createElement('div');
                    const titleSpan = document.createElement('span');
                    titleSpan.className = 's-card__title';
                    const titleText = document.createElement('span');
                    titleText.className = 'su-styled-text';
                    titleText.textContent = cardTitle;
                    titleSpan.appendChild(titleText);
                    fakeListing.appendChild(titleSpan);
                    
                    // Use the same function as search page
                    const cardNumber = cardInfo.fullCardNumber || cardInfo.cardNumber;
                    const setNumber = cardInfo.setNumber;
                    
                    // Store card data for PriceCharting to access
                    const cardData = {
                        cardNumber: cardNumber,
                        setNumber: setNumber,
                        ebayTitle: cardTitle
                    };
                    
                    const requestKey = storePriceChartingRequest(cardData);
                    
                    // Use createPriceChartingUrl like search page does
                    const finalUrl = await createPriceChartingUrl(cardNumber, setNumber, requestKey, fakeListing, false);
                    
                    if (finalUrl) {
                        checkButton.textContent = '💰 Opening PC...';
                        checkButton.style.background = '#27ae60';
                        
                        // Open PriceCharting in new tab (item page = view mode, not data sharing)
                        window.open(finalUrl, '_blank');
                        
                        setTimeout(() => {
                            checkButton.textContent = '✅ Opened!';
                            setTimeout(() => {
                                checkButton.textContent = '� Check PriceCharting';
                                checkButton.style.background = 'linear-gradient(45deg, #9b59b6, #8e44ad)';
                                checkButton.disabled = false;
                            }, 2000);
                        }, 500);
                    } else {
                        throw new Error('Could not create PriceCharting URL');
                    }
                } catch (error) {
                    console.error('❌ Error checking price:', error);
                    checkButton.textContent = '❌ ' + error.message.substring(0, 20);
                    checkButton.style.background = '#e74c3c';
                    setTimeout(() => {
                        checkButton.textContent = '� Check PriceCharting';
                        checkButton.style.background = 'linear-gradient(45deg, #9b59b6, #8e44ad)';
                        checkButton.disabled = false;
                    }, 3000);
                }
            });

            console.log('✅ Item page control panel added with card info:', cardInfo);
        }

        // Add notes and rating display to watchlist items
        function addWatchlistNotesDisplay() {
            debugLog('🔍 Searching for watchlist items...');
            
            // Use MutationObserver to handle dynamic content loading
            const processWatchlistItems = () => {
                // Find all watchlist items using the structure from the provided HTML
                const watchlistItems = document.querySelectorAll('.m-item-3');
                debugLog(`Found ${watchlistItems.length} watchlist items`);
                
                watchlistItems.forEach((item, index) => {
                    // Skip if already processed
                    if (item.querySelector('.pokespy-watchlist-note')) {
                        return;
                    }
                    
                    // Extract listing ID from the item
                    const itemIdAttr = item.querySelector('[data-itemid]')?.getAttribute('data-itemid') ||
                                      item.querySelector('[item-id]')?.getAttribute('item-id');
                    
                    if (!itemIdAttr) {
                        debugLog(`⚠️ Could not find item ID for watchlist item ${index + 1}`);
                        return;
                    }
                    
                    const listingId = itemIdAttr;
                    debugLog(`Processing watchlist item ${index + 1}: ID ${listingId}`);
                    
                    // Get the note for this listing
                    const note = getListingNote(listingId);
                    
                    if (!note) {
                        debugLog(`  No note found for item ${listingId}`);
                        return;
                    }
                    
                    debugLog(`  ✓ Found note: ${note.rating} - "${note.description}"`);
                    
                    // Find the note container in the watchlist item
                    const noteContainer = item.querySelector('.m-item-3-col__note [data-testid="user-note"]');
                    
                    if (!noteContainer) {
                        debugLog(`  ⚠️ Could not find note container for item ${listingId}`);
                        return;
                    }
                    
                    // Create the note display element
                    const noteDisplay = document.createElement('div');
                    noteDisplay.className = 'pokespy-watchlist-note';
                    
                    const ratingColor = getRatingColor(note.rating);
                    const ratingIcon = getRatingIcon(note.rating);
                    
                    noteDisplay.style.cssText = `
                        display: flex;
                        align-items: center;
                        gap: 8px;
                        padding: 8px 12px;
                        background: ${ratingColor};
                        color: white;
                        border-radius: 6px;
                        font-size: 13px;
                        font-weight: 500;
                        margin-top: 4px;
                        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                    `;
                    
                    noteDisplay.innerHTML = `
                        <div style="
                            width: 24px;
                            height: 24px;
                            border-radius: 50%;
                            background: rgba(255, 255, 255, 0.3);
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            font-size: 16px;
                            font-weight: bold;
                            flex-shrink: 0;
                        ">${ratingIcon}</div>
                        <div style="flex: 1; min-width: 0;">
                            <div style="font-weight: bold; text-transform: uppercase; font-size: 11px; opacity: 0.9; margin-bottom: 2px;">
                                ${note.rating}
                            </div>
                            ${note.description ? `
                                <div style="
                                    font-size: 12px;
                                    opacity: 0.95;
                                    line-height: 1.4;
                                    overflow: hidden;
                                    text-overflow: ellipsis;
                                    display: -webkit-box;
                                    -webkit-line-clamp: 2;
                                    -webkit-box-orient: vertical;
                                ">${note.description}</div>
                            ` : ''}
                        </div>
                    `;
                    
                    // Add click handler to edit note
                    noteDisplay.style.cursor = 'pointer';
                    noteDisplay.title = 'Click to edit note';
                    noteDisplay.addEventListener('click', (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        openWatchlistNoteModal(listingId, note.rating, note.description);
                    });
                    
                    // Insert the note display
                    noteContainer.appendChild(noteDisplay);
                });
            };
            
            // Initial processing
            processWatchlistItems();
            
            // Set up MutationObserver to handle dynamic content
            const observer = new MutationObserver((mutations) => {
                // Debounce: only process if we haven't processed recently
                if (observer.debounceTimer) {
                    clearTimeout(observer.debounceTimer);
                }
                observer.debounceTimer = setTimeout(() => {
                    processWatchlistItems();
                }, 500);
            });
            
            // Observe the watchlist container for changes
            const watchlistContainer = document.querySelector('#gh-wl-list, .m-items, [role="main"]');
            if (watchlistContainer) {
                observer.observe(watchlistContainer, {
                    childList: true,
                    subtree: true
                });
                debugLog('✅ MutationObserver set up for watchlist updates');
            } else {
                debugLog('⚠️ Could not find watchlist container for observation');
            }
        }
        
        // Modal for editing notes on watchlist page
        function openWatchlistNoteModal(listingId, rating, existingDescription = '') {
            // Remove any existing modal
            const existingModal = document.getElementById('pokespy-note-modal');
            if (existingModal) existingModal.remove();

            const modal = document.createElement('div');
            modal.id = 'pokespy-note-modal';
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.7);
                z-index: 999999;
                display: flex;
                align-items: center;
                justify-content: center;
                animation: fadeIn 0.2s ease;
            `;

            const modalContent = document.createElement('div');
            modalContent.style.cssText = `
                background: #2f3136;
                color: white;
                padding: 24px;
                border-radius: 12px;
                max-width: 500px;
                width: 90%;
                box-shadow: 0 10px 40px rgba(0,0,0,0.5);
                animation: slideIn 0.3s ease;
            `;

            const ratingColor = getRatingColor(rating);
            const ratingIcon = getRatingIcon(rating);

            modalContent.innerHTML = `
                <div style="display: flex; align-items: center; margin-bottom: 16px;">
                    <div style="width: 40px; height: 40px; border-radius: 50%; background: ${ratingColor}; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; margin-right: 12px;">
                        ${ratingIcon}
                    </div>
                    <div>
                        <div style="font-size: 18px; font-weight: bold;">Edit Note</div>
                        <div style="font-size: 12px; opacity: 0.7;">${rating.charAt(0).toUpperCase() + rating.slice(1)} Rating</div>
                    </div>
                </div>
                <textarea id="pokespy-note-textarea" placeholder="Why did you choose this rating?" style="
                    width: 100%;
                    min-height: 120px;
                    padding: 12px;
                    background: #40444b;
                    border: 2px solid ${ratingColor};
                    border-radius: 8px;
                    color: white;
                    font-family: inherit;
                    font-size: 14px;
                    resize: vertical;
                    margin-bottom: 16px;
                ">${existingDescription}</textarea>
                <div style="display: flex; gap: 12px; justify-content: flex-end;">
                    <button id="pokespy-note-cancel" style="
                        padding: 10px 20px;
                        background: #5865f2;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Cancel</button>
                    <button id="pokespy-note-delete" style="
                        padding: 10px 20px;
                        background: #ed4245;
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                        display: ${existingDescription ? 'block' : 'none'};
                    ">Delete</button>
                    <button id="pokespy-note-save" style="
                        padding: 10px 20px;
                        background: ${ratingColor};
                        color: white;
                        border: none;
                        border-radius: 6px;
                        font-size: 14px;
                        font-weight: bold;
                        cursor: pointer;
                    ">Save Note</button>
                </div>
            `;

            modal.appendChild(modalContent);
            document.body.appendChild(modal);

            // Focus textarea
            const textarea = document.getElementById('pokespy-note-textarea');
            textarea.focus();

            // Event handlers
            document.getElementById('pokespy-note-cancel').addEventListener('click', () => {
                modal.remove();
            });

            document.getElementById('pokespy-note-delete').addEventListener('click', () => {
                const noteKey = `listing_note_${listingId}`;
                GM_deleteValue(noteKey);
                modal.remove();
                // Refresh the page to update the display
                window.location.reload();
            });

            document.getElementById('pokespy-note-save').addEventListener('click', () => {
                const description = textarea.value.trim();
                
                storeListingNote(listingId, {
                    rating: rating,
                    description: description
                });

                modal.remove();
                // Refresh the page to update the display
                window.location.reload();
            });

            // Close on background click
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    modal.remove();
                }
            });

            // Close on escape key
            const escHandler = (e) => {
                if (e.key === 'Escape') {
                    modal.remove();
                    document.removeEventListener('keydown', escHandler);
                }
            };
            document.addEventListener('keydown', escHandler);
        }

        // Initialize eBay functionality
        loadSetsCache();

        // Detect what page we're on and run appropriate functions
        const currentUrl = window.location.href;
        console.log('🌐 Current URL:', currentUrl);
        console.log('🔍 Is search page?', currentUrl.includes('/sch/') || currentUrl.includes('/b/'));
        console.log('🔍 Is item page?', currentUrl.includes('/itm/'));
        console.log('🔍 Is watchlist page?', currentUrl.includes('/mye/myebay/watchlist'));
        
        if (currentUrl.includes('/sch/') || currentUrl.includes('/b/')) {
            debugLog('eBay search page detected');
            console.log('📋 eBay search page detected');
            addPriceResearchTools();
        } else if (currentUrl.includes('/itm/')) {
            debugLog('eBay item page detected');
            console.log('🖼️ eBay item page detected');
            addNotesButtonToItemPage();
            addItemPageControlPanel();
        } else if (currentUrl.includes('/mye/myebay/watchlist')) {
            debugLog('eBay watchlist page detected');
            console.log('👁️ eBay watchlist page detected');
            addWatchlistNotesDisplay();
        }
    }

    // Show rating selector with fan-out animation (for item page "Add Note" button)
    function showRatingSelector(panel, listingId) {
        // Create overlay
        const overlay = document.createElement('div');
        overlay.id = 'pokespy-rating-selector-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 999998;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.2s ease;
        `;

        // Create selector container
        const selectorContainer = document.createElement('div');
        selectorContainer.style.cssText = `
            background: #2f3136;
            padding: 32px;
            border-radius: 12px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            animation: slideIn 0.3s ease;
        `;

        selectorContainer.innerHTML = `
            <div style="color: white; font-size: 18px; font-weight: bold; margin-bottom: 24px; text-align: center;">
                Choose a Rating
            </div>
            <div id="pokespy-rating-buttons" style="display: flex; gap: 20px; justify-content: center; position: relative;">
                <button class="pokespy-rating-select" data-rating="good" style="
                    width: 80px;
                    height: 80px;
                    border-radius: 50%;
                    background: #27ae60;
                    border: 3px solid #fff;
                    cursor: pointer;
                    font-size: 36px;
                    font-weight: bold;
                    color: white;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    transition: all 0.3s ease;
                    opacity: 0;
                    transform: scale(0);
                ">✓</button>
                <button class="pokespy-rating-select" data-rating="neutral" style="
                    width: 80px;
                    height: 80px;
                    border-radius: 50%;
                    background: #f39c12;
                    border: 3px solid #fff;
                    cursor: pointer;
                    font-size: 36px;
                    font-weight: bold;
                    color: white;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    transition: all 0.3s ease;
                    opacity: 0;
                    transform: scale(0);
                ">−</button>
                <button class="pokespy-rating-select" data-rating="bad" style="
                    width: 80px;
                    height: 80px;
                    border-radius: 50%;
                    background: #e74c3c;
                    border: 3px solid #fff;
                    cursor: pointer;
                    font-size: 36px;
                    font-weight: bold;
                    color: white;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                    transition: all 0.3s ease;
                    opacity: 0;
                    transform: scale(0);
                ">✕</button>
            </div>
            <div style="margin-top: 20px; text-align: center;">
                <button id="pokespy-rating-cancel" style="
                    padding: 10px 20px;
                    background: #5865f2;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                ">Cancel</button>
            </div>
        `;

        overlay.appendChild(selectorContainer);
        document.body.appendChild(overlay);

        // Animate buttons in
        const buttons = selectorContainer.querySelectorAll('.pokespy-rating-select');
        buttons.forEach((btn, index) => {
            setTimeout(() => {
                btn.style.opacity = '1';
                btn.style.transform = 'scale(1)';
            }, index * 100);

            // Hover effect
            btn.addEventListener('mouseenter', () => {
                btn.style.transform = 'scale(1.1)';
                btn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)';
            });
            btn.addEventListener('mouseleave', () => {
                btn.style.transform = 'scale(1)';
                btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
            });

            // Click handler
            btn.addEventListener('click', () => {
                const rating = btn.getAttribute('data-rating');
                overlay.remove();
                openNoteModalWithRefresh(panel, listingId, rating, '');
            });
        });

        // Cancel button
        document.getElementById('pokespy-rating-cancel').addEventListener('click', () => {
            overlay.remove();
        });

        // Close on background click
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });

        // Close on escape key
        const escHandler = (e) => {
            if (e.key === 'Escape') {
                overlay.remove();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    }

    // Add notes button to individual item page
    function addNotesButtonToItemPage() {
        console.log('🔧 addNotesButtonToItemPage called');
        debugLog('🔧 addNotesButtonToItemPage called');
        
        // Wait for the page to load
        let attempts = 0;
        const maxAttempts = 30; // 15 seconds total
        
        const checkForImage = setInterval(() => {
            attempts++;
            const imageContainer = document.querySelector('.ux-image-carousel-container, .ux-image-carousel, .image-container');
            const watchButton = document.querySelector('.x-watch-heart, .watchlink, [class*="watch"]');
            
            console.log(`Attempt ${attempts}/${maxAttempts} - Checking for elements...`, { 
                imageContainer: !!imageContainer, 
                watchButton: !!watchButton,
                url: window.location.href
            });
            
            if (imageContainer) {
                clearInterval(checkForImage);
                console.log('✅ Found image container:', imageContainer);
                
                // Extract listing ID from URL
                const match = window.location.href.match(/\/itm\/(\d+)/);
                if (!match) {
                    debugLog('Could not extract item ID from URL');
                    return;
                }
                
                const listingId = match[1];
                const existingNote = getListingNote(listingId);
                
                debugLog(`Adding notes button to item page for ID: ${listingId}`);
                
                // Main notes button - styled to match eBay's icon buttons
                const mainButton = document.createElement('button');
                mainButton.className = 'icon-btn pokespy-notes-btn';
                mainButton.setAttribute('data-ebayui', '');
                mainButton.type = 'button';
                mainButton.setAttribute('aria-label', existingNote ? `Note: ${existingNote.rating}` : 'Add note');
                mainButton.innerHTML = existingNote ? getRatingIcon(existingNote.rating) : '📝';
                mainButton.title = existingNote ? `Note: ${existingNote.rating}\n${existingNote.description || 'No description'}` : 'Add note';
                mainButton.style.cssText = `
                    font-size: 20px;
                    background: ${existingNote ? getRatingColor(existingNote.rating) : 'rgba(255, 255, 255, 0.95)'};
                    border-radius: 50%;
                    position: relative;
                    transition: all 0.3s ease;
                `;
                
                // Create notes container to hold button and fan-out options
                const notesContainer = document.createElement('div');
                notesContainer.className = 'pokespy-notes-container-item-page';
                notesContainer.style.cssText = `
                    position: relative;
                    display: inline-block;
                `;

                // Rating options container (hidden by default)
                const optionsContainer = document.createElement('div');
                optionsContainer.className = 'pokespy-notes-options';
                optionsContainer.style.cssText = `
                    position: absolute;
                    top: 0px;
                    left: 20%;
                    transform: translateX(5%);
                    display: none;
                    pointer-events: auto;
                    width: 200px;
                    height: 150px;
                    margin-left: -100px;
                `;

                // Create rating buttons
                const ratings = [
                    { type: 'good', icon: '✓', color: '#27ae60', label: 'Good' },
                    { type: 'neutral', icon: '−', color: '#f39c12', label: 'Neutral' },
                    { type: 'bad', icon: '✕', color: '#e74c3c', label: 'Bad' }
                ];

                ratings.forEach((rating, index) => {
                    const ratingBtn = document.createElement('button');
                    ratingBtn.className = `pokespy-rating-btn pokespy-rating-${rating.type}`;
                    ratingBtn.innerHTML = rating.icon;
                    ratingBtn.title = rating.label;
                    ratingBtn.style.cssText = `
                        width: 40px;
                        height: 40px;
                        border-radius: 50%;
                        background: ${rating.color};
                        border: 2px solid #fff;
                        cursor: pointer;
                        font-size: 22px;
                        font-weight: bold;
                        color: white;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        box-shadow: 0 2px 8px rgba(0,0,0,0.3);
                        position: absolute;
                        top: 0;
                        left: 100px;
                        transform: translate(-50%, 0) scale(0);
                        opacity: 0;
                        transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
                        pointer-events: all;
                    `;

                    ratingBtn.addEventListener('click', (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        openNoteModalForItemPage(listingId, rating.type, mainButton);
                        hideOptions();
                    });

                    optionsContainer.appendChild(ratingBtn);
                });

                notesContainer.appendChild(mainButton);
                notesContainer.appendChild(optionsContainer);

                // Fan out animation
                let isExpanded = false;
                let hoverTimeout;

                function showOptions() {
                    isExpanded = true;
                    optionsContainer.style.display = 'block';
                    
                    const buttons = optionsContainer.querySelectorAll('.pokespy-rating-btn');
                    buttons.forEach((btn, index) => {
                        setTimeout(() => {
                            // Fan out downward in a horizontal arc
                            // index 0 (good) = left, index 1 (neutral) = center, index 2 (bad) = right
                            const positions = [
                                { x: -50, y: 50 },   // Good: down-left
                                { x: 0, y: 60 },     // Neutral: straight down
                                { x: 50, y: 50 }     // Bad: down-right
                            ];
                            const pos = positions[index];
                            
                            btn.style.opacity = '1';
                            btn.style.transform = `translate(calc(-50% + ${pos.x}px), ${pos.y}px) scale(1)`;
                        }, index * 50);
                    });
                }

                function hideOptions() {
                    isExpanded = false;
                    const buttons = optionsContainer.querySelectorAll('.pokespy-rating-btn');
                    buttons.forEach(btn => {
                        btn.style.opacity = '0';
                        btn.style.transform = 'translateX(-50%) scale(0)';
                    });
                    
                    setTimeout(() => {
                        optionsContainer.style.display = 'none';
                    }, 300);
                }

                mainButton.addEventListener('mouseenter', () => {
                    if (!isExpanded) showOptions();
                });

                // Listen for mouse leave on the options container (which now has padding)
                optionsContainer.addEventListener('mouseleave', () => {
                    clearTimeout(hoverTimeout);
                    if (isExpanded) {
                        setTimeout(hideOptions, 300);
                    }
                });

                // Also listen on the main container for extra safety
                notesContainer.addEventListener('mouseleave', (e) => {
                    // Only hide if we're not entering the options container
                    if (!optionsContainer.contains(e.relatedTarget)) {
                        clearTimeout(hoverTimeout);
                        if (isExpanded) {
                            setTimeout(hideOptions, 300);
                        }
                    }
                });

                mainButton.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    
                    if (existingNote) {
                        openNoteModalForItemPage(listingId, existingNote.rating, mainButton, existingNote.description);
                    } else if (!isExpanded) {
                        showOptions();
                    }
                });

                // Insert into the top-right button area (before the watch heart)
                const topRightButtons = document.querySelector('.ux-image-carousel-buttons__top-right');
                if (topRightButtons) {
                    // Insert before the watch heart button
                    const watchHeart = topRightButtons.querySelector('.x-watch-heart');
                    if (watchHeart) {
                        topRightButtons.insertBefore(notesContainer, watchHeart);
                        console.log('✅ Notes button added before watch heart');
                    } else {
                        topRightButtons.appendChild(notesContainer);
                        console.log('✅ Notes button added to button area');
                    }
                    debugLog('✅ Notes button added to item page');
                } else {
                    // Fallback: just append to the image container itself
                    console.log('⚠️ Could not find top-right buttons, appending to image container');
                    imageContainer.style.position = 'relative';
                    imageContainer.appendChild(notesContainer);
                }
            } else if (attempts >= maxAttempts) {
                console.log('❌ Timed out waiting for elements');
                clearInterval(checkForImage);
            }
        }, 500);

        // Stop checking after 15 seconds
        setTimeout(() => {
            clearInterval(checkForImage);
            console.log('⏱️ Stopped checking for item page elements');
        }, 15000);
    }

    // Open note modal for item page (simplified without listingElement)
    function openNoteModalForItemPage(listingId, rating, buttonElement, existingDescription = '') {
        // Remove any existing modal
        const existingModal = document.getElementById('pokespy-note-modal');
        if (existingModal) existingModal.remove();

        const modal = document.createElement('div');
        modal.id = 'pokespy-note-modal';
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.2s ease;
        `;

        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            background: #2f3136;
            color: white;
            padding: 24px;
            border-radius: 12px;
            max-width: 500px;
            width: 90%;
            box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            animation: slideIn 0.3s ease;
        `;

        const ratingColor = getRatingColor(rating);
        const ratingIcon = getRatingIcon(rating);

        modalContent.innerHTML = `
            <div style="display: flex; align-items: center; margin-bottom: 16px;">
                <div style="width: 40px; height: 40px; border-radius: 50%; background: ${ratingColor}; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; margin-right: 12px;">
                    ${ratingIcon}
                </div>
                <div>
                    <div style="font-size: 18px; font-weight: bold;">Add Note</div>
                    <div style="font-size: 12px; opacity: 0.7;">${rating.charAt(0).toUpperCase() + rating.slice(1)} Rating</div>
                </div>
            </div>
            <textarea id="pokespy-note-textarea" placeholder="Why did you choose this rating?" style="
                width: 100%;
                min-height: 120px;
                padding: 12px;
                background: #40444b;
                border: 2px solid ${ratingColor};
                border-radius: 8px;
                color: white;
                font-family: inherit;
                font-size: 14px;
                resize: vertical;
                margin-bottom: 16px;
            ">${existingDescription}</textarea>
            <div style="display: flex; gap: 12px; justify-content: flex-end;">
                <button id="pokespy-note-cancel" style="
                    padding: 10px 20px;
                    background: #5865f2;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                ">Cancel</button>
                <button id="pokespy-note-delete" style="
                    padding: 10px 20px;
                    background: #ed4245;
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    display: ${existingDescription ? 'block' : 'none'};
                ">Delete</button>
                <button id="pokespy-note-save" style="
                    padding: 10px 20px;
                    background: ${ratingColor};
                    color: white;
                    border: none;
                    border-radius: 6px;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                ">Save Note</button>
            </div>
        `;

        modal.appendChild(modalContent);
        document.body.appendChild(modal);

        // Focus textarea
        const textarea = document.getElementById('pokespy-note-textarea');
        textarea.focus();

        // Event handlers
        document.getElementById('pokespy-note-cancel').addEventListener('click', () => {
            modal.remove();
        });

        document.getElementById('pokespy-note-delete').addEventListener('click', () => {
            const noteKey = `listing_note_${listingId}`;
            GM_deleteValue(noteKey);
            
            // Update button
            if (buttonElement) {
                buttonElement.innerHTML = '📝';
                buttonElement.title = 'Add note';
                buttonElement.style.background = 'rgba(255, 255, 255, 0.95)';
                buttonElement.style.borderColor = '#ddd';
            }
            
            modal.remove();
        });

        document.getElementById('pokespy-note-save').addEventListener('click', () => {
            const description = textarea.value.trim();
            
            storeListingNote(listingId, {
                rating: rating,
                description: description
            });

            // Update button
            if (buttonElement) {
                buttonElement.innerHTML = getRatingIcon(rating);
                buttonElement.title = `Note: ${rating}\n${description || 'No description'}`;
                buttonElement.style.background = getRatingColor(rating);
                buttonElement.style.borderColor = '#fff';
            }

            modal.remove();
        });

        // Close on background click
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
            }
        });

        // Close on escape key
        const escHandler = (e) => {
            if (e.key === 'Escape') {
                modal.remove();
                document.removeEventListener('keydown', escHandler);
            }
        };
        document.addEventListener('keydown', escHandler);
    }

    // ============================================================================
    // PRICECHARTING FUNCTIONALITY
    // ============================================================================

    // Enhanced PriceCharting functionality with better URL parameter detection
    function initializePriceChartingFunctionality() {
        debugLog('💰 Initializing PriceCharting functionality...');

        // Check multiple ways for eBay request parameter
        const fullUrl = window.location.href;
        const url = new URL(fullUrl);
        const hash = window.location.hash;
        const search = window.location.search;

        debugLog(`🔍 Looking for eBay request parameter...`);
        debugLog(`Full URL: ${fullUrl}`);
        debugLog(`Search params: ${search}`);
        debugLog(`Hash: ${hash}`);

        let requestKey = null;

        // Method 1: Check URL search parameters
        requestKey = url.searchParams.get('ebay_request');
        if (requestKey) {
            debugLog(`✅ Found ebay_request in URL params: ${requestKey}`);
        }

        // Method 2: Check hash fragment for ebay_request
        if (!requestKey && hash) {
            const ebayRequestMatch = hash.match(/[&#]ebay_request=([^&\s]+)/);
            if (ebayRequestMatch) {
                requestKey = ebayRequestMatch[1];
                debugLog(`✅ Found ebay_request in hash: ${requestKey}`);
            }
        }

        // Method 3: Check if it's anywhere in the URL (fallback)
        if (!requestKey) {
            const urlRequestMatch = fullUrl.match(/[?&#]ebay_request=([^&\s#]+)/);
            if (urlRequestMatch) {
                requestKey = urlRequestMatch[1];
                debugLog(`✅ Found ebay_request in full URL: ${requestKey}`);
            }
        }

        debugLog(`Final request key: ${requestKey || 'None found'}`);

        if (requestKey) {
            debugLog(`🔗 PriceCharting opened from eBay with request key: ${requestKey}`);

            // Get the card data from eBay
            const cardData = getStoredData(requestKey);
            if (cardData) {
                debugLog(`📋 Retrieved card data from eBay:`, cardData);
                setupPriceChartingExtraction(requestKey, cardData);
            } else {
                debugLog(`❌ No card data found for request key: ${requestKey}`);
                // List all available keys for debugging
                const allKeys = GM_listValues();
                debugLog(`Available storage keys:`, allKeys.filter(key => key.includes('pc_request')));
            }
        } else if (window.location.hash === '#full-prices' || window.location.hash.includes('full-prices')) {
            debugLog(`ℹ️ No eBay request parameter found initially - checking if it's in the hash...`);
            
            // Sometimes the parameter gets moved into the hash, check there
            const hashMatch = window.location.href.match(/ebay_request=([^&\s#]+)/);
            if (hashMatch) {
                requestKey = hashMatch[1];
                debugLog(`✅ Found ebay_request in hash/URL: ${requestKey}`);
                
                // Retry extraction with the found key
                const cardData = getStoredData(requestKey);
                if (cardData) {
                    debugLog(`📋 Retrieved card data from eBay (hash match):`, cardData);
                    setupPriceChartingExtraction(requestKey, cardData);
                    return; // Exit early, extraction is set up
                }
            }

            debugLog(`⚠️ This appears to be a manual visit or the request key was lost`);

            // Auto-close window if opened programmatically but no request key found
            // This happens when the eBay script tries to open PriceCharting but the URL parameters get lost
            if (document.referrer.includes('ebay.com') || document.referrer.includes('ebay.co.uk')) {
                debugLog(`🔄 Auto-closing PriceCharting window in 35 seconds - no request key found from eBay (waiting for manual extraction)`);
                setTimeout(() => {
                    debugLog(`🔄 Closing window now (no request key, likely lost in URL)`);
                    window.close();
                }, 35000); // Wait 35 seconds to allow manual extraction to complete and debugging
            }
        }

        // Always add general extraction for manually opened PriceCharting pages
        setTimeout(() => {
            debugLog(`🔍 Running manual extraction fallback after 2 seconds...`);
            const manualData = extractPriceChartingDataFromPage();
            if (manualData && Object.keys(manualData.prices).length > 0) {
                debugLog('📊 Manual extraction successful:', manualData);

                // Debug logging for storage decision
                debugLog(`🔍 Request key: ${requestKey || 'NONE'}`);
                const existingData = requestKey ? getStoredData(`${requestKey}_data`) : null;
                debugLog(`🔍 Existing data check: ${existingData ? 'YES' : 'NO'}`);

                // If we have a request key, ALWAYS store the data (override any existing data)
                if (requestKey) {
                    debugLog(`🔄 Storing manual extraction data for eBay request: ${requestKey}`);
                    storePriceChartingData(requestKey, manualData);

                    // Show notification that data was stored
                    showExtractionNotification(manualData, { cardName: manualData.extractedCardName });
                    
                    // Auto-close if this was an eBay request
                    if (window.location.hash.includes('full-prices')) {
                        setTimeout(() => {
                            debugLog('🔄 Auto-closing after manual extraction success');
                            window.close();
                        }, 800);
                    }
                } else {
                    debugLog(`⚠️ No request key found - cannot send data back to eBay`);
                }
            } else {
                debugLog(`⚠️ Manual extraction failed or no prices found`);
                
                // If we have a request key and this is a #full-prices page, something went wrong
                if (requestKey && window.location.hash.includes('full-prices')) {
                    debugLog(`❌ Extraction failed for request ${requestKey} - keeping window open for 30 seconds for debugging`);
                    
                    // Keep window open longer for debugging
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing after extraction failure (30s delay for debugging)');
                        window.close();
                    }, 30000); // 30 seconds
                }
            }
        }, 2000); // Increased to 2 seconds to ensure page is fully loaded
    }

    // Set up automatic data extraction on PriceCharting - Enhanced with better error handling
    function setupPriceChartingExtraction(requestKey, cardData) {
        debugLog('🔄 Setting up PriceCharting data extraction...');
        debugLog(`🔑 Request key: ${requestKey}`);
        debugLog(`📋 Card data:`, cardData);
        debugLog(`📍 Current URL: ${window.location.href}`);

        // Wait for page to load
        let attempts = 0;
        const maxAttempts = 20;
        
        const extractData = () => {
            attempts++;
            const readyState = document.readyState;
            debugLog(`📄 Document ready state: ${readyState} (attempt ${attempts}/${maxAttempts})`);

            if (readyState !== 'complete' && attempts < maxAttempts) {
                debugLog(`⏳ Waiting for page to complete loading...`);
                setTimeout(extractData, 200);
                return;
            }

            if (attempts >= maxAttempts) {
                debugLog(`⚠️ Max attempts reached, proceeding with extraction anyway...`);
            }

            debugLog(`🔍 Page loaded, starting data extraction...`);
            debugLog(`🔍 Page title: ${document.title}`);

            const extractedData = extractPriceChartingDataFromPage(cardData);

            if (extractedData && Object.keys(extractedData.prices).length > 0) {
                debugLog(`✅ Data extraction successful!`);
                debugLog(`📊 Extracted data:`, extractedData);

                // Store the extracted data for eBay to retrieve
                debugLog(`💾 Storing data with key: ${requestKey}_data`);
                storePriceChartingData(requestKey, extractedData);

                // Verify the data was stored
                const verifyData = getStoredData(`${requestKey}_data`);
                if (verifyData) {
                    debugLog(`✅ Data storage verified successfully`);
                } else {
                    debugLog(`❌ Data storage verification failed!`);
                }

                // Show notification on PriceCharting page
                showExtractionNotification(extractedData, cardData);

                // Auto-close the tab after successful data extraction (only for direct PC checks)
                if (window.location.hash === '#full-prices') {
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing PriceCharting tab after successful data extraction...');
                        // Give a bit more time to ensure data is fully stored
                        setTimeout(() => {
                            window.close();
                        }, 300);
                    }, 500); // Wait 500ms before closing to ensure storage completes
                } else {
                    debugLog('📊 View page - not auto-closing, user can browse manually');
                }
            } else {
                debugLog(`❌ Data extraction failed or no prices found - keeping window open for 30 seconds for debugging`);

                // Still try to store something so eBay knows we tried
                const fallbackData = {
                    url: window.location.href,
                    title: document.title,
                    cardName: cardData?.cardName ? cleanEbayTitle(cardData.cardName) : 'Unknown',
                    prices: {},
                    extractedCardName: cardData?.cardName ? cleanEbayTitle(cardData.cardName) : null,
                    timestamp: Date.now(),
                    error: 'No prices found on page'
                };

                debugLog(`💾 Storing fallback data:`, fallbackData);
                storePriceChartingData(requestKey, fallbackData);
                
                // Keep window open longer for debugging
                if (window.location.hash === '#full-prices') {
                    setTimeout(() => {
                        debugLog('🔄 Auto-closing after extraction failure (30s delay for debugging)');
                        window.close();
                    }, 30000); // 30 seconds
                }
            }
        };

        // Start extraction with a slight delay
        setTimeout(extractData, 200);
    }

    // Extract data from current PriceCharting page - Enhanced to handle more grade variations
    function extractPriceChartingDataFromPage(cardData = null) {
        try {
            const data = {
                url: window.location.href,
                title: document.title,
                cardName: cardData?.cardName ? cleanEbayTitle(cardData.cardName) : 'Unknown',
                prices: {},
                availability: '',
                lastUpdated: '',
                timestamp: Date.now()
            };

            debugLog('🔍 Searching for prices on PriceCharting full-prices page...');
            debugLog(`🔍 Current page URL: ${window.location.href}`);
            debugLog(`🔍 Has #full-prices hash: ${window.location.hash === '#full-prices'}`);

            // Extract requestKey from URL for alternative URL checking
            let requestKey = null;
            const requestKeyMatch = window.location.href.match(/ebay_request=([^&\s#]+)/);
            if (requestKeyMatch) {
                requestKey = requestKeyMatch[1];
                debugLog(`🔑 Extracted request key from URL: ${requestKey}`);
            }

            // Check if we're on a search results page (not a specific card page)
            if (window.location.href.includes('/search-products') || window.location.href.includes('/search?')) {
                debugLog('⚠️ Landed on search results page - card URL not found');
                
                // Check if there's an alternative URL to try (for Mega cards)
                if (requestKey) {
                    const storedData = GM_getValue(requestKey);
                    if (storedData && storedData.alternativeUrl && !storedData.triedAlternative) {
                        debugLog('🔄 Trying alternative URL format for Mega card...');
                        debugLog(`🔗 Alternative URL: ${storedData.alternativeUrl}`);
                        storedData.triedAlternative = true;
                        GM_setValue(requestKey, storedData);
                        
                        // Redirect to alternative URL and stop further processing
                        debugLog('⏳ Redirecting now...');
                        window.location.replace(storedData.alternativeUrl); // Use replace to avoid back button issues
                        
                        // Don't return data - wait for the redirect
                        throw new Error('Redirecting to alternative URL'); // Stop execution
                    }
                }
                
                debugLog('⚠️ No alternative URL available, treating as not found');
                data.error = 'Card not found - redirected to search results';
                return data; // Return empty prices
            }

            // Check if we're on a 404 or error page
            const pageText = document.body.textContent.toLowerCase();
            if (pageText.includes('404') || pageText.includes('not found') || pageText.includes('no results')) {
                debugLog('⚠️ Appears to be a 404 or error page');
            }

            // Target the specific full-prices table structure
            const fullPricesTable = document.querySelector('#full-prices table');
            debugLog(`🔍 Full-prices table found: ${!!fullPricesTable}`);

            if (fullPricesTable) {
                debugLog('✓ Found full-prices table');

                // Extract data from each row
                const rows = fullPricesTable.querySelectorAll('tbody tr');
                debugLog(`Found ${rows.length} price rows`);

                rows.forEach((row, index) => {
                    const gradeCell = row.querySelector('td:first-child');
                    const priceCell = row.querySelector('td.price.js-price, .price, td:last-child');

                    if (gradeCell && priceCell) {
                        const grade = gradeCell.textContent.trim();
                        const priceText = priceCell.textContent.trim();

                        // Only store if price is not empty and not just a dash
                        if (priceText && priceText !== '-' && priceText !== '') {
                            const priceMatch = priceText.match(/\$[\d,]+\.?\d*/);
                            if (priceMatch) {
                                // Create clean key from grade (remove spaces and special chars)
                                let cleanGrade = grade.toLowerCase().replace(/[^a-z0-9]/g, '_');

                                // Special handling for specific grade formats
                                if (grade === 'Ungraded') {
                                    cleanGrade = 'ungraded';
                                } else if (grade.startsWith('Grade ')) {
                                    // Convert "Grade 9.5" to "grade_9_5"
                                    cleanGrade = grade.toLowerCase().replace('grade ', 'grade_').replace('.', '_');
                                } else if (grade.includes(' 10') && grade.includes('Black')) {
                                    cleanGrade = 'bgs_10_black';
                                } else if (grade.includes(' 10') && grade.includes('Pristine')) {
                                    cleanGrade = 'cgc_10_pristine';
                                }

                                data.prices[cleanGrade] = {
                                    price: priceMatch[0],
                                    grade: grade,
                                    rawText: priceText
                                };
                                debugLog(`  ✓ ${grade} (${cleanGrade}): ${priceMatch[0]}`);
                            }
                        } else {
                            debugLog(`  - ${grade}: No price available`);
                        }
                    }
                });
            } else {
                debugLog('✗ Full-prices table not found, trying fallback selectors...');

                // Fallback: Try general price extraction
                const priceSelectors = [
                    '.price.js-price',
                    '.price',
                    '[class*="price"]',
                    '.used-price',
                    '.new-price'
                ];

                priceSelectors.forEach(selector => {
                    const elements = document.querySelectorAll(selector);
                    elements.forEach((el, index) => {
                        const text = el.textContent.trim();
                        const priceMatch = text.match(/\$[\d,]+\.?\d*/);
                        if (priceMatch && parseFloat(priceMatch[0].replace(/[$,]/g, '')) > 0) {
                            const key = `${selector.replace(/[^a-zA-Z]/g, '')}_${index}`;
                            data.prices[key] = {
                                price: priceMatch[0],
                                grade: 'Unknown',
                                rawText: text
                            };
                        }
                    });
                });
            }

            // Extract card title from the full-prices heading
            const fullPricesHeading = document.querySelector('#full-prices h2');
            if (fullPricesHeading) {
                const headingText = fullPricesHeading.textContent.trim();
                const titleMatch = headingText.match(/Full Price Guide: (.+?) #(\d+)/);
                if (titleMatch) {
                    data.extractedCardName = cleanEbayTitle(titleMatch[1]);
                    data.extractedCardNumber = titleMatch[2];
                    debugLog(`📋 Extracted from heading: ${data.extractedCardName} #${data.extractedCardNumber}`);
                }
            }

            // Extract card image from product_details div
            const productDetailsDiv = document.querySelector('#product_details');
            if (productDetailsDiv) {
                const cardImage = productDetailsDiv.querySelector('.cover img[src*="storage.googleapis.com"]');
                if (cardImage && cardImage.src) {
                    data.imageUrl = cardImage.src;
                    debugLog(`🖼️ Extracted card image: ${data.imageUrl}`);
                } else {
                    debugLog(`⚠️ No card image found in #product_details`);
                }
            }

            // Always try to extract set name from page title regardless of card name source
            if (document.title) {
                const pageTitle = document.title.trim();
                // Match patterns like "CardName #SM210 Prices | Pokemon Promo | Pokemon Cards"
                const setNameMatch = pageTitle.match(/Prices\s*\|\s*(.+?)\s*\|/);
                if (setNameMatch) {
                    data.extractedSetName = setNameMatch[1];
                    debugLog(`📋 Extracted set name from page title: ${data.extractedSetName}`);
                }
            }

            // If no card name found from heading, try extracting from page title
            if (!data.extractedCardName && document.title) {
                const pageTitle = document.title.trim();
                // Match patterns like "CardName #SM210 Prices | Pokemon Promo | Pokemon Cards"
                const pageTitleMatch = pageTitle.match(/^(.+?)\s+#([A-Z0-9]+)\s+Prices\s*\|/);
                if (pageTitleMatch) {
                    data.extractedCardName = cleanEbayTitle(pageTitleMatch[1]);
                    data.extractedCardNumber = pageTitleMatch[2];
                    debugLog(`📋 Extracted from page title: ${data.extractedCardName} #${data.extractedCardNumber}`);
                }
            }

            // Look for last updated date anywhere on the page
            const dateSelectors = [
                '[class*="updated"]',
                '[class*="date"]',
                '.last-updated',
                '.data-date',
                'small', // Sometimes dates are in small tags
                '.text-muted' // Or muted text
            ];

            for (const selector of dateSelectors) {
                const elements = document.querySelectorAll(selector);
                for (const el of elements) {
                    const text = el.textContent.trim();
                    if (text.includes('202') || text.includes('updated') || text.includes('last')) {
                        data.lastUpdated = text;
                        break;
                    }
                }
                if (data.lastUpdated) break;
            }

            const priceCount = Object.keys(data.prices).length;
            debugLog(`💎 Extracted ${priceCount} prices from PriceCharting:`);

            // Log all extracted prices for debugging
            Object.entries(data.prices).forEach(([key, priceData]) => {
                debugLog(`  ${priceData.grade}: ${priceData.price}`);
            });

            if (priceCount > 0) {
                debugLog('✅ Price extraction successful');
            } else {
                debugLog('⚠️ No prices found - check selectors');
            }

            return data;

        } catch (error) {
            console.error('❌ Error extracting PriceCharting data:', error);
            return null;
        }
    }

    // Show notification on PriceCharting page - Updated with better messaging
    function showExtractionNotification(extractedData, cardData) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            background: #27ae60;
            color: white;
            padding: 15px;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            max-width: 300px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
        `;

        const totalPrices = Object.keys(extractedData.prices || {}).length;
        const pricedGrades = Object.values(extractedData.prices || {}).filter(p => p.hasPrice !== false && p.price !== 'Unpriced').length;

        let message = '';
        const cardName = cleanEbayTitle(extractedData.extractedCardName || cardData?.cardName || 'Unknown');
        if (totalPrices > 0) {
            message = `
                <strong>✅ Data Extracted for eBay</strong><br>
                Card: ${cardName}<br>
                Total grades: ${totalPrices}<br>
                With prices: ${pricedGrades}<br>
                <small>This data will appear in your eBay listing</small>
            `;
        } else {
            message = `
                <strong>⚠️ Data Attempted for eBay</strong><br>
                Card: ${cardName}<br>
                No prices found on this page<br>
                <small>eBay will show "Unpriced"</small>
            `;
        }

        notification.innerHTML = message;
        document.body.appendChild(notification);

        // Auto-remove after 6 seconds (slightly longer to read)
        setTimeout(() => {
            if (notification.parentNode) {
                notification.parentNode.removeChild(notification);
            }
        }, 6000);
    }

    // Enhanced function to detect grade from eBay title
    function detectGradeFromTitle(title) {
        if (!title) return null;

        const titleUpper = title.toUpperCase();
        debugLog(`🔍 Grade detection for title: "${title}"`);

        // PSA grades
        const psaMatch = titleUpper.match(/PSA\s*(\d+(?:\.\d+)?)/);
        if (psaMatch) {
            const grade = psaMatch[1];
            let key, displayName;

            if (grade === '10') {
                key = `psa_${grade}`;
                displayName = `PSA ${grade}`;
            } else {
                // PSA grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'PSA',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // BGS grades
        const bgsMatch = titleUpper.match(/BGS\s*(\d+(?:\.\d+)?)/);
        if (bgsMatch) {
            const grade = bgsMatch[1];
            let key, displayName;

            if (grade === '10' && titleUpper.includes('BLACK')) {
                key = 'bgs_10_black';
                displayName = 'BGS 10 Black';
            } else if (grade === '10') {
                key = 'bgs_10';
                displayName = 'BGS 10';
            } else {
                // BGS grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'BGS',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // CGC grades - handle both "CGC 10" and "CGC PRISTINE 10" formats
        const cgcMatch = titleUpper.match(/CGC\s*(?:PRISTINE\s*)?(\d+(?:\.\d+)?)/);
        if (cgcMatch) {
            debugLog(`🎯 CGC grade detected: match = "${cgcMatch[0]}", grade = "${cgcMatch[1]}"`);
            const grade = cgcMatch[1];
            let key, displayName;

            if (grade === '10' && titleUpper.includes('PRISTINE')) {
                key = 'cgc_10_pristine';
                displayName = 'CGC 10 Pristine';
                debugLog(`🏆 CGC Pristine 10 detected - using key: ${key}`);
            } else if (grade === '10') {
                key = 'cgc_10';
                displayName = 'CGC 10';
            } else {
                // CGC grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'CGC',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // SGC grades
        const sgcMatch = titleUpper.match(/SGC\s*(\d+(?:\.\d+)?)/);
        if (sgcMatch) {
            const grade = sgcMatch[1];
            let key, displayName;

            if (grade === '10') {
                key = 'sgc_10';
                displayName = 'SGC 10';
            } else {
                // SGC grades below 10 are listed as "Grade X" on PriceCharting
                key = `grade_${grade.replace('.', '_')}`;
                displayName = `Grade ${grade}`;
            }

            return {
                company: 'SGC',
                grade: grade,
                key: key,
                displayName: displayName
            };
        }

        // Generic Grade patterns (for Grade 9.5, etc.)
        const gradeMatch = titleUpper.match(/GRADE\s*(\d+(?:\.\d+)?)/);
        if (gradeMatch) {
            const grade = gradeMatch[1];
            return {
                company: 'Generic',
                grade: grade,
                key: `grade_${grade.replace('.', '_')}`,
                displayName: `Grade ${grade}`
            };
        }

        return null;
    }

})();