<!-- Binksy LogoNext — mode d'emploi des agents IA / AI agent guide. Source : docs/agents/AGENTS.{fr,en}.md -->

# BINKSY LOGONEXT — MODE D'EMPLOI POUR AGENTS IA

Ce document s'adresse aux agents IA (Claude Code, Codex, ou tout agent qui pilote un navigateur via Playwright / CDP, ou qui exécute du JavaScript dans la page). Il couvre tout l'outil : import d'un logo SVG ou d'un projet `.binksy`, réglages (marque, palette, variantes, formats, versions, QR code), export du ZIP et du projet `.binksynext`, et l'API publique `window.multiformats`.

Il existe en trois endroits, toujours identiques : `/AGENTS.md` (Markdown, français puis anglais), la page `/agent` et l'écran **Agents IA** de l'outil (`#/agents`, pied de l'accueil, barre latérale).

Points d'entrée découvrables sans JavaScript : la page `/` déclare `<link rel="alternate" type="text/markdown" href="AGENTS.md">`, `<link rel="help" href="agent/">` et `<link rel="alternate" type="text/plain" href="llms.txt">`, affiche un `<noscript>` qui renvoie ici, et écrit une seule ligne `console.info` au chargement. `/robots.txt` autorise tout et cite `/llms.txt` et `/AGENTS.md`.

---

# 1. RÈGLE FONDAMENTALE

Binksy LogoNext est un **outil de déclinaison pour graphistes**, pas un générateur de logos.

* Le graphiste apporte son logo (SVG, `.binksy` ou `.binksynext`). C'est la **source de référence**. N'invente jamais un logo, un symbole ou une couleur de marque ; ne redessine pas un SVG fourni.
* Sans lien fourni par le graphiste, **pas de QR code** : ne fabrique pas d'URL.
* Aucune clé API, aucun fournisseur d'IA, aucun serveur : tout est calculé dans l'onglet. C'est toi, l'agent, qui décides et vérifies ; l'outil fournit les fonctions.
* Pas de doublon avec **Binksy LogoKit** : les fichiers de base (SVG, PNG HD, PDF, JPEG) se font là-bas. Ici : formats spécialisés seulement.

Après chaque opération : vérifie la valeur réellement enregistrée (`await multiformats.state()`), puis le résultat (`export` → `files`, `kit.read`). **Un appel ou un clic n'est pas une preuve de réussite.**

---

# 2. DÉMARRER

## Lancer l'outil

* C'est un seul fichier `index.html` (plus `AGENTS.md`, `llms.txt`, `robots.txt`, `agent/`). Servir le dossier : `python3 -m http.server 8792` puis http://127.0.0.1:8792/ — ou l'adresse publique fournie.
* `file://` fonctionne aussi (l'API est la même). Ajoute `?fresh` à l'adresse pour ignorer la session enregistrée (localStorage `lmf-session`).
* Routes (hash) : `#/` accueil · `#/outil` l'outil · `#/docs` documentation · `#/agents` cette page (`#/agents/en` en anglais). L'API marche quelle que soit la route ; `load.*` et `export` ne changent pas de page.

## Deux façons de piloter

1. **API JavaScript** (recommandé) : `window.multiformats` dans la page, aussi accessible sous **`window.logonext`** (même objet ; l'outil s'appelle désormais Binksy LogoNext, le nom `multiformats` reste pour la compatibilité). Chaque méthode agit par les mêmes fonctions que l'interface et renvoie du JSON.
2. **Clics** : les commandes clés portent un attribut stable `data-agent` (voir § 8).

```js
await multiformats.ready();          // attend la démo AVORA ou la session
multiformats.version;                // "2.4"
multiformats.help();                 // [{path, signature, description}]
multiformats.help('load');           // un groupe
await multiformats.state();          // tout l'état
await multiformats.catalog();        // groupes, formats, tailles, modes, règle des noms
```

## Conventions de l'API

* Toutes les méthodes (sauf `help`) sont **async** et renvoient des données JSON simples.
* Erreur = exception claire : `multiformats.<chemin> : <cause> (valeurs autorisées)`. La validation se fait avant d'appliquer : rien n'est appliqué à moitié.
* Entrées de fichiers souples : `Blob`/`File`, `ArrayBuffer`, `Uint8Array`, data URL, base64, ou texte SVG.
* Exports : renvoient `{name, type, size, …}` ; ajoute `{base64: true}` pour recevoir le contenu, `{download: true}` pour déclencher le téléchargement du navigateur. Chaque fichier produit reste disponible dans `multiformats.files` (`list()`, `get(nom)`, `base64(nom)`, `download(nom)`), et chaque fichier du dernier ZIP dans `multiformats.kit` (`list()`, `read(chemin)`).
* Chaque réglage est enregistré dans la session (localStorage) comme depuis l'interface.

---

# 3. PARCOURS TYPE

```js
await multiformats.ready();
// 1. le logo (1 à 8 versions) — ou load.binksy(…)
await multiformats.load.svg([
  { svg: iconeSvgText, name: 'avora-icone', type: 'icone' },
  { svg: horizontalSvgText, name: 'avora-horizontal', type: 'horizontal' },
], { brand: 'AVORA' });
// 2. palette (facultative), mode, formats
await multiformats.setPalette([{ hex: '#ff5500', name: 'Orange', role: 'accent' }, { hex: '#111111', name: 'Noir', role: 'text' }]);
await multiformats.setMode('essentiel');
await multiformats.preset('site');                                   // ou setFormats({...})
await multiformats.setFormats({ 'g:ticket': true, webm: false });
// 3. QR (seulement avec un lien donné par le graphiste)
await multiformats.setQR({ url: 'https://avora.studio', logo: true, modules: 'arrondi', eyes: 'arrondi' });
// 4. contrôle avant export
const plan = await multiformats.plan();                              // {mode, tasks, folders[]}
// 5. export
const kit = await multiformats.export({ base64: true });             // {name, size, count, files[], base64}
const readme = await multiformats.kit.read('avora-kit/LISEZMOI.txt', { text: true });
const bx = await multiformats.exportBinksynext({ base64: true });   // avora.binksynext, réouvrable ici (renommé .binksy : LogoKit)
```

Avec Playwright (Python), le contenu d'un fichier local se passe en base64 :

```python
import base64
svg = open('logo.svg').read()
page.goto('http://127.0.0.1:8792/#/outil')
page.wait_for_function('window.multiformats')
page.evaluate('multiformats.ready()')
page.evaluate('(s) => multiformats.load.svg(s, {brand: "AVORA"})', svg)
bx = base64.b64encode(open('projet.binksy', 'rb').read()).decode()
page.evaluate('(b) => multiformats.load.binksy(b, {name: "projet.binksy"})', bx)
r = page.evaluate('multiformats.export({base64: true, list: false})')
open(r['name'], 'wb').write(base64.b64decode(r['base64']))
```

Ou par l'interface : `page.set_input_files('#file', 'logo.svg')` (SVG) et `page.set_input_files('#bxFile', 'projet.binksy')` (accepte `.binksy` et `.binksynext`).

---

# 4. RÉFÉRENCE DE L'API

| Méthode | Rôle | Retour |
| --- | --- | --- |
| `ready({timeout?})` | Attend que l'outil soit prêt | `{version, api, route, hasLogo}` |
| `help(prefix?)` | Signatures (synchrone) | `[{path, signature, description}]` |
| `state()` | État complet | voir ci-dessous |
| `catalog()` | Groupes et formats (usage, où, tailles, dossier, couleurs Essentiel), modes, préréglages, types de version, rôles, règle des noms | objet |
| `plan({mode?})` | Ce que l'export produira, sans rien rendre | `{mode, tasks, folders:[{format, group, version, versionName, color, colorName, folder}]}` |
| `route(name?)` | Lit ou change la page (`accueil`, `outil`, `docs`, `agents`) | route |
| `load.svg(input, {replace=true, brand?, palette?, name?, type?})` | Charge 1 à 8 versions. `input` : un SVG ou une liste ; un élément peut être `{svg, name, type}` | `versions` |
| `load.binksy(data, {name?})` | Ouvre un `.binksy` (LogoKit, v2 ZIP ou ancien JSON) ou un `.binksynext` (cet outil) ; le contenu est détecté, l'extension ne compte pas | `{brand, versions, palette, source, restored?, state}` |
| `load.demo()` | Démo AVORA : 4 versions (Horizontal, Vertical, Icône, Logotype) et palette nommée de 4 couleurs (Noir/Texte, Orange/Accent, Craie/Fond, Marron/Texte secondaire), comme l'import de son .binksy ; `state().isDemo` = `true`. Le prochain `load.svg` (remplacement) ou `load.binksy` la remplace, palette comprise | `versions` |
| `addVersions(input, {name?, type?})` | Ajoute des versions | `versions` |
| `setVersion(id, {name?, type?})` / `removeVersion(id)` | Renomme, retype, retire | version / `versions` |
| `setBrand(name)` | Nom de la marque (préfixe des fichiers) | `{brand, slug}` |
| `setMode('essentiel' \| 'complet')` | Contenu du ZIP | mode |
| `setPalette(list)` | Palette, 8 max : `["#hex"]` ou `[{hex, name?, role?}]` | `palette` |
| `setVariants({cle: bool})` | Coche / décoche des variantes de couleur | `variants` |
| `preset(id)` | `site`, `shop`, `app`, `all` | ids des formats actifs |
| `setFormats({id \| "g:groupe": bool})` | Active / coupe formats et groupes | `[{id, on, available}]` |
| `setFormatVersions(formatId, ids \| null)` | Versions d'un format ; `null` = conseil | `{format, versions, custom}` |
| `setQR({url?, fg?, bg?, logo?, modules?, eyes?, quiet?})` | QR code | `qr` |
| `setSettings({bg?, size?, gradMode?, knock?, darkMode?, thermal?})` | Réglages | `settings` |
| `export({mode?, base64?, download?, list?})` | Fabrique le ZIP | `{name, type, size, mode, count, ms, errors, files, base64?}` |
| `exportBinksynext({base64?, download?})` | Projet `marque.binksynext` (format `.binksy` v2, type `application/x-binksynext`) | `{name, type, size, base64?}` |
| `exportBinksy(…)` | Ancien nom de `exportBinksynext` (même fichier `.binksynext`) | idem |
| `kit.list()` / `kit.read(path, {text?})` | Fichiers du dernier ZIP | `[{path, size}]` / `{path, name, type, size, base64 \| text}` |
| `files.list() / get / base64 / download` | Fichiers produits (ZIP, .binksynext) | |

Valeurs autorisées :

* Types de version : `icone`, `horizontal`, `vertical`, `logotype`, `monogramme` (deviné au ratio si absent).
* Rôles de palette (clés LogoKit) : `text`, `secondary-text`, `accent`, `background`, `primary`, `secondary`… (liste exacte : `catalog().paletteRoles`).
* Variantes (`state().variants[].key`) : `original`, `noir`, `blanc`, `gris`, `sombre`, `aplat`, `map`, `mono-<hex>`, `inv-<hex>`, duos… — selon le logo et la palette.
* Formats : `favicon-ico`, `favicon-svg`, `apple-touch`, `android-chrome`, `maskable`, `manifest`, `og`, `ios`, `android`, `play`, `macos`, `windows`, `bmp58`, `bmp80`, `zpl`, `cut`, `eps`, `profiles`, `discord`, `slack`, `banners`, `visio`, `signature`, `bimi`, `watermark`, `qr`, `webm`. Groupes : `g:web`, `g:apps`, `g:ticket`, `g:fab`, `g:social`, `g:qr`, `g:video`.
* QR : `modules` `carre` \| `arrondi` \| `points` ; `eyes` `carre` \| `arrondi` \| `rond` ; `quiet` 2 à 8 ; `fg`/`bg` en `#rrggbb`. Sans `url`, aucun QR (le format `qr` est `available: false`). Contraste < 4:1 → `safe: false` et l'export retombe en noir sur blanc.
* Réglages : `bg` `auto` ou `#rrggbb` ; `size` 0.6 à 1 ; `gradMode` `keep` \| `avg` \| `mono` ; `knock` booléen (blanc = trou) ; `darkMode` `plate` \| `outline` ; `thermal` `{mode: threshold|floyd|atkinson, thr: 30..225}`.
* `webm` n'est `available` que si le navigateur sait enregistrer du WebM (pas Safari) ; il est coupé par défaut (3 s d'enregistrement en temps réel par couleur).

`state()` renvoie : `{app, version, api, route, ready, brand, slug, isDemo, mode, preset, versions:[{id, name, type, folder, kind, width, height, colors, warnings, legibleAt16px}], palette:[{hex, name, role}], variants:[{key, name, folder, group, on, locked, background, ink}], groups:[{id, name, folder, on}], formats:[{id, group, name, on, available, folder, versions, customVersions, essentiel}], qr:{url, text, active, fg, bg, logo, modules, eyes, quiet, contrast, safe}, settings:{…}, webm, binksyImport}`.

---

# 5. LE FORMAT .binksy ET `modules.multiformats`

Le `.binksy` est le fichier projet de Binksy LogoKit. Cet outil enregistre le **même format** sous l'extension **`.binksynext`** (pour le distinguer d'un projet LogoKit) ; à l'import, `.binksy` et `.binksynext` sont acceptés tous les deux (y compris les anciens `.binksy` enregistrés ici). Pour ouvrir un `.binksynext` dans LogoKit, renomme-le en `.binksy`. Version 2 = archive ZIP :

