/* cover-letter.jsx — cover letter editor + live preview */
var CL_DEFAULTS = {
date: "",
toName: "",
toTitle: "",
toCompany: "",
toAddress: "",
opening: "Dear Hiring Manager,",
body: "",
closing: "Sincerely,",
};
var CL_FONT_STACKS = {
geist: '"Geist", -apple-system, "Segoe UI", sans-serif',
ubuntu: '"Ubuntu", sans-serif',
lato: '"Lato", sans-serif',
roboto: '"Roboto", sans-serif',
opensans:'"Open Sans", sans-serif',
mono: '"Geist Mono", monospace',
auto: '"Geist", -apple-system, "Segoe UI", sans-serif',
};
function CoverLetterPreview({ cl, basics, accent, design }) {
basics = basics || {};
accent = accent || "#1C9BE6";
design = design || {};
var fontKey = design.font || "auto";
var fontStack = CL_FONT_STACKS[fontKey] || CL_FONT_STACKS.auto;
var date = cl.date || new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
var toLines = [cl.toName, cl.toTitle, cl.toCompany, cl.toAddress].filter(Boolean);
var body = cl.body || "";
var paras = body.split("\n\n").filter(Boolean);
return (
{/* Sender */}
{basics.name || "Your Name"}
{basics.title && (
{basics.title}
)}
{[basics.email, basics.phone, basics.location, basics.website].filter(Boolean).map(function(c, i) {
return {c} ;
})}
{/* Date */}
{date}
{/* Recipient */}
{toLines.length > 0 && (
{toLines.map(function(ln, i) {
return (
{ln}
);
})}
)}
{/* Opening */}
{cl.opening &&
{cl.opening}
}
{/* Body */}
{paras.length > 0 ? (
paras.map(function(para, i) {
return
{para}
;
})
) : (
Your letter body will appear here. Write focused paragraphs about why you're a great fit for this role.
)}
{/* Closing */}
{cl.closing || "Sincerely,"}
{basics.name || "Your Name"}
{basics.title && (
{basics.title}
)}
);
}
function CoverLetterEditor({ cl, onChange, data, showToast }) {
var set = function(k, v) { onChange(Object.assign({}, cl, { [k]: v })); };
var today = new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
const { useState: S, useRef: R } = React;
const [jd, setJd] = S("");
const [showJd, setShowJd] = S(false);
const [generating, setGenerating] = S(false);
const bodyRef = R(null);
const generate = () => {
if (generating || !window.groqStream) return;
setGenerating(true);
const ctx = window.buildResumeCtx ? window.buildResumeCtx(data || {}) : "";
const company = cl.toCompany || "the company";
const role = cl.toTitle || "the role";
const prompt =
"Write a targeted cover letter body using the résumé below" +
(jd.trim() ? " and the job description provided" : "") + ".\n\n" +
"RÉSUMÉ:\n" + ctx + "\n\n" +
(jd.trim() ? "JOB DESCRIPTION:\n" + jd.trim() + "\n\n" : "") +
"Company: " + company + "\n" +
"Role: " + role + "\n\n" +
"Write 3 paragraphs separated by a blank line (\\n\\n):\n" +
"1. Opening — genuine enthusiasm for " + company + ", briefly who I am and my strongest credential.\n" +
"2. Body — 2 specific, quantified achievements from my résumé that directly match the role's needs.\n" +
"3. Closing — confident call to action, invite for an interview.\n\n" +
"Under 350 words. Professional but warm. First-person. " +
"Return ONLY the three paragraphs — no salutation, no date, no sign-off, no subject line.";
let streamed = "";
window.groqStream({
messages: [
{ role: "system", content: window.GROQ_SYSTEM || "" },
{ role: "user", content: prompt },
],
maxTokens: 700,
onChunk: (_, full) => {
streamed = full;
set("body", full);
},
onDone: (full) => {
setGenerating(false);
set("body", (full || streamed).trim());
if (showToast) showToast("Cover letter generated!");
},
onError: (err) => {
setGenerating(false);
if (err === "free_limit_reached") {
if (showToast) showToast("Free AI limit reached — upgrade to Pro for unlimited use.");
} else {
if (showToast) showToast("AI error: " + err);
}
},
});
};
return (
Cover Letter
A targeted cover letter significantly improves your callback rate. Aim for three focused paragraphs — opening, body, close.
{/* ── AI generation panel ─────────────────────────────── */}
Generate with AI
Uses your résumé data{cl.toCompany ? " · " + cl.toCompany : ""}
{jd.trim() ? " · JD pasted" : ""}
setShowJd(s => !s)}
>
{showJd ? "Hide JD" : "Paste JD"}
{generating
? <> Writing…>
: <> Generate>}
{showJd && (
Job description optional — improves targeting
)}
{/* ── Manual fields ────────────────────────────────────── */}
Date
Letter
Opening salutation
Body
separate paragraphs with a blank line
{generating && ● writing… }
Closing
Tips for a strong cover letter
• Open with a specific achievement that directly addresses the role's needs.
• Mention something concrete you know about the company (a product, recent news, or their mission).
• Keep it to one page — 250–350 words is the sweet spot.
• Use "I" sparingly — put the company's needs at the centre, not yourself.
);
}
window.CL_DEFAULTS = CL_DEFAULTS;
window.CoverLetterEditor = CoverLetterEditor;
window.CoverLetterPreview = CoverLetterPreview;