Restyle CV: navy accent system + competency-first structure
Rework the résumé as an HR-first document. Move Kernkompetenzen (renamed from Fähigkeiten) directly under the Profil so the role-match is visible in the first seconds, ahead of the reverse-chronological experience. Introduce one restrained deep navy-slate accent used only for structure: the name, section labels, a short accent bar starting each section rule, and small square bullet markers — a coherent, precise motif. Body text stays near-black for a crisp, ATS-safe read. Verified on the with-photo and no-photo layouts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+33
-14
@@ -596,24 +596,36 @@ const RC = {
|
|||||||
ink: [26, 26, 26],
|
ink: [26, 26, 26],
|
||||||
sub: [92, 98, 106],
|
sub: [92, 98, 106],
|
||||||
hair: [206, 208, 212],
|
hair: [206, 208, 212],
|
||||||
|
// One deep, professional navy-slate accent — used sparingly for structure
|
||||||
|
// (name, section labels, the short header rule, bullet markers). It signals
|
||||||
|
// care and confidence without ever reading as flashy; body text stays near
|
||||||
|
// black so the résumé remains crisp and ATS-safe.
|
||||||
|
accent: [31, 64, 104],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Section heading: bold uppercase tracked label with a thin hairline beneath.
|
// Section heading: an accent-coloured, tracked uppercase label over a full-width
|
||||||
|
// hairline, with a short heavier accent segment at the start — a small, precise
|
||||||
|
// motif that gives every section a confident, consistent anchor.
|
||||||
function cvHeading(doc, S, dry, cur, title, ruleRight) {
|
function cvHeading(doc, S, dry, cur, title, ruleRight) {
|
||||||
const pt = 11;
|
const pt = 10.5;
|
||||||
cur.y += 3 * S;
|
cur.y += 3.6 * S;
|
||||||
doc.setFont('Lato', 'bold');
|
doc.setFont('Lato', 'bold');
|
||||||
doc.setFontSize(pt * S);
|
doc.setFontSize(pt * S);
|
||||||
|
const lineY = cur.y + pt * S * PT * 1.08;
|
||||||
if (!dry) {
|
if (!dry) {
|
||||||
doc.setCharSpace(0.6 * S);
|
doc.setCharSpace(0.9 * S);
|
||||||
doc.setTextColor(RC.ink[0], RC.ink[1], RC.ink[2]);
|
doc.setTextColor(RC.accent[0], RC.accent[1], RC.accent[2]);
|
||||||
doc.text(String(title).toUpperCase(), RV.mx, cur.y + pt * S * PT * 0.76);
|
doc.text(String(title).toUpperCase(), RV.mx, cur.y + pt * S * PT * 0.76);
|
||||||
doc.setCharSpace(0);
|
doc.setCharSpace(0);
|
||||||
|
const right = ruleRight || RV_R;
|
||||||
doc.setDrawColor(RC.hair[0], RC.hair[1], RC.hair[2]);
|
doc.setDrawColor(RC.hair[0], RC.hair[1], RC.hair[2]);
|
||||||
doc.setLineWidth(0.3 * S);
|
doc.setLineWidth(0.3 * S);
|
||||||
doc.line(RV.mx, cur.y + pt * S * PT * 1.0, ruleRight || RV_R, cur.y + pt * S * PT * 1.0);
|
doc.line(RV.mx, lineY, right, lineY);
|
||||||
|
doc.setDrawColor(RC.accent[0], RC.accent[1], RC.accent[2]);
|
||||||
|
doc.setLineWidth(1.0 * S);
|
||||||
|
doc.line(RV.mx, lineY, Math.min(RV.mx + 15 * S, right), lineY);
|
||||||
}
|
}
|
||||||
cur.y += pt * S * PT + 3.4 * S;
|
cur.y += pt * S * PT + 4.0 * S;
|
||||||
}
|
}
|
||||||
|
|
||||||
// One row: title (left) + muted date (right-aligned) on a single baseline.
|
// One row: title (left) + muted date (right-aligned) on a single baseline.
|
||||||
@@ -650,7 +662,12 @@ function cvBullets(doc, S, dry, cur, items, x, w, pt = 9.3) {
|
|||||||
const lines = doc.splitTextToSize(String(it), tw);
|
const lines = doc.splitTextToSize(String(it), tw);
|
||||||
lines.forEach((ln, i) => {
|
lines.forEach((ln, i) => {
|
||||||
if (!dry) {
|
if (!dry) {
|
||||||
if (i === 0) { doc.setFillColor(RC.ink[0], RC.ink[1], RC.ink[2]); doc.circle(x + 0.8 * S, cur.y + pt * S * PT * 0.42, 0.6 * S, 'F'); }
|
if (i === 0) {
|
||||||
|
// Small accent square marker (echoes the section-label motif).
|
||||||
|
const sz = 1.15 * S;
|
||||||
|
doc.setFillColor(RC.accent[0], RC.accent[1], RC.accent[2]);
|
||||||
|
doc.rect(x + 0.2 * S, cur.y + pt * S * PT * 0.42 - sz / 2, sz, sz, 'F');
|
||||||
|
}
|
||||||
doc.setTextColor(RC.ink[0], RC.ink[1], RC.ink[2]);
|
doc.setTextColor(RC.ink[0], RC.ink[1], RC.ink[2]);
|
||||||
doc.text(ln, tx, cur.y + pt * S * PT * 0.76);
|
doc.text(ln, tx, cur.y + pt * S * PT * 0.76);
|
||||||
}
|
}
|
||||||
@@ -712,8 +729,8 @@ function composeCvHeader(doc, S, dry, cur, header, titel, foto) {
|
|||||||
|
|
||||||
const gapPhoto = 6 * S;
|
const gapPhoto = 6 * S;
|
||||||
const leftW = photoOk ? (RV_W - photoW - gapPhoto) : RV_W * 0.62;
|
const leftW = photoOk ? (RV_W - photoW - gapPhoto) : RV_W * 0.62;
|
||||||
write(doc, S, dry, cur, header.name, { pt: 22, style: 'bold', color: RC.ink, x: RV.mx, width: leftW, factor: 1.05 });
|
write(doc, S, dry, cur, header.name, { pt: 22, style: 'bold', color: RC.accent, x: RV.mx, width: leftW, factor: 1.08 });
|
||||||
if (titel) write(doc, S, dry, cur, titel, { pt: 12, color: RC.sub, x: RV.mx, width: leftW, factor: 1.15 });
|
if (titel) write(doc, S, dry, cur, titel, { pt: 11.5, color: RC.sub, x: RV.mx, width: leftW, factor: 1.32, charSpace: 0.2 });
|
||||||
if (header.geburtsdatum) write(doc, S, dry, cur, `Geburtsdatum: ${header.geburtsdatum}`, { pt: 9, color: RC.sub, x: RV.mx, width: leftW, factor: 1.3 });
|
if (header.geburtsdatum) write(doc, S, dry, cur, `Geburtsdatum: ${header.geburtsdatum}`, { pt: 9, color: RC.sub, x: RV.mx, width: leftW, factor: 1.3 });
|
||||||
if (photoOk) {
|
if (photoOk) {
|
||||||
// contact details left-aligned under the name/title (photo takes the right)
|
// contact details left-aligned under the name/title (photo takes the right)
|
||||||
@@ -789,6 +806,12 @@ function composeCV(doc, S, dry, cur, { cv, header, titel, foto }) {
|
|||||||
} else {
|
} else {
|
||||||
cur.y = Math.max(cur.y, geo.photoBottom);
|
cur.y = Math.max(cur.y, geo.photoBottom);
|
||||||
}
|
}
|
||||||
|
// Kernkompetenzen right after the Profil: the recruiter sees the role-match
|
||||||
|
// in the first seconds, before diving into the experience below.
|
||||||
|
if (cv.kenntnisse.length) {
|
||||||
|
cvHeading(doc, S, dry, cur, 'Kernkompetenzen');
|
||||||
|
cvTwoColBullets(doc, S, dry, cur, cv.kenntnisse, RV.mx, RV_W);
|
||||||
|
}
|
||||||
if (cv.berufserfahrung.length) {
|
if (cv.berufserfahrung.length) {
|
||||||
cvHeading(doc, S, dry, cur, 'Berufserfahrung');
|
cvHeading(doc, S, dry, cur, 'Berufserfahrung');
|
||||||
cv.berufserfahrung.forEach((e) => cvExperience(doc, S, dry, cur, e));
|
cv.berufserfahrung.forEach((e) => cvExperience(doc, S, dry, cur, e));
|
||||||
@@ -798,10 +821,6 @@ function composeCV(doc, S, dry, cur, { cv, header, titel, foto }) {
|
|||||||
cvHeading(doc, S, dry, cur, t);
|
cvHeading(doc, S, dry, cur, t);
|
||||||
entries.forEach((e) => cvEducation(doc, S, dry, cur, e));
|
entries.forEach((e) => cvEducation(doc, S, dry, cur, e));
|
||||||
});
|
});
|
||||||
if (cv.kenntnisse.length) {
|
|
||||||
cvHeading(doc, S, dry, cur, 'Fähigkeiten');
|
|
||||||
cvTwoColBullets(doc, S, dry, cur, cv.kenntnisse, RV.mx, RV_W);
|
|
||||||
}
|
|
||||||
if (cv.sprachen.length) {
|
if (cv.sprachen.length) {
|
||||||
cvHeading(doc, S, dry, cur, 'Sprachen');
|
cvHeading(doc, S, dry, cur, 'Sprachen');
|
||||||
cv.sprachen.forEach((s) => cvRow(doc, S, dry, cur, s.sprache, s.niveau, 9.7, 'normal'));
|
cv.sprachen.forEach((s) => cvRow(doc, S, dry, cur, s.sprache, s.niveau, 9.7, 'normal'));
|
||||||
|
|||||||
Reference in New Issue
Block a user