mochikao 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/README.fr.md +102 -1
  2. package/README.md +102 -1
  3. package/package.json +1 -1
package/README.fr.md CHANGED
@@ -16,7 +16,7 @@ Un visage pour chaque nom. `mochikao` transforme n'importe quelle chaîne (pseud
16
16
  - 5 yeux, 6 bouches, 11 accessoires (joues, taches, antenne, pousse, reflet, lunettes, lunettes noires, nœud, couronne, chapeau de fête).
17
17
  - **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.
18
18
  - **8 expressions** : `idle`, `happy`, `sad`, `mad`, `love`, `shy`, `sick`, `thinking`.
19
- - **Contraste visage/corps ≥ 4.5:1** pour toute teinte et tout ton (vérifié par les tests).
19
+ - **Contraste visage/corps ≥ 4.5:1** pour toute teinte et tout ton (vérifié par les tests). L'avatar contre *ta* page n'est pas garanti : voir [Contraste](docs/editor.fr.md#8-contraste--ce-qui-est-garanti-et-ce-qui-ne-lest-pas).
20
20
  - **Animations CSS** (respiration, clignement, coups d'œil), **transition animée entre expressions** et **regard qui suit le pointeur**.
21
21
  - Fonction, composants **React / Vue / Svelte / Solid**, web component, API HTTP et CLI.
22
22
 
@@ -156,6 +156,69 @@ Dans les URL, attributs HTML et la CLI, on joint les noms avec `+` (`?family=blo
156
156
 
157
157
  Une famille, c'est un objet `ShapeFamily` (`name`, `shapes`, `silhouette`, et en option `decorate` et `hue`) : `src/families/` montre comment en écrire une.
158
158
 
159
+ ### Laisser les utilisateurs personnaliser leur avatar
160
+
161
+ mochikao n'embarque pas de sélecteur : un avatar, c'est `nom + options épinglées`. Ton appli enregistre ce que l'utilisateur a touché et le repasse. Ne stocke que les clés épinglées ; supprimer une clé remet ce trait en « auto » (tiré du nom).
162
+
163
+ ```tsx
164
+ import { useState } from 'react';
165
+ import { Mochikao } from 'mochikao/react';
166
+ import { EYES, MOUTHS, EXTRAS, resolve, mix, blobs, type MochikaoOptions } from 'mochikao';
167
+ import { animals } from 'mochikao/animals';
168
+
169
+ const family = mix(blobs, animals); // les formes que tu proposes
170
+ type Prefs = Omit<MochikaoOptions, 'name' | 'family'>;
171
+
172
+ const CHOICES = { shape: family.shapes, eyes: EYES, mouth: MOUTHS, extra: EXTRAS } as const;
173
+
174
+ export function AvatarEditor({ userId, saved, onSave }: {
175
+ userId: string; // un id stable, pas un pseudo modifiable
176
+ saved: Prefs; // lu en base
177
+ onSave: (prefs: Prefs) => void;
178
+ }) {
179
+ const [prefs, setPrefs] = useState<Prefs>(saved);
180
+ const auto = resolve({ name: userId, family }); // ce que donne le nom quand rien n'est épinglé
181
+
182
+ // value === undefined remet le trait en « auto »
183
+ const pin = (key: keyof typeof CHOICES, value?: string) =>
184
+ setPrefs(({ [key]: _, ...rest }) => (value ? { ...rest, [key]: value } : rest));
185
+
186
+ return (
187
+ <div>
188
+ <Mochikao name={userId} family={family} size={128} {...prefs} />
189
+
190
+ {(Object.keys(CHOICES) as (keyof typeof CHOICES)[]).map((key) => (
191
+ <label key={key}>
192
+ {key}
193
+ <select value={prefs[key] ?? ''} onChange={(e) => pin(key, e.target.value || undefined)}>
194
+ <option value="">auto ({auto[key]})</option>
195
+ {CHOICES[key].map((v) => <option key={v}>{v}</option>)}
196
+ </select>
197
+ </label>
198
+ ))}
199
+
200
+ <label>
201
+ teinte
202
+ <input type="range" min={0} max={360} value={prefs.hue ?? auto.hue}
203
+ onChange={(e) => setPrefs({ ...prefs, hue: +e.target.value })} />
204
+ </label>
205
+
206
+ <button onClick={() => onSave(prefs)}>Enregistrer</button>
207
+ <button onClick={() => setPrefs({})}>Réinitialiser</button>
208
+ </div>
209
+ );
210
+ }
211
+ ```
212
+
213
+ Partout ailleurs dans l'appli, on l'affiche pareil : `<Mochikao name={user.id} family={family} {...user.avatarPrefs} />`.
214
+
215
+ - **Stocker du texte** : `toParams(prefs).toString()` donne une chaîne compacte (`shape=frog&eyes=sleepy&hue=140`) et `parseParams(userId, new URLSearchParams(str), [animals])` la relit. Les valeurs invalides sont ignorées : c'est la bonne façon de valider ce qui vient du client.
216
+ - **Même avatar ailleurs** : construis `/api/<id>.svg?family=animals+blob&<prefs>` pour les e-mails, les images OG ou les clients sans JS.
217
+ - **Aperçus** : une tuile, c'est `mochikao({ name, family, ...prefs, eyes: 'wide' })`.
218
+ - **Rester sobre** : plus on laisse épingler de traits, moins deux utilisateurs se distinguent.
219
+
220
+ Le guide complet (modèle, pièges, référence sans framework, aperçus, validation côté serveur) est dans [docs/editor.fr.md](docs/editor.fr.md), avec aussi une version Vue 3. Un exemple Vue complet, proche de l'éditeur du site, est dans [examples/vue/AvatarEditor.vue](examples/vue/AvatarEditor.vue).
221
+
159
222
  ### Transition entre expressions
160
223
 
161
224
  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.
@@ -252,6 +315,44 @@ La CI (`.github/workflows/ci.yml`) vérifie types, tests et builds sur Node 22 e
252
315
 
253
316
  Une fois publié, le rendu des avatars est un contrat : voir Stabilité.
254
317
 
318
+ ## Crédit
319
+
320
+ mochikao est sous licence MIT : tu l'utilises librement, la mention n'est pas obligatoire. Si tu l'embarques, une citation aide beaucoup, et j'aimerais voir où il atterrit :
321
+
322
+ - **Dans ton appli** : une ligne en pied de page ou sur la page « à propos », par exemple `Avatars par <a href="https://mochikao.alexvolkihar.ovh">mochikao</a>`.
323
+ - **Dans ton dépôt** : un lien vers <https://github.com/Alexvolkihar/mochikao> dans ton README (section crédits ou remerciements).
324
+ - **Dis-le-moi** : [ouvre une issue](https://github.com/Alexvolkihar/mochikao/issues/new) avec un lien vers ton projet et j'envisagerai de le lister.
325
+
326
+ ### Badge
327
+
328
+ Un petit badge SVG pour un README ou une page « à propos », en version **sombre** et **claire** (texte au-dessus de 4.5:1 dans les deux, et une bordure qui tient sur une page blanche ou sombre) :
329
+
330
+ | Sombre | Clair |
331
+ | --- | --- |
332
+ | [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-dark.svg)](https://mochikao.alexvolkihar.ovh) | [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-light.svg)](https://mochikao.alexvolkihar.ovh) |
333
+ | `badge-dark.svg` | `badge-light.svg` |
334
+
335
+ ```md
336
+ [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-dark.svg)](https://mochikao.alexvolkihar.ovh)
337
+ ```
338
+
339
+ ```html
340
+ <a href="https://mochikao.alexvolkihar.ovh"><img src="https://mochikao.alexvolkihar.ovh/badge-dark.svg" alt="avatars by mochikao" height="24"></a>
341
+ ```
342
+
343
+ Pour suivre automatiquement le thème du lecteur (README GitHub et toute page qui gère `<picture>`) :
344
+
345
+ ```html
346
+ <a href="https://mochikao.alexvolkihar.ovh">
347
+ <picture>
348
+ <source media="(prefers-color-scheme: dark)" srcset="https://mochikao.alexvolkihar.ovh/badge-light.svg">
349
+ <img src="https://mochikao.alexvolkihar.ovh/badge-dark.svg" alt="avatars by mochikao" height="24">
350
+ </picture>
351
+ </a>
352
+ ```
353
+
354
+ `badge.svg` reste un alias de la version sombre. Les fichiers sont dans [`site/public/`](site/public/) si tu préfères en héberger une copie.
355
+
255
356
  ## Licence
256
357
 
257
358
  MIT
package/README.md CHANGED
@@ -16,7 +16,7 @@ A face for every name. `mochikao` turns any string (username, email, ID) into a
16
16
  - 5 eyes, 6 mouths, 11 accessories (blush, freckles, antenna, sprout, shine, glasses, sunglasses, bow, crown, party hat).
17
17
  - **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.
18
18
  - **8 expressions**: `idle`, `happy`, `sad`, `mad`, `love`, `shy`, `sick`, `thinking`.
19
- - **Face-to-body contrast ≥ 4.5:1** for every hue and tone (checked by the tests).
19
+ - **Face-to-body contrast ≥ 4.5:1** for every hue and tone (checked by the tests). The avatar against *your* page is not guaranteed: see [Contrast](docs/editor.md#8-contrast-what-is-guaranteed-and-what-is-not).
20
20
  - **CSS animations** (breathing, blinking, glances), **animated transitions between expressions** and **eyes that follow the pointer**.
21
21
  - A function, **React / Vue / Svelte / Solid** components, a web component, an HTTP API and a CLI.
22
22
 
@@ -156,6 +156,69 @@ In URLs, HTML attributes and the CLI, join names with `+` (`?family=blob+animals
156
156
 
157
157
  A family is a `ShapeFamily` object (`name`, `shapes`, `silhouette`, and optionally `decorate` and `hue`): `src/families/` shows how to write one.
158
158
 
159
+ ### Letting users customize their avatar
160
+
161
+ mochikao has no built-in picker: an avatar is `name + pinned options`, so your app stores what the user touched and passes it back. Store only the pinned keys; removing a key puts that trait back on "auto" (drawn from the name).
162
+
163
+ ```tsx
164
+ import { useState } from 'react';
165
+ import { Mochikao } from 'mochikao/react';
166
+ import { EYES, MOUTHS, EXTRAS, resolve, mix, blobs, type MochikaoOptions } from 'mochikao';
167
+ import { animals } from 'mochikao/animals';
168
+
169
+ const family = mix(blobs, animals); // the shapes you offer
170
+ type Prefs = Omit<MochikaoOptions, 'name' | 'family'>;
171
+
172
+ const CHOICES = { shape: family.shapes, eyes: EYES, mouth: MOUTHS, extra: EXTRAS } as const;
173
+
174
+ export function AvatarEditor({ userId, saved, onSave }: {
175
+ userId: string; // a stable ID, not a renamable pseudonym
176
+ saved: Prefs; // loaded from your database
177
+ onSave: (prefs: Prefs) => void;
178
+ }) {
179
+ const [prefs, setPrefs] = useState<Prefs>(saved);
180
+ const auto = resolve({ name: userId, family }); // what the name gives when nothing is pinned
181
+
182
+ // value === undefined puts the trait back to "auto"
183
+ const pin = (key: keyof typeof CHOICES, value?: string) =>
184
+ setPrefs(({ [key]: _, ...rest }) => (value ? { ...rest, [key]: value } : rest));
185
+
186
+ return (
187
+ <div>
188
+ <Mochikao name={userId} family={family} size={128} {...prefs} />
189
+
190
+ {(Object.keys(CHOICES) as (keyof typeof CHOICES)[]).map((key) => (
191
+ <label key={key}>
192
+ {key}
193
+ <select value={prefs[key] ?? ''} onChange={(e) => pin(key, e.target.value || undefined)}>
194
+ <option value="">auto ({auto[key]})</option>
195
+ {CHOICES[key].map((v) => <option key={v}>{v}</option>)}
196
+ </select>
197
+ </label>
198
+ ))}
199
+
200
+ <label>
201
+ hue
202
+ <input type="range" min={0} max={360} value={prefs.hue ?? auto.hue}
203
+ onChange={(e) => setPrefs({ ...prefs, hue: +e.target.value })} />
204
+ </label>
205
+
206
+ <button onClick={() => onSave(prefs)}>Save</button>
207
+ <button onClick={() => setPrefs({})}>Reset</button>
208
+ </div>
209
+ );
210
+ }
211
+ ```
212
+
213
+ Anywhere else in your app, display it the same way: `<Mochikao name={user.id} family={family} {...user.avatarPrefs} />`.
214
+
215
+ - **Store text**: `toParams(prefs).toString()` gives a compact string (`shape=frog&eyes=sleepy&hue=140`) and `parseParams(userId, new URLSearchParams(str), [animals])` reads it back. Invalid values are ignored, which makes it the right way to validate what comes from the client.
216
+ - **Same avatar elsewhere**: build `/api/<id>.svg?family=animals+blob&<prefs>` for emails, OG images or non-JS clients.
217
+ - **Previews**: a tile is just `mochikao({ name, family, ...prefs, eyes: 'wide' })`.
218
+ - **Keep it small**: the more traits you let people pin, the less two users differ.
219
+
220
+ The full guide (model, pitfalls, a framework-free reference, previews, server-side validation) is in [docs/editor.md](docs/editor.md), with a Vue 3 version as well. A complete Vue example, close to the site's editor, is in [examples/vue/AvatarEditor.vue](examples/vue/AvatarEditor.vue).
221
+
159
222
  ### Transitions between expressions
160
223
 
161
224
  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.
@@ -252,6 +315,44 @@ CI (`.github/workflows/ci.yml`) checks types, tests and builds on Node 22 and 24
252
315
 
253
316
  Once published, the avatars' rendering is a contract: see Stability.
254
317
 
318
+ ## Credit
319
+
320
+ mochikao is MIT: you can use it freely, credit is not required. If you ship it, though, a mention helps a lot, and I'd love to see where it ends up:
321
+
322
+ - **In your app**: a line in the footer or the "about" page, such as `Avatars by <a href="https://mochikao.alexvolkihar.ovh">mochikao</a>`.
323
+ - **In your repo**: a link to <https://github.com/Alexvolkihar/mochikao> in your README (credits or acknowledgements section).
324
+ - **Tell me**: [open an issue](https://github.com/Alexvolkihar/mochikao/issues/new) with a link to your project and I'll consider listing it.
325
+
326
+ ### Badge
327
+
328
+ A small SVG badge for a README or an "about" page, in a **dark** and a **light** version (text above 4.5:1 in both, and a border that holds on a white or a dark page):
329
+
330
+ | Dark | Light |
331
+ | --- | --- |
332
+ | [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-dark.svg)](https://mochikao.alexvolkihar.ovh) | [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-light.svg)](https://mochikao.alexvolkihar.ovh) |
333
+ | `badge-dark.svg` | `badge-light.svg` |
334
+
335
+ ```md
336
+ [![avatars by mochikao](https://mochikao.alexvolkihar.ovh/badge-dark.svg)](https://mochikao.alexvolkihar.ovh)
337
+ ```
338
+
339
+ ```html
340
+ <a href="https://mochikao.alexvolkihar.ovh"><img src="https://mochikao.alexvolkihar.ovh/badge-dark.svg" alt="avatars by mochikao" height="24"></a>
341
+ ```
342
+
343
+ To follow the reader's theme automatically (GitHub READMEs and any page that supports `<picture>`):
344
+
345
+ ```html
346
+ <a href="https://mochikao.alexvolkihar.ovh">
347
+ <picture>
348
+ <source media="(prefers-color-scheme: dark)" srcset="https://mochikao.alexvolkihar.ovh/badge-light.svg">
349
+ <img src="https://mochikao.alexvolkihar.ovh/badge-dark.svg" alt="avatars by mochikao" height="24">
350
+ </picture>
351
+ </a>
352
+ ```
353
+
354
+ `badge.svg` is kept as an alias of the dark one. The files live in [`site/public/`](site/public/) if you prefer to host a copy.
355
+
255
356
  ## License
256
357
 
257
358
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mochikao",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Deterministic avatars: any string becomes a cute, unique SVG character. Zero dependencies, React / Vue / Svelte / Solid / web component.",
5
5
  "keywords": [
6
6
  "avatar",