Your Agency
Enter password to continue
License activation
Activate your
suite.
Enter your purchase email to activate. Takes 5 seconds — done once, remembered forever.
Verifying with Stripe…
Can't find your purchase? Email [email protected]
All Documents
Block Library
Cover Page
Divider
Gold Rule
HHeading
hSubheading
Body Text
"Testimonial
Services Grid
$Pricing Table
Metrics / KPIs
Timeline
CTA Block
Signature
AI Write SectionClaude
Canvas is empty
Click a block from the palette on the left,
or start from a template.
Properties
◈ AI Assistant
Document Title
Client Name
Prepared By
Date
Valid Until / Due Date
Document Settings
Total Value ($)
Notes (internal)
Click any template to load it into the builder
Loading…
Preview: Document
`); w.document.close(); setTimeout(()=>{ w.print(); },500); toast('Opening print dialog…','gold'); } // ══════════════════════════════════════ // TEMPLATES // ══════════════════════════════════════ const TEMPLATES = [ { id:'t1', name:'Agency Proposal', type:'proposal', desc:'Full-service digital agency proposal with cover, services grid, pricing table, timeline, and signature blocks.', tags:['Cover','Services','Pricing','Timeline','Signature'], blocks:[ {id:'bt1',type:'cover',data:{title:'Digital Services Proposal',sub:'Comprehensive digital strategy & execution',client:'[Client Name]',date:fmtDate(today())}}, {id:'bt2',type:'divider',data:{}}, {id:'bt3',type:'heading',data:{text:'Executive Summary'}}, {id:'bt4',type:'text',data:{text:'[Your Agency] is pleased to present this proposal for a comprehensive digital engagement. Our team will deliver measurable growth through targeted strategy, expert execution, and data-driven optimization.'}}, {id:'bt5',type:'services',data:{items:[{name:'Strategy & Discovery',price:'$2,000',desc:'Market research, competitive analysis, roadmap'},{name:'Website Development',price:'$4,500',desc:'Custom design & development'},{name:'Content & SEO',price:'$1,200/mo',desc:'Blog, landing pages, optimization'},{name:'Paid Advertising',price:'$1,500/mo',desc:'Google, Meta, LinkedIn campaigns'}]}}, {id:'bt6',type:'divider-gold',data:{}}, {id:'bt7',type:'heading',data:{text:'Investment'}}, {id:'bt8',type:'pricing',data:{rows:[['Strategy & Discovery','1x','$2,000'],['Website Development','1x','$4,500'],['Monthly Retainer','1 mo.','$2,700']],total:'$9,200'}}, {id:'bt9',type:'heading',data:{text:'Project Timeline'}}, {id:'bt10',type:'timeline',data:{phases:[{label:'1',title:'Discovery',desc:'Kick-off, research, and strategy definition',dur:'Week 1–2'},{label:'2',title:'Design & Build',desc:'Wireframes, design, development',dur:'Week 3–7'},{label:'3',title:'Launch',desc:'QA, go-live, and handoff',dur:'Week 8'}]}}, {id:'bt11',type:'cta',data:{title:'Ready to grow?',sub:'Letuis turn your goals into results.',btn:'Accept Proposal'}}, {id:'bt12',type:'signature',data:{prepBy:'[Your Agency]',prepRole:'Lead Strategist',clientName:'[Client Name]',clientRole:'Authorized Signatory'}}, ] }, { id:'t2', name:'Statement of Work', type:'sow', desc:'Detailed SOW template with deliverables, timeline, payment schedule, and legal signature section.', tags:['Cover','Scope','Timeline','Pricing','Signature'], blocks:[ {id:'bs1',type:'cover',data:{title:'Statement of Work',sub:'Project scope, deliverables & terms',client:'[Client Name]',date:fmtDate(today())}}, {id:'bs2',type:'heading',data:{text:'Project Scope'}}, {id:'bs3',type:'text',data:{text:'This Statement of Work defines the scope, deliverables, timeline, and compensation for services provided by [Your Agency] to the Client. All work outside this scope will require a separate written agreement.'}}, {id:'bs4',type:'heading',data:{text:'Deliverables'}}, {id:'bs5',type:'services',data:{items:[{name:'Deliverable 1',price:'Week 2',desc:'Description of first deliverable'},{name:'Deliverable 2',price:'Week 4',desc:'Description of second deliverable'},{name:'Deliverable 3',price:'Week 6',desc:'Description of third deliverable'},{name:'Final Handoff',price:'Week 8',desc:'All assets, access, and documentation'}]}}, {id:'bs6',type:'heading',data:{text:'Timeline & Milestones'}}, {id:'bs7',type:'timeline',data:{phases:[{label:'1',title:'Kick-off',desc:'Project start, access, and onboarding',dur:'Day 1'},{label:'2',title:'Mid-point Review',desc:'Check-in and feedback round',dur:'Week 4'},{label:'3',title:'Final Delivery',desc:'All deliverables submitted',dur:'Week 8'}]}}, {id:'bs8',type:'heading',data:{text:'Payment Schedule'}}, {id:'bs9',type:'pricing',data:{rows:[['50% Deposit','Upon signing','$0'],['50% Balance','Upon delivery','$0']],total:'$0'}}, {id:'bs10',type:'signature',data:{prepBy:'[Your Agency]',prepRole:'Authorized Representative',clientName:'[Client Name]',clientRole:'Authorized Signatory'}}, ] }, { id:'t3', name:'Monthly Report', type:'report', desc:'Client-facing monthly performance report with key metrics, results summary, and next steps.', tags:['Cover','Metrics','Results','Next Steps'], blocks:[ {id:'br1',type:'cover',data:{title:'Monthly Performance Report',sub:'Results & insights for the period',client:'[Client Name]',date:fmtDate(today())}}, {id:'br2',type:'heading',data:{text:'Key Performance Metrics'}}, {id:'br3',type:'metrics',data:{items:[{val:'0%',lbl:'Growth'},{val:'$0',lbl:'Revenue'},{val:'0',lbl:'Leads'}]}}, {id:'br4',type:'heading',data:{text:'Summary'}}, {id:'br5',type:'text',data:{text:'This month, we focused on expanding reach and improving conversion rates. Key initiatives included campaign optimization, content production, and audience retargeting.'}}, {id:'br6',type:'divider-gold',data:{}}, {id:'br7',type:'heading',data:{text:'What Worked'}}, {id:'br8',type:'text',data:{text:'Insert highlights and wins for the period here.'}}, {id:'br9',type:'heading',data:{text:'Next Month Focus'}}, {id:'br10',type:'timeline',data:{phases:[{label:'1',title:'Priority One',desc:'Description of top priority',dur:'Week 1–2'},{label:'2',title:'Priority Two',desc:'Description of next priority',dur:'Week 3–4'}]}}, ] }, { id:'t4', name:'Quick Quote', type:'proposal', desc:'Minimal one-page proposal for fast turnaround quotes — cover, pricing, and signature only.', tags:['Cover','Pricing','Signature'], blocks:[ {id:'bq1',type:'cover',data:{title:'Service Quote',sub:'Investment overview for your review',client:'[Client Name]',date:fmtDate(today())}}, {id:'bq2',type:'text',data:{text:'Thank you for the opportunity. Please review the investment below and sign to proceed.'}}, {id:'bq3',type:'pricing',data:{rows:[['Service Description','1x','$0']],total:'$0'}}, {id:'bq4',type:'cta',data:{title:'Questions?',sub:'Reach out any time.',btn:'Accept Quote'}}, {id:'bq5',type:'signature',data:{prepBy:'[Your Agency]',prepRole:'Representative',clientName:'[Client Name]',clientRole:'Authorized Signatory'}}, ] }, { id:'t5', name:'Case Study', type:'report', desc:'Showcase client results with a structured case study — challenge, approach, results.', tags:['Cover','Problem','Solution','Results','Testimonial'], blocks:[ {id:'bc1',type:'cover',data:{title:'Client Success Story',sub:'How [Your Agency] delivered results',client:'[Client Name]',date:fmtDate(today())}}, {id:'bc2',type:'heading',data:{text:'The Challenge'}}, {id:'bc3',type:'text',data:{text:'Describe the client situation, pain points, and goals before engaging [Your Agency].'}}, {id:'bc4',type:'heading',data:{text:'Our Approach'}}, {id:'bc5',type:'timeline',data:{phases:[{label:'1',title:'Audit',desc:'Full analysis of existing systems',dur:'Week 1'},{label:'2',title:'Strategy',desc:'Custom roadmap creation',dur:'Week 2'},{label:'3',title:'Execution',desc:'Implementation and optimization',dur:'Week 3–8'}]}}, {id:'bc6',type:'heading',data:{text:'Results'}}, {id:'bc7',type:'metrics',data:{items:[{val:'0%',lbl:'Increase'},{val:'$0k',lbl:'Generated'},{val:'0×',lbl:'ROI'}]}}, {id:'bc8',type:'testimonial',data:{quote:'Working with [Your Agency] completely changed how we approach our digital strategy.',name:'Client Name',role:'CEO, Company'}}, ] }, { id:'t6', name:'Partnership Proposal', type:'proposal', desc:'Co-brand partnership or referral agreement proposal with mutual benefits and terms.', tags:['Cover','Benefits','Terms','Signature'], blocks:[ {id:'bp1',type:'cover',data:{title:'Partnership Proposal',sub:'Strategic collaboration & mutual growth',client:'[Partner Name]',date:fmtDate(today())}}, {id:'bp2',type:'heading',data:{text:'The Opportunity'}}, {id:'bp3',type:'text',data:{text:'[Your Agency] proposes a strategic partnership to co-develop client opportunities, share referrals, and deliver expanded value to both client bases.'}}, {id:'bp4',type:'heading',data:{text:'What We Offer'}}, {id:'bp5',type:'services',data:{items:[{name:'Referral Fees',price:'15%',desc:'On all converted referrals'},{name:'Co-Marketing',price:'Shared',desc:'Joint content and campaigns'},{name:'White Label',price:'Available',desc:'Under your brand if needed'},{name:'Priority Access',price:'Included',desc:'First access to new products'}]}}, {id:'bp6',type:'cta',data:{title:'Letuis build something great together.',sub:'Review and sign to activate the partnership.',btn:'Accept Partnership'}}, {id:'bp7',type:'signature',data:{prepBy:'[Your Agency]',prepRole:'Partnerships',clientName:'[Partner Name]',clientRole:'Representative'}}, ] }, ]; function renderTemplates(){ const mockColors={proposal:'#1a0a2a',sow:'#0a1a0a',report:'#1a1204',invoice:'#0a0a1a'}; document.getElementById('templatesGrid').innerHTML=TEMPLATES.map(t=>`
${t.type}
${t.name}
${t.desc}
${t.tags.map(tag=>`${tag}`).join('')}
`).join(''); } function loadTemplate(tid){ const t=TEMPLATES.find(x=>x.id===tid); if(!t) return; canvasBlocks=t.blocks.map(b=>({...b,data:{...b.data},id:newBlockId()})); editingDocId=null; document.getElementById('canvasName').value=t.name; document.getElementById('canvasType').value=t.type; renderCanvas(); showView('builder',null); toast(`Template "${t.name}" loaded`,'success'); } // ══════════════════════════════════════ // CLIENTS // ══════════════════════════════════════ function renderClients(){ document.getElementById('clientsMeta').textContent=`${S.clients.length} client${S.clients.length!==1?'s':''} · ${S.documents.length} documents total`; const grid=document.getElementById('clientsGrid'); if(S.clients.length===0){ grid.innerHTML='
No clients yet
'; return; } grid.innerHTML=S.clients.map(c=>{ const docs=S.documents.filter(d=>d.client===c.company||d.client===c.name); const totalVal=docs.reduce((a,d)=>a+(d.value||0),0); return `
${initials(c.name)}
${c.name}
${c.company||'—'} · ${c.email||'—'}
${docs.length}
Docs
$${(totalVal/1000).toFixed(0)}k
Value
`; }).join(''); } function newDocForClient(clientId){ const c=getClient(clientId); if(!c) return; editingDocId=null; canvasBlocks=[]; document.getElementById('canvasName').value=`Proposal for ${c.company||c.name}`; document.getElementById('propClientName').value=c.company||c.name; renderCanvas(); showView('builder',null); } function deleteClient(id){ if(!confirm('Delete this client?')) return; S.clients=S.clients.filter(c=>c.id!=id); save(); renderAll(); toast('Client deleted','error'); } function populateClientSelects(){ const sel=document.getElementById('canvasClient'); if(sel){ const cur=sel.value; sel.innerHTML=''+S.clients.map(c=>``).join(''); sel.value=cur; } } // ══════════════════════════════════════ // AI SECTION WRITER // ══════════════════════════════════════ async function aiWriteSection(){ const prompt=document.getElementById('aiPromptInput').value.trim(); if(!prompt){ toast('Enter a prompt first','error'); return; } const label=document.getElementById('aiGenLabel'); label.innerHTML='
Writing with Claude…'; const docTitle=document.getElementById('canvasName').value||'proposal'; const clientName=document.getElementById('propClientName').value||'the client'; const docType=document.getElementById('canvasType')?.value||'proposal'; const systemPrompt=`You are an expert business writer for [Your Agency], a business development agency. Write compelling, professional content for a ${docType} document titled "${docTitle}" for ${clientName}. Your writing should be polished, confident, and persuasive — but never vague. Be specific. Respond with just the content text — no headers, no markdown, no preamble. 2-4 sentences max unless asked for more.`; try{ const response=await fetch('https://api.ridgelinecrm.com/v1/messages',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({model:'claude-sonnet-4-20250514',max_tokens:400,system:systemPrompt,messages:[{role:'user',content:prompt}]}) }); const data=await response.json(); if(data.content?.[0]?.text){ const text=data.content[0].text; addBlock('text',{text}); document.getElementById('aiPromptInput').value=''; label.innerHTML='◈ AI Assistant'; toast('AI block added','success'); } }catch(e){ label.innerHTML='◈ AI Assistant'; toast('AI error — check connection','error'); } } async function aiGenerateSection(){ const input=document.getElementById('aiPromptInput'); if(!input.value.trim()){ input.focus(); input.placeholder='Describe what to write first, then click Generate…'; toast('Type a prompt in the AI field first','error'); return; } await aiWriteSection(); } // ══════════════════════════════════════ // PROPOSAL GENERATOR // ══════════════════════════════════════ function getVinculumContacts() { try { const v = localStorage.getItem('vinculum_v1'); if (!v) return []; const data = JSON.parse(v); return (data.contacts || []).filter(c => c.stage !== 'lost'); } catch(e) { return []; } } let propGenData = {}; function openProposalGenerator() { propGenData = {}; const contacts = getVinculumContacts(); const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); const ft = document.getElementById('modalFt'); title.innerHTML = '◈ Generate Proposal'; const vinculumHTML = contacts.length > 0 ? `
Pull client from Vinculum CRM ${contacts.length} contact${contacts.length>1?'s':''} available
` : ''; const projectTypes = ['Social Media Management','SEO & Content Strategy','Paid Advertising','Web Design & Development','Brand Identity','Email Marketing','Video Production','PR & Communications','Consulting Retainer','Custom Project']; body.innerHTML = `
1 · Brief
2 · Scope
3 · Generate
${vinculumHTML}
01
02
03
Building your proposal…
Generating sections
`; ft.innerHTML = ` `; document.getElementById('modalOverlay').classList.add('open'); } function propGenBack() { const steps = [1,2,3]; const current = steps.find(n => document.getElementById('pgs'+n)?.classList.contains('active')); if (current <= 1) return; propGenGoTo(current - 1); } function propGenNext() { const steps = [1,2,3]; const current = steps.find(n => document.getElementById('pgs'+n)?.classList.contains('active')); if (current === 1) { const client = document.getElementById('pg_client')?.value.trim(); const type = document.getElementById('pg_type')?.value; if (!client) { toast('Client name is required','error'); return; } if (!type) { toast('Select a project type','error'); return; } propGenData.client = client; propGenData.type = type; propGenData.budget = document.getElementById('pg_budget')?.value.trim() || 'to be agreed'; propGenGoTo(2); } else if (current === 2) { const p1 = document.getElementById('pg_pain1')?.value.trim(); if (!p1) { toast('Add at least one pain point','error'); return; } propGenData.pains = [p1, document.getElementById('pg_pain2')?.value.trim(), document.getElementById('pg_pain3')?.value.trim() ].filter(Boolean); propGenData.notes = document.getElementById('pg_notes')?.value.trim() || ''; propGenGoTo(3); runProposalGenerator(); } } function propGenGoTo(n) { [1,2,3].forEach(i => { document.getElementById('pgs'+i)?.classList.toggle('active', i===n); const p = document.getElementById('pgp'+i); if (p) { p.classList.toggle('active', i===n); p.classList.toggle('done', i 1 ? '' : 'none'; document.getElementById('pg_next').style.display = n < 3 ? '' : 'none'; } function pullVinculumContact() { const contacts = getVinculumContacts(); if (!contacts.length) return; // Show sub-picker inline const pull = document.querySelector('.vinculum-pull'); pull.outerHTML = `
`; } function applyVinculumContact(id) { const contacts = getVinculumContacts(); const c = contacts.find(x => String(x.id) === String(id)); if (!c) return; const nameField = document.getElementById('pg_client'); if (nameField) nameField.value = c.company ? c.name + ' / ' + c.company : c.name; propGenData.vinculumContact = c; // Pre-fill budget from deal value if (c.value) { const budget = document.getElementById('pg_budget'); if (budget && !budget.value) budget.value = '$' + Number(c.value).toLocaleString(); } toast('Contact pulled from Vinculum ✓', 'success'); } async function runProposalGenerator() { const { client, type, budget, pains, notes, vinculumContact } = propGenData; const vinculumCtx = vinculumContact ? `CRM context: ${vinculumContact.name} at ${vinculumContact.company||'their company'}, currently in "${vinculumContact.stage}" stage, deal value $${(vinculumContact.value||0).toLocaleString()}.` : ''; const prompt = `You are an expert business proposal writer. Generate a complete, professional proposal for the following brief. Return ONLY a JSON object, no other text. CLIENT: ${client} PROJECT TYPE: ${type} BUDGET: ${budget} PAIN POINTS: ${pains.map((p,i)=>`${i+1}. ${p}`).join('\n')} ${notes ? 'ADDITIONAL CONTEXT: ' + notes : ''} ${vinculumCtx} Return this exact JSON structure: { "title": "proposal title (specific, not generic)", "subtitle": "one-line proposal subtitle", "executiveSummary": "2-3 sentence executive summary paragraph, specific to the client", "services": [ {"name": "Service Name", "price": "$X,XXX/mo", "desc": "one-line description"}, {"name": "Service Name", "price": "$X,XXX/mo", "desc": "one-line description"}, {"name": "Service Name", "price": "$X,XXX/mo", "desc": "one-line description"} ], "pricingRows": [ ["Service Name", "Monthly", "$X,XXX"], ["Service Name", "Monthly", "$X,XXX"] ], "pricingTotal": "$X,XXX/mo", "timeline": [ {"title": "Phase Name", "dur": "Wk 1–2", "desc": "what happens"}, {"title": "Phase Name", "dur": "Wk 3–6", "desc": "what happens"}, {"title": "Phase Name", "dur": "Wk 7–8", "desc": "what happens"} ], "nextSteps": "one paragraph: what happens after they sign" }`; try { const res = await fetch('https://api.ridgelinecrm.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1200, messages: [{ role:'user', content: prompt }] }) }); const data = await res.json(); const raw = data.content?.[0]?.text?.trim() || ''; const clean = raw.replace(/```json|```/g,'').trim(); const proposal = JSON.parse(clean); closeModal(); // Build blocks from proposal const today_ = today(); const fmtDate_ = fmtDate(today_); canvasBlocks = []; editingDocId = null; addBlock('cover', { title: proposal.title, sub: proposal.subtitle, client, date: fmtDate_ }); addBlock('divider-gold', {}); addBlock('heading', { text: 'Executive Summary' }); addBlock('text', { text: proposal.executiveSummary }); addBlock('heading', { text: 'Scope of Work' }); addBlock('services', { items: proposal.services }); addBlock('heading', { text: 'Investment' }); addBlock('pricing', { rows: proposal.pricingRows, total: proposal.pricingTotal }); addBlock('heading', { text: 'Project Timeline' }); addBlock('timeline', { phases: proposal.timeline }); addBlock('heading', { text: 'Next Steps' }); addBlock('text', { text: proposal.nextSteps }); addBlock('divider', {}); addBlock('signature', { prepBy: window.__agencyName || '[Your Agency]', prepRole: 'Lead Strategist', clientName: client, clientRole: 'Authorized Signatory' }); // Set builder fields document.getElementById('canvasName').value = proposal.title; document.getElementById('propClientName').value = client; document.getElementById('canvasType').value = 'proposal'; document.getElementById('propDate').value = today_; if (proposal.pricingTotal) { const numVal = parseInt(proposal.pricingTotal.replace(/[^0-9]/g,'')) || 0; document.getElementById('propValue').value = numVal; } showView('builder', null); saveDocument(); toast('Proposal generated — ' + canvasBlocks.length + ' blocks added', 'success'); } catch(e) { console.error('Proposal generator error:', e); document.querySelector('.prop-gen-gen-title').textContent = 'Generation failed'; document.querySelector('.prop-gen-gen-status').textContent = 'Check your AI connection and try again'; document.querySelector('.prop-gen-gen-icon').textContent = '✕'; document.querySelector('.prop-gen-gen-dots').style.display = 'none'; document.getElementById('pg_next').style.display = ''; document.getElementById('pg_next').textContent = 'Retry'; document.getElementById('pg_next').onclick = () => { propGenGoTo(3); runProposalGenerator(); }; } } // ══════════════════════════════════════ // PROPOSAL STATUS TRACKING // ══════════════════════════════════════ const STATUS_LABELS = { draft: 'Draft', sent: 'Sent', viewed: 'Viewed', accepted: 'Accepted', declined: 'Declined', }; function changeDocStatus(docId, newStatus) { const d = getDoc(docId); if (!d) return; d.status = newStatus; d.statusUpdatedAt = new Date().toISOString(); // If accepted → offer Calculus bridge if (newStatus === 'accepted') { save(); renderAll(); toast('Proposal accepted 🎉', 'success'); setTimeout(() => offerCalculusBridge(docId), 600); return; } save(); renderAll(); toast('Status updated → ' + STATUS_LABELS[newStatus]); } function offerCalculusBridge(docId) { const d = getDoc(docId); if (!d || !d.value) return; const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); const ft = document.getElementById('modalFt'); title.innerHTML = 'Push to Calculus'; body.innerHTML = `

