Nouveaux blocs et outils

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 format 9:16 ou 1:1, mise en page autour des blocs emaki existants (statement, title, stat, list).

Ce que cela apporte à « écrire une fois, raconter partout » :

Sans ces blocsAvec ces blocs
une story Instagram demande un outil graphique et un PNG par languela 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 licenceun 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 logiciella mascotte est une grille versionnée, avec des poses en frames, animée par motion
les goldens dépendent d'images externestout 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 fichiersla 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.ts appelle renderStill de @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œud vector (chemins SVG). Les blocs se déclarent par defineBlock({ type, props: zod, text }).
  • Les thèmes emaki exposent des jetons colors (bg, surface, text, muted, accent), data (peach, mint…), fonts et type (tailles). C'est la palette naturelle d'un sprite.
  • @recit/cli déclare déjà playwright en peer dépendance optionnelle pour recit export --to pdf. Le plan 15 déplace ce pair dans @recit/paged, dont le sous-export @recit/paged/runner est 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 images doit produire un PNG 1080×1920 par carte (ST3), et le pont stories ⇄ motion (ST2) est déjà promis. :::card est déjà une directive du deck. sharp est désactivé dans allowBuilds et 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_block sert déjà de catalogue de schémas.
  • Aucun skill « brocoli » ni « projet basic » n'a été trouvé dans emaki, dans .claude/skills (seulement recit-import et upstream-sync), dans sites/plans/pages/ ou dans context.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

