Mono Editorial Landing
Une page d'atterrissage conçue comme un magazine : encre sur papier, un bandeau avec une fine ligne "numéro", un titre en Instrument Serif assez grand pour être lu à travers une pièce, une introduction avec une lettrine, puis des colonnes numérotées séparées par des filets, des doubles pages de fonctionnalités qui alternent les côtés avec des figures encadrées et des légendes ("Fig. 1"), une citation en italique centrée, une liste de questions, une ligne de clôture et un colophon en style machine à écrire. Noir sur blanc chaud par défaut ; clair sur presque noir, ou avec une couleur d'accent pour les mots en italique, en modifiant trois couleurs.
Vous décrivez la page dans page.json, ou laissez un script la rédiger à partir du README du projet. La construction la vérifie et écrit out/index.html, un fichier avec son CSS (et out/assets/ pour les images). Pour un produit qui n'est pas encore sorti, le héros contient un vrai formulaire d'attente qui envoie vers Formspree, Netlify Forms, Buttondown ou n'importe quel endpoint ; pour un produit déjà sorti, deux boutons.
Ce dont vous avez besoin
- Node.js 18 ou plus récent. Rien à installer.
- Chrome, Chromium ou Edge pour les vérifications (les scripts le trouvent ; définissez
CHROME_PATHs'ils ne le trouvent pas). - Pour une liste d'attente : un compte avec un service de formulaire, ou un endpoint qui accepte un envoi de formulaire.
- Accès à Internet lorsque la page est ouverte, pour ses Google Fonts (Instrument Serif, Newsreader, Inter et IBM Plex Mono). Sans cela, la page utilise les polices système.
Le dossier de cette compétence est <skill> ci-dessous : le dossier dans lequel se trouve ce SKILL.md.
Étapes
1. Rédiger à partir du README
node <skill>/scripts/from-readme.mjs README.md page.json
Il lit le README (et package.json à côté, s'il existe) et écrit un page.json qui se construit déjà : le nom, le slogan comme premier titre, l'introduction comme accroche, une liste "Fonctionnalités" dont les éléments commencent par un nom en gras comme colonnes, les commandes d'installation et de démarrage rapide comme une figure de terminal, les autres sections comme des doubles pages (avec leur capture d'écran ou leur code), une section FAQ comme questions, et le dépôt comme bouton. Il ignore les badges, logos, lignes de liens, tables des matières, sections de licence et de contribution.
Il n'écrit jamais de contenu original au-delà d'étiquettes comme "Commencez", et se termine par trois listes. Travaillez-les avec la personne :
- Écrire avec la personne : toujours le titre, et chaque titre de fonctionnalité qui est encore un titre du README ("Installation", "Comment ça marche").
- Couper pour adapter : texte qu'il a raccourci à une limite de phrase ou de mot. Relisez chacun.
- Dans le README mais pas sur la page : sections et fonctionnalités pour lesquelles il n'y avait pas de place. Échangez-les si elles sont plus importantes.
Sans README, ou avec un README léger, écrivez page.json à partir des notes de la personne, du site actuel ou de la documentation, en commençant par l'un des exemples.
2. Décider ce que le héros demande
-
Pas encore lancé : une liste d'attente. Demandez quel service collecte les e-mails et définissez
hero.form:providerConfiguration Ce qu'il faut mettre dans formformspreeCréez un formulaire sur formspree.io ; son URL se termine par l'ID du formulaire. Le plus simple sur n'importe quel hébergeur. "id": "xayzbqwe"netlifyUniquement lorsque la page est déployée sur Netlify ; les inscriptions apparaissent sous l'onglet Forms du site. "name": "waitlist"buttondownUne newsletter Buttondown. Les personnes reçoivent leur propre page de confirmation Buttondown. "username": "votrenom"endpointTout autre chose qui accepte une soumission de formulaire : votre API, ou l'URL actiond'un formulaire de liste de diffusion (Mailchimp, Kit, Loops et similaires)."url": "https://…","field"si ce n'est pasemail(Mailchimp utiliseEMAIL),"ajax": falsepour les services de listeAvec
ajax(par défaut sauf pour Buttondown), la page envoie le formulaire avec JavaScript et affichesuccessouerrorsur place ; sans JavaScript, ou avec"ajax": false, il s'agit d'une soumission de formulaire normale. Un endpoint utilisé avecajaxdoit répondre avec un code 2xx et, sur un autre domaine, autoriser l'origine de la page (CORS). Chaque fournisseur sauf Buttondown reçoit un champ anti-spam caché ; ignorez les inscriptions où celui-ci est rempli. Chaque bouton "Rejoindre" sur la page (la barre, la clôture) fait défiler vers le formulaire et place le curseur dedans. -
Déjà sorti :
hero.actionsavec un boutonprimary(téléchargement, inscription, installation) et unsecondaryoptionnel (docs, source). Pointez vers de vraies URL ou vers#get-startedsur la page.
3. Écrire la page
Copiez l'exemple le plus proche et remplacez tout : examples/quietly/page.json (une liste d'attente, clair) ou examples/galley/page.json (un outil open-source, sombre, rédigé à partir de examples/galley/README.md).
| Champ | Ce que c'est | Limite |
|---|---|---|
brand.name, brand.href | Le nom en haut à gauche, défini dans la police d'affichage sérif | 24 |
nav[] | Jusqu'à 5 { "label", "href" }. Les liens #id doivent pointer vers quelque chose sur la page (ci-dessous) | 16 |
action | Le bouton en haut à droite ; par défaut : l'action principale du héros. false pour aucun | 22 |
issue[] | Jusqu'à 3 courtes lignes sous la barre, en petites capitales : un numéro, les parties du produit, une date de lancement ou une version. Sur téléphone, seuls le premier et le dernier s'affichent | 40 chacun |
hero.title | Le titre. *mot* le met en italique (dans theme.accent si défini), \n coupe la ligne (jusqu'à 3 lignes) | 64 |
hero.lede | Ce que c'est, pour qui, et pourquoi, en une ou deux phrases. La première lettre devient une lettrine ("dropcap": false pour la désactiver) | 300 |
hero.form ou hero.actions | Étape 2. Textes du formulaire : placeholder (32), button (22), success, error (120). Boutons : { "label", "href" } (26) | – |
hero.note | Une petite ligne sous le formulaire ou les boutons : quand les invitations sont envoyées, ce que vous enverrez, la licence | 100 |
columns[] | 2 à 4 { "kicker", "title", "text" }, numérotés 01, 02… Section id highlights | 24, 44, 220 |
features[] | Jusqu'à 4 doubles pages : kicker (28), title (72, *italique* fonctionne), text (420 ; une ligne vide commence un paragraphe), points (jusqu'à 4, 90 chacun), id (pour les liens nav), figure (ci-dessous) | – |
quote | { "text", "cite" }, une vraie citation d'une vraie personne qui a accepté. Sinon, laissez-le vide | 220, 60 |
faq | title (40, par défaut "Avant de demander") et 1 à 8 items de { "q", "a" }. Section id faq | 100, 500 |
closing | kicker (28), title (64), text (220), action (par défaut : l'action principale du héros). Section id closing | – |
footer.items[], links[] | Jusqu'à 3 lignes dans le colophon (par défaut "Nom © année") et jusqu'à 6 liens | 48, 20 |
theme | paper et ink (hex ; ink doit atteindre 7:1 sur paper), accent optionnel ; polices display, serif, sans, mono (familles Google Fonts) ; fonts ("" pour aucune, ou votre URL) | – |
meta | title (70) et description (170) pour les aperçus de recherche et de lien ; url, image (une image https pour l'aperçu de lien), icon, lang | – |
Les champs de texte acceptent le Markdown en ligne : code, liens, gras et italique.
Figures. Chaque fonctionnalité peut en avoir une, dessinée dans un cadre avec une ombre décalée et une caption optionnelle (90), numérotée "Fig. 1", "Fig. 2" le long de la page. Les doubles pages avec une figure alternent les côtés ; celles sans figure placent le titre à côté du texte.
type | Champs | Utilisez-le pour |
|---|---|---|
image | src (un fichier à côté de page.json : png, jpg, webp, avif, gif ou svg ; ou une URL https) et alt (160) : ce que l'image montre | Une vraie capture d'écran, une photo, une illustration |
window | title (40), subtitle (60), sidebar (jusqu'à 6 éléments, 20 chacun ; active sélectionne le point culminant, par défaut 0), rows (1 à 6 de meta (8), label (48), tag (14), fill true/false) | L'écran principal d'une application, dessiné dans le style de la page |
stats | items : 2 à 4 de value (10) et label (60) | Des chiffres vrais et sourcés |
code | code (jusqu'à 14 lignes de 64 caractères ; les lignes $ obtiennent un prompt, les lignes # et // sont atténuées) et title (24) pour la barre du cadre, par ex. "Terminal" | Commandes d'installation et de premier lancement, un fichier de configuration |
Conseils de rédaction : le titre dit ce que le lecteur obtient, dans ses mots, en aussi peu de mots que nécessaire ; laissez un mot porter l'italique. Les titres de colonnes sont de courtes déclarations ("Silence par défaut."), pas des noms de fonctionnalités. Chaque nombre, citation, client et affirmation doit venir de la personne, du README ou d'une source qu'elle indique. Une figure window doit montrer ce que le produit fait vraiment ; ses lignes peuvent être des exemples inventés, comme un écran d'exemple, alors dites-le dans la légende si des noms apparaissent.
4. Construire
node <skill>/scripts/build.mjs page.json out
Il liste chaque problème dans page.json en une fois (champs manquants, texte dépassant sa limite, un lien de navigation vers un id qui n'est pas sur la page, couleurs qui ne contrastent pas) et n'écrit rien tant qu'ils ne sont pas corrigés. Ensuite, il indique où le formulaire envoie, et liste ce que seule la personne peut confirmer : la citation, les chiffres, les figures de fenêtre.
5. Vérifier et regarder
node <skill>/scripts/check.mjs out/index.html
node <skill>/scripts/try-form.mjs out/index.html # avec un formulaire d'attente
node <skill>/scripts/screenshot.mjs out/index.html
check.mjscharge la page à 390, 768, 1024 et 1440 px et doit direokà chaque fois : rien de plus large que l'écran ou coupé, contraste lisible partout, texte alternatif, un champ de formulaire étiqueté, un h1, aucun niveau de titre sauté. Les lignes marquées·méritent un coup d'œil mais ne font pas échouer.try-form.mjss'inscrit avec[email protected]dans Chrome headless contre un substitut du service de formulaire sur cette machine (rien n'atteint le vrai), et montre ce qui a été envoyé, ce que le visateur voit après, et ce qu'il voit lorsque le service échoue. Il doit se terminer parThe form works.screenshot.mjsenregistreout/screens/desktop.pngetmobile.png. Regardez les deux : le premier écran dit-il ce que c'est, un titre se casse-t-il mal, une double page est-elle déséquilibrée (raccourcissez le texte ou la figure) ?
6. Mettre en ligne
Téléchargez out/index.html et out/assets/ vers n'importe quel hébergeur statique : Netlify, Vercel, Cloudflare Pages, GitHub Pages, S3. Pour conserver la page dans le dépôt du projet, ajoutez la construction à ses scripts, par ex. "landing": "node tools/landing/build.mjs landing/page.json public" après avoir copié build.mjs, inline.mjs, markup.mjs et color.mjs dans tools/landing/.
Avec une liste d'attente, inscrivez-vous une fois sur la page en ligne avec l'adresse de la personne elle-même et confirmez qu'elle atteint le service. Liez une politique de confidentialité depuis footer.links lorsque la page collecte des e-mails ; demandez à la personne son URL.
Dépannage
build: fix page.json first: chaque ligne nomme le champ et ce qu'il faut changer.points at nothing on the page: un lien de navigation ou de bouton vers#quelquechosequi n'est pas unidici. Les doubles pages de fonctionnalités en reçoivent un avec"id"; les propres identifiants de la page sont#join(le formulaire),#highlights,#faqet#closing.contrast: les couleurs ne respectent pas le rapport de contraste WCAG AA (7:1 en police normale).theme.inkest trop clair surtheme.paper, ou une couleurtheme.accentne contraste pas assez. Changez-les.text overflow: le texte dépasse sa limite. Coupez.- La page ne s'affiche pas correctement sur mobile : exécutez
screenshot.mjs, réglez le titre et la largeur des colonnes, ou vérifiez quehero.leden'est pas trop long pour la largeur de l'écran. - Le formulaire ne fonctionne pas : exécutez
try-form.mjs. Si le message de succès ne s'affiche pas, le endpoint (ou le proxy local) peut rejeter la requête. Vérifiez les en-têtes CORS si vous utilisezajaxavec un endpoint personnalisé.