@usenavii/core 0.1.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as AvatarOptions, c as AvatarSpec } from './types-BlMtdWZf.cjs';
2
- export { A as AccessoryId, a as AntennaStyleId, B as BackgroundId, d as BodyShapeId, E as EyeStyleId, M as MouthStyleId, P as Palette, T as TopperId } from './types-BlMtdWZf.cjs';
1
+ import { b as AvatarOptions, c as AvatarSpec, d as BodyShapeId, E as EyeStyleId, M as MouthStyleId, a as AntennaStyleId, A as AccessoryId, B as BackgroundId, T as TopperId } from './types-BlMtdWZf.cjs';
2
+ export { P as Palette } from './types-BlMtdWZf.cjs';
3
3
 
4
4
  /**
5
5
  * Seed → stable PRNG stream.
@@ -72,9 +72,136 @@ interface GroupOptions extends AvatarOptions {
72
72
  declare function renderGroup(seeds: string[], options?: GroupOptions): string;
73
73
 
74
74
  /**
75
- * Convenience: seed SVG string in one call. Same seed always produces the
76
- * same output. Pass options to override size, palette, or background.
75
+ * Seed helper composes the most stable identifier from a user-shaped
76
+ * object into a single string for `createAvatar`.
77
+ *
78
+ * Priority: `id` → `email` → `name + createdAt` → `name` alone.
79
+ *
80
+ * The whole point: stop devs accidentally passing a display name. Names
81
+ * collide; ids and emails don't. When only a name is available, composing
82
+ * it with `createdAt` makes the result globally unique while remaining
83
+ * stable across renders (assuming `createdAt` is set once at signup).
84
+ */
85
+ interface SeedFields {
86
+ /** Stable primary key (database id, UUID, OAuth sub). Best choice. */
87
+ id?: string | number | null | undefined;
88
+ /** Email. Stable + unique. Good fallback when id isn't available client-side. */
89
+ email?: string | null | undefined;
90
+ /** Display name. Collision-prone — only acceptable composed with `createdAt`. */
91
+ name?: string | null | undefined;
92
+ /** Account creation time. Combined with `name` to bake uniqueness in at signup. */
93
+ createdAt?: string | number | Date | null | undefined;
94
+ }
95
+ /**
96
+ * Compose a stable seed string from the most unique field available.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const s = seed({ id: user.id, email: user.email, name: user.name });
101
+ * createAvatar(s);
102
+ * ```
103
+ *
104
+ * @example If only a name exists, pass `createdAt` to avoid collisions:
105
+ * ```ts
106
+ * seed({ name: 'Alice', createdAt: user.createdAt });
107
+ * // → "Alice|1700000000000"
108
+ * ```
109
+ *
110
+ * @throws if no usable field is provided.
111
+ */
112
+ declare function seed(fields: SeedFields): string;
113
+
114
+ /**
115
+ * Direct construction of an avatar from explicit part choices — no seed.
116
+ *
117
+ * Use when you need a SPECIFIC mascot rather than a deterministic one
118
+ * derived from a user id. Common uses: brand mascots, logo marks, fixed
119
+ * tests, designer curation, gallery hero shots.
120
+ *
121
+ * Any field left unspecified falls back to its first variant (mostly the
122
+ * neutral / "none" option). Continuous params default to 0 / 1 (no tweak).
123
+ */
124
+ interface BuildSpec {
125
+ palette?: string;
126
+ body?: BodyShapeId;
127
+ eyes?: EyeStyleId;
128
+ mouth?: MouthStyleId;
129
+ antenna?: AntennaStyleId;
130
+ accessory?: AccessoryId;
131
+ background?: BackgroundId;
132
+ topper?: TopperId;
133
+ hueShift?: number;
134
+ bodyScale?: number;
135
+ eyeGapShift?: number;
136
+ mouthCurveScale?: number;
137
+ antennaTilt?: number;
138
+ }
139
+ /**
140
+ * Build an SVG avatar from explicit part choices.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const svg = build({
145
+ * body: 'tall',
146
+ * eyes: 'star',
147
+ * mouth: 'grin',
148
+ * palette: 'violet',
149
+ * topper: 'crown',
150
+ * }, { size: 192 });
151
+ * ```
152
+ *
153
+ * Pass `AvatarOptions` (size, title, animated, tileBg) as the second arg —
154
+ * same shape as `createAvatar`'s options.
155
+ */
156
+ declare function build(spec?: BuildSpec, options?: AvatarOptions): string;
157
+
158
+ /**
159
+ * Render a deterministic mascot avatar from a seed.
160
+ *
161
+ * Same seed in → same SVG out, byte-identical, forever.
162
+ *
163
+ * @param seed Stable unique identifier per user. Recommended order: `user.id`
164
+ * → UUID → `user.email`. **Avoid display names** — two users called "Alice"
165
+ * would get the same avatar. **Never pass `Date.now()`** or any value that
166
+ * changes between renders; the avatar would change every refresh.
167
+ * See {@link seed} for a helper that picks the right field automatically.
168
+ *
169
+ * @param options.size Output size in px. Default 96. Range 16–1024.
170
+ * @param options.paletteId Force a color family (e.g. `'mint'`).
171
+ * @param options.background `'none' | 'solid' | 'ring'` or `{ color }`.
172
+ * @param options.tileBg Opaque disc behind the avatar. Color or `'auto'`.
173
+ * @param options.title Accessible label (sets `<title>` + `aria-label`).
174
+ * @param options.animated Emit idle motion (float, blink, sway, twinkle).
175
+ *
176
+ * @returns Self-contained `<svg>` string. Safe to embed via `<img src="data:image/svg+xml;...">`
177
+ * or to insert into the DOM directly.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const svg = createAvatar(user.id, { size: 96, animated: true });
182
+ * ```
77
183
  */