* `manifest.json` : `{format: "binksy", version: 2, kind: "multiformats" (export de cet outil seulement ; ignoré par LogoKit), app, appVersion, createdAt, savedAt, projectId, name, modules: {…, multiformats: true}, files: [{path, id, size, sha256}], notEmbedded: []}`.
* `project.json` : `{format: "binksy-project", version: 2, id, name, brand, createdAt, updatedAt, assets, roles, extras, palette, customColorRoles, typography, variants, mockups, modules, missing}`.
  * `assets[id]` : `{id, hash, mime: "image/svg+xml", size, kind: "svg", name, w, h, origin: "multiformats", file: "assets/svg/<id>.svg", embedded: true}` ; `id` = `a` + 20 premiers caractères du SHA-256 du SVG.
  * `roles` : `{logo, icon, wordmark}` (icône = Icône sinon Monogramme ; logotype = Logotype ; logo = Horizontal sinon Vertical sinon une autre version) ; les autres versions dans `extras`.
  * `variants` : `[{id: horizontal|vertical|icon|wordmark|mf-N, name, asset, kind: composition|icon|wordmark}]`.
  * `palette` : `[{id, name, hex, role}]` (rôles en clés LogoKit ; rôles personnalisés listés dans `customColorRoles`).
* `assets/svg/<id>.svg` : le SVG d'origine de chaque version.