${d.title} has been accepted. Create an invoice in Calculus for $${Number(d.value).toLocaleString()}?

Invoice preview
${d.client || 'Client'} — ${d.title}
$${Number(d.value).toLocaleString()}
`; ft.innerHTML = ` `; document.getElementById('modalOverlay').classList.add('open'); } function pushToCalculus(docId) { const d = getDoc(docId); if (!d) return; try { const calcKey = 'calculus_v1'; let calcData = {}; try { const r = localStorage.getItem(calcKey); if(r) calcData = JSON.parse(r); } catch(e){} if (!calcData.invoices) calcData.invoices = []; if (!calcData.nextId) calcData.nextId = 100; const invNum = 'INV-' + String(++calcData.nextId).padStart(4,'0'); calcData.invoices.push({ id: calcData.nextId, number: invNum, client: d.client || '', title: d.title || 'Services', amount: d.value || 0, status: 'draft', date: today(), sourceDoc: docId, type: 'invoice', }); localStorage.setItem(calcKey, JSON.stringify(calcData)); toast('Invoice ' + invNum + ' created in Calculus ✓', 'success'); } catch(e) { toast('Could not write to Calculus — open it to sync', 'error'); } } // ══════════════════════════════════════ // MODALS // ══════════════════════════════════════ function openModal(type, id=null){ const title=document.getElementById('modalTitle'); const body=document.getElementById('modalBody'); const ft=document.getElementById('modalFt'); if(type==='client'){ const c=id?getClient(id):null; title.innerHTML=c?`Edit Client`:`New Client`; body.innerHTML=`
`; ft.innerHTML=``; } document.getElementById('modalOverlay').classList.add('open'); } function closeModal(){ document.getElementById('modalOverlay').classList.remove('open'); } function bgCloseModal(e){ if(e.target===document.getElementById('modalOverlay')) closeModal(); } function saveClient(id){ const name=document.getElementById('cl_name').value.trim(); if(!name){ toast('Name required','error'); return; } const data={name,company:document.getElementById('cl_company').value.trim(),email:document.getElementById('cl_email').value.trim(),phone:document.getElementById('cl_phone').value.trim()}; if(id){ const c=getClient(id); if(c) Object.assign(c,data); toast('Client updated'); } else { S.clients.push({id:uid(),...data}); toast('Client added'); } save(); closeModal(); renderAll(); } // ══════════════════════════════════════ // KEYBOARD // ══════════════════════════════════════ document.addEventListener('keydown',e=>{ if(e.key==='Escape'){ if(document.getElementById('exportOverlay').classList.contains('open')) closeExport(); else if(document.getElementById('modalOverlay').classList.contains('open')) closeModal(); } }); // Initialize date fields document.getElementById('propDate').value=today(); // ══════════════════════════════════════ // INIT // ══════════════════════════════════════ renderAll(); // ══════════════════════════════════════ // AI ASSISTANT DRAWER // ══════════════════════════════════════ const aiHistory = []; function openAI() { document.getElementById('aiDrawer').classList.add('open'); document.getElementById('aiOverlay').classList.add('open'); setTimeout(() => document.getElementById('aiInput').focus(), 300); } function closeAI() { document.getElementById('aiDrawer').classList.remove('open'); document.getElementById('aiOverlay').classList.remove('open'); } function aiKey(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAI(); } } function aiPrompt(text) { document.getElementById('aiInput').value = text; document.getElementById('aiSuggestions').style.display = 'none'; sendAI(); } function aiContext() { const docs = S.docs || []; const total = docs.length; const proposals = docs.filter(d => d.type === 'proposal').length; const sows = docs.filter(d => d.type === 'sow').length; const reports = docs.filter(d => d.type === 'report').length; const recentDocs = docs.slice(-5).map(d => `"${d.title||'Untitled'}" (${d.type||'doc'}, client: ${d.client||'?'})`).join('; '); return `You are Lumen AI, the document intelligence assistant for the [Your Agency] suite. You help agencies write compelling proposals, airtight SOWs, and professional client reports. CURRENT DOCUMENT LIBRARY: - Total documents: ${total} - Proposals: ${proposals} | SOWs: ${sows} | Reports: ${reports} - Recent documents: ${recentDocs || 'none yet'} You are an expert in agency business writing — proposals, scope of work documents, client reporting, and executive communications. Give specific, actionable advice. When writing content, be polished and persuasive.`; } async function sendAI() { const input = document.getElementById('aiInput'); const text = input.value.trim(); if (!text) return; document.getElementById('aiSuggestions').style.display = 'none'; input.value = ''; document.getElementById('aiSendBtn').disabled = true; aiHistory.push({ role: 'user', content: text }); renderAIMsgs(); const msgs = document.getElementById('aiMsgs'); const typing = document.createElement('div'); typing.className = 'ai-typing'; typing.id = 'aiTyping'; typing.innerHTML = ''; msgs.appendChild(typing); msgs.scrollTop = msgs.scrollHeight; try { const res = await fetch('https://api.ridgelinecrm.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1000, system: aiContext(), messages: aiHistory }) }); const data = await res.json(); const reply = data.content?.[0]?.text || 'Sorry, I could not get a response.'; aiHistory.push({ role: 'assistant', content: reply }); } catch(e) { aiHistory.push({ role: 'assistant', content: 'Connection error. Check your AI proxy is running.' }); } document.getElementById('aiTyping')?.remove(); renderAIMsgs(); document.getElementById('aiSendBtn').disabled = false; input.focus(); } function renderAIMsgs() { const msgs = document.getElementById('aiMsgs'); const welcome = msgs.querySelector('.ai-msg.assistant'); msgs.innerHTML = ''; if (welcome) msgs.appendChild(welcome); aiHistory.forEach(m => { const div = document.createElement('div'); div.className = `ai-msg ${m.role}`; div.innerHTML = `
${m.role === 'user' ? 'You' : 'Lumen AI'}
${m.content.replace(/\*\*(.+?)\*\*/g,'$1').replace(/\n/g,'
')}
`; msgs.appendChild(div); }); msgs.scrollTop = msgs.scrollHeight; } // Shift AI FAB when builder view (with right panel) is active const _origShowView = typeof showView === 'function' ? showView : null; function patchFabForBuilder() { const fab = document.querySelector('.ai-fab'); if (!fab) return; const builderActive = document.getElementById('view-builder')?.classList.contains('active'); fab.style.right = builderActive ? '348px' : '28px'; } // Patch showView to reposition FAB const _showViewOrig = window.showView; window.showView = function(view, el) { if (_showViewOrig) _showViewOrig(view, el); setTimeout(patchFabForBuilder, 50); }; patchFabForBuilder();
Lumen AI Document Intelligence
Lumen AI
I can see all your documents, proposals, and SOWs. Ask me to help you write stronger proposals, improve existing content, or advise on how to present your services to a specific client.