`;
});
html += `
`;
});
outputContainer.innerHTML = html;
pdfDownloadBtn.classList.remove('hidden');
};
/**
* A fetch wrapper with exponential backoff for retrying failed requests.
*/
async function fetchWithExponentialBackoff(url, options, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url, options);
if (response.status === 429 || response.status >= 500) {
throw new Error(`Retryable error: ${response.status}`);
}
return response;
} catch (error) {
attempt++;
if (attempt >= maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
/**
* Generates and downloads a well-formatted, multi-page PDF.
*/
const downloadPdf = () => {
if (loadingOverlay) loadingOverlay.style.display = 'flex';
try {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' });
const pageHeight = doc.internal.pageSize.getHeight();
const margin = 40;
let y = margin;
const addText = (text, options) => {
const fontSize = options.fontSize || 10;
const lineHeight = fontSize * 1.2;
doc.setFont(options.font || 'helvetica', options.fontStyle || 'normal');
doc.setFontSize(fontSize);
if (options.color) doc.setTextColor(options.color[0], options.color[1], options.color[2]);
const lines = doc.splitTextToSize(text, doc.internal.pageSize.getWidth() - margin * 2);
if (y + (lines.length * lineHeight) > pageHeight - margin) {
doc.addPage();
y = margin;
}
doc.text(lines, margin, y);
y += (lines.length * lineHeight) + (options.marginBottom || 0);
};
addText(`Phrasebook: ${sourceLangEl.value} to ${targetLangEl.value}`, { fontSize: 22, fontStyle: 'bold', marginBottom: 20 });
const categories = outputContainer.querySelectorAll('h3');
categories.forEach(catEl => {
addText(catEl.innerText, { fontSize: 16, fontStyle: 'bold', color: [79, 70, 229], marginBottom: 10 });
const phrases = catEl.nextElementSibling.children;
for(const phraseEl of phrases) {
const sourceText = phraseEl.children[0].innerText;
const translatedText = phraseEl.children[1].innerText;
addText(sourceText, { fontSize: 10, color: [55, 65, 81] });
addText(translatedText, { fontSize: 10, color: [37, 99, 235], marginBottom: 8 });
}
});
doc.save(`Phrasebook-${sourceLangEl.value}-to-${targetLangEl.value}.pdf`);
} catch (error) {
console.error("PDF Generation Error:", error);
} finally {
if (loadingOverlay) loadingOverlay.style.display = 'none';
}
};
// IV.A.2: Attach primary event listeners
generateBtn.addEventListener('click', generatePhrasebook);
pdfDownloadBtn.addEventListener('click', downloadPdf);
// Initial setup
populateCategories();
});