À l'import, l'outil lit : `brand` (sinon `name`), `variants[]` sinon `roles` (+ `extras`), sinon l'ancien `modules.logokit` ; `palette[]` (8 max) ; les anciens `.binksy` JSON V1–V5 (versions recomposées comme LogoKit). Le lecteur ZIP ne lit que l'index central et les fichiers utiles (deflate via `DecompressionStream`), ZIP64 compris.

## `modules.multiformats`

Ajouté par « Exporter en .binksynext » ; LogoKit le conserve tel quel. S'il est présent à l'import, **tout l'état revient** (et seules les versions sont lues dans `assets`).

```json
{
  "app": "Binksy LogoNext", "v": 1, "savedAt": "2026-10-29T10:00:00.000Z",
  "session": {
    "v": 2,
    "shapes": [{ "name": "Icône", "type": "icone", "file": "avora-icone", "asset": "a1b2c3…" }],
    "kitName": "AVORA", "isDemo": false, "mode": "essentiel", "preset": "custom",
    "palette": ["#ff5500", "#111111"],
    "palLabels": { "#ff5500": { "name": "Orange", "role": "Accent" } },
    "map": {}, "mapTouched": false,
    "off": ["gris"], "fmtOff": ["g:video", "webm"], "fmtShapes": { "banners": [1] },
    "ctxVar": "auto", "gradMode": "keep", "knock": true, "darkMode": "plate",
    "bg": "auto", "size": 0.8, "thermal": { "mode": "atkinson", "thr": 128 },
    "qr": { "url": "https://avora.studio", "fg": "#111111", "bg": "#ffffff", "logo": true, "modules": "carre", "eyes": "carre", "quiet": 4 }
  }
}
```

