Restyle the cover letter to match the CV; refine the résumé head
Cover letter: a big confident name + role in tracked caps, the shared hairline-with-accent motif, and a centred contact footer strip (moved out of the header) so it reads as one set with the monochrome résumé. Résumé: drop the divider rule above the Profil heading — the section headings carry the structure on their own. Prompt: forbid the headline from using a protected professional title the applicant has not verifiably earned (e.g. Fachinformatiker, Ingenieur, Meister); fall back to a neutral activity label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+40
-28
@@ -228,7 +228,13 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen
|
||||
`(keine anderen, keine zusätzlichen Felder, "zeitraum" immer als einzelner String):\n\n` +
|
||||
`${skeleton}\n\n` +
|
||||
`Vorgaben (deutscher Lebenslauf nach aktueller Norm/Best Practice):\n` +
|
||||
`- headline: kurze Berufsbezeichnung des Bewerbers, ggf. auf die Zielstelle zugeschnitten.\n` +
|
||||
`- headline: kurze, sachliche Berufsbezeichnung des Bewerbers, ggf. auf die Zielstelle zugeschnitten. ` +
|
||||
`NIEMALS eine geschützte oder formal an eine Ausbildung/Prüfung/einen Studienabschluss gebundene ` +
|
||||
`Berufsbezeichnung verwenden, die der Bewerber laut Unterlagen nicht nachweislich erworben hat ` +
|
||||
`(z. B. "Fachinformatiker", "Ingenieur", "Techniker", "Meister", "Kaufmann/-frau", "Geselle", ` +
|
||||
`"Facharzt", "Bachelor/Master", "Dipl.-..." u. Ä.). Im Zweifel eine neutrale, nicht geschützte ` +
|
||||
`Tätigkeitsbezeichnung wählen (z. B. "Softwareentwickler", "IT-Fachkraft", "Anwendungsentwickler") ` +
|
||||
`statt eines geschützten Titels.\n` +
|
||||
`- kontakt: E-Mail, Telefon, Ort, Webseite, Geburtsdatum NUR übernehmen, wenn in den ` +
|
||||
`Basis-Unterlagen vorhanden; sonst leerer String. Nichts erfinden. Geburtsdatum als ` +
|
||||
`DD.MM.YYYY (z. B. "03.04.1990"). Keine anderen persönlichen Angaben (Führerschein, ` +
|
||||
@@ -900,18 +906,12 @@ function cvEducation(doc, S, dry, cur, e) {
|
||||
cur.y += 4 * S;
|
||||
}
|
||||
|
||||
// Big name + target role, closed by a short accent rule — the confident head
|
||||
// the recruiter's eye lands on first.
|
||||
// Big name + target role — the confident head the recruiter's eye lands on
|
||||
// first. No divider rule here; the first section heading provides the structure.
|
||||
function composeMainHeader(doc, S, dry, cur, header, titel) {
|
||||
write(doc, S, dry, cur, header.name, { pt: 25, style: 'bold', color: RC.accent, x: MAIN_X, width: MAIN_W, factor: 1.06 });
|
||||
if (titel) write(doc, S, dry, cur, titel, { pt: 12, color: RC.sub, x: MAIN_X, width: MAIN_W, factor: 1.36, charSpace: 0.3 });
|
||||
cur.y += 2.6 * S;
|
||||
if (!dry) {
|
||||
doc.setDrawColor(RC.accent[0], RC.accent[1], RC.accent[2]);
|
||||
doc.setLineWidth(1.2 * S);
|
||||
doc.line(MAIN_X, cur.y, MAIN_X + 20, cur.y);
|
||||
}
|
||||
cur.y += 2.2 * S;
|
||||
cur.y += 2 * S;
|
||||
}
|
||||
|
||||
function composeMain(doc, S, dry, { cv, header, titel }) {
|
||||
@@ -961,7 +961,7 @@ function renderCV(cv, header, titel, foto) {
|
||||
// Cover letter — clean, single-column DIN-5008 business letter (monochrome)
|
||||
// ===========================================================================
|
||||
|
||||
const LET = { mx: 25, mr: 20, top: 24, bottom: 18 }; // DIN-ish margins
|
||||
const LET = { mx: 25, mr: 20, top: 24, bottom: 24 }; // DIN-ish margins (bottom reserves the footer strip)
|
||||
const LET_R = PAGE.w - LET.mr; // 190
|
||||
const LET_W = LET_R - LET.mx; // 165
|
||||
|
||||
@@ -991,29 +991,22 @@ function composeLetter(doc, S, dry, cur, { letter, header, job, anlagen, signatu
|
||||
const betreff = letter.betreff || (job.stelle ? `Bewerbung als ${job.stelle}` : 'Bewerbung');
|
||||
const stadt = cityName(header); // just the town for the date line ("Ort, den …")
|
||||
|
||||
// --- Letterhead: name (left) + contact (right), accented rule ---
|
||||
const top = cur.y;
|
||||
write(doc, S, dry, cur, header.name, { pt: 17, style: 'bold', color: LC.accent, x, width: width * 0.55, factor: 1.15 });
|
||||
if (header.headline) write(doc, S, dry, cur, header.headline, { pt: 10, color: LC.muted, x, width: width * 0.55, factor: 1.25, charSpace: 0.2 });
|
||||
const leftBottom = cur.y;
|
||||
|
||||
const contactLines = [];
|
||||
if (header.adresse) String(header.adresse).split(',').forEach((s) => { if (s.trim()) contactLines.push(s.trim()); });
|
||||
if (header.telefon) contactLines.push(header.telefon);
|
||||
if (header.email) contactLines.push(header.email);
|
||||
const rc = { y: top };
|
||||
contactLines.forEach((line) => write(doc, S, dry, rc, line, { pt: 9, color: LC.muted, x, width, align: 'right', right, factor: 1.32 }));
|
||||
// --- Letterhead: a big, confident name + role in tracked caps, closed by
|
||||
// the shared hairline-with-accent motif. Full contact details live in the
|
||||
// footer strip, which keeps the head clean and echoes the résumé header. ---
|
||||
write(doc, S, dry, cur, header.name, { pt: 20, style: 'bold', color: LC.accent, x, width, factor: 1.12 });
|
||||
if (header.headline) write(doc, S, dry, cur, header.headline, { pt: 9.5, color: LC.muted, x, width, factor: 1.32, charSpace: 0.8, upper: true });
|
||||
|
||||
// Hairline across, with a short heavier accent segment at the start — the
|
||||
// same motif that anchors every section of the résumé.
|
||||
cur.y = Math.max(leftBottom, rc.y) + 3 * S;
|
||||
cur.y += 3.4 * S;
|
||||
if (!dry) {
|
||||
doc.setDrawColor(LC.hair[0], LC.hair[1], LC.hair[2]);
|
||||
doc.setLineWidth(0.3 * S);
|
||||
doc.line(x, cur.y, right, cur.y);
|
||||
doc.setDrawColor(LC.accent[0], LC.accent[1], LC.accent[2]);
|
||||
doc.setLineWidth(1.0 * S);
|
||||
doc.line(x, cur.y, x + 15 * S, cur.y);
|
||||
doc.setLineWidth(1.1 * S);
|
||||
doc.line(x, cur.y, x + 16 * S, cur.y);
|
||||
}
|
||||
cur.y += 13 * S;
|
||||
|
||||
@@ -1031,9 +1024,9 @@ function composeLetter(doc, S, dry, cur, { letter, header, job, anlagen, signatu
|
||||
const dateLine = stadt ? `${stadt}, den ${today}` : today;
|
||||
write(doc, S, dry, cur, dateLine, { pt: 10, color: LC.ink, x, width, align: 'right', right, factor: 1.2 });
|
||||
|
||||
// --- Subject (bold navy, no "Betreff:" label) — the letter's headline ---
|
||||
// --- Subject (bold, no "Betreff:" label) — the letter's headline ---
|
||||
cur.y += 7 * S;
|
||||
write(doc, S, dry, cur, betreff, { pt: 11, style: 'bold', color: LC.accent, x, width, factor: 1.3 });
|
||||
write(doc, S, dry, cur, betreff, { pt: 11.5, style: 'bold', color: LC.accent, x, width, factor: 1.3 });
|
||||
|
||||
// --- Salutation + body ---
|
||||
cur.y += 6 * S;
|
||||
@@ -1075,6 +1068,25 @@ function composeLetter(doc, S, dry, cur, { letter, header, job, anlagen, signatu
|
||||
write(doc, S, dry, cur, (anlagen.length > 1 ? 'Anlagen: ' : 'Anlage: ') + anlagen.join(', '),
|
||||
{ pt: 9, color: LC.muted, x, width, factor: 1.2 });
|
||||
}
|
||||
|
||||
// --- Footer letterhead: a centred contact strip under a hairline, drawn in
|
||||
// the reserved bottom margin so it never collides with the body. ---
|
||||
if (!dry) {
|
||||
const parts = [];
|
||||
if (header.adresse) parts.push(String(header.adresse).replace(/\s*,\s*/g, ', '));
|
||||
if (header.telefon) parts.push(header.telefon);
|
||||
if (header.email) parts.push(header.email);
|
||||
if (parts.length) {
|
||||
const fy = PAGE.h - 15;
|
||||
doc.setDrawColor(LC.hair[0], LC.hair[1], LC.hair[2]);
|
||||
doc.setLineWidth(0.3 * S);
|
||||
doc.line(x, fy, right, fy);
|
||||
doc.setFont('Lato', 'normal');
|
||||
doc.setFontSize(8 * S);
|
||||
doc.setTextColor(LC.muted[0], LC.muted[1], LC.muted[2]);
|
||||
doc.text(parts.join(' · '), (x + right) / 2, fy + 4 * S, { align: 'center' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderSingleColumn(compose) {
|
||||
|
||||
Reference in New Issue
Block a user