11 — Images en full JS : blocs pixel et textcard pour motion et stories
Statut : accepté · Phase : P6 à P11 · Mis à jour : 25/10/2026.
Ce document reprend l'idée de sites/plans/pages/archive/BACKLOG_IDEA.md (section motion.recit.dev)
: « récupération du skill afin de faire le brocoli » et « récupération de projet basic afin de créer
des images en full JS, création de Pixel Image + format Text afin de créer des stories ». Il la
pousse jusqu'à une spécification, au même niveau que 05-video.md et 08-formats.md, avec les
mêmes règles : une seule stack, une seule source, aucune métrique inventée, aucune devinette.
Ce plan ne crée pas de nouveau format : deux blocs de contenu suffisent (D1). Le backlog disait «
format Text » ; la question 1 a été tranchée le 24/10/2026, sans l'utilisateur et à sa demande
(décision réversible) : textcard est un bloc de motion et de stories, pas un format. Le
personnage (« le brocoli »), sa persona, ses décors et ses prompts relèvent du plan 14
(14-ia-scenario.md), qui en est propriétaire. Ici, on ne traite que ce qui produit des images
: le sprite, la carte de texte, l'export.
Placement, domaines, exemples et règles communes aux plans 10 à 14 : 06-roadmap.md, section «
Backlog : plans 10 à 14 ». En résumé : un lot avancé en P11 (bloc de code ```pixel et
textcard), rien en P4 ni en P5. « Pet » et « le skill » sont identifiés depuis le 25/10
(pets.md) : c'est le skill hatch-pet d'OpenAI, outil externe, et le personnage s'appelle
Broc (§2.3, D9). Seul ce qui dépend du « projet basic » reste bloqué.
1. L'idée en une phrase
Une image de story s'écrit comme le reste : en Markdown ou en JSON, rendue par du code, jamais par un fichier binaire figé.
Deux blocs :
pixel: une image en pixel art décrite en données (une grille de caractères et une palette, ou une graine pour un générateur), rendue en SVG. Pas de PNG à versionner, pas de modèle, pas de droits ambigus.textcard: une carte de texte (citation, titre, chiffre, légende, liste) au format9:16ou1:1, mise en page autour des blocs emaki existants (statement,title,stat,list).
Ce que cela apporte à « écrire une fois, raconter partout » :
| Sans ces blocs | Avec ces blocs |
|---|---|
| une story Instagram demande un outil graphique et un PNG par langue | la carte est du texte Markdown, traduite par les compagnons *.fr.md (plan 09), l'image est refaite |
| un exemple public a besoin d'images sous licence | un sprite écrit en grille donne une identité visuelle sans aucun asset tiers |
| la mascotte d'une marque est un PNG figé, impossible à animer sans un logiciel | la mascotte est une grille versionnée, avec des poses en frames, animée par motion |
| les goldens dépendent d'images externes | tout est déterministe : même source, même graine, même SVG, même PNG |
| une carte de story, un plan vidéo et une slide sont trois fichiers | la même scène se lit en stories, se rend en MP4 9:16, s'affiche en slide ou dans la doc |
2. Ce que la recherche a appris
2.1 Dans le dépôt et dans emaki
- emaki rend déjà des images fixes :
packages/render/src/render.tsappellerenderStillde@remotion/renderer(renderDeckStill,renderDeckStills) après un seul bundle. C'est ce qui produit ses goldens PNG. Le même fichier intègre les images locales en data URI (inlineImages). - emaki a un nœud
image(src,w,h,fit,radius,alt) et un nœudvector(chemins SVG). Les blocs se déclarent pardefineBlock({ type, props: zod, text }). - Les thèmes emaki exposent des jetons
colors(bg,surface,text,muted,accent),data(peach,mint…),fontsettype(tailles). C'est la palette naturelle d'un sprite. @recit/clidéclare déjàplaywrighten peer dépendance optionnelle pourrecit export --to pdf. Le plan 15 déplace ce pair dans@recit/paged, dont le sous-export@recit/paged/runnerest le seul module « lancer Chromium, servir un build, attendre prêt, mesurer, capturer » de Récit : les captures de ce plan passent par lui.- Plan 08 :
recit export --to imagesdoit produire un PNG1080×1920par carte (ST3), et le pont stories ⇄ motion (ST2) est déjà promis.:::cardest déjà une directive du deck.sharpest désactivé dansallowBuildset réservé à la découpe du toon. - Le guide MCP d'emaki pose la règle : le serveur n'appelle jamais de modèle, l'hôte réfléchit
et les outils sont déterministes.
describe_blocksert déjà de catalogue de schémas. - Aucun skill « brocoli » ni « projet basic » n'a été trouvé dans emaki, dans
.claude/skills(seulementrecit-importetupstream-sync), danssites/plans/pages/ou danscontext.md. Le skill a été identifié le 25/10 par l'utilisateur :hatch-pet(§2.3). Le « projet basic » reste inconnu.
2.2 Techniques d'image en JavaScript
| Technique | Node sans binaire natif | Verdict pour Récit |
|---|---|---|
SVG <rect> + shape-rendering="crispEdges" | oui (une chaîne) | retenu : vectoriel, net à toute taille, déterministe, identique en SSR, dans Remotion et dans Playwright |
Canvas 2D / OffscreenCanvas.convertToBlob() | non (node-canvas, @napi-rs/canvas…) | navigateur seulement : bouton « Télécharger PNG » éventuel |
| satori + resvg | oui en wasm, natif sinon | rejeté : second moteur de mise en page et de texte, trois dépendances, alors que Chromium est déjà là |
Remotion renderStill() | Chrome Headless Shell téléchargé | retenu pour une frame précise d'une scène animée, déjà dans emaki, confiné à @recit/motion-render |
Playwright locator.screenshot() | Chromium déjà requis par l'export PDF | retenu pour les cartes statiques : fidélité exacte au lecteur web, appelé par @recit/paged/runner |
| Encodeurs PNG en JS (pngjs, UPNG.js, fast-png) | oui | inutile tant que Chromium rasterise ; en réserve seulement |
Pixel art procédural sans dépendance : PRNG à graine (mulberry32 ou sfc32, quelques lignes), sprites symétriques par demi-grille reflétée avec contour automatique (technique des identicons), dithering ordonné (matrice de Bayer) ou par diffusion d'erreur (Floyd–Steinberg), quantification vers une palette (distance RGB ou OKLab). Tout tient dans un module TypeScript sans dépendance.
2.3 Le skill hatch-pet : c'est « Pet » (répondu le 25/10, vérifié)
L'utilisateur l'a confirmé le 25/10 (pets.md) : « Pet » et « le skill du brocoli » sont le skill
open source hatch-pet d'OpenAI (openai/skills, Apache-2.0), lancé avec l'image de référence de
Broc. Les faits vérifiés (entrées, sorties, lignes, contrôles, licence, imagegen) sont dans
14-ia-scenario.md §2.2, propriétaire du personnage. Ce qui compte pour les images :
- la sortie est une planche raster (PNG ou WebP,
1536×1872, cases192×208, 8 colonnes, 9 lignes, fond transparent), dans le style de la référence : pour Broc, un rendu 3D doux. Ce n'est pas une grille```pixel; - une liste fermée de 9 états, une ligne chacun :
idle(6 images),running-right(8),running-left(8),waving(4),jumping(5),failed(8),waiting(6),running(6),review(6), avec des durées image par image ; - la première image d'
idleest l'image fixe deprefers-reduced-motion; - l'application Codex anime la planche par décalage CSS de l'arrière-plan, case par case : un rendu qui ne demande ni canvas ni décodage ;
- la génération est déléguée au skill
imagegen(outilimage_gende l'hôte, ou API OpenAI sur demande explicite).hatch-petetimagegenrestent hors de Récit : jamais une dépendance, aucun script copié.
2.4 lebrocoli.fr
- Le site appartient à l'écosystème de l'utilisateur (Base for Music). Il sert d'exemple « gabarit » parce que l'utilisateur l'a nommé.
- La mascotte (
lebrocoli.png) est un brocoli de dessin animé plat, contour noir épais, sans visage, sans texte, fond transparent. Le site ne lui donne ni nom, ni voix, ni histoire. - Polices : Lexend (OFL) pour le texte, Monument Extended (Pangram Pangram, gratuite pour un usage personnel seulement) pour les titres. La seconde ne peut pas entrer dans un exemple public : Unbounded (Google Fonts, NT Type, SIL OFL 1.1) la remplace dans le dépôt (décision du 25/10/2026).
- Les vignettes du site actuel viennent de gstatic, sans licence connue : à ne jamais réutiliser.
- Broc, le personnage nommé le 25/10, n'est pas cette mascotte plate : son image de référence
(
examples/scenario-lebrocoli/cast/broc/reference.png, 1024×1024, fournie par l'utilisateur) montre un brocoli vert souriant au rendu 3D doux, en T-pose, sans smoking ni casquette. L'image fait foi pour toutes ses images (plan 14, D12) : toute description qui s'en écarte est corrigée d'après elle. - Les couleurs relevées à l'œil sur la mascotte sont des estimations. La palette du sprite devra être extraite de l'image réelle et validée par l'utilisateur, pas recopiée de ce relevé.
2.5 Sources
- Remotion
renderStill: https://www.remotion.dev/docs/renderer/render-still ; licence : https://www.remotion.dev/license - Satori : https://github.com/vercel/satori ; resvg-js (natif) et
@resvg/resvg-wasm: https://github.com/yisibl/resvg-js OffscreenCanvas.convertToBlob: https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlobimage-rendering: pixelated: https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering- Playwright, captures : https://playwright.dev/docs/screenshots
- Dithering ordonné : https://en.wikipedia.org/wiki/Ordered_dithering ; Floyd–Steinberg : https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
- Web Stories : https://amp.dev/documentation/guides-and-tutorials/start/create_successful_stories
- hatch-pet (vérifié le 25/10) : https://github.com/openai/skills/blob/main/skills/.curated/hatch-pet/SKILL.md ; lignes : https://github.com/openai/skills/blob/main/skills/.curated/hatch-pet/references/animation-rows.md ; contrat Codex : https://github.com/openai/skills/blob/main/skills/.curated/hatch-pet/references/codex-pet-contract.md ; licence : https://github.com/openai/skills/blob/main/skills/.curated/hatch-pet/LICENSE.txt
- imagegen : https://github.com/openai/skills/tree/main/skills/.system/imagegen
- Agent Skills (format
SKILL.md) : https://agentskills.io/specification - Droit d'auteur et images générées (US Copyright Office, Part 2, 29/01/2025) : https://ipwatchdog.com/2025/01/29/part-two-copyright-office-ai-report-says-creative-prompting-doesnt-constitute-authorship/
- AI Act, article 50 (transparence) : https://digital-strategy.ec.europa.eu/en/policies/code-practice-ai-generated-content
3. Décisions proposées
| # | Sujet | Recommandation |
|---|---|---|
| D1 | Nouveau format ou nouveaux blocs ? | Tranché (24/10, question 1) : deux blocs, pas de format. Le format reste motion ou stories |
| D2 | Où vit le code pixel ? | un module pur dans @recit/components, utilisé par PixelImage et par le bloc animé de @recit/motion-blocks. Pas de package @recit/pixel pour l'instant (tranché) |
| D3 | Syntaxe pixel en v1 | le bloc de code ```pixel (grille + légende → SVG). Générateurs, fonds générés et images source : plus tard |
| D4 | Syntaxe textcard | une directive conteneur :::textcard{variant=…} qui habille un bloc emaki existant ; d'abord un plan motion 9:16, puis une carte stories |
| D5 | Schéma JSON | PixelBlock et TextCardBlock dans @recit/core/schema. En v1, les blocs restent des directives dans le body (règle de 06) |
| D6 | Scène = ? | inchangée : un plan en motion, une carte en stories. Raccourci motion --- type=pixel / type=textcard |
| D7 | Rasterisation | Playwright pour les cartes (déjà prévu par ST3), par @recit/paged/runner. renderStill pour une frame animée : plus tard |
| D8 | Palettes et polices | palette theme par défaut. Palettes nommées et police pixel seulement sous licence libre, inscrites dans NOTICE.md |
| D9 | Personnage (Broc) | une entrée entry=character de la bible du plan 14, avec son kit (plan 14 D12). Ce plan rend ses poses avec un bloc sprite qui lit la planche hatch-pet importée. Débloqué le 25/10 ; la publication attend la licence de l'image de référence (§8) |
| D10 | Reprise du « skill » et du « projet basic » | le skill est hatch-pet : outil externe, jamais repris ni appelé ; sa planche s'importe par une étape manuelle (plan 14 D12). Le « projet basic » reste bloqué |
| D11 | Placement dans la feuille de route | lot avancé en P11 (IM1, IM2). Le reste est plus tard ou bloqué. Pas de phase propre |
D1 — Deux blocs, pas un format (tranché le 24/10)
Le backlog dit « Pixel Image + format Text afin de créer des storys ». Deux lectures sont possibles :
- un format « texte » : une nouvelle manière de lire, faite de cartes de texte seules ;
- un bloc « carte de texte » dans les formats existants (
motion,stories).
Recommandation : la seconde. Un format Récit se définit par sa lecture (slide par slide, timeline,
défilement, tap). Une carte de texte au format 9:16 se lit déjà au tap dans stories et se joue
déjà en timeline dans motion. Ces images vivent donc dans les scènes des formats existants. Elles
s'enregistrent comme blocs emaki (defineBlock), et describe_block (MCP), la validation et
l'inférence fonctionnent sans code de plus. La question 1 (§8) a été tranchée dans ce sens le 24/10.
D2 — Où vit le code pixel
| Option | Pour | Contre |
|---|---|---|
A. Tout dans @recit/motion-blocks | aucun package de plus | deck et docs devraient installer les blocs motion pour une image fixe |
B. Module pur dans @recit/components, rendu animé dans motion-blocks | aucun package de plus, un sprite fixe s'affiche partout | @recit/components grossit d'un module sans dépendance |
C. Fondation @recit/pixel dès le départ | réutilisable hors de Récit | un package à publier avant d'avoir un second consommateur |
Tranché (24/10) : B. Le module ne contient que des fonctions pures : lecture de grille,
légende, palette theme, grille → rectangles fusionnés → SVG (chaîne ou hast). Pas de DOM.
PixelImage(statique, SSR) et la version statique deTextCardvivent dans@recit/components. Une fondation ne dépend jamais d'un format : deck, docs et report n'installent donc pas@recit/motion-blockspour une image fixe ou une carte de texte.- Les blocs animés
pixelettextcardvivent dans@recit/motion-blocks, qui importe le module de@recit/components. - Pas de package
@recit/pixelpour l'instant (question 3, tranchée le 24/10). Le nom reste réservé (🔒) ; le module n'en est extrait que si un second consommateur hors de@recit/componentsapparaît (par exemple le « projet basic », s'il existe).
D3 — Syntaxe pixel
v1 (lot P11) : le bloc de code ```pixel, dessiné à la main, lisible et diffable sur GitHub.
Markdown
| Élément | Rôle |
|---|---|
```pixel (bloc de code) | image dessinée à la main. En tête, la légende caractère = couleur ; après une ligne vide, la grille. Plusieurs grilles séparées par une ligne vide = des frames |
| légende | jetons de thème (text, accent, data.mint) ou couleurs hex. Un sprite écrit avec des jetons suit le thème et le mode sombre |
scale | facteur d'affichage entier (auto = le plus grand entier qui tient) |
fps | cadence des frames |
reveal | animation d'apparition en motion : scan, none |
alt | texte alternatif, obligatoire en mode strict, localisable |
Plus tard (IM3, IM5), sans engagement : directive ::pixel{gen=sprite|noise seed=…} (générateurs à
graine), fond de scène généré background=pixel:<seed>, reveal=dissolve, rotation de palette
(cycle), image source réduite et tramée (src + dither). Ces options ne font pas partie de la
syntaxe v1.
Règles (erreurs avec numéro de ligne, jamais de correction silencieuse) :
story.md:24 (pixel): row 3 has 7 cells, expected 8;- caractère absent de la légende, jeton de thème inconnu, couleur hex invalide : erreur ;
- frames de tailles différentes : erreur ;
- grille au-delà du plafond de taille (valeur à fixer après mesure, voir risques) : erreur ;
altmanquant : avertissement, erreur en mode strict ;- deux couleurs de légende identiques : avertissement.
D4 — Syntaxe textcard
Le nom textcard évite :::card (déjà pris par le deck) et poster (attribut vidéo). La carte
existe d'abord comme plan motion 9:16 (motion est disponible depuis P4), puis elle est reprise
telle quelle comme carte de stories quand le lecteur stories arrive (P11, ST2). Exemple complet :
Markdown
| Variante | Bloc emaki habillé | Contenu attendu |
|---|---|---|
quote | statement | un paragraphe, puis une ligne — attribution facultative |
title | title | un titre, puis un sous-titre facultatif |
stat | stat | un :value de @recit/data, ou un nombre littéral avec source= |
caption | statement | un paragraphe court |
list | list | une liste de trois à cinq éléments (règle de densité) |
Attributs communs : background (theme, gradient, une image locale avec alt), aspect
(hérité de la story, sinon 9:16 ou 1:1). Le nom background est le même que l'attribut de scène
de stories (09 §2.3) : il n'y a pas de second nom bg. Plus tard : safe (préréglages de zones
sûres par plateforme), mark (filigrane), background=pixel:<seed>.
Principe : aucun nouveau moteur de texte. textcard est un cadre (marges sûres, fond) autour
d'un bloc existant. En motion, on garde donc les animations déjà écrites (révélation mot à mot de
statement, compteur de stat).
Ajustement du texte : le composant cherche par dichotomie la plus grande taille de police qui tient,
mesurée dans le navigateur, entre les bornes type.* du thème. Avant mesure, le rendu SSR utilise
la borne basse (aucun débordement possible). @remotion/layout-utils est exclu, car Remotion reste
confiné à @recit/motion-render.
Règles :
- variante inconnue, ou contenu qui ne correspond pas à la variante : erreur avec ligne ;
variant=statsans:valuenisource=: erreur (règle « aucune métrique inventée ») ;- texte qui ne tient pas même à la borne basse : erreur de densité, avec la langue concernée
(
story.fr.md:12 (textcard): text overflows at minimum size) ; - fond image sans
alten mode strict : erreur (même règle que les fonds de stories, plan 08 §6).
D5 — Schéma JSON typé
Les schémas zod vont dans @recit/core/schema, à côté de ceux du plan 09. Le navigateur utilise
zod/mini. Esquisse :
TypeScript
Règle commune des plans 10 à 14 (06, « Backlog ») : en v1, un bloc transverse reste une directive
dans le body Markdown de la scène. SceneSource (09 §2.2) ne gagne aucun champ blocks.
PixelBlock et TextCardBlock valident la directive ou le bloc de code, et servent de props aux
blocs emaki (defineBlock). En JSON, une scène de stories s'écrit donc :
JSON
- Champs localisables :
alt,body,attribution. Jamais localisables : grille, légende, couleurs, tailles,source(règle du plan 09 §2.3). - Équivalence testée (plan 09 §2.4) :
compile(md) == compile(toJson(md))pour chaque exemple. - Un champ typé
blocks?dansSceneSourceserait un ajout compatible plus tard, décidé une fois pour tous les blocs transverses (form, pixel, textcard, map), pas dans ce plan.
D6 — Scène = quoi, et rendu dans les formats existants
La scène ne change pas de sens. En motion, le raccourci reprend la convention --- type=… de
05-video.md V3 (type= est réservé aux blocs motion) : --- type=pixel id=sprout fait du sprite
le bloc principal du plan, et --- type=textcard variant=quote fait de la carte le plan entier.
| Format | pixel | textcard |
|---|---|---|
motion | bloc animé (reveal, frames), fonction pure de la frame (lot P11) | un plan 9:16 ou 1:1, avec les animations du bloc habillé (lot P11) |
stories | image de carte (lot P11) | cas principal : une carte = une textcard ; export PNG 1080×1920 (ST3, lot P11) |
deck | plus tard, hors v1 (question 5) : image fixe (PixelImage, @recit/components) | plus tard, hors v1 (question 5) : slide pleine, version statique |
docs | plus tard, hors v1 (question 5) : image fixe dans une page ou en MDX | plus tard, hors v1 (question 5) : encart de citation ou de chiffre, version statique |
| autres | plus tard (report, book, toon), sur besoin réel | plus tard |
Ponts : stories ⇄ motion est déjà prévu en P11 (08-formats.md §0) et couvre les deux blocs. Les
ponts report → stories (:::kpi → textcard variant=stat) et book → stories (épigraphe →
textcard variant=quote) sont plus tard (IM3).
D7 — Rasterisation : deux moteurs, déjà présents
- Cartes statiques (stories, textcard, sprite fixe) :
recit export --to images(ST3) ouvre le lecteur dans le Chromium de Playwright par@recit/paged/runner(le module partagé avecrecit export --to pdf, plan 15) et capture chaque carte. Le rendu attend le signal « prêt » du contrat d'impression commun (whenPrintable()de@recit/core/print, plan 15), posé après l'ajustement du texte et le chargement des polices, au lieu d'un attributdata-readypropre. Préréglage v1 :1080×1920, déjà dans le plan 08. - Plus tard : frame d'une scène animée (
--frame <n>oustill=<n>) parrenderStillde@recit/motion-render, seulement si ce package est installé ; préréglages1080×1080et1200×630; bouton « Télécharger PNG » parOffscreenCanvas. - Exclus : satori, resvg,
@napi-rs/canvas, node-canvas, etsharphors du toon.
D8 — Palettes et polices
- Légende en jetons de thème par défaut (
bg,surface,text,muted,accent,data.*). Un sprite suit donc le mode sombre et le thème du site. - Palettes nommées : seulement des palettes publiées sous licence explicite (CC0 ou équivalent), chacune avec une ligne NOTICE.md. Aucune en v1.
textcardutilise les polices du thème. Une police pixel n'est possible qu'en OFL, avec sa ligne NOTICE.md, et servie localement (pas de CDN).- Plus tard : un lint de contraste texte / fond de
textcard(seuils WCAG AA).
D9 — Le personnage (Broc) : une entrée de la bible du plan 14, rendue par un bloc sprite
Le plan 14 est propriétaire du personnage, de la bible, du kit et de la provenance des images
(D12 : character.md et son jumeau character.json, schéma SpriteSheet, layout codex-pet-v1,
import manuel de la planche). Il n'y a ni kind: "character" ni schéma de personnage propre à ce
plan. Ce plan n'ajoute que le rendu :
- un bloc
sprite(props zodSpriteBlockdans@recit/core/schema) :src,layout,pose,loop. Il est déclaré pardefineBlockdans@recit/motion-blocks; c'est une fonction pure de la frame : l'image à montrer se calcule depuis les durées cumulées de la ligne, puis la case s'affiche parbackground-positionsur la planche, comme dans l'application Codex. Aucun décodage ni canvas, donc le même rendu dans le lecteur web, dans Remotion et dans une capture Playwright ; - une version statique
SpriteImagedans@recit/components(une case fixe, image 0 par défaut) pour les cartes de stories, l'export PNG (ST3) etprefers-reduced-motion(image 0 d'idle) ; - une scène utilise le personnage par les attributs communs du plan 14 (
cast=broc pose=waving) ;::sprite{character=broc pose=waving}place le sprite dans un bloc précis. Une pose absente du layout, ou un personnage sanssheet, est une erreur avec ligne ; - lecture : boucle ou une fois puis tenue, selon le tableau de correspondance du plan 14 D12,
modifiable par
loop=; - mise à l'échelle lisse (
image-rendering: auto) : la planche est en 3D, pas en pixel art. Une case de192×208affichée bien plus grand sera floue : la taille maximale sans perte se mesure sur les goldens, elle n'est pas fixée d'avance ; inferredet provenance : modèle unique du plan 14 (Trait<T>, sidecarkind: "asset").recit validate --strictrefuse une planche sans provenance ou sans licence.
Le bloc ```pixel reste pour les sprites dessinés à la main (IM1). Un Broc en pixel art n'est pas
prévu : ce serait une seconde apparence à maintenir à côté de la référence 3D (§8, question 2).
D10 — « Récupération du skill » (répondu) et « du projet basic » (bloqué)
- Le skill : c'est
hatch-pet(répondu le 25/10). La décision « génération d'images toujours hors de Récit » (plan 14, §8 question 1) s'applique :hatch-petetimagegensont lancés par l'auteur hors de Récit, ne sont jamais une dépendance, et aucun de leurs textes ou scripts n'est copié. Leurs requêtes et leurs planches sont des données du projet. L'import d'une planche est une étape manuelle documentée (plan 14 D12) ; une commanderecit import petne viendra que si des imports répétés en montrent le besoin. - Projet basic : toujours inconnu, donc aucune fonction conçue d'avance. S'il existe, il passe
par le skill
recit-import(renommage, outillage, style), avec sa ligne dans NOTICE.md. Il deviendrait alors le second consommateur qui justifie@recit/pixel(D2). - Skill produit : s'il en faut un, il est généré par la commande unique
recit skills export(plan 14, IA3.3) depuis la ressourcerecit://guide/pixel, sans second générateur. - Licence :
hatch-petest sous Apache-2.0. Comme rien n'en est repris, il n'a pas de ligne « Third-party assets » ; seul le layoutcodex-pet-v1(faits d'interopérabilité réécrits) cite sa source (§5).
D11 — Placement dans la feuille de route
Rien en P4 ni en P5 (l'import de motion est en cours). Lot avancé en P11 (IM1, IM2), parce que
le lecteur stories et l'export d'images (ST2, ST3) y arrivent et que motion est disponible depuis
P4. IM3 et IM5 sont plus tard, IM4 (débloqué le 25/10) rejoint P16 (Scenario). Détail dans
06-roadmap.md, « Backlog : plans 10 à 14 ».
4. Packages, site, exemples et tests
4.1 Packages
| Package | Groupe | Statut | Contenu | Dépendances |
|---|---|---|---|---|
@recit/components | fondations | ✏️ P11 | module pixel pur (grille, légende, rectangles fusionnés, SVG), PixelImage, version statique de TextCard ; SpriteImage (IM4) | aucune nouvelle |
@recit/core/schema | fondations | ✏️ P11 | PixelBlock, TextCardBlock ; noms pixel et textcard dans le registre de directives ; SpriteBlock et nom sprite (IM4), SpriteSheet et layout codex-pet-v1 (plan 14 D12) | zod (existant) |
@recit/motion-blocks | formats | ✏️ P11 | blocs animés pixel et textcard ; sprite (IM4) | @recit/components |
@recit/stories | formats | ✏️ P11 | textcard comme carte | @recit/motion-blocks (peer optionnel, déjà prévu) |
@recit/mcp | distribution | ✏️ P11 | ressource recit://guide/pixel ; describe_block couvre déjà les deux blocs | MCP SDK (existant, confiné ici) |
@recit/pixel | fondations | 🔒 réservé | pas de package pour l'instant ; extraction du module pixel seulement au second consommateur (D2) | aucune |
- Aucune dépendance nouvelle dans le monorepo.
- À reporter :
@recit/pixeldans la liste des noms npm à réserver de03-packaging.md§6.
4.2 Site
- Pas de nouveau sous-domaine : l'idée du backlog est rangée sous motion.recit.dev. Les images
ont une section
motion.recit.dev/images(sprites animés, cartes de texte) et une rangée sur story.recit.dev (cartes et export PNG). - La doc reste sur docs.recit.dev (règle anti-doublon), dans la section unique des blocs transverses
:
docs.recit.dev/components/pixeletdocs.recit.dev/components/textcard.
| Composant site-kit | Rôle sur motion.recit.dev/images |
|---|---|
FormatHero | un sprite qui se révèle, écrit en ```pixel, avec la source dépliable |
SourceToggle | la même carte en rendu, en Markdown et en JSON |
LocaleDemo | une textcard en en et en fr, l'image se refait avec la langue |
ComponentShowcase | PixelImage, les variantes de textcard |
ExportPanel | composant unique de @recit/site-kit, lit exports.json : PNG 1080×1920 et MP4 9:16 produits en CI, variantes kind: "png" et kind: "mp4" de ExportManifestSchema (@recit/core), ajoutées avec IM2 et ST3 |
BridgeCard | stories ⇄ motion |
TemplateCard | npm create recit -- --template stories-textcards |
DocsLinks | renvoi vers les pages de docs.recit.dev |
4.3 Exemples
| Exemple | Contenu | Langues | Phase |
|---|---|---|---|
examples/motion-pixel | sprites dessinés en ```pixel, reveal, frames ; MP4 9:16 et goldens | en, fr | lot P11 |
examples/stories-textcards | les cinq variantes de textcard, un :value de données Lorem | en, fr | lot P11 |
examples/stories-character | kit de personnage Lorem : bible à une entrée et planche synthétique codex-pet-v1 (cases numérotées, dessinées par un script de test), aucune image générée | en, fr | IM4 |
Contenu Lorem Ipsum, aucun asset tiers. Broc n'a pas de dossier propre : son kit (plan 14 D12)
est une section du dossier unique examples/scenario-lebrocoli, porté par le plan 14, avec un seul
jeu de lignes NOTICE et une seule exception à la règle Lorem.
4.4 Tests
- Unitaires (vitest, module pixel) : déterminisme (même grille → même SVG, octet pour octet), fusion des rectangles, lecture de grille et diagnostics.
- Équivalence Markdown ⇄ JSON (plan 09) sur chaque exemple.
- Goldens : cartes via Playwright (
@recit/paged/runner) ; frames motion selon le moyen de rendu des goldens de motion. - e2e :
e2e/motion.e2e.tsete2e/stories.e2e.tsétendus (captures clair et sombre, mobile,prefers-reduced-motion, en et fr), contrôle axe (role="img",alt). - Erreurs : un test par diagnostic (ligne trop courte, caractère inconnu,
statsans source, débordement par langue).
5. Sécurité, vie privée, licences
Sécurité :
- Le SVG est construit à partir de données validées : couleurs filtrées par une expression
stricte (hex ou jeton connu), entiers bornés, aucune chaîne de l'auteur insérée dans un attribut
SVG hors
alt(échappé). Pas de<foreignObject>, pas de script, pas de lien. - Plafonds de taille de grille et de nombre de frames, pour éviter un rendu ou un DOM démesuré.
- Plus tard, si
srcest retenu : fichiers locaux du projet seulement, décodés dans Chromium.
Vie privée :
- Aucun appel réseau pour produire une image. Aucune API de génération d'images dans Récit.
- Polices et palettes servies localement, sans CDN.
Droits et IA :
- La provenance et la mention IA suivent le plan 14 (sidecar
kind: "asset",aiGenerated, AI Act article 50). Ce plan n'a pas de second schéma de provenance. - Les manifestes C2PA présents dans une image d'entrée ne sont pas retirés. Aucune signature en v1.
Lignes NOTICE.md à prévoir :
| Section | Ligne |
|---|---|
| What comes from where | rien de nouveau tant que @recit/pixel n'est pas extrait (le module vit dans packages/components) |
| Third-party assets | chaque palette nommée ou police pixel embarquée (OFL seulement), plus tard |
| What comes from where | layout codex-pet-v1 : faits d'interopérabilité relevés dans hatch-pet (Apache-2.0), réécrits ; aucun texte ni script repris (ligne unique, portée par le plan 14 §5.2) |
| Third-party assets | le « projet basic », s'il est importé, avec son origine et sa licence |
| Third-party assets | algorithmes repris plus tard (mulberry32 ou sfc32, Bayer, Floyd–Steinberg) : licence de l'implémentation de référence, ou réécriture |
L'image de référence et la planche de Broc, Lexend et Unbounded (la police de titre retenue le
25/10, SIL OFL 1.1) relèvent des lignes NOTICE du dossier unique examples/scenario-lebrocoli (plan
14). L'image de référence de Broc est © vguerand, tous droits réservés : elle n'est pas couverte
par la licence MIT du dépôt, et sa ligne NOTICE le dit. Monument Extended et les vignettes gstatic
de lebrocoli.fr ne doivent jamais entrer dans le dépôt.
6. Phases
Tâches IM, lots IM1 à IM5, sous-tâches IMn.m (convention de 06, « Backlog »). Rien en P4 ni en
P5.
IM1 — Le sprite dessiné, fixe et animé (S/M), lot avancé de P11
- IM1.1 — Module pixel pur dans
@recit/components: lecture de grille et de légende, jetons de thème, grille → rectangles fusionnés → SVG. - IM1.2 — Schéma
PixelBlockdans@recit/core/schema, nompixeldans le registre. - IM1.3 — Plugin remark du bloc de code
```pixeldans le module pixel de@recit/components(le cœur n'en garde que le nom, règle B4), avec les diagnostics de §3 D3. - IM1.4 —
PixelImage(statique, SSR,role="img") et blocpixelanimé dans@recit/motion-blocks(reveal,frames, repli statique avecprefers-reduced-motion), raccourci--- type=pixel. - IM1.5 —
examples/motion-pixel(en, fr), sectionmotion.recit.dev/images, pagedocs.recit.dev/components/pixel, ressourcerecit://guide/pixel. - Tests : déterminisme, diagnostics, équivalence Markdown ⇄ JSON, goldens, MP4
9:16.
Sortie : examples/motion-pixel se joue sur le web et produit un MP4 9:16 identique aux
goldens ; la même grille donne deux fois le même SVG ; aucune dépendance ajoutée.
IM2 — textcard : plan motion puis carte stories (M), lot avancé de P11 (avec ST2, ST3)
- IM2.1 — Schéma
TextCardBlock, directive:::textcard, raccourci--- type=textcard. - IM2.2 — Bloc
textcarddans@recit/motion-blocks(plan9:16d'abord) : variantes, ajustement du texte, fondsthemeetgradient. Version statique dans@recit/components. - IM2.3 — La même
textcardcomme carte dans@recit/stories, export PNG1080×1920par ST3. - IM2.4 — Lints :
statsans source, densité par langue. - IM2.5 —
examples/stories-textcards(en, fr), rangée sur story.recit.dev, pagedocs.recit.dev/components/textcard, gabaritstories-textcards. - Tests : goldens des cinq variantes en
enetfr, e2e (tap, clavier), débordement enfr.
Sortie : une même source .md donne le lecteur stories, un MP4 vertical et un PNG par carte,
dans chaque langue, sans dépendance native ajoutée.
IM3 — Extensions (plus tard, sur besoin réel)
- Générateurs
::pixel{gen=sprite|noise seed=…}, fond de scènebackground=pixel:<seed>,reveal=dissolve,cycle. - Préréglages
1080×1080et1200×630, zones sûres versionnées, filigranemark. -
--frame/still=parrenderStill(@recit/motion-render). - Ponts
report → storiesetbook → stories, ajoutés à08-formats.md§0. - Lint de contraste de
textcard.
IM4 — Personnage en sprites (M), P16 (débloqué le 25/10)
Débloqué par les réponses du 25/10 (pets.md) : « Pet » = hatch-pet, personnage Broc. La
publication du kit de Broc attend encore la licence de l'image de référence (06, question 19) ; le
repli Lorem avance sans elle.
- IM4.1 — Bloc
sprite(SpriteBlock) dans@recit/motion-blocksetSpriteImagedans@recit/components, résolus depuis la bible et le layoutcodex-pet-v1(plan 14 D12). - IM4.2 —
examples/stories-characteren Lorem, avec une planche synthétique ; puis le sprite de Broc dansexamples/scenario-lebrocoli(plan 14, IA5), après accord sur la licence. - Tests : image calculée pour chaque frame (durées cumulées), boucle et tenue, image 0 d'
idleenprefers-reduced-motion, pose absente (erreur avec ligne), planche de mauvaise taille (« layout inconnu »), trait inféré refusé parrecit validate --strict.
Sortie : le personnage d'exemple joue chacune des 9 poses dans une story et dans un plan motion depuis la seule bible, et aucun trait inféré ni aucune planche sans provenance n'est publié.
IM5 — Options (après P12)
- Image source réduite et tramée (
src+dither), traitée dans Chromium seulement. - Bouton « Télécharger PNG » par
OffscreenCanvas. - Palettes nommées et police pixel, après vérification des licences.
- Extraction de
@recit/pixelsi un second consommateur apparaît.
Sortie : chaque option a son exemple, ses goldens et sa ligne NOTICE.md.
7. Risques
| Risque | Parade |
|---|---|
| Les blocs motion d'emaki dépendent de Remotion au rendu web | vérifier après P4-V1 qu'ils se rendent avec un numéro de frame fourni par le lecteur web ; la version statique de textcard vit déjà dans @recit/components |
| Texte qui déborde dans une langue | ajustement par dichotomie, erreur de densité avec la langue et la ligne |
| Goldens instables entre macOS et Linux (polices, anticrénelage) | rendu en conteneur (déjà prévu au plan 05) ; les sprites en crispEdges sont peu sensibles |
| Grilles très grandes, DOM lourd | fusion des rectangles identiques ; plafond de taille fixé après mesure, pas à l'avance |
| Fonctions conçues avant de connaître le « projet basic » | D10 bloqué pour le projet basic ; hatch-pet reste un outil externe, rien n'en est réimplémenté |
Planche floue une fois agrandie (cases de 192×208) | taille maximale mesurée sur les goldens ; Broc reste petit dans le cadre, ou on régénère une planche plus grande quand un layout le permet |
| Mascotte réelle utilisée sans droits clairs | dossier unique du plan 14, conditionné à l'accord ; repli Lorem |
| Un chiffre affiché sans source dans une carte | variant=stat exige :value ou source= |
Confusion avec :::card du deck | nom textcard, documenté dans le glossaire |
| Dérive vers un éditeur graphique | hors périmètre : la grille s'écrit en texte |
8. Questions ouvertes
Les questions communes au backlog (« Pet », « le skill », « le projet basic », la mascotte, le nom
et la voix du personnage, la police de titre, la mention IA, l'exception lebrocoli.fr) sont
regroupées une seule fois dans 06-roadmap.md, « Questions ouvertes ». « Pet », « le skill » et le
nom du personnage y sont répondus depuis le 25/10 (pets.md). Restent ici :
Les questions 1, 3, 4, 5 et 6 ont été tranchées le 24/10/2026, sans l'utilisateur et à sa demande
(décisions réversibles). La question 2 est répondue le 25/10 par pets.md.
« Format Text » : un nouveau format fait de cartes de texte seules, ou une carte de texte (Tranché (24/10) :textcard) dans les formatsmotionetstories?textcardest un bloc demotionetstories, pas un nouveau format (D1).Le brocoli en pixel art : grille dessinée à la main (versionnée), ou dérivée de la mascotte ? Qui valide la palette ?Répondu (25/10, pets.md) : ni l'un ni l'autre. Broc est en 3D ; ses poses viennent de la planchehatch-petgénérée depuis son image de référence (D9). L'utilisateur valide les poses sur la planche contact et les GIF dehatch-pet, avant l'import. Pas de Broc en pixel art.Package : garder le module pixel dansTranché (24/10) : le module reste dans@recit/components, ou créer la fondation tout de suite ?@recit/componentstant qu'il n'a pas de second consommateur ; pas de package@recit/pixelpour l'instant (nom réservé).Nom de la directive texte-image :Tranché (24/10) :textcardconvient-il ?textcardest retenu.Portée v1 :Tranché (24/10) : v1 limitée à motion et stories.pixelettextcarddans motion et stories seulement, ou aussi dans deck et docs dès P11 ?Placement : lot avancé en P11, le reste plus tard ou bloqué : d'accord ?Tranché (24/10) : oui (ordre du backlog accepté, 06 question 14).