* `shapes[].asset` pointe vers `assets[id]` ; `fmtShapes` donne, par format, les **index** des versions choisies.
* `off` : clés de variantes décochées ; `fmtOff` : formats (`id`) et groupes (`g:<groupe>`) coupés.
* `palLabels[hex].role` est le libellé français (Texte, Accent, Fond…) ; dans `project.json`, `palette[].role` porte la clé LogoKit.

---

# 6. LE ZIP

Nom : `<marque>-kit-<mode>.zip` (ex. `avora-kit-essentiel.zip`). Racine : `<marque>-kit/`.

```
<marque>-kit/
  LISEZMOI.txt                                    UTF-8 avec BOM, CRLF
  1-web/favicons/[<version>/]<couleur>/           favicon.ico, <m>_favicon_<c>.svg, apple-touch-icon.png,
                                                  <m>_android-chrome-192x192_<c>.png, …-512x512…, <m>_maskable-192x192_<c>.png,
                                                  …-512x512…, site.webmanifest, <m>_a-coller-dans-head_<c>.html
  1-web/image-de-partage/…                        <m>_og-image-1200x630_<c>.png
  2-apps/ios/…/AppIcon.appiconset/                AppIcon-1024.png, AppIcon-1024-dark.png, AppIcon-1024-tinted.png, Contents.json
  2-apps/android/…/res/                           mipmap-{mdpi…xxxhdpi}/ic_launcher{,_round,_foreground,_background,_monochrome}.png,
                                                  mipmap-anydpi-v26/ic_launcher{,_round}.xml, values/ic_launcher_background.xml
  2-apps/google-play/…  macos/…  windows/…        <m>_google-play-512_<c>.png, <m>_macos_<c>.icns, <m>_macos-1024_<c>.png, <m>_windows_<c>.ico
  3-ticket/ticket-58mm|ticket-80mm/…/noir/        <m>_ticket-58mm-384px_noir.bmp (+ -apercu .png), 80 mm = 576px
  3-ticket/etiquette-zebra/…/noir/                <m>_etiquette-zebra-203dpi_noir.zpl
  4-fabrication/decoupe|eps/<version>/<couleur>/  <m>_decoupe[_<version>]_<c>.svg, <m>_logo[_<version>]_<c>.eps
  5-reseaux/photos-de-profil/…                    instagram-320, instagram-1080, linkedin-400, x-400, whatsapp-business-640,
                                                  youtube-800, facebook-720, google-business-720 (.png)
  5-reseaux/bannieres/…                           youtube-banniere-2560x1440, linkedin-banniere-1584x396, x-banniere-1500x500,
                                                  facebook-couverture-1640x624, twitch-banniere-1200x480, google-business-couverture-1024x576
  5-reseaux/discord|slack|fond-visio/…            discord-512, slack-emoji-128, fond-visio-1920x1080
  5-reseaux/signature-email/…                     <m>_signature-email-<L>px_<c>.png + <m>_signature-email_<c>.html
  5-reseaux/bimi/…                                <m>_bimi-tiny-ps_<c>.svg
  6-qr-code/qr-code/<style>/                      <m>_qr-code_<s>.svg, <m>_qr-code-1024_<s>.png, <m>_qr-code-2048_<s>.png
  7-video/filigrane|animation/…                   <m>_filigrane-1920x1080_<c>.png, <m>_animation-fondu-1920x1080_<c>.webm
```

* Règle des noms : `marque_format[_version]_couleur.ext` ; `marque` = slug du nom de la marque ; `_version` seulement si le format sort dans plusieurs versions ; le niveau `<version>/` n'existe que si plusieurs versions sont chargées.
* **Noms imposés** (jamais préfixés) : `favicon.ico`, `apple-touch-icon.png`, `site.webmanifest`, tout `AppIcon.appiconset/`, tout `res/` Android, `LISEZMOI.txt`.
* Dossiers de couleur (`<c>`) : `originale`, `noir`, `blanc`, `niveaux-de-gris`, `fond-sombre`, `degrades-aplatis`, `recoloree-palette`, `couleur-<nom>`, `blanc-sur-<nom>`, `<duo>` ; QR : `ton-style`, `noir`, `blanc-sur-noir`.
* `site.webmanifest`, le code `<head>`, `Contents.json` et `ic_launcher.xml` ne pointent que vers des fichiers présents dans le même dossier.
* Un format en échec produit `erreurs/<marque>_<format>.txt` (et `export().errors` le liste) : ne le passe pas sous silence.

---

# 7. VÉRIFIER UN EXPORT

* `export()` → `count`, `errors` (doit être vide), `files`.
* `kit.read('<marque>-kit/LISEZMOI.txt', {text: true})` : mode, règle des noms, chaque format, liste complète.
* Dimensions : lis un PNG (`kit.read(path)` → base64) ; chaque taille est dans le nom (`-1200x630`, `-512`…).
* QR : décode le PNG avec un lecteur (zxing) et compare au lien.
* Le dépôt fournit `test/verify.py <zip>` (en-têtes ICO/ICNS/BMP/PNG/SVG/JSON/EPS/ZPL/WebM, dimensions, logo visible, liens des manifestes, noms à la marque, QR décodés, BIMI tiny-ps, signature HTML, LISEZMOI).

---

# 8. SÉLECTEURS STABLES (`data-agent`)

