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
This commit is contained in:
289
pledge-now-pay-later/scripts/generate-photos.ts
Normal file
289
pledge-now-pay-later/scripts/generate-photos.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Generate landing page photography — Round 2
|
||||
* Young, modern, British-Muslim charity photography
|
||||
* Model: Gemini 3 Pro Image Preview
|
||||
*
|
||||
* Run: npx tsx scripts/generate-photos.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 in .env");
|
||||
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/landing");
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
interface PhotoSpec {
|
||||
filename: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* PHOTOGRAPHY DIRECTION
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Audience: Young British Muslims (20s-30s) who run/volunteer
|
||||
* for Islamic charities in the UK. Design-literate. Can smell
|
||||
* stock photography instantly.
|
||||
*
|
||||
* Visual language: Amaliah magazine, Ramadan Tent Project,
|
||||
* Penny Appeal's younger campaigns. Understated, cinematic,
|
||||
* lived-in. NOT UN poster diversity. NOT Western gala.
|
||||
*
|
||||
* Rules:
|
||||
* - NO alcohol/wine glasses. Ever. Water jugs, chai, dates.
|
||||
* - Real British-Muslim spaces: community halls with strip
|
||||
* lighting, cramped charity offices, the Overground, home.
|
||||
* - Quiet expressions > exaggerated reactions
|
||||
* - Max 2 images showing someone holding a phone
|
||||
* - Cultural texture through environment, not costume
|
||||
* - Camera is a fly on the wall. Nobody performs.
|
||||
* - One coherent visual story: before → during → after
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
const STYLE = `Photorealistic documentary photography. Shot on Sony A7III, shallow depth of field, available light only. Fly-on-the-wall candid — nobody is looking at the camera. No stock photo aesthetic. No staged poses. No bright studio lighting. No visible text or watermarks. No wine or alcohol.`;
|
||||
|
||||
const PHOTOS: PhotoSpec[] = [
|
||||
// ─── HERO (3:4 portrait) ───────────────────────────────────
|
||||
// Psychology: "This is MY world. These are MY people."
|
||||
// The first image the visitor sees. Must create instant recognition.
|
||||
{
|
||||
filename: "hero-gala-moment.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Portrait orientation, 3:4 aspect ratio.
|
||||
|
||||
A young British-Muslim man, late 20s, neat trimmed beard, wearing a crisp white thobe with a dark blazer over it. He is standing at the front of a packed community hall during a charity fundraising appeal, speaking passionately with one hand slightly raised. He is mid-sentence, totally in the moment — not posing.
|
||||
|
||||
The camera is positioned BEHIND the seated audience, shooting through the crowd toward him. Blurred silhouettes of heads and shoulders in the dark foreground. The audience is seated at round tables with white tablecloths. On the tables: water jugs, paper plates, foil trays of biryani. NO wine, NO alcohol, NO candles.
|
||||
|
||||
The hall has basic overhead strip lighting mixed with some string fairy lights someone has hung along the wall. A rolled-up vinyl charity banner is leaning against the side wall. The ceiling has standard suspended ceiling tiles. This is a real UK community hall, not a hotel.
|
||||
|
||||
The audience is mixed — men in thobes, suits, casual clothes; women in hijabs and modest dresses. They are LISTENING, leaning forward, engaged. Natural warm tone from the tungsten strip lights.
|
||||
|
||||
85mm lens, f/1.4, the speaker is sharp, the foreground audience is beautiful creamy bokeh. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── PERSONA: FUNDRAISER (16:9) ───────────────────────────
|
||||
// Psychology: "I am this person." Identity mirror.
|
||||
// The young charity hustler — modern, grounded, purposeful.
|
||||
{
|
||||
filename: "persona-fundraiser-street.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
A young British-Muslim man, early 20s, short fade haircut, wearing a black North Face puffer jacket, dark joggers, and clean white Nike Air Force 1 trainers. He is walking along Whitechapel Road in East London on an overcast morning. One hand holds a paper coffee cup, the other is in his jacket pocket. He is looking ahead with quiet focus — not at the camera, not at a phone. Just walking with purpose.
|
||||
|
||||
The background shows the real Whitechapel streetscape: a halal butcher shop with Arabic and English signage, a red London bus in the far background, Victorian brick buildings, the grey overcast London sky. Pigeons on the pavement. A discarded Metro newspaper on a bench.
|
||||
|
||||
The colour palette is cool and muted — grey sky, dark jacket, white trainers popping. Morning light is flat and even, no harsh shadows.
|
||||
|
||||
50mm lens, f/2.0, shallow depth of field with the man sharp and the shopfronts softly blurred behind him. Street-level camera angle. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── PERSONA: TREASURER (16:9) ─────────────────────────────
|
||||
// Psychology: "I am this person." The unglamorous reality.
|
||||
// The volunteer treasurer drowning in spreadsheets.
|
||||
{
|
||||
filename: "persona-treasurer-community.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
A young British-Muslim woman, early 30s, wearing a plain black hijab and a simple dark jumper. She is sitting at a cramped desk in a tiny community centre office, rubbing her temples with one hand while staring down at a printed spreadsheet covered in highlighter marks. The expression is focused exhaustion — the face of someone reconciling pledge amounts at 10pm on a Tuesday.
|
||||
|
||||
The desk is genuinely messy: stacked brown envelopes, a clear plastic charity collection bucket with coins in it, a calculator, two empty mugs, a Tesco meal deal wrapper, scattered receipts, a battered ring binder. Behind her: a small window showing it is DARK outside (evening). A corkboard on the wall with pinned flyers — one says "Ramadan Food Drive" in English. Fluorescent tube light overhead giving that slightly blue-green office tint.
|
||||
|
||||
NO laptop screen visible. NO phone. Just paper, pen, and the weight of volunteer admin.
|
||||
|
||||
35mm lens, f/2.0, focused on her face, the desk clutter falling into gentle blur. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── STEP 1: CREATE LINK (16:9) ───────────────────────────
|
||||
// Psychology: "This is easy to start." Low-stakes familiarity.
|
||||
// The quiet preparation before an event.
|
||||
{
|
||||
filename: "step-create-link.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
Close-up of a pair of young brown hands taping a small printed A5 poster to a community hall wall with strips of masking tape. The poster has a simple QR code on it — no readable text, just the QR pattern. A roll of masking tape sits on a folding table below.
|
||||
|
||||
The background is out of focus but tells a story: stacked grey plastic chairs, folding tables being set up, a digital prayer timetable display on the wall showing prayer times, a fire exit sign. Someone in the far background is laying out white tablecloths on round tables.
|
||||
|
||||
The lighting is overhead fluorescent tubes — that familiar harsh community hall light. The wall is painted magnolia (that specific UK institutional cream colour).
|
||||
|
||||
The shot is tight on the hands and poster — we don't see a face. This is about the ACT of preparation, not the person. Intimate, quiet, mundane.
|
||||
|
||||
50mm macro, f/2.8, hands and poster sharp, background a soft blur of event setup. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── STEP 2: DONOR PLEDGES (16:9) ─────────────────────────
|
||||
// Psychology: "This is the moment it works." Energy, momentum.
|
||||
// The live appeal — the room is charged.
|
||||
{
|
||||
filename: "step-donor-pledges.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
Wide shot of a British-Muslim charity fundraising dinner mid-appeal. A large community hall filled with guests at round tables with white tablecloths. On the tables: water jugs, dates in small bowls, foil biryani trays, stacked paper plates, water bottles. Absolutely NO wine, NO alcohol, NO candles.
|
||||
|
||||
In the foreground, a middle-aged man in a suit is raising his hand — he is making a pledge. His table neighbours are looking at him, one person smiling. At the next table, a young woman in a patterned hijab is looking down at her phone, about to tap something. The phone screen is NOT visible to the camera.
|
||||
|
||||
At the far end of the hall, a speaker stands at a small podium with a projected screen behind showing a fundraising thermometer graphic. The room is ALIVE — people are animated, hands going up, conversations happening.
|
||||
|
||||
The lighting is standard community hall: overhead fluorescent strips mixed with some warm decorative fairy lights strung along the ceiling edges. A UK fire exit sign visible. This is a real community hall, not a hotel ballroom.
|
||||
|
||||
24mm wide lens, f/2.8, capturing the full scope and energy of the room. The man pledging in the foreground is sharpest, the rest falls into a busy, energetic blur. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── STEP 3: FOLLOW-UP NOTIFICATION (16:9) ────────────────
|
||||
// Psychology: "It happens automatically, in daily life."
|
||||
// The morning after. Effortless. Normal.
|
||||
{
|
||||
filename: "step-followup-notification.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
A young British-Muslim woman, mid-20s, wearing a beige trench coat and a dark hijab, sitting on the London Overground train during a morning commute. She is looking out the window with a small, private, contented half-smile — like she just remembered something good. She is NOT looking at a phone. Her AirPods are in. A tote bag is on the seat beside her.
|
||||
|
||||
Through the train window: blurred terraced houses and a grey London sky streaming past. The train interior is the distinctive orange-and-blue TfL Overground seating. A few other commuters visible in the blurred background, one reading a newspaper.
|
||||
|
||||
Morning flat light coming through the window, casting soft even illumination on her face. Cool colour palette — greys, beiges, the pop of orange seats.
|
||||
|
||||
85mm lens, f/1.8, shot from across the aisle. Her face is in sharp focus, everything else is soft. The feeling is: quiet, private, a good notification arrived and she doesn't need to do anything about it right now. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── STEP 4: MONEY ARRIVES (16:9) ─────────────────────────
|
||||
// Psychology: "The payoff. It worked." Relief, not performance.
|
||||
// Quiet satisfaction, not stock-photo celebration.
|
||||
{
|
||||
filename: "step-money-arrives.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
Two young British-Muslim people in a cramped community centre office. A young man in a plain crewneck and a young woman in a hijab and cardigan, both late 20s. They are sitting side by side at a desk. The man is leaning back slightly in his chair with a quiet exhale of relief — eyes closed for a second, the smallest smile, like a weight just lifted. The woman next to him is still looking at a printed sheet of paper on the desk, running her finger down a column of numbers.
|
||||
|
||||
This is NOT a screaming celebration. It is the private, quiet moment of "we actually did it." Understated. Real.
|
||||
|
||||
On the desk: the printed pledge summary, two mugs of tea (one is the classic brown PG Tips colour), a calculator, a pen. On the wall behind: a printed fundraising target poster with a hand-drawn thermometer that is coloured in to near the top. A small window shows street light outside — it is evening.
|
||||
|
||||
35mm lens, f/2.0, both faces in focus, shallow enough that the background details are legible but soft. Warm overhead fluorescent light. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── COMPLIANCE: MOSQUE HALL (16:9) ────────────────────────
|
||||
// Psychology: "Scale. Trust. This is serious."
|
||||
// The establishing shot. Gravitas.
|
||||
{
|
||||
filename: "compliance-mosque-hall.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Landscape orientation, 16:9 aspect ratio.
|
||||
|
||||
Grand wide interior shot of a large British mosque community hall during a major fundraising dinner. Over 150 guests seated at round tables with crisp white tablecloths. The architecture is distinctly British-mosque: ornate Islamic geometric plasterwork on the ceiling, large brass Moroccan-style pendant chandeliers casting warm golden light, arched windows with geometric stained glass.
|
||||
|
||||
On the tables: water jugs, dates, samosa platters, water bottles. NO wine, NO alcohol. The guests are a mix: older men in traditional thobes and topis, younger men in suits and smart-casual, women in hijabs of every colour and style. Families, not just adults.
|
||||
|
||||
In the middle ground, two young teenage volunteers in matching charity t-shirts are carrying trays of food between tables. The camera is positioned at the back of the hall shooting forward, capturing the full depth and grandeur of the space. Blurred silhouettes of standing figures in the immediate foreground frame the shot.
|
||||
|
||||
The mood is warm, communal, important. This is the biggest night of the year for this community. 24mm lens, f/2.8, available light from the chandeliers creating pools of warm gold. ${STYLE}`,
|
||||
},
|
||||
|
||||
// ─── PAYMENT FLEX: HOME (1:1 square) ──────────────────────
|
||||
// Psychology: "Donors pay on their terms. No pressure."
|
||||
// Domestic, intimate, unhurried.
|
||||
{
|
||||
filename: "payment-flex-kitchen.jpg",
|
||||
prompt: `Generate a photorealistic photograph. Square format, 1:1 aspect ratio.
|
||||
|
||||
A young British-Muslim couple, late 20s, on a sofa in their living room in the evening. She is wearing a loose headscarf and an oversized knit cardigan, reading a paperback book with her legs tucked under her. He is next to her, casually looking at his phone (phone screen NOT visible to camera — the phone is angled away). He has a short beard and is wearing a plain t-shirt.
|
||||
|
||||
Between them on the sofa is a small cushion. On the coffee table in front: two small glasses of Moroccan-style mint tea on a brass tray, a small bowl of dates, the TV remote. A floor lamp with a warm bulb casts a golden pool of light over them. The TV is off. The walls have a simple framed Arabic calligraphy print.
|
||||
|
||||
The mood is completely unhurried. Evening. Quiet. They are comfortable and there is no urgency. This is the opposite of a hard sell — it is "whenever you are ready."
|
||||
|
||||
50mm lens, f/1.8, focused on the couple, the coffee table details softly blurred in the foreground. Warm, intimate, still. ${STYLE}`,
|
||||
},
|
||||
];
|
||||
|
||||
async function generateImage(spec: PhotoSpec): Promise<void> {
|
||||
const outPath = path.join(OUTPUT_DIR, spec.filename);
|
||||
|
||||
console.log(`\n🎨 Generating: ${spec.filename}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
contents: [{ parts: [{ text: spec.prompt }] }],
|
||||
generationConfig: {
|
||||
responseModalities: ["IMAGE", "TEXT"],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
console.error(` ❌ API error ${response.status}: ${errText.slice(0, 300)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data: any = await response.json();
|
||||
const candidates = data.candidates;
|
||||
|
||||
if (!candidates?.length) {
|
||||
console.error(` ❌ No candidates returned`);
|
||||
console.error(` Response: ${JSON.stringify(data).slice(0, 400)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = candidates[0].content?.parts;
|
||||
if (!parts) {
|
||||
console.error(` ❌ No parts in candidate`);
|
||||
console.error(` Finish reason: ${candidates[0].finishReason}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const imagePart = parts.find((p: any) => p.inlineData?.mimeType?.startsWith("image/"));
|
||||
if (!imagePart) {
|
||||
// Check if there's a text explanation (usually safety filter)
|
||||
const textPart = parts.find((p: any) => p.text);
|
||||
if (textPart) {
|
||||
console.error(` ❌ Text response instead of image: ${textPart.text.slice(0, 200)}`);
|
||||
} else {
|
||||
console.error(` ❌ No image part found`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(imagePart.inlineData.data, "base64");
|
||||
fs.writeFileSync(outPath, buffer);
|
||||
|
||||
console.log(` ✅ ${spec.filename} — ${(buffer.length / 1024).toFixed(0)}KB`);
|
||||
} catch (err: any) {
|
||||
console.error(` ❌ Error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("═══════════════════════════════════════════════");
|
||||
console.log(" PNPL Photography — Round 2");
|
||||
console.log(" Young British-Muslim charity photography");
|
||||
console.log(` Model: ${MODEL}`);
|
||||
console.log(` Images: ${PHOTOS.length}`);
|
||||
console.log("═══════════════════════════════════════════════");
|
||||
|
||||
for (const spec of PHOTOS) {
|
||||
await generateImage(spec);
|
||||
// Rate limit — be gentle
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
}
|
||||
|
||||
console.log("\n═══════════════════════════════════════════════");
|
||||
console.log(" Done. Hard refresh your browser (Ctrl+Shift+R)");
|
||||
console.log("═══════════════════════════════════════════════");
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user