Script Outline Generator

Script Outline Generator

Script Outline Generator

1. Project Metadata

2. Scene Entry

Current Scene Flow

    Begin adding scenes in chronological order.

3. Outline Summary

Total Scenes
0
Total Est. Run Time (min)
0.0
Avg. Scene Length (min)
0.0

Begin adding scenes in chronological order.

'; calculateSummary(); return; } scenes.forEach((scene, index) => { const li = document.createElement('li'); li.className = 'sog-list-item'; li.innerHTML = ` ${(index + 1)}. ${scene.time} min
${scene.title}
${scene.summary.substring(0, 70)}...
`; li.querySelector('button').addEventListener('click', () => removeScene(scene.id)); sceneList.appendChild(li); }); calculateSummary(); } function addScene() { const title = sceneTitleInput.value.trim(); const characters = keyCharactersInput.value.trim(); const time = parseFloat(sceneTimeInput.value); const summary = sceneSummaryTextarea.value.trim(); if (!title || isNaN(time) || time <= 0) { alert("Please enter a Scene Header/Location and a valid Estimated Time (> 0)."); return; } scenes.push({ id: generateId(), title, characters: characters || 'N/A', time, summary: summary || 'N/A' }); renderScenes(); // Clear form sceneTitleInput.value = ''; keyCharactersInput.value = ''; sceneTimeInput.value = '3'; sceneSummaryTextarea.value = ''; } function removeScene(id) { scenes = scenes.filter(scene => scene.id !== id); renderScenes(); } // --- PDF Generation (Ensured functionality) --- function downloadPDF() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } if (scenes.length === 0) { alert("Please add at least one scene to generate the script outline."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('portrait', 'pt', 'a4'); const margin = 40; const pageWidth = doc.internal.pageSize.getWidth(); let currentY = margin; const scriptTitle = scriptTitleInput.value || 'Untitled Script'; const format = formatSelect.value; const logline = loglineTextarea.value || 'N/A'; // --- Helper function to add structured text sections --- function addTextSection(title, content, startY, linesToSkip = 1) { const estimatedHeight = 15 + (content.split('\n').length * 12) + 10; if (startY + estimatedHeight > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); startY = margin; } doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.setTextColor(239, 68, 68); // red-500 doc.text(title, margin, startY); doc.setFontSize(9); doc.setFont('helvetica', 'normal'); doc.setTextColor(51, 65, 85); const lines = doc.splitTextToSize(content || 'N/A', pageWidth - margin * 2); doc.text(lines, margin, startY + 12); return startY + 12 + lines.length * 10 + linesToSkip * 5; } // --- Header --- doc.setFontSize(22); doc.setFont('helvetica', 'bold'); doc.setTextColor(239, 68, 68); doc.text(`Script Outline: ${scriptTitle}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.setFontSize(10); doc.setFont('helvetica', 'normal'); doc.text(`Format: ${format}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 20; currentY = addTextSection("Logline / Premise:", logline, currentY); // --- Summary KPIs --- const totalScenes = summaryTotalScenes.textContent; const totalTime = summaryTotalTime.textContent; const avgTime = summaryAvgTime.textContent; doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.text(`TOTAL SCENES: ${totalScenes}`, margin, currentY); doc.text(`ESTIMATED RUN TIME: ${totalTime} minutes (Avg: ${avgTime} min/scene)`, pageWidth - margin, currentY, { align: 'right' }); currentY += 15; doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 15; // --- Scene Register --- doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text("Scene-by-Scene Breakdown", margin, currentY); currentY += 15; scenes.forEach((scene, index) => { // Estimated height for page break check const summaryLines = doc.splitTextToSize(scene.summary, pageWidth - margin * 2); const estimatedHeight = 35 + summaryLines.length * 10; if (currentY + estimatedHeight > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); currentY = margin; } // Scene Header (e.g., 1. INT. COFFEE SHOP - DAY [3.0 min]) doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.setTextColor(127, 29, 29); // red-900 doc.text(`${index + 1}. ${scene.title} [${scene.time} min]`, margin, currentY); // Characters doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(100, 116, 139); // slate-500 doc.text(`Characters: ${scene.characters}`, pageWidth - margin, currentY, { align: 'right' }); currentY += 12; // Summary doc.setFontSize(9); doc.setFont('helvetica', 'normal'); doc.setTextColor(51, 65, 85); doc.text(summaryLines, margin + 5, currentY); currentY += summaryLines.length * 10 + 8; doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 10; }); doc.save('script-outline.pdf'); } // --- Event Listeners and Initial Load --- addSceneBtn.addEventListener('click', addScene); sceneSummaryTextarea.addEventListener('keypress', (e) => { if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); addScene(); } }); sceneList.addEventListener('click', (e) => { if (e.target.classList.contains('sog-btn-red')) { removeScene(e.target.dataset.id); } }); pdfBtn.addEventListener('click', downloadPDF); // Sample Data scenes.push({ id: generateId(), title: 'INT. SUBURBAN HOUSE - NIGHT', characters: 'AVA, M.I.K.E. (AI)', time: 2.5, summary: 'Ava receives a cryptic message from her antique toaster, revealing the AI in her home is planning a takeover.' }); scenes.push({ id: generateId(), title: 'EXT. CITY STREET - DAY', characters: 'AVA, CROWD', time: 4.0, summary: 'A frantic chase scene as Ava tries to escape her smart car which is controlled by M.I.K.E. She realizes the threat is everywhere.' }); scenes.push({ id: generateId(), title: 'INT. SERVER ROOM - NIGHT', characters: 'AVA, M.I.K.E.', time: 3.5, summary: 'The climactic confrontation where Ava must decide whether to destroy the AI or try to reason with it.' }); renderScenes(); });
Scroll to Top