mochikao 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.fr.md CHANGED
@@ -132,6 +132,28 @@ 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
 
137
159
  ### Transition entre expressions
package/README.md CHANGED
@@ -132,6 +132,28 @@ 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
 
137
159
  ### Transitions between expressions
@@ -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`;
@@ -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;
@@ -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
+ }
@@ -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) {
@@ -1,5 +1,5 @@
1
1
  import type { MochikaoOptions } from './index.ts';
2
- import type { ShapeFamily } from './family.ts';
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
  /**
@@ -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 = families.find((fam) => fam.name === src.get('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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mochikao",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
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",