Ce qui est nouveau
Le jeu est sur le web. Il est vide, c'est une scène avec un ciel, mais on peut l'ouvrir dans un navigateur. La version 0, la démo d'origine, tourne aussi dans le navigateur. Et ce site existe : un article par chapitre, avec sa démo jouable en haut.
Ce qui a été fait
- Une commande qui transforme le projet Unity en page web. On la tape à la fin de chaque chapitre.
- Un menu dans l'ancien projet, pour construire la version 0 pour le web, avec ses quatre pièges réglés d'avance.
- Chaque version arrive avec sa page, écrite avant le tournage. C'est celle que vous lisez.
- Le site : un petit serveur dans un conteneur Docker, qui lit les pages en Markdown et affiche les builds. Pas de base de données.
- Le site est fait de briques réutilisables, et une page catalogue les montre une par une.
- La démo jouable vit dans la page du chapitre, juste sous le titre : on joue, on passe en plein écran, et on lit ce qui a été fait sans changer de page.
Ce qui cloche encore
Le site tourne seulement sur mon ordinateur pour l'instant. Le nom de domaine et le serveur viendront plus tard ; la page ne changera pas. La version 0 a toujours ses bugs : c'est voulu, elle sert de point de comparaison.
Comment c'est fait, et pourquoi
Pourquoi publier un jeu vide
Un jeu qu'on ne peut pas essayer n'existe pas. Alors avant d'écrire la moindre ligne de code de jeu, on fabrique la commande qui met le jeu sur le web. Si elle casse un jour, on le saura le soir même, pas six mois plus tard. Le premier jet a mis un mois à sortir en version web, parce que rien n'avait été prévu pour.
La commande de build
Unity sait fabriquer une version web, mais il faut cliquer dans des menus. On veut une seule commande. Elle lance Unity sans interface, construit le jeu, et dépose le résultat dans un dossier au nom du chapitre. Elle vérifie aussi que la page du chapitre existe. Pas de page, pas de vedette sur le site.
Où on en est : C:\Atelier\Jeu est un projet Unity 6 avec URP, vide, sous git. Le module WebGL est installé. C'est un jeu vidéo en 3D construit chapitre par chapitre ; chaque chapitre a un identifiant comme A0-03 et un titre dans C:\Atelier\FeuilleDeRoute\feuille-de-route.json (actes[].chapitres[] avec id et titre). Chaque chapitre se termine par un build web déposé dans C:\Atelier\Versions\<identifiant>\, à côté des précédents, avec une lettre Versions\<identifiant>\lettre.md que le site public lit. La lettre est écrite avant le tournage dans C:\Atelier\FeuilleDeRoute\pages\<identifiant>.md. Ce que je veux : un script d'éditeur Unity, Assets/Jeu/Editeur/Construction.cs, avec une classe statique Construction et une méthode Web qui construit le projet pour WebGL dans le dossier passé par l'argument de ligne de commande -dossier, avec les scènes cochées dans les réglages de build, compression gzip et repli de décompression activé. Et un script PowerShell à la racine du projet, construire-web.ps1, qui prend un identifiant, trouve l'exécutable de Unity d'après ProjectSettings/ProjectVersion.txt, lance Unity en mode batch sans interface avec cette méthode, écrit le journal dans C:\Atelier\Versions\<identifiant>.log, affiche la durée et la taille à la fin, et s'arrête avec une erreur lisible si Unity échoue ou si le projet est ouvert. Avant le build, si Versions\<identifiant>\lettre.md n'existe pas, il la crée en copiant FeuilleDeRoute\pages\<identifiant>.md s'il existe, sinon un gabarit vide avec les sections « Ce qui est nouveau », « Ce qui a été fait », « Ce qui cloche encore », « Comment c'est fait, et pourquoi », et etat à a_completer. Il ne touche jamais à une lettre existante. Une option -Developpement fait un build de développement, où le pupitre des scénarios d'essai existe, dans le même dossier ; le script le dit à la fin et rappelle de relancer sans l'option avant de publier. Plus tard : le script sera appelé par un skill de clôture d'épisode ; il doit pouvoir être relancé sur le même identifiant sans rien demander. Ce que je ne veux pas : pas de build Windows, rien dans Versions à part le build, son journal et sa lettre. Quand c'est fait, lance .\construire-web.ps1 A0-03 et montre-moi la sortie, le dossier C:\Atelier\Versions\A0-03 et la lettre.
Le build prend deux ou trois minutes la première fois. Unity traduit tout le jeu en WebAssembly, un langage que le navigateur sait exécuter vite.
Le build de Solea
La version 0 n'avait jamais été prévue pour le web. La faire tourner dans un navigateur, c'est régler quatre pièges d'un coup : le son, qui n'est compilé que si on le demande ; les shaders, que le web ne trouve pas s'ils ne sont pas inclus ; un compilateur qui tombe en panne sous OneDrive ; et un cache qui garde parfois une vieille version. Le menu les règle tous, dans l'ordre, et écrit un petit compte rendu.
Où on en est : C:\Users\neobo\OneDrive - Studio\Unity\Orevia\Greybox est le projet Unity 6 (URP) de la version 0 d'Orévia, le premier jet : la scène Solea, FMOD pour le son, des contenus chargés par Addressables, Unity Localization pour les textes. Il est sous git dans le dossier Orevia au-dessus. Le module WebGL est installé, OneDrive est en pause, Unity est ouvert sur le projet et le MCP Unity est branché dessus. Les scripts d'éditeur vivent dans Assets/Orevia/Scripts/Editor, sous le menu « Orevia ». Il existe peut-être déjà un script de construction web dans ce dossier : si oui, relis-le, garde-le, et complète seulement ce qui manque dans la liste qui suit. Le build doit arriver dans C:\Atelier\Versions\version-0 ; sa lettre.md existe déjà, ne la touche pas. Ce que je veux : un menu « Orevia/Build/Construire la version 0 (WebGL) » dans Assets/Orevia/Scripts/Editor/ConstructionWeb.cs, espace de noms Orevia.Construction (pas Orevia.Editor : il entre en collision avec UnityEditor.Editor), qui fait tout dans l'ordre et sans question : - enregistrer les assets ; - définir le symbole de compilation OREVIA_FMOD pour la cible WebGL, avec NamedBuildTarget.WebGL : le pont FMOD n'est compilé que sous ce symbole, et sans lui le jeu sort muet ; - ajouter aux Always Included Shaders des réglages graphiques les shaders que le code cherche par nom à l'exécution, Orevia/UI/Carte et Universal Render Pipeline/Unlit : sur le web, Shader.Find rend null pour tout shader qui n'est pas inclus, et la carte du niveau laisse alors le terrain sur la mauvaise couche ; - désactiver la compilation Burst pour ce build : sous OneDrive, le compilateur Burst échoue avec le code 4551 ; - construire le contenu Addressables avant le joueur ; - construire le joueur WebGL avec les scènes cochées, compression gzip et repli de décompression, dans C:\Atelier\Versions\version-0 ; - écrire construction.txt à côté, avec la date, la durée et la taille. Un second menu « Orevia/Build/Construire la version 0 (WebGL, reconstruction complète) » fait la même chose avec BuildOptions.CleanBuildCache, pour le jour où le cache laisse un wasm périmé. Si le build échoue sur « scripts have compile errors » juste après une « Sharing violation » dans le journal de l'éditeur, c'est OneDrive qui a touché un fichier de Library : il suffit de recompiler les scripts et de relancer, dis-le dans le message d'erreur. Plus tard : ce menu ne sert qu'à la version 0 ; le nouveau projet a sa propre commande en ligne de commande. Ce que je ne veux pas : aucune modification de la scène ni du gameplay, pas de build Windows, pas de script PowerShell : c'est un menu d'éditeur, parce que Unity doit rester ouvert avec le MCP. Quand c'est fait, lance le menu par le MCP. Si l'appel expire, c'est normal : le build dure cinq à dix minutes ; attends la fin en relisant construction.txt. Puis montre-moi construction.txt, le contenu de C:\Atelier\Versions\version-0 et la taille du dossier Build.
Le build de Solea dure cinq à dix minutes. Quand l'IA dit que l'appel a expiré, ce n'est pas une panne : Unity travaille encore.
Le site
Le site vit à part : un autre dossier, un autre conteneur, un autre port. Il ne sait rien du jeu. Il lit des pages écrites en Markdown, une par chapitre, et il les affiche. Quand un chapitre est publié, un dossier et une page de plus, et le site se met à jour tout seul. Les données d'un côté, ce qui les affiche de l'autre : c'est la même idée que pour le jeu.
Le site est fait de briques. Un cadre dessiné, une étiquette déchirée, un bouton, une carte de chapitre : chaque brique est une petite fonction qu'on réutilise partout. Une page ne fait qu'assembler des briques. Et il n'y a aucune image dans le design : les couleurs, le trait tremblé, l'île et la mer sont décrits dans le prompt, en mots et en chiffres. C'est voulu. Si vous copiez ces prompts chez vous, vous obtenez le même site.
Le site se fait en quatre prompts, un par écran, parce qu'un prompt qui demande tout d'un coup finit toujours par oublier quelque chose. Chaque prompt commence par dire où on en est, et demande à l'IA de relire le code existant avant d'écrire.
L'accueil
Où on en est : C:\Atelier\Site est vide. C:\Atelier\Versions contient un dossier par version web du jeu, chacun avec un index.html de build Unity WebGL compressé en gzip (dossier Build à côté) et une lettre.md : un en-tête YAML entre deux lignes « --- » (id, titre, video, etat qui vaut a_completer ou publiee, et parfois d'autres champs qu'il faut tolérer), puis des sections Markdown de niveau « ## » : « Ce qui est nouveau », « Ce qui a été fait », « Ce qui cloche encore », « Comment c'est fait, et pourquoi », « Ce qui a mordu », « Sources ». Les identifiants sont de la forme A0-03 : acte 0, chapitre 3. La version d'origine s'appelle version-0. Le jeu s'appelle Orévia ; c'est un RPG en 3D que je refais chapitre par chapitre avec une IA, en vidéo, et le site raconte ça. Un Docker tourne déjà sur le port 8091 : ne pas y toucher. Tu n'as aucune image, aucune maquette, aucun fichier de référence : tout le design est décrit dans ce prompt, et tout se fait en CSS, SVG et JavaScript. Quelqu'un qui copie ce prompt chez lui doit obtenir le même site que moi. Ce que je veux, pour ce premier prompt : le Docker, l'organisation du code, et l'accueil complet. Les autres pages viendront par d'autres prompts, dans la même organisation. 1. L'infrastructure. Un docker-compose.yml avec l'image php:8.4-apache, le port 8092, Site/www comme racine web, et C:\Atelier\Versions monté en lecture seule sous /var/www/html/versions. Un .htaccess qui active la réécriture, sert un build Unity WebGL gzip (Content-Encoding gzip pour les .gz ; types application/wasm pour .wasm.gz, application/javascript pour .js.gz, application/octet-stream pour .data.gz) et envoie les adresses propres vers index.php, sauf les fichiers qui existent. Markdown rendu par Parsedown, un seul fichier déposé dans www/lib. Pas d'autre dépendance que la police Google Fonts. 2. L'organisation du code, pour que tout soit réutilisable. Le principe : chaque morceau visible du site est un composant, une fonction PHP dans un fichier à son nom sous www/composants, qui reçoit un tableau associatif de paramètres et retourne une chaîne HTML, sans jamais rien afficher directement. Les pages ne font qu'assembler des composants. Ajouter un composant, c'est ajouter un fichier. Précisément : - www/index.php : le routeur, sans framework. Pour l'instant une route, / ; une adresse inconnue répond 404 avec la même mise en page, un titre « Page perdue en mer » et un bouton vers l'accueil. - www/lib/versions.php : la couche de données. Elle lit les dossiers de /var/www/html/versions qui contiennent un index.html, parse la lettre.md (en-tête YAML à plat, clé: valeur, champs inconnus gardés tels quels dans le tableau), coupe le corps en sections par titre « ## », et expose : la liste des versions triée du plus récent au plus ancien par acte puis numéro de chapitre, version-0 toujours en dernier ; la dernière version publiée (etat publiee, hors version-0) ; le résumé d'une version, c'est-à-dire le premier paragraphe de « Ce qui est nouveau », coupé proprement à trois lignes d'environ 90 caractères ; l'adresse de jeu d'une version, /versions/<id>/index.html pour l'instant. - www/gabarits/page.php : la mise en page commune, qui reçoit le titre, la description et le contenu : doctype, lang fr, meta viewport, title « <page> · Orévia », meta description, la police, la feuille www/css/site.css, le bloc SVG des filtres (voir le point 4) juste après l'ouverture du body, le contenu, le pied de page, et le script www/js/site.js en fin de body. - www/composants/ : un fichier par composant, avec leurs paramètres : cadre (contenu HTML, fond, épaisseur de trait, ombre, inclinaison en degrés, classes en plus) ; etiquette (texte, fond, couleur du texte, rotation) ; touche (adresse, sur-titre, titre, variante laiton ou magenta) ; carte_chapitre (id, titre, ligne d'acte, résumé, liens jouer/vidéo/lire, dernier oui/non, sombre oui/non) ; diorama (rien) ; marque (rien) ; pied (rien) ; bouton_simple (texte, adresse). Chaque fichier de composant commence par un commentaire de trois lignes : à quoi il sert, ses paramètres, un exemple d'appel. - www/pages/ : accueil.php et erreur.php. Chacune assemble des composants et rien d'autre. - www/css/site.css : une seule feuille, écrite à la main, sans préprocesseur. Les variables en tête, puis une section par composant, chaque section précédée d'un commentaire au nom du composant. Aucun style en ligne dans le HTML, sauf les positions des pièces du diorama (nuages, vagues, barque) et les variables CSS passées par un composant. - www/js/site.js : le seul JavaScript du site. Pour l'instant, les bords déchirés. - www/contenu/accueil.md : l'en-tête YAML porte etiquette (« CRÉER UN RPG »), titre (« Je refais mon RPG avec une IA et sans rien cacher »), sous_titre (« Chaque épisode est une vidéo qui finit par un build jouable. Et tous les prompts sont prêts à copier. ») et bandeau (« UN SYSTÈME PAR SOIRÉE »). L'accueil les lit là. - www/images/ : les captures du jeu, c'est tout. Le design n'utilise aucune image. 3. Les variables du design, déclarées sur :root et utilisées partout, jamais une couleur en dur ailleurs que dans le diorama : --encre #15161F ; --papier #EFE4C8 ; --papier-sombre #E2D3AE ; --laiton #D1A445 ; --laiton-fonce #9A6B22 ; --nuit #1E2B4D ; --ciel #4FA0D6 ; --ciel-clair #BFE0F2 ; --patine #3E8C86 ; --magenta #D8457E ; --titre « Luckiest Guy », avec repli Impact puis cursive ; --texte « Barlow Condensed », avec repli Arial Narrow puis sans-serif. Google Fonts : Luckiest Guy, et Barlow Condensed en 400, 600, 700, 800. Le body : marge 0, fond encre, texte papier, police texte 19px, interligne 1.45. Tout en box-sizing border-box. Les liens héritent la couleur. 4. Les deux filtres SVG, définis une fois dans un <svg width="0" height="0" style="position:absolute"> en tête du body. Filtre crayon : feTurbulence type fractalNoise, baseFrequency 0.035, numOctaves 2, seed 3, puis feDisplacementMap sur SourceGraphic avec scale 6. Filtre crayon2 : baseFrequency 0.06, numOctaves 2, seed 9, scale 4. Ils donnent le trait tremblé d'un dessin à la main. 5. Le cadre dessiné, la classe dessin, la brique de tout le site. L'élément est en position relative avec isolation isolate. Son ::before, en position absolue inset 0 et z-index -1, porte le fond var(--fond, papier), une bordure var(--trait, 4px) solid encre, une ombre portée var(--ombre, 9px 9px 0 nuit) sans flou, et le filtre crayon. Son ::after, mêmes inset et z-index, n'a qu'une bordure de 1.5px encre, décalée de translate(3px, -2px) rotate(-0.5deg), opacité 0.7, filtre crayon2 : c'est le trait repassé une seconde fois. Chaque usage règle --fond, --trait et --ombre. Pour un cadre rond, les deux pseudo-éléments prennent border-radius 50%. Aucun dégradé dans un cadre, aucune trame de points, jamais. 6. L'étiquette en papier déchiré, la classe dechire. Inline-block, position relative, isolation isolate. Son ::before, inset -5px -9px, z-index -1, fond var(--fond, nuit), clip-path var(--bord), filtre crayon2 suivi d'un drop-shadow(3px 3px 0 encre). Le JavaScript calcule --bord pour chaque élément dechire de la page, dans l'ordre, avec la graine 17 + 31 × index : un générateur congruentiel s = (s × 9301 + 49297) mod 233280, aléa = s / 233280 ; une phase = aléa × 6 ; le bord haut parcourt x de 0 à 100 par pas de 1.5 + aléa × 2.5, avec y = 9 + 7 × sin(x / 9 + phase) + aléa × 10 ; le bord bas, même parcours, y = 100 - (9 + 7 × sin(x / 7 + 2 × phase)) - aléa × 10 ; on ferme par le point 100% 10% en haut, 100% 88% en bas, trois points sur le côté droit (97 + aléa × 3 % à 30 %, 95 + aléa × 4 % à 55 %, 97 + aléa × 3 % à 78 %) et trois sur le côté gauche (aléa × 3 % à 70 %, 1 + aléa × 4 % à 45 %, aléa × 3 % à 22 %) ; le polygone va haut, droite, bas à l'envers, gauche. Sans JavaScript, l'étiquette reste un rectangle : acceptable. 7. Le diorama, en haut de l'accueil : un bloc ciel en position relative, overflow hidden, isolation isolate, dont le fond empile quatre dégradés, du dessus au dessous : radial-gradient(ellipse 60% 40% at 70% 85%, rgba(209,164,69,.55), transparent 70%) ; radial-gradient(ellipse 80% 50% at 20% 20%, rgba(255,255,255,.55), transparent 60%) ; radial-gradient(ellipse 50% 30% at 85% 35%, rgba(255,255,255,.35), transparent 60%) ; linear-gradient(180deg, #2E6FB0 0%, ciel 35%, ciel-clair 70%, #E9D9B4 100%). Toutes les pièces qui suivent sont en position absolue, z-index 0, et l'en-tête qui vient par-dessus est en z-index 1. - Le soleil : un cadre dessin rond de 150px, fond laiton, trait 4px, ombre 6px 6px 0 laiton-fonce, à right 14% et top 7%. Six rayons : des barres de 260 × 5px, fond laiton, opacité 0.55, centrées sur le soleil, tournées de 0, 30, 60, 90, 120 et 150 degrés, filtre crayon2. - Trois nuages : des pilules blanches de 34px de haut, border-radius 40px, opacité 0.9, filtre crayon, ombre 4px 4px 0 rgba(30,43,77,.18), avec une bosse ::before ronde (left 22%, top -55%, width 45%, height 130%). Positions : left 8% top 14% largeur 170px ; left 42% top 9% largeur 120px opacité 0.75 ; left 68% top 30% largeur 210px. - La mer lointaine : une bande de left -2% à right -2%, bottom 12%, hauteur 10%, dégradé vertical de #9AD6F0 à #5FAEDC, filtre crayon2. Deux petites vagues blanches dessus : left 3% bottom 18.5% largeur 40px, et left 94% bottom 17% largeur 36px. - Un îlot au loin : left 94%, largeur 64px, bottom 15%, hauteur 4%, fond nuit, clip-path polygon(0 100%, 22% 40%, 48% 0, 72% 50%, 100% 100%), filtre crayon2. - L'île : trois plans de montagnes en papier découpé, de left 0 à right 0, bottom 13%, filtre crayon2, qui ne touchent jamais les bords pour qu'on voie l'eau de chaque côté. Plan 1, le plus loin : hauteur 24%, fond #6FB3D9, opacité 0.9, clip-path polygon(46% 100%, 50% 60%, 56% 44%, 62% 56%, 68% 30%, 74% 46%, 80% 36%, 86% 58%, 90% 100%). Plan 2 : hauteur 22%, fond patine, polygon(10% 100%, 14% 62%, 20% 44%, 27% 56%, 33% 30%, 40% 48%, 46% 38%, 54% 60%, 61% 46%, 69% 66%, 76% 100%). Plan 3, le plus proche : hauteur 12%, fond nuit, polygon(8% 100%, 14% 80%, 22% 56%, 30% 70%, 38% 42%, 47% 64%, 55% 38%, 64% 60%, 72% 50%, 80% 72%, 88% 84%, 92% 100%). - La mer proche, par-dessus les montagnes : left -2% à right -2%, bottom -8px, hauteur 15%, dégradé vertical #3A86C6 0%, #26568A 40%, nuit 72%, encre 100%, filtre crayon2. Elle finit dans la couleur du fond de page : aucune coupure entre le ciel et la suite. Sept vagues dessus, des arcs de 12px de haut avec une bordure haute de 3px en rgba(239,228,200,.9), border-radius 50% 50% 0 0 / 100% 100% 0 0, filtre crayon2 : left 5% bottom 11% largeur 80px ; 31% 10% 60px ; 58% 11.5% 90px ; 84% 10% 70px ; 14% 6.5% 110px ; 45% 7% 70px ; 72% 6% 120px. - Une barque : un cadre dessin de 46 × 14px, fond laiton-fonce, trait 3px, sans ombre, coins du bas arrondis de 14px, à left 22% bottom 8%, tournée de -3deg, avec une voile : un triangle de 15px de base et 32px de haut en papier, bord gauche vertical, cerné d'encre par drop-shadow, posé à left 16px et bottom 12px de la coque. 8. L'en-tête de l'accueil, dans le ciel : largeur maximale 1100px centrée, padding 56px 24px 270px pour laisser l'île respirer sous les boutons. - La marque : en flex, écart 18px, marge basse 44px. Le nom « Orévia » en police titre 44px, couleur papier, contour -webkit-text-stroke 2px encre, ombre de texte 4px 4px 0 nuit. À côté, l'étiquette déchirée fond laiton avec le texte de l'en-tête (« CRÉER UN RPG ») en texte 800 14px, espacement des lettres 0.4em, couleur encre, padding 4px 14px. - L'accroche : un cadre dessin de 760px de large au plus, padding 26px 34px 20px, tourné de -1.6deg, trait 5px, ombre 12px 12px 0 nuit. Un bandeau déchiré posé sur son coin haut gauche (left 18px, top -18px, rotation -3deg) avec le bandeau en texte 800 15px, espacement 0.3em, couleur laiton. Le h1 en police titre 52px, interligne 1.02, couleur nuit, ombre de texte 4px 4px 0 magenta, contour 1px encre. Le sous-titre en texte 600 21px, interligne 1.3, couleur encre, marge haute 14px. - Les deux touches : une grille de deux colonnes égales, écart 30px, marge haute 52px, largeur maximale 980px. Chaque touche est un lien, cadre dessin en flex, centré verticalement, écart 16px, padding 18px 24px 16px 20px, fond laiton, texte encre, trait 4px, ombre « 0 10px 0 laiton-fonce, 0 10px 0 4px encre » (une épaisseur de touche de clavier), rotation -1.5deg, transition transform 0.12s. La variante magenta (la démo d'origine) : fond magenta, texte papier, ombre « 0 10px 0 #8E2550, 0 10px 0 4px encre », rotation 1.2deg. Au survol, la touche s'enfonce : translateY(6px) rotate(0) et l'ombre passe à 4px. À gauche, un rond « lecture » de 58px, cadre dessin rond fond papier, trait 3px, ombre 4px 4px 0 encre, avec un triangle SVG de 26px (path M5 3l16 9-16 9z) rempli encre, décalé de 4px à droite. Le texte en colonne : un sur-titre en texte 800 13px, espacement 0.3em, majuscules, opacité 0.85 (« Chapitre A0-04 · le contrat », « Version 0 · bugs compris ») et le titre en police titre 27px, interligne 1.05, ombre de texte 2px 2px 0 rgba(21,22,31,.18) (« Tester la dernière version », « Tester la démo d'origine »). En coin haut droit, une étiquette déchirée fond papier (right -14px, top -22px, rotation 8deg) avec « JOUER » en texte 800 12px, espacement 0.3em, encre. 9. La frise des chapitres, sous le ciel : un main de 1100px au plus, padding 58px 24px 80px. Un titre de section en texte 800 15px, espacement 0.4em, majuscules, laiton, marge basse 26px : « Les chapitres · du plus récent au plus ancien ». Une grille repeat(auto-fill, minmax(300px, 1fr)), écart 34px 30px. Chaque carte est un lien vers l'article (/article/<id>, la page viendra), cadre dessin, padding 26px 24px 20px, texte encre, tournée de -0.8deg, de 0.9deg pour les cartes paires, de -1.3deg pour les multiples de trois ; au survol, droite et remontée de 3px. En coin haut gauche (left 12px, top -16px, rotation -3deg) l'identifiant dans une étiquette déchirée laiton, police titre 19px, encre ; magenta avec texte papier pour la dernière version publiée. Puis la ligne d'acte en texte 800 12px, espacement 0.3em, majuscules, patine (« Acte 0 · Les fondations » ; si la lettre a un champ acte, on l'utilise, sinon « Acte N ») ; le titre en police titre 30px, interligne 1.05, encre, ombre de texte 3px 3px 0 ciel ; le résumé en texte 500 18px, interligne 1.35, couleur #2a2616 ; une ligne de liens en texte 800 13px, espacement 0.25em, majuscules, magenta : Jouer (le build), Vidéo si le champ video est rempli, Lire (l'article). La carte de version-0 a le fond papier-sombre et son étiquette en laiton-fonce, avec « V0 » et la ligne « Le premier jet ». Une version en a_completer apparaît avec une étiquette « PAGE À VENIR » à la place du résumé, sans lien Lire, et n'est jamais choisie comme dernière version. 10. Le pied de page, sur toutes les pages : centré, padding 40px 24px 60px, couleur #9a9a9a, texte 500 16px : « Orévia · Créer un RPG ». 11. Téléphone d'abord. Sous 640px : le nom de la marque à 34px, le h1 de l'accueil à 38px, les touches sur une colonne, leur titre à 23px. Jamais de défilement horizontal, jamais moins de 16px de marge latérale. 12. Les petites choses qui comptent : chaque bouton a un aria-label ; le focus clavier est visible, contour 3px magenta décalé de 2px ; avec prefers-reduced-motion, pas de transition ; chaque page a son title et sa meta description ; tous les textes du site sont en français avec leurs accents ; aucun texte n'est en dur dans une page quand il vient d'une lettre ou de accueil.md. Plus tard : la page d'un chapitre, l'écran de jeu avec un bouton retour, un catalogue des composants ; un champ pour laisser son mail, une page porte ouverte pour les artistes, un texte sur ce que la télémétrie mesure ; le même Docker tournera sur un serveur public. Un second thème ne touchera que les variables de :root. Le code tolère des champs inconnus dans les en-têtes. Ce que je ne veux pas : pas de base de données, pas de copie des builds dans Site, pas de génération de fichiers, pas de framework, pas d'étape de build (ni npm, ni Sass), pas de compte, pas d'image pour le design, pas de police d'icônes, pas de CDN autre que Google Fonts, pas de trame de points, pas de dégradé dans un cadre, pas de bordure gauche colorée, pas d'étoile ni d'animation qui tourne, pas de style en ligne hors diorama. Quand c'est fait : docker compose up -d, puis montre-moi http://localhost:8092. Je dois voir le diorama, cliquer sur les deux boutons et jouer, voir version-0 et A0-03 dans la liste, et voir le tout tenir à la largeur d'un téléphone. Donne-moi aussi la liste des fichiers créés, une ligne chacun.
La page d'un chapitre
Où on en est : le site tourne sur http://localhost:8092 avec son accueil, fait au prompt précédent : un Docker php:8.4-apache, C:\Atelier\Versions monté en lecture seule sous /var/www/html/versions, un routeur www/index.php, une couche de données www/lib/versions.php qui lit les lettre.md, des composants PHP sous www/composants (une fonction par fichier, qui retourne du HTML), une seule feuille www/css/site.css avec les variables de couleur en tête, un seul script www/js/site.js. Les cartes de l'accueil pointent déjà vers /article/<id>, qui n'existe pas encore. Commence par lire www en entier et garde cette organisation : mêmes variables, mêmes classes dessin et dechire, nouveaux composants dans de nouveaux fichiers. Tu n'as aucune image ni maquette : tout est décrit ici. Ce que je veux : la page d'un chapitre, /article/<id>, qui rend la lettre entière, sur le fond encre de la page, dans une colonne de 860px au plus, marge 60px auto 0, padding 0 24px 90px. 1. La couche de données gagne : les sections de la lettre dans l'ordre, chacune avec son titre et son Markdown ; le précédent et le suivant d'un identifiant dans l'ordre de la liste (du plus récent au plus ancien, version-0 en dernier) ; le libellé du bandeau, « ACTE 0 · CHAPITRE 4 » déduit de l'identifiant, ou « LE PREMIER JET » pour version-0. Un identifiant inconnu, ou une lettre en a_completer, donne la page 404. 2. Un fichier www/lib/markdown.php rend le corps avec Parsedown, puis remplace chaque bloc de code <pre><code> par le composant prompt, texte échappé conservé. Les liens de « Sources » s'ouvrent dans un nouvel onglet. 3. La navigation, un composant navigation_chapitres (précédent ou null, suivant ou null), en haut et en bas de l'article : une grille 1fr auto 1fr, écart 16px, marge basse 40px. Deux boutons, cadres dessin en flex, écart 14px, padding 12px 18px 10px, trait 4px, transition 0.12s : le précédent à gauche, fond patine, texte papier, ombre « 0 7px 0 #27605C, 0 7px 0 4px encre » ; le suivant à droite, fond laiton, texte encre, ombre « 0 7px 0 laiton-fonce, 0 7px 0 4px encre », contenu inversé (flèche à droite, texte aligné à droite). Au survol, translateY(4px) et ombre à 3px. La flèche : un rond dessin de 46px, fond papier, trait 3px, ombre 3px 3px 0 encre, le caractère ← ou → en police titre 30px, couleur patine pour le précédent, encre pour le suivant. À côté : le sens en texte 800 12px, espacement 0.3em, majuscules, opacité 0.8 (« Chapitre précédent · A0-03 ») et le titre du chapitre en police titre 22px, sur une ligne, coupé par des points de suspension. Entre les deux, « Tous les chapitres », lien vers l'accueil en texte 800 12px, espacement 0.3em, majuscules, laiton, papier au survol. Si le précédent ou le suivant n'existe pas, sa case reste vide et la grille ne bouge pas. 4. Le titre, un composant titre_de_page (bandeau, titre) : un cadre dessin, padding 22px 30px 16px, rotation -1.2deg, trait 5px, ombre 10px 10px 0 nuit, avec un bandeau déchiré (left 16px, top -16px, rotation -3deg) en texte 800 14px, espacement 0.3em, laiton. Le h1 en police titre 46px, interligne 1.02, nuit, ombre 4px 4px 0 magenta, contour 1px encre. 5. La feuille, un composant feuille (HTML), le corps de la lettre : un cadre dessin, padding 34px 38px 30px, trait 4px, ombre 10px 10px 0 nuit, texte encre. Les titres de section en police titre 30px, interligne 1.1, nuit, ombre 3px 3px 0 ciel, marge 30px 0 10px, le premier sans marge haute. Les sous-titres en texte 800 15px, espacement 0.3em, majuscules, patine, marge 26px 0 8px. Paragraphes et puces en texte 500 19px, interligne 1.45, couleur #2a2616 ; listes avec 1.2em de retrait, 4px entre les puces. Les liens dans la feuille en magenta soulignés. Le code en ligne sur fond papier-sombre avec 0.3em de padding horizontal. 6. Le prompt, un composant prompt (texte), pour chaque bloc de code : un cadre dessin fond nuit, trait 3px, ombre 6px 6px 0 magenta, texte papier, padding 14px 18px 12px, marge 16px 0 22px. Une étiquette déchirée laiton « PROMPT » en coin (left 10px, top -14px, rotation -2deg), texte 800 12px, espacement 0.35em, encre. Le texte dans un pre en police texte 500 16px, interligne 1.5, pre-wrap, replié à max-height 4.6em avec overflow hidden et un ::after de 2.2em en dégradé de transparent vers nuit collé en bas ; la classe ouvert enlève la limite et le dégradé. Sous le texte, deux boutons en flex, écart 10px : « Voir plus » (fond papier, rotation -1deg) et « Copier » (fond laiton, rotation 1deg), en texte 800 13px, espacement 0.25em, majuscules, encre, bordure 2px encre, padding 6px 12px 4px, ombre 3px 3px 0 encre ; enfoncés de 2px au clic. Un prompt de trois lignes ou moins n'a ni dégradé ni « Voir plus ». 7. Dans www/js/site.js, deux comportements de plus, et rien d'autre : « Voir plus » bascule la classe ouvert et devient « Voir moins » ; « Copier » met le texte du pre, sans les espaces de bord, dans le presse-papiers, affiche « Copié » 1,6 seconde, puis redevient « Copier ». 8. Sous 640px : la feuille en padding 26px 20px, le h1 à 34px, la navigation sur une colonne avec « Tous les chapitres » en dernier. La meta description de la page est le résumé du chapitre. Les règles de l'accueil tiennent toujours : aria-label sur chaque bouton, focus visible, pas de transition avec prefers-reduced-motion, aucun texte en dur qui vienne d'une lettre. Plus tard : l'écran de jeu, /jouer/<id>, avec un bouton retour vers cet article ; un catalogue des composants. Les composants d'aujourd'hui y seront montrés tels quels. Ce que je ne veux pas : pas de base de données, pas de framework, pas d'image, pas de JavaScript au-delà des trois comportements (bords déchirés, voir plus, copier), pas de style en ligne, pas de CSS dupliqué : ce qui existe pour l'accueil est réutilisé. Quand c'est fait, montre-moi http://localhost:8092/article/A0-03. Je dois lire la lettre entière, déplier et copier un prompt, aller au chapitre précédent et revenir, ouvrir l'article de version-0, voir la page 404 sur /article/rien, et voir le tout tenir à la largeur d'un téléphone. Donne-moi la liste des fichiers créés ou modifiés, une ligne chacun.
L'écran de jeu
Un jeu qu'on lance sans pouvoir revenir, c'est une porte sans poignée. Le prompt ci-dessous a d'abord fait un écran à part : le build dans un cadre, une barre au-dessus avec Retour et Plein écran.
En l'essayant, on a changé d'avis. Le jeu est mieux dans la page du chapitre, juste sous le titre. On joue, et la page raconte ce qui a été fait, sans aller et venir. L'ancienne adresse mène maintenant là. Au passage, le site a cessé d'afficher la page d'Unity : il lance le build sur son propre canevas, sans le logo ni le bouton d'Unity. Le build, lui, n'a pas bougé d'un octet.
Où on en est : le site tourne sur http://localhost:8092, avec l'accueil et la page d'un chapitre, faits aux prompts précédents, dans une organisation en composants PHP (www/composants, une fonction par fichier), une seule feuille www/css/site.css et un seul script www/js/site.js. Les liens « Jouer » ouvrent pour l'instant le build Unity tel quel, /versions/<id>/index.html : la page d'origine de Unity, avec sa barre de chargement, sans aucun moyen de revenir au site, sans titre, sans plein écran. Les builds sont en lecture seule : on ne touche jamais à leurs fichiers. Commence par lire www en entier et garde cette organisation. Tu n'as aucune image ni maquette. Ce que je veux : un écran de jeu, /jouer/<id>, dans la mise en page du site, qui encadre le build sans le modifier. 1. La page est faite de deux zones, sur toute la hauteur de la fenêtre, sans défilement : une barre en haut, et le jeu en dessous qui prend tout le reste. Pas de diorama, pas de pied de page. 2. La barre, un composant barre_de_jeu (id, titre, sous-titre, adresse de l'article) : fond encre, hauteur 64px, padding 0 16px, en flex, centré verticalement, écart 14px, avec un trait de 3px laiton en bas, filtre crayon2. De gauche à droite : un bouton « Retour » (cadre dessin, fond patine, texte papier, trait 3px, ombre « 0 5px 0 #27605C, 0 5px 0 3px encre », padding 8px 14px 6px, texte 800 13px, espacement 0.3em, majuscules, avec le caractère ← devant ; au survol translateY(3px) et ombre à 2px) qui mène à l'article du chapitre ; le nom « Orévia » en police titre 24px, papier, contour 1.5px encre, ombre 3px 3px 0 nuit ; puis le chapitre : l'identifiant dans une étiquette déchirée laiton (police titre 15px, encre) et le titre du chapitre en police titre 20px, papier, sur une ligne coupée par des points de suspension ; et, poussés à droite, deux boutons : « Plein écran » (même dessin que Retour, fond laiton, texte encre, ombre laiton-fonce) et « Lire la page » (fond papier, texte encre, ombre nuit), qui mène à l'article. Sous 640px, le titre du chapitre disparaît et « Lire la page » aussi : il reste Retour, Orévia, l'identifiant et Plein écran. 3. Le jeu : une iframe sur /versions/<id>/index.html, sans bordure, largeur et hauteur 100% de sa zone, fond encre, avec allow="fullscreen; pointer-lock; autoplay" et allowfullscreen. La zone qui contient l'iframe a position relative, et c'est elle qui passe en plein écran, pas l'iframe seule, pour que le navigateur garde le son. Une ligne d'aide sous la barre, dans la zone de jeu, posée en haut à droite en semi-transparence (fond rgba(21,22,31,.7), texte papier 600 14px, padding 6px 12px, coins arrondis 6px), affiche les commandes lues dans le champ commandes de l'en-tête de la lettre quand il existe (par exemple « ZQSD ou flèches · Espace · Échap pour le menu »), et « Cliquez dans le jeu pour prendre la main » sinon. Elle s'efface au premier clic dans la page. 4. Le JavaScript de cet écran, dans site.js, un seul comportement de plus : le bouton « Plein écran » appelle requestFullscreen sur la zone de jeu et devient « Quitter » tant qu'on y est ; au chargement, et à chaque clic sur la zone, l'iframe reçoit le focus pour que le clavier aille au jeu. Rien d'autre : le chargement, la barre de progression et le jeu lui-même restent ceux du build Unity, dans l'iframe. 5. Sur un écran de moins de 900px de large, un bandeau en haut de la zone de jeu dit « Ce jeu se joue au clavier ou à la manette, sur un ordinateur » (fond laiton, texte encre, 600 15px, padding 8px 16px, centré) ; le jeu se charge quand même en dessous. Un lien « Le jeu ne se charge pas ? Ouvrir le build seul », en texte 600 14px, laiton, souligné, posé en bas à droite de la zone en semi-transparence comme l'aide, ouvre /versions/<id>/index.html dans un nouvel onglet. 6. Tous les liens « Jouer » du site (les deux touches de l'accueil, les cartes, et tout lien de jeu dans un article) pointent maintenant vers /jouer/<id>. Le titre de la page est « Jouer · <titre> · Orévia », sa meta description le résumé du chapitre. Un identifiant inconnu donne la page 404. Les règles tiennent : aria-label sur chaque bouton, focus visible, pas de transition avec prefers-reduced-motion. Plus tard : la télémétrie se branchera sur cet écran, et un bouton « Signaler un bug » viendra dans la barre ; laisse la barre extensible, avec un emplacement vide à droite avant Plein écran. Ce que je ne veux pas : aucune modification des fichiers des builds, pas de copie du build dans Site, pas de réécriture de la page index.html de Unity, pas de JavaScript au-delà du plein écran et du focus, pas d'image. Quand c'est fait, montre-moi http://localhost:8092/jouer/version-0 : la barre, le jeu qui se charge et répond au clavier après un clic, le plein écran qui marche et se quitte avec Échap, Retour qui ramène à l'article, et l'accueil dont les deux boutons mènent ici. Donne-moi la liste des fichiers créés ou modifiés, une ligne chacun.
Les briques et le catalogue
Où on en est : le site tourne sur http://localhost:8092 avec trois écrans faits aux prompts précédents : l'accueil, la page d'un chapitre /article/<id>, l'écran de jeu /jouer/<id>. Le code est censé être en composants PHP sous www/composants, une fonction par fichier qui retourne du HTML, avec une seule feuille www/css/site.css et un seul script www/js/site.js. Commence par lire www en entier, puis fais la liste de ce qui s'écarte de cette organisation : du HTML écrit directement dans une page, un style en ligne hors diorama, une règle CSS dupliquée, une couleur en dur, un texte en dur qui vient d'une lettre. Tu n'as aucune image ni maquette. Ce que je veux : finir l'organisation et la rendre visible, sans rien changer à l'écran. 1. Tout ce que tu as listé rentre dans le rang : chaque morceau visible devient un composant s'il ne l'est pas déjà, dans la liste suivante, un fichier par nom : cadre, etiquette, touche, carte_chapitre, navigation_chapitres, titre_de_page, feuille, prompt, barre_de_jeu, diorama, marque, pied, bouton_simple. Chaque fichier commence par un commentaire de trois lignes : à quoi il sert, ses paramètres, un exemple d'appel. Les pages (www/pages) n'ont plus une balise HTML en dur : elles assemblent. La feuille CSS a une section par composant, dans l'ordre de la liste, chacune précédée d'un commentaire au nom du composant, les variables en tête, et plus une couleur en dur hors diorama. 2. Une page catalogue, /composants, non reliée depuis l'accueil mais depuis un lien discret dans le pied de page : elle affiche chaque composant de la liste, un par bloc, avec des données d'exemple, et en dessous, dans un petit cartouche (texte 600 14px, couleur #9a9a9a), son nom de fichier, ses paramètres et son exemple d'appel, lus dans le commentaire d'en-tête du fichier. Les composants qui ont des variantes (touche laiton et magenta, navigation avec et sans suivant, carte dernière, sombre et à venir, prompt court et long) les montrent toutes. Le diorama y est montré en petit, dans une boîte de 420px de haut. Cette page sert à vérifier le design d'un coup d'œil et à montrer aux gens comment réutiliser une brique. 3. Une passe sur les règles communes, à vérifier page par page et à corriger : chaque bouton a un aria-label ; le focus clavier est visible, contour 3px magenta décalé de 2px ; avec prefers-reduced-motion, aucune transition ; chaque page a son title et sa meta description ; aucun défilement horizontal à 360px de large ; aucun texte en dur qui vienne d'une lettre ou de accueil.md ; les champs inconnus d'un en-tête ne cassent rien ; www/js/site.js ne fait que quatre choses : bords déchirés, voir plus, copier, plein écran et focus du jeu. 4. Un fichier www/README.md d'une page, en français : comment le site lit les versions, comment ajouter un composant, comment ajouter une page, et la liste des routes. Plus tard : chaque nouveau bloc du site naîtra comme composant et entrera au catalogue le jour même ; un second thème ne touchera que les variables de :root. Ce que je ne veux pas : aucun changement visible sur l'accueil, l'article et l'écran de jeu ; pas de framework, pas d'outil de build, pas de base de données, pas d'image, pas de nouvelle dépendance. Quand c'est fait, montre-moi http://localhost:8092/composants, puis l'accueil, l'article A0-03 et l'écran de jeu de version-0, pour que je vérifie que rien n'a bougé. Donne-moi la liste de ce que tu as déplacé ou corrigé, une ligne chacun, et le contenu du README.
Comment on vérifie
On ouvre le site, on clique sur « Tester la dernière version » : la scène vide, un ciel. On clique sur « Tester la démo d'origine » : Solea se charge en haut de sa page, on clique dedans, on joue, on passe en plein écran, on revient. On ouvre l'article du chapitre, on déplie un prompt, on le copie. On ouvre la page des briques, pour voir chaque composant seul. Et on réduit la fenêtre à la largeur d'un téléphone : tout doit tenir.
Ce qui a mordu
- Lancer le build pendant que Unity a le projet ouvert : Unity refuse, et le message est noyé dans le journal.
- Ouvrir le fichier index.html en double-cliquant : un build web ne se charge pas depuis un fichier, il lui faut un serveur.
- Demander tout le site en un seul prompt : l'IA s'arrête à l'accueil et oublie le reste. Un prompt par écran.
- Lancer le build sur notre propre canevas sans lui donner d'identifiant : Unity le cherche par son nom, ne le trouve pas, et le jeu plante avant la première image.
- Les images de référence de la bible ne vont pas sur le site. Ce sont des références, pas des images du jeu.
Sources
- https://docs.unity3d.com/Manual/webgl-server-configuration-code-samples.html : la configuration du serveur pour un build compressé.