Pathway Generator

Pathway to a Gift Generator :root{ –bg:#0b1220; –card:#111a2e; –muted:#a9b4d0; –text:#e9eeff; –accent:#7aa2ff; –border:#26304a; } body{ margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: linear-gradient(180deg, #070b14, #0b1220 40%, #070b14); color:var(–text); } .wrap{ max-width:1100px; margin:24px auto 60px; padding:0 16px; } h1{ margin:0 0 6px; font-size:26px; letter-spacing:.2px; } .sub{ color:var(–muted); margin:0 0 18px; line-height:1.35; } .grid{ display:grid; grid-template-columns: 1fr 1fr; gap:16px; align-items:start; } @media (max-width: 980px){ .grid{ grid-template-columns:1fr; } } .card{ background: rgba(17,26,46,.92); border:1px solid var(–border); border-radius:14px; padding:16px; box-shadow: 0 12px 35px rgba(0,0,0,.35); } label{ display:block; font-weight:650; margin:10px 0 6px; font-size:13px; color:#dbe3ff; } input[type=”text”], textarea{ width:100%; box-sizing:border-box; border-radius:10px; border:1px solid var(–border); background:#0c1427; color:var(–text); padding:10px 12px; outline:none; } textarea{ min-height:70px; resize:vertical; line-height:1.25; } input[type=”text”]:focus, textarea:focus{ border-color: rgba(122,162,255,.75); box-shadow: 0 0 0 3px rgba(122,162,255,.12); } .row{ display:grid; grid-template-columns: 160px 1fr; gap:12px; align-items:end; } @media (max-width: 620px){ .row{ grid-template-columns: 1fr; } } .eyebrow{ font-size:12px; color:var(–muted); margin-top:-2px; } .count{ font-size:12px; color:var(–muted); float:right; margin-top:-22px; } .btns{ display:flex; gap:10px; flex-wrap:wrap; margin-top:14px; } button{ border:0; border-radius:12px; padding:10px 14px; font-weight:700; cursor:pointer; color:#071026; background: linear-gradient(180deg, #9fb8ff, #7aa2ff); } button.secondary{ background: transparent; color:var(–text); border:1px solid var(–border); } button:active{ transform: translateY(1px); } .preview{ display:flex; flex-direction:column; gap:10px; } .canvasWrap{ background:#ffffff; border-radius:14px; overflow:hidden; border:1px solid var(–border); } .hint{ color:var(–muted); font-size:13px; line-height:1.35; margin:0; } a.download{ display:inline-block; color:#cfe0ff; text-decoration:none; border:1px solid var(–border); padding:8px 10px; border-radius:12px; width:max-content; } a.download:hover{ border-color: rgba(122,162,255,.75); } .tiny{ font-size:12px; color:var(–muted); margin:6px 0 0; }

Pathway to a Gift Generator

Enter up to 8 engagements (180 characters each). Click Generate Image to produce a PNG you can download.

Prospect Name
Engagements (Month/Year sits above the engagement text)

Tip: Use Month/Year like “JAN 2023” (or “01/2023”) — the generator prints what you type.

Preview (generated client-side). You can right-click to save, or use the download button.

// ———- Form UI generation ———- const engagementFieldsEl = document.getElementById(“engagementFields”); const MAX_ENGAGEMENTS = 8; const MAX_CHARS = 180; function makeEngagementBlock(i){ const n = i + 1; const block = document.createElement(“div”); block.style.marginTop = “14px”; block.style.padding = “12px”; block.style.border = “1px solid var(–border)”; block.style.borderRadius = “12px”; block.style.background = “#0a1020”; const title = document.createElement(“div”); title.style.fontWeight = “800”; title.style.marginBottom = “6px”; title.textContent = `Engagement ${n}`; block.appendChild(title); // Month/Year ABOVE engagement text field, but still “paired” const monthLabel = document.createElement(“label”); monthLabel.setAttribute(“for”, `m${n}`); monthLabel.textContent = “Month/Year”; block.appendChild(monthLabel); const monthInput = document.createElement(“input”); monthInput.type = “text”; monthInput.id = `m${n}`; monthInput.name = `m${n}`; monthInput.placeholder = “e.g., JAN 2025”; monthInput.maxLength = 12; block.appendChild(monthInput); const engLabel = document.createElement(“label”); engLabel.setAttribute(“for”, `e${n}`); engLabel.style.marginTop = “10px”; engLabel.textContent = `Engagement ${n} (max ${MAX_CHARS} chars)`; block.appendChild(engLabel); const counter = document.createElement(“div”); counter.className = “count”; counter.id = `c${n}`; counter.textContent = `0/${MAX_CHARS}`; block.appendChild(counter); const ta = document.createElement(“textarea”); ta.id = `e${n}`; ta.name = `e${n}`; ta.maxLength = MAX_CHARS; ta.placeholder = “Type engagement details…”; ta.addEventListener(“input”, () => { document.getElementById(`c${n}`).textContent = `${ta.value.length}/${MAX_CHARS}`; }); block.appendChild(ta); return block; } for(let i=0;i<MAX_ENGAGEMENTS;i++){ engagementFieldsEl.appendChild(makeEngagementBlock(i)); } // ———- Canvas drawing ———- const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const statusEl = document.getElementById("status"); const downloadLink = document.getElementById("downloadLink"); // Ribbon palette (feel free to change to match your brand) const colors = [ "#2F3D73", // navy "#6C7CB8", // slate blue "#7DA8A1", // teal "#F2A15D", // orange "#B6654B", // rust "#8B4B46", // maroon "#C06B83", // rose "#5F6B8A" // steel ]; function roundRect(ctx, x, y, w, h, r){ const radius = Math.min(r, h/2, w/2); ctx.beginPath(); ctx.moveTo(x+radius, y); ctx.arcTo(x+w, y, x+w, y+h, radius); ctx.arcTo(x+w, y+h, x, y+h, radius); ctx.arcTo(x, y+h, x, y, radius); ctx.arcTo(x, y, x+w, y, radius); ctx.closePath(); } function drawNode(x, y, outerR=22, innerR=10){ // outer white ring ctx.save(); ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.arc(x, y, outerR, 0, Math.PI*2); ctx.fill(); // inner hole ctx.fillStyle = "#0f172a"; ctx.beginPath(); ctx.arc(x, y, innerR, 0, Math.PI*2); ctx.fill(); ctx.restore(); } function wrapText(ctx, text, x, y, maxWidth, lineHeight, maxLines){ const words = text.split(/\s+/).filter(Boolean); let line = ""; let lines = 0; for (let i=0; i maxWidth && line){ ctx.fillText(line, x, y); line = words[i]; y += lineHeight; lines++; if (maxLines && lines >= maxLines) return; } else { line = testLine; } } if (line && (!maxLines || lines < maxLines)){ ctx.fillText(line, x, y); } } function drawPathway({ prospect, items }){ // Background ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillStyle = "#ffffff"; ctx.fillRect(0,0,canvas.width,canvas.height); // Title ctx.fillStyle = "#6b7aa6"; ctx.font = "700 56px system-ui, -apple-system, Segoe UI, Roboto, Arial"; ctx.textAlign = "center"; ctx.fillText("Pathway to a Gift", canvas.width/2, 90); ctx.fillStyle = "#111827"; ctx.font = "600 40px system-ui, -apple-system, Segoe UI, Roboto, Arial"; ctx.fillText(prospect || "Prospect Name", canvas.width/2, 140); // Layout constants const marginX = 90; const topY = 210; const segH = 120; const gap = 26; const ribbonW = canvas.width – marginX*2; const radius = 60; // Text styles const monthFont = "800 34px system-ui, -apple-system, Segoe UI, Roboto, Arial"; const bodyFont = "600 24px system-ui, -apple-system, Segoe UI, Roboto, Arial"; for(let i=0; i<MAX_ENGAGEMENTS; i++){ const y = topY + i*(segH + gap); const dirLTR = (i % 2 === 0); // alternate direction visually const x = marginX; const color = colors[i % colors.length]; // Ribbon segment ctx.save(); ctx.fillStyle = color; roundRect(ctx, x, y, ribbonW, segH, radius); ctx.fill(); ctx.restore(); // Nodes at both ends (gives the “pathway” feel) const leftNodeX = x; const rightNodeX = x + ribbonW; const nodeY = y + segH/2; drawNode(leftNodeX, nodeY); drawNode(rightNodeX, nodeY); // Month/Year label placed at the "start side" (alternates L/R) const month = (items[i]?.month || "").trim(); ctx.save(); ctx.font = monthFont; ctx.fillStyle = "#111827"; ctx.textBaseline = "middle"; if(dirLTR){ ctx.textAlign = "left"; ctx.fillText(month || "", x – 10, nodeY); } else { ctx.textAlign = "right"; ctx.fillText(month || "", x + ribbonW + 10, nodeY); } ctx.restore(); // Engagement text const engagement = (items[i]?.text || "").trim(); ctx.save(); ctx.font = bodyFont; ctx.fillStyle = "#111827"; ctx.textBaseline = "top"; // Keep text away from nodes const padLeft = 70; const padRight = 70; const textBoxX = x + padLeft; const textBoxY = y + 26; const textMaxW = ribbonW – padLeft – padRight; const lineH = 30; // If empty, don’t print placeholder lines (keeps it clean) if(engagement){ // Wrap into up to ~3 lines ctx.textAlign = "left"; wrapText(ctx, engagement, textBoxX, textBoxY, textMaxW, lineH, 3); } ctx.restore(); } // Update download link const dataUrl = canvas.toDataURL("image/png"); downloadLink.href = dataUrl; statusEl.textContent = "Image generated."; } function getFormData(){ const prospect = document.getElementById("prospect").value.trim(); const items = []; for(let i=1;i { e.preventDefault(); const data = getFormData(); drawPathway(data); }); document.getElementById(“fillExample”).addEventListener(“click”, () => { document.getElementById(“prospect”).value = “Liangxiao Zhu”; const example = [ { month:”JAN 2023″, text:”Initial identification and early research, laying the foundation for future engagement.” }, { month:”JAN 2025″, text:”Lead resurfaced and connected internally, ensuring the prospect was back on the radar.” }, { month:”FEB 2025″, text:”Introductory visit confirmed philanthropic interest and alignment with priorities.” }, { month:”JUN 2025″, text:”Campus visit and tour inspired a follow-up philanthropy conversation.” }, { month:”JUN 2025″, text:”Proposal shaped and refined; naming opportunity details clarified.” }, { month:”JUL 2025″, text:”Proposal presented; verbal commitments secured including giving society pledge.” }, { month:”AUG 2025″, text:”Gift agreement drafted and finalized based on internal insights and details.” }, { month:”SEP 2025″, text:”Agreement signed: scholarship established and naming gift secured.” }, ]; for(let i=1;i { document.getElementById(“prospect”).value = “”; for(let i=1;i<=MAX_ENGAGEMENTS;i++){ document.getElementById(`m${i}`).value = ""; document.getElementById(`e${i}`).value = ""; document.getElementById(`c${i}`).textContent = `0/${MAX_CHARS}`; } statusEl.textContent = ""; ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillStyle = "#ffffff"; ctx.fillRect(0,0,canvas.width,canvas.height); downloadLink.href = "#"; }); // Draw a blank canvas on load ctx.fillStyle = "#ffffff"; ctx.fillRect(0,0,canvas.width,canvas.height);