| Attribut | Élément |
| --- | --- |
| `home-open-tool`, `home-start-tool` | Ouvrir l'outil (accueil) |
| `home-import-binksy`, `home-start-binksy` | Importer un .binksy / .binksynext (accueil) |
| `home-import-svg`, `home-start-svg`, `home-demo`, `home-docs`, `home-resume` | Accueil |
| `nav-home`, `nav-docs`, `nav-agents`, `nav-tool` | En-tête du site |
| `tool-import` (`#file`), `tool-import-binksy` (`#bxFile`), `tool-export-binksy`, `tool-demo` | Barre latérale de l'outil |
| `tool-mode-essentiel`, `tool-mode-complet`, `tool-export` | En-tête de l'outil |
| `tool-view-variants`, `tool-view-context`, `tool-view-files` | Vues |
| `side-home`, `side-docs`, `side-agents` | Liens du site dans l'outil |

---

# 9. LIMITES

* WebM transparent : pas dans Safari (format `available: false`). Enregistrement en temps réel : environ 3 s par couleur.
* Import `.binksy` v2 : `DecompressionStream` requis (Safari ≥ 16.4) ; `state().binksyImport` le dit.
* Pas d'EMF, pas de PDF ni de fichiers de base du logo (Binksy LogoKit).
* EPS, découpe et BIMI : dégradés moyennés ; textes, images, filtres et masques ignorés.
* Lisibilité à 16 px : heuristique (`versions[].legibleAt16px`).
* Huit versions et huit couleurs de palette au maximum.

---

<a id="english"></a>

# BINKSY LOGONEXT — GUIDE FOR AI AGENTS

This document is for AI agents (Claude Code, Codex, or any agent driving a browser through Playwright / CDP, or running JavaScript in the page). It covers the whole tool: importing an SVG logo or a `.binksy` project, settings (brand, palette, colour variants, formats, shape versions, QR code), exporting the ZIP and the `.binksynext` project, and the public `window.multiformats` API. The user interface is in French; the API, its errors and its values are the same whatever your language.

It lives in three places, always identical: `/AGENTS.md` (Markdown, French then English), the `/agent` page and the tool's **Agents IA** screen (`#/agents/en`).

Discoverable without JavaScript: `/` declares `<link rel="alternate" type="text/markdown" href="AGENTS.md">`, `<link rel="help" href="agent/">` and `<link rel="alternate" type="text/plain" href="llms.txt">`, shows a `<noscript>` pointing here, and logs a single `console.info` line on load. `/robots.txt` allows everything and lists `/llms.txt` and `/AGENTS.md`.

---

# 1. GOLDEN RULE

Binksy LogoNext is a **logo-adaptation tool for graphic designers**, not a logo generator.

* The designer brings the logo (SVG, `.binksy` or `.binksynext`). It is the **source of truth**. Never invent a logo, a symbol or a brand colour; never redraw a supplied SVG.
* No link from the designer → **no QR code**. Never make up a URL.
* No API key, no AI provider, no server: everything runs in the tab. You, the agent, decide and verify; the tool provides the functions.
* No overlap with **Binksy LogoKit**: base logo files (SVG, HD PNG, PDF, JPEG) are made there. Here: specialised formats only.

After every operation, check the stored value (`await multiformats.state()`) and the result (`export` → `files`, `kit.read`). **A call or a click is not proof of success.**

---

# 2. GETTING STARTED

## Running the tool

* One `index.html` file (plus `AGENTS.md`, `llms.txt`, `robots.txt`, `agent/`). Serve the folder: `python3 -m http.server 8792`, then http://127.0.0.1:8792/ — or use the public address you were given.
* `file://` works too (same API). Add `?fresh` to the URL to ignore the saved session (localStorage `lmf-session`).
* Hash routes: `#/` landing · `#/outil` the tool · `#/docs` documentation (French) · `#/agents` this guide (`#/agents/en` in English). The API works on any route; `load.*` and `export` do not change the page.

## Two ways to drive it

1. **JavaScript API** (recommended): `window.multiformats` in the page, also available as **`window.logonext`** (same object; the tool is now called Binksy LogoNext, the `multiformats` name stays for compatibility). Each method uses the same functions as the UI and returns JSON.
2. **Clicks**: key controls carry a stable `data-agent` attribute (see § 8).

```js
await multiformats.ready();          // waits for the AVORA demo or the saved session
multiformats.version;                // "2.4"
multiformats.help();                 // [{path, signature, description}] (descriptions in French)
await multiformats.state();          // full state
await multiformats.catalog();        // groups, formats, sizes, modes, naming rule
```

## API conventions

* Every method except `help` is **async** and returns plain JSON.
* Errors are clear exceptions: `multiformats.<path> : <cause> (allowed values)`. Input is validated first: nothing is half-applied.
* Flexible file inputs: `Blob`/`File`, `ArrayBuffer`, `Uint8Array`, data URL, base64, or SVG text.
* Exports return `{name, type, size, …}`; pass `{base64: true}` to get the content, `{download: true}` to trigger the browser download. Produced files stay in `multiformats.files` (`list()`, `get(name)`, `base64(name)`, `download(name)`); files of the last ZIP in `multiformats.kit` (`list()`, `read(path)`).
* Every setting is saved in the session (localStorage), as from the UI.

---

# 3. TYPICAL FLOW

```js
await multiformats.ready();
await multiformats.load.svg([
  { svg: iconSvgText, name: 'avora-icon', type: 'icone' },
  { svg: horizontalSvgText, name: 'avora-horizontal', type: 'horizontal' },
], { brand: 'AVORA' });                                              // or load.binksy(…)
await multiformats.setPalette([{ hex: '#ff5500', name: 'Orange', role: 'accent' }, { hex: '#111111', name: 'Black', role: 'text' }]);
await multiformats.setMode('essentiel');
await multiformats.preset('site');                                   // or setFormats({...})
await multiformats.setFormats({ 'g:ticket': true, webm: false });
await multiformats.setQR({ url: 'https://avora.studio', logo: true, modules: 'arrondi', eyes: 'arrondi' });
const plan = await multiformats.plan();                              // {mode, tasks, folders[]}
const kit = await multiformats.export({ base64: true });             // {name, size, count, files[], base64}
const readme = await multiformats.kit.read('avora-kit/LISEZMOI.txt', { text: true });
const bx = await multiformats.exportBinksynext({ base64: true });   // avora.binksynext, reopens here (renamed .binksy: LogoKit)
```