TechniqueNode sans binaire natifVerdict 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 + resvgoui en wasm, natif sinonrejeté : 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 PDFretenu pour les cartes statiques : fidélité exacte au lecteur web, appelé par @recit/paged/runner
Encodeurs PNG en JS (pngjs, UPNG.js, fast-png)ouiinutile 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, cases 192×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'idle est l'image fixe de prefers-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 (outil image_gen de l'hôte, ou API OpenAI sur demande explicite). hatch-pet et imagegen restent 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

3. Décisions proposées

#SujetRecommandation
D1Nouveau format ou nouveaux blocs ?Tranché (24/10, question 1) : deux blocs, pas de format. Le format reste motion ou stories
D2Où 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é)
D3Syntaxe pixel en v1le bloc de code ```pixel (grille + légende → SVG). Générateurs, fonds générés et images source : plus tard
D4Syntaxe textcardune directive conteneur :::textcard{variant=…} qui habille un bloc emaki existant ; d'abord un plan motion 9:16, puis une carte stories
D5Schéma JSONPixelBlock et TextCardBlock dans @recit/core/schema. En v1, les blocs restent des directives dans le body (règle de 06)
D6Scène = ?inchangée : un plan en motion, une carte en stories. Raccourci motion --- type=pixel / type=textcard
D7RasterisationPlaywright pour les cartes (déjà prévu par ST3), par @recit/paged/runner. renderStill pour une frame animée : plus tard
D8Palettes et policespalette theme par défaut. Palettes nommées et police pixel seulement sous licence libre, inscrites dans NOTICE.md
D9Personnage (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)
D10Reprise 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é
D11Placement dans la feuille de routelot 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

OptionPourContre
A. Tout dans @recit/motion-blocksaucun package de plusdeck et docs devraient installer les blocs motion pour une image fixe
B. Module pur dans @recit/components, rendu animé dans motion-blocksaucun 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épartréutilisable hors de Récitun 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 de TextCard vivent dans @recit/components. Une fondation ne dépend jamais d'un format : deck, docs et report n'installent donc pas @recit/motion-blocks pour une image fixe ou une carte de texte.
  • Les blocs animés pixel et textcard vivent dans @recit/motion-blocks, qui importe le module de @recit/components.
  • Pas de package @recit/pixel pour 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/components apparaî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.

MarkdownMarkdown
--- title: Lorem Sprites format: motion lang: en aspect: 9:16 --- --- id=hand-drawn type=pixel ```pixel id=sprout alt="Lorem sprout, waving" scale=auto fps=4 reveal=scan . = transparent k = text g = accent m = data.mint ...kk... ..kggk.. .kggggk. ..kmmk.. ...kk... ...kk... ..kggk.k .kggggk. ..kmmk.. ...kk... ``` Dolor sit amet.
ÉlémentRô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égendejetons de thème (text, accent, data.mint) ou couleurs hex. Un sprite écrit avec des jetons suit le thème et le mode sombre
scalefacteur d'affichage entier (auto = le plus grand entier qui tient)
fpscadence des frames
revealanimation d'apparition en motion : scan, none
alttexte 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 ;
  • alt manquant : 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 :

MarkdownMarkdown
--- title: Lorem Quotes format: stories lang: en duration: 6s --- --- id=quote :::textcard{variant=quote background=theme} Lorem ipsum dolor sit amet, consectetur adipiscing elit. — Lorem Ipsum ::: --- id=headline :::textcard{variant=title background=gradient} # Dolor sit amet consectetur adipiscing ::: --- id=figure :::textcard{variant=stat} :value{data=sales field=revenue agg=sum format=currency} dolor sit amet this quarter ::: --- id=steps :::textcard{variant=list background=theme} - Lorem ipsum - Dolor sit - Amet consectetur :::
VarianteBloc emaki habilléContenu attendu
quotestatementun paragraphe, puis une ligne — attribution facultative
titletitleun titre, puis un sous-titre facultatif
statstatun :value de @recit/data, ou un nombre littéral avec source=
captionstatementun paragraphe court
listlistune 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=stat sans :value ni source= : 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 alt en 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 :

TypeScriptTypeScript
type PixelBlock = { type: "pixel"; id?: string; props: { grid: string[] | string[][]; // rows, or frames of rows legend: Record<string, string>; // char -> theme token or hex scale?: number | "auto"; fps?: number; reveal?: "scan" | "none"; alt: Localized<string>; }; }; type TextCardBlock = { type: "textcard"; id?: string; props: { variant: "quote" | "title" | "stat" | "caption" | "list"; body: Localized<string>; // Markdown of the inner block attribution?: Localized<string>; source?: string; // required for a literal stat background?: "theme" | "gradient" | { src: string; alt: Localized<string> }; aspect?: "9:16" | "1:1"; }; };

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 :

JSONJSON
{ "$schema": "https://recit.dev/schema/v1/stories.json", "kind": "story", "version": 1, "format": "stories", "meta": { "title": { "en": "Lorem Quotes", "fr": "Citations Lorem" } }, "scenes": [ { "id": "quote", "body": { "en": ":::textcard{variant=quote}\nLorem ipsum dolor sit amet.\n\n— Lorem Ipsum\n:::", "fr": ":::textcard{variant=quote}\nLorem ipsum dolor, en français.\n\n— Lorem Ipsum\n:::" } } ] }
  • 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? dans SceneSource serait 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.

Formatpixeltextcard
motionbloc 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)
storiesimage de carte (lot P11)cas principal : une carte = une textcard ; export PNG 1080×1920 (ST3, lot P11)
deckplus tard, hors v1 (question 5) : image fixe (PixelImage, @recit/components)plus tard, hors v1 (question 5) : slide pleine, version statique
docsplus tard, hors v1 (question 5) : image fixe dans une page ou en MDXplus tard, hors v1 (question 5) : encart de citation ou de chiffre, version statique
autresplus tard (report, book, toon), sur besoin réelplus 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é avec recit 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 attribut data-ready propre. Préréglage v1 : 1080×1920, déjà dans le plan 08.
  • Plus tard : frame d'une scène animée (--frame <n> ou still=<n>) par renderStill de @recit/motion-render, seulement si ce package est installé ; préréglages 1080×1080 et 1200×630 ; bouton « Télécharger PNG » par OffscreenCanvas.
  • Exclus : satori, resvg, @napi-rs/canvas, node-canvas, et sharp hors 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.
  • textcard utilise 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 zod SpriteBlock dans @recit/core/schema) : src, layout, pose, loop. Il est déclaré par defineBlock dans @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 par background-position sur 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 SpriteImage dans @recit/components (une case fixe, image 0 par défaut) pour les cartes de stories, l'export PNG (ST3) et prefers-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 sans sheet, 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 de 192×208 affichée bien plus grand sera floue : la taille maximale sans perte se mesure sur les goldens, elle n'est pas fixée d'avance ;
  • inferred et provenance : modèle unique du plan 14 (Trait<T>, sidecar kind: "asset"). recit validate --strict refuse 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-pet et imagegen sont 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 commande recit import pet ne 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 ressource recit://guide/pixel, sans second générateur.
  • Licence : hatch-pet est sous Apache-2.0. Comme rien n'en est repris, il n'a pas de ligne « Third-party assets » ; seul le layout codex-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

PackageGroupeStatutContenuDépendances
@recit/componentsfondations✏️ P11module pixel pur (grille, légende, rectangles fusionnés, SVG), PixelImage, version statique de TextCard ; SpriteImage (IM4)aucune nouvelle
@recit/core/schemafondations✏️ P11PixelBlock, 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-blocksformats✏️ P11blocs animés pixel et textcard ; sprite (IM4)@recit/components
@recit/storiesformats✏️ P11textcard comme carte@recit/motion-blocks (peer optionnel, déjà prévu)
@recit/mcpdistribution✏️ P11ressource recit://guide/pixel ; describe_block couvre déjà les deux blocsMCP SDK (existant, confiné ici)
@recit/pixelfondations🔒 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/pixel dans la liste des noms npm à réserver de 03-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/pixel et docs.recit.dev/components/textcard.
Composant site-kitRôle sur motion.recit.dev/images
FormatHeroun sprite qui se révèle, écrit en ```pixel, avec la source dépliable
SourceTogglela même carte en rendu, en Markdown et en JSON
LocaleDemoune textcard en en et en fr, l'image se refait avec la langue
ComponentShowcasePixelImage, les variantes de textcard
ExportPanelcomposant 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
BridgeCardstories ⇄ motion
TemplateCardnpm create recit -- --template stories-textcards
DocsLinksrenvoi vers les pages de docs.recit.dev

