SYBRO.Tech



PDF to JPG Converter | Extract Images from PDF

PDF to JPG Converter

Extract high-quality JPG images from your PDF documents. Perfect for individual pages or batch conversion.

    <!-- Main Converter -->
    <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden">
        <!-- File Drop Zone -->
        <div id="dropzone" class="dropzone border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer mb-6 hover:border-blue-400 transition-colors">
            <div class="flex flex-col items-center justify-center space-y-3">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
                </svg>
                <h3 class="text-lg font-medium text-gray-700">Drag & drop your PDF file here</h3>
                <p class="text-sm text-gray-500">or click to browse files</p>
                <input type="file" id="fileInput" class="hidden" accept=".pdf,application/pdf" multiple>
                <button id="selectFilesBtn" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
                    Select PDF File
                </button>
            </div>
        </div>

        <!-- Options Section -->
        <div class="px-6 py-4 border-t border-gray-200">
            <h3 class="text-lg font-medium text-gray-800 mb-4">Conversion Options</h3>
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">Image Quality</label>
                    <select id="quality" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
                        <option value="1.0">High (100%)</option>
                        <option value="0.9" selected>Good (90%)</option>
                        <option value="0.7">Medium (70%)</option>
                        <option value="0.5">Low (50%)</option>
                    </select>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">Pages to Convert</label>
                    <select id="pages" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
                        <option value="all" selected>All Pages</option>
                        <option value="range">Custom Range</option>
                        <option value="single">Single Page</option>
                    </select>
                </div>
                <div id="pageRangeContainer" class="hidden">
                    <label class="block text-sm font-medium text-gray-700 mb-1">Page Range</label>
                    <input type="text" id="pageRange" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="e.g. 1-3,5,7-9">
                </div>
                <div id="singlePageContainer" class="hidden">
                    <label class="block text-sm font-medium text-gray-700 mb-1">Page Number</label>
                    <input type="number" id="singlePage" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" min="1" value="1">
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">Output Format</label>
                    <select id="format" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
                        <option value="jpg" selected>JPG</option>
                        <option value="png">PNG</option>
                    </select>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">DPI (Resolution)</label>
                    <select id="dpi" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
                        <option value="150">150 DPI (Good)</option>
                        <option value="300" selected>300 DPI (High)</option>
                        <option value="600">600 DPI (Ultra)</option>
                    </select>
                </div>
            </div>
        </div>

        <!-- PDF Preview Section -->
        <div id="pdfPreviewSection" class="px-6 py-4 border-t border-gray-200 hidden">
            <div class="flex justify-between items-center mb-4">
                <h3 class="text-lg font-medium text-gray-800">PDF Preview</h3>
                <div class="text-sm text-gray-500">
                    <span id="pageCount">0 pages</span>
                </div>
            </div>
            <div id="pdfThumbnails" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 max-h-96 overflow-y-auto p-2">
                <!-- PDF page thumbnails will appear here -->
            </div>
            <div class="flex justify-between items-center mt-4">
                <button id="clearPdfBtn" class="text-sm text-red-500 hover:text-red-700 focus:outline-none">
                    Remove PDF
                </button>
                <div class="flex items-center space-x-2">
                    <span class="text-sm text-gray-500">Select:</span>
                    <button id="selectAllBtn" class="text-sm text-blue-500 hover:text-blue-700 focus:outline-none">
                        All
                    </button>
                    <button id="selectNoneBtn" class="text-sm text-blue-500 hover:text-blue-700 focus:outline-none">
                        None
                    </button>
                </div>
            </div>
        </div>

        <!-- Action Buttons -->
        <div class="px-6 py-4 bg-gray-50 flex flex-col sm:flex-row justify-end space-y-3 sm:space-y-0 sm:space-x-3">
            <button id="cancelBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors hidden">
                Cancel
            </button>
            <button id="convertBtn" class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center" disabled>
                <span>Convert to JPG</span>
                <svg id="convertSpinner" class="animate-spin -mr-1 ml-2 h-4 w-4 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                </svg>
            </button>
        </div>
    </div>

    <!-- Instructions -->
    <div class="max-w-4xl mx-auto mt-8 bg-white rounded-xl shadow-md overflow-hidden">
        <div class="p-6">
            <h2 class="text-xl font-bold text-gray-800 mb-4">How to convert PDF to JPG</h2>
            <ol class="list-decimal list-inside space-y-2 text-gray-700">
                <li>Upload your PDF file by dragging or clicking "Select PDF File"</li>
                <li>Select which pages to convert (all, range, or single page)</li>
                <li>Adjust quality and resolution settings if needed</li>
                <li>Click "Convert to JPG" and download your images</li>
            </ol>
            <div class="mt-6 p-4 bg-blue-50 rounded-lg">
                <h3 class="text-md font-medium text-blue-800 mb-2">Pro Tip</h3>
                <p class="text-sm text-blue-700">For multi-page PDFs, the converter will create a ZIP file containing all JPG images for easy download.</p>
            </div>
        </div>
    </div>
