@usenavii/core 0.2.1 → 0.4.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
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';
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, O as OutfitId } from './types-BfsKZ8zK.cjs';
2
+ export { P as Palette } from './types-BfsKZ8zK.cjs';
3
3
 
4
4
  /**
5
5
  * Seed → stable PRNG stream.
@@ -130,6 +130,7 @@ interface BuildSpec {
130
130
  accessory?: AccessoryId;
131
131
  background?: BackgroundId;
132
132
  topper?: TopperId;
133
+ outfit?: OutfitId;
133
134
  hueShift?: number;
134
135
  bodyScale?: number;
135
136
  eyeGapShift?: number;
@@ -182,6 +183,37 @@ declare function build(spec?: BuildSpec, options?: AvatarOptions): string;
182
183
  * ```
183
184
  */
184
185
  declare function createAvatar(seed: string, options?: AvatarOptions): string;
186
+ /**
187
+ * Generate a random avatar without supplying a seed.
188
+ *
189
+ * Picks a fresh UUID seed via `crypto.randomUUID()` (or a `Math.random()`
190
+ * fallback when WebCrypto is unavailable) and renders the avatar. The seed
191
+ * is returned alongside the SVG so callers can **persist it** — e.g. save
192
+ * to the user's profile so future renders are stable.
193
+ *
194
+ * Use for: "spin again" UX, dev/demo seeding, lazy onboarding flows where
195
+ * the user gets an avatar before picking one.
196
+ *
197
+ * Each call returns a different avatar — DO NOT call inline in a render
198
+ * function or the avatar will change every re-render. Stabilize with a
199
+ * `useState`/`useMemo` init or persist the returned seed:
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const { svg, seed } = Navii.random({ size: 96 });
204
+ * saveToProfile(user.id, { naviiSeed: seed });
205
+ * ```
206
+ *
207
+ * @example React
208
+ * ```tsx
209
+ * const [random] = useState(() => Navii.random());
210
+ * return <Navii seed={random.seed} />;
211
+ * ```
212
+ */
213
+ declare function random(options?: AvatarOptions): {
214
+ svg: string;
215
+ seed: string;
216
+ };
185
217
  /**
186
218
  * Convenience namespace — bundles every public function under one import.
187
219
  *
@@ -197,6 +229,7 @@ declare function createAvatar(seed: string, options?: AvatarOptions): string;
197
229
  */
198
230
  declare const Navii: {
199
231
  readonly create: typeof createAvatar;
232
+ readonly random: typeof random;
200
233
  readonly render: typeof renderAvatar;
201
234
  readonly select: typeof selectAvatar;
202
235
  readonly group: typeof renderGroup;
@@ -204,4 +237,4 @@ declare const Navii: {
204
237
  readonly build: typeof build;
205
238
  };
206
239
 
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 };
240
+ export { AccessoryId, AntennaStyleId, AvatarOptions, AvatarSpec, BackgroundId, BodyShapeId, type BuildSpec, EyeStyleId, type GroupOptions, MouthStyleId, Navii, OutfitId, type SeedFields, TopperId, build, createAvatar, createRng, cyrb53, random, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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';
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, O as OutfitId } from './types-BfsKZ8zK.js';
2
+ export { P as Palette } from './types-BfsKZ8zK.js';
3
3
 
4
4
  /**
5
5
  * Seed → stable PRNG stream.
@@ -130,6 +130,7 @@ interface BuildSpec {
130
130
  accessory?: AccessoryId;
131
131
  background?: BackgroundId;
132
132
  topper?: TopperId;
133
+ outfit?: OutfitId;
133
134
  hueShift?: number;
134
135
  bodyScale?: number;
135
136
  eyeGapShift?: number;
@@ -182,6 +183,37 @@ declare function build(spec?: BuildSpec, options?: AvatarOptions): string;
182
183
  * ```
183
184
  */
184
185
  declare function createAvatar(seed: string, options?: AvatarOptions): string;
186
+ /**
187
+ * Generate a random avatar without supplying a seed.
188
+ *
189
+ * Picks a fresh UUID seed via `crypto.randomUUID()` (or a `Math.random()`
190
+ * fallback when WebCrypto is unavailable) and renders the avatar. The seed
191
+ * is returned alongside the SVG so callers can **persist it** — e.g. save
192
+ * to the user's profile so future renders are stable.
193
+ *
194
+ * Use for: "spin again" UX, dev/demo seeding, lazy onboarding flows where
195
+ * the user gets an avatar before picking one.
196
+ *
197
+ * Each call returns a different avatar — DO NOT call inline in a render
198
+ * function or the avatar will change every re-render. Stabilize with a
199
+ * `useState`/`useMemo` init or persist the returned seed:
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const { svg, seed } = Navii.random({ size: 96 });
204
+ * saveToProfile(user.id, { naviiSeed: seed });
205
+ * ```
206
+ *
207
+ * @example React
208
+ * ```tsx
209
+ * const [random] = useState(() => Navii.random());
210
+ * return <Navii seed={random.seed} />;
211
+ * ```
212
+ */
213
+ declare function random(options?: AvatarOptions): {
214
+ svg: string;
215
+ seed: string;
216
+ };
185
217
  /**
186
218
  * Convenience namespace — bundles every public function under one import.
187
219
  *
@@ -197,6 +229,7 @@ declare function createAvatar(seed: string, options?: AvatarOptions): string;
197
229
  */
