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.
- package/README.fr.md +102 -1
- package/README.md +102 -1
- 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
|
+
| [](https://mochikao.alexvolkihar.ovh) | [](https://mochikao.alexvolkihar.ovh) |
|
|
333
|
+
| `badge-dark.svg` | `badge-light.svg` |
|
|
334
|
+
|
|
335
|
+
```md
|
|
336
|
+
[](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
|
+
| [](https://mochikao.alexvolkihar.ovh) | [](https://mochikao.alexvolkihar.ovh) |
|
|
333
|
+
| `badge-dark.svg` | `badge-light.svg` |
|
|
334
|
+
|
|
335
|
+
```md
|
|
336
|
+
[](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