Sunny: Anschreiben dezenter — Farbe nur noch im Kopfband

Das Anschreiben war ein zweites Poster: Konfetti im Band, solides
Akzent-Badge, Betreff und Anrede beide in der Akzentfarbe, ein pastellener
Marker-Strich hinter dem Namen und eine Farbleiste als Fußzeile. Neben dem
Lebenslauf las sich das als Lärm, nicht als Set.

Jetzt trägt die Seite genau eine Farbfläche — das Kopfband —, alles darunter
ist Typografie:

- Kopfband ohne Konfetti, Name 27pt -> 22pt
- Rollen-Badge als weiße Pille mit Akzentschrift (der Lebenslauf behält sein
  solides Badge; die Umstellung sitzt in der Letter-Palette)
- Datum als schlichte rechtsbündige Zeile statt Pastell-Chip
- Betreff und Anrede in Tinte; die Bar über dem Betreff bleibt der einzige
  Farbakzent im Textkörper
- Signatur ohne Bild: Name über feinem Haarstrich statt Marker-Swipe
- Fußzeile: Haarlinie mit gedämpftem Kontakttext statt Farbleiste

suKonfetti/suDot hatten damit keinen Aufrufer mehr und fallen weg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 18:54:18 +02:00
co-authored by Claude Opus 4.8
parent 21608b8ca7
commit 0301a1c430
2 changed files with 45 additions and 61 deletions
+6 -2
View File
@@ -250,8 +250,12 @@ function resolve(overrides = {}) {
cardBorder: rc.cardBorder, cardBorder: rc.cardBorder,
deko: rc.deko, deko: rc.deko,
band: rc.band, band: rc.band,
pillBg: rc.pillBg, // The letter's role badge sits on the pastel head band, so a solid accent
pillInk: rc.pillInk, // slug reads too loud next to the résumé's poster. A white pill with the
// accent knocked out keeps the motif but softens it — the letter is the
// quiet twin, not a second poster.
pillBg: rc.cardBg,
pillInk: accent,
}, },
}; };
const lc = LC[s.layout] || LC.sidebar; const lc = LC[s.layout] || LC.sidebar;
+39 -59
View File
@@ -1573,27 +1573,6 @@ function suBar(doc, p, S, dry, y, align, frac = 0.62) {
doc.rect(x, y, w, 2.4 * S, 'F'); doc.rect(x, y, w, 2.4 * S, 'F');
} }
// A filled dot. The confetti scattered through the Sunny documents is built from
// these — a designer's flourish, kept to the margins where no text can collide.
function suDot(doc, cx, cy, r, color) {
doc.setFillColor(color[0], color[1], color[2]);
doc.circle(cx, cy, r, 'F');
}
// Confetti in the band's gutters — the strips between its edges and the text
// column, empty by construction, so the decoration can never run into a word.
//
// It all lives on the *right*: dots down the left gutter sat directly beside the
// name and read as bullet points in front of it, not as decoration. The only
// thing on the left is a sparkle low in the band, well clear of the name.
function suKonfetti(doc, p, S, bandY, bandH) {
const gx = SU.r - 3 * S;
suDot(doc, gx, bandY + bandH * 0.26, 1.4 * S, p.accent);
suDot(doc, gx, bandY + bandH * 0.48, 0.9 * S, [255, 255, 255]);
suDot(doc, gx, bandY + bandH * 0.66, 1.9 * S, [255, 255, 255]);
sparkle(doc, SU.mx + 3.4 * S, bandY + bandH - 5.5 * S, 4.4 * S, [255, 255, 255]);
}
// Display heading: the poster type. Set as large as the layout's rhythm allows // Display heading: the poster type. Set as large as the layout's rhythm allows
// and shrunk only if a long word (Berufsausbildung…) would not otherwise fit. // and shrunk only if a long word (Berufsausbildung…) would not otherwise fit.
function suHeading(doc, t, S, dry, cur, title, align = 'left') { function suHeading(doc, t, S, dry, cur, title, align = 'left') {
@@ -2277,7 +2256,7 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
const cl = { y: top }; const cl = { y: top };
const name = String(header.name || ''); const name = String(header.name || '');
let namePt = 27; let namePt = 22;
doc.setFont(doc.__sans, 'bold'); doc.setFont(doc.__sans, 'bold');
for (; namePt > 14; namePt -= 0.5) { for (; namePt > 14; namePt -= 0.5) {
doc.setFontSize(namePt * S); doc.setFontSize(namePt * S);
@@ -2308,24 +2287,27 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
if (!dry) { if (!dry) {
doc.setFillColor(t.lc.band[0], t.lc.band[1], t.lc.band[2]); doc.setFillColor(t.lc.band[0], t.lc.band[1], t.lc.band[2]);
doc.rect(SU.mx, bandY, SU.w, bandH, 'F'); doc.rect(SU.mx, bandY, SU.w, bandH, 'F');
suKonfetti(doc, t.lc, S, bandY, bandH);
inner({ y: bandY }, false); inner({ y: bandY }, false);
} }
cur.y = bandY + bandH + 9 * S; cur.y = bandY + bandH + 9 * S;
// --- Recipient (Anschriftfeld) with the date as a pastel chip on the same // --- Recipient (Anschriftfeld) with the date on the same line — DIN keeps its
// line — DIN keeps its address block, the date just stops being a lonely // address block; the date sits right-aligned in the accent, a quiet line
// right-aligned string and becomes part of the layout's chip language. // rather than a pastel chip, so the letter's head is the only block of
// colour on the page.
const emp = letter.empfaenger || {}; const emp = letter.empfaenger || {};
const empFirma = emp.firma || job.firma || ''; const empFirma = emp.firma || job.firma || '';
const empOrt = emp.ort || job.ort || ''; const empOrt = emp.ort || job.ort || '';
const datum = stadt ? `${stadt}, ${today}` : today; const datum = stadt ? `${stadt}, ${today}` : today;
if (!dry) { if (!dry) {
const p = pill(doc, t, S, true, 0, 0, datum, { pt: 8.6, style: 'normal' }); doc.setFont(doc.__sans, 'normal');
pill(doc, t, S, false, right - p.w, cur.y - 0.5 * S, datum, doc.setFontSize(8.6 * S);
{ pt: 8.6, style: 'normal', bg: t.lc.band, ink: t.lc.accent }); doc.setTextColor(t.lc.accent[0], t.lc.accent[1], t.lc.accent[2]);
// Align the date's baseline with the first recipient line so the two read
// as one row rather than two floats at different heights.
doc.text(datum, right, cur.y + 10.2 * S * PT * 0.76, { align: 'right' });
} }
const empW = width * 0.6; // keep the address clear of the date chip const empW = width * 0.6; // keep the address clear of the date line
if (empFirma) write(doc, S, dry, cur, empFirma, { pt: 10.2, style: 'bold', color: t.lc.ink, x, width: empW, factor: 1.32 }); if (empFirma) write(doc, S, dry, cur, empFirma, { pt: 10.2, style: 'bold', color: t.lc.ink, x, width: empW, factor: 1.32 });
if (emp.ansprechpartner) write(doc, S, dry, cur, `z. Hd. ${emp.ansprechpartner}`, { pt: 10.2, color: t.lc.ink, x, width: empW, factor: 1.32 }); if (emp.ansprechpartner) write(doc, S, dry, cur, `z. Hd. ${emp.ansprechpartner}`, { pt: 10.2, color: t.lc.ink, x, width: empW, factor: 1.32 });
if (emp.adresse) write(doc, S, dry, cur, emp.adresse, { pt: 10.2, color: t.lc.ink, x, width: empW, factor: 1.32 }); if (emp.adresse) write(doc, S, dry, cur, emp.adresse, { pt: 10.2, color: t.lc.ink, x, width: empW, factor: 1.32 });
@@ -2337,13 +2319,13 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
cur.y += 9 * S; cur.y += 9 * S;
suBar(doc, t.lc, S, dry, cur.y, 'left', 0.34); suBar(doc, t.lc, S, dry, cur.y, 'left', 0.34);
cur.y += 2.4 * S + 4.2 * S; cur.y += 2.4 * S + 4.2 * S;
write(doc, S, dry, cur, betreff, { pt: 15, style: 'bold', color: t.lc.accent, x, width, factor: 1.18 }); write(doc, S, dry, cur, betreff, { pt: 13.5, style: 'bold', color: t.lc.ink, x, width, factor: 1.18 });
// --- Salutation + body. The salutation carries the accent: it is the moment // --- Salutation + body. The salutation is set bold in ink rather than the
// the letter starts speaking, and it echoes the coloured entry titles of // accent — the bar above the subject is the page's one colour cue, and the
// the résumé. // letter's voice stays quiet so the résumé can be the loud twin.
cur.y += 5.5 * S; cur.y += 5.5 * S;
if (letter.anrede) { write(doc, S, dry, cur, letter.anrede, { pt: 10.2, style: 'bold', color: t.lc.accent, x, width, factor: 1.4 }); cur.y += 2.8 * S; } if (letter.anrede) { write(doc, S, dry, cur, letter.anrede, { pt: 10.2, style: 'bold', color: t.lc.ink, x, width, factor: 1.4 }); cur.y += 2.8 * S; }
letter.absaetze.forEach((p, i) => { letter.absaetze.forEach((p, i) => {
if (i > 0) cur.y += 3 * S; if (i > 0) cur.y += 3 * S;
write(doc, S, dry, cur, p, { pt: 10.2, color: t.lc.ink, x, width, factor: 1.52 }); write(doc, S, dry, cur, p, { pt: 10.2, color: t.lc.ink, x, width, factor: 1.52 });
@@ -2369,24 +2351,21 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
if (!dry) doc.addImage(signatur.dataUrl, signatur.format || 'PNG', x, cur.y, sigW, sigH); if (!dry) doc.addImage(signatur.dataUrl, signatur.format || 'PNG', x, cur.y, sigW, sigH);
cur.y += sigH; cur.y += sigH;
} else { } else {
// No signature image: the typed name gets a pastel highlight swiped behind // No signature image: the typed name stands alone in the accent with a
// it, like a marker stroke — the flourish that stands in for the missing // short hairline beneath it — a quiet flourish in place of the missing
// handwriting instead of leaving an empty gap. // handwriting, instead of the poster's loud pastel marker swipe.
cur.y += 12 * S; cur.y += 12 * S;
const pt = 10.6; const pt = 10.6;
const baseY = cur.y + pt * S * PT * 0.76;
write(doc, S, dry, cur, header.name, { pt, style: 'bold', color: t.lc.accent, x, width, factor: 1.25 });
if (!dry) { if (!dry) {
doc.setFont(doc.__sans, 'bold'); doc.setFont(doc.__sans, 'bold');
doc.setFontSize(pt * S); doc.setFontSize(pt * S);
const w = doc.getTextWidth(String(header.name || '')); const w = Math.min(doc.getTextWidth(String(header.name || '')), 42 * S);
// Centre the swipe on the glyphs: `write` puts the baseline at doc.setDrawColor(t.lc.accent[0], t.lc.accent[1], t.lc.accent[2]);
// cur.y + pt*PT*0.76, so a box hung off cur.y alone sits too low and the doc.setLineWidth(0.35 * S);
// capitals poke out over its top edge. doc.line(x, baseY + 1.6 * S, x + w, baseY + 1.6 * S);
const h = pt * S * PT * 1.2;
const y = cur.y + pt * S * PT * 0.76 - h * 0.72;
doc.setFillColor(t.lc.band[0], t.lc.band[1], t.lc.band[2]);
doc.roundedRect(x - 1.8 * S, y, w + 3.6 * S, h, 1.4 * S, 1.4 * S, 'F');
} }
write(doc, S, dry, cur, header.name, { pt, style: 'bold', color: t.lc.accent, x, width, factor: 1.25 });
} }
// --- Enclosures as chips, in the same pill language as the contact details --- // --- Enclosures as chips, in the same pill language as the contact details ---
@@ -2401,7 +2380,10 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
cur.y = c.y; cur.y = c.y;
} }
// --- Footer: the contact strip on a pastel bar, in the reserved bottom margin --- // --- Footer: a single hairline above a quiet, centred contact line — the
// letter's footer stays as contact information, not a second band of
// colour echoing the head. The poster résumé can carry the dots; the
// letter closes with a rule instead.
if (!dry) { if (!dry) {
const parts = []; const parts = [];
if (header.adresse) { if (header.adresse) {
@@ -2413,17 +2395,15 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
if (parts.length) { if (parts.length) {
const fh = 7.5 * S; const fh = 7.5 * S;
const fy = PAGE.h - SU.bottom - fh; const fy = PAGE.h - SU.bottom - fh;
doc.setFillColor(t.lc.band[0], t.lc.band[1], t.lc.band[2]); doc.setDrawColor(t.lc.hair[0], t.lc.hair[1], t.lc.hair[2]);
doc.rect(SU.mx, fy, SU.w, fh, 'F'); doc.setLineWidth(0.4 * S);
// Dots closing the strip at both ends — the head's confetti, answered. doc.line(SU.mx, fy, SU.r, fy);
suDot(doc, SU.mx + 4 * S, fy + fh / 2, 1.1 * S, t.lc.accent);
suDot(doc, SU.r - 4 * S, fy + fh / 2, 1.1 * S, t.lc.accent);
// Fit the strip to the band. A long address plus a long e-mail address // Fit the strip to the line. A long address plus a long e-mail address
// overruns it at a fixed size — the line then slides out over the dots and // overruns it at a fixed size. Shrink to fit; if even the floor is too
// off the band's edge. Shrink to fit; if even the floor is too wide, drop // wide, drop the postal address and keep the two ways to actually reach
// the postal address and keep the two ways to actually reach the person. // the person.
const raum = SU.w - 16 * S; // band minus the dots at both ends const raum = SU.w - 6 * S;
doc.setFont(doc.__sans, 'normal'); doc.setFont(doc.__sans, 'normal');
const passt = (text, pt) => { const passt = (text, pt) => {
doc.setFontSize(pt * S); doc.setFontSize(pt * S);
@@ -2442,7 +2422,7 @@ function composeLetterSunny(doc, t, S, dry, cur, { letter, header, job, anlagen,
if (fpt === null) fpt = 5.4; // Notnagel: lieber klein als über den Rand if (fpt === null) fpt = 5.4; // Notnagel: lieber klein als über den Rand
doc.setFontSize(fpt * S); doc.setFontSize(fpt * S);
doc.setTextColor(t.lc.accent[0], t.lc.accent[1], t.lc.accent[2]); doc.setTextColor(t.lc.muted[0], t.lc.muted[1], t.lc.muted[2]);
doc.text(strip, PAGE.w / 2, fy + fh / 2 + fpt * S * PT * 0.35, { align: 'center' }); doc.text(strip, PAGE.w / 2, fy + fh / 2 + fpt * S * PT * 0.35, { align: 'center' });
} }
} }