diff --git a/marketing/MarkupScreenshots/IMG_4038.PNG b/marketing/MarkupScreenshots/IMG_4038.PNG new file mode 100644 index 0000000..42d264a Binary files /dev/null and b/marketing/MarkupScreenshots/IMG_4038.PNG differ diff --git a/marketing/MarkupScreenshots/IMG_4039.PNG b/marketing/MarkupScreenshots/IMG_4039.PNG new file mode 100644 index 0000000..4661672 Binary files /dev/null and b/marketing/MarkupScreenshots/IMG_4039.PNG differ diff --git a/marketing/MarkupScreenshots/IMG_4040.PNG b/marketing/MarkupScreenshots/IMG_4040.PNG new file mode 100644 index 0000000..60715cc Binary files /dev/null and b/marketing/MarkupScreenshots/IMG_4040.PNG differ diff --git a/marketing/MarkupScreenshots/IMG_4041.PNG b/marketing/MarkupScreenshots/IMG_4041.PNG new file mode 100644 index 0000000..6cfa50a Binary files /dev/null and b/marketing/MarkupScreenshots/IMG_4041.PNG differ diff --git a/marketing/MarkupScreenshots/IMG_4042.PNG b/marketing/MarkupScreenshots/IMG_4042.PNG new file mode 100644 index 0000000..b5d5872 Binary files /dev/null and b/marketing/MarkupScreenshots/IMG_4042.PNG differ diff --git a/marketing/app-store/README.md b/marketing/app-store/README.md new file mode 100644 index 0000000..cc3852e --- /dev/null +++ b/marketing/app-store/README.md @@ -0,0 +1,47 @@ +# Markup — App Store submission kit + +Everything to submit Markup to the App Store, in one place: the marketing +screenshots, the listing copy (EN + 中文), the App Privacy answers, and the App +Review notes. The account-gated step (the actual **Submit for Review**) is still +yours — this kit is the paste-ready content + assets. + +Target: **1.0.0** on the iOS App Store (and the Mac App Store in parallel) — see +[../../docs/app-store/launch-1.0-checklist.md](../../docs/app-store/launch-1.0-checklist.md) +for the full step-by-step (certs, ASC, submit). + +## What's in here +- **[listing.md](./listing.md)** — name, subtitle, promo text, keywords, + description, category, URLs, copyright, age (EN + 中文) + the screenshot captions. +- **[privacy-and-review.md](./privacy-and-review.md)** — App Privacy questionnaire + answers ("Data Not Collected"), App Review notes, export compliance. +- **[screenshots/ios-6.9/](./screenshots/ios-6.9/)** — 5 ready-to-upload iPhone + 6.9″ marketing screenshots (1320×2868). +- **[screenshots/README.md](./screenshots/README.md)** — caption map, sizes, and + what's still needed (iPad). Source captures: `../MarkupScreenshots/`. +- Regenerate screenshots: `python3 marketing/scripts/make-screenshots.py`. + +## iOS — status +| Item | Status | +|---|---| +| App record | ✅ "Markup - MD Reader" · `com.appkon.markup.ios` · ASC `6775530509` | +| Build | ✅ 1.0.0 (100000) uploaded to TestFlight | +| Name / subtitle / category / copyright | ✅ values in [listing.md](./listing.md) | +| Promo / keywords / description | ✅ in [listing.md](./listing.md) | +| App Privacy label | ✅ "Data Not Collected" ([privacy-and-review.md](./privacy-and-review.md)) | +| iPhone 6.9″ screenshots | ✅ 5 in [screenshots/ios-6.9/](./screenshots/ios-6.9/) | +| **iPad 13″ screenshots** | ❌ still needed — the app is universal, so Apple requires them | +| Submit for Review | 🔑 you, in App Store Connect | + +## To finish iOS (in App Store Connect) +1. **App 信息** → paste name / subtitle / category / content-rights ([listing.md](./listing.md) §App info). +2. **1.0 版本页** → paste promo / description / keywords / URLs / copyright; attach build **100000**. +3. **App 隐私** → "Data Not Collected" ([privacy-and-review.md](./privacy-and-review.md)). +4. **Upload screenshots** from `screenshots/ios-6.9/` (+ iPad once captured). +5. **App 审核信息** → paste the review notes ([privacy-and-review.md](./privacy-and-review.md)). +6. **Submit for Review.** + +## Mac +Listing copy for the Mac app is in [listing.md](./listing.md) (§Mac). Mac +screenshots aren't in this kit yet (the provided captures are iPhone) — capture +macOS shots (editor, GitHub vault, search, themes) and they can be framed the +same way via the generator script. diff --git a/marketing/app-store/listing.md b/marketing/app-store/listing.md new file mode 100644 index 0000000..759dc20 --- /dev/null +++ b/marketing/app-store/listing.md @@ -0,0 +1,184 @@ +# App Store listing copy — Markup + +Paste-ready text for App Store Connect, EN + 中文. iOS = read-first vault on +iPhone/iPad with iCloud; Mac = WYSIWYG editor. Character limits are ASC's. + +## App info (shared across platforms) +- **App name** (30): `Markup - MD Reader` ← as set in ASC (iOS). Brand/product name is "Markup". +- **Primary category**: Productivity · **Secondary** (optional): Utilities +- **Price**: Free · no in-app purchases +- **Age rating**: 4+ (no objectionable content) +- **Content Rights** (App Information → Content Rights): *does NOT contain, show, or access third-party content* (Markup only handles the user's own files). +- **Support URL**: https://github.com/oratis/Markup +- **Marketing URL** (optional): https://github.com/oratis/Markup +- **Privacy Policy URL**: https://github.com/oratis/Markup/blob/main/PRIVACY.md +- **Copyright**: `© 2026 Bihao Wang` + +--- + +## iOS + +### Subtitle (30) +- EN: `Markdown reader & editor` +- 中文: `Markdown 阅读与编辑` + +### Promotional text (170, editable any time) +- EN: `Read your Markdown like a page — code, math, diagrams, tables. Point it at any iCloud/Files folder, or open a GitHub repo as a vault. Private by default.` +- 中文: `像读一页纸那样读 Markdown——代码、公式、图表、表格。指向任意 iCloud/「文件」文件夹,或把一个 GitHub 仓库作为 vault 打开。默认隐私。` + +### Keywords (100, comma-separated, no spaces) +EN — trimmed of words already in the name/subtitle (markdown, md, reader, editor), so no slot is wasted: +``` +notes,obsidian,vault,icloud,github,preview,writing,latex,mermaid,diagram,code,tags,outline +``` +中文: +``` +markdown,笔记,编辑器,阅读,vault,obsidian,iCloud,github,预览,写作,公式,图表 +``` + +### Description +EN: +``` +Markup reads and edits your Markdown beautifully — and it works with the folder +of notes you already have, on your iPhone and iPad. + +POINT IT AT YOUR FOLDER — OR A GITHUB REPO +• Open any folder of .md files from iCloud Drive or Files — the same vault you + use on your Mac. Nothing to import, no lock-in. +• Open a GitHub repo as a vault and read the whole thing offline. +• Open several vaults and switch between them. + +READ LIKE A PAGE +• High-fidelity rendering: syntax-highlighted code, LaTeX math, Mermaid + diagrams, tables, task lists, and GitHub-style callouts. +• Light / Dark / Sepia themes, adjustable text size and width, Dynamic Type. +• Wikilinks, backlinks with context, a per-document outline, and tags. + +FIND ANYTHING +• Full-text search with tag: and path: filters; fuzzy Quick Open. + +EDIT WITH EASE +• A focused Markdown editor with smart lists, auto-closing brackets, table + formatting, and inline [[wikilink]] and #tag autocomplete. +• Insert images straight into your vault. Export or share as PDF, HTML, or + Markdown. + +iPAD +• Open documents in tabs; edit with a live rendered preview beside the source. +• Hardware-keyboard shortcuts. + +HTML, TOO +• Open and read .html and web bundles, with scripts off by default for safety. + +PRIVATE BY DEFAULT +• No account required, no telemetry, no tracking. Your notes stay on your device + and your iCloud. Signing in to GitHub is optional and only used to fetch the + files you ask for. + +English and 简体中文, switchable in-app. +``` + +中文: +``` +Markup 把你的 Markdown 漂亮地读出来、顺手地写下去——而且直接用你已经有的笔记 +文件夹,在 iPhone 和 iPad 上。 + +指向你的文件夹,或一个 GitHub 仓库 +• 打开 iCloud 云盘或「文件」里任意 .md 文件夹——和你 Mac 上用的是同一个仓库。 + 无需导入,不绑定。 +• 把一个 GitHub 仓库作为 vault 打开,整库离线阅读。 +• 可打开多个仓库并随时切换。 + +像读一页纸 +• 高保真渲染:代码高亮、LaTeX 公式、Mermaid 图表、表格、任务列表,以及 + GitHub 风格的提示框(callout)。 +• 浅色/深色/护眼三种主题,字号与宽度可调,支持动态字体。 +• 双链 [[wikilink]]、带上下文的反向链接、单文档大纲、标签。 + +快速查找 +• 全文搜索,支持 tag: 与 path: 过滤;模糊「快速打开」。 + +轻松编辑 +• 专注的 Markdown 编辑器:智能列表、括号自动闭合、表格对齐,行内 + [[双链]] 与 #标签 自动补全。 +• 图片可直接插入仓库。可导出/分享为 PDF、HTML 或 Markdown。 + +iPad +• 多文档标签页;编辑时源码旁实时渲染预览。 +• 支持硬件键盘快捷键。 + +也支持 HTML +• 可打开并阅读 .html 与网页压缩包,默认关闭脚本以保安全。 + +默认隐私 +• 无需账户、无遥测、无追踪。你的笔记只留在本机与你的 iCloud。登录 GitHub 为 + 可选,且仅用于拉取你指定的文件。 + +支持 English 与简体中文,App 内即可切换。 +``` + +### Screenshot captions (iOS 6.9″) — see `screenshots/ios-6.9/` +| # | File | Headline | Subline | +|---|------|----------|---------| +| 1 | 01_read | Read Markdown, beautifully rendered | Code, math, diagrams, tables — in tabs | +| 2 | 02_github-vault | Open any GitHub repo as a vault | Read the whole repo — offline | +| 3 | 03_bring-your-own | Bring your own folder or GitHub repo | iCloud, Files, or GitHub — nothing to import | +| 4 | 04_reader-first | A reader-first Markdown app | Light, dark & sepia themes | +| 5 | 05_files-icloud | Works with Files & iCloud Drive | Private by default — your notes stay yours | + +--- + +## Mac + +### Subtitle (30) +- EN: `Markdown editor for your vault` +- 中文: `面向你仓库的 Markdown 编辑器` + +### Promotional text (170) +- EN: `A fast, local-first Markdown editor. WYSIWYG editing, full-text search, math & diagrams, HTML export. Open any folder or a GitHub repo — your notes stay on your Mac.` +- 中文: `快速、本地优先的 Markdown 编辑器。所见即所得、全文搜索、公式与图表、HTML 导出。打开任意文件夹或 GitHub 仓库——笔记只留在你的 Mac。` + +### Keywords (100) +EN: +``` +markdown,editor,notes,vault,obsidian,wysiwyg,search,latex,mermaid,html,export,writing,canvas +``` +中文: +``` +markdown,编辑器,笔记,仓库,obsidian,所见即所得,搜索,公式,图表,html,导出,写作 +``` + +### Description +EN: +``` +Markup is a fast, local-first Markdown editor for the folder of notes you +already have. + +• WYSIWYG editing that stays plain Markdown on disk — no proprietary format. +• Open any folder as a vault; instant full-text search, tags, wikilinks, and + backlinks. +• Open a GitHub repo as a vault — read and edit it locally. +• Syntax-highlighted code, LaTeX math, Mermaid diagrams, tables, task lists, + and GitHub-style callouts. +• Export or preview as self-contained HTML; print to PDF. +• Light / Dark / Sepia themes and custom CSS. +• English and 简体中文. + +Private by default: no account, no telemetry. Your notes never leave your Mac. +The Mac App Store edition is fully sandboxed. +``` + +中文: +``` +Markup 是一款快速、本地优先的 Markdown 编辑器,直接用你已经有的笔记文件夹。 + +• 所见即所得编辑,磁盘上始终是纯 Markdown——无私有格式。 +• 把任意文件夹作为仓库打开;即时全文搜索、标签、双链与反向链接。 +• 把一个 GitHub 仓库作为 vault 打开——在本地阅读与编辑。 +• 代码高亮、LaTeX 公式、Mermaid 图表、表格、任务列表,以及 GitHub 风格提示框。 +• 可导出或预览为自包含 HTML;可打印为 PDF。 +• 浅色/深色/护眼主题,支持自定义 CSS。 +• 支持 English 与简体中文。 + +默认隐私:无账户、无遥测,笔记绝不离开你的 Mac。Mac App Store 版完全沙盒化。 +``` diff --git a/marketing/app-store/privacy-and-review.md b/marketing/app-store/privacy-and-review.md new file mode 100644 index 0000000..07c5358 --- /dev/null +++ b/marketing/app-store/privacy-and-review.md @@ -0,0 +1,68 @@ +# App Privacy + App Review notes — Markup + +Exact answers for App Store Connect's **App Privacy** questionnaire and the +**App Review Information** notes. Markup has no backend and no analytics, so the +label is **Data Not Collected** on both platforms — backed in code by the iOS +`PrivacyInfo.xcprivacy` (Tracking = false, Collected data types = none) and the +absence of any analytics SDK in either app. + +## App Privacy questionnaire → answers +**"Do you or your third-party partners collect data from this app?"** → **No.** +Result label: **Data Not Collected.** + +Rationale you can paste if asked: +- No account system, no server owned by us — nowhere for data to be collected to. +- No analytics, crash-reporting, advertising, or tracking SDKs. +- Notes/files stay on the device (and the user's own iCloud, iOS only). +- The only outbound network is **user-initiated**: + - **GitHub API** (api.github.com / raw.githubusercontent.com) when the user + opens a file/repo from GitHub. Requests go to GitHub, not to us. An optional + OAuth token is stored **on-device** (iOS Keychain / macOS login Keychain) and + sent only to GitHub as an `Authorization` header. + - **Math/diagram CDN** (jsDelivr) only inside *exported/preview* HTML using + KaTeX/Mermaid (Mac export; the iOS reader bundles these offline). No personal + data is sent. +- This contacts third parties (GitHub, jsDelivr) but does **not** "collect data" + in the App Privacy sense — nothing about the user is gathered; the calls fetch + content the user explicitly asked for. + +**Tracking** (App Tracking Transparency): **No tracking.** Do not add +`NSUserTrackingUsageDescription` — the app never tracks. + +## Export compliance (iOS) — already answered in the project +`ITSAppUsesNonExemptEncryption = NO` is set in the Xcode build settings, so each +upload auto-answers the export-compliance prompt (standard HTTPS / ATS is +exempt). Nothing to do per-submission. + +## App Review notes (paste into "App Review Information → Notes") +``` +Markup is a local-first Markdown reader/editor. It has NO account and NO server +of ours — there is no login to Markup itself and no test credentials are needed. + +HOW TO REVIEW (the app works on a folder of files the user picks): +1. Launch the app. On first run an onboarding card explains the "bring your own + folder / GitHub repo" model. +2. Tap "Open folder" and choose any folder of .md files (on the simulator, use + the Files app's "On My iPhone" area), OR tap "Open from GitHub" and paste a + public repo — e.g. github.com/oratis/Markup — then "Open as Vault". No account + needed to review. +3. A sample vault for review is included in the repo at + docs/app-store/reviewer-sample-vault/ — copy it into Files/iCloud and open it. + It demonstrates rendering (code, math, tables, callouts), wikilinks, search, + and editing. + +OPTIONAL GITHUB SIGN-IN: +- "Open from GitHub" works with PUBLIC repos WITHOUT signing in. +- Signing in (GitHub OAuth device flow) is OPTIONAL and only unlocks private + repos + higher rate limits. The token is stored in the Keychain on-device and + is sent only to GitHub. + +PRIVACY: no analytics, no tracking, no data collection. See PRIVACY.md. +``` +(Mac: same notes; instead of Files, the reviewer uses ⌘⇧O / "Open Folder" and the +same sample vault. The Mac App Store build is sandboxed.) + +## Why this matters (BYO-folder gotcha) +Markup's whole model is "point me at a folder/repo you already have." A reviewer +who launches it cold sees onboarding, not content. The review notes + the bundled +sample vault prevent a "the app appears non-functional / incomplete" rejection. diff --git a/marketing/app-store/screenshots/README.md b/marketing/app-store/screenshots/README.md new file mode 100644 index 0000000..e2c4ec7 --- /dev/null +++ b/marketing/app-store/screenshots/README.md @@ -0,0 +1,32 @@ +# Screenshots + +Marketing screenshots for the App Store, composed from the raw device captures in +`../../MarkupScreenshots/` by `marketing/scripts/make-screenshots.py` (brand +gradient + caption + framed screenshot; iOS status bar / TestFlight banner cropped). + +Regenerate: `python3 marketing/scripts/make-screenshots.py` + +## iOS — `ios-6.9/` (1320×2868, "6.9-inch display") +This is Apple's current primary iPhone size; ASC scales it to the smaller iPhone +slots, so a separate 6.5″ set is optional. + +| # | File | Headline | Source | +|---|------|----------|--------| +| 1 | `01_read.png` | Read Markdown, beautifully rendered | IMG_4042 (reader + code + tabs) | +| 2 | `02_github-vault.png` | Open any GitHub repo as a vault | IMG_4041 (repo file list) | +| 3 | `03_bring-your-own.png` | Bring your own folder or GitHub repo | IMG_4039 (onboarding) | +| 4 | `04_reader-first.png` | A reader-first Markdown app | IMG_4038 (onboarding) | +| 5 | `05_files-icloud.png` | Works with Files & iCloud Drive | IMG_4040 (Files picker) | + +Upload at least 1–3; the first 2–3 appear in App Store search results, so lead +with `01` and `02`. `05` (the system Files picker) is the weakest — optional. + +## Still needed +- **iPad 13″ (2064×2752)** — the app is universal, so Apple **requires** iPad + screenshots. Capture a few on an iPad (or iPad simulator) — e.g. tabs + split + live preview, the file list beside the reader — drop the raw PNGs in + `../../MarkupScreenshots/`, add them to `SHOTS` in the generator with an + `ipad-13` output dir, and re-run. +- **Mac (1280×800 or 2560×1600)** — for the Mac App Store listing; capture the + desktop app (editor, GitHub vault, search, HTML export, themes). +- Optional: a localized 简体中文 set (re-run with 中文 captions). diff --git a/marketing/app-store/screenshots/ios-6.9/01_read.png b/marketing/app-store/screenshots/ios-6.9/01_read.png new file mode 100644 index 0000000..d964f2a Binary files /dev/null and b/marketing/app-store/screenshots/ios-6.9/01_read.png differ diff --git a/marketing/app-store/screenshots/ios-6.9/02_github-vault.png b/marketing/app-store/screenshots/ios-6.9/02_github-vault.png new file mode 100644 index 0000000..4c6bdaa Binary files /dev/null and b/marketing/app-store/screenshots/ios-6.9/02_github-vault.png differ diff --git a/marketing/app-store/screenshots/ios-6.9/03_bring-your-own.png b/marketing/app-store/screenshots/ios-6.9/03_bring-your-own.png new file mode 100644 index 0000000..63c3575 Binary files /dev/null and b/marketing/app-store/screenshots/ios-6.9/03_bring-your-own.png differ diff --git a/marketing/app-store/screenshots/ios-6.9/04_reader-first.png b/marketing/app-store/screenshots/ios-6.9/04_reader-first.png new file mode 100644 index 0000000..20ed6b2 Binary files /dev/null and b/marketing/app-store/screenshots/ios-6.9/04_reader-first.png differ diff --git a/marketing/app-store/screenshots/ios-6.9/05_files-icloud.png b/marketing/app-store/screenshots/ios-6.9/05_files-icloud.png new file mode 100644 index 0000000..5ed9c19 Binary files /dev/null and b/marketing/app-store/screenshots/ios-6.9/05_files-icloud.png differ diff --git a/marketing/scripts/make-screenshots.py b/marketing/scripts/make-screenshots.py new file mode 100644 index 0000000..0c7486e --- /dev/null +++ b/marketing/scripts/make-screenshots.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Compose App Store marketing screenshots for Markup (iOS). + +Takes the raw device captures in marketing/MarkupScreenshots/ and renders them +onto branded 1320×2868 canvases (App Store "6.9-inch display" size) with a +headline caption, a subline, and the screenshot framed with rounded corners + +a soft shadow. The iOS status bar / TestFlight banner is cropped off the top. + +Run: python3 marketing/scripts/make-screenshots.py +Out: marketing/app-store/screenshots/ios-6.9/NN_slug.png +""" +import os +from PIL import Image, ImageDraw, ImageFont, ImageFilter + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(os.path.dirname(HERE)) # repo root +SRC = os.path.join(ROOT, "marketing", "MarkupScreenshots") +OUT = os.path.join(ROOT, "marketing", "app-store", "screenshots", "ios-6.9") +os.makedirs(OUT, exist_ok=True) + +# App Store "6.9-inch display" canvas (iPhone 16 Pro Max class). Apple accepts +# this size for every iPhone slot. +W, H = 1320, 2868 + +# Brand palette: navy → indigo gradient, light text, blue wordmark/accent. +GRAD_TOP = (13, 19, 38) # #0D1326 deep navy +GRAD_BOTTOM = (37, 26, 74) # #251A4A indigo +WORDMARK = (110, 168, 255) # #6EA8FF +HEADLINE = (255, 255, 255) +SUBLINE = (183, 193, 214) # #B7C1D6 + +CROP_TOP = 132 # drop the iOS status bar / "◀ TestFlight" band +CARD_W = 1040 +CARD_TOP = 612 +CARD_RADIUS = 46 + +# (source, headline (\n = line break), subline, output slug). Strongest first — +# the App Store shows the first 2–3 in search results. +SHOTS = [ + ("IMG_4042.PNG", "Read Markdown,\nbeautifully rendered", + "Code, math, diagrams, tables — in tabs", "01_read"), + ("IMG_4041.PNG", "Open any GitHub repo\nas a vault", + "Read the whole repo — offline", "02_github-vault"), + ("IMG_4039.PNG", "Bring your own folder\nor GitHub repo", + "iCloud, Files, or GitHub — nothing to import", "03_bring-your-own"), + ("IMG_4038.PNG", "A reader-first\nMarkdown app", + "Light, dark & sepia themes", "04_reader-first"), + ("IMG_4040.PNG", "Works with Files\n& iCloud Drive", + "Private by default — your notes stay yours", "05_files-icloud"), +] + + +def load_font(size, bold=False): + """Prefer SF Pro (the iOS system font); fall back to Helvetica/Arial.""" + sf = "/System/Library/Fonts/SFNS.ttf" + if os.path.exists(sf): + try: + f = ImageFont.truetype(sf, size) + try: + f.set_variation_by_name("Bold" if bold else "Regular") + except Exception: + pass + return f + except Exception: + pass + cands = (["/System/Library/Fonts/Supplemental/Arial Bold.ttf"] if bold + else ["/System/Library/Fonts/Supplemental/Arial.ttf"]) + cands += ["/System/Library/Fonts/Helvetica.ttc"] + for p in cands: + if os.path.exists(p): + try: + return ImageFont.truetype(p, size) + except Exception: + continue + return ImageFont.load_default() + + +def gradient(w, h, top, bottom): + img = Image.new("RGB", (w, h), top) + d = ImageDraw.Draw(img) + for y in range(h): + t = y / (h - 1) + d.line([(0, y), (w, y)], fill=tuple( + int(top[i] + (bottom[i] - top[i]) * t) for i in range(3))) + return img + + +def rounded(img, radius): + img = img.convert("RGBA") + mask = Image.new("L", img.size, 0) + ImageDraw.Draw(mask).rounded_rectangle( + [0, 0, img.size[0] - 1, img.size[1] - 1], radius=radius, fill=255) + img.putalpha(mask) + return img + + +def centered(draw, text, font, y, fill): + w = draw.textlength(text, font=font) + draw.text(((W - w) / 2, y), text, font=font, fill=fill) + asc, desc = font.getmetrics() + return y + asc + desc + + +def compose(src_name, headline, subline, slug): + canvas = gradient(W, H, GRAD_TOP, GRAD_BOTTOM).convert("RGBA") + draw = ImageDraw.Draw(canvas) + + wm = load_font(46, bold=True) + centered(draw, "Markup", wm, 84, WORDMARK) + + hf = load_font(98, bold=True) + y = 196 + for line in headline.split("\n"): + y = centered(draw, line, hf, y, HEADLINE) + 6 + sf = load_font(46, bold=False) + y += 14 + for line in subline.split("\n"): + y = centered(draw, line, sf, y, SUBLINE) + 4 + + shot = Image.open(os.path.join(SRC, src_name)).convert("RGB") + shot = shot.crop((0, CROP_TOP, shot.width, shot.height)) + card_h = round(CARD_W * shot.height / shot.width) + shot = shot.resize((CARD_W, card_h), Image.LANCZOS) + card = rounded(shot, CARD_RADIUS) + x = (W - CARD_W) // 2 + yc = CARD_TOP + + shadow = Image.new("RGBA", (W, H), (0, 0, 0, 0)) + ImageDraw.Draw(shadow).rounded_rectangle( + [x, yc + 30, x + CARD_W, yc + card_h + 30], radius=CARD_RADIUS, + fill=(0, 0, 0, 150)) + shadow = shadow.filter(ImageFilter.GaussianBlur(40)) + canvas = Image.alpha_composite(canvas, shadow) + canvas.alpha_composite(card, (x, yc)) + # Hairline rim so dark-mode screenshots separate from the dark gradient. + ImageDraw.Draw(canvas).rounded_rectangle( + [x, yc, x + CARD_W - 1, yc + card_h - 1], radius=CARD_RADIUS, + outline=(255, 255, 255, 48), width=2) + + out = os.path.join(OUT, f"{slug}.png") + canvas.convert("RGB").save(out, "PNG") + return out + + +if __name__ == "__main__": + for s in SHOTS: + print("wrote", compose(*s))