diff --git a/teressa-copeland-homes/src/lib/ai/field-placement.ts b/teressa-copeland-homes/src/lib/ai/field-placement.ts index 56919af..eec2c3b 100644 --- a/teressa-copeland-homes/src/lib/ai/field-placement.ts +++ b/teressa-copeland-homes/src/lib/ai/field-placement.ts @@ -41,11 +41,14 @@ export function aiCoordsToPagePdfSpace( const screenY = (coords.yPct / 100) * pageHeight; // screen Y from top const x = screenX; + // Nudge yPct down by 0.5% so the field sits on the underline rather than floating above it. + // AI tends to report the top of the blank area; we want the field aligned to the line itself. + const nudgedScreenY = screenY + pageHeight * 0.005; // PDF y = distance from BOTTOM. screenY is from top, so flip: // pdfY = pageHeight - screenY - fieldHeight (bottom edge of field) // Clamp to [0, pageHeight - fieldHeight] so AI coords near page edges // don't produce negative y values that render outside the canvas. - const rawY = pageHeight - screenY - fieldHeight; + const rawY = pageHeight - nudgedScreenY - fieldHeight; const y = Math.max(0, Math.min(rawY, pageHeight - fieldHeight)); return { x, y, width: fieldWidth, height: fieldHeight }; @@ -145,8 +148,9 @@ POSITIONING AND SIZING: - xPct and yPct are percentages from the TOP-LEFT of that specific page image - Place the field AT the blank underline — align it to sit on top of the line - For a row like "Signature __________ Date _______", create TWO separate fields: one for the signature blank and one for the date blank, each at their own x position -- widthPct: match the visual width of the underline — short blanks get small widths, long signature lines get wider -- heightPct: keep fields thin — signature/text ~2.5%, initials/date ~2% +- widthPct: match the visual width of the underline — short blanks get small widths (5-15%), long lines wider (20-30%) +- heightPct: THIN — use 1.2% for text/date/initials, 1.8% for signatures. Fields must not overlap the text above the blank. +- yPct: point to the underline itself. The field sits ON the blank line. If you place it too high it will cover the printed text above. - Do NOT place checkbox fields PREFILL: @@ -192,13 +196,13 @@ PREFILL: // Use AI-estimated size, clamped to type-appropriate min/max const sizeLimits: Record = { - 'client-signature': { minW: 100, maxW: 250, minH: 20, maxH: 40 }, - 'agent-signature': { minW: 100, maxW: 250, minH: 20, maxH: 40 }, - 'initials': { minW: 36, maxW: 80, minH: 16, maxH: 28 }, - 'agent-initials': { minW: 36, maxW: 80, minH: 16, maxH: 28 }, - 'date': { minW: 60, maxW: 130, minH: 14, maxH: 24 }, - 'text': { minW: 60, maxW: 280, minH: 14, maxH: 24 }, - 'checkbox': { minW: 16, maxW: 24, minH: 16, maxH: 24 }, + 'client-signature': { minW: 100, maxW: 250, minH: 16, maxH: 26 }, + 'agent-signature': { minW: 100, maxW: 250, minH: 16, maxH: 26 }, + 'initials': { minW: 30, maxW: 70, minH: 12, maxH: 18 }, + 'agent-initials': { minW: 30, maxW: 70, minH: 12, maxH: 18 }, + 'date': { minW: 55, maxW: 120, minH: 12, maxH: 16 }, + 'text': { minW: 40, maxW: 260, minH: 12, maxH: 16 }, + 'checkbox': { minW: 14, maxW: 20, minH: 14, maxH: 20 }, }; const lim = sizeLimits[aiField.fieldType] ?? sizeLimits['text']; const width = Math.max(lim.minW, Math.min(rawW, lim.maxW));