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 ────────────────