#!/usr/bin/env python3 """Generate 5 promotional posters for the Reflect mood tracking app.""" from PIL import Image, ImageDraw, ImageFont import os import math OUT_DIR = os.path.dirname(os.path.abspath(__file__)) W, H = 1080, 1920 # Standard story/poster size def get_font(size, bold=False): """Try system fonts, fall back to default.""" paths = [ "/System/Library/Fonts/SFCompact.ttf", "/System/Library/Fonts/Supplemental/Arial Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Arial.ttf", "/System/Library/Fonts/Helvetica.ttc", "/Library/Fonts/Arial.ttf", ] for p in paths: try: return ImageFont.truetype(p, size) except (OSError, IOError): continue return ImageFont.load_default() def draw_rounded_rect(draw, xy, radius, fill): x0, y0, x1, y1 = xy draw.rectangle([x0 + radius, y0, x1 - radius, y1], fill=fill) draw.rectangle([x0, y0 + radius, x1, y1 - radius], fill=fill) draw.pieslice([x0, y0, x0 + 2*radius, y0 + 2*radius], 180, 270, fill=fill) draw.pieslice([x1 - 2*radius, y0, x1, y0 + 2*radius], 270, 360, fill=fill) draw.pieslice([x0, y1 - 2*radius, x0 + 2*radius, y1], 90, 180, fill=fill) draw.pieslice([x1 - 2*radius, y1 - 2*radius, x1, y1], 0, 90, fill=fill) def draw_mood_emoji(draw, cx, cy, size, mood_color, emoji_char): """Draw a colored circle with an emoji-like symbol.""" r = size // 2 draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill=mood_color) font = get_font(int(size * 0.5)) draw.text((cx, cy), emoji_char, fill="white", font=font, anchor="mm") def gradient_fill(img, start_color, end_color, direction="vertical"): """Fill image with a gradient.""" draw = ImageDraw.Draw(img) r1, g1, b1 = start_color r2, g2, b2 = end_color if direction == "vertical": for y in range(H): t = y / H r = int(r1 + (r2 - r1) * t) g = int(g1 + (g2 - g1) * t) b = int(b1 + (b2 - b1) * t) draw.line([(0, y), (W, y)], fill=(r, g, b)) else: for x in range(W): t = x / W r = int(r1 + (r2 - r1) * t) g = int(g1 + (g2 - g1) * t) b = int(b1 + (b2 - b1) * t) draw.line([(x, 0), (x, H)], fill=(r, g, b)) return draw def add_stars(draw, count=30): """Add decorative dots/stars.""" import random random.seed(42) for _ in range(count): x = random.randint(0, W) y = random.randint(0, H) r = random.randint(1, 3) opacity = random.randint(40, 120) draw.ellipse([x-r, y-r, x+r, y+r], fill=(255, 255, 255, opacity)) # ── Poster 1: Hero / Brand Introduction ── def poster_1(): img = Image.new("RGB", (W, H)) draw = gradient_fill(img, (88, 86, 214), (175, 82, 222)) # Purple gradient # Decorative circles for i, (x, y, rad, alpha) in enumerate([ (150, 300, 200, 40), (900, 500, 150, 30), (200, 1400, 180, 35), (850, 1600, 120, 25), (540, 200, 100, 20) ]): overlay = Image.new("RGBA", (W, H), (0, 0, 0, 0)) od = ImageDraw.Draw(overlay) od.ellipse([x-rad, y-rad, x+rad, y+rad], fill=(255, 255, 255, alpha)) img = Image.alpha_composite(img.convert("RGBA"), overlay).convert("RGB") draw = ImageDraw.Draw(img) # App name font_big = get_font(120, bold=True) draw.text((W//2, 500), "Reflect", fill="white", font=font_big, anchor="mm") # Tagline font_med = get_font(48) draw.text((W//2, 620), "Your mood. Your story.", fill=(255, 255, 255, 220), font=font_med, anchor="mm") # Mood circles row moods = [ ((231, 76, 60), ":("), # horrible ((230, 126, 34), ":/"), # bad ((241, 196, 15), ":|"), # average ((46, 204, 113), ":)"), # good ((52, 152, 219), ":D"), # great ] labels = ["Horrible", "Bad", "Average", "Good", "Great"] start_x = 140 spacing = 200 for i, ((color, sym), label) in enumerate(zip(moods, labels)): cx = start_x + i * spacing cy = 900 draw_mood_emoji(draw, cx, cy, 120, color, sym) font_sm = get_font(28) draw.text((cx, cy + 85), label, fill="white", font=font_sm, anchor="mm") # Description font_desc = get_font(38) lines = [ "Track your daily mood", "Discover emotional patterns", "Gain AI-powered insights", ] for i, line in enumerate(lines): draw.text((W//2, 1150 + i * 70), line, fill="white", font=font_desc, anchor="mm") # Bottom CTA draw_rounded_rect(draw, (290, 1550, 790, 1650), 30, (255, 255, 255)) font_cta = get_font(40, bold=True) draw.text((W//2, 1600), "Download Free", fill=(88, 86, 214), font=font_cta, anchor="mm") # Footer font_foot = get_font(28) draw.text((W//2, 1780), "Available on the App Store", fill=(200, 200, 255), font=font_foot, anchor="mm") img.save(os.path.join(OUT_DIR, "poster_1_hero.png"), quality=95) print("✓ Poster 1: Hero") # ── Poster 2: Features Showcase ── def poster_2(): img = Image.new("RGB", (W, H)) draw = gradient_fill(img, (20, 20, 40), (40, 40, 80)) # Dark blue # Title font_title = get_font(80, bold=True) draw.text((W//2, 200), "Why Reflect?", fill="white", font=font_title, anchor="mm") # Feature cards features = [ ("☀️", "Daily Check-ins", "Rate your day in seconds\nwith our simple 5-point scale"), ("📊", "Visual Patterns", "See your mood trends across\ndays, months, and years"), ("🧠", "AI Insights", "On-device AI analyzes your\npatterns and offers guidance"), ("⌚", "Everywhere", "iPhone, Apple Watch, widgets,\nSiri, and Live Activities"), ("🔒", "Private & Secure", "Face ID protection with\niCloud sync across devices"), ] font_icon = get_font(60) font_feat = get_font(36, bold=True) font_sub = get_font(28) for i, (icon, title, desc) in enumerate(features): y = 370 + i * 270 # Card background draw_rounded_rect(draw, (80, y, W - 80, y + 230), 20, (255, 255, 255, 15)) # Use a colored rectangle instead since we can't render emoji reliably colors = [(88, 86, 214), (52, 152, 219), (46, 204, 113), (230, 126, 34), (231, 76, 60)] draw.ellipse([120, y + 40, 220, y + 140], fill=colors[i]) draw.text((170, y + 90), icon[0] if len(icon) == 1 else "★", fill="white", font=get_font(40), anchor="mm") draw.text((260, y + 60), title, fill="white", font=font_feat, anchor="lm") for j, line in enumerate(desc.split("\n")): draw.text((260, y + 110 + j * 35), line, fill=(180, 180, 220), font=font_sub, anchor="lm") # Bottom font_bottom = get_font(36) draw.text((W//2, 1780), "Reflect — Know yourself better", fill=(150, 150, 200), font=font_bottom, anchor="mm") img.save(os.path.join(OUT_DIR, "poster_2_features.png"), quality=95) print("✓ Poster 2: Features") # ── Poster 3: Mood Calendar Visual ── def poster_3(): img = Image.new("RGB", (W, H)) draw = gradient_fill(img, (15, 32, 39), (32, 58, 67)) # Teal dark # Title font_title = get_font(72, bold=True) draw.text((W//2, 180), "See Your Year", fill="white", font=font_title, anchor="mm") font_sub = get_font(36) draw.text((W//2, 270), "in living color", fill=(100, 200, 200), font=font_sub, anchor="mm") # Draw a mock calendar grid (7x5 for a month view) mood_colors = [ (231, 76, 60), (230, 126, 34), (241, 196, 15), (46, 204, 113), (52, 152, 219) ] import random random.seed(123) cell_size = 110 gap = 12 grid_w = 7 * (cell_size + gap) - gap start_x = (W - grid_w) // 2 start_y = 400 # Month label font_month = get_font(44, bold=True) draw.text((W//2, 360), "March 2026", fill="white", font=font_month, anchor="mm") # Day headers days = ["M", "T", "W", "T", "F", "S", "S"] font_day = get_font(28) for i, d in enumerate(days): x = start_x + i * (cell_size + gap) + cell_size // 2 draw.text((x, start_y), d, fill=(150, 200, 200), font=font_day, anchor="mm") # Calendar cells for row in range(5): for col in range(7): day_num = row * 7 + col + 1 if day_num > 31: continue x = start_x + col * (cell_size + gap) y = start_y + 40 + row * (cell_size + gap) # Weight towards good/great moods weights = [0.05, 0.1, 0.2, 0.35, 0.3] color = random.choices(mood_colors, weights=weights, k=1)[0] draw_rounded_rect(draw, (x, y, x + cell_size, y + cell_size), 16, color) font_num = get_font(32) draw.text((x + cell_size//2, y + cell_size//2), str(day_num), fill="white", font=font_num, anchor="mm") # Year mini grid (12 months x ~4 rows of tiny dots) font_label = get_font(36, bold=True) draw.text((W//2, 1100), "Your Year at a Glance", fill="white", font=font_label, anchor="mm") dot_size = 14 dot_gap = 4 months_labels = ["J","F","M","A","M","J","J","A","S","O","N","D"] grid_start_x = 100 grid_start_y = 1170 font_tiny = get_font(22) for m in range(12): mx = grid_start_x + m * 78 draw.text((mx + 20, grid_start_y), months_labels[m], fill=(150, 200, 200), font=font_tiny, anchor="mm") for d in range(30): row = d // 6 col = d % 6 dx = mx + col * (dot_size + dot_gap) dy = grid_start_y + 25 + row * (dot_size + dot_gap) color = random.choices(mood_colors, weights=weights, k=1)[0] draw.ellipse([dx, dy, dx + dot_size, dy + dot_size], fill=color) # CTA draw_rounded_rect(draw, (290, 1580, 790, 1680), 30, (46, 204, 113)) font_cta = get_font(40, bold=True) draw.text((W//2, 1630), "Start Tracking", fill="white", font=font_cta, anchor="mm") font_foot = get_font(28) draw.text((W//2, 1780), "Reflect — Beautiful mood tracking", fill=(100, 180, 180), font=font_foot, anchor="mm") img.save(os.path.join(OUT_DIR, "poster_3_calendar.png"), quality=95) print("✓ Poster 3: Calendar") # ── Poster 4: Apple Ecosystem ── def poster_4(): img = Image.new("RGB", (W, H)) draw = gradient_fill(img, (10, 10, 10), (30, 30, 50)) # Near black # Title font_title = get_font(72, bold=True) draw.text((W//2, 200), "One App.", fill="white", font=font_title, anchor="mm") draw.text((W//2, 290), "Every Device.", fill=(88, 86, 214), font=font_title, anchor="mm") # Device mockups as stylized rectangles # iPhone phone_x, phone_y = W//2 - 20, 650 pw, ph = 260, 500 draw_rounded_rect(draw, (phone_x - pw//2, phone_y - ph//2, phone_x + pw//2, phone_y + ph//2), 30, (50, 50, 70)) draw_rounded_rect(draw, (phone_x - pw//2 + 10, phone_y - ph//2 + 40, phone_x + pw//2 - 10, phone_y + ph//2 - 40), 15, (88, 86, 214)) font_dev = get_font(28) draw.text((phone_x, phone_y), "Reflect", fill="white", font=get_font(36, bold=True), anchor="mm") draw.text((phone_x, phone_y + 45), ":)", fill="white", font=get_font(48), anchor="mm") draw.text((phone_x, phone_y + ph//2 + 40), "iPhone", fill=(180, 180, 200), font=font_dev, anchor="mm") # Watch watch_x = 180 watch_y = 720 wr = 100 draw_rounded_rect(draw, (watch_x - wr, watch_y - wr, watch_x + wr, watch_y + wr), 30, (50, 50, 70)) draw_rounded_rect(draw, (watch_x - wr + 8, watch_y - wr + 8, watch_x + wr - 8, watch_y + wr - 8), 22, (46, 204, 113)) draw.text((watch_x, watch_y - 10), ":D", fill="white", font=get_font(40), anchor="mm") draw.text((watch_x, watch_y + 30), "Great", fill="white", font=get_font(22), anchor="mm") draw.text((watch_x, watch_y + wr + 30), "Apple Watch", fill=(180, 180, 200), font=font_dev, anchor="mm") # Widget widg_x = W - 180 widg_y = 720 ww, wh = 180, 180 draw_rounded_rect(draw, (widg_x - ww//2, widg_y - wh//2, widg_x + ww//2, widg_y + wh//2), 25, (50, 50, 70)) # Mini mood grid for r in range(3): for c in range(3): colors = [(52, 152, 219), (46, 204, 113), (241, 196, 15), (46, 204, 113), (52, 152, 219), (231, 76, 60), (46, 204, 113), (52, 152, 219), (46, 204, 113)] idx = r * 3 + c bx = widg_x - 60 + c * 45 by = widg_y - 60 + r * 45 draw_rounded_rect(draw, (bx, by, bx + 38, by + 38), 8, colors[idx]) draw.text((widg_x, widg_y + wh//2 + 30), "Widgets", fill=(180, 180, 200), font=font_dev, anchor="mm") # Feature list features = [ "Live Activities on your Lock Screen", "Siri Shortcuts — log mood by voice", "Control Center quick access", "iCloud sync across all devices", "Face ID & Touch ID protection", ] font_feat = get_font(34) for i, feat in enumerate(features): y = 1100 + i * 70 draw.ellipse([160, y - 10, 180, y + 10], fill=(88, 86, 214)) draw.text((210, y), feat, fill="white", font=font_feat, anchor="lm") # CTA draw_rounded_rect(draw, (290, 1580, 790, 1680), 30, (88, 86, 214)) font_cta = get_font(40, bold=True) draw.text((W//2, 1630), "Get Reflect", fill="white", font=font_cta, anchor="mm") font_foot = get_font(26) draw.text((W//2, 1780), "Free to try · Premium unlocks everything", fill=(120, 120, 150), font=font_foot, anchor="mm") img.save(os.path.join(OUT_DIR, "poster_4_ecosystem.png"), quality=95) print("✓ Poster 4: Ecosystem") # ── Poster 5: Social Proof / Testimonial Style ── def poster_5(): img = Image.new("RGB", (W, H)) draw = gradient_fill(img, (245, 245, 250), (220, 220, 235)) # Light/white # Top accent bar draw.rectangle([0, 0, W, 8], fill=(88, 86, 214)) # Title font_title = get_font(64, bold=True) draw.text((W//2, 180), "Know Yourself", fill=(30, 30, 50), font=font_title, anchor="mm") draw.text((W//2, 260), "Better", fill=(88, 86, 214), font=font_title, anchor="mm") # Fake review cards reviews = [ ("★★★★★", "Finally an app that makes\nmood tracking effortless.", "— Sarah K."), ("★★★★★", "The year view changed how I\nunderstand my emotions.", "— Mike T."), ("★★★★★", "Beautiful design. Love the\nApple Watch integration.", "— Priya R."), ] font_stars = get_font(32) font_review = get_font(32) font_author = get_font(26) for i, (stars, text, author) in enumerate(reviews): y = 400 + i * 320 # Card draw_rounded_rect(draw, (80, y, W - 80, y + 270), 20, (255, 255, 255)) # Shadow effect (subtle darker rect behind) draw.text((140, y + 40), stars, fill=(241, 196, 15), font=font_stars, anchor="lm") for j, line in enumerate(text.split("\n")): draw.text((140, y + 90 + j * 42), line, fill=(50, 50, 70), font=font_review, anchor="lm") draw.text((140, y + 210), author, fill=(130, 130, 150), font=font_author, anchor="lm") # Stats bar stats_y = 1400 draw_rounded_rect(draw, (80, stats_y, W - 80, stats_y + 160), 20, (88, 86, 214)) font_stat_num = get_font(52, bold=True) font_stat_label = get_font(24) stat_data = [("4.9★", "Rating"), ("50K+", "Users"), ("7", "Languages")] for i, (num, label) in enumerate(stat_data): sx = 200 + i * 300 draw.text((sx, stats_y + 55), num, fill="white", font=font_stat_num, anchor="mm") draw.text((sx, stats_y + 110), label, fill=(200, 200, 255), font=font_stat_label, anchor="mm") # CTA draw_rounded_rect(draw, (290, 1650, 790, 1750), 30, (30, 30, 50)) font_cta = get_font(40, bold=True) draw.text((W//2, 1700), "Try Reflect Free", fill="white", font=font_cta, anchor="mm") font_foot = get_font(26) draw.text((W//2, 1830), "30-day free trial · No credit card required", fill=(130, 130, 150), font=font_foot, anchor="mm") img.save(os.path.join(OUT_DIR, "poster_5_social.png"), quality=95) print("✓ Poster 5: Social Proof") if __name__ == "__main__": poster_1() poster_2() poster_3() poster_4() poster_5() print(f"\nAll 5 posters saved to: {OUT_DIR}")