LearnableMeta to Anki Exporter

Export LearnableMeta maps to Anki txt files

// ==UserScript==
// @name         LearnableMeta to Anki Exporter
// @namespace    https://learnablemeta.com/
// @version      1.0.0
// @description  Export LearnableMeta maps to Anki txt files
// @match        https://learnablemeta.com/maps/*
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *
// @run-at       document-end
// @author       BennoGHG
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function $(s) { return document.querySelector(s); }
    function $$(s) { return Array.from(document.querySelectorAll(s)); }
    function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }

    // Enhanced CSS styles - Production Ready
    GM_addStyle([
        '@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");',
        '#lm-window { position: fixed; top: 50px; left: calc(100vw - 380px); width: 360px; min-width: 320px; min-height: 450px; max-width: 600px; max-height: 80vh;',
        'background: linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 100%); color: #f0f0f0; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
        'border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);',
        'z-index: 2147483647; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
        'backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.1); user-select: none; resize: none; }',

        '#lm-window.dragging { transition: none; cursor: move; }',
        '#lm-window.resizing { transition: none; }',
        '#lm-window.hidden { transform: translateX(420px) scale(0.9); opacity: 0; pointer-events: none; }',

        '#lm-header { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); padding: 16px 20px;',
        'cursor: move; user-select: none; display: flex; align-items: center; justify-content: space-between;',
        'border-radius: 16px 16px 0 0; position: relative; box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2); }',
        '#lm-header:active { cursor: grabbing; }',

        '#lm-title { font-size: 15px; font-weight: 600; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.2); }',

        '#lm-hide-btn { background: rgba(255,255,255,0.15); border: none; color: white; width: 28px; height: 28px;',
        'border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center;',
        'font-size: 14px; transition: all 0.2s ease; font-weight: 500; }',
        '#lm-hide-btn:hover { background: rgba(255,255,255,0.25); transform: scale(1.05); }',

        '#lm-content { padding: 20px; display: flex; flex-direction: column; gap: 16px; height: calc(100% - 64px); overflow-y: auto; }',
        '#lm-content::-webkit-scrollbar { width: 6px; }',
        '#lm-content::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 3px; }',
        '#lm-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }',
        '#lm-content::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }',

        '#lm-show-btn { position: fixed; top: 50px; right: 20px; background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);',
        'border: none; color: white; width: 48px; height: 48px; border-radius: 50%; cursor: pointer;',
        'display: none; align-items: center; justify-content: center; font-size: 18px; z-index: 2147483646;',
        'box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3); transition: all 0.3s ease;',
        'border: 2px solid rgba(255,255,255,0.1); }',
        '#lm-show-btn:hover { transform: scale(1.05); box-shadow: 0 12px 30px rgba(59, 130, 246, 0.4); }',

        '.lm-section { background: rgba(255,255,255,0.03); padding: 16px; border-radius: 10px;',
        'border: 1px solid rgba(255,255,255,0.08); backdrop-filter: blur(10px); }',

        '.lm-section label { font-size: 12px; margin-bottom: 8px; display: block; color: #a0a0a0; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }',

        '.lm-section input[type=text] { width: 100%; padding: 10px 12px; border-radius: 6px;',
        'border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.05); color: #f0f0f0;',
        'box-sizing: border-box; font-family: Inter; transition: all 0.2s ease; font-size: 14px; }',
        '.lm-section input[type=text]:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); background: rgba(255,255,255,0.08); }',

        '.lm-section input[type=range] { width: 100%; margin: 8px 0; accent-color: #3b82f6; }',

        '.lm-button { padding: 10px 16px; border: none; border-radius: 8px; font-size: 12px; cursor: pointer;',
        'margin-bottom: 8px; width: 100%; font-family: Inter; font-weight: 500; transition: all 0.2s ease;',
        'text-transform: uppercase; letter-spacing: 0.5px; position: relative; overflow: hidden; }',

        '.lm-button.primary { background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: white; }',
        '.lm-button.primary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139, 92, 246, 0.3); }',

        '.lm-button.secondary { background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; }',
        '.lm-button.secondary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(59, 130, 246, 0.3); }',

        '.lm-button.warning { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white; }',
        '.lm-button.warning:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(245, 158, 11, 0.3); }',

        '.lm-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }',

        '.lm-progress-bar { width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; margin: 12px 0; }',
        '.lm-progress-fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #8b5cf6); width: 0%; transition: width 0.3s ease; border-radius: 3px; }',

        '.lm-slider-container { display: flex; align-items: center; gap: 12px; margin-top: 8px; }',
        '.lm-slider-value { background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; padding: 4px 10px;',
        'border-radius: 12px; font-size: 11px; font-weight: 600; min-width: 20px; text-align: center; }',

        '#lm-status { font-size: 11px; color: #10b981; background: rgba(16, 185, 129, 0.1); padding: 12px; border-radius: 8px;',
        'border: 1px solid rgba(16, 185, 129, 0.2); font-weight: 500; }',

        '#lm-meta-count { font-size: 10px; color: #9ca3af; text-align: center; margin-top: 6px; }',

        '.lm-resize-handle { position: absolute; background: transparent; transition: background 0.2s ease; }',
        '.lm-resize-se { bottom: 0; right: 0; width: 16px; height: 16px; cursor: se-resize; }',
        '.lm-resize-s { bottom: 0; left: 16px; right: 16px; height: 4px; cursor: s-resize; }',
        '.lm-resize-e { right: 0; top: 16px; bottom: 16px; width: 4px; cursor: e-resize; }',
        '.lm-resize-se:hover { background: rgba(59, 130, 246, 0.2); }',
        '.lm-resize-s:hover, .lm-resize-e:hover { background: rgba(59, 130, 246, 0.15); }',

        'body.lm-dragging { cursor: move !important; user-select: none !important; }',
        'body.lm-resizing { user-select: none !important; cursor: inherit !important; }',
        'body.lm-resizing * { pointer-events: none !important; }',

        '@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }',
        '#lm-window { animation: fadeIn 0.3s ease-out; }',

        '@media (max-width: 768px) { #lm-window { width: calc(100vw - 40px); left: 20px; right: 20px; } }'
    ].join('\n'));

    function updateStatus(message) {
        var status = document.getElementById('lm-status');
        if (status) status.textContent = message;
    }

    function updateProgress(current, total) {
        var progressFill = document.querySelector('.lm-progress-fill');
        var metaCount = document.getElementById('lm-meta-count');

        if (progressFill) {
            var percent = total > 0 ? (current / total) * 100 : 0;
            progressFill.style.width = percent + '%';
        }

        if (metaCount) {
            metaCount.textContent = 'Processing: ' + current + '/' + total;
        }
    }

    function sanitizeFilename(filename) {
        if (!filename) return 'LearnableMeta_Export';
        return filename
            .replace(/[<>:"/\\|?*]/g, '')
            .replace(/[^\w\s\-\.]/g, '')
            .replace(/\s+/g, '_')
            .replace(/_{2,}/g, '_')
            .replace(/^_+|_+$/g, '')
            .substring(0, 100) || 'LearnableMeta_Export';
    }

    function downloadFile(content, filename, mimeType) {
        var sanitizedFilename = sanitizeFilename(filename);
        console.log('📥 Downloading:', sanitizedFilename);

        try {
            var blob = new Blob([content], { type: mimeType });
            var url = URL.createObjectURL(blob);

            var link = document.createElement('a');
            link.href = url;
            link.download = sanitizedFilename;
            link.style.display = 'none';

            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            setTimeout(() => URL.revokeObjectURL(url), 1000);
            updateStatus('✅ Downloaded: ' + sanitizedFilename);
        } catch (error) {
            console.error('❌ Download failed:', error);
            updateStatus('❌ Download failed: ' + error.message);
        }
    }

    function waitForTable() {
        return new Promise(function(resolve) {
            var attempts = 0;
            var maxAttempts = 20; // 10 seconds max

            function checkTable() {
                attempts++;
                var table = document.querySelector('table');
                if (table && table.querySelectorAll('td').length > 0) {
                    resolve({
                        cells: table.querySelectorAll('td'),
                        hasCheckboxes: document.querySelectorAll('input[type="checkbox"]').length > 0,
                        checkboxes: document.querySelectorAll('input[type="checkbox"]')
                    });
                } else if (attempts >= maxAttempts) {
                    throw new Error('Table not found after ' + maxAttempts + ' attempts');
                } else {
                    setTimeout(checkTable, 500);
                }
            }
            checkTable();
        });
    }

    function isMetaSelected(metaName, checkboxes) {
        if (!checkboxes || checkboxes.length === 0) return true;
        for (var i = 0; i < checkboxes.length; i++) {
            var parent = checkboxes[i].closest('tr, div, li') || checkboxes[i].parentElement;
            if (parent && parent.textContent.includes(metaName)) {
                return checkboxes[i].checked;
            }
        }
        return true;
    }

    function countSelectedMetas(tableCells, checkboxes) {
        var selectedCount = 0, totalCount = 0;
        for (var i = 0; i < tableCells.length; i++) {
            var metaName = tableCells[i].textContent.trim();
            if (metaName) {
                totalCount++;
                if (isMetaSelected(metaName, checkboxes)) selectedCount++;
            }
        }
        return { selected: selectedCount, total: totalCount };
    }

    function findContentDiv(metaName) {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < divs.length; i++) {
            if (divs[i].textContent.includes(metaName) &&
                (divs[i].querySelector('img') || divs[i].querySelector('p'))) {
                return divs[i];
            }
        }
        return null;
    }

    function extractImages(container, maxImages) {
        var imgs = container.querySelectorAll('img');
        var result = [];
        for (var i = 0; i < imgs.length && result.length < maxImages; i++) {
            var src = imgs[i].src;
            if (!src || src.indexOf('http') !== 0) continue;
            if (src.includes('logo') || src.includes('icon') || src.includes('nav') ||
                src.includes('menu') || src.includes('header') || src.includes('_app/')) continue;
            if ((imgs[i].width > 0 && imgs[i].width < 50) ||
                (imgs[i].height > 0 && imgs[i].height < 50)) continue;
            result.push(src);
        }
        return result;
    }

    function extractDescription(container) {
        var elements = container.querySelectorAll('p, li, div, span');
        var bestDescription = '';
        var bestScore = 0;

        for (var i = 0; i < elements.length; i++) {
            var text = elements[i].textContent.trim();
            if (text.length < 20 || text.length > 1000) continue;

            var lower = text.toLowerCase();
            if (lower.includes('meta list') || lower.includes('home') ||
                lower.includes('plonkit.net') || lower.includes('www.') ||
                text === 'Play' || text === 'Maps') continue;

            var score = 0;
            if (text.includes('.') || text.includes('!')) score += 10;
            if (lower.includes('note:')) score += 20;
            if (text.length > 50) score += text.length / 10;
            if (lower.includes('used') || lower.includes('typically') ||
                lower.includes('common') || lower.includes('found')) score += 5;

            if (score > bestScore) {
                bestScore = score;
                bestDescription = text;
            }
        }

        return bestDescription.replace(/Meta List[^.]*\./gi, '')
                           .replace(/Play\s*/gi, '')
                           .replace(/\s+/g, ' ')
                           .trim();
    }

    function cleanDescription(description) {
        if (!description) return '';
        return description.replace(/",LearnableMeta/g, '')
                         .replace(/,LearnableMeta/g, '')
                         .replace(/LearnableMeta$/g, '')
                         .replace(/\s+/g, ' ')
                         .trim();
    }

    function cleanMetaTitle(title) {
        return title ? title.replace(/\s*\(\d+\)\s*$/, '').trim() : '';
    }

    function setButtonsEnabled(enabled) {
        var buttons = document.querySelectorAll('.lm-button');
        for (var i = 0; i < buttons.length; i++) {
            buttons[i].disabled = !enabled;
        }
    }

    function scrapeMetas(maxImages) {
        updateStatus('⏳ Waiting for meta table...');
        setButtonsEnabled(false);

        return waitForTable().then(function(tableData) {
            var tableCells = tableData.cells;
            var checkboxes = tableData.hasCheckboxes ? Array.from(tableData.checkboxes) : null;
            var metas = [], processedCount = 0;
            var counts = countSelectedMetas(tableCells, checkboxes);

            updateStatus(checkboxes ?
                '🔍 Processing ' + counts.selected + '/' + counts.total + ' selected metas...' :
                '🔍 Processing all ' + counts.total + ' metas...');

            function processNextCell(index) {
                if (index >= tableCells.length) {
                    updateStatus('✅ Found ' + metas.length + ' metas with content');
                    setButtonsEnabled(true);
                    return Promise.resolve(metas);
                }

                var cell = tableCells[index];
                var metaName = cell.textContent.trim();
                if (!metaName || !isMetaSelected(metaName, checkboxes)) {
                    return processNextCell(index + 1);
                }

                processedCount++;
                updateProgress(processedCount, counts.selected || counts.total);
                updateStatus('📝 Processing (' + processedCount + '): ' + metaName);

                cell.scrollIntoView({ behavior: 'smooth', block: 'center' });
                cell.click();

                return sleep(800).then(function() {
                    try {
                        var contentDiv = findContentDiv(metaName);
                        if (contentDiv) {
                            var images = extractImages(contentDiv, maxImages);
                            var description = cleanDescription(extractDescription(contentDiv));
                            var cleanTitle = cleanMetaTitle(metaName);

                            if (images.length > 0 || description) {
                                metas.push({
                                    title: cleanTitle,
                                    images: images,
                                    description: description || cleanTitle
                                });
                            }
                        }
                    } catch (error) {
                        console.warn('⚠️ Error processing meta:', metaName, error);
                    }
                    return processNextCell(index + 1);
                });
            }

            return processNextCell(0);
        }).catch(function(error) {
            setButtonsEnabled(true);
            throw error;
        });
    }

    function createPerfectTxtExport(deckName, metas) {
        updateStatus('📝 Creating production-ready Anki file...');

        var timestamp = new Date().toLocaleString();
        var instructions = [
            '# 🎯 PRODUCTION ANKI IMPORT FILE',
            '# ==============================',
            '# Deck: ' + deckName,
            '# Cards: ' + metas.length,
            '# Created: ' + timestamp,
            '# Format: Premium styled cards with responsive design',
            '# Quality: Production ready with error handling',
            '#',
            '# 📥 IMPORT INSTRUCTIONS:',
            '# 1. Open Anki Desktop',
            '# 2. File → Import',
            '# 3. Select this TXT file',
            '# 4. Import Settings:',
            '#    • Type: "Text separated by tabs or semicolons"',
            '#    • Field separator: Tab',
            '#    • Field 1 → Front',
            '#    • Field 2 → Back',
            '#    • Field 3 → Tags',
            '#    • ✅ Allow HTML in fields',
            '#    • Deck: "' + deckName + '"',
            '# 5. Click Import',
            '#',
            '# 🎨 CARD DESIGN:',
            '# • Mobile-responsive layout',
            '# • High-quality image display',
            '# • Professional typography',
            '# • Optimized for learning',
            '#'
        ].join('\n') + '\n';

        var csvContent = instructions + 'Front\tBack\tTags\n';

        for (var i = 0; i < metas.length; i++) {
            var meta = metas[i];

            var front = '';
            if (meta.images.length > 0) {
                var imageStyles = 'max-width: 100%; max-height: 400px; width: auto; height: auto; display: block; margin: 15px auto; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); object-fit: contain;';

                var imageHtml = meta.images.map(function(img) {
                    return '<img src="' + img + '" alt="' + meta.title + '" style="' + imageStyles + '" loading="lazy">';
                }).join('');

                front = '<div style="text-align: center; padding: 25px; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-radius: 16px; margin: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.08); min-height: 200px; display: flex; flex-direction: column; justify-content: center;">' + imageHtml + '</div>';
            } else {
                front = '<div style="text-align: center; padding: 60px 20px; background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); border-radius: 16px; margin: 12px; color: #1e40af; font-size: 48px; min-height: 200px; display: flex; align-items: center; justify-content: center;">🗺️<div style="font-size: 16px; margin-top: 10px; color: #64748b;">No image available</div></div>';
            }

            var back = '<div style="font-family: Inter, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; max-width: 700px; margin: 0 auto; background: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.1); border: 1px solid #e5e7eb;"><div style="background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); color: white; padding: 32px 24px; text-align: center;"><h1 style="margin: 0; font-size: 28px; font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,0.2); line-height: 1.2;">' + meta.title + '</h1></div><div style="padding: 32px 24px; line-height: 1.7; color: #374151;"><div style="background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-left: 4px solid #3b82f6; padding: 24px; border-radius: 0 12px 12px 0; font-size: 16px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); margin-bottom: 16px;">' + meta.description + '</div><div style="font-size: 12px; color: #9ca3af; text-align: center; padding-top: 16px; border-top: 1px solid #e5e7eb;">LearnableMeta Export</div></div></div>';

            var tags = 'LearnableMeta ' + deckName.replace(/\s+/g, '_') + ' geography visual_learning production_ready';

            csvContent += front + '\t' + back + '\t' + tags + '\n';
        }

        var filename = sanitizeFilename(deckName) + '.txt';
        downloadFile(csvContent, filename, 'text/plain;charset=utf-8');
    }

    function checkSelection() {
        updateStatus('🔍 Analyzing selection...');
        setButtonsEnabled(false);

        waitForTable().then(function(tableData) {
            var counts = countSelectedMetas(tableData.cells, tableData.checkboxes);
            setButtonsEnabled(true);

            if (tableData.hasCheckboxes) {
                updateStatus('📊 Selection: ' + counts.selected + ' of ' + counts.total + ' metas');
                alert('Selection Status:\n\n' +
                      '✅ Selected: ' + counts.selected + ' metas\n' +
                      '📊 Total available: ' + counts.total + ' metas\n\n' +
                      (counts.selected === 0 ?
                       '⚠️ Please select some metas to export!' :
                       '🚀 Ready to export ' + counts.selected + ' selected metas!'));
            } else {
                updateStatus('📊 Will process all ' + counts.total + ' metas');
                alert('Export Status:\n\n' +
                      '📊 Found: ' + counts.total + ' metas\n' +
                      '🚀 Will process all metas when exported\n\n' +
                      'No selection controls detected.');
            }
        }).catch(function(error) {
            setButtonsEnabled(true);
            updateStatus('❌ Error: ' + error.message);
            alert('Error checking selection:\n' + error.message);
        });
    }

    function exportData() {
        var deckName = document.getElementById('lm-deck').value.trim() || 'LearnableMeta';
        var maxImages = parseInt(document.getElementById('lm-range').value) || 2;

        updateStatus('🚀 Starting export process...');

        scrapeMetas(maxImages).then(function(metas) {
            if (metas.length === 0) {
                updateStatus('⚠️ No content found to export');
                alert('Export Failed:\n\nNo metas with content were found.\n\nTips:\n• Make sure metas are loaded\n• Check if any metas are selected\n• Verify the page has content');
                return;
            }

            updateStatus('📦 Creating download file...');
            createPerfectTxtExport(deckName, metas);

            // Success notification
            setTimeout(function() {
                updateStatus('🎉 Export completed successfully!');
            }, 1000);

        }).catch(function(error) {
            console.error('💥 Export failed:', error);
            updateStatus('❌ Export failed: ' + error.message);
            alert('Export Error:\n\n' + error.message + '\n\nPlease try again or check the console for details.');
        });
    }

    function createPanel() {
        var mapTitle = (document.querySelector('h1, h2, title') || {}).textContent || 'LearnableMeta';

        // Clean up the map title
        mapTitle = mapTitle.replace(/LearnableMeta\s*[-|]\s*/gi, '').trim();

        var window = document.createElement('div');
        window.id = 'lm-window';

        var showBtn = document.createElement('button');
        showBtn.id = 'lm-show-btn';
        showBtn.innerHTML = '📚';
        showBtn.title = 'Show Anki Exporter';

        window.innerHTML = [
            '<div id="lm-header">',
            '<div id="lm-title">📚 Anki Exporter</div>',
            '<button id="lm-hide-btn" title="Hide Window">×</button>',
            '</div>',
            '<div id="lm-content">',
            '<div class="lm-section">',
            '<label>Deck Name</label>',
            '<input id="lm-deck" type="text" value="' + sanitizeFilename(mapTitle) + '" placeholder="Enter deck name">',
            '</div>',
            '<div class="lm-section">',
            '<label>Images per Card (0-5)</label>',
            '<div class="lm-slider-container">',
            '<input id="lm-range" type="range" min="0" max="5" value="2">',
            '<span id="lm-slider-value" class="lm-slider-value">2</span>',
            '</div></div>',
            '<div class="lm-section">',
            '<button id="lm-export-main" class="lm-button primary">🚀 Export</button>',
            '<button id="lm-check-selection" class="lm-button warning">📊 Check Selection</button>',
            '<div class="lm-progress-bar"><div class="lm-progress-fill"></div></div>',
            '<div id="lm-meta-count"></div>',
            '</div>',
            '<div id="lm-status">✅ Production ready! Click Export to begin.</div>',
            '</div>',
            '<div class="lm-resize-handle lm-resize-se"></div>',
            '<div class="lm-resize-handle lm-resize-s"></div>',
            '<div class="lm-resize-handle lm-resize-e"></div>'
        ].join('');

        document.body.appendChild(window);
        document.body.appendChild(showBtn);

        // FIXED DRAG FUNCTIONALITY
        var isDragging = false;
        var dragOffset = { x: 0, y: 0 };

        var header = document.getElementById('lm-header');
        header.addEventListener('mousedown', startDrag);

        function startDrag(e) {
            if (e.target.id === 'lm-hide-btn') return;

            isDragging = true;
            var rect = window.getBoundingClientRect();

            dragOffset.x = e.clientX - rect.left;
            dragOffset.y = e.clientY - rect.top;

            window.classList.add('dragging');
            document.body.classList.add('lm-dragging');

            document.addEventListener('mousemove', onDrag);
            document.addEventListener('mouseup', stopDrag);
            e.preventDefault();
        }

        function onDrag(e) {
            if (!isDragging) return;

            var newX = e.clientX - dragOffset.x;
            var newY = e.clientY - dragOffset.y;

            var maxX = document.documentElement.clientWidth - window.offsetWidth;
            var maxY = document.documentElement.clientHeight - window.offsetHeight;

            newX = Math.max(0, Math.min(newX, maxX));
            newY = Math.max(0, Math.min(newY, maxY));

            window.style.left = newX + 'px';
            window.style.top = newY + 'px';
        }

        function stopDrag() {
            isDragging = false;
            window.classList.remove('dragging');
            document.body.classList.remove('lm-dragging');
            document.removeEventListener('mousemove', onDrag);
            document.removeEventListener('mouseup', stopDrag);
        }

        // FIXED RESIZE FUNCTIONALITY
        var isResizing = false;
        var resizeType = '';
        var resizeStart = { x: 0, y: 0, width: 0, height: 0 };

        // Use window.querySelector instead of document.querySelector
        var resizeSE = window.querySelector('.lm-resize-se');
        var resizeS = window.querySelector('.lm-resize-s');
        var resizeE = window.querySelector('.lm-resize-e');

        if (resizeSE) resizeSE.addEventListener('mousedown', function(e) { startResize(e, 'se'); });
        if (resizeS) resizeS.addEventListener('mousedown', function(e) { startResize(e, 's'); });
        if (resizeE) resizeE.addEventListener('mousedown', function(e) { startResize(e, 'e'); });

        function startResize(e, type) {
            isResizing = true;
            resizeType = type;

            var rect = window.getBoundingClientRect();
            resizeStart.x = e.clientX;
            resizeStart.y = e.clientY;
            resizeStart.width = rect.width;
            resizeStart.height = rect.height;

            window.classList.add('resizing');
            document.body.classList.add('lm-resizing');

            document.addEventListener('mousemove', onResize);
            document.addEventListener('mouseup', stopResize);
            e.preventDefault();
            e.stopPropagation();
        }

        function onResize(e) {
            if (!isResizing) return;

            var deltaX = e.clientX - resizeStart.x;
            var deltaY = e.clientY - resizeStart.y;

            var newWidth = resizeStart.width;
            var newHeight = resizeStart.height;

            if (resizeType.includes('e')) {
                newWidth = Math.max(320, Math.min(600, resizeStart.width + deltaX));
            }

            if (resizeType.includes('s')) {
                newHeight = Math.max(450, Math.min(window.innerHeight * 0.9, resizeStart.height + deltaY));
            }

            window.style.width = newWidth + 'px';
            window.style.height = newHeight + 'px';
        }

        function stopResize() {
            isResizing = false;
            window.classList.remove('resizing');
            document.body.classList.remove('lm-resizing');
            document.removeEventListener('mousemove', onResize);
            document.removeEventListener('mouseup', stopResize);
        }

        // Hide/Show functionality
        var hideBtn = document.getElementById('lm-hide-btn');
        var isHidden = false;

        hideBtn.addEventListener('click', function() {
            if (!isHidden) {
                window.classList.add('hidden');
                showBtn.style.display = 'flex';
                isHidden = true;
            }
        });

        showBtn.addEventListener('click', function() {
            if (isHidden) {
                window.classList.remove('hidden');
                showBtn.style.display = 'none';
                isHidden = false;
            }
        });

        // Event listeners
        document.getElementById('lm-range').addEventListener('input', function(e) {
            document.getElementById('lm-slider-value').textContent = e.target.value;
        });

        document.getElementById('lm-export-main').addEventListener('click', exportData);
        document.getElementById('lm-check-selection').addEventListener('click', checkSelection);

        // Keyboard shortcuts
        document.addEventListener('keydown', function(e) {
            if (e.ctrlKey || e.metaKey) {
                if (e.key === 'h' && !isHidden) {
                    e.preventDefault();
                    hideBtn.click();
                } else if (e.key === 'e' && !isHidden) {
                    e.preventDefault();
                    exportData();
                }
            }
        });
    }

    // Initialize with error handling
    setTimeout(function() {
        try {
            createPanel();
            updateStatus('✅ Ready! Select Metas for Export.');
            console.log('✅ LearnableMeta Anki Exporter v1.0 loaded successfully');
        } catch (error) {
            console.error('❌ Failed to initialize Anki Exporter:', error);
        }
    }, 1000);

})();