Playwright (Python), passing local files as base64:

```python
import base64
page.goto('http://127.0.0.1:8792/#/outil')
page.wait_for_function('window.multiformats')
page.evaluate('multiformats.ready()')
page.evaluate('(s) => multiformats.load.svg(s, {brand: "AVORA"})', open('logo.svg').read())
bx = base64.b64encode(open('project.binksy', 'rb').read()).decode()
page.evaluate('(b) => multiformats.load.binksy(b, {name: "project.binksy"})', bx)
r = page.evaluate('multiformats.export({base64: true, list: false})')
open(r['name'], 'wb').write(base64.b64decode(r['base64']))
```

Or through the UI: `page.set_input_files('#file', 'logo.svg')` (SVG) and `page.set_input_files('#bxFile', 'project.binksy')` (accepts `.binksy` and `.binksynext`).

---

# 4. API REFERENCE

| Method | Purpose | Returns |
| --- | --- | --- |
| `ready({timeout?})` | Waits until the tool is ready | `{version, api, route, hasLogo}` |
| `help(prefix?)` | Signatures (synchronous) | `[{path, signature, description}]` |
| `state()` | Full state | see below |
| `catalog()` | Groups and formats (use, where, sizes, folder, Essentiel colours), modes, presets, version types, roles, naming rule | object |
| `plan({mode?})` | What the export will produce, without rendering | `{mode, tasks, folders:[{format, group, version, versionName, color, colorName, folder}]}` |
| `route(name?)` | Reads or changes the page (`accueil`, `outil`, `docs`, `agents`) | route |
| `load.svg(input, {replace=true, brand?, palette?, name?, type?})` | Loads 1–8 versions; an item may be `{svg, name, type}` | `versions` |
| `load.binksy(data, {name?})` | Opens a `.binksy` (LogoKit, v2 ZIP or legacy JSON) or a `.binksynext` (this tool); content is sniffed, the extension does not matter | `{brand, versions, palette, source, restored?, state}` |
| `load.demo()` | AVORA demo: 4 versions (Horizontal, Vertical, Icône, Logotype) and a named 4-colour palette (Noir/Text, Orange/Accent, Craie/Background, Marron/Secondary text), same as importing its .binksy; `state().isDemo` = `true`. The next replacing `load.svg` or `load.binksy` replaces it, palette included | `versions` |
| `addVersions(input, {name?, type?})` | Adds versions | `versions` |
| `setVersion(id, {name?, type?})` / `removeVersion(id)` | Rename, retype, remove | version / `versions` |
| `setBrand(name)` | Brand name (file prefix) | `{brand, slug}` |
| `setMode('essentiel' \| 'complet')` | ZIP content | mode |
| `setPalette(list)` | Palette, max 8: `["#hex"]` or `[{hex, name?, role?}]` | `palette` |
| `setVariants({key: bool})` | Toggles colour variants | `variants` |
| `preset(id)` | `site`, `shop`, `app`, `all` | active format ids |
| `setFormats({id \| "g:group": bool})` | Turns formats and groups on/off | `[{id, on, available}]` |
| `setFormatVersions(formatId, ids \| null)` | Versions used by a format; `null` = recommended | `{format, versions, custom}` |
| `setQR({url?, fg?, bg?, logo?, modules?, eyes?, quiet?})` | QR code | `qr` |
| `setSettings({bg?, size?, gradMode?, knock?, darkMode?, thermal?})` | Settings | `settings` |
| `export({mode?, base64?, download?, list?})` | Builds the ZIP | `{name, type, size, mode, count, ms, errors, files, base64?}` |
| `exportBinksynext({base64?, download?})` | `brand.binksynext` project (`.binksy` v2 format, type `application/x-binksynext`) | `{name, type, size, base64?}` |
| `exportBinksy(…)` | Old name of `exportBinksynext` (same `.binksynext` file) | same |
| `kit.list()` / `kit.read(path, {text?})` | Files of the last ZIP | `[{path, size}]` / `{path, name, type, size, base64 \| text}` |
| `files.list() / get / base64 / download` | Produced files (ZIP, .binksynext) | |

Allowed values:

* Version types: `icone` (icon), `horizontal`, `vertical`, `logotype` (wordmark), `monogramme` (monogram); guessed from the aspect ratio if omitted.
* Palette roles (LogoKit keys): `text`, `secondary-text`, `accent`, `background`, `primary`, `secondary`… (exact list: `catalog().paletteRoles`).
* Variants (`state().variants[].key`): `original`, `noir` (black), `blanc` (white), `gris` (greyscale), `sombre` (on dark), `aplat` (flattened gradients), `map` (palette recolour), `mono-<hex>`, `inv-<hex>` (white on colour), pairs… depending on the logo and palette.
* Formats: `favicon-ico`, `favicon-svg`, `apple-touch`, `android-chrome`, `maskable`, `manifest`, `og`, `ios`, `android`, `play`, `macos`, `windows`, `bmp58`, `bmp80`, `zpl`, `cut`, `eps`, `profiles`, `discord`, `slack`, `banners`, `visio`, `signature`, `bimi`, `watermark`, `qr`, `webm`. Groups: `g:web`, `g:apps`, `g:ticket`, `g:fab`, `g:social`, `g:qr`, `g:video`.
* QR: `modules` `carre` (square) \| `arrondi` (rounded) \| `points` (dots); `eyes` `carre` \| `arrondi` \| `rond` (round); `quiet` 2–8; `fg`/`bg` as `#rrggbb`. No `url` → no QR (`qr` is `available: false`). Contrast < 4:1 → `safe: false` and the export falls back to black on white.
* Settings: `bg` `auto` or `#rrggbb`; `size` 0.6–1; `gradMode` `keep` \| `avg` \| `mono`; `knock` boolean (white = hole); `darkMode` `plate` \| `outline`; `thermal` `{mode: threshold|floyd|atkinson, thr: 30..225}`.
* `webm` is only `available` where the browser can record WebM (not Safari); it is off by default (3 s real-time recording per colour).

