Historical Timeline Generator
Add and Manage Your Historical Events
Customize Your Timeline
Timeline Preview
My Historical Timeline
Add some events to see your timeline.
`; return; } events.forEach(event => { const eventElement = document.createElement('div'); eventElement.classList.add('htg-timeline-event'); eventElement.innerHTML = `${event.displayDate}
${event.title}
${event.description.replace(/\n/g, '
')}
`;
timelinePreview.appendChild(eventElement);
});
}
[primaryColorInput, textColorInput, bgColorInput, eventBgColorInput, timelineTitleInput].forEach(input => {
if (input) {
input.addEventListener('input', updateTimelinePreview);
if (input.type === 'color') { // Color pickers often use 'change'
input.addEventListener('change', updateTimelinePreview);
}
}
});
if (downloadPdfButton && !downloadPdfButton.disabled) { // Only add listener if not disabled
downloadPdfButton.addEventListener('click', () => {
if (!jsPDF_constructor_for_tool) {
alert("PDF library is not available. Cannot download PDF.");
return;
}
const events = getEventData();
const timelineTitleStr = timelineTitleInput?.value || "Historical Timeline";
if (events.length === 0) {
alert("Please add some events before generating a PDF.");
return;
}
const pdf = new jsPDF_constructor_for_tool({
orientation: 'p',
unit: 'mm',
format: 'a4'
});
const primaryColor = primaryColorInput?.value || '#3498DB';
const textColor = textColorInput?.value || '#333333';
const pageBgColor = bgColorInput?.value || '#FFFFFF';
pdf.setFillColor(pageBgColor);
pdf.rect(0, 0, pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight(), 'F');
const margin = 15;
const contentWidth = pdf.internal.pageSize.getWidth() - 2 * margin;
let currentY = margin;
const lineSpacing = 4;
const titleFontSize = 18;
const dateFontSize = 9;
const eventTitleFontSize = 13;
const descriptionFontSize = 10;
const timelineLineX = margin + 4;
const eventContentX = timelineLineX + 8;
const eventDotRadius = 1.5;
pdf.setFontSize(titleFontSize);
pdf.setTextColor(primaryColor);
const titleTextLines = pdf.splitTextToSize(timelineTitleStr, contentWidth);
pdf.text(titleTextLines, pdf.internal.pageSize.getWidth() / 2, currentY, { align: 'center', maxWidth: contentWidth });
currentY += (titleTextLines.length * titleFontSize * 0.7) + lineSpacing * 2;
let pageFirstEventY = currentY; // Y position for the start of timeline line on current page
events.forEach((event, index) => {
const eventBlockStartY = currentY;
let eventBlockHeight = 0;
// Calculate space needed for this event for page break logic
let tempYCalc = 0;
tempYCalc += dateFontSize * 0.7 + lineSpacing / 2;
const titleLinesCalc = pdf.splitTextToSize(event.title, contentWidth - (eventContentX - margin));
tempYCalc += (titleLinesCalc.length * eventTitleFontSize * 0.7) + lineSpacing / 2;
if (event.description) {
const descriptionLinesCalc = pdf.splitTextToSize(event.description, contentWidth - (eventContentX - margin));
tempYCalc += (descriptionLinesCalc.length * descriptionFontSize * 0.7);
}
tempYCalc += lineSpacing * 1.5;
eventBlockHeight = tempYCalc;
if (currentY + eventBlockHeight > pdf.internal.pageSize.getHeight() - margin) {
// Draw timeline line for current page before adding new page
pdf.setDrawColor(primaryColor);
pdf.setLineWidth(0.8);
pdf.line(timelineLineX, pageFirstEventY - (dateFontSize*0.7), timelineLineX, currentY - lineSpacing); // End line above last item's bottom spacing
pdf.addPage();
currentY = margin;
pageFirstEventY = currentY; // Reset for new page
pdf.setFillColor(pageBgColor);
pdf.rect(0, 0, pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight(), 'F');
}
// Event Display Date
pdf.setFontSize(dateFontSize);
pdf.setTextColor(textColor);
pdf.text(event.displayDate, eventContentX, currentY);
currentY += dateFontSize * 0.7 + lineSpacing / 2;
// Event Title
pdf.setFontSize(eventTitleFontSize);
pdf.setTextColor(primaryColor);
const renderedTitleLines = pdf.splitTextToSize(event.title, contentWidth - (eventContentX - margin));
pdf.text(renderedTitleLines, eventContentX, currentY);
currentY += (renderedTitleLines.length * eventTitleFontSize * 0.7) + lineSpacing / 2;
// Event Description
if (event.description) {
pdf.setFontSize(descriptionFontSize);
pdf.setTextColor(textColor);
const renderedDescLines = pdf.splitTextToSize(event.description, contentWidth - (eventContentX - margin));
pdf.text(renderedDescLines, eventContentX, currentY);
currentY += (renderedDescLines.length * descriptionFontSize * 0.7);
}
pdf.setFillColor(primaryColor);
pdf.setDrawColor(primaryColor); // For stroke of circle
pdf.circle(timelineLineX, eventBlockStartY + (dateFontSize * 0.7 / 2) , eventDotRadius, 'FD');
currentY += lineSpacing * 1.5;
});
// Draw timeline line for the last (or only) page
pdf.setDrawColor(primaryColor);
pdf.setLineWidth(0.8);
if (events.length > 0) { // Only draw line if there are events
pdf.line(timelineLineX, pageFirstEventY - (dateFontSize*0.7) , timelineLineX, currentY - lineSpacing * 1.5); // Adjusted end of line
}
let safeTitle = timelineTitleStr.replace(/[^a-z0-9]/gi, '_').toLowerCase() || "historical_timeline";
if (safeTitle.length > 50) safeTitle = safeTitle.substring(0, 50);
pdf.save(safeTitle + '.pdf');
});
}
// Initialize
if (addEventButton) {
addEventButton.click(); // Add one event block by default
}
switchTab('events'); // Set initial tab and apply styles
updateTimelinePreview(); // Initial preview render if needed (though events tab is first)
});
')}
