datewise/brand/make_screenshots.py
metatroncubeswdev 244de30d34 feat(listing): screenshots, framing script, listing copy; bookings list polish
- brand/make_screenshots.py frames raw shots at 1600x900 with captions + alt text
- brand/LISTING.md: introduction, details, features, search terms, testing steps
- bookings filters no longer overflow; footer reads '1 booking'; no empty pager

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:26:26 -04:00

107 lines
4.5 KiB
Python

"""Turns raw screenshots into 1600x900 Shopify App Store listing images in the Datewise brand.
Usage: python -I brand/make_screenshots.py
Input: brand/screenshots/raw/<n>-<slug>.(png|jpg) (browser screenshots; CROP below trims each one)
Output: brand/screenshots/listing/<n>-<slug>.png + alt-text.txt
Rules (Shopify): 1600x900, no pricing, reviews or outcome claims on the image.
"""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter, ImageFont
ROOT = Path(__file__).resolve().parent
RAW = ROOT / "screenshots" / "raw"
OUT = ROOT / "screenshots" / "listing"
W, H = 1600, 900
TEAL_DEEP = (4, 90, 81) # #045A51
TEAL = (13, 107, 93) # #0D6B5D
SAGE = (232, 242, 237) # #E8F2ED
WASH = (20, 125, 110)
# n: (title, subtitle, alt text)
SHOTS = {
1: ("Customers choose a time in the cart",
"Store pickup, local delivery or shipping, then a date and a time slot",
"Shopify cart page with the Datewise scheduler: store pickup selected, a calendar, time slots and a confirmation showing the time held"),
2: ("Today's bookings at a glance",
"Bookings, capacity for the next 14 days and upcoming festival dates",
"Datewise Today page in the Shopify admin with booking counts and a 14-day capacity chart for a pickup location"),
3: ("Hours, slots and limits for each location",
"Preparation time, cutoffs, bookable days and orders per slot",
"Location settings with pickup on, slot length, preparation time, same-day cutoff and orders per slot"),
4: ("Every booking in one list",
"Filter by date, method and location, move a booking or export CSV",
"Bookings list with filters and a confirmed store pickup booking"),
5: ("A valid time is required at checkout",
"Works on every Shopify plan, including Buy it now and express checkouts",
"Datewise settings with the checkout guard on, the date and time shown at checkout, and payment rules"),
6: ("The chosen time stays with the order",
"Shown on the delivery line, in the order details and as order tags",
"Shopify order showing the pickup line with the chosen date and time, booking details and Datewise tags"),
}
FONT = Path.home() / "AppData/Local/Microsoft/Windows/Fonts/Inter-VariableFont_slnt,wght.ttf"
def font(size, weight=400):
try:
f = ImageFont.truetype(str(FONT), size)
try:
axes = f.get_variation_axes()
f.set_variation_by_axes([weight] + [0] * (len(axes) - 1))
except Exception:
pass
return f
except OSError:
return ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf" if weight >= 600 else "C:/Windows/Fonts/segoeui.ttf", size)
def background():
bg = Image.new("RGB", (W, H), TEAL_DEEP)
glow = Image.new("L", (W, H), 0)
ImageDraw.Draw(glow).ellipse((-520, -620, 900, 520), fill=80)
glow = glow.filter(ImageFilter.GaussianBlur(170))
return Image.composite(Image.new("RGB", (W, H), WASH), bg, glow)
def compose(raw_path, title, subtitle):
bg = background()
d = ImageDraw.Draw(bg)
d.text((80, 60), title, font=font(52, 700), fill=(255, 255, 255))
d.text((82, 132), subtitle, font=font(27, 450), fill=SAGE)
shot = Image.open(raw_path).convert("RGB")
max_w, max_h = W - 160, H - 230
scale = min(max_w / shot.width, max_h / shot.height)
shot = shot.resize((round(shot.width * scale), round(shot.height * scale)), Image.LANCZOS)
x, y = (W - shot.width) // 2, 200
# shadow + rounded frame
shadow = Image.new("L", (W, H), 0)
ImageDraw.Draw(shadow).rounded_rectangle((x + 6, y + 18, x + shot.width + 6, y + shot.height + 18), 18, fill=120)
shadow = shadow.filter(ImageFilter.GaussianBlur(24))
bg = Image.composite(Image.new("RGB", (W, H), (2, 40, 36)), bg, shadow)
mask = Image.new("L", shot.size, 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, shot.width - 1, shot.height - 1), 16, fill=255)
bg.paste(shot, (x, y), mask)
return bg
def main():
OUT.mkdir(parents=True, exist_ok=True)
alt_lines = []
for raw in sorted(RAW.glob("*.*")):
n = int(raw.name.split("-", 1)[0])
if n not in SHOTS:
continue
title, subtitle, alt = SHOTS[n]
out = OUT / (raw.stem + ".png")
compose(raw, title, subtitle).save(out, optimize=True)
alt_lines.append(f"{out.name}: {alt}")
print("wrote", out.name)
(OUT / "alt-text.txt").write_text("\n".join(alt_lines) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()