78
184
  declare function createAvatar(seed: string, options?: AvatarOptions): string;
185
+ /**
186
+ * Convenience namespace — bundles every public function under one import.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * import { Navii } from '@usenavii/core';
191
+ *
192
+ * Navii.create(user.id);
193
+ * Navii.seed({ id: user.id, email: user.email });
194
+ * Navii.build({ body: 'tall', eyes: 'star', palette: 'violet' });
195
+ * Navii.group([user1.id, user2.id, user3.id]);
196
+ * ```
197
+ */
198
+ declare const Navii: {
199
+ readonly create: typeof createAvatar;
200
+ readonly render: typeof renderAvatar;
201
+ readonly select: typeof selectAvatar;
202
+ readonly group: typeof renderGroup;
203
+ readonly seed: typeof seed;
204
+ readonly build: typeof build;
205
+ };
79
206
 
80
- export { AvatarOptions, AvatarSpec, type GroupOptions, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, selectAvatar };
207
+ export { AccessoryId, AntennaStyleId, AvatarOptions, AvatarSpec, BackgroundId, BodyShapeId, type BuildSpec, EyeStyleId, type GroupOptions, MouthStyleId, Navii, type SeedFields, TopperId, build, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as AvatarOptions, c as AvatarSpec } from './types-BlMtdWZf.js';
2
- export { A as AccessoryId, a as AntennaStyleId, B as BackgroundId, d as BodyShapeId, E as EyeStyleId, M as MouthStyleId, P as Palette, T as TopperId } from './types-BlMtdWZf.js';
1
+ import { b as AvatarOptions, c as AvatarSpec, d as BodyShapeId, E as EyeStyleId, M as MouthStyleId, a as AntennaStyleId, A as AccessoryId, B as BackgroundId, T as TopperId } from './types-BlMtdWZf.js';
2
+ export { P as Palette } from './types-BlMtdWZf.js';
3
3
 
