mochikao 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.fr.md +216 -0
- package/README.md +215 -2
- package/dist/bin/mochikao.d.ts +2 -0
- package/dist/bin/mochikao.js +47 -0
- package/dist/src/color.d.ts +20 -0
- package/dist/src/color.js +56 -0
- package/dist/src/element.d.ts +23 -0
- package/dist/src/element.js +84 -0
- package/dist/src/face.d.ts +44 -0
- package/dist/src/face.js +214 -0
- package/dist/src/families/animals.d.ts +11 -0
- package/dist/src/families/animals.js +75 -0
- package/dist/src/families/kit.d.ts +24 -0
- package/dist/src/families/kit.js +42 -0
- package/dist/src/families/produce.d.ts +12 -0
- package/dist/src/families/produce.js +90 -0
- package/dist/src/family.d.ts +51 -0
- package/dist/src/family.js +5 -0
- package/dist/src/index.d.ts +78 -0
- package/dist/src/index.js +158 -0
- package/dist/src/morph.d.ts +15 -0
- package/dist/src/morph.js +98 -0
- package/dist/src/params.d.ts +19 -0
- package/dist/src/params.js +74 -0
- package/dist/src/random.d.ts +4 -0
- package/dist/src/random.js +25 -0
- package/dist/src/react.d.ts +12 -0
- package/dist/src/react.js +41 -0
- package/dist/src/shape.d.ts +44 -0
- package/dist/src/shape.js +130 -0
- package/dist/src/solid.d.ts +14 -0
- package/dist/src/solid.js +71 -0
- package/dist/src/traits.d.ts +81 -0
- package/dist/src/traits.js +69 -0
- package/dist/src/vue.d.ts +58 -0
- package/dist/src/vue.js +58 -0
- package/package.json +134 -4
- package/src/svelte/Mochikao.svelte +46 -0
- package/src/svelte/Mochikao.svelte.d.ts +13 -0
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
|
+

|
|
8
|
+
|
|
9
|
+

|
|
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
|
-
#
|
|
1
|
+
# mochikao
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+

|
|
8
|
+
|
|
9
|
+

|
|
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,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;
|