198
230
  declare const Navii: {
199
231
  readonly create: typeof createAvatar;
232
+ readonly random: typeof random;
200
233
  readonly render: typeof renderAvatar;
201
234
  readonly select: typeof selectAvatar;
202
235
  readonly group: typeof renderGroup;
@@ -204,4 +237,4 @@ declare const Navii: {
204
237
  readonly build: typeof build;
205
238
  };
206
239
 
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 };
240
+ export { AccessoryId, AntennaStyleId, AvatarOptions, AvatarSpec, BackgroundId, BodyShapeId, type BuildSpec, EyeStyleId, type GroupOptions, MouthStyleId, Navii, OutfitId, type SeedFields, TopperId, build, createAvatar, createRng, cyrb53, random, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
package/dist/index.js CHANGED
@@ -557,6 +557,76 @@ function renderTopper(id, anchor, palette) {
557
557
  }
558
558
  }
559
559
 
560
+ // src/parts/outfit.ts
561
+ function renderOutfit(id, anchor, palette) {
562
+ if (id === "none") return "";
563
+ const cx = anchor.cx;
564
+ const cy = anchor.mouthY + (anchor.groundY - anchor.mouthY) * 0.55;
565
+ const ink = palette.ink;
566
+ const accent = palette.accent;
567
+ switch (id) {
568
+ case "collar":
569
+ return [
570
+ `<path d="M${cx - 9} ${cy} L${cx - 2} ${cy - 4} L${cx - 2} ${cy + 5} Z" fill="${accent}" stroke="${ink}" stroke-width="0.7" />`,
571
+ `<path d="M${cx + 9} ${cy} L${cx + 2} ${cy - 4} L${cx + 2} ${cy + 5} Z" fill="${accent}" stroke="${ink}" stroke-width="0.7" />`,
572
+ // tiny button at center
573
+ `<circle cx="${cx}" cy="${cy + 4}" r="0.9" fill="${ink}" />`
574
+ ].join("");
575
+ case "scarf":
576
+ return [
577
+ // wrap band
578
+ `<path d="M${cx - 14} ${cy - 2} Q${cx} ${cy + 3} ${cx + 14} ${cy - 2} L${cx + 14} ${cy + 3} Q${cx} ${cy + 8} ${cx - 14} ${cy + 3} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.6" />`,
579
+ // tail 1 (left)
580
+ `<path d="M${cx - 6} ${cy + 5} L${cx - 9} ${cy + 12} L${cx - 4} ${cy + 12} L${cx - 2} ${cy + 5} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.5" />`,
581
+ // tail 2 (slightly right)
582
+ `<path d="M${cx + 1} ${cy + 6} L${cx + 4} ${cy + 13} L${cx - 1} ${cy + 13} L${cx - 2} ${cy + 6} Z" fill="${palette.bodyFrom}" stroke="${ink}" stroke-width="0.5" />`
583
+ ].join("");
584
+ case "bowtie":
585
+ return [
586
+ // left wing
587
+ `<path d="M${cx - 1} ${cy} L${cx - 9} ${cy - 4} L${cx - 9} ${cy + 4} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.7" />`,
588
+ // right wing
589
+ `<path d="M${cx + 1} ${cy} L${cx + 9} ${cy - 4} L${cx + 9} ${cy + 4} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.7" />`,
590
+ // center knot
591
+ `<rect x="${cx - 1.4}" y="${cy - 2.4}" width="2.8" height="4.8" rx="0.8" fill="${palette.bodyFrom}" stroke="${ink}" stroke-width="0.5" />`
592
+ ].join("");
593
+ case "sunflower": {
594
+ const fx = cx - 8;
595
+ const fy = cy + 2;
596
+ const petals = [];
597
+ for (let i = 0; i < 8; i++) {
598
+ const a = i / 8 * Math.PI * 2;
599
+ const px = fx + Math.cos(a) * 3.2;
600
+ const py = fy + Math.sin(a) * 3.2;
601
+ petals.push(
602
+ `<ellipse cx="${px.toFixed(2)}" cy="${py.toFixed(2)}" rx="2.4" ry="1.3" fill="#FACC15" stroke="${ink}" stroke-width="0.35" transform="rotate(${(a * 180 / Math.PI).toFixed(1)} ${px.toFixed(2)} ${py.toFixed(2)})" />`
603
+ );
604
+ }
605
+ return [
606
+ // stem (tucked behind)
607
+ `<path d="M${fx + 2} ${fy + 2} Q${fx + 4} ${fy + 6} ${fx + 1} ${fy + 10}" stroke="#16A34A" stroke-width="1.1" fill="none" stroke-linecap="round" />`,
608
+ // leaf
609
+ `<path d="M${fx + 3} ${fy + 6} Q${fx + 7} ${fy + 4} ${fx + 6} ${fy + 8} Q${fx + 4} ${fy + 8} ${fx + 3} ${fy + 6} Z" fill="#22C55E" stroke="${ink}" stroke-width="0.35" />`,
610
+ ...petals,
611
+ // center disc
612
+ `<circle cx="${fx}" cy="${fy}" r="2" fill="#92400E" stroke="${ink}" stroke-width="0.4" />`,
613
+ // texture dots on disc
614
+ `<circle cx="${fx - 0.6}" cy="${fy - 0.5}" r="0.4" fill="#451A03" />`,
615
+ `<circle cx="${fx + 0.7}" cy="${fy + 0.3}" r="0.4" fill="#451A03" />`,
616
+ `<circle cx="${fx - 0.4}" cy="${fy + 0.8}" r="0.4" fill="#451A03" />`
617
+ ].join("");
618
+ }
619
+ case "necklace":
620
+ return [
621
+ // chain (curve from collarbone left → drop → right)
622
+ `<path d="M${cx - 10} ${cy} Q${cx} ${cy + 8} ${cx + 10} ${cy}" stroke="${accent}" stroke-width="0.8" fill="none" stroke-linecap="round" />`,
623
+ // pendant
624
+ `<circle cx="${cx}" cy="${cy + 7}" r="1.6" fill="${accent}" stroke="${ink}" stroke-width="0.5" />`,
625
+ `<circle cx="${cx}" cy="${cy + 7}" r="0.7" fill="${palette.blush}" />`
626
+ ].join("");
627
+ }
628
+ }
629
+
560
630
  // src/parts/index.ts