4
4
  /**
5
5
  * Seed → stable PRNG stream.
@@ -72,9 +72,136 @@ interface GroupOptions extends AvatarOptions {
72
72
  declare function renderGroup(seeds: string[], options?: GroupOptions): string;
73
73
 
74
74
  /**
75
- * Convenience: seed SVG string in one call. Same seed always produces the
76
- * same output. Pass options to override size, palette, or background.
75
+ * Seed helper composes the most stable identifier from a user-shaped
76
+ * object into a single string for `createAvatar`.
77
+ *
78
+ * Priority: `id` → `email` → `name + createdAt` → `name` alone.
79
+ *
80
+ * The whole point: stop devs accidentally passing a display name. Names
81
+ * collide; ids and emails don't. When only a name is available, composing
82
+ * it with `createdAt` makes the result globally unique while remaining
83
+ * stable across renders (assuming `createdAt` is set once at signup).
84
+ */
85
+ interface SeedFields {
86
+ /** Stable primary key (database id, UUID, OAuth sub). Best choice. */
87
+ id?: string | number | null | undefined;
88
+ /** Email. Stable + unique. Good fallback when id isn't available client-side. */
89
+ email?: string | null | undefined;
90
+ /** Display name. Collision-prone — only acceptable composed with `createdAt`. */
91
+ name?: string | null | undefined;
92
+ /** Account creation time. Combined with `name` to bake uniqueness in at signup. */
93
+ createdAt?: string | number | Date | null | undefined;
94
+ }
95
+ /**
96
+ * Compose a stable seed string from the most unique field available.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const s = seed({ id: user.id, email: user.email, name: user.name });
101
+ * createAvatar(s);
102
+ * ```
103
+ *
104
+ * @example If only a name exists, pass `createdAt` to avoid collisions:
105
+ * ```ts
106
+ * seed({ name: 'Alice', createdAt: user.createdAt });
107
+ * // → "Alice|1700000000000"
108
+ * ```
109
+ *
110
+ * @throws if no usable field is provided.
111
+ */
112
+ declare function seed(fields: SeedFields): string;
113
+
114
+ /**
115
+ * Direct construction of an avatar from explicit part choices — no seed.
116
+ *
117
+ * Use when you need a SPECIFIC mascot rather than a deterministic one
118
+ * derived from a user id. Common uses: brand mascots, logo marks, fixed
119
+ * tests, designer curation, gallery hero shots.
120
+ *
121
+ * Any field left unspecified falls back to its first variant (mostly the
122
+ * neutral / "none" option). Continuous params default to 0 / 1 (no tweak).
123
+ */
124
+ interface BuildSpec {
125
+ palette?: string;
126
+ body?: BodyShapeId;
127
+ eyes?: EyeStyleId;
128
+ mouth?: MouthStyleId;
129
+ antenna?: AntennaStyleId;
130
+ accessory?: AccessoryId;
131
+ background?: BackgroundId;
132
+ topper?: TopperId;
133
+ hueShift?: number;
134
+ bodyScale?: number;
135
+ eyeGapShift?: number;
136
+ mouthCurveScale?: number;
137
+ antennaTilt?: number;
138
+ }
139
+ /**
140
+ * Build an SVG avatar from explicit part choices.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const svg = build({
145
+ * body: 'tall',
146
+ * eyes: 'star',
147
+ * mouth: 'grin',
148
+ * palette: 'violet',
149
+ * topper: 'crown',
150
+ * }, { size: 192 });
151
+ * ```
152
+ *
153
+ * Pass `AvatarOptions` (size, title, animated, tileBg) as the second arg —
154
+ * same shape as `createAvatar`'s options.
155
+ */
156
+ declare function build(spec?: BuildSpec, options?: AvatarOptions): string;
157
+
158
+ /**
159
+ * Render a deterministic mascot avatar from a seed.
160
+ *
161
+ * Same seed in → same SVG out, byte-identical, forever.
162
+ *
163
+ * @param seed Stable unique identifier per user. Recommended order: `user.id`
164
+ * → UUID → `user.email`. **Avoid display names** — two users called "Alice"
165
+ * would get the same avatar. **Never pass `Date.now()`** or any value that
166
+ * changes between renders; the avatar would change every refresh.
167
+ * See {@link seed} for a helper that picks the right field automatically.
168
+ *
169
+ * @param options.size Output size in px. Default 96. Range 16–1024.
170
+ * @param options.paletteId Force a color family (e.g. `'mint'`).
171
+ * @param options.background `'none' | 'solid' | 'ring'` or `{ color }`.
172
+ * @param options.tileBg Opaque disc behind the avatar. Color or `'auto'`.
173
+ * @param options.title Accessible label (sets `<title>` + `aria-label`).
174
+ * @param options.animated Emit idle motion (float, blink, sway, twinkle).
175
+ *
176
+ * @returns Self-contained `<svg>` string. Safe to embed via `<img src="data:image/svg+xml;...">`
177
+ * or to insert into the DOM directly.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const svg = createAvatar(user.id, { size: 96, animated: true });
182
+ * ```
77
183
  */
