Novel Chapter Planner
Organize your story, one chapter at a time.
Your Chapters
Add New Chapter
Confirm Deletion
Are you sure you want to delete this chapter? This action cannot be undone.
${chapter.synopsis || 'No synopsis provided.'}
`; chaptersList.appendChild(chapterCard); }); }; // --- Form Handling --- const resetAndShowForm = (mode = 'add', chapter = null) => { form.reset(); document.getElementById('chapterId').value = ''; if (mode === 'add') { formTitle.textContent = 'Add New Chapter'; } else if (mode === 'edit' && chapter) { formTitle.textContent = 'Edit Chapter'; document.getElementById('chapterId').value = chapter.id; document.getElementById('chapterTitle').value = chapter.title; document.getElementById('povCharacter').value = chapter.pov; document.getElementById('sceneSynopsis').value = chapter.synopsis; document.getElementById('charactersInvolved').value = chapter.characters; document.getElementById('settingLocation').value = chapter.setting; document.getElementById('plotPoints').value = chapter.plotPoints; document.getElementById('authorNotes').value = chapter.notes; } switchTab('details'); }; addNewChapterBtn.addEventListener('click', () => resetAndShowForm('add')); cancelBtn.addEventListener('click', () => switchTab('planner')); form.addEventListener('submit', (e) => { e.preventDefault(); const chapterData = { id: document.getElementById('chapterId').value || `ch-${Date.now()}`, title: document.getElementById('chapterTitle').value, pov: document.getElementById('povCharacter').value, synopsis: document.getElementById('sceneSynopsis').value, characters: document.getElementById('charactersInvolved').value, setting: document.getElementById('settingLocation').value, plotPoints: document.getElementById('plotPoints').value, notes: document.getElementById('authorNotes').value, }; const existingIndex = chapters.findIndex(c => c.id === chapterData.id); if (existingIndex > -1) { chapters[existingIndex] = chapterData; // Update } else { chapters.push(chapterData); // Add } renderChapters(); switchTab('planner'); }); // --- Delete Logic with Modal --- const showDeleteModal = (id) => { chapterToDeleteId = id; deleteModal.classList.remove('hidden'); setTimeout(() => { deleteModal.classList.remove('opacity-0'); deleteModal.querySelector('.modal-container').classList.remove('scale-95'); }, 10); }; const hideDeleteModal = () => { deleteModal.classList.add('opacity-0'); deleteModal.querySelector('.modal-container').classList.add('scale-95'); setTimeout(() => { deleteModal.classList.add('hidden'); chapterToDeleteId = null; }, 300); }; chaptersList.addEventListener('click', e => { const editBtn = e.target.closest('.edit-btn'); if (editBtn) { const chapterId = editBtn.dataset.id; const chapter = chapters.find(c => c.id === chapterId); if (chapter) resetAndShowForm('edit', chapter); } const deleteBtn = e.target.closest('.delete-btn'); if (deleteBtn) { showDeleteModal(deleteBtn.dataset.id); } }); modalCancelBtn.addEventListener('click', hideDeleteModal); modalConfirmBtn.addEventListener('click', () => { if (chapterToDeleteId) { chapters = chapters.filter(c => c.id !== chapterToDeleteId); renderChapters(); } hideDeleteModal(); }); // --- PDF Download Logic --- downloadPdfBtn.addEventListener('click', () => { if (typeof window.jspdf === 'undefined') { console.error('jsPDF library is not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const novelTitle = novelTitleInput.value.trim() || 'My Novel'; const reportTitle = `Chapter Plan: ${novelTitle}`; const generationDate = new Date().toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const margin = 15; const pageWidth = doc.internal.pageSize.getWidth(); const pageHeight = doc.internal.pageSize.getHeight(); const usableWidth = pageWidth - margin * 2; let y = margin; let pageNumber = 1; const addHeader = () => { doc.setFont('helvetica', 'bold'); doc.setFontSize(22); doc.setTextColor(45, 55, 72); doc.text(reportTitle, pageWidth / 2, y + 10, { align: 'center' }); y += 25; }; const addFooter = () => { doc.setFont('helvetica', 'italic'); doc.setFontSize(9); doc.setTextColor(150); doc.text(`Page ${pageNumber}`, pageWidth / 2, pageHeight - 10, { align: 'center' }); }; const addPageBreak = () => { addFooter(); doc.addPage(); pageNumber++; y = margin; addFooter(); }; const addSection = (title, content) => { if (!content || content.trim() === '') return; if (y > pageHeight - 40) addPageBreak(); doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.setTextColor(60, 70, 80); doc.text(title, margin, y); y += 6; doc.setFont('helvetica', 'normal'); doc.setFontSize(10); doc.setTextColor(80, 80, 80); const textLines = doc.splitTextToSize(content, usableWidth); doc.text(textLines, margin, y); y += (textLines.length * 4) + 6; }; // --- Start PDF Generation --- addHeader(); addFooter(); chapters.forEach((chapter, index) => { const chapterBlockHeight = 10 + (doc.splitTextToSize(chapter.synopsis || '', usableWidth).length * 4) + (doc.splitTextToSize(chapter.characters || '', usableWidth).length * 4) + (doc.splitTextToSize(chapter.setting || '', usableWidth).length * 4) + (doc.splitTextToSize(chapter.plotPoints || '', usableWidth).length * 4) + (doc.splitTextToSize(chapter.notes || '', usableWidth).length * 4) + 40; if (y + chapterBlockHeight > pageHeight - margin) addPageBreak(); doc.setFont('helvetica', 'bold'); doc.setFontSize(16); doc.setTextColor(41, 128, 185); doc.text(chapter.title || `Chapter ${index + 1}`, margin, y); y += 10; addSection('Point of View:', chapter.pov); addSection('Synopsis:', chapter.synopsis); addSection('Characters:', chapter.characters); addSection('Setting:', chapter.setting); addSection('Key Plot Points:', chapter.plotPoints); addSection('Notes:', chapter.notes); if (index < chapters.length - 1) { doc.setDrawColor(220, 220, 220); doc.setLineWidth(0.2); doc.line(margin, y, pageWidth - margin, y); y += 8; } }); doc.save(`${novelTitle.toLowerCase().replace(/\s/g, '-')}-plan.pdf`); }); // --- Initial Setup --- const loadSampleData = () => { chapters = [{ id: `ch-${Date.now()}`, title: 'Chapter 1: The Unexpected Guest', pov: 'Johnathan "John" Reed', synopsis: 'On a stormy night in rural Vermont, a reclusive author, John Reed, is interrupted by a mysterious stranger seeking shelter. The stranger carries an old, leather-bound book that seems to cause the storm to worsen whenever it is opened.', characters: 'Johnathan Reed, The Stranger (Elias)', setting: 'A secluded cabin in the Green Mountains, Vermont, USA', plotPoints: 'Introduction of the protagonist and the inciting incident (arrival of the stranger). The mysterious book is introduced as a key plot device.', notes: 'Establish a sense of isolation and foreboding. The dialogue between John and Elias should be tense and filled with subtext.' }]; renderChapters(); }; loadSampleData(); updateNavButtons(); });