mochikao 0.1.3 → 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 +124 -1
- package/README.md +124 -1
- package/dist/bin/mochikao.js +1 -1
- package/dist/src/family.d.ts +13 -0
- package/dist/src/family.js +33 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/params.d.ts +3 -1
- package/dist/src/params.js +18 -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
|
|
|
@@ -132,8 +132,93 @@ mochikao({ name: 'alex', family: produce, shape: 'lemon' });
|
|
|
132
132
|
|
|
133
133
|
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.
|
|
134
134
|
|
|
135
|
+
**Mélanger des familles.** `mix()` combine plusieurs familles : le nom choisit alors parmi toutes leurs formes, chacune avec la même chance.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { mochikao, blobs, mix } from 'mochikao';
|
|
139
|
+
|
|
140
|
+
mochikao({ name: 'alex', family: mix(blobs, animals) }); // blobs + animaux
|
|
141
|
+
mochikao({ name: 'alex', family: mix(animals, produce) }); // animaux + fruits & légumes
|
|
142
|
+
mochikao({ name: 'alex', family: mix(blobs, animals, produce) }); // tout
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Mode | `family=` | Formes |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| Blobs (défaut) | `blob` | 10 |
|
|
148
|
+
| Animaux | `animals` | 6 |
|
|
149
|
+
| Fruits & légumes | `produce` | 6 |
|
|
150
|
+
| Blobs + animaux | `animals+blob` | 16 |
|
|
151
|
+
| Blobs + fruits & légumes | `blob+produce` | 16 |
|
|
152
|
+
| Animaux + fruits & légumes | `animals+produce` | 12 |
|
|
153
|
+
| Tout | `all` (ou `animals+blob+produce`) | 22 |
|
|
154
|
+
|
|
155
|
+
Dans les URL, attributs HTML et la CLI, on joint les noms avec `+` (`?family=blob+animals`) ; `all` prend toutes les familles déclarées plus les blobs. L'ordre n'a pas d'importance, et un mélange des mêmes familles donne toujours le même avatar pour un nom.
|
|
156
|
+
|
|
135
157
|
Une famille, c'est un objet `ShapeFamily` (`name`, `shapes`, `silhouette`, et en option `decorate` et `hue`) : `src/families/` montre comment en écrire une.
|
|
136
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
|
+
|
|
137
222
|
### Transition entre expressions
|
|
138
223
|
|
|
139
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.
|
|
@@ -230,6 +315,44 @@ La CI (`.github/workflows/ci.yml`) vérifie types, tests et builds sur Node 22 e
|
|
|
230
315
|
|
|
231
316
|
Une fois publié, le rendu des avatars est un contrat : voir Stabilité.
|
|
232
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
|
+
|
|
233
356
|
## Licence
|
|
234
357
|
|
|
235
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
|
|
|
@@ -132,8 +132,93 @@ mochikao({ name: 'alex', family: produce, shape: 'lemon' });
|
|
|
132
132
|
|
|
133
133
|
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.
|
|
134
134
|
|
|
135
|
+
**Mixing families.** `mix()` combines several families: the name then picks among all their shapes, each shape equally likely.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { mochikao, blobs, mix } from 'mochikao';
|
|
139
|
+
|
|
140
|
+
mochikao({ name: 'alex', family: mix(blobs, animals) }); // blobs + animals
|
|
141
|
+
mochikao({ name: 'alex', family: mix(animals, produce) }); // animals + fruits & veggies
|
|
142
|
+
mochikao({ name: 'alex', family: mix(blobs, animals, produce) }); // everything
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Mode | `family=` | Shapes |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| Blobs (default) | `blob` | 10 |
|
|
148
|
+
| Animals | `animals` | 6 |
|
|
149
|
+
| Fruits & veggies | `produce` | 6 |
|
|
150
|
+
| Blobs + animals | `animals+blob` | 16 |
|
|
151
|
+
| Blobs + fruits & veggies | `blob+produce` | 16 |
|
|
152
|
+
| Animals + fruits & veggies | `animals+produce` | 12 |
|
|
153
|
+
| Everything | `all` (or `animals+blob+produce`) | 22 |
|
|
154
|
+
|
|
155
|
+
In URLs, HTML attributes and the CLI, join names with `+` (`?family=blob+animals`); `all` takes every family you declared plus the blobs. The order doesn't matter, and a mix with the same families always gives the same avatar for a given name.
|
|
156
|
+
|
|
135
157
|
A family is a `ShapeFamily` object (`name`, `shapes`, `silhouette`, and optionally `decorate` and `hue`): `src/families/` shows how to write one.
|
|
136
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
|
+
|
|
137
222
|
### Transitions between expressions
|
|
138
223
|
|
|
139
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.
|
|
@@ -230,6 +315,44 @@ CI (`.github/workflows/ci.yml`) checks types, tests and builds on Node 22 and 24
|
|
|
230
315
|
|
|
231
316
|
Once published, the avatars' rendering is a contract: see Stability.
|
|
232
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
|
+
|
|
233
356
|
## License
|
|
234
357
|
|
|
235
358
|
MIT
|
package/dist/bin/mochikao.js
CHANGED
|
@@ -14,7 +14,7 @@ const USAGE = `usage : mochikao <nom> [options]
|
|
|
14
14
|
--tone <0-1> luminosité
|
|
15
15
|
--expression <e> idle | happy | sad | mad | love | shy | sick | thinking
|
|
16
16
|
--animate <a> none | hover | always
|
|
17
|
-
--family <f> blob | animals | produce
|
|
17
|
+
--family <f> blob | animals | produce | all, ou un mélange : blob+animals, animals+produce...
|
|
18
18
|
--color <c> pastel | pale | mid | deep | bright | ink
|
|
19
19
|
--shape --eyes --mouth --extra épingle un trait
|
|
20
20
|
--info affiche les traits au lieu du SVG`;
|
package/dist/src/family.d.ts
CHANGED
|
@@ -49,3 +49,16 @@ export interface ShapeFamily<S extends string = string> {
|
|
|
49
49
|
hue?(shape: S): number | undefined;
|
|
50
50
|
}
|
|
51
51
|
export declare function isFamily(value: unknown): value is ShapeFamily;
|
|
52
|
+
/**
|
|
53
|
+
* Combine plusieurs familles en une : le nom choisit parmi toutes leurs formes.
|
|
54
|
+
*
|
|
55
|
+
* mix(blobs, animals) // blobs et animaux
|
|
56
|
+
* mix(blobs, animals, produce) // tout
|
|
57
|
+
*
|
|
58
|
+
* Chaque forme a la même chance d'être tirée, donc une famille pèse selon son
|
|
59
|
+
* nombre de formes. L'ordre des arguments ne compte pas : les familles sont
|
|
60
|
+
* triées par nom (le résultat s'appelle `animals+blob`), ce qui fixe la liste
|
|
61
|
+
* des formes et donc quel avatar sort pour quel nom. Si deux familles ont une
|
|
62
|
+
* forme du même nom, la première (dans l'ordre trié) l'emporte.
|
|
63
|
+
*/
|
|
64
|
+
export declare function mix(...families: ShapeFamily[]): ShapeFamily;
|
package/dist/src/family.js
CHANGED
|
@@ -3,3 +3,36 @@ export function isFamily(value) {
|
|
|
3
3
|
return typeof f === 'object' && f !== null && typeof f.name === 'string' &&
|
|
4
4
|
Array.isArray(f.shapes) && f.shapes.length > 0 && typeof f.silhouette === 'function';
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Combine plusieurs familles en une : le nom choisit parmi toutes leurs formes.
|
|
8
|
+
*
|
|
9
|
+
* mix(blobs, animals) // blobs et animaux
|
|
10
|
+
* mix(blobs, animals, produce) // tout
|
|
11
|
+
*
|
|
12
|
+
* Chaque forme a la même chance d'être tirée, donc une famille pèse selon son
|
|
13
|
+
* nombre de formes. L'ordre des arguments ne compte pas : les familles sont
|
|
14
|
+
* triées par nom (le résultat s'appelle `animals+blob`), ce qui fixe la liste
|
|
15
|
+
* des formes et donc quel avatar sort pour quel nom. Si deux familles ont une
|
|
16
|
+
* forme du même nom, la première (dans l'ordre trié) l'emporte.
|
|
17
|
+
*/
|
|
18
|
+
export function mix(...families) {
|
|
19
|
+
const unique = [...new Map(families.filter(isFamily).map((f) => [f.name, f])).values()]
|
|
20
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
21
|
+
if (unique.length === 0)
|
|
22
|
+
throw new TypeError('mix() attend au moins une famille');
|
|
23
|
+
if (unique.length === 1)
|
|
24
|
+
return unique[0];
|
|
25
|
+
const owner = new Map();
|
|
26
|
+
for (const f of unique)
|
|
27
|
+
for (const s of f.shapes)
|
|
28
|
+
if (!owner.has(s))
|
|
29
|
+
owner.set(s, f);
|
|
30
|
+
const of = (shape) => owner.get(shape) ?? unique[0];
|
|
31
|
+
return {
|
|
32
|
+
name: unique.map((f) => f.name).join('+'),
|
|
33
|
+
shapes: [...owner.keys()],
|
|
34
|
+
silhouette: (shape, traits) => of(shape).silhouette(shape, traits),
|
|
35
|
+
decorate: (shape, body, colors, eyeY) => of(shape).decorate?.(shape, body, colors, eyeY) ?? {},
|
|
36
|
+
hue: (shape) => of(shape).hue?.(shape),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type ShapeFamily } from './family.ts';
|
|
|
3
3
|
import { type Animation, type Background, type ColorPreset, type Expression, type ExtraName, type EyesName, type MouthName, type Traits } from './traits.ts';
|
|
4
4
|
export * from './traits.ts';
|
|
5
5
|
export { blobs } from './shape.ts';
|
|
6
|
-
export { isFamily } from './family.ts';
|
|
6
|
+
export { isFamily, mix } from './family.ts';
|
|
7
7
|
export type { Decoration, ShapeDef, ShapeFamily } from './family.ts';
|
|
8
8
|
export { palette, contrast, hslToRgb } from './color.ts';
|
|
9
9
|
export type { Palette } from './color.ts';
|
package/dist/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { blobs, buildBody, superellipsePath } from "./shape.js";
|
|
|
6
6
|
import { ANIMATIONS, BACKGROUNDS, COLORS, EXPRESSIONS, EXTRAS, EYES, MOUTHS, deriveTraits, pick, positionOf, } from "./traits.js";
|
|
7
7
|
export * from "./traits.js";
|
|
8
8
|
export { blobs } from "./shape.js";
|
|
9
|
-
export { isFamily } from "./family.js";
|
|
9
|
+
export { isFamily, mix } from "./family.js";
|
|
10
10
|
export { palette, contrast, hslToRgb } from "./color.js";
|
|
11
11
|
/** Ce que le nom donne, sans dessiner. */
|
|
12
12
|
export function resolve(options) {
|
package/dist/src/params.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MochikaoOptions } from './index.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ShapeFamily } from './family.ts';
|
|
3
3
|
type Source = {
|
|
4
4
|
get(key: string): string | null | undefined;
|
|
5
5
|
};
|
|
@@ -8,6 +8,8 @@ type Source = {
|
|
|
8
8
|
* Les valeurs invalides sont ignorées plutôt que de faire échouer le rendu.
|
|
9
9
|
* `families` liste les familles qu'on accepte de désigner par leur nom
|
|
10
10
|
* (`?family=animals`) ; sans elles, seuls les blobs sont disponibles.
|
|
11
|
+
* Plusieurs familles se combinent avec « + » (`?family=blob+animals`), et
|
|
12
|
+
* `family=all` prend toutes celles qu'on fournit.
|
|
11
13
|
*/
|
|
12
14
|
export declare function parseParams(name: string, src: Source, families?: readonly ShapeFamily[]): MochikaoOptions;
|
|
13
15
|
/**
|
package/dist/src/params.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mix } from "./family.js";
|
|
1
2
|
import { blobs } from "./shape.js";
|
|
2
3
|
import { ANIMATIONS, BACKGROUNDS, COLORS, CONTINUOUS_AXES, EXPRESSIONS, EXTRAS, EYES, MOUTHS } from "./traits.js";
|
|
3
4
|
const oneOf = (list, v) => v != null && list.includes(v) ? v : undefined;
|
|
@@ -7,11 +8,27 @@ const num = (v, min, max) => {
|
|
|
7
8
|
const n = Number(v);
|
|
8
9
|
return Number.isFinite(n) ? Math.min(max, Math.max(min, n)) : undefined;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* `animals`, `blob`, ou un mode combiné : `blob+animals`, `animals+produce`...
|
|
13
|
+
* `all` prend les blobs et toutes les familles fournies. Les noms inconnus
|
|
14
|
+
* sont ignorés ; sans aucun nom valide, on n'a pas de famille.
|
|
15
|
+
*/
|
|
16
|
+
function findFamily(value, families) {
|
|
17
|
+
if (!value)
|
|
18
|
+
return undefined;
|
|
19
|
+
const known = [blobs, ...families];
|
|
20
|
+
if (value === 'all')
|
|
21
|
+
return mix(...known);
|
|
22
|
+
const found = value.split(/[+ ]/).map((n) => known.find((f) => f.name === n)).filter((f) => f !== undefined);
|
|
23
|
+
return found.length ? mix(...found) : undefined;
|
|
24
|
+
}
|
|
10
25
|
/**
|
|
11
26
|
* Options depuis des paramètres texte (query string, attributs HTML, flags CLI).
|
|
12
27
|
* Les valeurs invalides sont ignorées plutôt que de faire échouer le rendu.
|
|
13
28
|
* `families` liste les familles qu'on accepte de désigner par leur nom
|
|
14
29
|
* (`?family=animals`) ; sans elles, seuls les blobs sont disponibles.
|
|
30
|
+
* Plusieurs familles se combinent avec « + » (`?family=blob+animals`), et
|
|
31
|
+
* `family=all` prend toutes celles qu'on fournit.
|
|
15
32
|
*/
|
|
16
33
|
export function parseParams(name, src, families = []) {
|
|
17
34
|
const options = { name };
|
|
@@ -26,7 +43,7 @@ export function parseParams(name, src, families = []) {
|
|
|
26
43
|
set('expression', oneOf(EXPRESSIONS, src.get('expression')));
|
|
27
44
|
set('animate', oneOf(ANIMATIONS, src.get('animate')));
|
|
28
45
|
// `family` désigne une famille par son nom, parmi celles qu'on nous a confiées.
|
|
29
|
-
const family =
|
|
46
|
+
const family = findFamily(src.get('family'), families);
|
|
30
47
|
set('family', family);
|
|
31
48
|
set('shape', oneOf((family ?? blobs).shapes, src.get('shape')));
|
|
32
49
|
set('eyes', oneOf(EYES, src.get('eyes')));
|
package/package.json
CHANGED