4.3 Exemples

ExempleContenuLanguesPhase
examples/motion-pixelsprites dessinés en ```pixel, reveal, frames ; MP4 9:16 et goldensen, frlot P11
examples/stories-textcardsles cinq variantes de textcard, un :value de données Loremen, frlot P11
examples/stories-characterkit 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éeen, frIM4

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.ts et e2e/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, stat sans 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 src est 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 :

SectionLigne
What comes from whererien de nouveau tant que @recit/pixel n'est pas extrait (le module vit dans packages/components)
Third-party assetschaque palette nommée ou police pixel embarquée (OFL seulement), plus tard
What comes from wherelayout 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 assetsle « projet basic », s'il est importé, avec son origine et sa licence
Third-party assetsalgorithmes 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 PixelBlock dans @recit/core/schema, nom pixel dans le registre.
  • IM1.3 — Plugin remark du bloc de code ```pixel dans 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 bloc pixel animé dans @recit/motion-blocks (reveal, frames, repli statique avec prefers-reduced-motion), raccourci --- type=pixel.
  • IM1.5 — examples/motion-pixel (en, fr), section motion.recit.dev/images, page docs.recit.dev/components/pixel, ressource recit://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 textcard dans @recit/motion-blocks (plan 9:16 d'abord) : variantes, ajustement du texte, fonds theme et gradient. Version statique dans @recit/components.
  • IM2.3 — La même textcard comme carte dans @recit/stories, export PNG 1080×1920 par ST3.
  • IM2.4 — Lints : stat sans source, densité par langue.
  • IM2.5 — examples/stories-textcards (en, fr), rangée sur story.recit.dev, page docs.recit.dev/components/textcard, gabarit stories-textcards.
  • Tests : goldens des cinq variantes en en et fr, e2e (tap, clavier), débordement en fr.

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ène background=pixel:<seed>, reveal=dissolve, cycle.
  • Préréglages 1080×1080 et 1200×630, zones sûres versionnées, filigrane mark.
  • --frame / still= par renderStill (@recit/motion-render).
  • Ponts report → stories et book → 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-blocks et SpriteImage dans @recit/components, résolus depuis la bible et le layout codex-pet-v1 (plan 14 D12).
  • IM4.2 — examples/stories-character en Lorem, avec une planche synthétique ; puis le sprite de Broc dans examples/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'idle en prefers-reduced-motion, pose absente (erreur avec ligne), planche de mauvaise taille (« layout inconnu »), trait inféré refusé par recit 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/pixel si un second consommateur apparaît.

Sortie : chaque option a son exemple, ses goldens et sa ligne NOTICE.md.

7. Risques

RisqueParade
Les blocs motion d'emaki dépendent de Remotion au rendu webvé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 langueajustement 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 lourdfusion 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 clairsdossier unique du plan 14, conditionné à l'accord ; repli Lorem
Un chiffre affiché sans source dans une cartevariant=stat exige :value ou source=
Confusion avec :::card du decknom textcard, documenté dans le glossaire
Dérive vers un éditeur graphiquehors 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.

  1. « Format Text » : un nouveau format fait de cartes de texte seules, ou une carte de texte (textcard) dans les formats motion et stories ? Tranché (24/10) : textcard est un bloc de motion et stories, pas un nouveau format (D1).
  2. 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 planche hatch-pet générée depuis son image de référence (D9). L'utilisateur valide les poses sur la planche contact et les GIF de hatch-pet, avant l'import. Pas de Broc en pixel art.
  3. Package : garder le module pixel dans @recit/components, ou créer la fondation tout de suite ? Tranché (24/10) : le module reste dans @recit/components tant qu'il n'a pas de second consommateur ; pas de package @recit/pixel pour l'instant (nom réservé).
  4. Nom de la directive texte-image : textcard convient-il ? Tranché (24/10) : textcard est retenu.
  5. Portée v1 : pixel et textcard dans motion et stories seulement, ou aussi dans deck et docs dès P11 ? Tranché (24/10) : v1 limitée à motion et stories.
  6. Placement : lot avancé en P11, le reste plus tard ou bloqué : d'accord ? Tranché (24/10) : oui (ordre du backlog accepté, 06 question 14).