561
631
  var BODY_IDS = [
562
632
  "orb",
@@ -655,6 +725,7 @@ function selectAvatar(seed2, options = {}) {
655
725
  accessory,
656
726
  background,
657
727
  topper,
728
+ outfit: "none",
658
729
  hueShift,
659
730
  bodyScale,
660
731
  eyeGapShift,
@@ -738,10 +809,13 @@ function renderAvatarInner(spec, options = {}) {
738
809
  const antennaWrapped = antennaSvg ? `<g${transformAntenna(spec.antennaTilt ?? 0, anchor)}><g class="antenna">${antennaSvg}</g></g>` : "";
739
810
  const tileBg = resolveTileBg(options.tileBg, spec.palette);
740
811
  const tileCircle = tileBg ? `<circle cx="50" cy="50" r="50" fill="${tileBg}" />` : "";
812
+ const outfitSvg = renderOutfit(spec.outfit ?? "none", anchor, spec.palette);
741
813
  const parts = [
742
814
  tileCircle,
743
815
  renderBackground(spec.background, spec.palette, bgOverride),
744
816
  bodyWrapped,
817
+ // outfit sits on the body but below the face, so face features stay readable
818
+ outfitSvg,
745
819
  renderTopper(spec.topper, anchor, spec.palette),
746
820
  wrap("eyes", renderEyes(spec.eyes, spec.palette, anchor)),
747
821
  renderMouth(spec.mouth, spec.palette, anchor, spec.mouthCurveScale ?? 1),
@@ -857,6 +931,7 @@ function build(spec = {}, options = {}) {
857
931
  accessory: spec.accessory ?? "none",
858
932
  background: spec.background ?? "none",
859
933
  topper: spec.topper ?? "none",
934
+ outfit: spec.outfit ?? "none",
860
935
  hueShift: spec.hueShift ?? 0,
861
936
  bodyScale: spec.bodyScale ?? 1,
862
937
  eyeGapShift: spec.eyeGapShift ?? 0,
@@ -873,8 +948,18 @@ function createAvatar(seed2, options = {}) {
873
948
  }
874
949
  return renderAvatar(selectAvatar(seed2, options), options);
875
950
  }
951
+ function random(options = {}) {
952
+ const seed2 = randomSeed();
953
+ return { svg: createAvatar(seed2, options), seed: seed2 };
954
+ }
955
+ function randomSeed() {
956
+ const c = globalThis.crypto;
957
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
958
+ return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
959
+ }
876
960
  var Navii = {
877
961
  create: createAvatar,
962
+ random,
878
963
  render: renderAvatar,
879
964
  select: selectAvatar,
880
965
  group: renderGroup,
@@ -882,6 +967,6 @@ var Navii = {
882
967
  build
883
968
  };
884
969
 
885
- export { Navii, build, createAvatar, createRng, cyrb53, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
970
+ export { Navii, build, createAvatar, createRng, cyrb53, random, renderAvatar, renderAvatarInner, renderGroup, seed, selectAvatar };
886
971
  //# sourceMappingURL=index.js.map
887
972
  //# sourceMappingURL=index.js.map