Short Story Outline Creator
Bring your story to life by building a solid foundation.
Protagonist
Antagonist
Your generated story outline will appear here once you complete the previous steps.
${outlineData.resolution}
`; const pdfContentDiv = document.getElementById('pdf-content'); if (pdfContentDiv) { pdfContentDiv.innerHTML = outlineHTML; } } // Event Listeners for Tab buttons tabs.forEach((tab, index) => { tab.addEventListener('click', () => updateTabUI(index)); }); // Event Listener for Next Button nextBtn.addEventListener('click', () => { if (currentTab < totalTabs - 1) { if (currentTab === totalTabs - 2) { generateOutline(); } updateTabUI(currentTab + 1); } }); // Event Listener for Previous Button prevBtn.addEventListener('click', () => { if (currentTab > 0) { updateTabUI(currentTab - 1); } }); // Event Listener for PDF Download Button downloadPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); // --- Helper function to get form values --- const getValue = id => document.getElementById(id)?.value.trim() || 'Not Provided'; // --- Collect all data --- const outlineData = { genre: getValue('genre'), theme: getValue('theme'), logline: getValue('logline'), protagonistName: getValue('protagonist-name'), protagonistGoal: getValue('protagonist-goal'), protagonistConflict: getValue('protagonist-conflict'), antagonistName: getValue('antagonist-name'), antagonistGoal: getValue('antagonist-goal'), settingLocation: getValue('setting-location'), settingTime: getValue('setting-time'), settingAtmosphere: getValue('setting-atmosphere'), incitingIncident: getValue('plot-inciting-incident'), risingAction: getValue('plot-rising-action'), climax: getValue('plot-climax'), fallingAction: getValue('plot-falling-action'), resolution: getValue('plot-resolution') }; // --- PDF Document Styling and Layout Variables --- const pageW = doc.internal.pageSize.getWidth(); const pageH = doc.internal.pageSize.getHeight(); const margin = 20; const usableWidth = pageW - margin * 2; let y = 0; // Current Y position const indigoColor = '#4F46E5'; const darkGrayColor = '#374151'; const lightGrayColor = '#6B7280'; // --- PDF Generation Helper Functions --- // Function to add page footer with page numbers const addFooter = () => { const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(10); doc.setTextColor(lightGrayColor); const footerText = `Short Story Outline | Page ${i} of ${pageCount}`; doc.text(footerText, pageW / 2, pageH - 10, { align: 'center' }); } }; // Function to check if a new page is needed and add it const checkPageBreak = (heightNeeded) => { if (y + heightNeeded >= pageH - margin) { doc.addPage(); y = margin; } }; // Function to add a main section header const addSectionHeader = (text) => { checkPageBreak(20); y += 12; doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.setTextColor(indigoColor); doc.text(text, margin, y); y += 4; doc.setDrawColor(indigoColor); doc.line(margin, y, margin + 40, y); y += 8; }; // Function to add a sub-header (like for characters) const addSubHeader = (text) => { checkPageBreak(10); y += 6; doc.setFontSize(13); doc.setFont(undefined, 'bold'); doc.setTextColor(darkGrayColor); doc.text(text, margin, y); y += 6; }; // Function to add content (label + value) with text wrapping const addContent = (label, value) => { doc.setFontSize(11); doc.setFont(undefined, 'bold'); doc.setTextColor(darkGrayColor); // Split long values into manageable lines const valueLines = doc.splitTextToSize(value, usableWidth - 15); const heightNeeded = (valueLines.length * 5) + 5; checkPageBreak(heightNeeded); doc.text(label, margin, y); doc.setFont(undefined, 'normal'); doc.setTextColor(lightGrayColor); doc.text(valueLines, margin + 1, y + 5); y += heightNeeded; }; // --- Build the PDF Document --- // 1. Add Main Title y = margin + 10; doc.setFontSize(24); doc.setFont(undefined, 'bold'); doc.setTextColor(darkGrayColor); doc.text('Short Story Outline Report', pageW / 2, y, { align: 'center' }); y += 15; // 2. Core Concept Section addSectionHeader('Core Concept'); addContent('Genre:', outlineData.genre); addContent('Central Theme:', outlineData.theme); addContent('Logline / Premise:', outlineData.logline); // 3. Characters Section addSectionHeader('Characters'); addSubHeader(`Protagonist: ${outlineData.protagonistName}`); addContent('Goal / Motivation:', outlineData.protagonistGoal); addContent('Internal / External Conflict:', outlineData.protagonistConflict); y += 5; // Add a little space addSubHeader(`Antagonist: ${outlineData.antagonistName}`); addContent('Goal / Motivation:', outlineData.antagonistGoal); // 4. Setting Section addSectionHeader('Setting'); addContent('Primary Location:', outlineData.settingLocation); addContent('Time Period:', outlineData.settingTime); addContent('Mood & Atmosphere:', outlineData.settingAtmosphere); // 5. Plot Structure Section addSectionHeader('Plot Structure'); addContent('Inciting Incident:', outlineData.incitingIncident); addContent('Rising Action (Key Events):', outlineData.risingAction); addContent('Climax:', outlineData.climax); addContent('Falling Action:', outlineData.fallingAction); addContent('Resolution:', outlineData.resolution); // --- Finalize and Save PDF --- addFooter(); doc.save('Short-Story-Outline-Report.pdf'); }); // Initial UI setup updateTabUI(0); lucide.createIcons(); });