Ghostwriting Assistant

Ghostwriting Assistant

Manage clients, capture their voice, and draft content seamlessly.

Project At a Glance

0 / 1000 words

Client & Project Details

Client Voice Profile

Drafting Space

Word Count: 0

Deadline: ${projectData.deadline ? new Date(projectData.deadline + 'T00:00:00').toLocaleDateString() : 'Not Set'}

`; // Progress Bar const draftWords = projectData.draft.trim().split(/\s+/).filter(Boolean).length; const target = projectData.wordTarget || 1; const progressPercent = Math.min((draftWords / target) * 100, 100); document.getElementById('progress-bar').style.width = `${progressPercent}%`; document.getElementById('progress-text').textContent = `${draftWords} / ${projectData.wordTarget || 0} words`; document.getElementById('draft-word-count').textContent = `Word Count: ${draftWords}`; // Voice Strength const strengthEl = document.getElementById('voice-strength'); let strength = 0; if (projectData.voice.samples.trim()) strength++; if (projectData.voice.tone.trim()) strength++; if (projectData.voice.pace.trim()) strength++; if (projectData.voice.diction.trim()) strength++; strengthEl.innerHTML = Array(4).fill(0).map((_, i) => `
` ).join(''); } // --- DATA BINDING --- function bindInputs() { inputs.title.addEventListener('input', e => { projectData.title = e.target.value; updateDashboard(); }); inputs.client.addEventListener('input', e => { projectData.client = e.target.value; updateDashboard(); }); inputs.audience.addEventListener('input', e => projectData.audience = e.target.value); inputs.messages.addEventListener('input', e => projectData.messages = e.target.value); inputs.wordTarget.addEventListener('input', e => { projectData.wordTarget = parseInt(e.target.value) || 0; updateDashboard(); }); inputs.deadline.addEventListener('input', e => { projectData.deadline = e.target.value; updateDashboard(); }); inputs.voiceSamples.addEventListener('input', e => { projectData.voice.samples = e.target.value; updateDashboard(); }); inputs.voiceTone.addEventListener('input', e => { projectData.voice.tone = e.target.value; updateDashboard(); }); inputs.voicePace.addEventListener('input', e => { projectData.voice.pace = e.target.value; updateDashboard(); }); inputs.voiceDiction.addEventListener('input', e => { projectData.voice.diction = e.target.value; updateDashboard(); }); inputs.draft.addEventListener('input', e => { projectData.draft = e.target.value; updateDashboard(); }); } // --- TAB LOGIC --- let currentTabIndex = 0; const tabs = ['dashboard', 'client', 'voice', 'draft']; function switchTab(tabIndex) { currentTabIndex = tabIndex; tabBtns.forEach((btn, index) => { const tabName = btn.dataset.tab; document.getElementById(`${tabName}-content`).classList.toggle('active', index === tabIndex); btn.classList.toggle('active', index === tabIndex); }); prevTabBtn.disabled = tabIndex === 0; nextTabBtn.disabled = tabIndex === tabs.length - 1; } tabBtns.forEach((button, index) => button.addEventListener('click', () => switchTab(index))); prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); }); nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabs.length - 1) switchTab(currentTabIndex + 1); }); // --- PDF GENERATION --- async function generatePdfReport() { const button = downloadPdfBtn; const originalText = button.textContent; button.disabled = true; button.textContent = 'Generating...'; const reportHtml = `

${projectData.title || 'Project Brief'}

For: ${projectData.client || 'N/A'}

Project Scope

Target Audience
${projectData.audience || 'Not specified'}
Deadline
${projectData.deadline ? new Date(projectData.deadline + 'T00:00:00').toLocaleDateString() : 'Not specified'}
Word Target
${projectData.wordTarget || 'Not specified'}
Key Messages
${projectData.messages.replace(/\n/g, '
') || 'Not specified'}

Client Voice Profile

Tone
${projectData.voice.tone || 'Not specified'}
Pace
${projectData.voice.pace || 'Not specified'}
Diction
${projectData.voice.diction || 'Not specified'}

Writing Sample Snippet:

${projectData.voice.samples.substring(0, 300) + (projectData.voice.samples.length > 300 ? '...' : '') || 'No samples provided.'}

Content Draft

${projectData.draft || 'No draft content written.'}
`; const pdfTemplate = document.getElementById('pdf-template'); pdfTemplate.innerHTML = reportHtml; pdfTemplate.style.position = 'absolute'; pdfTemplate.style.left = '-9999px'; pdfTemplate.classList.remove('invisible'); try { const { jsPDF } = window.jspdf; const canvas = await html2canvas(pdfTemplate.querySelector('.pdf-report-container'), { scale: 2, useCORS: true }); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); pdf.save(`${(projectData.title || 'Project_Brief').replace(/\s+/g, '_')}.pdf`); } catch(e) { console.error('PDF Generation Error:', e); } finally { button.disabled = false; button.textContent = originalText; pdfTemplate.innerHTML = ''; pdfTemplate.classList.add('invisible'); } } downloadPdfBtn.addEventListener('click', generatePdfReport); // --- INITIALIZATION --- switchTab(0); bindInputs(); updateDashboard(); });
Scroll to Top