From a03b0e0befe0d5e1b3fde784f13f0720a575d274 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 16:59:34 +0000 Subject: [PATCH] Update PDF to kbenestad colour scheme and smart filename - Replace dark navy (30,45,69) with accent blue #2f6fed (47,111,237) throughout: issuer name, invoice word, charge-to name, table header bar, divider line, to-pay bar - Named colour constants (ACCENT, BODY, MUTED, BORDER, WHITE, STRIPE) for clarity - Filename: [ISSUER]_[YYYYMMDD]_[INVOICENUMBER].pdf with non-alphanumeric chars sanitised https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS --- app/index.html | 79 +++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/app/index.html b/app/index.html index 4df86ec..fe12f87 100644 --- a/app/index.html +++ b/app/index.html @@ -1613,19 +1613,27 @@ function buildPDF() { let y = MT; let ly = y, ry = y; - if (sName) { fb(13); tc(30,45,69); tL(sName, ML, ly); ly += 6; } - fn(8.5); tc(75,85,99); + // Accent: #2f6fed = rgb(47,111,237) Muted text: rgb(107,114,128) Body: rgb(17,24,39) + const ACCENT = [47,111,237]; + const BODY = [17,24,39]; + const MUTED = [107,114,128]; + const BORDER = [209,213,219]; + const WHITE = [255,255,255]; + const STRIPE = [249,250,251]; + + if (sName) { fb(13); tc(...ACCENT); tL(sName, ML, ly); ly += 6; } + fn(8.5); tc(...MUTED); [...sAddr, sCntry].filter(Boolean).forEach(l => { tL(l, ML, ly); ly += 4.5; }); if (sPh || sEm || sTax) { const parts = []; if (sPh) parts.push(`${td("sender-phone")}: ${sPh}`); if (sEm) parts.push(`${td("sender-email")}: ${sEm}`); if (sTax) parts.push(`${td("vat-id")}: ${sTax}`); - fn(8); tc(107,114,128); + fn(8); tc(...MUTED); sp(parts.join(" "), LW).forEach(l => { tL(l, ML, ly); ly += 4; }); ly += 1; } - fb(24); tc(30,45,69); tR(td("invoice"), XR, ry); ry += 10; + fb(24); tc(...ACCENT); tR(td("invoice"), XR, ry); ry += 10; const metaRows = [ iNo ? [td("invoice-no"), iNo] : null, iDate ? [td("invoice-date"), fmtDate(iDate)] : null, @@ -1633,20 +1641,20 @@ function buildPDF() { iCur ? [td("invoice-currency"), iCur] : null, ].filter(Boolean); metaRows.forEach(([lbl, val]) => { - fn(8.5); tc(107,114,128); tR(lbl + ":", XR - 42, ry); - fb(8.5); tc(17,24,39); tR(val, XR, ry); + fn(8.5); tc(...MUTED); tR(lbl + ":", XR - 42, ry); + fb(8.5); tc(...BODY); tR(val, XR, ry); ry += 5; }); const row1Y = Math.max(ly, ry) + 4; - dc(209,213,219); doc.setLineWidth(0.3); + dc(...BORDER); doc.setLineWidth(0.3); doc.line(ML, row1Y, XR, row1Y); let ly2 = row1Y + 5, ry2 = row1Y + 5; - fb(7); tc(107,114,128); tL(td("charge-to").toUpperCase(), ML, ly2); ly2 += 5; + fb(7); tc(...MUTED); tL(td("charge-to").toUpperCase(), ML, ly2); ly2 += 5; if (ctName) { - fb(10); tc(30,45,69); tL(ctName, ML, ly2); ly2 += 5.5; - fn(8.5); tc(17,24,39); + fb(10); tc(...ACCENT); tL(ctName, ML, ly2); ly2 += 5.5; + fn(8.5); tc(...BODY); [...ctAddr, ctCntry].filter(Boolean).forEach(l => { tL(l, ML, ly2); ly2 += 4.5; }); const ctParts = []; if (ctPh) ctParts.push(`${td("charge-to-phone")}: ${ctPh}`); @@ -1654,16 +1662,16 @@ function buildPDF() { if (ctVat) ctParts.push(`${td("vat-id")}: ${ctVat}`); if (ctReg) ctParts.push(`${td("registration-no")}: ${ctReg}`); if (ctParts.length) { - fn(8); tc(107,114,128); + fn(8); tc(...MUTED); sp(ctParts.join(" "), LW).forEach(l => { tL(l, ML, ly2); ly2 += 4; }); ly2 += 1; } } if (pTerm > 0 || showBank) { - fb(7); tc(107,114,128); tL(td("payment").toUpperCase(), XM_L, ry2); ry2 += 5; + fb(7); tc(...MUTED); tL(td("payment").toUpperCase(), XM_L, ry2); ry2 += 5; if (pTerm > 0) { const ts = `${td("payment-terms")}: ${pTerm} ${td("payment-days")}${pPayBy ? ` ${td("pay-by")}: ${pPayBy}` : ""}`; - fn(8.5); tc(17,24,39); tL(ts, XM_L, ry2); ry2 += 5; + fn(8.5); tc(...BODY); tL(ts, XM_L, ry2); ry2 += 5; } if (showBank) { const LLBL = 46; @@ -1674,22 +1682,22 @@ function buildPDF() { (pBadr1||pBadr2) ? [td("bank-address"), [pBadr1,pBadr2].filter(Boolean).join(", ")] : null, ].filter(Boolean); payRows.forEach(([lbl, val]) => { - fn(8); tc(107,114,128); tL(lbl + ":", XM_L, ry2); - fn(8.5); tc(17,24,39); + fn(8); tc(...MUTED); tL(lbl + ":", XM_L, ry2); + fn(8.5); tc(...BODY); const wrapped = sp(val, LW - LLBL - 2); wrapped.forEach((line, i) => tL(line, XM_L + LLBL, ry2 + i * 4)); ry2 += Math.max(4.5, wrapped.length * 4); }); if (pRef) { - fn(8); tc(107,114,128); tL(td("payment-ref") + ":", XM_L, ry2); - fb(8.5); tc(17,24,39); tL(pRef, XM_L + LLBL, ry2); ry2 += 5; + fn(8); tc(...MUTED); tL(td("payment-ref") + ":", XM_L, ry2); + fb(8.5); tc(...BODY); tL(pRef, XM_L + LLBL, ry2); ry2 += 5; } } } y = Math.max(ly2, ry2) + 5; - dc(30,45,69); doc.setLineWidth(0.6); + dc(...ACCENT); doc.setLineWidth(0.6); doc.line(ML, y, XR, y); y += 6; // ── LINE ITEMS TABLE ────────────────────────────────────────────────────── @@ -1700,9 +1708,9 @@ function buildPDF() { if (y + TH > PH - 40) { doc.addPage(); y = MT; } - fc(30,45,69); dc(255,255,255); doc.setLineWidth(0); + fc(...ACCENT); dc(...WHITE); doc.setLineWidth(0); doc.rect(ML, y, CW, TH, "F"); - fb(8); tc(255,255,255); + fb(8); tc(...WHITE); tL(td("qty"), xQ+2, y+4.8); tL(td("uom"), xU+2, y+4.8); tL(td("description"), xD+2, y+4.8); @@ -1727,27 +1735,27 @@ function buildPDF() { if (y + rh > PH - 30) { doc.addPage(); y = MT; } if (idx % 2 === 1) { - fc(249,250,251); dc(255,255,255); doc.setLineWidth(0); + fc(...STRIPE); dc(...WHITE); doc.setLineWidth(0); doc.rect(ML, y, CW, rh, "F"); } - dc(209,213,219); doc.setLineWidth(0.1); + dc(...BORDER); doc.setLineWidth(0.1); doc.line(ML, y+rh, XR, y+rh); const yt = y + 5; const qStr = row.qty % 1 === 0 ? String(row.qty) : fmt(row.qty); - fn(8.5); tc(17,24,39); + fn(8.5); tc(...BODY); tL(qStr, xQ+2, yt); tL(row.uomLbl, xU+2, yt); dLines.forEach((dline, li) => tL(dline, xD+2, yt + li*3.8)); - fn(8.5); tc(17,24,39); tR(fmt(row.price), xP+CP-2, yt); - fb(8.5); tc(17,24,39); tR(fmt(row.tot), XR-2, yt); + fn(8.5); tc(...BODY); tR(fmt(row.price), xP+CP-2, yt); + fb(8.5); tc(...BODY); tR(fmt(row.tot), XR-2, yt); if (row.fxNote) { const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} ` + `(${(+row.fxNote.rate).toFixed(5)} ${row.fxNote.cur} = 1 ${iCur})`; - fn(7); tc(107,114,128); + fn(7); tc(...MUTED); const fxLines = sp(fxStr, CD + CP - 4); fxLines.forEach((fl, fi) => tL(fl, xD+2, y + ROW_H + descH + 3.2 + fi * 3.5)); } @@ -1771,21 +1779,26 @@ function buildPDF() { totRows.forEach(([lbl, val]) => { if (y + TRH > PH - 20) { doc.addPage(); y = MT; } - fn(8.5); tc(107,114,128); tR(lbl, TLBX, y+4.5); - fn(8.5); tc(17,24,39); tR(val, XR-2, y+4.5); - dc(209,213,219); doc.setLineWidth(0.1); + fn(8.5); tc(...MUTED); tR(lbl, TLBX, y+4.5); + fn(8.5); tc(...BODY); tR(val, XR-2, y+4.5); + dc(...BORDER); doc.setLineWidth(0.1); doc.line(TX, y+TRH, XR, y+TRH); y += TRH; }); if (y + 9 > PH - 10) { doc.addPage(); y = MT; } - fc(30,45,69); dc(255,255,255); doc.setLineWidth(0); + fc(...ACCENT); dc(...WHITE); doc.setLineWidth(0); doc.rect(TX, y, TW, 9, "F"); - fn(9); tc(180,195,215); tR(td("to-pay"), TLBX, y+5.8); - fb(11); tc(255,255,255); tR(fmt(toPay), XR-2, y+5.8); + fn(9); tc(180,210,255); tR(td("to-pay"), TLBX, y+5.8); + fb(11); tc(...WHITE); tR(fmt(toPay), XR-2, y+5.8); y += 9; - doc.save(iNo ? `invoice-${iNo}.pdf` : "invoice.pdf"); + const safeName = s => (s || "").replace(/[^a-zA-Z0-9_\-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, ""); + const fnIssuer = safeName(sName); + const fnDate = (iDate || "").replace(/-/g, ""); + const fnNo = safeName(iNo); + const parts = [fnIssuer, fnDate, fnNo].filter(Boolean); + doc.save(parts.length ? parts.join("_") + ".pdf" : "invoice.pdf"); } // ── Update FX labels when currency or invoice currency changes ────────────────