✓ Copied to clipboard!
✍️ Your Details
Preview updates live as you type
⚙️ Setup needed: AI generation requires a Cloudflare Worker proxy with your Anthropic API key. Set CF_WORKER_URL at the top of the script. The ghost preview works without it.
Comma-separated, relevant to the role
Professional
Formal & polished
Confident
Bold & direct
Enthusiastic
Warm & energetic
Concise
Short & punchy
Your Cover Letter Preview

Cover Letters That Get Read

🎯
Be Specific
Name the company and role. Generic letters are skipped immediately.
📊
Lead with Impact
Open with your biggest win — not "I am applying for...". Recruiters skim the first two lines.
⏱️
Keep It Short
3 short paragraphs, under 250 words. Less than 30 seconds is all you get.
💌
Personal Touch
One genuine reason why you want THIS company. A product, mission, or value you believe in.
/* ─── CONFIG: set your Cloudflare Worker URL here ─── The Worker proxies POST to Anthropic API with your key. Without it, the ghost preview works but AI generation will fail. ─────────────────────────────────────────────────── */ const CF_WORKER_URL = 'https://api.nex-resume.com'; /* ─── GHOST SAMPLE DATA ─── */ const GHOST = { name: 'Rahul Sharma', jobTitle: 'Senior Software Engineer', company: 'Google India', manager: '', skills: 'React, Node.js, System Design, AWS, Team Leadership', achievement: 'Led migration cutting API latency by 62%', why: "Google's culture of engineering excellence and global scale" }; const GHOST_BODY = [ "With four years of hands-on experience building scalable web applications in React and Node.js, I am genuinely excited to apply for the Senior Software Engineer position at Google India. My background in system design, AWS architecture, and team leadership aligns closely with the challenges your engineering teams tackle every day.", "At Infosys, I led the migration of a legacy monolith to a distributed microservices architecture — reducing API response latency by 62% and improving platform reliability for over 800,000 active users. I thrive in environments where strong engineering judgment and clear communication matter as much as clean code.", "What draws me to Google India is your culture of engineering excellence and the sheer global scale at which you operate. I believe the best engineers don't just write software — they think deeply about the people who use it. I would be proud to bring that mindset to your team.", "I would love the opportunity to discuss how my experience can contribute to your goals. Thank you for your time and consideration." ]; let selectedTone = 'professional'; let aiGenerated = false; /* ─── HELPERS ─── */ function gv(id) { return (document.getElementById(id)?.value || '').trim(); } function getFields() { return { name: gv('cl-name'), jobTitle: gv('cl-job-title'), company: gv('cl-company'), manager: gv('cl-manager'), experience: gv('cl-experience') || '3-5', skills: gv('cl-skills'), achievement: gv('cl-achievement'), why: gv('cl-why'), }; } function selectTone(el) { document.querySelectorAll('.tone-opt').forEach(o => o.classList.remove('selected')); el.classList.add('selected'); selectedTone = el.dataset.tone; } function setBadge(text, scheme) { const b = document.getElementById('status-badge'); if (!b) return; b.textContent = text; const schemes = { preview: ['rgba(251,191,36,0.12)', '#fbbf24', 'rgba(251,191,36,0.25)'], live: ['rgba(74,222,128,0.12)', '#4ade80', 'rgba(74,222,128,0.25)'], generated: ['rgba(59,130,246,0.15)', '#93c5fd', 'rgba(59,130,246,0.35)'], demo: ['rgba(251,191,36,0.12)', '#fbbf24', 'rgba(251,191,36,0.25)'], }; const [bg, color, border] = schemes[scheme] || schemes.preview; b.style.background = bg; b.style.color = color; b.style.borderColor = border; } function showActionBtns(show) { ['copy-btn','download-btn','regen-btn'].forEach(id => { const el = document.getElementById(id); if (el) el.style.display = show ? 'inline-flex' : 'none'; }); } /* ─── GHOST / LIVE PREVIEW ─── */ function liveUpdate() { if (aiGenerated) return; // don't overwrite AI output renderPreviewLetter(); } function renderPreviewLetter() { const f = getFields(); const anyReal = !!(f.name || f.jobTitle || f.company || f.skills); // Merge real values into ghost const name = f.name || GHOST.name; const jobTitle = f.jobTitle || GHOST.jobTitle; const company = f.company || GHOST.company; const manager = f.manager || GHOST.manager; const today = new Date().toLocaleDateString('en-IN', { year:'numeric', month:'long', day:'numeric' }); const recipient = manager ? `Dear ${manager},` : 'Dear Hiring Manager,'; // Substitute real values into ghost paragraphs where possible const bodyParagraphs = GHOST_BODY.map(p => { let t = p; t = t.replace(GHOST.name, name); t = t.replace(GHOST.jobTitle, jobTitle); t = t.replace(GHOST.company, company); if (f.achievement) t = t.replace(GHOST.achievement, f.achievement); if (f.why) t = t.replace(GHOST.why, f.why); return `

${t}

`; }).join(''); const ghostClass = anyReal ? '' : 'is-ghost'; document.getElementById('cl-output-body').innerHTML = `
${today}
${recipient}
${company}
Re: Application for ${jobTitle} at ${company}
${bodyParagraphs}
Sincerely,
${name}
`; setBadge(anyReal ? 'Live Preview' : 'Preview', anyReal ? 'live' : 'preview'); showActionBtns(false); } /* ─── AI GENERATION ─── */ async function generateCoverLetter() { const f = getFields(); // Validate required let missing = false; ['cl-name','cl-job-title','cl-company'].forEach(id => { const el = document.getElementById(id); if (el && !el.value.trim()) { el.style.borderColor = 'var(--danger)'; el.addEventListener('input', () => { el.style.borderColor = ''; }, { once: true }); missing = true; } }); if (missing) { document.getElementById('cl-name').focus(); return; } // No Worker URL — show demo mode if (!CF_WORKER_URL) { document.getElementById('api-notice').style.display = 'block'; displayLetter(GHOST_BODY.map(p => { let t = p; t = t.replace(GHOST.name, f.name || GHOST.name); t = t.replace(GHOST.jobTitle, f.jobTitle || GHOST.jobTitle); t = t.replace(GHOST.company, f.company || GHOST.company); if (f.achievement) t = t.replace(GHOST.achievement, f.achievement); if (f.why) t = t.replace(GHOST.why, f.why); return t; }).join('\n\n'), f, 'demo'); return; } // ── Call API via Worker ── aiGenerated = true; const outputBody = document.getElementById('cl-output-body'); outputBody.innerHTML = `
AI is crafting your cover letter...
`; setBadge('Generating...', 'preview'); const btn = document.getElementById('generate-btn'); btn.disabled = true; document.getElementById('gen-btn-text').textContent = 'Generating...'; document.getElementById('gen-spinner').style.display = 'inline-block'; const expLabels = { 'fresher':'a fresher / recent graduate','1-2':'1-2 years','3-5':'3-5 years', '5-10':'5-10 years','senior':'10+ years / senior level' }; const prompt = `Write a ${selectedTone} cover letter body for: Applicant: ${f.name} Role: ${f.jobTitle} at ${f.company} ${f.manager ? `Hiring manager: ${f.manager}` : 'Salutation: Dear Hiring Manager'} Experience: ${expLabels[f.experience] || '3-5 years'} Skills: ${f.skills || 'not specified'} ${f.achievement ? `Achievement: ${f.achievement}` : ''} ${f.why ? `Why this company: ${f.why}` : ''} Output only the 3-4 body paragraphs (no date, no salutation, no sign-off). Under 280 words. No bracket placeholders. Make it specific, genuine and compelling.`; try { const res = await fetch(CF_WORKER_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 800, messages: [{ role: 'user', content: prompt }] }) }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const text = data.content?.[0]?.text || ''; if (!text) throw new Error('Empty response from API'); displayLetter(text, f, 'generated'); } catch(err) { console.error(err); outputBody.innerHTML = `
Generation failed: ${err.message}

Check that CF_WORKER_URL is set correctly in the script.
`; aiGenerated = false; } finally { btn.disabled = false; document.getElementById('gen-btn-text').textContent = '✨ Generate with AI'; document.getElementById('gen-spinner').style.display = 'none'; } } function displayLetter(bodyText, f, badge) { const today = new Date().toLocaleDateString('en-IN', { year:'numeric', month:'long', day:'numeric' }); const recipient = f.manager ? `Dear ${f.manager},` : 'Dear Hiring Manager,'; const name = f.name || GHOST.name; const jobTitle = f.jobTitle || GHOST.jobTitle; const company = f.company || GHOST.company; const paras = bodyText.split(/\n\n+/).filter(p => p.trim()); document.getElementById('cl-output-body').innerHTML = `
${today}
${recipient}
${company}
Re: Application for ${jobTitle} at ${company}
${paras.map(p=>`

${p.replace(/\n/g,'
')}

`).join('')}
Sincerely,
${name}
`; const labels = { generated:'AI Generated', demo:'Demo Mode' }; setBadge(labels[badge] || 'Preview', badge); showActionBtns(true); aiGenerated = true; } /* ─── COPY & DOWNLOAD ─── */ function copyLetter() { const paper = document.getElementById('letter-paper'); if (!paper) return; navigator.clipboard.writeText(paper.innerText).then(() => { const t = document.getElementById('copy-toast'); t.style.display = 'block'; setTimeout(() => t.style.display = 'none', 2500); }); } async function downloadLetter() { const paper = document.getElementById('letter-paper'); if (!paper) return; const btn = document.getElementById('download-btn'); btn.textContent = '⏳ Generating...'; try { const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation:'portrait', unit:'mm', format:'a4' }); const W = pdf.internal.pageSize.getWidth(); const H = pdf.internal.pageSize.getHeight(); const m = 22, lh = 6.5; pdf.setFont('times','normal'); pdf.setFontSize(11); let y = m; pdf.splitTextToSize(paper.innerText, W - m*2).forEach(line => { if (y > H - m) { pdf.addPage(); y = m; } pdf.text(line, m, y); y += lh; }); const name = gv('cl-name') || 'cover-letter'; pdf.save(name.toLowerCase().replace(/\s+/g,'-') + '-cover-letter.pdf'); } catch(e) { alert('PDF failed. Try again.'); } finally { btn.textContent = '⬇ PDF'; } } /* ─── INIT ─── */ renderPreviewLetter();