</div>

<!-- Results Modal -->
<div id="resultsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
    <div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] flex flex-col">
        <div class="flex justify-between items-center border-b border-gray-200 px-6 py-4">
            <h3 class="text-lg font-medium text-gray-800">Conversion Results</h3>
            <button id="closeResultsBtn" class="text-gray-500 hover:text-gray-700">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                </svg>
            </button>
        </div>
        <div class="flex-1 overflow-auto p-6">
            <div id="conversionResults" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                <!-- Conversion results will appear here -->
            </div>
        </div>
        <div class="border-t border-gray-200 px-6 py-4 flex justify-between items-center">
            <div id="downloadAllContainer" class="hidden">
                <button id="downloadAllBtn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors flex items-center">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                    </svg>
                    Download All as ZIP
                </button>
            </div>
            <button id="closeModalBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors">
                Close
            </button>
        </div>
    </div>
</div>

<script>
    // Initialize PDF.js worker
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';

    // DOM Elements
    const dropzone = document.getElementById('dropzone');
    const fileInput = document.getElementById('fileInput');
    const selectFilesBtn = document.getElementById('selectFilesBtn');
    const pdfPreviewSection = document.getElementById('pdfPreviewSection');
    const pdfThumbnails = document.getElementById('pdfThumbnails');
    const pageCount = document.getElementById('pageCount');
    const clearPdfBtn = document.getElementById('clearPdfBtn');
    const convertBtn = document.getElementById('convertBtn');
    const cancelBtn = document.getElementById('cancelBtn');
    const convertSpinner = document.getElementById('convertSpinner');
    const resultsModal = document.getElementById('resultsModal');
    const closeResultsBtn = document.getElementById('closeResultsBtn');
    const conversionResults = document.getElementById('conversionResults');
    const downloadAllBtn = document.getElementById('downloadAllBtn');
    const downloadAllContainer = document.getElementById('downloadAllContainer');
    const closeModalBtn = document.getElementById('closeModalBtn');
    const pagesSelect = document.getElementById('pages');
    const pageRangeContainer = document.getElementById('pageRangeContainer');
    const singlePageContainer = document.getElementById('singlePageContainer');
    const selectAllBtn = document.getElementById('selectAllBtn');
    const selectNoneBtn = document.getElementById('selectNoneBtn');

    // State
    let pdfDoc = null;
    let totalPages = 0;
    let conversionInProgress = false;
    let selectedPages = new Set();

    // Event Listeners
    selectFilesBtn.addEventListener('click', () => fileInput.click());
    fileInput.addEventListener('change', handleFileSelect);
    dropzone.addEventListener('dragover', handleDragOver);
    dropzone.addEventListener('dragleave', handleDragLeave);
    dropzone.addEventListener('drop', handleDrop);
    clearPdfBtn.addEventListener('click', clearPdf);
    convertBtn.addEventListener('click', convertToJpg);
    cancelBtn.addEventListener('click', cancelConversion);
    closeResultsBtn.addEventListener('click', () => resultsModal.classList.add('hidden'));
    closeModalBtn.addEventListener('click', () => resultsModal.classList.add('hidden'));
    downloadAllBtn.addEventListener('click', downloadAllImages);
    pagesSelect.addEventListener('change', updatePageSelectionUI);
    selectAllBtn.addEventListener('click', selectAllPages);
    selectNoneBtn.addEventListener('click', selectNoPages);

    // Functions
    function handleFileSelect(e) {
        const selectedFiles = Array.from(e.target.files);
        if (selectedFiles.length > 0) {
            loadPdf(selectedFiles[0]);
        }
    }

    function handleDragOver(e) {
        e.preventDefault();
        e.stopPropagation();
        dropzone.classList.add('active');
    }

    function handleDragLeave(e) {
        e.preventDefault();
        e.stopPropagation();
        dropzone.classList.remove('active');
    }

    function handleDrop(e) {
        e.preventDefault();
        e.stopPropagation();
        dropzone.classList.remove('active');

        const droppedFiles = Array.from(e.dataTransfer.files);
        if (droppedFiles.length > 0) {
            // Check if any dropped file is a PDF
            const pdfFile = droppedFiles.find(file => file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'));
            if (pdfFile) {
                loadPdf(pdfFile);
            } else {
                alert('Please drop a PDF file.');
            }
        }
    }

    async function loadPdf(file) {
        if (!file) return;

        try {
            const arrayBuffer = await file.arrayBuffer();
            pdfDoc = await pdfjsLib.getDocument(arrayBuffer).promise;
            totalPages = pdfDoc.numPages;

            // Update UI
            pageCount.textContent = `${totalPages} ${totalPages === 1 ? 'page' : 'pages'}`;
            pdfPreviewSection.classList.remove('hidden');
            convertBtn.disabled = false;
            fileInput.value = ''; // Reset file input

            // Render thumbnails
            await renderThumbnails();

            // Initialize page selection
            updatePageSelection();
        } catch (error) {
            console.error('PDF loading error:', error);
            alert('Failed to load PDF. Please try another file.');
        }
    }

    async function renderThumbnails() {
        pdfThumbnails.innerHTML = '';
        selectedPages.clear();

        // Determine which pages to render based on selection
        const pagesToRender = getPagesToRender();

        for (const pageNum of pagesToRender) {
            const page = await pdfDoc.getPage(pageNum);
            const viewport = page.getViewport({ scale: 0.2 });

            const thumbnailContainer = document.createElement('div');
            thumbnailContainer.className = 'page-thumbnail relative bg-gray-100 rounded-md overflow-hidden';

            const canvas = document.createElement('canvas');
            canvas.className = 'w-full h-auto';
            canvas.height = viewport.height;
            canvas.width = viewport.width;

            const pageLabel = document.createElement('div');
            pageLabel.className = 'absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white text-center py-1 text-sm';
            pageLabel.textContent = `Page ${pageNum}`;

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = `page-${pageNum}`;
            checkbox.className = 'absolute top-2 left-2';
            checkbox.checked = true;
            checkbox.addEventListener('change', () => {
                if (checkbox.checked) {
                    selectedPages.add(pageNum);
                } else {
                    selectedPages.delete(pageNum);
                }
                updateConvertButtonState();
            });

            // Select page by default
            selectedPages.add(pageNum);

            const pageActions = document.createElement('div');
            pageActions.className = 'page-actions absolute top-2 right-2 opacity-0 transition-opacity';
            pageActions.innerHTML = `
                <button class="preview-page bg-white bg-opacity-80 rounded-full p-1 hover:bg-opacity-100" data-page="${pageNum}">
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-800" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
                    </svg>
                </button>
            `;

            thumbnailContainer.appendChild(canvas);
            thumbnailContainer.appendChild(pageLabel);
            thumbnailContainer.appendChild(checkbox);
            thumbnailContainer.appendChild(pageActions);
            pdfThumbnails.appendChild(thumbnailContainer);

            // Render page to canvas
            await page.render({
                canvasContext: canvas.getContext('2d'),
                viewport: viewport
            }).promise;
        }

        updateConvertButtonState();

        // Add event listeners for preview buttons
        document.querySelectorAll('.preview-page').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const pageNum = parseInt(e.currentTarget.getAttribute('data-page'));
                previewPage(pageNum);
            });
        });
    }

    function getPagesToRender() {
        const selectionMode = pagesSelect.value;
        const pages = [];

        if (selectionMode === 'all') {
            for (let i = 1; i <= totalPages; i++) {
                pages.push(i);
            }
        } else if (selectionMode === 'range') {
            const rangeInput = document.getElementById('pageRange').value;
            if (rangeInput) {
                // Parse range input (e.g., "1-3,5,7-9")
                const parts = rangeInput.split(',');
                for (const part of parts) {
                    if (part.includes('-')) {
                        const [start, end] = part.split('-').map(Number);
                        for (let i = start; i <= end; i++) {
                            if (i >= 1 && i <= totalPages) {
                                pages.push(i);
                            }
                        }
                    } else {
                        const pageNum = parseInt(part);
                        if (pageNum >= 1 && pageNum <= totalPages) {
                            pages.push(pageNum);
                        }
                    }
                }
            }
        } else if (selectionMode === 'single') {
            const pageNum = parseInt(document.getElementById('singlePage').value) || 1;
            if (pageNum >= 1 && pageNum <= totalPages) {
                pages.push(pageNum);
            }
        }

        // Default to first page if no valid pages found
        if (pages.length === 0 && totalPages > 0) {
            pages.push(1);
        }

        return [...new Set(pages)].sort((a, b) => a - b);
    }

    function updatePageSelection() {
        const selectionMode = pagesSelect.value;

        if (selectionMode === 'range') {
            pageRangeContainer.classList.remove('hidden');
            singlePageContainer.classList.add('hidden');
        } else if (selectionMode === 'single') {
            pageRangeContainer.classList.add('hidden');
            singlePageContainer.classList.remove('hidden');
            // Set max value for single page input
            document.getElementById('singlePage').max = totalPages;
        } else {
            pageRangeContainer.classList.add('hidden');
            singlePageContainer.classList.add('hidden');
        }

        // Re-render thumbnails when selection changes
        if (pdfDoc) {
            renderThumbnails();
        }
    }

    function updatePageSelectionUI() {
        updatePageSelection();

        // Add event listeners for range inputs
        if (pagesSelect.value === 'range') {
            document.getElementById('pageRange').addEventListener('change', () => {
                if (pdfDoc) renderThumbnails();
            });
        } else if (pagesSelect.value === 'single') {
            document.getElementById('singlePage').addEventListener('change', () => {
                if (pdfDoc) renderThumbnails();
            });
        }
    }

    function selectAllPages() {
        document.querySelectorAll('#pdfThumbnails input[type="checkbox"]').forEach(checkbox => {
            checkbox.checked = true;
            const pageNum = parseInt(checkbox.id.split('-')[1]);
            selectedPages.add(pageNum);
        });
        updateConvertButtonState();
    }

    function selectNoPages() {
        document.querySelectorAll('#pdfThumbnails input[type="checkbox"]').forEach(checkbox => {
            checkbox.checked = false;
            const pageNum = parseInt(checkbox.id.split('-')[1]);
            selectedPages.delete(pageNum);
        });
        updateConvertButtonState();
    }

    function updateConvertButtonState() {
        convertBtn.disabled = selectedPages.size === 0;
    }

    async function previewPage(pageNum) {
        if (!pdfDoc || pageNum < 1 || pageNum > totalPages) return;

        const page = await pdfDoc.getPage(pageNum);
        const viewport = page.getViewport({ scale: 1.0 });

        // Create a temporary canvas for preview
        conversionResults.innerHTML = `
            <div class="col-span-full flex justify-center">
                <canvas id="largePreview" class="border border-gray-200 shadow-sm"></canvas>
            </div>
        `;

        const canvas = document.getElementById('largePreview');
        canvas.height = viewport.height;
        canvas.width = viewport.width;

        await page.render({
            canvasContext: canvas.getContext('2d'),
            viewport: viewport
        }).promise;

        resultsModal.classList.remove('hidden');
        downloadAllContainer.classList.add('hidden');
    }

    async function convertToJpg() {
        if (!pdfDoc || selectedPages.size === 0 || conversionInProgress) return;

        conversionInProgress = true;
        convertBtn.disabled = true;
        cancelBtn.classList.remove('hidden');
        convertSpinner.classList.remove('hidden');
        convertBtn.innerHTML = '<span>Converting...</span>';

        try {
            // Get conversion options
            const quality = parseFloat(document.getElementById('quality').value);
            const format = document.getElementById('format').value;
            const dpi = parseInt(document.getElementById('dpi').value);

            // Calculate scale based on DPI (72 DPI is default PDF resolution)
            const scale = dpi / 72;

            // Prepare results container
            conversionResults.innerHTML = '';
            let completed = 0;
            const total = selectedPages.size;

            // Convert each selected page
            const imageUrls = [];
            const sortedPages = Array.from(selectedPages).sort((a, b) => a - b);

            for (const pageNum of sortedPages) {
                if (!conversionInProgress) break;

                const page = await pdfDoc.getPage(pageNum);
                const viewport = page.getViewport({ scale: scale });

                // Create canvas for conversion
                const canvas = document.createElement('canvas');
                canvas.height = viewport.height;
                canvas.width = viewport.width;

                // Render page to canvas
                await page.render({
                    canvasContext: canvas.getContext('2d'),
                    viewport: viewport
                }).promise;

                // Convert canvas to image
                const imageUrl = canvas.toDataURL(`image/${format}`, quality);
                imageUrls.push({
                    url: imageUrl,
                    pageNum: pageNum,
                    format: format
                });

                // Update progress
                completed++;
                convertBtn.innerHTML = `<span>Converting (${completed}/${total})...</span>`;

                // Add to results preview
                addResultToPreview(pageNum, imageUrl, format);
            }

            if (conversionInProgress && imageUrls.length > 0) {
                // Show results
                resultsModal.classList.remove('hidden');

                // Show download all button if multiple images
                if (imageUrls.length > 1) {
                    downloadAllContainer.classList.remove('hidden');
                    // Store image URLs for download all
                    downloadAllBtn.dataset.images = JSON.stringify(imageUrls);
                }
            }
        } catch (error) {
            console.error('Conversion error:', error);
            alert('An error occurred during conversion. Please try again.');
        } finally {
            resetConversionUI();
        }
    }

    function addResultToPreview(pageNum, imageUrl, format) {
        const resultItem = document.createElement('div');
        resultItem.className = 'result-item bg-gray-50 rounded-lg overflow-hidden border border-gray-200';
        resultItem.innerHTML = `
            <div class="p-4">
                <img src="${imageUrl}" alt="Page ${pageNum}" class="w-full h-auto mb-3">
                <div class="flex justify-between items-center">
                    <span class="text-sm font-medium text-gray-700">page_${pageNum}.${format}</span>
                    <button class="download-single px-3 py-1 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 transition-colors" data-url="${imageUrl}" data-filename="page_${pageNum}.${format}">
                        Download
                    </button>
                </div>
            </div>
        `;
        conversionResults.appendChild(resultItem);

        // Add event listener to download button
        resultItem.querySelector('.download-single').addEventListener('click', (e) => {
            const url = e.currentTarget.dataset.url;
            const filename = e.currentTarget.dataset.filename;
            downloadImage(url, filename);
        });
    }

    function downloadImage(url, filename) {
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }

    async function downloadAllImages() {
        if (!downloadAllBtn.dataset.images) return;

        try {
            // Show loading state
            downloadAllBtn.disabled = true;
            downloadAllBtn.innerHTML = `
                <svg class="animate-spin h-5 w-5 mr-2 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                </svg>
                Preparing ZIP...
            `;

            const images = JSON.parse(downloadAllBtn.dataset.images);

            // Load JSZip library dynamically
            const JSZip = await loadJSZip();
            const zip = new JSZip();

            // Add each image to the ZIP
            for (const img of images) {
                const blob = await fetch(img.url).then(r => r.blob());
                zip.file(`page_${img.pageNum}.${img.format}`, blob);
            }

            // Generate the ZIP file
            const content = await zip.generateAsync({ type: 'blob' });
            const zipUrl = URL.createObjectURL(content);

            // Download the ZIP
            const a = document.createElement('a');
            a.href = zipUrl;
            a.download = 'converted_pages.zip';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);

            // Clean up
            URL.revokeObjectURL(zipUrl);
        } catch (error) {
            console.error('ZIP creation error:', error);
            alert('Failed to create ZIP file. Please try downloading images individually.');
        } finally {
            // Reset button state
            downloadAllBtn.disabled = false;
            downloadAllBtn.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                </svg>
                Download All as ZIP
            `;
        }
    }

    function loadJSZip() {
        return new Promise((resolve, reject) => {
            if (window.JSZip) {
                resolve(window.JSZip);
                return;
            }

            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
            script.onload = () => resolve(window.JSZip);
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    function cancelConversion() {
        conversionInProgress = false;
        resetConversionUI();
    }

    function resetConversionUI() {
        convertBtn.disabled = selectedPages.size === 0;
        cancelBtn.classList.add('hidden');
        convertSpinner.classList.add('hidden');
        convertBtn.innerHTML = '<span>Convert to JPG</span>';
        conversionInProgress = false;
    }

    function clearPdf() {
        pdfDoc = null;
        totalPages = 0;
        selectedPages.clear();
        pdfPreviewSection.classList.add('hidden');
        convertBtn.disabled = true;
        fileInput.value = '';
    }
</script>