world-class hero image + 85% image optimization + sharp

HERO IMAGE:
- Generated 3 concepts with gemini-3-pro-image-preview, picked #1
- Phone showing 'Payment Received' notification at a charity gala dinner
- Warm tungsten bokeh chandeliers against dark bg-gray-950
- Directly visualizes the headline: 'money in the bank'
- Candid documentary angle, not looking at camera, brand compliant

IMAGE OPTIMIZATION (85% total reduction):
- All 21 images resized: landscape max 1200px, portrait max 1000px
- Compressed JPEG quality 80, progressive encoding, EXIF stripped
- Total: 13.6MB -> 2.1MB (saved 11.5MB)
- Individual savings: 81-90% per image

NEXT.JS IMAGE PIPELINE:
- Added sharp (10x faster than squoosh for image processing)
- next.config.mjs: WebP format, proper device/image sizes, 1yr cache TTL
- Dockerfile: libc6-compat + NEXT_SHARP_PATH for Alpine sharp support
- First request: ~3s (processing), cached: <1s

WebP served sizes: hero 52KB, cards 32-40KB (vs original 500-800KB JPEGs)
This commit is contained in:
2026-03-03 21:10:59 +08:00
parent 2592c4ba5b
commit ac19afce4e
29 changed files with 702 additions and 4 deletions

88
gen_hero.py Normal file
View File

@@ -0,0 +1,88 @@
"""Generate a world-class hero image for Pledge Now, Pay Later."""
import sys, io, os, time
sys.stdout.reconfigure(encoding='utf-8')
from google import genai
from google.genai import types
from PIL import Image
client = genai.Client(api_key="AIzaSyCHnesXLjPw-UgeZaQotut66bgjFdvy12E")
MODEL = "gemini-3-pro-image-preview"
OUT_DIR = "pledge-now-pay-later/public/images/landing"
BRAND_DIR = "pledge-now-pay-later/brand/photography"
PROMPTS = [
# Concept 1: Phone notification at gala
"""Photorealistic close-up documentary photograph.
A woman's hand holding a smartphone at a charity gala dinner table. The phone screen glows bright showing a green checkmark payment confirmation notification. Her hand is in sharp focus.
Background: beautifully soft bokeh of warm golden tungsten chandelier lights, round dinner tables with white tablecloths, blurred guests in formal attire. A glass of water and edge of a plate visible on the dark wooden table.
British South Asian woman, dark navy blazer sleeve, simple gold bracelet on wrist.
Shot on Sony A7IV, 50mm f/1.4, available warm tungsten light. Extremely shallow depth of field. Documentary candid style, warm color temperature.
The mood: quiet triumph. The pledge came through. Money in the bank.
Portrait orientation, 4:5 aspect ratio. Professional editorial photography.""",
# Concept 2: Dashboard laptop at desk after event
"""Photorealistic documentary photograph of a charity manager's desk, end of a successful fundraising evening.
An open MacBook showing a dashboard with bright green progress bars at 100 percent and payment confirmations. The laptop screen glows in the dim warm light. A phone beside it shows a WhatsApp message. A cup of tea, reading glasses folded on the desk.
The setting is a quiet office after an event. Warm desk lamp casting golden light, a window showing evening London skyline with city lights in the far background, completely out of focus.
British South Asian woman in her 40s, slight smile, looking at the laptop screen, only her silhouette partially visible from the side. Not looking at camera.
Shot on Leica Q2, 28mm f/1.7, available warm lamp light and blue window light. Shallow depth of field. Documentary candid style.
The mood: satisfied relief. Every pledge tracked. Every penny accounted for.
Portrait orientation, 4:5 aspect ratio. Professional editorial photography.""",
# Concept 3: The green glow moment
"""Photorealistic documentary photograph capturing the exact moment of success.
A close-up of a smartphone in a woman's hand, the screen casting a soft green glow on her face from below. She is standing at the edge of a busy charity gala ballroom. The phone shows a payment dashboard with multiple green indicators.
Background: a sweeping view of a London hotel ballroom with crystal chandeliers creating beautiful warm bokeh circles. Guests at round tables, energy and generosity in the air. All beautifully blurred.
British woman wearing a dark structured blazer, hijab, professional. She holds the phone at mid-chest level, glancing down at it with a subtle knowing expression. Candid, not posed.
Shot on Canon R5, 85mm f/1.2, warm tungsten available light. Extremely shallow depth of field. The phone and her nearest hand are razor sharp, face slightly soft, background completely dissolved into warm golden bokeh circles.
Cinematic documentary photography. The feeling: this is what success looks like. Quiet. Precise. Money in the bank.
Portrait orientation, 4:5 aspect ratio. Professional editorial photography.""",
]
os.makedirs(OUT_DIR, exist_ok=True)
os.makedirs(BRAND_DIR, exist_ok=True)
results = []
for i, prompt in enumerate(PROMPTS):
for attempt in range(3):
try:
print(f"\n--- Generating concept {i+1}/3 (attempt {attempt+1}) ---")
response = client.models.generate_content(
model=MODEL,
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
temperature=1.0,
),
)
found = False
for part in response.candidates[0].content.parts:
if part.inline_data and part.inline_data.mime_type.startswith("image/"):
img = Image.open(io.BytesIO(part.inline_data.data))
fname = f"hero-concept-{i+1}.jpg"
path = os.path.join(OUT_DIR, fname)
if img.mode != "RGB":
img = img.convert("RGB")
img.save(path, "JPEG", quality=92, optimize=True)
sz = os.path.getsize(path)
print(f" OK {fname} -- {img.size[0]}x{img.size[1]}, {sz//1024}KB")
results.append((fname, img.size, sz))
found = True
break
if found:
break
else:
print(" No image in response, retrying...")
except Exception as e:
emsg = str(e).encode('ascii', 'replace').decode('ascii')
print(f" Error: {emsg}")
if attempt < 2:
time.sleep(5)
print(f"\n=== Generated {len(results)}/3 hero concepts ===")
for fname, size, sz in results:
print(f" {fname}: {size[0]}x{size[1]}, {sz//1024}KB")