diff --git a/app/index.html b/app/index.html
index f76d163..e457bad 100644
--- a/app/index.html
+++ b/app/index.html
@@ -1207,22 +1207,28 @@ async function generatePDF() {
const doc = await PDFDocument.create();
const pageW = CFG['page-size'] === 'letter' ? 612 : 595.28;
const pageH = CFG['page-size'] === 'letter' ? 792 : 841.89;
- const M = { top: 50, bottom: 65, left: 50, right: 50 };
+ const M = { top: 52, bottom: 65, left: 48, right: 48 };
const W = pageW - M.left - M.right;
const fontBody = await doc.embedFont(StandardFonts.Helvetica);
const fontBold = await doc.embedFont(StandardFonts.HelveticaBold);
const fontMono = await doc.embedFont(StandardFonts.Courier);
- const sz = CFG['font-size'] || 10;
+ const sz = CFG['font-size'] || 10;
const szSm = sz - 1;
- const szLg = sz + 4;
- const szXl = sz + 6;
- const lh = sz + 4;
+ const szXs = Math.max(sz - 2, 7); // eyebrow labels
+ const szLg = sz + 4; // section headings, grand total figure
+ const lh = sz + 5; // line height
+
+ // ── kBenestad color tokens ────────────────────────────────────────────────
+ const clrAccent = parseHex(CFG['accent-colour'] || '#2F6FED');
+ const clrText = rgb(0.078, 0.094, 0.118); // #14181E
+ const clrTextSoft = rgb(0.227, 0.263, 0.310); // #3A434F
+ const clrMuted = rgb(0.373, 0.412, 0.459); // #5F6975
+ const clrBorder = rgb(0.890, 0.918, 0.933); // #E3E7EE
+ const clrBorderStrong = rgb(0.827, 0.851, 0.886); // #D3D9E2
+ const clrSurface2 = rgb(0.973, 0.976, 0.984); // #F8F9FB
+ const clrWarn = rgb(0.788, 0.318, 0.000); // out-of-period date
- const accent = parseHex(CFG['accent-colour'] || '#2F6FED');
- const black = rgb(0.13, 0.13, 0.13);
- const gray = rgb(0.45, 0.45, 0.45);
- const lineCol = rgb(0.75, 0.75, 0.75);
const baseCur = state.baseCurrency;
let logoImage = null;
@@ -1233,6 +1239,26 @@ async function generatePDF() {
const receiptRefs = [];
let justBroke = false;
+ // ── Drawing helpers ───────────────────────────────────────────────────────
+
+ // Eyebrow label: uppercase, bold, muted, szXs
+ function lbl(text, x, yy) {
+ pg.drawText(text.toUpperCase(), { x, y: yy, size: szXs, font: fontBold, color: clrMuted });
+ }
+ // Body value in Helvetica
+ function val(text, x, yy, extra) {
+ pg.drawText(text, { x, y: yy, size: sz, font: fontBody, color: clrText, ...(extra||{}) });
+ }
+ // Mono value (amounts, dates, FX rates) in Courier
+ function mono(text, x, yy, extra) {
+ pg.drawText(text, { x, y: yy, size: sz, font: fontMono, color: clrText, ...(extra||{}) });
+ }
+ // 3pt accent left-stripe for item/section headers
+ function accentStripe(yy) {
+ pg.drawRectangle({ x: M.left, y: yy - 1, width: 3, height: lh + 1, color: clrAccent });
+ }
+
+ // ── Page management ───────────────────────────────────────────────────────
function addPage(isFirst) {
pg = doc.addPage([pageW, pageH]);
pages.push(pg);
@@ -1246,156 +1272,216 @@ async function generatePDF() {
if (y - h < M.bottom) addPage(false);
}
+ // Continuation header: light strip with staff + period
function drawContHeader() {
- pg.drawText(state.staff, { x: M.left, y, size: sz, font: fontBold, color: black });
- const periodStr = `Period: ${state.periodFrom} to ${state.periodTo}`;
- const pw = fontBody.widthOfTextAtSize(periodStr, sz);
- pg.drawText(periodStr, { x: M.left + W - pw, y, size: sz, font: fontBody, color: gray });
- y -= lh + 2;
- pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:1.5, color:accent });
- y -= lh;
+ const stripH = szXs + lh + 14;
+ pg.drawRectangle({ x: 0, y: y - stripH, width: pageW, height: stripH, color: clrSurface2 });
+ pg.drawLine({ start:{x:0, y}, end:{x:pageW, y}, thickness:0.5, color:clrBorder });
+ pg.drawLine({ start:{x:0, y:y-stripH}, end:{x:pageW, y:y-stripH}, thickness:0.5, color:clrBorder });
+
+ const labY = y - 7;
+ const valY = labY - szXs - 5;
+ lbl('Staff', M.left, labY);
+ lbl('Period', M.left + W * 0.45, labY);
+ pg.drawText(state.staff, { x:M.left, y:valY, size:sz, font:fontBold, color:clrText });
+ mono(`${state.periodFrom} to ${state.periodTo}`, M.left + W * 0.45, valY);
+
+ y -= stripH + 12;
}
+ // ── Page 1 ────────────────────────────────────────────────────────────────
addPage(true);
- const mm10 = 10 * 2.83465;
- if (logoImage) {
- const maxW = (CFG['logo-maxwidth'] || 4) * 28.3465;
- const scale = Math.min(maxW / logoImage.width, 50 / logoImage.height, 1);
- const lw = logoImage.width * scale, lhh = logoImage.height * scale;
- const logoTop = pageH - mm10;
- pg.drawImage(logoImage, { x: mm10, y: logoTop - lhh, width: lw, height: lhh });
- y = Math.min(y, logoTop - lhh - 8);
- } else if (CFG.organization) {
- pg.drawText(CFG.organization, { x: M.left, y, size: szLg, font: fontBold, color: accent });
- y -= szLg + 8;
- }
- const titleStr = 'REIMBURSEMENT FORM';
- const tw = fontBold.widthOfTextAtSize(titleStr, szLg);
- pg.drawText(titleStr, { x: M.left + W - tw, y, size: szLg, font: fontBold, color: accent });
- y -= szLg + 8;
+ // Header: brand block left | doc title right
+ const hdrTopY = y;
+ const boxSize = 44;
+ if (logoImage) {
+ const maxWLogo = (CFG['logo-maxwidth'] || 4) * 28.3465;
+ const scale = Math.min(maxWLogo / logoImage.width, boxSize / logoImage.height, 1);
+ const lw = logoImage.width * scale, lhImg = logoImage.height * scale;
+ pg.drawImage(logoImage, { x: M.left, y: hdrTopY - lhImg, width: lw, height: lhImg });
+ } else {
+ // Initials box (surface-2 fill, border, accent initials)
+ const raw = (CFG.organization || 'ORG').trim();
+ const initials = raw.split(/\s+/).map(w => w[0] || '').join('').toUpperCase().slice(0, 2) || '??';
+ pg.drawRectangle({ x: M.left, y: hdrTopY - boxSize, width: boxSize, height: boxSize,
+ color: clrSurface2, borderColor: clrBorder, borderWidth: 0.5 });
+ const initW = fontBold.widthOfTextAtSize(initials, szLg);
+ pg.drawText(initials, { x: M.left + (boxSize - initW) / 2, y: hdrTopY - boxSize / 2 - szLg * 0.35,
+ size: szLg, font: fontBold, color: clrAccent });
+ }
+
+ // Org name + subtitle
+ const orgX = M.left + boxSize + 11;
+ pg.drawText(CFG.organization || '', { x: orgX, y: hdrTopY - 15, size: sz + 2, font: fontBold, color: clrText });
+ pg.drawText('Expense reimbursement', { x: orgX, y: hdrTopY - 15 - (sz + 2) - 4, size: szSm, font: fontBody, color: clrMuted });
+
+ // Title + claim date (right-aligned)
+ const titleStr = 'Reimbursement';
+ const titleW = fontBold.widthOfTextAtSize(titleStr, szLg);
+ pg.drawText(titleStr, { x: M.left + W - titleW, y: hdrTopY - 15, size: szLg, font: fontBold, color: clrText });
+
+ const nowD = new Date();
+ const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+ const claimDate = `Claim · ${nowD.getDate()} ${MONTHS[nowD.getMonth()]} ${nowD.getFullYear()}`;
+ const claimDateW = fontMono.widthOfTextAtSize(claimDate, szSm);
+ pg.drawText(claimDate, { x: M.left + W - claimDateW, y: hdrTopY - 15 - szLg - 4, size: szSm, font: fontMono, color: clrMuted });
+
+ y = hdrTopY - boxSize - 10;
+
+ // Hairline below header
+ pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.5, color:clrBorder });
+ y -= 12;
+
+ // Intro text
if (CFG.intro) {
const introLines = wrapText(CFG.intro, fontBody, sz, W);
- introLines.forEach(line => { pg.drawText(line, {x:M.left, y, size:sz, font:fontBody, color:gray}); y -= lh; });
- y -= 4;
+ introLines.forEach(line => { pg.drawText(line, {x:M.left, y, size:sz, font:fontBody, color:clrMuted}); y -= lh; });
+ y -= 6;
}
- const col2 = W * 0.5;
- const col3 = W * 0.8;
+ // Staff / Period / Currency info block
+ const infoH = szXs + lh + 14;
+ pg.drawRectangle({ x: M.left - 8, y: y - infoH, width: W + 16, height: infoH, color: clrSurface2 });
+ pg.drawLine({ start:{x:M.left-8, y}, end:{x:M.left+W+8, y}, thickness:0.5, color:clrBorder });
+ pg.drawLine({ start:{x:M.left-8, y:y-infoH}, end:{x:M.left+W+8, y:y-infoH}, thickness:0.5, color:clrBorder });
- pg.drawText('Staff', {x:M.left, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Period', {x:M.left+col2, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Currency', {x:M.left+col3, y, size:szSm-1, font:fontBold, color:gray});
- y -= lh;
- pg.drawText(state.staff, {x:M.left, y, size:sz, font:fontBody, color:black});
- pg.drawText(`${state.periodFrom} to ${state.periodTo}`, {x:M.left+col2, y, size:sz, font:fontBody, color:black});
- pg.drawText(baseCur, {x:M.left+col3, y, size:sz, font:fontBold, color:black});
- y -= lh + 6;
+ const iLabY = y - 7;
+ const iValY = iLabY - szXs - 5;
+ const iC2 = W * 0.45, iC3 = W * 0.78;
- pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:1.5, color:accent});
- y -= lh;
+ lbl('Staff', M.left, iLabY);
+ lbl('Period', M.left + iC2, iLabY);
+ lbl('Currency', M.left + iC3, iLabY);
+ pg.drawText(state.staff, { x: M.left, y: iValY, size: sz, font: fontBold, color: clrText });
+ mono(`${state.periodFrom} to ${state.periodTo}`, M.left + iC2, iValY);
+ pg.drawText(baseCur, { x: M.left + iC3, y: iValY, size: sz, font: fontBold, color: clrAccent });
+
+ y -= infoH + 14;
+
+ // ── Items ─────────────────────────────────────────────────────────────────
state.items.forEach(item => {
- needSpace(lh * 6);
- pg.drawText('ITEM / PROJECT / TRAVEL', {x:M.left, y, size:szSm, font:fontBold, color:accent});
- const subStr = `Subtotal: ${baseCur} ${fmtAmt(item._subtotal)}`;
- const subW = fontBold.widthOfTextAtSize(subStr, sz);
- pg.drawText(subStr, {x:M.left+W-subW, y, size:sz, font:fontBold, color:accent});
- y -= lh;
- pg.drawText(item.name, {x:M.left, y, size:sz, font:fontBody, color:black});
- y -= lh + 4;
+ needSpace(lh * 7);
+ // Section header: accent stripe + item name left, subtotal right
+ accentStripe(y);
+ pg.drawText(item.name || '(no name)', { x: M.left + 8, y, size: sz, font: fontBold, color: clrTextSoft });
+ const subStr = `${baseCur} ${fmtAmt(item._subtotal)}`;
+ const subW = fontMono.widthOfTextAtSize(subStr, sz);
+ pg.drawText(subStr, { x: M.left + W - subW, y, size: sz, font: fontMono, color: clrAccent });
+ y -= lh + 2;
+
+ // Hairline below section header
+ pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.5, color:clrBorder });
+ y -= 8;
+
+ // Lines
item.lines.forEach((ln, li) => {
- needSpace(lh * 7);
+ needSpace(lh * 9);
if (li > 0 && !justBroke) {
y -= 4;
- pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:0.3, color:lineCol});
+ pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.3, color:clrBorder });
y -= 8;
}
- const c1=0, c2=W*0.22, c3=W*0.68, c4=W*0.82;
- pg.drawText('Date', {x:M.left+c1, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Vendor', {x:M.left+c2, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Currency', {x:M.left+c3, y, size:szSm-1, font:fontBold, color:gray});
- const fxLbl = 'FX rate'; pg.drawText(fxLbl, {x:M.left+W-fontBold.widthOfTextAtSize(fxLbl,szSm-1), y, size:szSm-1, font:fontBold, color:gray});
- y -= lh;
+ const c1=0, c2=W*0.22, c3=W*0.64;
+
+ // Row 1 — Date | Vendor | Currency | FX rate
+ lbl('Date', M.left + c1, y);
+ lbl('Vendor', M.left + c2, y);
+ lbl('Currency', M.left + c3, y);
+ const fxLblStr = 'FX RATE';
+ pg.drawText(fxLblStr, { x: M.left + W - fontBold.widthOfTextAtSize(fxLblStr, szXs), y,
+ size: szXs, font: fontBold, color: clrMuted });
+ y -= szXs + 4;
+
const dateInPeriod = isDateInPeriod(ln.date);
- const dateColor = dateInPeriod ? black : rgb(0.9, 0.33, 0);
- pg.drawText((ln.date || '–') + (dateInPeriod ? '' : ' (!)'), {x:M.left+c1, y, size:sz, font:fontBody, color:dateColor});
- pg.drawText(truncate(ln.vendor, fontBody, sz, (c3-c2)-8), {x:M.left+c2, y, size:sz, font:fontBody, color:black});
- pg.drawText(ln.currency, {x:M.left+c3, y, size:sz, font:fontBody, color:black});
+ mono((ln.date || '–') + (dateInPeriod ? '' : ' (!)'), M.left + c1, y,
+ { color: dateInPeriod ? clrText : clrWarn });
+ val(truncate(ln.vendor, fontBody, sz, (c3 - c2) - 8), M.left + c2, y);
+ val(ln.currency || '–', M.left + c3, y);
const fxStr = ln.currency === baseCur ? '–' : parseFloat(ln.fxRate).toFixed(5);
- const fxW = fontBody.widthOfTextAtSize(fxStr, sz);
- pg.drawText(fxStr, {x:M.left+W-fxW, y, size:sz, font:fontBody, color:black});
- y -= lh + 2;
+ const fxW = fontMono.widthOfTextAtSize(fxStr, sz);
+ mono(fxStr, M.left + W - fxW, y);
+ y -= lh + 4;
- pg.drawText('Description', {x:M.left, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Receipt', {x:M.left+c3, y, size:szSm-1, font:fontBold, color:gray});
- const amtLbl = 'Amount'; pg.drawText(amtLbl, {x:M.left+W-fontBold.widthOfTextAtSize(amtLbl,szSm-1), y, size:szSm-1, font:fontBold, color:gray});
- y -= lh;
- pg.drawText(truncate(ln.description, fontBody, sz, (c3)-8), {x:M.left, y, size:sz, font:fontBody, color:black});
- pg.drawText(ln.hasReceipt ? 'Yes' : 'No', {x:M.left+c3, y, size:sz, font:fontBody, color:black});
+ // Row 2 — Description | Receipt | Amount
+ lbl('Description', M.left, y);
+ lbl('Receipt', M.left + c3, y);
+ const amtLblStr = 'AMOUNT';
+ pg.drawText(amtLblStr, { x: M.left + W - fontBold.widthOfTextAtSize(amtLblStr, szXs), y,
+ size: szXs, font: fontBold, color: clrMuted });
+ y -= szXs + 4;
+
+ val(truncate(ln.description, fontBody, sz, c3 - 8), M.left, y);
+ val(ln.hasReceipt ? 'Yes' : 'No', M.left + c3, y);
const amtStr = `${ln.currency} ${fmtAmt(ln.amount)}`;
- const amtW = fontBody.widthOfTextAtSize(amtStr, sz);
- pg.drawText(amtStr, {x:M.left+W-amtW, y, size:sz, font:fontBody, color:black});
- y -= lh + 2;
+ const amtW = fontMono.widthOfTextAtSize(amtStr, sz);
+ mono(amtStr, M.left + W - amtW, y);
+ y -= lh + 4;
- pg.drawText('Account', {x:M.left, y, size:szSm-1, font:fontBold, color:gray});
- pg.drawText('Program', {x:M.left+W*0.5, y, size:szSm-1, font:fontBold, color:gray});
- y -= lh;
- pg.drawText(ln.account || '–', {x:M.left, y, size:sz, font:fontBody, color:black});
+ // Row 3 — Account | Program
+ lbl('Account', M.left, y);
+ lbl('Program', M.left + W * 0.5, y);
+ y -= szXs + 4;
+
+ val(truncate(ln.account || '–', fontBody, sz, W * 0.5 - 8), M.left, y);
const progs = ln.programs || [];
if (progs.length <= 1) {
const pe = progs[0] || {};
const progStr = pe.program === 'Other' ? `Other: ${pe.programOther}` : (pe.program || '–');
- pg.drawText(truncate(progStr, fontBody, sz, W * 0.5 - 8), {x:M.left+W*0.5, y, size:sz, font:fontBody, color:black});
+ val(truncate(progStr, fontBody, sz, W * 0.5 - 8), M.left + W * 0.5, y);
y -= lh;
} else {
- const lineBaseAmt = (() => { const a = parseFloat(ln.amount)||0, r = parseFloat(ln.fxRate)||1; return r>0?a/r:0; })();
+ const lineBaseAmt = (() => { const a=parseFloat(ln.amount)||0, r=parseFloat(ln.fxRate)||1; return r>0?a/r:0; })();
progs.forEach((pe, pi) => {
if (pi > 0) needSpace(lh);
const progStr = pe.program === 'Other' ? `Other: ${pe.programOther}` : (pe.program || '–');
const pct = parseFloat(pe.percent) || 0;
const progAmt = lineBaseAmt * pct / 100;
- const suffix = ` ${pct.toFixed(2)}% – ${baseCur} ${fmtAmt(progAmt)}`;
- pg.drawText(truncate(progStr, fontBody, sz, W * 0.45 - 8), {x:M.left+W*0.5, y, size:sz, font:fontBody, color:black});
- const sfxW = fontBody.widthOfTextAtSize(suffix, szSm);
- pg.drawText(suffix, {x:M.left+W-sfxW, y, size:szSm, font:fontBody, color:gray});
+ const suffix = `${pct.toFixed(2)}% · ${baseCur} ${fmtAmt(progAmt)}`;
+ val(truncate(progStr, fontBody, sz, W * 0.42 - 8), M.left + W * 0.5, y);
+ const sfxW = fontMono.widthOfTextAtSize(suffix, szSm);
+ pg.drawText(suffix, { x: M.left + W - sfxW, y, size: szSm, font: fontMono, color: clrMuted });
y -= lh;
});
}
+ // Receipt page reference (filled in after receipt pages are built)
if (ln.hasReceipt && ln.receipts.length > 0) {
ln.receipts.forEach((r, ri) => {
const key = `${ln.id}-${ri}`;
- const refX = M.left + W * 0.6;
- receiptRefs.push({ pageIdx: pages.length - 1, x: refX, y, key });
+ receiptRefs.push({ pageIdx: pages.length - 1, x: M.left + W * 0.55, y, key });
y -= lh;
});
} else if (!ln.hasReceipt) {
needSpace(lh * 2);
- pg.drawText('Explanation for no receipt:', {x:M.left, y, size:szSm-1, font:fontBold, color:gray});
- y -= lh;
+ lbl('Reason for no receipt', M.left, y);
+ y -= szXs + 4;
const explLines = wrapText(ln.noReceiptExplanation || '–', fontBody, sz, W);
- explLines.forEach(line => {
- needSpace(lh);
- pg.drawText(line, {x:M.left, y, size:sz, font:fontBody, color:black});
- y -= lh;
- });
+ explLines.forEach(line => { needSpace(lh); val(line, M.left, y); y -= lh; });
}
+
y -= 6;
});
+
+ y -= 10;
});
- needSpace(lh * 2);
- pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:3, color:accent});
+ // Grand total
+ needSpace(lh * 3 + 6);
+ y -= 6;
+ pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:1.5, color:clrBorderStrong });
y -= lh;
- const gtStr = `Total reimbursement claim: ${baseCur} ${fmtAmt(state._grandTotal)}`;
- const gtW = fontBold.widthOfTextAtSize(gtStr, sz + 2);
- pg.drawText(gtStr, {x: M.left + W - gtW, y, size: sz+2, font: fontBold, color: accent});
- // ---- RECEIPT PAGES ----
+ pg.drawText('Total reimbursement claim', { x: M.left, y, size: sz, font: fontBold, color: clrTextSoft });
+ const gtStr = `${baseCur} ${fmtAmt(state._grandTotal)}`;
+ const gtW = fontBold.widthOfTextAtSize(gtStr, szLg);
+ pg.drawText(gtStr, { x: M.left + W - gtW, y: y - (szLg - sz) / 2, size: szLg, font: fontBold, color: clrAccent });
+
+ // ── Receipt pages ─────────────────────────────────────────────────────────
const formPageCount = pages.length;
const receiptPageMap = {};
@@ -1416,7 +1502,7 @@ async function generatePDF() {
const ep = doc.addPage([pageW, pageH]);
pages.push(ep);
ep.drawText(`Could not embed: ${r.name}`, {x:M.left, y:pageH-M.top, size:sz, font:fontBody, color:rgb(0.8,0,0)});
- ep.drawText(String(e.message || e), {x:M.left, y:pageH-M.top-lh, size:szSm, font:fontBody, color:gray});
+ ep.drawText(String(e.message || e), {x:M.left, y:pageH-M.top-lh, size:szSm, font:fontBody, color:clrMuted});
}
} else {
const rp = doc.addPage([pageW, pageH]);
@@ -1425,13 +1511,10 @@ async function generatePDF() {
let img;
if (r.type === 'image/png') img = await doc.embedPng(r.data);
else img = await doc.embedJpg(r.data);
- const maxW2 = pageW - M.left - M.right;
- const maxH2 = pageH - M.top - M.bottom;
+ const maxW2 = pageW - M.left - M.right, maxH2 = pageH - M.top - M.bottom;
const sc = Math.min(maxW2 / img.width, maxH2 / img.height, 1);
const iw = img.width * sc, ih = img.height * sc;
- const ix = M.left + (maxW2 - iw) / 2;
- const iy = M.bottom + (maxH2 - ih) / 2;
- rp.drawImage(img, {x:ix, y:iy, width:iw, height:ih});
+ rp.drawImage(img, { x: M.left + (maxW2-iw)/2, y: M.bottom + (maxH2-ih)/2, width:iw, height:ih });
} catch (e) {
rp.drawText(`Could not embed: ${r.name}`, {x:M.left, y:pageH-M.top, size:sz, font:fontBody, color:rgb(0.8,0,0)});
}
@@ -1441,31 +1524,33 @@ async function generatePDF() {
}
}
+ // ── Back-fill receipt page references ─────────────────────────────────────
receiptRefs.forEach(ref => {
const pageNum = receiptPageMap[ref.key];
- if (pageNum != null) {
- const pg2 = pages[ref.pageIdx];
- pg2.drawText(`See page ${pageNum} for receipt`, {x:ref.x, y:ref.y, size:szSm, font:fontBody, color:gray});
- }
+ if (pageNum != null)
+ pages[ref.pageIdx].drawText(`See page ${pageNum} for receipt`,
+ { x:ref.x, y:ref.y, size:szSm, font:fontBody, color:clrMuted });
});
+ // ── Footers on every page ─────────────────────────────────────────────────
const totalPages = pages.length;
const nowTs = new Date();
const printed = `${nowTs.getFullYear()}-${String(nowTs.getMonth()+1).padStart(2,'0')}-${String(nowTs.getDate()).padStart(2,'0')} ${String(nowTs.getHours()).padStart(2,'0')}:${String(nowTs.getMinutes()).padStart(2,'0')}`;
pages.forEach((p, i) => {
const fy = M.bottom - 30;
- p.drawLine({start:{x:M.left, y:fy+18}, end:{x:M.left+W, y:fy+18}, thickness:0.5, color:lineCol});
- p.drawText('Reimbursement form', {x:M.left, y:fy, size:szSm-1, font:fontBody, color:gray});
- p.drawText(state.staff, {x:M.left, y:fy-lh+2, size:szSm-1, font:fontBody, color:gray});
+ p.drawLine({ start:{x:M.left, y:fy+18}, end:{x:M.left+W, y:fy+18}, thickness:0.5, color:clrBorder });
+ p.drawText('Reimbursement form', { x:M.left, y:fy, size:szXs, font:fontBody, color:clrMuted });
+ p.drawText(state.staff, { x:M.left, y:fy-lh+2, size:szXs, font:fontBody, color:clrMuted });
const pgStr = `Page ${i+1}/${totalPages}`;
- const pgW2 = fontBody.widthOfTextAtSize(pgStr, szSm-1);
- p.drawText(pgStr, {x:M.left+W-pgW2, y:fy, size:szSm-1, font:fontBody, color:gray});
+ const pgW2 = fontBody.widthOfTextAtSize(pgStr, szXs);
+ p.drawText(pgStr, { x:M.left+W-pgW2, y:fy, size:szXs, font:fontBody, color:clrMuted });
const prStr = `Printed: ${printed}`;
- const prW = fontBody.widthOfTextAtSize(prStr, szSm-1);
- p.drawText(prStr, {x:M.left+W-prW, y:fy-lh+2, size:szSm-1, font:fontBody, color:gray});
+ const prW = fontBody.widthOfTextAtSize(prStr, szXs);
+ p.drawText(prStr, { x:M.left+W-prW, y:fy-lh+2, size:szXs, font:fontBody, color:clrMuted });
});
+ // ── Save and download ──────────────────────────────────────────────────────
const pdfBytes = await doc.save();
const blob = new Blob([pdfBytes], {type:'application/pdf'});
const url = URL.createObjectURL(blob);