Files
calvana/pledge-now-pay-later/scripts/generate-brand-photos-4.ts
Omair Saleh ef37ca0c18 Fix payment flexibility quote length and orphan word
- Shorten quote 03 from 'Can I split it across a few months?' to 'Can I pay monthly?' for column symmetry
- Add nbsp between 'money' and 'arriving' to prevent orphan line break
2026-03-04 13:58:42 +08:00

274 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Generate 30 PRODUCT-SPECIFIC brand photos — Batch 4
* Every image maps to a moment in the PNPL user journey:
* Setup → Event → Pledge → Follow-up → Payment → Celebration
*
* Run: npx tsx scripts/generate-brand-photos-4.ts
*/
import fs from "fs";
import path from "path";
import dotenv from "dotenv";
dotenv.config();
const API_KEY = process.env.GEMINI_API_KEY;
if (!API_KEY) { console.error("Missing GEMINI_API_KEY"); process.exit(1); }
const MODEL = "gemini-3-pro-image-preview";
const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${API_KEY}`;
const OUTPUT_DIR = path.join(process.cwd(), "public/images/brand");
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const STYLE = `Photorealistic documentary photography. Sony A7III, shallow depth of field, available light. Candid fly-on-the-wall. Nobody looks at camera. No stock aesthetic. No staged poses. No alcohol or wine ever. No visible watermarks. Young modern British-Muslim community. South Asian and Arab features.`;
interface Photo { filename: string; prompt: string; }
const PHOTOS: Photo[] = [
// ═══════════════════════════════════════════
// PRE-EVENT SETUP (6 photos)
// The charity admin preparing pledge links
// ═══════════════════════════════════════════
{
filename: "product-setup-01-creating-link.jpg",
prompt: `16:9 landscape. A young British-Muslim woman in hijab sitting at a desk in a small charity office, working on a laptop. She's leaning forward slightly, one hand on the trackpad, focused and purposeful. On the desk beside the laptop: a printed event programme, a mug of tea, and a notepad with handwritten notes including a URL and amounts. The laptop screen casts a soft glow on her face (screen content NOT visible). Evening, dark window behind. Setting up a fundraising campaign before the big event. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-setup-02-printing-qr.jpg",
prompt: `16:9 landscape. Close-up of a small desktop printer outputting A5 cards, each with a large QR code printed on them. A young British-Muslim man's hands are picking up the freshly printed cards and stacking them neatly. Beside the printer: a paper cutter, a stack of already-cut tent cards, and a roll of sellotape. The office desk is cluttered with event prep materials — lanyards, name badges, a hi-vis vest. Fluorescent overhead light. The meticulous preparation before a fundraising event. 50mm f/2.8. ${STYLE}`,
},
{
filename: "product-setup-03-tent-cards.jpg",
prompt: `3:4 portrait. Overhead birds-eye shot of a desk covered with neat rows of folded tent cards, each showing a QR code on the front. A young person's hands are folding the last few cards along a scored line. A ruler and craft knife visible. The cards are white with minimal design — just a QR code and a small line of text below. The satisfying production line of event preparation. Clean, organised, purposeful. 50mm f/2.8 from directly above. ${STYLE}`,
},
{
filename: "product-setup-04-whatsapp-share.jpg",
prompt: `3:4 portrait. Close-up of a young British-Muslim man's hands holding a phone, composing a WhatsApp message. The phone screen shows a chat with a green send button (screen content blurred/not readable, but the WhatsApp green interface is recognisable). He's sitting on a sofa at home, wearing casual clothes. A cup of chai on the coffee table. Sharing a pledge link with the community before an event. Warm living room lamp light. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-setup-05-briefing-volunteers.jpg",
prompt: `16:9 landscape. A young British-Muslim charity organiser standing in front of 6-7 seated volunteers in a community hall, holding up a printed QR tent card to show them. He's in smart-casual — shirt, no tie. The volunteers are in matching charity t-shirts, listening attentively, some holding their own copies of the cards. A whiteboard behind the organiser shows a rough event floor plan. Pre-event briefing — "put one of these on every table." 35mm f/2.0. ${STYLE}`,
},
{
filename: "product-setup-06-table-setting.jpg",
prompt: `16:9 landscape. A community hall being set up for a fundraising dinner. In the foreground, a young British-Muslim woman in hijab is carefully placing a QR code tent card in the centre of a round table that already has a white tablecloth, water bottles, and a small bowl of dates. Behind her, other volunteers are setting other tables in a large hall. The tent card is small, white, elegant — sitting upright between the water jug and dates. The detail that will change everything. 50mm f/2.0. ${STYLE}`,
},
// ═══════════════════════════════════════════
// DURING EVENT — PLEDGING MOMENT (8 photos)
// Donors discovering and using the QR/link
// ═══════════════════════════════════════════
{
filename: "product-pledge-01-scanning-table.jpg",
prompt: `16:9 landscape. At a charity dinner table, a young British-Muslim man in a blazer is holding his phone camera up to a QR code tent card on the table. His phone is angled toward the card, we see the back of his phone and his hands. The tent card stands upright between plates of biryani, dates, and water. Other guests at the table are eating and chatting, not paying attention. Warm fairy-light ambiance. The private, effortless moment of scanning a pledge link. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-pledge-02-phone-form.jpg",
prompt: `3:4 portrait. Over-the-shoulder shot of a young British-Muslim woman at a charity dinner, looking at her phone which she holds in both hands. The phone screen shows a simple white form interface with a blue button (screen intentionally slightly blurred so text is not readable, but the layout of a clean mobile form is recognisable — white background, input fields, a coloured button at the bottom). Her thumbs hover over the screen. The decisive moment of entering a pledge amount. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-pledge-03-couple-discussing.jpg",
prompt: `16:9 landscape. A young British-Muslim couple at a charity dinner table, heads close together, discussing something on one of their phones. The husband is holding the phone between them (screen NOT visible), pointing at it. The wife is nodding thoughtfully. Between them on the table: a QR tent card, water glasses, half-eaten food. They're deciding together how much to pledge. Warm tungsten light. The intimacy of a shared financial decision for good. 85mm f/1.8. ${STYLE}`,
},
{
filename: "product-pledge-04-quick-tap.jpg",
prompt: `3:4 portrait. Extreme close-up of a thumb tapping a phone screen. The phone is held in one hand over a charity dinner table (blurred biryani and tent card in the background). The screen shows a confirmation-style layout — a coloured button being pressed (screen slightly blurred, colours recognisable but text not readable). The split-second of committing to a pledge. Shallow depth of field, everything except the thumb and screen is bokeh. 85mm macro f/1.4. ${STYLE}`,
},
{
filename: "product-pledge-05-confirmation-smile.jpg",
prompt: `3:4 portrait. A young British-Muslim man at a charity dinner, looking at his phone with a small satisfied smile. He's just completed something on his phone — putting it down on the table. His expression is quiet contentment, not performance. A QR tent card visible on the table near his plate. Other guests are chatting around him, unaware. The private satisfaction of having made a pledge. Warm ambient light. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-pledge-06-multiple-phones.jpg",
prompt: `16:9 landscape. Wide shot of a charity dinner table where multiple guests are simultaneously looking at their phones. Five or six people at a round table, at least three of them have phones out, scanning or tapping. A QR tent card sits in the centre of the table. The speaker at the far end of the hall is mid-appeal (blurred). The viral moment when one person scans and everyone follows. Energy and momentum. 35mm f/2.0. ${STYLE}`,
},
{
filename: "product-pledge-07-outdoor-event.jpg",
prompt: `16:9 landscape. An outdoor charity fundraising event — a marquee or gazebo in a park. A young British-Muslim woman is holding her phone up to a QR code on a printed poster attached to a display board. She's in casual clothes — denim jacket, colourful hijab. Behind her: families on the grass, a bouncy castle, charity stalls. Daytime, overcast. Pledging doesn't just happen at dinners — it happens anywhere. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-pledge-08-mosque-friday.jpg",
prompt: `16:9 landscape. Just outside a mosque after Friday prayer. A printed A3 poster on a noticeboard near the exit shows a QR code with a headline (text blurred). A young British-Muslim man in a thobe has stopped to scan it with his phone on his way out. Other congregants are filing past him. Afternoon daylight through the entrance. The mosque foyer has a shoe rack, coats on hooks, and community notices. Pledging in the flow of daily religious life. 35mm f/2.0. ${STYLE}`,
},
// ═══════════════════════════════════════════
// POST-EVENT — FOLLOW-UP & DASHBOARD (6 photos)
// The morning after, tracking pledges, sending reminders
// ═══════════════════════════════════════════
{
filename: "product-dashboard-01-morning-after.jpg",
prompt: `16:9 landscape. Morning light streaming through a kitchen window. A young British-Muslim man in a t-shirt sits at the kitchen table with a laptop open and a mug of coffee. His expression is quietly amazed — eyebrows slightly raised, leaning forward toward the screen. The laptop screen casts light on his face (content NOT visible). His phone on the table shows a notification (screen blurred). The morning after the fundraiser — checking how much was pledged overnight. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-dashboard-02-team-review.jpg",
prompt: `16:9 landscape. A small charity office. Three young British-Muslim team members — two men and a woman in hijab — gathered around a single laptop on a desk. One is pointing at the screen, another is writing numbers on a notepad, the third has her hand on her chin looking pleased. The laptop screen (NOT visible to camera, shot from behind) shows data. Tea mugs and biscuits on the desk. The team debrief — reviewing pledge numbers after an event. 35mm f/2.0. ${STYLE}`,
},
{
filename: "product-dashboard-03-phone-notification.jpg",
prompt: `3:4 portrait. A young British-Muslim woman's hand holding her phone, which shows a notification banner on the lock screen (notification content blurred but the banner shape is clear — a white rounded rectangle at the top of the screen with an app icon). She's on a London bus, the window shows a rainy street. Her other hand holds the pole. A pledge payment just came through — the system works even when you're not thinking about it. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-dashboard-04-whatsapp-reminder.jpg",
prompt: `16:9 landscape. Split focus shot. In the foreground (sharp): a phone lying on a desk showing a WhatsApp message thread with a green chat bubble (content blurred). In the background (slightly soft): a young British-Muslim charity worker at the desk, reaching for the phone with a curious expression. A gentle follow-up reminder has arrived for a donor — the automated system nudging at the right time. Natural desk lamp light. 50mm f/2.8. ${STYLE}`,
},
{
filename: "product-dashboard-05-spreadsheet-vs-app.jpg",
prompt: `16:9 landscape. A charity office desk showing the "before and after" of pledge tracking. On the left side of the desk: a messy stack of paper pledge forms, a calculator, scribbled Post-it notes, and a stressed-looking pen. On the right side: a clean laptop with a calm screen glow. A young British-Muslim woman's hands are pushing the paper pile to the side, turning toward the laptop. The transition from chaos to system. Overhead fluorescent. 35mm f/2.8. ${STYLE}`,
},
{
filename: "product-dashboard-06-treasurer-reconciling.jpg",
prompt: `16:9 landscape. An older British-Muslim man (50s, glasses, trimmed beard) sitting at a home office desk with a laptop, a bank statement printout, and a cup of chai. He's running his finger down the printed statement, cross-referencing with the laptop screen (not visible). His expression is focused but not stressed — things are matching up. A desk lamp, family photos on the shelf behind. The charity treasurer's monthly reconciliation made simple. 50mm f/2.0. ${STYLE}`,
},
// ═══════════════════════════════════════════
// DONOR JOURNEY — PAYMENT & COMPLETION (6 photos)
// The donor side: receiving reminders, paying, feeling good
// ═══════════════════════════════════════════
{
filename: "product-donor-01-reminder-commute.jpg",
prompt: `16:9 landscape. A young British-Muslim man sitting on a London Underground tube train, looking at his phone with recognition — "oh right, I pledged at that dinner." He's in work clothes — shirt, no tie, messenger bag on lap. The tube carriage has other commuters reading, on phones. Through the dark window: tunnel reflections. A reminder notification has just arrived. The gentle nudge that converts a pledge to a payment. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-donor-02-paying-sofa.jpg",
prompt: `16:9 landscape. A young British-Muslim woman in hijab sitting on a sofa at home, casually completing a payment on her phone. She's relaxed — legs tucked under her, a blanket, the TV on in the background (blurred). Her phone is in one hand, her other hand holds a cup of tea. She's tapping the screen with her thumb. Warm living room lamp light. Paying a pledge in her own time, on her own sofa, no pressure. The ease of delayed giving. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-donor-03-payday-payment.jpg",
prompt: `3:4 portrait. Close-up of a young British-Muslim man's hands holding his phone over a kitchen counter. Next to the phone on the counter: his car keys, a wallet, and a pay slip (partially visible, blurred). He's completing a transaction on his phone (screen blurred, but a confirmation-style layout with a button is visible). Payday — the day he can finally honour his pledge from last month. Morning light. The integrity of following through. 85mm f/1.8. ${STYLE}`,
},
{
filename: "product-donor-04-installment-calendar.jpg",
prompt: `16:9 landscape. A young British-Muslim woman at a desk with a physical wall calendar visible behind her. Some dates on the calendar have small coloured stickers on them — marking her pledge payment installments. She's on her phone (screen not visible), relaxed, chin in hand. The calendar shows this is a planned, manageable commitment spread over months, not a single painful payment. Soft afternoon light. Financial flexibility for generous hearts. 50mm f/2.0. ${STYLE}`,
},
{
filename: "product-donor-05-confirmation-peace.jpg",
prompt: `3:4 portrait. A young British-Muslim man sitting alone on a park bench, phone in his lap, looking up at the sky with a peaceful half-smile. He's just completed his final pledge payment. The phone screen is dark/off. Trees in the background, late afternoon golden light. A private moment of completion — promise kept, obligation fulfilled. No fanfare, just quiet peace. 85mm f/1.4. ${STYLE}`,
},
{
filename: "product-donor-06-gift-aid-tick.jpg",
prompt: `3:4 portrait. Extreme close-up of a phone screen showing a simple checkbox or toggle being tapped by a thumb. The checkbox area is in focus with a clean white interface and blue accent colour (specific text NOT readable). The background behind the phone is blurred — looks like a dinner table setting with warm light. The small action of ticking Gift Aid that adds 25% to a donation at no cost. The detail that charities need. 85mm macro f/1.4. ${STYLE}`,
},
// ═══════════════════════════════════════════
// CELEBRATION — TARGET HIT (4 photos)
// The moment when pledges convert and targets are reached
// ═══════════════════════════════════════════
{
filename: "product-celebrate-01-target-hit.jpg",
prompt: `16:9 landscape. A small charity office. A young British-Muslim man jumps up from his desk chair, arms raised, fists clenched in celebration. His laptop is open in front of him. A young woman in hijab at the next desk is turning to look at him with a surprised laugh, mid-sip of tea. Papers on desks, charity posters on walls. Fluorescent light. The moment the pledge collection target is hit — the dashboard showed 100%. Genuine, unguarded joy. 35mm f/2.0. ${STYLE}`,
},
{
filename: "product-celebrate-02-showing-phone.jpg",
prompt: `16:9 landscape. Two young British-Muslim charity volunteers at a post-event cleanup. One is holding his phone out to show the other something on the screen (phone screen facing away from camera, NOT visible). The one looking has wide eyes and is covering her mouth with her hand in disbelief. They're standing in a half-cleared community hall — stacked chairs, folded tablecloths. The total pledged amount exceeding expectations. 50mm f/1.8. ${STYLE}`,
},
{
filename: "product-celebrate-03-announcing-total.jpg",
prompt: `16:9 landscape. A charity fundraising dinner. The MC/speaker at the front podium holds up a piece of paper and announces the total to the room. The audience is mid-applause — hands up, some standing, faces lit with pride. Round tables with dinner remnants. A projection screen behind the speaker shows a large number (blurred). Fairy lights. The climactic announcement powered by real-time pledge data. The room erupts. 24mm f/2.8. ${STYLE}`,
},
{
filename: "product-celebrate-04-thank-you-text.jpg",
prompt: `3:4 portrait. A young British-Muslim woman in hijab sitting at a desk, typing on her phone with both thumbs, a warm smile on her face. She's sending thank-you messages to donors. On the desk beside her: a laptop with the lid half-closed, a notepad with a list of names (some ticked off), and an empty mug. Warm desk lamp light, evening. The personal follow-up that turns a one-time donor into a lifelong supporter. 85mm f/1.4. ${STYLE}`,
},
];
// ─── CONCURRENT GENERATION ENGINE ──────────────────────────
async function generateOne(spec: Photo): Promise<boolean> {
const outPath = path.join(OUTPUT_DIR, spec.filename);
const t0 = Date.now();
try {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text: `Generate a photorealistic photograph. ${spec.prompt}` }] }],
generationConfig: { responseModalities: ["IMAGE", "TEXT"] },
}),
});
if (!res.ok) {
const err = await res.text();
console.error(`${spec.filename} — API ${res.status}: ${err.slice(0, 150)}`);
return false;
}
const data: any = await res.json();
const parts = data.candidates?.[0]?.content?.parts;
const imgPart = parts?.find((p: any) => p.inlineData?.mimeType?.startsWith("image/"));
if (!imgPart) {
const textPart = parts?.find((p: any) => p.text);
console.error(`${spec.filename} — No image${textPart ? ": " + textPart.text.slice(0, 100) : ""}`);
return false;
}
const buf = Buffer.from(imgPart.inlineData.data, "base64");
fs.writeFileSync(outPath, buf);
const ms = Date.now() - t0;
console.log(`${spec.filename}${(buf.length / 1024).toFixed(0)}KB (${(ms / 1000).toFixed(1)}s)`);
return true;
} catch (e: any) {
console.error(`${spec.filename}${e.message}`);
return false;
}
}
async function main() {
const BATCH_SIZE = 5;
const batches: Photo[][] = [];
for (let i = 0; i < PHOTOS.length; i += BATCH_SIZE) {
batches.push(PHOTOS.slice(i, i + BATCH_SIZE));
}
console.log("═══════════════════════════════════════════════════════");
console.log(" PNPL Brand Photography — Batch 4 (Product Journey)");
console.log(` Model: ${MODEL}`);
console.log(` Strategy: ${batches.length} batches × ${BATCH_SIZE} concurrent`);
console.log(` Output: ${OUTPUT_DIR}`);
console.log("═══════════════════════════════════════════════════════");
const t0 = Date.now();
let success = 0;
let failed: Photo[] = [];
for (let i = 0; i < batches.length; i++) {
console.log(`\n⚡ Batch ${i + 1}/${batches.length} — firing ${batches[i].length} requests...`);
const results = await Promise.allSettled(batches[i].map(p => generateOne(p)));
const batchSuccess = results.filter(r => r.status === "fulfilled" && r.value).length;
success += batchSuccess;
for (const spec of batches[i]) {
if (!fs.existsSync(path.join(OUTPUT_DIR, spec.filename))) {
failed.push(spec);
}
}
if (i < batches.length - 1) {
console.log(` ⏳ 3s cooldown...`);
await new Promise(r => setTimeout(r, 3000));
}
}
if (failed.length > 0) {
console.log(`\n🔄 Retrying ${failed.length} failures (5s between each)...`);
await new Promise(r => setTimeout(r, 5000));
for (const spec of failed) {
const ok = await generateOne(spec);
if (ok) success++;
await new Promise(r => setTimeout(r, 5000));
}
}
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
console.log("\n═══════════════════════════════════════════════════════");
console.log(` Done: ${success}/${PHOTOS.length} photos in ${elapsed}s`);
console.log(` Total brand library: 120 photos`);
console.log(` Output: ${OUTPUT_DIR}`);
console.log("═══════════════════════════════════════════════════════");
}
main();