"""Composite app screenshots into Apple App Store mockups with phone frames.""" from PIL import Image, ImageDraw, ImageFont import os, math, textwrap SCREENSHOTS_DIR = '/tmp/nur-falah-screenshots' OUTPUT_DIR = '/tmp/nur-falah-mockups' # Device frame dimensions (iPhone 14 Pro proportions) FRAME_W = 720 FRAME_H = 1480 BORDER_RADIUS = 80 BEZEL_COLOR = '#1A1A1A' SCREEN_X = 24 SCREEN_Y = 24 SCREEN_W = FRAME_W - 48 SCREEN_H = FRAME_H - 48 SCREEN_RADIUS = 60 # Canvas for 6.7" display (1290x2796 logical @ 3x = mockup at 1x smooth) CANVAS_W = 1290 CANVAS_H = 2796 FONTS_DIRS = ['/System/Library/Fonts', '/System/Library/Fonts/Supplemental'] def find_font(name, size): for fd in FONTS_DIRS: for root, dirs, files in os.walk(fd): for f in files: if name.lower() in f.lower() and f.endswith(('.ttf', '.ttc')): try: return ImageFont.truetype(os.path.join(root, f), size) except: pass return ImageFont.load_default() def draw_phone_frame(draw, x, y, w, h): """Draw an iPhone-like bezel frame.""" # Outer bezel (rounded rect) draw.rounded_rectangle( [x, y, x + w, y + h], radius=BORDER_RADIUS, fill=BEZEL_COLOR, ) # Screen area (darker) draw.rounded_rectangle( [x + SCREEN_X, y + SCREEN_Y, x + w - SCREEN_X, y + h - SCREEN_Y], radius=SCREEN_RADIUS, fill='#000000', ) # Dynamic Island (pill shape at top center) island_w, island_h = 126, 36 island_x = x + w//2 - island_w//2 island_y = y + SCREEN_Y + 14 draw.rounded_rectangle( [island_x, island_y, island_x + island_w, island_y + island_h], radius=18, fill='#000000', ) # Home indicator bar bar_w, bar_h = 134, 5 bar_x = x + w//2 - bar_w//2 bar_y = y + h - SCREEN_Y - 12 draw.rounded_rectangle( [bar_x, bar_y, bar_x + bar_w, bar_y + bar_h], radius=3, fill='#333333', ) def make_mockup(screenshot_path, output_path, label): """Create a single mockup: screenshot composited into phone frame.""" # Load screenshot screenshot = Image.open(screenshot_path).convert('RGB') sw, sh = screenshot.size # Target screen area inside the phone frame inner_w = FRAME_W - 2 * SCREEN_X inner_h = FRAME_H - 2 * SCREEN_Y # Scale screenshot to fit while maintaining aspect ratio scale = min(inner_w / sw, inner_h / sh) new_w = int(sw * scale) new_h = int(sh * scale) screenshot_resized = screenshot.resize((new_w, new_h), Image.LANCZOS) # Create phone frame on transparent background frame = Image.new('RGBA', (FRAME_W, FRAME_H), (0, 0, 0, 0)) draw = ImageDraw.Draw(frame) draw_phone_frame(draw, 0, 0, FRAME_W, FRAME_H) # Paste screenshot centered in the screen area paste_x = SCREEN_X + (inner_w - new_w) // 2 paste_y = SCREEN_Y + (inner_h - new_h) // 2 frame.paste(screenshot_resized, (paste_x, paste_y)) # Create final canvas with gradient background canvas = Image.new('RGB', (CANVAS_W, CANVAS_H), '#070A0D') # Subtle radial gradient bg = ImageDraw.Draw(canvas) for i in range(CANVAS_H): # Vertical gradient: dark at edges, slightly lighter center ratio = abs(i / CANVAS_H - 0.5) * 2 r = int(7 + (1 - ratio) * 3) g = int(10 + (1 - ratio) * 4) b = int(13 + (1 - ratio) * 4) bg.line([(0, i), (CANVAS_W, i)], fill=(r, g, b)) # Position phone frame centered phone_x = (CANVAS_W - FRAME_W) // 2 phone_y = 120 canvas.paste(frame, (phone_x, phone_y), frame) # Add label text below the phone draw2 = ImageDraw.Draw(canvas) font = find_font('SFNS', 32) or find_font('Helvetica', 32) or ImageFont.load_default() # Wrap label text words = label.split() lines = [] current_line = '' for word in words: test = current_line + ' ' + word if current_line else word if len(test) * 20 < CANVAS_W - 200: # rough estimate current_line = test else: lines.append(current_line) current_line = word if current_line: lines.append(current_line) y_offset = phone_y + FRAME_H + 60 for line in lines: bbox = draw2.textbbox((0, 0), line, font=font) tw = bbox[2] - bbox[0] draw2.text( ((CANVAS_W - tw) // 2, y_offset), line, fill='#C9A84C', font=font, ) y_offset += 48 # Save canvas.save(output_path, 'PNG', quality=95) print(f' ✅ {output_path} ({canvas.size})') def main(): os.makedirs(OUTPUT_DIR, exist_ok=True) mockups = [ ('prayer.png', '1-6.7-prayer-times', 'Accurate Prayer Times with\n5 Calculation Methods'), ('qibla.png', '2-6.7-qibla-finder', 'Find the Qibla from\nAnywhere in the World'), ('quran.png', '3-6.7-holy-quran', 'The Complete Holy Quran —\nAll 114 Surahs'), ('names99.png', '4-6.7-99-names', 'Study and Reflect on the\n99 Beautiful Names of Allah'), ('tasbih.png', '5-6.7-tasbih-counter', 'Digital Tasbih —\nTap to Remember Allah'), ('hijri.png', '6-6.7-hijri-calendar', 'Stay Connected to the\nIslamic Calendar'), ] for filename, output_name, label in mockups: src = os.path.join(SCREENSHOTS_DIR, filename) dst = os.path.join(OUTPUT_DIR, f'{output_name}.png') if os.path.exists(src): make_mockup(src, dst, label) else: print(f' ⚠️ Missing: {src}') print(f'\n🎉 All mockups saved to {OUTPUT_DIR}/') if __name__ == '__main__': main()