78
184
  declare function createAvatar(seed: string, options?: AvatarOptions): string;
185
+ /**
186
+ * Convenience namespace — bundles every public function under one import.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * import { Navii } from '@usenavii/core';
191
+ *
192
+ * Navii.create(user.id);
193
+ * Navii.seed({ id: user.id, email: user.email });
194
+ * Navii.build({ body: 'tall', eyes: 'star', palette: 'violet' });
195
+ * Navii.group([user1.id, user2.id, user3.id]);
196
+ * ```
197
+ */
198
+ declare const Navii: {
199
+ readonly create: typeof createAvatar;
200
+ readonly render: typeof renderAvatar;
201
+ readonly select: typeof selectAvatar;
202
+ readonly group: typeof renderGroup;
203
+ readonly seed: typeof seed;
204
+ readonly build: typeof build;
205
+ };
79
206
 
80
- export { AvatarOptions, AvatarSpec, type GroupOptions, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, selectAvatar };
207
+ export { AccessoryId, AntennaStyleId, AvatarOptions, AvatarSpec, BackgroundId, BodyShapeId, type BuildSpec, EyeStyleId, type GroupOptions, MouthStyleId, Navii, type SeedFields, TopperId, build, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
package/dist/index.js CHANGED
@@ -13,9 +13,9 @@ function cyrb53(input, salt = 0) {
13
13
  h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
14
14
  return [h1 >>> 0, h2 >>> 0];
15
15
  }
16
- function createRng(seed) {
17
- const [a, b] = cyrb53(seed, 0);
18
- const [c, d] = cyrb53(seed, 1);
16
+ function createRng(seed2) {
17
+ const [a, b] = cyrb53(seed2, 0);
18
+ const [c, d] = cyrb53(seed2, 1);
19
19
  let s0 = a >>> 0;
20
20
  let s1 = b >>> 0;
21
21
  let s2 = c >>> 0;
@@ -621,8 +621,8 @@ var TOPPER_IDS = [
621
621
  ];
622
622
 
623
623
  // src/select.ts
624
- function selectAvatar(seed, options = {}) {
625
- const rng = createRng(seed);
624
+ function selectAvatar(seed2, options = {}) {
625
+ const rng = createRng(seed2);
626
626
  const paletteOverride = options.paletteId ? PALETTE_BY_ID[options.paletteId] : void 0;
627
627
  const palette = paletteOverride ?? rng.pick(PALETTES);
628
628
  const body = rng.pick(BODY_IDS);
@@ -646,7 +646,7 @@ function selectAvatar(seed, options = {}) {
646
646
  const mouthCurveScale = Number(rng.range(0.85, 1.15).toFixed(3));
647
647
  const antennaTilt = Math.round(rng.range(-8, 8));
648
648
  return {
649
- seed,
649
+ seed: seed2,
650
650
  palette,
651
651
  body,
652
652
  eyes,
@@ -775,9 +775,9 @@ function wrap(cls, inner) {
775
775
  if (!inner) return inner;
776
776
  return `<g class="${cls}">${inner}</g>`;
777
777
  }
778
- function stableId(seed) {
778
+ function stableId(seed2) {
779
779
  let h = 5381;
780
- for (let i = 0; i < seed.length; i++) h = (h << 5) + h + seed.charCodeAt(i) | 0;
780
+ for (let i = 0; i < seed2.length; i++) h = (h << 5) + h + seed2.charCodeAt(i) | 0;
781
781
  return (h >>> 0).toString(36);
782
782
  }
783
783
  function escapeXml(s) {
@@ -801,9 +801,9 @@ function renderGroup(seeds, options = {}) {
801
801
  const tileCount = visibleSeeds.length + (overflow > 0 ? 1 : 0);
802
802
  const step = size * (1 - overlap);
803
803
  const totalWidth = tileCount > 0 ? step * (tileCount - 1) + size : 0;
804
- const tiles = visibleSeeds.map((seed, i) => {
804
+ const tiles = visibleSeeds.map((seed2, i) => {
805
805
  const x = i * step;
806
- const spec = selectAvatar(seed, options);
806
+ const spec = selectAvatar(seed2, options);
807
807
  const bgCircle = tileBg !== "transparent" ? `<circle cx="50" cy="50" r="50" fill="${tileBg}" />` : "";
808
808
  return `<svg x="${x}" y="0" width="${size}" height="${size}" viewBox="0 0 100 100" overflow="visible">
809
809
  <defs><clipPath id="navii-clip"><circle cx="50" cy="50" r="50" /></clipPath></defs>
@@ -825,14 +825,63 @@ function clamp(n, lo, hi) {
825
825
  return Math.max(lo, Math.min(hi, n));
826
826
  }
827
827
 
828
+ // src/seed.ts
829
+ function seed(fields) {
830
+ if (fields.id !== null && fields.id !== void 0 && String(fields.id).length > 0) {
831
+ return String(fields.id);
832
+ }
833
+ if (fields.email && fields.email.length > 0) {
834
+ return fields.email;
835
+ }
836
+ if (fields.name && fields.name.length > 0) {
837
+ if (fields.createdAt !== null && fields.createdAt !== void 0) {
838
+ const ts = fields.createdAt instanceof Date ? fields.createdAt.getTime() : typeof fields.createdAt === "number" ? fields.createdAt : Date.parse(fields.createdAt);
839
+ if (Number.isFinite(ts)) return `${fields.name}|${ts}`;
840
+ return `${fields.name}|${fields.createdAt}`;
841
+ }
842
+ return fields.name;
843
+ }
844
+ throw new Error("navii: seed() requires at least one of { id, email, name }");
845
+ }
846
+
847
+ // src/build.ts
848
+ function build(spec = {}, options = {}) {
849
+ const palette = spec.palette ? PALETTE_BY_ID[spec.palette] ?? PALETTES[0] : PALETTES[0];
850
+ const resolved = {
851
+ seed: "__build__",
852
+ palette,
853
+ body: spec.body ?? "orb",
854
+ eyes: spec.eyes ?? "round",
855
+ mouth: spec.mouth ?? "smile",
856
+ antenna: spec.antenna ?? "none",
857
+ accessory: spec.accessory ?? "none",
858
+ background: spec.background ?? "none",
859
+ topper: spec.topper ?? "none",
860
+ hueShift: spec.hueShift ?? 0,
861
+ bodyScale: spec.bodyScale ?? 1,
862
+ eyeGapShift: spec.eyeGapShift ?? 0,
863
+ mouthCurveScale: spec.mouthCurveScale ?? 1,
864
+ antennaTilt: spec.antennaTilt ?? 0
865
+ };
866
+ return renderAvatar(resolved, options);
867
+ }
868
+
828
869
  // src/index.ts
829
- function createAvatar(seed, options = {}) {
830
- if (typeof seed !== "string" || seed.length === 0) {
870
+ function createAvatar(seed2, options = {}) {
871
+ if (typeof seed2 !== "string" || seed2.length === 0) {
831
872
  throw new Error("navii: seed must be a non-empty string");
832
873
  }
833
- return renderAvatar(selectAvatar(seed, options), options);
874
+ return renderAvatar(selectAvatar(seed2, options), options);
834
875
  }
876
+ var Navii = {
877
+ create: createAvatar,
878
+ render: renderAvatar,
879
+ select: selectAvatar,
880
+ group: renderGroup,
881
+ seed,
882
+ build
883
+ };
835
884
 
836
- export { createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, selectAvatar };
885
+ export { Navii, build, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
837
886
  //# sourceMappingURL=index.js.map
838
887
  //# sourceMappingURL=index.js.map