@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/README.md +18 -1
- package/dist/index.cjs +86 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +86 -1
- package/dist/index.js.map +1 -1
- package/dist/parts.cjs +83 -0
- package/dist/parts.cjs.map +1 -1
- package/dist/parts.d.cts +14 -2
- package/dist/parts.d.ts +14 -2
- package/dist/parts.js +82 -1
- package/dist/parts.js.map +1 -1
- package/dist/{types-BlMtdWZf.d.cts → types-BfsKZ8zK.d.cts} +3 -1
- package/dist/{types-BlMtdWZf.d.ts → types-BfsKZ8zK.d.ts} +3 -1
- package/package.json +1 -1
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-
|
|
2
|
-
export { P as Palette } from './types-
|
|
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-
|
|
2
|
-
export { P as Palette } from './types-
|
|
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
|