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.'}
Generated on ${new Date().toLocaleDateString("en-US")} by Ghostwriting Assistant