`state()` returns `{app, version, api, route, ready, brand, slug, isDemo, mode, preset, versions:[{id, name, type, folder, kind, width, height, colors, warnings, legibleAt16px}], palette:[{hex, name, role}], variants:[{key, name, folder, group, on, locked, background, ink}], groups:[{id, name, folder, on}], formats:[{id, group, name, on, available, folder, versions, customVersions, essentiel}], qr:{url, text, active, fg, bg, logo, modules, eyes, quiet, contrast, safe}, settings:{…}, webm, binksyImport}`.

---

# 5. THE .binksy FORMAT AND `modules.multiformats`

`.binksy` is Binksy LogoKit's project file. This tool saves the **same format** with the **`.binksynext`** extension (to tell it apart from a LogoKit project); import accepts both `.binksy` and `.binksynext` (including older `.binksy` files saved here). To open a `.binksynext` in LogoKit, rename it to `.binksy`. Version 2 is a ZIP archive:

* `manifest.json`: `{format: "binksy", version: 2, kind: "multiformats" (this tool's exports only; ignored by LogoKit), app, appVersion, createdAt, savedAt, projectId, name, modules: {…, multiformats: true}, files: [{path, id, size, sha256}], notEmbedded: []}`.
* `project.json`: `{format: "binksy-project", version: 2, id, name, brand, createdAt, updatedAt, assets, roles, extras, palette, customColorRoles, typography, variants, mockups, modules, missing}`.
  * `assets[id]`: `{id, hash, mime: "image/svg+xml", size, kind: "svg", name, w, h, origin: "multiformats", file: "assets/svg/<id>.svg", embedded: true}`; `id` = `a` + first 20 hex chars of the SVG's SHA-256.
  * `roles`: `{logo, icon, wordmark}`; other versions go to `extras`.
  * `variants`: `[{id: horizontal|vertical|icon|wordmark|mf-N, name, asset, kind: composition|icon|wordmark}]`.
  * `palette`: `[{id, name, hex, role}]` (LogoKit role keys; custom roles listed in `customColorRoles`).
* `assets/svg/<id>.svg`: each version's original SVG.

On import the tool reads `brand` (else `name`), `variants[]` else `roles` (+ `extras`) else legacy `modules.logokit`, `palette[]` (max 8), and legacy JSON `.binksy` V1–V5 (versions recomposed like LogoKit). The ZIP reader only reads the central directory and the needed entries (deflate via `DecompressionStream`), ZIP64 included.

## `modules.multiformats`

Written by "Exporter en .binksynext"; LogoKit keeps it untouched. When present on import, **the whole state is restored**.

```json
{
  "app": "Binksy LogoNext", "v": 1, "savedAt": "2026-10-29T10:00:00.000Z",
  "session": {
    "v": 2,
    "shapes": [{ "name": "Icône", "type": "icone", "file": "avora-icone", "asset": "a1b2c3…" }],
    "kitName": "AVORA", "isDemo": false, "mode": "essentiel", "preset": "custom",
    "palette": ["#ff5500", "#111111"],
    "palLabels": { "#ff5500": { "name": "Orange", "role": "Accent" } },
    "map": {}, "mapTouched": false,
    "off": ["gris"], "fmtOff": ["g:video", "webm"], "fmtShapes": { "banners": [1] },
    "ctxVar": "auto", "gradMode": "keep", "knock": true, "darkMode": "plate",
    "bg": "auto", "size": 0.8, "thermal": { "mode": "atkinson", "thr": 128 },
    "qr": { "url": "https://avora.studio", "fg": "#111111", "bg": "#ffffff", "logo": true, "modules": "carre", "eyes": "carre", "quiet": 4 }
  }
}
```

* `shapes[].asset` points to `assets[id]`; `fmtShapes` maps a format id to the **indexes** of its chosen versions.
* `off`: unticked variant keys; `fmtOff`: disabled formats (`id`) and groups (`g:<group>`).
* `palLabels[hex].role` is the French label (Texte, Accent, Fond…); `project.json` `palette[].role` holds the LogoKit key.

---

# 6. THE ZIP

