你将商店截图集构建为数据,而非生成的图像。每个图块都是一个 HTML Canvas,包含多层:背景、标题、一个带有真实截图的设备框架,以及从屏幕中提取的 3D 标注(截取部分或矢量文字标注)。每个字符串都存储在 locales/<locale>.json 中,因此添加新语言只需一个 JSON 文件和对应的截图,并且渲染过程是确定性的:相同的输入产生相同的像素。
下面的 $S 是这个 SKILL.md 文件所在的文件夹,$PY 是 $S/.venv/bin/python。如果虚拟环境不存在,请创建一次:
python3 -m venv $S/.venv && $S/.venv/bin/pip install -r $S/requirements.txt && $S/.venv/bin/playwright install chromium
流水线:
1 卖点 -> 2 幻灯片 -> 3 capture.json -> 4 截图 (模拟器) -> 5 组合
-> 6 背景 -> 7 set.json + 本地化文件 -> 8 渲染 -> 9 审查 -> (更多语言, 更多设备)
仓库中的 $S/../../examples/demo/ 是一个完整的项目(虚构应用,两种语言,五个设备)。在编写自己的 set.json 之前,请先阅读它。
项目布局
<project>/
capture.json 应用、截图设备、需要截图的屏幕
set.json 所有语言共享的组合配置
locales/<locale>.json 每个字符串:动词、副标题、标注文本
sim/<locale>/<slide>.png 手机截图(每个截图集一个文件夹)
sim-tablet/ sim-duo/ sim-android/
fonts/ assets/ 可选字体、背景图片、道具
out/<locale>/<device>/ 仅限商店文件
out/_sheets/ 每个语言和设备的联系表
out/_captures/ 每种语言截图后的联系表
建议将 store-screenshots/ 放在应用仓库的根目录。将 out/ 和 .cache/ 添加到其 .gitignore。
1. 卖点
阅读代码库:导航(标签页、路由、屏幕)、引导文案、付费墙文案、README、现有的商店列表。列出 5 到 8 个用户获得的好处,按它们能推动安装的程度排序。每张幻灯片一个卖点。
每张幻灯片的标题是一个简短的大写动词加上最多两行的副标题:PLAN / YOUR WEEK IN MINUTES,SAVE / ON EVERY SHOP。动词命名的是好处,而不是功能。展示列表,并附上能证明每个卖点的屏幕,获得用户确认。
2. 幻灯片
ID 格式为 NN-name;数字是商店中的位置。为每个幻灯片选择屏幕并记录其所需状态(已登录、数据存在、某面板打开)。对图片密集的屏幕使用倾斜,对数据密集的屏幕保持平直。
3. capture.json
从 $S/templates/capture.json 开始。
| 键 | 含义 |
|---|---|
app.ios | bundleId,以及 app(指向模拟器 .app 构建的路径)或 copyFrom(已安装应用的模拟器 UDID) |
app.android | package,可选的 activity,可选的要安装的 apk |
devices.<key> | platform ios 或 android,dir(截图文件夹),iOS:name(专用模拟器,由 setup 创建)和 type(xcrun simctl list devicetypes),可选的 runtime;Android:avd(emulator -list-avds) |
| 设备额外选项 | appearance(light),locale(覆盖下面的模式),displaySize(2853x2007 用于 iPhone Duo 内屏),rotate(角度,用于横向截图的校正) |
locale | 每个平台。iOS 使用 reboot(默认,最可靠)、relaunch(不重启,保持折叠屏姿态)或 launchArgs。Android 使用 app(默认,Android 13+ 的每应用语言,需要应用的 localeConfig)或 system(需要 root 权限的模拟器镜像) |
launch | 启动后的 wait 秒数,可选的 url(用于替代启动,如 Expo 开发客户端 URL),可选的 steps |
screens | {id, steps, ready, after, devices, copyFrom} 列表,以及通过 byDevice/byLocale 块合并到屏幕上的内容 |
步骤按顺序执行,每个步骤都带有等待时间(wait,默认 1 到 2 秒):
| 步骤 | 效果 |
|---|---|
{"open": "myapp://route"} | 深度链接。优于点击操作:在所有设备和平台上都有效 |
{"tap": {"label": "Save"}} | 点击具有该无障碍标签的元素(或 {"id": ...}),精确匹配然后子串匹配 |
{"tap": {"at": [0.5, 0.9]}} | 在屏幕的某个比例位置点击 |
{"tap": {...}, "until": {"label": "Details"}} | 一直点击(最多 3 次)直到该元素出现;用于屏幕还在稳定时的点击 |
{"waitFor": {"label": "Done", "timeout": 20}} | 等待直到屏幕上出现某个元素,用于加载或动画中的屏幕点击前 |
{"type": "pasta"}, {"key": "enter"}, {"key": "back"} | 文本和按键 |
{"swipe": [0.5, 0.8, 0.5, 0.3]} | 滑动,使用比例坐标 |
{"launch": true} | 重启应用 |
ready: {"region": [x, y, w, h], "minStd": 20, "tries": 5, "delay": 3} 会在该区域颜色单一(加载骨架行、旋转指示器、正在加载的图片)时重新截图。iPhone 截图会在模拟器渲染了灵动岛后自动重试。
copyFrom: "01-home" 会在该设备上复用另一张截图(例如平板电脑以分屏视图显示两个屏幕)。devices: ["phone"] 将屏幕限制在某些设备上。
capture.py PROJECT describe --device phone 会打印带有标签和框架的可点击元素。
4. 截图
$PY $S/capture.py <project> doctor # 工具、设备、应用 ID
$PY $S/capture.py <project> setup # 创建/启动专用模拟器,启动 AVD,安装应用
$PY $S/capture.py <project> run --locale en-US # 每个设备和屏幕
$PY $S/capture.py <project> run --locale en-US --device phone --screen 03-search
- 截图设备是专用的:iOS 模拟器按名称创建(
Capture iPhone...),Android 使用capture.json中命名的 AVD。绝不要将capture.json指向用户正在使用的模拟器,并在启动设备或切换其语言之前征询同意。 - 在
setup之后,应用通常需要演示状态:登录到演示账户、填充数据、关闭首次运行对话框(保存密码、通知、评价提示)。在每个设备上只做一次;该状态在语言切换后仍然保持。向用户询问演示凭据,绝不要将其写入capture.json。 - 显示调试覆盖层(开发菜单按钮、Toast)的开发构建需要为截图会话隐藏这些内容。在本地修补它们,并且不要提交该修补。
- 逐张检查
out/_captures/<locale>.jpg:没有其他语言的字符串,没有对话框、Toast、旋转指示器或空状态。修复数据或步骤,然后重新运行该屏幕。渲染器会原样显示截图;不会对错误进行任何涂抹。 - iPhone Duo:首先在 Simulator 或 DeviceHub 中展开模拟器并置于横屏;
simctl无法折叠或旋转。使用深度链接(HID 点击可能无法到达内屏),"locale": "relaunch"和"displaySize": "2853x2007"。
跨平台应用(React Native、Flutter)可以跳过 Android 截图:Android 图块会复用 iPhone 和 iPad 的截图,这也是默认设备所做的。原生 Android 应用会将其屏幕截图到 sim-android/,请参见第 10 节。
5. 组合(渲染前用户需批准)
用 ASCII 草图绘制每张幻灯片:标题、设备位置和倾斜度、哪个元素被提取出来以及它落在何处。获得同意后,再编写 set.json。
+------------------------------+
| WATER | 动词,一行
| EVERY PLANT RIGHT | 副标题,最多两行
| ON TIME |
| .--------------. |
| [* 3 plants today] | 标注,被提取,向左突出
| | Today ... | | 设备倾斜 y 15, x 4
+------------------------------+
6. 背景
每个截图集一个背景,所有语言和设备共享。可以是 CSS("background": {"css": "radial-gradient(...), #1f4a37"},演示中使用了这个)或图片({"image": "assets/bg.png"},background-size cover)。图片生成器适用于柔和的背景;提示生成一个无缝的 9:16 演播室背景,使用品牌色调和柔和光线,并明确指出:没有物体、没有文字、没有设备、没有形状。生成两个,让用户选择。道具(贴纸、产品照片)是带有透明通道的 PNG,按幻灯片放置。
7. set.json 和本地化文件
使用 measure.py 以截图像素为单位测量每个矩形,不要凭目测:
$PY $S/measure.py sim/en-US/05-fill.png --at 700,790 # 泛洪填充按钮、芯片、药丸形状
$PY $S/measure.py sim/en-US/01-plan.png --ink 40,415,700,75 # 在纯色区域上的文本边界
$PY $S/measure.py sim/en-US/02-cook.png --grid 440,1510,340,110 # 临时目录中的缩放网格图像
| 键 | 含义 |
|---|---|
locales, devices | 完整渲染覆盖的范围;设备 ID 来自 $S/devices/devices.json |
background | {"image": ...} 或 {"css": ...},可选的 position |
fonts | {"family": "fonts/File.otf"},路径相对于项目;默认 display 字体是自带的 Archivo Black |
headline | font, descFont, verbColor, descColor, shadow, verbTracking, descTracking, descWordSpacing |
capture | platform, statusBarBand(截图行被混合掉,iOS 默认 [66, 128]),sets({"tablet": {"dir": "sim-tablet", "statusBarBand": [12, 56]}}),devices({"android-phone": "android"} 将设备指向另一个截图集) |
style | gloss, deviceShadow, thicknessRatio(侧面墙壁深度),sideLayers |
statusBar | 渲染器绘制的状态栏的 time、color |
layout | 按设备 ID,覆盖 devices.json 中的布局(deviceTop, deviceWidth, verbMax ...) |
baseline | 实时截图集的 {device: {locale: folder}},用于 --compare |
slides | 列表,见下方 |
幻灯片:id,sim(如果截图文件名不是 <id>.png 时使用),tilt {x, y, z} 度,offset {x, y} 画布比例,layout,breakouts,props(src,x,y,w 画布比例,rotate,shadow,z:front 或 back)。
标注(Breakout):
| 键 | 含义 |
|---|---|
type | crop:截图自身的像素;callout:带有本地化文本的矢量药丸形状 |
rect 或 anchor | 来源元素在截图像素中的 [x, y, w, h] |
at 或 dx/dy | 落点位置(at = 在截图像素中的新左上角)或偏移量 |
scale, lift | 大小和 Z 轴提升(像素,默认 80) |
vacate, vacatePad | 用周围颜色填充原始位置(移动,而不是复制) |
radius, shadow, origin | 圆角半径(截图像素)、阴影强度、变换原点 |
text, icon, style | callout:本地化文件 callouts 中的键,tag 或 sparkles,以及 font,size,bg,fg,padX,padY,gap,radius,weight,tracking,iconScale,iconColor |
slides 中的任何值都可以按设备和按语言变化:{"byDevice": {"phone": ..., "tablet": ..., "default": ...}}(键:设备 ID、截图集、default)和 {"byLocale": {"de-DE": ..., "default": ...}},两者可以嵌套。矩形几乎总是按语言变化,因为同一个芯片在德语中会更宽。
裁剪(Crop)对于大元素可以在最大约 1.7 倍缩放时保持清晰。小的芯片和文本会变成 callout(矢量),在任何缩放比例下都保持清晰。
本地化文件:
{
"captures": "en-US",
"dir": "ltr",
"fonts": {"display": "fonts/NotoSansJP-Black.otf"},
"slides": {
"01-plan": {"verb": "Plan", "desc": "Your week in minutes", "callouts": {"savings": "approx. €7.81 saved"}}
}
}
只有 slides 是必需的。captures 使一个语言复用另一个语言的截图(例如 en-GB 读取 en-US)。headline 和 statusBar 会覆盖 set.json 中的设置。Archivo Black 只覆盖拉丁字符;西里尔文、希腊文、中日韩文字、阿拉伯文和希伯来文需要 fonts 覆盖,从右到左的文字需要 "dir": "rtl"。
8. 渲染
$PY $S/render.py <project> # 渲染所有内容
$PY $S/render.py <project> --locale de-DE --device iphone-67 --slide 01-plan
$PY $S/render.py <project> --compare # 基线 vs 新版本联系表
标题大小在某个语言和设备的幻灯片之间共享,因此整套看起来像一个整体。当截图的宽高比与设备屏幕开口不匹配时,渲染器会停止。
9. 审查
打开 out/_sheets/<locale>-<device>.jpg 并放大查看每个标注。打印为 CHECK 的行会命名问题(标题最小尺寸,重叠)。标题达到最小尺寸意味着文案太长:缩短它,不要降低限制。向用户展示联系表。
10. 更多语言和设备
添加语言:
$PY $S/add_locale.py <project> es-ES init --from en-US # 本地化文件、byLocale 占位符、文件夹
$PY $S/capture.py <project> run --locale es-ES # 截图
# 翻译 locales/es-ES.json:商店文案、简短动词、与截图匹配的标注
$PY $S/add_locale.py <project> es-ES measure --from en-US # 重新查找矩形位置,审查并排图像
$PY $S/add_locale.py <project> es-ES measure --from en-US --write
$PY $S/add_locale.py <project> es-ES check # 渲染前确保 0 错误
$PY $S/render.py <project> --locale es-ES
区域变体(en-GB、de-AT、fr-CA):使用 init --share-captures,然后只需更改文本。
$S/devices/devices.json 中的设备:
| id | 画布 | 读取 | 商店插槽 |
|---|---|---|---|
iphone-67 | 1290x2796 | 手机 (1206x2622) | ASC APP_IPHONE_67 |
android-phone | 1080x1920 | 手机 | Play 手机截图 |
ipad-13 | 2064x2752 | 平板 (2064x2752) | ASC APP_IPAD_PRO_3GEN_129 |
android-tablet | 1792x2400 | 平板 | Play 7 英寸和 10 英寸平板 |
iphone-duo | 2853x2007 | duo (2853x2007) | ASC iPhone Duo |
这些框架是用 make_frame.py 生成的通用图形,可自由分发。没有匹配框架的截图尺寸会生成一个:
# 原生 Android 截图,1080x2400,放入 sim-android/
$PY $S/make_frame.py --style android --screen 1080x2400 --device android-phone --captures android
# 然后在 set.json 中:"capture": {"sets": {"android": {"dir": "sim-android", "statusBarBand": [30, 105]}}}
样式:ios、android、tablet、landscape。--canvas WxH 设置新设备 ID 的商店尺寸。在真实截图上测量 statusBarBand:状态栏的行数加上一些保留区域。
设备框架
设备框架位于 $S/devices/frames/,由 $S/make_frame.py 生成。它们是简单的几何绘图,不包含来自品牌手机的闯入性图像,因此分发没有法律问题。
主要设备及其框架:
- iPhone 6.7 英寸 (
iphone-67):细长圆角矩形,带有动态岛的凹口。 - Android 手机 (
android-phone):带圆角的矩形,薄边框。 - iPad 13 英寸 (
ipad-13):更大的矩形,平直的顶部和底部边缘,Home 指示器条。 - Android 平板 (
android-tablet):中等矩形,横屏方向。 - iPhone Duo (
iphone-duo):横屏的大折叠设备,带有显示铰链的中间折痕。
每个框架都放置在 HTML Canvas 上,并可以相对于画布进行定位和倾斜。
常见工作流程
从头开始创建一个新的截图集
# 1. 创建项目结构
$PY $S/create_project.py my-app --app-path /path/to/app
# 2. 分析代码库以获取卖点
# (手动步骤,与 AI 协作)
# 3. 设置截图设备
$PY $S/capture.py my-app setup
# 4. 截取第一个语言的屏幕
$PY $S/capture.py my-app run --locale en-US
# 5. 组合幻灯片(在 set.json 中编写)
# 6. 渲染
$PY $S/render.py my-app
# 7. 审查并迭代
为现有截图集添加新语言
$PY $S/add_locale.py my-app fr-FR init --from en-US
# 翻译 locales/fr-FR.json
$PY $S/capture.py my-app run --locale fr-FR
$PY $S/add_locale.py my-app fr-FR measure --from en-US --write
$PY $S/add_locale.py my-app fr-FR check
$PY $S/render.py my-app --locale fr-FR
添加新设备
# 在 capture.json 中添加设备定义
# 在 devices.json 中添加设备 ID 和布局
$PY $S/make_frame.py --style ios --screen 1290x2796 --device new-iphone --captures phone
# 为舌头设备截图
$PY $S/capture.py my-app run --locale en-US --device new-iphone
# 渲染该设备
$PY $S/render.py my-app --device new-iphone
最佳实践
- 卖点第一:始终从分析代码库中的卖点开始。商店截图应传达价值,而不仅仅是功能。
- 稀疏截图:每张幻灯片只突出一个主要卖点。避免杂乱的布局。
- 一致的标题样式:对所有幻灯片使用相同的字体、大小和颜色,以保持品牌统一。
- 清晰的标注:确保标注(callouts)指向屏幕上的正确元素,并使用清晰、可操作的文本。
- 跨语言测试:为每个目标语言截图和渲染,验证位置和文本在多语言中是否合适。
- 版本控制:将
out/和.cache/添加到.gitignore,但跟踪capture.json、set.json、locales/和fonts/。 - ISO 镜像:使用
--locale参数为多个区域设置生成截图,每个区域设置有自己的本地化文件和文本。 - 自动化 CI/CD:将截图生成集成到 CI 流程中,以便在每次应用更新时自动更新商店截图。
故障排除
| 问题 | 解决 |
|---|---|
setup 无法创建模拟器 | 确保 xcrun simctl 已安装,并且 macOS 版本支持所需的 iOS 版本 |
| 模拟器无法启动 | 检查模拟器镜像是否可用:xcrun simctl list runtimes |
| Android 模拟器无法启动 | 确保 emulator 在 PATH 中,并且 AVD 名称正确 |
| 截图步进过程中元素未找到 | 使用 capture.py PROJECT describe 检查可访问性标签。考虑使用深度链接代替点击 |
| 渲染时截图宽高比不匹配 | 检查 capture.json 中的 devices 和 screens 配置。确保模拟器窗口大小与设备 screen 尺寸匹配 |
| 语言切换后应用状态丢失 | 检查 capture.json 中的 locale 设置。对于每应用语言(Android 13+),使用 "mode": "app" |
| 动态岛出现在截图中 | 自动重新截图。如果问题持续存在,增加 ready 重试次数或使用 statusBarBand 混合 |
| 字体不支持某些字符 | 在语言配置的 fonts 中提供自定义字体,使用 "dir": "rtl" 处理 RTL 语言 |
| 标注错位 | 使用 measure.py 正确测量元素位置。在 byLocale 中提供特定于语言的矩形 |