Você constrói conjuntos de capturas de tela como dados, não como imagens geradas. Cada slide é um canvas HTML com camadas: fundo, manchete, uma moldura de dispositivo segurando uma captura real e destaques (recortes da captura ou callouts vetoriais) que se destacam da tela em 3D. Cada texto está em locales/<locale>.json, então um novo idioma é um arquivo JSON mais as capturas, e a renderização é determinística: a mesma entrada gera os mesmos pixels.
$S abaixo é a pasta onde este SKILL.md reside, $PY é $S/.venv/bin/python. Se o venv não existir, crie-o uma vez:
python3 -m venv $S/.venv && $S/.venv/bin/pip install -r $S/requirements.txt && $S/.venv/bin/playwright install chromium
Pipeline:
1 benefícios -> 2 slides -> 3 capture.json -> 4 captura (simuladores) -> 5 composição
-> 6 fundo -> 7 set.json + locales -> 8 renderizar -> 9 revisão -> (mais idiomas, dispositivos)
$S/../../examples/demo/ no repositório é um projeto completo (app fictício, dois idiomas, cinco dispositivos). Leia o set.json dele antes de escrever um.
Layout do projeto
<project>/
capture.json app, dispositivos de captura, telas a capturar
set.json composição compartilhada por cada localidade
locales/<locale>.json cada texto: verbo, subtítulo, textos de callout
sim/<locale>/<slide>.png capturas do celular (uma pasta por conjunto de captura)
sim-tablet/ sim-duo/ sim-android/
fonts/ assets/ fontes opcionais, imagem de fundo, adereços
out/<locale>/<device>/ apenas arquivos da loja
out/_sheets/ folha de contato por localidade e dispositivo
out/_captures/ folha de contato por localidade após capturar
Sugira store-screenshots/ na raiz do repositório do aplicativo. Adicione out/ e .cache/ ao .gitignore dele.
1. Benefícios
Leia o código-fonte: navegação (abas, rotas, telas), texto de integração, texto do paywall, README, listagem existente da loja. Liste as 5 a 8 coisas que um usuário obtém, classificadas por quanto elas influenciariam uma instalação. Um benefício por slide.
Cada manchete do slide é um verbo curto em maiúsculas mais um subtítulo de no máximo duas linhas:
PLANEJE / SUA SEMANA EM MINUTOS, ECONOMIZE / EM CADA COMPRA. O verbo nomeia o benefício, não o recurso. Mostre a lista com a tela que comprova cada um e obtenha aprovação.
2. Slides
Id NN-nome; o número é a posição na loja. Escolha a tela por slide e observe o estado que ela precisa (logado, dados presentes, uma folha aberta). Incline telas com muitas imagens, mantenha telas densas de dados retas.
3. capture.json
Comece a partir de $S/templates/capture.json.
| Chave | Significado |
|---|---|
app.ios | bundleId, e app (caminho para um build .app do simulador) ou copyFrom (UDID de um simulador que o tenha instalado) |
app.android | package, activity opcional, apk opcional para instalador |
devices.<chave> | platform ios ou android, dir (pasta de captura), iOS: name (simulador dedicado, criado pelo setup) e type (xcrun simctl list devicetypes), runtime opcional; Android: avd (emulator -list-avds) |
| extras do dispositivo | appearance (light), locale (substitui o modo abaixo), displaySize (2853x2007 escolhe a tela interna do iPhone Duo), rotate (graus, para uma captura que sai de lado) |
locale | por plataforma. iOS reboot (padrão, mais confiável), relaunch (sem reinicializar, mantém a postura de um dobrável) ou launchArgs. Android app (padrão, idioma por app no Android 13+, precisa do localeConfig do app) ou system (precisa de uma imagem de emulador com root) |
launch | wait segundos após iniciar, url opcional para abrir em vez de iniciar (URL do cliente de desenvolvimento Expo), steps opcionais |
screens | lista de {id, steps, ready, after, devices, copyFrom}, além de blocos byDevice / byLocale mesclados sobre a tela |
Os passos são executados em ordem e cada um espera (wait, padrão 1 a 2 s):
| Passo | Efeito |
|---|---|
{"open": "myapp://route"} | link profundo. Prefira isso a toques: funciona em todos os dispositivos e plataformas |
{"tap": {"label": "Salvar"}} | toca no elemento com esse rótulo de acessibilidade (ou {"id": ...}), correspondência exata depois substring |
{"tap": {"at": [0.5, 0.9]}} | toca em uma fração da tela |
{"tap": {...}, "until": {"label": "Detalhes"}} | toca novamente (até 3 vezes) até que esse elemento apareça; use para toques em telas que ainda estão estabilizando |
{"waitFor": {"label": "Concluído", "timeout": 20}} | espera até que um elemento esteja na tela, antes de toques em telas que carregam ou animam |
{"type": "pasta"}, {"key": "enter"}, {"key": "back"} | texto e teclas |
{"swipe": [0.5, 0.8, 0.5, 0.3]} | deslizar, frações |
{"launch": true} | reiniciar o aplicativo |
ready: {"region": [x, y, w, h], "minStd": 20, "tries": 5, "delay": 3} refaz a captura enquanto essa região for uma cor sólida (linhas de esqueleto, um spinner, uma imagem ainda carregando). As capturas do iPhone são refeitas automaticamente quando o simulador incorpora a Dynamic Island nelas.
copyFrom: "01-home" reutiliza outra captura naquele dispositivo (um tablet mostrando duas telas em uma visualização dividida). devices: ["phone"] limita uma tela a alguns dispositivos.
capture.py PROJECT describe --device phone imprime elementos tocáveis com rótulos e molduras.
4. Captura
$PY $S/capture.py <project> doctor # ferramentas, dispositivos, IDs de aplicativos
$PY $S/capture.py <project> setup # criar/iniciar simuladores dedicados, iniciar AVDs, instalar o app
$PY $S/capture.py <project> run --locale pt-BR # todos os dispositivos e telas
$PY $S/capture.py <project> run --locale pt-BR --device phone --screen 03-search
- Os dispositivos de captura são dedicados: os simuladores iOS são criados por nome (
Capture iPhone...), o Android usa os AVDs nomeados em capture.json. Nunca aponte capture.json para o simulador em que o usuário trabalha e peça permissão antes de iniciar dispositivos ou alterar o idioma deles. - Após
setup, o aplicativo geralmente precisa de um estado de demonstração: faça login em uma conta de demonstração, preencha dados, dispense diálogos da primeira execução (salvar senha, notificações, solicitações de avaliação). Faça isso uma vez por dispositivo; o estado sobrevive a mudanças de idioma. Peça ao usuário as credenciais de demonstração, nunca as escreva em capture.json. - Um build de desenvolvimento que mostra sobreposições de depuração (botões de menu de desenvolvimento, toasts) precisa ocultá-los para a sessão de captura. Remova-os localmente e não confirme a remoção.
- Revise
out/_captures/<locale>.jpgtile por tile: nenhum texto em outro idioma, nenhum diálogo, toast, spinner ou estado vazio. Corrija dados ou passos e execute novamente essa tela. O renderizador mostra a captura como está; nada pinta sobre um erro. - iPhone Duo: desdobre o simulador e coloque-o em paisagem no Simulador ou DeviceHub primeiro; o simctl não pode dobrar ou girar. Use links profundos (toques HID podem não alcançar a tela interna),
"locale": "relaunch"e"displaySize": "2853x2007".
Aplicativos multiplataforma (React Native, Flutter) podem pular a captura Android: os tiles Android reutilizam as capturas do iPhone e iPad, que é o que os dispositivos padrão fazem. Aplicativos Android nativos capturam suas próprias telas em sim-android/, veja a seção 10.
5. Composição (usuário aprova antes de qualquer renderização)
Esboce cada slide em ASCII: manchete, posição e inclinação do dispositivo, qual elemento se destaca e onde ele pousa. Obtenha aprovação e então escreva set.json.
+------------------------------+
| ÁGUA | verbo, uma linha
| CADA PLANTA NO | subtítulo, máximo 2 linhas
| TEMPO CERTO |
| .--------------. |
| [* 3 plantas hoje] | callout, elevado, saindo pela esquerda
| | Hoje ... | | dispositivo inclinado y 15, x 4
+------------------------------+
6. Fundo
Um fundo por conjunto, compartilhado por todas as localidades e dispositivos. Ou CSS
("background": {"css": "radial-gradient(...), #1f4a37"}, a demonstração usa isso) ou uma imagem
({"image": "assets/bg.png"}, background-size cover). Um gerador de imagem funciona bem para um fundo suave;
solicite um fundo de estúdio 9:16 sem costura na cor da marca com luz suave,
e afirme explicitamente: sem objetos, sem texto, sem dispositivos, sem formas. Gere dois, o usuário escolhe. Adereços (adesivos, fotos de produtos) são PNGs com alfa colocados por slide.
7. set.json e locales
Meça cada retângulo em pixels de captura com measure.py, nunca a olho:
$PY $S/measure.py sim/pt-BR/05-fill.png --at 700,790 # preenche um botão, chip, pílula
$PY $S/measure.py sim/pt-BR/01-plan.png --ink 40,415,700,75 # limites de texto em uma área simples
$PY $S/measure.py sim/pt-BR/02-cook.png --grid 440,1510,340,110 # imagem de grade ampliada no diretório temporário
| Chave | Significado |
|---|---|
locales, devices | o que uma renderização completa cobre; IDs de dispositivo de $S/devices/devices.json |
background | {"image": ...} ou {"css": ...}, position opcional |
fonts | {"family": "fonts/File.otf"}, caminhos relativos ao projeto; padrão display é o Archivo Black incluído |
headline | font, descFont, verbColor, descColor, shadow, verbTracking, descTracking, descWordSpacing |
capture | platform, statusBarBand (linhas de captura mescladas, iOS padrão [66, 128]), sets ({"tablet": {"dir": "sim-tablet", "statusBarBand": [12, 56]}}), devices ({"android-phone": "android"} para apontar um dispositivo para outro conjunto de captura) |
style | gloss, deviceShadow, thicknessRatio (profundidade da parede lateral), sideLayers |
statusBar | time, color da barra de status que o renderizador desenha |
layout | por ID de dispositivo, substitui o layout de devices.json (deviceTop, deviceWidth, verbMax ...) |
baseline | {device: {locale: folder}} de um conjunto ativo, para --compare |
slides | lista, abaixo |
Slide: id, sim (arquivo de captura se não for <id>.png), tilt {x, y, z} graus, offset
{x, y} frações do canvas, layout, breakouts, props (src, x, y, w frações do canvas, rotate, shadow, z: front ou back).
Breakout:
| Chave | Significado |
|---|---|
type | crop: os próprios pixels da captura; callout: pílula vetorial com texto da localidade |
rect ou anchor | [x, y, w, h] em pixels da captura do elemento de onde vem |
at ou dx/dy | posição de pouso (at = novo canto superior esquerdo em pixels da captura) ou deslocamento |
scale, lift | tamanho e elevação z em pixels (padrão 80) |
vacate, vacatePad | preencher o local original com a cor ao redor (movido, não copiado) |
radius, shadow, origin | raio do canto em pixels da captura, intensidade da sombra, origem da transformação |
text, icon, style | callout: chave nos callouts da localidade, tag ou sparkles, e font, size, bg, fg, padX, padY, gap, radius, weight, tracking, iconScale, iconColor |
Qualquer valor dentro de slides pode variar por dispositivo e por localidade:
{"byDevice": {"phone": ..., "tablet": ..., "default": ...}} (chaves: ID do dispositivo, conjunto de captura,
default) e {"byLocale": {"de-DE": ..., "default": ...}}, aninhados de qualquer forma. Os retângulos
quase sempre variam por localidade, porque o mesmo chip é mais largo em alemão.
Os recortes permanecem nítidos até cerca de 1.7x para elementos grandes. Chips pequenos e texto tornam-se callouts, que são vetoriais e permanecem nítidos em qualquer escala.
Arquivo de localidade:
{
"captures": "pt-BR",
"dir": "ltr",
"fonts": {"display": "fonts/NotoSansJP-Black.otf"},
"slides": {
"01-plan": {"verb": "Planeje", "desc": "Sua semana em minutos", "callouts": {"savings": "aprox. €7,81 economizados"}}
}
}
Apenas slides é obrigatório. captures faz uma localidade reutilizar as capturas de outra localidade (en-GB lendo en-US). headline e statusBar substituem set.json. Archivo Black cobre apenas latim;
Cirílico, Grego, CJK, Árabe e Hebraico precisam de uma substituição fonts, RTL precisa de "dir": "rtl".
8. Renderizar
$PY $S/render.py <project> # tudo
$PY $S/render.py <project> --locale de-DE --device iphone-67 --slide 01-plan
$PY $S/render.py <project> --compare # folhas de linha de base vs novas
Os tamanhos das manchetes são compartilhados em todos os slides de uma localidade e dispositivo, para que um conjunto seja lido como um. O renderizador para quando a proporção de uma captura não corresponde à abertura da tela do dispositivo.
9. Revisão
Abra out/_sheets/<locale>-<device>.jpg e amplie em cada breakout. Linhas impressas como
CHECK nomeiam o problema (manchete no tamanho mínimo, sobreposição). Uma manchete no tamanho mínimo significa
que o texto é muito longo: encurte-o, não abaixe os limites. Mostre as folhas ao usuário.
10. Mais idiomas e dispositivos
Adicionar um idioma:
$PY $S/add_locale.py <project> es-ES init --from en-US # arquivo de localidade, placeholders byLocale, pastas
$PY $S/capture.py <project> run --locale es-ES # capturas
# traduza locales/es-ES.json: texto da loja, verbos curtos, callouts correspondentes à captura
$PY $S/add_locale.py <project> es-ES measure --from en-US # reencontra retângulos, revise a imagem lado a lado
$PY $S/add_locale.py <project> es-ES measure --from en-US --write
$PY $S/add_locale.py <project> es-ES check # 0 erros antes de renderizar
$PY $S/render.py <project> --locale es-ES
Uma variante regional (en-GB, de-AT, fr-CA): init --share-captures, então apenas os textos mudam.
Dispositivos em $S/devices/devices.json:
| id | canvas | lê | slot da loja |
|---|---|---|---|
iphone-67 | 1290x2796 | phone (1206x2622) | ASC APP_IPHONE_67 |
android-phone | 1080x1920 | phone | Capturas de tela do Play |
ipad-13 | 2064x2752 | tablet (2064x2752) | ASC APP_IPAD_PRO_3GEN_129 |
android-tablet | 1792x2400 | tablet | Tablet de 7" e 10" do Play |
iphone-duo | 2853x2007 | duo (2853x2007) | ASC iPhone Duo |
As molduras são desenhos genéricos feitos com make_frame.py, livres para redistribuir. Um tamanho de captura
sem moldura correspondente ganha uma:
# capturas Android nativas, 1080x2400, em sim-android/
$PY $S/make_frame.py --style android --screen 1080x2400 --device android-phone --captures android
# então em set.json: "capture": {"sets": {"android": {"dir": "sim-android", "statusBarBand": [30, 105]}}}
Estilos: ios, android, tablet, landscape. --canvas WxH define o tamanho da loja para um novo
ID de dispositivo. Meça statusBarBand em uma captura real: as linhas da barra de status mais algumas
... [truncado para tradução]