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) });
Scroll to Top