${result.name}
${fmtSize(result.originalSize)} → ${fmtSize(result.webpBlob.size)} ${isPos ? '▼ ' : '▲ '}${Math.abs(saving)}%
`; resultsGrid.appendChild(card); } function showSummary() { const totalOrig = results.reduce((a, r) => a + r.originalSize, 0); const totalWebp = results.reduce((a, r) => a + r.webpBlob.size, 0); const saved = ((1 - totalWebp / totalOrig) * 100).toFixed(1); resultsSummary.innerHTML = `
${results.length} file(s) ${fmtSize(totalOrig)} → ${fmtSize(totalWebp)} ${saved > 0 ? '▼ ' : '▲ '}${Math.abs(saved)}% total `; } /* ══════════════════════════════════════════════ DOWNLOAD ══════════════════════════════════════════════ */ function downloadSingle(idx) { const r = results[idx]; const url = URL.createObjectURL(r.webpBlob); triggerDownload(url, r.name); setTimeout(() => URL.revokeObjectURL(url), 5000); } downloadAllBtn.addEventListener('click', async () => { if (!results.length) return; if (results.length === 1) { downloadSingle(0); return; } // Use JSZip CDN for multi-file zip showToast('📦 Preparing ZIP download…'); try { const JSZip = await loadJSZip(); const zip = new JSZip(); results.forEach(r => zip.file(r.name, r.webpBlob)); const blob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 1 } }); triggerDownload(URL.createObjectURL(blob), 'webp-images.zip'); showToast('✅ ZIP downloaded!'); } catch { // Fallback: download individually for (let i = 0; i < results.length; i++) { await delay(300 * i); downloadSingle(i); } } }); function triggerDownload(url, name) { const a = document.createElement('a'); a.href = url; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function loadJSZip() { return new Promise((resolve, reject) => { if (window.JSZip) { resolve(window.JSZip); return; } const s = document.createElement('script'); s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; s.onload = () => resolve(window.JSZip); s.onerror = reject; document.head.appendChild(s); }); } /* ══════════════════════════════════════════════ RESET ══════════════════════════════════════════════ */ resetBtn.addEventListener('click', () => { files = []; results = []; resultsGrid.innerHTML = ''; resultsSection.classList.remove('active'); progressWrap.classList.remove('active'); fileInput.value = ''; emptyState.style.display = 'block'; convertBtn.disabled = true; showToast('🔄 Reset complete.'); }); /* ══════════════════════════════════════════════ HELPERS ══════════════════════════════════════════════ */ function fmtSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024*1024) return (bytes/1024).toFixed(1) + ' KB'; return (bytes/(1024*1024)).toFixed(2) + ' MB'; } function delay(ms) { return new Promise(r => setTimeout(r, ms)); } let toastTimeout; function showToast(msg, success = true) { const toast = document.getElementById('toast'); const icon = document.getElementById('toastIcon'); const text = document.getElementById('toastMsg'); icon.textContent = success ? '✅' : '⚠️'; text.textContent = msg; toast.classList.add('show'); clearTimeout(toastTimeout); toastTimeout = setTimeout(() => toast.classList.remove('show'), 3500); } // Expose for inline onclick window.downloadSingle = downloadSingle;