Name: `<brand>-kit-<mode>.zip` (e.g. `avora-kit-essentiel.zip`). Root: `<brand>-kit/`. Tree: **use / format / [version /] colour /**, the version level only when several versions are loaded.

| Folder | Files (`<m>` = brand slug, `<c>` = colour folder) |
| --- | --- |
| `1-web/favicons/` | `favicon.ico`, `<m>_favicon_<c>.svg`, `apple-touch-icon.png`, `<m>_android-chrome-192x192_<c>.png`, `…-512x512…`, `<m>_maskable-192x192_<c>.png`, `…-512x512…`, `site.webmanifest`, `<m>_a-coller-dans-head_<c>.html` |
| `1-web/image-de-partage/` | `<m>_og-image-1200x630_<c>.png` |
| `2-apps/ios/…/AppIcon.appiconset/` | `AppIcon-1024.png`, `AppIcon-1024-dark.png`, `AppIcon-1024-tinted.png`, `Contents.json` |
| `2-apps/android/…/res/` | `mipmap-{mdpi…xxxhdpi}/ic_launcher{,_round,_foreground,_background,_monochrome}.png`, `mipmap-anydpi-v26/*.xml`, `values/ic_launcher_background.xml` |
| `2-apps/google-play`, `macos`, `windows` | `<m>_google-play-512_<c>.png`, `<m>_macos_<c>.icns`, `<m>_macos-1024_<c>.png`, `<m>_windows_<c>.ico` |
| `3-ticket/…/noir/` | `<m>_ticket-58mm-384px_noir.bmp` (+ `-apercu` PNG), `…80mm-576px…`, `<m>_etiquette-zebra-203dpi_noir.zpl` |
| `4-fabrication/decoupe`, `eps` | `<m>_decoupe[_<version>]_<c>.svg`, `<m>_logo[_<version>]_<c>.eps` |
| `5-reseaux/photos-de-profil/` | `instagram-320`, `instagram-1080`, `linkedin-400`, `x-400`, `whatsapp-business-640`, `youtube-800`, `facebook-720`, `google-business-720` |
| `5-reseaux/bannieres/` | `youtube-banniere-2560x1440`, `linkedin-banniere-1584x396`, `x-banniere-1500x500`, `facebook-couverture-1640x624`, `twitch-banniere-1200x480`, `google-business-couverture-1024x576` |
| `5-reseaux/discord`, `slack`, `fond-visio` | `discord-512`, `slack-emoji-128`, `fond-visio-1920x1080` |
| `5-reseaux/signature-email/` | `<m>_signature-email-<W>px_<c>.png` + `<m>_signature-email_<c>.html` |
| `5-reseaux/bimi/` | `<m>_bimi-tiny-ps_<c>.svg` (SVG Tiny 1.2 PS) |
| `6-qr-code/qr-code/<style>/` | `<m>_qr-code_<s>.svg`, `<m>_qr-code-1024_<s>.png`, `<m>_qr-code-2048_<s>.png` |
| `7-video/filigrane`, `animation` | `<m>_filigrane-1920x1080_<c>.png`, `<m>_animation-fondu-1920x1080_<c>.webm` |

* Naming rule: `brand_format[_version]_colour.ext`; `_version` only when the format is exported for several versions.
* **Fixed names** (never prefixed): `favicon.ico`, `apple-touch-icon.png`, `site.webmanifest`, everything in `AppIcon.appiconset/` and Android `res/`, `LISEZMOI.txt`.
* Colour folders: `originale`, `noir`, `blanc`, `niveaux-de-gris`, `fond-sombre`, `degrades-aplatis`, `recoloree-palette`, `couleur-<name>`, `blanc-sur-<name>`, pairs; QR: `ton-style`, `noir`, `blanc-sur-noir`.
* `site.webmanifest`, the `<head>` snippet, `Contents.json` and `ic_launcher.xml` only reference files present in the same folder.
* `LISEZMOI.txt` (French, UTF-8 BOM, CRLF) describes the mode, naming, each version, colour and format, and lists every file.
* A failed format yields `erreurs/<brand>_<format>.txt` (also listed in `export().errors`): report it.

---

# 7. VERIFYING AN EXPORT

* `export()` → `count`, `errors` (must be empty), `files`.
* `kit.read('<brand>-kit/LISEZMOI.txt', {text: true})`.
* Sizes are in file names (`-1200x630`, `-512`…); read a PNG with `kit.read(path)` (base64) to check.
* QR: decode the PNG (zxing) and compare with the link.
* The repository ships `test/verify.py <zip>` (ICO/ICNS/BMP/PNG/SVG/JSON/EPS/ZPL/WebM headers, sizes, logo visibility, manifest links, brand prefixes, decoded QR codes, BIMI tiny-ps, signature HTML, LISEZMOI).

---

# 8. STABLE SELECTORS (`data-agent`)

| Attribute | Element |
| --- | --- |
| `home-open-tool`, `home-start-tool` | Open the tool (landing) |
| `home-import-binksy`, `home-start-binksy` | Import a .binksy / .binksynext (landing) |
| `home-import-svg`, `home-start-svg`, `home-demo`, `home-docs`, `home-resume` | Landing |
| `nav-home`, `nav-docs`, `nav-agents`, `nav-tool` | Site header |
| `tool-import` (`#file`), `tool-import-binksy` (`#bxFile`), `tool-export-binksy`, `tool-demo` | Tool sidebar |
| `tool-mode-essentiel`, `tool-mode-complet`, `tool-export` | Tool header |
| `tool-view-variants`, `tool-view-context`, `tool-view-files` | Views |
| `side-home`, `side-docs`, `side-agents` | Site links inside the tool |

---

# 9. LIMITS

* Transparent WebM: not in Safari (`available: false`); real-time recording, about 3 s per colour.
* `.binksy` v2 import needs `DecompressionStream` (Safari ≥ 16.4); see `state().binksyImport`.
* No EMF, no PDF, no base logo files (Binksy LogoKit does those).
* EPS, cut SVG and BIMI: gradients averaged; text, images, filters and masks ignored.
* 16 px legibility is a heuristic (`versions[].legibleAt16px`).
* At most eight versions and eight palette colours.
