Vous construisez des ensembles de captures d'écran pour le store sous forme de données, et non d'images générées. Chaque vignette est un canevas HTML avec des couches : arrière-plan, titre, un cadre d'appareil contenant une capture réelle, et des éléments détachés (recadrages de la capture ou légendes vectorielles) en 3D. Chaque chaîne de caractères se trouve dans locales/<locale>.json, donc une nouvelle langue se résume à un fichier JSON plus les captures, et le rendu est déterministe : les mêmes entrées donnent les mêmes pixels.
$S ci-dessous désigne le dossier où se trouve ce fichier SKILL.md, $PY est $S/.venv/bin/python. Si l'environnement virtuel est absent, créez-le une fois :
python3 -m venv $S/.venv && $S/.venv/bin/pip install -r $S/requirements.txt && $S/.venv/bin/playwright install chromium
Pipeline :
1 avantages -> 2 slides -> 3 capture.json -> 4 capture (simulateurs) -> 5 composition
-> 6 arrière-plan -> 7 set.json + locales -> 8 rendu -> 9 révision -> (autres langues, appareils)
$S/../../examples/demo/ dans le dépôt est un projet complet (application fictive, deux langues, cinq appareils). Lisez son set.json avant d'en écrire un.
Structure du projet
<projet>/
capture.json app, appareils de capture, écrans à capturer
set.json composition partagée par toutes les locales
locales/<locale>.json toutes les chaînes : verbe, sous-titre, textes de légende
sim/<locale>/<slide>.png captures téléphone (un dossier par ensemble de captures)
sim-tablet/ sim-duo/ sim-android/
fonts/ assets/ polices optionnelles, image d'arrière-plan, accessoires
out/<locale>/<appareil>/ fichiers store uniquement
out/_sheets/ feuille de contact par locale et appareil
out/_captures/ feuille de contact par locale après capture
Suggérez store-screenshots/ à la racine du dépôt de l'application. Ajoutez out/ et .cache/ à son .gitignore.
1. Avantages
Lisez le code source : navigation (onglets, routes, écrans), texte d'onboarding, texte du paywall, README, fiche actuelle du store. Listez les 5 à 8 choses que l'utilisateur obtient, classées par leur impact sur l'installation. Un avantage par slide.
Chaque titre de slide est un verbe court en majuscules accompagné d'un sous-titre d'au plus deux lignes : PLANIFIEZ / VOTRE SEMAINE EN MINUTES, ÉCONOMISEZ / SUR CHAQUE COURSE. Le verbe nomme l'avantage, pas la fonctionnalité. Montrez la liste avec l'écran qui prouve chaque avantage et obtenez une validation.
2. Slides
Id NN-nom ; le numéro indique la position dans le store. Choisissez l'écran par slide et notez l'état nécessaire (connecté, données présentes, feuille ouverte). Inclinez les écrans riches en images, gardez droits les écrans denses en données.
3. capture.json
Partez de $S/templates/capture.json.
| Clé | Signification |
|---|---|
app.ios | bundleId, et app (chemin vers un build .app du simulateur) ou copyFrom (UDID d'un simulateur qui l'a installée) |
app.android | package, activity optionnel, apk optionnel à installer |
devices.<clé> | platform ios ou android, dir (dossier de capture), iOS : name (simulateur dédié, créé par setup) et type (xcrun simctl list devicetypes), runtime optionnel ; Android : avd (emulator -list-avds) |
| extras appareil | appearance (light), locale (remplace le mode ci-dessous), displaySize (2853x2007 choisit l'écran intérieur de l'iPhone Duo), rotate (degrés, pour une capture mal orientée) |
locale | par plateforme. iOS reboot (par défaut, le plus fiable), relaunch (pas de redémarrage, conserve la position d'un pliable) ou launchArgs. Android app (par défaut, langue par application sur Android 13+, nécessite le localeConfig de l'application) ou system (nécessite une image émulateur rootable) |
launch | wait secondes après lancement, url optionnel à ouvrir au lieu de lancer (URL client de développement Expo), steps optionnels |
screens | liste de {id, steps, ready, after, devices, copyFrom}, plus blocs byDevice / byLocale fusionnés sur l'écran |
Les étapes s'exécutent dans l'ordre et chacune attend (wait, par défaut 1 à 2 s) :
| Étape | Effet |
|---|---|
{"open": "myapp://route"} | lien profond. Préférez-le aux tap : il fonctionne sur tous les appareils et plateformes |
{"tap": {"label": "Enregistrer"}} | tape sur l'élément avec ce label d'accessibilité (ou {"id": ...}), correspondance exacte puis par sous-chaîne |
{"tap": {"at": [0.5, 0.9]}} | tape à une fraction de l'écran |
{"tap": {...}, "until": {"label": "Détails"}} | tape à nouveau (jusqu'à 3 fois) jusqu'à ce que cet élément apparaisse ; utilisez-le pour des tap sur des écrans qui se stabilisent |
{"waitFor": {"label": "Terminé", "timeout": 20}} | attend qu'un élément soit à l'écran, avant les tap sur des écrans qui chargent ou animent |
{"type": "pâtes"}, {"key": "enter"}, {"key": "back"} | texte et touches |
{"swipe": [0.5, 0.8, 0.5, 0.3]} | balayage, fractions |
{"launch": true} | redémarre l'application |
ready: {"region": [x, y, w, h], "minStd": 20, "tries": 5, "delay": 3} reprend la capture tant que cette région est d'une couleur unie (lignes de squelette, indicateur de chargement, image en cours de chargement). Les captures iPhone sont reprises automatiquement lorsque le simulateur intègre le Dynamic Island.
copyFrom: "01-home" réutilise une autre capture sur cet appareil (une tablette affichant deux écrans dans une vue fractionnée). devices: ["phone"] limite un écran à certains appareils.
capture.py PROJET describe --device phone affiche les éléments cliquables avec leurs labels et cadres.
4. Capture
$PY $S/capture.py <projet> doctor # outils, appareils, ids d'app
$PY $S/capture.py <projet> setup # créer/démarrer les sims dédiés, démarrer les AVD, installer l'app
$PY $S/capture.py <projet> run --locale fr-FR # tous les appareils et écrans
$PY $S/capture.py <projet> run --locale fr-FR --device phone --screen 03-recherche
- Les appareils de capture sont dédiés : les simulateurs iOS sont créés par nom (
Capture iPhone...), Android utilise les AVD nommés dans capture.json. Ne pointez jamais capture.json vers le simulateur sur lequel l'utilisateur travaille, et demandez avant de démarrer les appareils ou de changer leur langue. - Après
setup, l'application nécessite généralement un état de démonstration : connexion à un compte de démonstration, données de test, fermeture des dialogues de premier lancement (enregistrer le mot de passe, notifications, demandes d'avis). Faites-le une fois par appareil ; l'état persiste entre les changements de langue. Demandez les identifiants de démonstration à l'utilisateur, ne les écrivez jamais dans capture.json. - Un build de développement qui affiche des superpositions de débogage (boutons de menu de développement, toasts) doit les masquer pour la session de capture. Corrigez-les localement sans les valider.
- Révisez
out/_captures/<locale>.jpgvignette par vignette : aucune chaîne dans une autre langue, aucun dialogue, toast, indicateur ou état vide. Corrigez les données ou les étapes et relancez cet écran. Le moteur de rendu affiche la capture telle quelle ; rien ne masque une erreur. - iPhone Duo : dépliez le simulateur et mettez-le en paysage dans Simulator ou DeviceHub d'abord ; simctl ne peut pas plier ni pivoter. Utilisez des liens profonds (les tap HID peuvent ne pas atteindre l'écran intérieur),
"locale": "relaunch"et"displaySize": "2853x2007".
Les applications multiplateformes (React Native, Flutter) peuvent sauter la capture Android : les vignettes Android réutilisent les captures iPhone et iPad, ce que font les appareils par défaut. Les applications Android natives capturent leurs propres écrans dans sim-android/, voir section 10.
5. Composition (approbation de l'utilisateur avant tout rendu)
Esquissez chaque slide en ASCII : titre, position et inclinaison de l'appareil, quel élément se détache et où il atterrit. Obtenez une validation, puis écrivez set.json.
+------------------------------+
| EAU | verbe, une ligne
| CHAQUE PLANTE À | sous-titre, max 2 lignes
| L'HEURE |
| .--------------. |
| [* 3 plantes aujourd'hui] | légende, détachée, dépasse à gauche
| | Aujourd'hui ...| | appareil incliné y 15, x 4
+------------------------------+
6. Arrière-plan
Un arrière-plan par ensemble, partagé par toutes les locales et tous les appareils. Soit CSS ("background": {"css": "radial-gradient(...), #1f4a37"}, l'exemple utilise ceci) soit une image ({"image": "assets/fond.png"}, background-size cover). Un générateur d'images fonctionne bien pour un fond doux ; demandez un fond de studio 9:16 sans couture dans la couleur de la marque avec une lumière douce, et spécifiez explicitement : aucun objet, aucun texte, aucun appareil, aucune forme. Générez deux propositions, l'utilisateur choisit. Les accessoires (autocollants, photos de produit) sont des PNG avec transparence placés par slide.
7. set.json et locales
Mesurez chaque rectangle en pixels de capture avec measure.py, jamais à l'œil :
$PY $S/measure.py sim/fr-FR/05-remplir.png --at 700,790 # remplit un bouton, une puce, une pilule
$PY $S/measure.py sim/fr-FR/01-planifier.png --ink 40,415,700,75 # limites d'un texte sur une zone simple
$PY $S/measure.py sim/fr-FR/02-cuisiner.png --grid 440,1510,340,110 # image de grille zoomée dans le dossier temporaire
| Clé | Signification |
|---|---|
locales, devices | ce que couvre un rendu complet ; ids d'appareil depuis $S/devices/devices.json |
background | {"image": ...} ou {"css": ...}, position optionnel |
fonts | {"family": "fonts/Fichier.otf"}, chemins relatifs au projet ; par défaut display est Archivo Black fourni |
headline | font, descFont, verbColor, descColor, shadow, verbTracking, descTracking, descWordSpacing |
capture | platform, statusBarBand (lignes de capture mélangées, iOS par défaut [66, 128]), sets ({"tablet": {"dir": "sim-tablet", "statusBarBand": [12, 56]}}), devices ({"android-phone": "android"} pour pointer un appareil vers un autre ensemble de capture) |
style | gloss, deviceShadow, thicknessRatio (profondeur de la paroi latérale), sideLayers |
statusBar | time, color de la barre d'état dessinée par le moteur de rendu |
layout | par id d'appareil, remplace le layout de devices.json (deviceTop, deviceWidth, verbMax ...) |
baseline | {appareil: {locale: dossier}} d'un ensemble existant, pour --compare |
slides | liste, ci-dessous |
Slide : id, sim (fichier de capture si différent de <id>.png), tilt {x, y, z} degrés, offset {x, y} fractions du canevas, layout, breakouts, props (src, x, y, w fractions du canevas, rotate, shadow, z : front ou back).
Élément détaché :
| Clé | Signification |
|---|---|
type | crop : les pixels de la capture eux-mêmes ; callout : pilule vectorielle avec texte de locale |
rect ou anchor | [x, y, w, h] en px de capture de l'élément dont il provient |
at ou dx/dy | position d'atterrissage (at = nouveau coin supérieur gauche en px de capture) ou décalage |
scale, lift | taille et élévation en px (par défaut 80) |
vacate, vacatePad | remplir l'emplacement d'origine avec la couleur environnante (déplacé, pas copié) |
radius, shadow, origin | rayon de coin en px de capture, force de l'ombre, origine de transformation |
text, icon, style | callout : clé vers callouts de la locale, tag ou sparkles, et font, size, bg, fg, padX, padY, gap, radius, weight, tracking, iconScale, iconColor |
Toute valeur à l'intérieur de slides peut varier par appareil et par locale :
{"byDevice": {"phone": ..., "tablet": ..., "default": ...}} (clés : id d'appareil, ensemble de capture, default) et {"byLocale": {"de-DE": ..., "default": ...}}, imbriqués dans les deux sens. Les rectangles varient presque toujours par locale, car la même puce est plus large en allemand.
Les recadrages restent nets jusqu'à environ 1,7x pour les grands éléments. Les petites puces et textes deviennent des callouts, qui sont vectoriels et restent nets à n'importe quelle échelle.
Fichier de locale :
{
"captures": "fr-FR",
"dir": "ltr",
"fonts": {"display": "fonts/NotoSansJP-Black.otf"},
"slides": {
"01-plan": {"verb": "Planifier", "desc": "Votre semaine en minutes", "callouts": {"economies": "env. 7,81 € économisés"}}
}
}
Seul slides est obligatoire. captures permet à une locale de réutiliser les captures d'une autre locale (en-GB lisant en-US). headline et statusBar remplacent set.json. Archivo Black ne couvre que le latin ; le cyrillique, le grec, le CJK, l'arabe et l'hébreu nécessitent un remplacement de fonts, le RTL a besoin de "dir": "rtl".
8. Rendu
$PY $S/render.py <projet> # tout
$PY $S/render.py <projet> --locale de-DE --device iphone-67 --slide 01-plan
$PY $S/render.py <projet> --compare # baseline vs nouvelles feuilles
Les tailles de titre sont partagées entre tous les slides d'une locale et d'un appareil, afin qu'un ensemble se lise comme un tout. Le moteur de rendu s'arrête lorsque le rapport hauteur/largeur d'une capture ne correspond pas à l'ouverture d'écran de l'appareil.
9. Révision
Ouvrez out/_sheets/<locale>-<appareil>.jpg et zoomez sur chaque élément détaché. Les lignes imprimées sous forme de CHECK nomment le problème (titre à taille minimale, chevauchement). Un titre à taille minimale signifie que le texte est trop long : raccourcissez-le, n'abaissez pas les limites. Montrez les feuilles à l'utilisateur.
10. Autres langues et appareils
Ajouter une langue :
$PY $S/add_locale.py <projet> es-ES init --from fr-FR # fichier de locale, placeholders byLocale, dossiers
$PY $S/capture.py <projet> run --locale es-ES # captures
# traduisez locales/es-ES.json : texte du store, verbes courts, légendes correspondant à la capture
$PY $S/add_locale.py <projet> es-ES measure --from fr-FR # retrouve les rectangles, révisez l'image côte à côte
$PY $S/add_locale.py <projet> es-ES measure --from fr-FR --write
$PY $S/add_locale.py <projet> es-ES check # 0 erreurs avant rendu
$PY $S/render.py <projet> --locale es-ES
Une variante régionale (en-GB, de-AT, fr-CA) : init --share-captures, puis seuls les textes changent.
Appareils dans $S/devices/devices.json :
| id | canevas | lit | emplacement store |
|---|---|---|---|
iphone-67 | 1290x2796 | téléphone (1206x2622) | ASC APP_IPHONE_67 |
android-phone | 1080x1920 | téléphone | Play captures téléphone |
ipad-13 | 2064x2752 | tablette (2064x2752) | ASC APP_IPAD_PRO_3GEN_129 |
android-tablet | 1792x2400 | tablette | Play tablette 7" et 10" |
iphone-duo | 2853x2007 | duo (2853x2007) | ASC iPhone Duo |
Les cadres sont des dessins génériques créés avec make_frame.py, librement redistribuables. Une taille de capture sans cadre correspondant en reçoit un :
# captures Android natives, 1080x2400, dans sim-android/
$PY $S/make_frame.py --style android --screen 1080x2400 --device android-phone --captures android
# puis dans set.json : "capture": {"sets": {"android": {"dir": "sim-android", "statusBarBand": [30, 105]}}}
Styles : ios, android, tablet, landscape. --canvas LxH définit la taille du store pour un nouvel id d'appareil. Mesurez statusBarBand sur une capture réelle : les lignes de la barre d'état plus quelques pixels de marge en haut.
11. Rebuild d'un ensemble existant
Pour reconstruire un ensemble entier (texte et UI doivent rester exacts) :
- Utilisez le même
capture.jsonetset.jsonque l'ensemble original. - Exécutez la capture et le rendu normalement.
- Comparez les sorties avec l'original à l'aide de
--compare. Le moteur de rendu produit une feuille de comparaison mettant en évidence les différences. - Si des différences apparaissent (par exemple en raison de modifications de l'UI), ajustez les étapes de capture ou les layouts du set.json, puis refaites le rendu.
- Utilisez
add_locale.pypour ajouter de nouvelles langues basées sur l'ensemble existant.
12. Bonnes pratiques
- Lisez d'abord le code source : Comprenez la navigation, les écrans clés et les flux de l'application avant de planifier les avantages et les slides.
- État de démonstration : Assurez-vous que l'application dispose de données réalistes (comptes, contenu, abonnements) pour que chaque capture soit convaincante.
- Textes localisés : Traduisez les verbes, sous-titres et légendes en veillant à ce qu'ils captent l'avantage et s'intègrent dans les limites de l'UI.
- Évitez les superpositions : Aucun dialogue, toast, indicateur de chargement ou barre de débogage ne doit apparaître dans les captures. Désactivez-les dans les options de développement ou créez un build de release.
- Mesurez précisément : Utilisez
measure.pypour toutes les dimensions. Ne devinez ni ne touchez. Les rects dépendent de la locale : les textes longs changent les largeurs. - Examen humain : Révisez toujours les feuilles de contact avant de publier. Zoomez sur chaque élément détaché et vérifiez les superpositions.
- Contrôle de version : Validez les dossiers
capture.json,set.json,locales/,fonts/,assets/etsim/. Ignorezout/et.cache/.
13. Dépannage
| Problème | Solution |
|---|---|
| Les captures iPhone montrent un Dynamic Island | Redémarrez le simulateur et relancez la capture. Le script réessaie automatiquement. |
| Android ne trouve pas le package | Vérifiez que app.android.package est correct et que l'APK est installé. |
| Le rendu échoue : rapport hauteur/largeur ne correspond pas | Vérifiez que les dimensions de capture correspondent à l'appareil dans devices.json. |
| Les polices ne s'affichent pas | Assurez-vous que le chemin est relatif au projet et que la police est installée. |
| Les callouts sont flous | Convertissez les recadrages en callouts vectoriels (type: "callout"). |