mochikao 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexvolkihar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.fr.md ADDED
@@ -0,0 +1,216 @@
1
+ # mochikao
2
+
3
+ [English](README.md) · **Français**
4
+
5
+ Un visage pour chaque nom. `mochikao` transforme n'importe quelle chaîne (pseudo, e-mail, id) en un petit personnage SVG. Le même nom donne toujours le même mochikao.
6
+
7
+ ![Page d'accueil de mochikao : un nom tapé, l'avatar généré et le choix d'expression](docs/home.png)
8
+
9
+ ![Éditeur de mochikao en thème sombre : un lapin, d'autres noms avec les mêmes réglages, le panneau de réglages à épingler](docs/editor.png)
10
+
11
+ - **Déterministe** : `cyrb53` → `mulberry32`, aucun appel réseau, aucun état.
12
+ - **Zéro dépendance**, ~7 Ko min+gzip.
13
+ - **10 formes de blobs**, et deux familles optionnelles importées à part : 6 animaux (`mochikao/animals`, +1,1 Ko) et 6 fruits & légumes (`mochikao/produce`, +1,5 Ko, avec leur teinte naturelle).
14
+ - 5 yeux, 6 bouches, 11 accessoires (joues, taches, antenne, pousse, reflet, lunettes, lunettes noires, nœud, couronne, chapeau de fête).
15
+ - **10 axes continus** réglables (taille et proportion du corps, carrure, ondulation, taille/écart/inclinaison des yeux, regard x/y, taille de la bouche) et 6 préréglages de couleur.
16
+ - **8 expressions** : `idle`, `happy`, `sad`, `mad`, `love`, `shy`, `sick`, `thinking`.
17
+ - **Contraste visage/corps ≥ 4.5:1** pour toute teinte et tout ton (vérifié par les tests).
18
+ - **Animations CSS** (respiration, clignement, coups d'œil), **transition animée entre expressions** et **regard qui suit le pointeur**.
19
+ - Fonction, composants **React / Vue / Svelte / Solid**, web component, API HTTP et CLI.
20
+
21
+ Inspiré de [blobatar](https://blobatar.dev).
22
+
23
+ ## Démarrer
24
+
25
+ ```bash
26
+ npm install
27
+ npm run dev # site (accueil + /editor.html) sur http://localhost:5173, API /api/*.svg branchée
28
+ npm test # tests node:test
29
+ npm run api # API seule sur :8787
30
+ npm run build # lib + CLI dans dist/
31
+ npm run build:site # site statique dans dist-site/
32
+ ```
33
+
34
+ Pour développer, Node ≥ 22.18 : les sources `.ts` s'exécutent directement (type stripping). Le paquet publié, lui, est du JavaScript ES2022 (ESM) : Node ≥ 20 et tous les bundlers. Les tests utilisent la condition d'export `mochikao-source` pour importer le package depuis ses sources, sans build préalable.
35
+
36
+ ## Utilisation
37
+
38
+ ### Fonction
39
+
40
+ ```ts
41
+ import { mochikao, resolve } from 'mochikao';
42
+
43
+ const svg = mochikao({ name: 'alex@mail.com', size: 64, background: 'squircle' });
44
+ resolve('alex'); // { shape: 'boxy', eyes: 'dot', mouth: 'cat', extra: 'blush', hue: 348, colors: {...} }
45
+ ```
46
+
47
+ | Option | Valeurs | Défaut |
48
+ | --- | --- | --- |
49
+ | `name` | toute chaîne (insensible à la casse et aux espaces autour) | — |
50
+ | `size` | px | `128` |
51
+ | `background` | `none` `circle` `squircle` `square` | `none` |
52
+ | `hue` | 0–360 | tirée du nom |
53
+ | `tone` | 0 (sombre) – 1 (clair) | `0.5` |
54
+ | `saturation` | 0–1 | `0.74` |
55
+ | `color` | préréglage ton + saturation : `pastel` `pale` `mid` `deep` `bright` `ink` | — |
56
+ | `family` | une famille importée : `animals`, `produce` (voir plus bas) | blobs |
57
+ | `expression` | `idle` `happy` `sad` `mad` `love` `shy` `sick` `thinking` | `idle` |
58
+ | `animate` | `none` `hover` `always` | `none` |
59
+ | `shape` `eyes` `mouth` `extra` | épingle un trait par son nom (`shape` parmi les formes de la famille) | tirés du nom |
60
+ | `traits` | épingle des axes 0–1 : `bodySize` `proportion` `squareness` `wobble` `eyeSize` `spacing` `lean` `gazeX` `gazeY` `mouthSize` | tirés du nom |
61
+ | `gaze` | ajoute le CSS de suivi du regard (`--gx`, `--gy`) | `false` |
62
+
63
+ `toParams(options)` (depuis `mochikao/params`) donne les mêmes options en paramètres d'URL, et `parseParams` fait l'inverse : c'est ce qu'utilisent l'API, la CLI, le web component et les liens de l'éditeur.
64
+
65
+ `mochikaoDataUri(options)` renvoie la même image en data URI, encodée au plus court pour un attribut HTML. `mochikaoImage(options)` renvoie directement `{ src, width, height, alt }`.
66
+
67
+ Épingler un axe ne change pas les autres : on peut forcer la forme sans perdre la couleur ni le visage.
68
+
69
+ ### React, Vue, Svelte, Solid
70
+
71
+ ```tsx
72
+ import { Mochikao } from 'mochikao/react'; // React 18+ (et Preact via preact/compat)
73
+ <Mochikao name="alex" size={48} animate="hover" className="avatar" />
74
+ ```
75
+
76
+ ```vue
77
+ <script setup>
78
+ import { Mochikao } from 'mochikao/vue'; // Vue 3.3+
79
+ </script>
80
+ <template>
81
+ <Mochikao name="alex" :size="48" animate="hover" />
82
+ </template>
83
+ ```
84
+
85
+ ```svelte
86
+ <script>
87
+ import Mochikao from 'mochikao/svelte'; // Svelte 5
88
+ </script>
89
+ <Mochikao name="alex" size={48} animate="hover" />
90
+ ```
91
+
92
+ ```tsx
93
+ import { Mochikao } from 'mochikao/solid'; // Solid 1.8+
94
+ <Mochikao name="alex" size={48} animate="hover" class="avatar" />
95
+ ```
96
+
97
+ Mêmes props que les options de `mochikao()` (sauf `gaze`), plus `inline`. Les autres attributs (`class`, `style`, événements...) sont transmis à l'élément rendu. Le composant React est un composant client (`'use client'`, pour la transition entre expressions) : il est quand même rendu côté serveur et s'utilise depuis des Server Components. Les frameworks sont des `peerDependencies` optionnelles : seul celui que tu importes est requis.
98
+
99
+ **`<img>` ou SVG en ligne ?** Sans animation, les composants rendent un simple `<img src="data:image/svg+xml,...">` : 1 nœud DOM au lieu d'une vingtaine (18 à 27 selon l'avatar), ce qui compte dans les longues listes. Avec `animate="hover"` ou `"always"`, ils rendent le SVG en ligne, nécessaire pour que l'animation fonctionne. `inline` force le SVG en ligne, par exemple pour le styler en CSS. Le web component suit la même règle, et passe aussi en SVG en ligne avec `gaze`.
100
+
101
+ Le composant Svelte est livré en `.svelte` source, compilé par ton bundler (condition d'export `svelte`). Le composant Solid est écrit sans JSX : pas besoin du plugin Babel de Solid pour le consommer, et toutes les props restent réactives.
102
+
103
+ ### Familles de formes
104
+
105
+ mochikao n'embarque que les blobs. Les autres familles sont des modules à importer et à passer en option, pour que seuls ceux qui s'en servent en paient le poids :
106
+
107
+ ```ts
108
+ import { mochikao } from 'mochikao';
109
+ import { animals } from 'mochikao/animals'; // chat, ours, lapin, grenouille, cochon, souris
110
+ import { produce } from 'mochikao/produce'; // pomme, poire, fraise, citron, carotte, avocat
111
+
112
+ mochikao({ name: 'alex', family: animals });
113
+ mochikao({ name: 'alex', family: produce, shape: 'lemon' });
114
+ ```
115
+
116
+ Choisir une famille ne change que la forme : yeux, bouche, accessoire et réglages restent ceux du nom. Là où les options arrivent en texte (attributs HTML, URL, CLI), on déclare les familles disponibles une fois : `defineMochikao({ families: [animals] })` puis `<mochikao-avatar family="animals">`, ou `parseParams(nom, params, [animals, produce])`. L'API et la CLI fournies acceptent les deux.
117
+
118
+ Une famille, c'est un objet `ShapeFamily` (`name`, `shapes`, `silhouette`, et en option `decorate` et `hue`) : `src/families/` montre comment en écrire une.
119
+
120
+ ### Transition entre expressions
121
+
122
+ En SVG en ligne (`animate`, `inline` ou `gaze`), changer `expression` déclenche une petite transition : les yeux clignent, la bouche se referme, le visage change pendant qu'ils sont fermés, puis tout se rouvre avec un rebond (~450 ms). Les composants et le web component le font tout seuls. Un `<img>` ne peut pas s'animer : en mode image, le changement est immédiat.
123
+
124
+ Pour ton propre rendu, `morphTo(element, svg)` (depuis `mochikao/morph`) remplace le SVG contenu dans `element` avec la transition. Elle ne s'applique qu'entre deux expressions du même personnage (attribut `data-mochikao` identique) ; sinon, ou si `prefers-reduced-motion` est actif, le remplacement est direct. Un appel pendant une transition repart de l'état affiché, sans étape intermédiaire.
125
+
126
+ ### Web component
127
+
128
+ ```html
129
+ <script type="module">
130
+ import { defineMochikao } from 'mochikao/element';
131
+ defineMochikao();
132
+ </script>
133
+
134
+ <mochikao-avatar name="alex" animate="hover" background="circle" gaze></mochikao-avatar>
135
+ ```
136
+
137
+ Mêmes attributs que les options ; `gaze` fait suivre le pointeur aux yeux.
138
+
139
+ ### API HTTP
140
+
141
+ ```
142
+ GET /api/<nom>.svg?size=&bg=&hue=&tone=&color=&expression=&animate=&family=&shape=&eyes=&mouth=&extra=
143
+ ```
144
+
145
+ Réponse `image/svg+xml` avec `Cache-Control: immutable` (même entrée → même sortie). Les paramètres invalides sont ignorés. Le handler (`api/handler.ts`) est une simple fonction `(req, res)` de `node:http`, à brancher où tu veux.
146
+
147
+ ### CLI
148
+
149
+ ```bash
150
+ npx mochikao alex --bg circle --expression happy -o alex.svg
151
+ npx mochikao alex --info # traits et couleurs en JSON
152
+ ```
153
+
154
+ ## Site
155
+
156
+ - **Accueil** : nom à taper, expressions, familles, galerie, exemples de code.
157
+ - **Éditeur** (`/editor.html`) : épingle chaque trait (grilles avec aperçus, curseurs avec cadenas), vois le résultat sur d'autres noms, récupère le code (React, Vue, Svelte, Solid, HTML, JS, URL), télécharge en SVG/PNG. L'URL de la page reflète la config : copier le lien suffit pour la partager.
158
+ - **Thème** clair / sombre / système (mémorisé, sans flash au chargement) et **langues** français, anglais, espagnol, japonais (`?lang=en`, sinon la langue du navigateur).
159
+
160
+ ## Stabilité
161
+
162
+ La constante `GENERATION` (`src/traits.ts`) et l'ordre des tirages font partie de la graine. Les changer modifie **tous** les avatars, donc toutes les URL déjà publiées. Le test « les traits de "alex" ne bougent pas » sert de garde-fou.
163
+
164
+ ## Structure
165
+
166
+ ```
167
+ src/
168
+ random.ts hachage cyrb53 + PRNG mulberry32
169
+ traits.ts axes, listes de traits, dérivation depuis le nom
170
+ shape.ts silhouettes (fonctions polaires → Catmull-Rom → Bézier)
171
+ color.ts palette HSL avec contraste garanti
172
+ face.ts yeux, bouches, sourcils, accessoires, expressions
173
+ index.ts rendu SVG + animations
174
+ params.ts options depuis query string / attributs / flags
175
+ family.ts contrat d'une famille de formes (ShapeFamily)
176
+ families/ animals.ts, produce.ts (modules à part) et kit.ts (outils communs)
177
+ morph.ts transition animée entre expressions (Web Animations)
178
+ element.ts <mochikao-avatar>
179
+ react.ts composant React
180
+ vue.ts composant Vue
181
+ solid.ts composant Solid
182
+ svelte/ composant Svelte (livré en source)
183
+ api/ handler HTTP + serveur Node
184
+ bin/ CLI
185
+ site/ accueil + éditeur (Vite) ; site/shared : thème clair/sombre, traductions fr/en/es/ja
186
+ test/ node:test
187
+ ```
188
+
189
+ ## Publier
190
+
191
+ Un seul paquet, `mochikao`, sert npm, pnpm, yarn et bun : ils installent tous depuis le registre npm. Les intégrations sont des sous-chemins (`mochikao/react`, `mochikao/animals`...), et React, Vue, Svelte et Solid sont des `peerDependencies` optionnelles.
192
+
193
+ **Première version, à la main** (le paquet doit exister avant d'activer la publication automatique) :
194
+
195
+ ```bash
196
+ npm pack --dry-run # liste exacte des fichiers envoyés
197
+ npm login
198
+ npm publish # prepublishOnly lance types, tests et build avant
199
+ ```
200
+
201
+ **Versions suivantes, par la CI** (`.github/workflows/release.yml`, Trusted Publishing : aucun jeton npm, preuve d'origine automatique) :
202
+
203
+ 1. Sur npmjs.com, dans les réglages du paquet : ajouter GitHub Actions comme *trusted publisher* (dépôt, workflow `release.yml`, environnement `npm`), puis « Require two-factor authentication and disallow tokens ».
204
+ 2. Pour publier :
205
+ ```bash
206
+ npm version patch # ou minor / major : met à jour package.json et crée le tag
207
+ git push --follow-tags # le tag vX.Y.Z déclenche la publication
208
+ ```
209
+
210
+ La CI (`.github/workflows/ci.yml`) vérifie types, tests et builds sur Node 22 et 24 à chaque push et pull request, plus l'audit des dépendances publiées et publint.
211
+
212
+ Une fois publié, le rendu des avatars est un contrat : voir Stabilité.
213
+
214
+ ## Licence
215
+
216
+ MIT
package/README.md CHANGED
@@ -1,3 +1,216 @@
1
- # Temporary Holding Version
1
+ # mochikao
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **English** · [Français](README.fr.md)
4
+
5
+ A face for every name. `mochikao` turns any string (username, email, ID) into a little SVG character. The same name always gives the same mochikao.
6
+
7
+ ![mochikao home page: a typed name, the generated avatar and the expression picker](docs/home.png)
8
+
9
+ ![mochikao editor in dark mode: a bunny, other names with the same settings, the panel of pinnable settings](docs/editor.png)
10
+
11
+ - **Deterministic**: `cyrb53` → `mulberry32`, no network calls, no state.
12
+ - **Zero dependencies**, ~7 KB min+gzip.
13
+ - **10 blob shapes**, plus two optional families imported separately: 6 animals (`mochikao/animals`, +1.1 KB) and 6 fruits & veggies (`mochikao/produce`, +1.5 KB, each with its natural color).
14
+ - 5 eyes, 6 mouths, 11 accessories (blush, freckles, antenna, sprout, shine, glasses, sunglasses, bow, crown, party hat).
15
+ - **10 continuous axes** you can tune (body size and proportion, squareness, wobble, eye size/spacing/lean, gaze x/y, mouth size) and 6 color presets.
16
+ - **8 expressions**: `idle`, `happy`, `sad`, `mad`, `love`, `shy`, `sick`, `thinking`.
17
+ - **Face-to-body contrast ≥ 4.5:1** for every hue and tone (checked by the tests).
18
+ - **CSS animations** (breathing, blinking, glances), **animated transitions between expressions** and **eyes that follow the pointer**.
19
+ - A function, **React / Vue / Svelte / Solid** components, a web component, an HTTP API and a CLI.
20
+
21
+ Inspired by [blobatar](https://blobatar.dev).
22
+
23
+ ## Getting started
24
+
25
+ ```bash
26
+ npm install
27
+ npm run dev # site (home + /editor.html) on http://localhost:5173, with the /api/*.svg API
28
+ npm test # node:test tests
29
+ npm run api # API alone on :8787
30
+ npm run build # library + CLI into dist/
31
+ npm run build:site # static site into dist-site/
32
+ ```
33
+
34
+ Development needs Node ≥ 22.18: the `.ts` sources run directly (type stripping). The published package is plain ES2022 JavaScript (ESM): Node ≥ 20 and every bundler. The tests use the `mochikao-source` export condition to import the package from its sources, with no build step.
35
+
36
+ ## Usage
37
+
38
+ ### Function
39
+
40
+ ```ts
41
+ import { mochikao, resolve } from 'mochikao';
42
+
43
+ const svg = mochikao({ name: 'alex@mail.com', size: 64, background: 'squircle' });
44
+ resolve('alex'); // { shape: 'boxy', eyes: 'dot', mouth: 'cat', extra: 'blush', hue: 348, colors: {...} }
45
+ ```
46
+
47
+ | Option | Values | Default |
48
+ | --- | --- | --- |
49
+ | `name` | any string (case-insensitive, surrounding spaces ignored) | — |
50
+ | `size` | px | `128` |
51
+ | `background` | `none` `circle` `squircle` `square` | `none` |
52
+ | `hue` | 0–360 | from the name |
53
+ | `tone` | 0 (dark) – 1 (light) | `0.5` |
54
+ | `saturation` | 0–1 | `0.74` |
55
+ | `color` | tone + saturation preset: `pastel` `pale` `mid` `deep` `bright` `ink` | — |
56
+ | `family` | an imported family: `animals`, `produce` (see below) | blobs |
57
+ | `expression` | `idle` `happy` `sad` `mad` `love` `shy` `sick` `thinking` | `idle` |
58
+ | `animate` | `none` `hover` `always` | `none` |
59
+ | `shape` `eyes` `mouth` `extra` | pin a trait by name (`shape` among the family's shapes) | from the name |
60
+ | `traits` | pin 0–1 axes: `bodySize` `proportion` `squareness` `wobble` `eyeSize` `spacing` `lean` `gazeX` `gazeY` `mouthSize` | from the name |
61
+ | `gaze` | adds the CSS for pointer tracking (`--gx`, `--gy`) | `false` |
62
+
63
+ `toParams(options)` (from `mochikao/params`) turns the same options into URL parameters, and `parseParams` does the reverse: that is what the API, the CLI, the web component and the editor links use.
64
+
65
+ `mochikaoDataUri(options)` returns the same image as a data URI, encoded as compactly as possible for an HTML attribute. `mochikaoImage(options)` returns `{ src, width, height, alt }` directly.
66
+
67
+ Pinning one axis leaves the others alone: you can force the shape without losing the color or the face.
68
+
69
+ ### React, Vue, Svelte, Solid
70
+
71
+ ```tsx
72
+ import { Mochikao } from 'mochikao/react'; // React 18+ (and Preact via preact/compat)
73
+ <Mochikao name="alex" size={48} animate="hover" className="avatar" />
74
+ ```
75
+
76
+ ```vue
77
+ <script setup>
78
+ import { Mochikao } from 'mochikao/vue'; // Vue 3.3+
79
+ </script>
80
+ <template>
81
+ <Mochikao name="alex" :size="48" animate="hover" />
82
+ </template>
83
+ ```
84
+
85
+ ```svelte
86
+ <script>
87
+ import Mochikao from 'mochikao/svelte'; // Svelte 5
88
+ </script>
89
+ <Mochikao name="alex" size={48} animate="hover" />
90
+ ```
91
+
92
+ ```tsx
93
+ import { Mochikao } from 'mochikao/solid'; // Solid 1.8+
94
+ <Mochikao name="alex" size={48} animate="hover" class="avatar" />
95
+ ```
96
+
97
+ Same props as the `mochikao()` options (except `gaze`), plus `inline`. Other attributes (`class`, `style`, event handlers…) are passed to the rendered element. The React component is a client component (`'use client'`, for the expression transition): it still renders on the server and can be used from Server Components. The frameworks are optional `peerDependencies`: only the one you import is required.
98
+
99
+ **`<img>` or inline SVG?** Without animation, the components render a plain `<img src="data:image/svg+xml,...">`: 1 DOM node instead of about twenty (18 to 27 depending on the avatar), which matters in long lists. With `animate="hover"` or `"always"`, they render inline SVG, which the animation needs. `inline` forces inline SVG, for example to style it with CSS. The web component follows the same rule, and also switches to inline SVG with `gaze`.
100
+
101
+ The Svelte component ships as a `.svelte` source file, compiled by your bundler (`svelte` export condition). The Solid component is written without JSX: no Solid Babel plugin needed to consume it, and every prop stays reactive.
102
+
103
+ ### Shape families
104
+
105
+ mochikao only bundles the blobs. The other families are modules you import and pass as an option, so only the people who use them pay for their size:
106
+
107
+ ```ts
108
+ import { mochikao } from 'mochikao';
109
+ import { animals } from 'mochikao/animals'; // cat, bear, bunny, frog, pig, mouse
110
+ import { produce } from 'mochikao/produce'; // apple, pear, strawberry, lemon, carrot, avocado
111
+
112
+ mochikao({ name: 'alex', family: animals });
113
+ mochikao({ name: 'alex', family: produce, shape: 'lemon' });
114
+ ```
115
+
116
+ Picking a family only changes the shape: eyes, mouth, accessory and settings still come from the name. Where options arrive as text (HTML attributes, URLs, CLI), declare the available families once: `defineMochikao({ families: [animals] })` then `<mochikao-avatar family="animals">`, or `parseParams(name, params, [animals, produce])`. The bundled API and CLI accept both.
117
+
118
+ A family is a `ShapeFamily` object (`name`, `shapes`, `silhouette`, and optionally `decorate` and `hue`): `src/families/` shows how to write one.
119
+
120
+ ### Transitions between expressions
121
+
122
+ With inline SVG (`animate`, `inline` or `gaze`), changing `expression` plays a short transition: the eyes blink, the mouth closes, the face changes while they are shut, then everything reopens with a little bounce (~450 ms). The components and the web component do it on their own. An `<img>` cannot animate: in image mode the change is immediate.
123
+
124
+ For your own rendering, `morphTo(element, svg)` (from `mochikao/morph`) replaces the SVG inside `element` with the transition. It only applies between two expressions of the same character (same `data-mochikao` attribute); otherwise, or when `prefers-reduced-motion` is on, the replacement is instant. A call during a transition starts again from what is on screen, with no intermediate step.
125
+
126
+ ### Web component
127
+
128
+ ```html
129
+ <script type="module">
130
+ import { defineMochikao } from 'mochikao/element';
131
+ defineMochikao();
132
+ </script>
133
+
134
+ <mochikao-avatar name="alex" animate="hover" background="circle" gaze></mochikao-avatar>
135
+ ```
136
+
137
+ Same attributes as the options; `gaze` makes the eyes follow the pointer.
138
+
139
+ ### HTTP API
140
+
141
+ ```
142
+ GET /api/<name>.svg?size=&bg=&hue=&tone=&color=&expression=&animate=&family=&shape=&eyes=&mouth=&extra=
143
+ ```
144
+
145
+ Responds with `image/svg+xml` and `Cache-Control: immutable` (same input → same output). Invalid parameters are ignored. The handler (`api/handler.ts`) is a plain `node:http` `(req, res)` function you can mount anywhere.
146
+
147
+ ### CLI
148
+
149
+ ```bash
150
+ npx mochikao alex --bg circle --expression happy -o alex.svg
151
+ npx mochikao alex --info # traits and colors as JSON
152
+ ```
153
+
154
+ ## Site
155
+
156
+ - **Home**: type a name, try expressions, browse the families and the gallery, copy code examples.
157
+ - **Editor** (`/editor.html`): pin each trait (grids with previews, sliders with locks), see the result on other names, get the code (React, Vue, Svelte, Solid, HTML, JS, URL), download as SVG/PNG. The page URL holds the config: copying the link is enough to share it.
158
+ - **Light / dark / system theme** (remembered, no flash on load) and **languages**: English, French, Spanish, Japanese (`?lang=en`, otherwise the browser language).
159
+
160
+ ## Stability
161
+
162
+ The `GENERATION` constant (`src/traits.ts`) and the order of the random draws are part of the seed. Changing them changes **every** avatar, and so every URL already in use. The "alex's traits do not move" test guards against that.
163
+
164
+ ## Project structure
165
+
166
+ ```
167
+ src/
168
+ random.ts cyrb53 hash + mulberry32 PRNG
169
+ traits.ts axes, trait lists, derivation from the name
170
+ shape.ts silhouettes (polar functions → Catmull-Rom → Bézier)
171
+ color.ts HSL palette with guaranteed contrast
172
+ face.ts eyes, mouths, brows, accessories, expressions
173
+ index.ts SVG rendering + animations
174
+ params.ts options from query strings / attributes / flags
175
+ family.ts shape family contract (ShapeFamily)
176
+ families/ animals.ts, produce.ts (separate modules) and kit.ts (shared helpers)
177
+ morph.ts animated transitions between expressions (Web Animations)
178
+ element.ts <mochikao-avatar>
179
+ react.ts React component
180
+ vue.ts Vue component
181
+ solid.ts Solid component
182
+ svelte/ Svelte component (shipped as source)
183
+ api/ HTTP handler + Node server
184
+ bin/ CLI
185
+ site/ home + editor (Vite); site/shared: light/dark theme, en/fr/es/ja translations
186
+ test/ node:test
187
+ ```
188
+
189
+ ## Publishing
190
+
191
+ A single package, `mochikao`, serves npm, pnpm, yarn and bun: they all install from the npm registry. The integrations are subpaths (`mochikao/react`, `mochikao/animals`…), and React, Vue, Svelte and Solid are optional `peerDependencies`.
192
+
193
+ **First release, by hand** (the package has to exist before automated publishing can be turned on):
194
+
195
+ ```bash
196
+ npm pack --dry-run # exact list of the files that will be published
197
+ npm login
198
+ npm publish # prepublishOnly runs types, tests and build first
199
+ ```
200
+
201
+ **Later releases, from CI** (`.github/workflows/release.yml`, Trusted Publishing: no npm token, provenance attached automatically):
202
+
203
+ 1. On npmjs.com, in the package settings: add GitHub Actions as a *trusted publisher* (repository, workflow `release.yml`, environment `npm`), then pick "Require two-factor authentication and disallow tokens".
204
+ 2. To publish:
205
+ ```bash
206
+ npm version patch # or minor / major: updates package.json and creates the tag
207
+ git push --follow-tags # the vX.Y.Z tag triggers the release
208
+ ```
209
+
210
+ CI (`.github/workflows/ci.yml`) checks types, tests and builds on Node 22 and 24 for every push and pull request, plus an audit of the published dependencies and publint.
211
+
212
+ Once published, the avatars' rendering is a contract: see Stability.
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync } from 'node:fs';
3
+ import { parseArgs } from 'node:util';
4
+ import { mochikao, resolve } from "../src/index.js";
5
+ import { animals } from "../src/families/animals.js";
6
+ import { produce } from "../src/families/produce.js";
7
+ import { parseParams } from "../src/params.js";
8
+ const USAGE = `usage : mochikao <nom> [options]
9
+
10
+ -o, --out <fichier> écrit le SVG dans un fichier (sinon stdout)
11
+ --size <px> taille (défaut 128)
12
+ --bg <fond> none | circle | squircle | square
13
+ --hue <0-360> teinte
14
+ --tone <0-1> luminosité
15
+ --expression <e> idle | happy | sad | mad | love | shy | sick | thinking
16
+ --animate <a> none | hover | always
17
+ --family <f> blob | animals | produce
18
+ --color <c> pastel | pale | mid | deep | bright | ink
19
+ --shape --eyes --mouth --extra épingle un trait
20
+ --info affiche les traits au lieu du SVG`;
21
+ const keys = ['size', 'bg', 'hue', 'tone', 'saturation', 'color', 'expression', 'animate', 'family', 'shape', 'eyes', 'mouth', 'extra'];
22
+ const { values, positionals } = parseArgs({
23
+ allowPositionals: true,
24
+ options: {
25
+ out: { type: 'string', short: 'o' },
26
+ info: { type: 'boolean' },
27
+ help: { type: 'boolean', short: 'h' },
28
+ ...Object.fromEntries(keys.map((k) => [k, { type: 'string' }])),
29
+ },
30
+ });
31
+ const name = positionals.join(' ');
32
+ if (values.help || !name) {
33
+ console.log(USAGE);
34
+ process.exit(values.help ? 0 : 1);
35
+ }
36
+ const options = parseParams(name, { get: (k) => values[k] }, [animals, produce]);
37
+ if (values.info) {
38
+ const { traits, colors, family, ...rest } = resolve(options);
39
+ console.log(JSON.stringify({ name, family: family.name, ...rest, colors }, null, 2));
40
+ }
41
+ else if (values.out) {
42
+ writeFileSync(values.out, mochikao(options));
43
+ console.error(`✓ ${values.out}`);
44
+ }
45
+ else {
46
+ process.stdout.write(`${mochikao(options)}\n`);
47
+ }
@@ -0,0 +1,20 @@
1
+ type Rgb = [number, number, number];
2
+ export interface Palette {
3
+ body: string;
4
+ shade: string;
5
+ ink: string;
6
+ cheek: string;
7
+ bg: string;
8
+ /** Contraste réel encre / corps, toujours >= 4.5. */
9
+ contrast: number;
10
+ }
11
+ export declare function hslToRgb(h: number, s: number, l: number): Rgb;
12
+ export declare function luminance([r, g, b]: Rgb): number;
13
+ export declare function contrast(a: Rgb, b: Rgb): number;
14
+ export declare function toHex([r, g, b]: Rgb): string;
15
+ /**
16
+ * Palette d'un avatar. La luminosité du corps est ajustée jusqu'à ce que
17
+ * l'encre (sombre ou claire) atteigne 4.5:1, quelle que soit la teinte.
18
+ */
19
+ export declare function palette(hue: number, tone?: number, saturation?: number): Palette;
20
+ export {};
@@ -0,0 +1,56 @@
1
+ const MIN_CONTRAST = 4.5;
2
+ export function hslToRgb(h, s, l) {
3
+ const sat = s / 100;
4
+ const lig = l / 100;
5
+ const k = (n) => (n + h / 30) % 12;
6
+ const a = sat * Math.min(lig, 1 - lig);
7
+ const f = (n) => lig - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
8
+ return [f(0), f(8), f(4)];
9
+ }
10
+ /** Arrondi 8 bits : le contraste est mesuré sur la couleur réellement émise. */
11
+ const quantize = ([r, g, b]) => [Math.round(r * 255) / 255, Math.round(g * 255) / 255, Math.round(b * 255) / 255];
12
+ export function luminance([r, g, b]) {
13
+ const lin = (c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
14
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
15
+ }
16
+ export function contrast(a, b) {
17
+ const la = luminance(a);
18
+ const lb = luminance(b);
19
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
20
+ }
21
+ export function toHex([r, g, b]) {
22
+ const h = (c) => Math.round(c * 255).toString(16).padStart(2, '0');
23
+ return `#${h(r)}${h(g)}${h(b)}`;
24
+ }
25
+ /**
26
+ * Palette d'un avatar. La luminosité du corps est ajustée jusqu'à ce que
27
+ * l'encre (sombre ou claire) atteigne 4.5:1, quelle que soit la teinte.
28
+ */
29
+ export function palette(hue, tone = 0.5, saturation = 0.74) {
30
+ const h = ((hue % 360) + 360) % 360;
31
+ const t = Math.min(1, Math.max(0, tone));
32
+ const s = Math.max(8, Math.min(1, saturation) * 100);
33
+ const dark = quantize(hslToRgb(h, 45, 12));
34
+ const light = quantize(hslToRgb(h, 60, 97));
35
+ const step = t >= 0.35 ? 1.5 : -1.5;
36
+ let l = 30 + t * 55;
37
+ for (let i = 0; i < 60; i++) {
38
+ const body = quantize(hslToRgb(h, s, l));
39
+ const cd = contrast(body, dark);
40
+ const cl = contrast(body, light);
41
+ const useDark = cd >= cl;
42
+ const best = useDark ? cd : cl;
43
+ if (best >= MIN_CONTRAST || l <= 5 || l >= 95) {
44
+ return {
45
+ body: toHex(body),
46
+ shade: toHex(hslToRgb(h, Math.min(100, s + 6), Math.max(0, l - 12))),
47
+ ink: toHex(useDark ? dark : light),
48
+ cheek: toHex(hslToRgb(350, 85, useDark ? 68 : 62)),
49
+ bg: toHex(hslToRgb((h + 28) % 360, 65, useDark ? 91 : 22)),
50
+ contrast: best,
51
+ };
52
+ }
53
+ l += step;
54
+ }
55
+ throw new Error('unreachable');
56
+ }
@@ -0,0 +1,23 @@
1
+ import { type ShapeFamily } from './index.ts';
2
+ /**
3
+ * <mochikao-avatar name="alex" animate="always" gaze></mochikao-avatar>
4
+ * Avec `gaze`, les yeux suivent le pointeur. Sans animation ni `gaze`, rend un
5
+ * simple <img> (sauf avec l'attribut `inline`).
6
+ */
7
+ export declare class MochikaoAvatar extends HTMLElement {
8
+ #private;
9
+ static observedAttributes: string[];
10
+ /** Familles utilisables via l'attribut `family` (voir defineMochikao). */
11
+ static families: readonly ShapeFamily[];
12
+ connectedCallback(): void;
13
+ disconnectedCallback(): void;
14
+ attributeChangedCallback(attr: string): void;
15
+ }
16
+ /**
17
+ * Déclare <mochikao-avatar>. Les familles passées ici deviennent utilisables par
18
+ * leur nom : `defineMochikao({ families: [animals] })` puis `family="animals"`.
19
+ */
20
+ export declare function defineMochikao({ tag, families }?: {
21
+ tag?: string;
22
+ families?: readonly ShapeFamily[];
23
+ }): void;