`;
/* Card drag-sort events */ card.addEventListener('dragstart', () => { dragSrcId = item.id; card.classList.add('dragging-card'); });
card.addEventListener('dragend', () => { dragSrcId = null; card.classList.remove('dragging-card'); document.querySelectorAll('.drag-target').forEach(el => el.classList.remove('drag-target')); });
card.addEventListener('dragover', e => { e.preventDefault(); card.classList.add('drag-target'); });
card.addEventListener('dragleave', () => card.classList.remove('drag-target'));
card.addEventListener('drop', e => {
e.preventDefault();
card.classList.remove('drag-target');
if (!dragSrcId || dragSrcId === item.id) return;
const srcIdx = files.findIndex(f => f.id === dragSrcId);
const destIdx = files.findIndex(f => f.id === item.id);
if (srcIdx < 0 || destIdx < 0) return;
const [moved] = files.splice(srcIdx, 1);
files.splice(destIdx, 0, moved);
renderList();
});
fileListEl.appendChild(card);
});
/* Button events */ fileListEl.querySelectorAll('[data-action]').forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const act = btn.dataset.action;
const idx = files.findIndex(f => f.id === id);
if (act === 'remove') {
files.splice(idx, 1);
renderList();
} else if (act === 'up' && idx > 0) {
[files[idx - 1], files[idx]] = [files[idx], files[idx - 1]];
renderList();
} else if (act === 'down' && idx < files.length - 1) {
[files[idx], files[idx + 1]] = [files[idx + 1], files[idx]];
renderList();
}
});
});
}
/* Clear all */ document.getElementById('clear-all').addEventListener('click', () => {
files.length = 0;
renderList();
});
/* ======================================
MERGE
====================================== */ mergeBtn.addEventListener('click', mergePDFs);
async function mergePDFs() {
if (files.length < 2) { toast('Add at least 2 PDF files to merge.', 'error'); return; }
const progressWrap = document.getElementById('progress-wrap');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const progressPct = document.getElementById('progress-pct');
mergeBtn.disabled = true;
progressWrap.classList.add('active');
setProgress(0, 'Reading files…');
try {
const merged = await PDFLib.PDFDocument.create();
for (let i = 0; i < files.length; i++) {
const pct = Math.round(((i) / files.length) * 85);
setProgress(pct, `Merging ${i + 1} of ${files.length}: ${files[i].name}`);
const buf = await files[i].file.arrayBuffer();
const doc = await PDFLib.PDFDocument.load(buf, { ignoreEncryption: true });
const pageIndices = doc.getPageIndices();
const copied = await merged.copyPages(doc, pageIndices);
copied.forEach(p => merged.addPage(p));
}
setProgress(90, 'Finalising PDF…');
const bytes = await merged.save();
setProgress(100, 'Done!');
const outputName = (document.getElementById('output-name').value.trim() || 'merged-document') + '.pdf';
triggerDownload(bytes, outputName);
toast(`✅ Merged ${files.length} files → ${outputName}`, 'success');
setTimeout(() => {
progressWrap.classList.remove('active');
mergeBtn.disabled = false;
setProgress(0, 'Processing…');
}, 2500);
} catch (err) {
console.error(err);
toast('Merge failed: ' + (err.message || 'Unknown error'), 'error');
progressWrap.classList.remove('active');
mergeBtn.disabled = false;
}
}
function setProgress(pct, label) {
document.getElementById('progress-bar').style.width = pct + '%';
document.getElementById('progress-text').textContent = label;
document.getElementById('progress-pct').textContent = pct + '%';
}
function triggerDownload(bytes, filename) {
const blob = new Blob([bytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 5000);
}
/* ======================================
UTILITIES
====================================== */ function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(2) + ' MB';
}
function escHtml(str) {
return str.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
}
function toast(msg, type = 'success') {
const el = document.createElement('div');
el.className = `toast ${type}`;
el.innerHTML = `
${type === 'success' ? '✅' : '⚠️'}${escHtml(msg)}`;
const container = document.getElementById('toast-container');
container.appendChild(el);
setTimeout(() => {
el.style.animation = 'toastOut .3s ease forwards';
setTimeout(() => el.remove(), 350);
}, 4000);
}