@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/README.md
CHANGED
|
@@ -64,6 +64,7 @@ It picks the most unique field automatically: `id` → `email` → `name + creat
|
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
66
|
createAvatar(seed: string, options?: AvatarOptions): string
|
|
67
|
+
random(options?: AvatarOptions): { svg: string; seed: string }
|
|
67
68
|
selectAvatar(seed: string, options?: AvatarOptions): AvatarSpec
|
|
68
69
|
renderAvatar(spec: AvatarSpec, options?: AvatarOptions): string
|
|
69
70
|
renderGroup(seeds: string[], options?: GroupOptions): string
|
|
@@ -71,7 +72,7 @@ renderGroup(seeds: string[], options?: GroupOptions): string
|
|
|
71
72
|
seed(fields: SeedFields): string // pick most-unique field
|
|
72
73
|
build(spec?: BuildSpec, opts?): string // manual mix-and-match (no seed)
|
|
73
74
|
|
|
74
|
-
Navii.{ create, render, select, group, seed, build }
|
|
75
|
+
Navii.{ create, random, render, select, group, seed, build }
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
### `AvatarOptions`
|
|
@@ -85,6 +86,22 @@ Navii.{ create, render, select, group, seed, build }
|
|
|
85
86
|
| `title` | accessible label (sets `<title>` + `aria-label`) | none |
|
|
86
87
|
| `animated` | `boolean` — idle float / blink / sway / pulse / twinkle | `false` |
|
|
87
88
|
|
|
89
|
+
### `random()` — pick a seed for you
|
|
90
|
+
|
|
91
|
+
For "spin again" UX, onboarding before the user picks an avatar, dev/demo seeding. Returns the chosen seed so you can persist it:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const { svg, seed } = Navii.random({ size: 96 });
|
|
95
|
+
await db.users.update(user.id, { naviiSeed: seed });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Calling `random()` in a React render gives a new avatar every re-render. Stabilize with `useState`:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const [{ seed }] = useState(() => Navii.random());
|
|
102
|
+
return <Navii seed={seed} />;
|
|
103
|
+
```
|
|
104
|
+
|
|
88
105
|
### `build()` — direct construction without a seed
|
|
89
106
|
|
|
90
107
|
Use for brand mascots, logo marks, designer-curated avatars:
|
package/dist/index.cjs
CHANGED
|
@@ -559,6 +559,76 @@ function renderTopper(id, anchor, palette) {
|
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
+
// src/parts/outfit.ts
|
|
563
|
+
function renderOutfit(id, anchor, palette) {
|
|
564
|
+
if (id === "none") return "";
|
|
565
|
+
const cx = anchor.cx;
|
|
566
|
+
const cy = anchor.mouthY + (anchor.groundY - anchor.mouthY) * 0.55;
|
|
567
|
+
const ink = palette.ink;
|
|
568
|
+
const accent = palette.accent;
|
|
569
|
+
switch (id) {
|
|
570
|
+
case "collar":
|
|
571
|
+
return [
|
|
572
|
+
`<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" />`,
|
|
573
|
+
`<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" />`,
|
|
574
|
+
// tiny button at center
|
|
575
|
+
`<circle cx="${cx}" cy="${cy + 4}" r="0.9" fill="${ink}" />`
|
|
576
|
+
].join("");
|
|
577
|
+
case "scarf":
|
|
578
|
+
return [
|
|
579
|
+
// wrap band
|
|
580
|
+
`<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" />`,
|
|
581
|
+
// tail 1 (left)
|
|
582
|
+
`<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" />`,
|
|
583
|
+
// tail 2 (slightly right)
|
|
584
|
+
`<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" />`
|
|
585
|
+
].join("");
|
|
586
|
+
case "bowtie":
|
|
587
|
+
return [
|
|
588
|
+
// left 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
|
+
// right wing
|
|
591
|
+
`<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" />`,
|
|
592
|
+
// center knot
|
|
593
|
+
`<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" />`
|
|
594
|
+
].join("");
|
|
595
|
+
case "sunflower": {
|
|
596
|
+
const fx = cx - 8;
|
|
597
|
+
const fy = cy + 2;
|
|
598
|
+
const petals = [];
|
|
599
|
+
for (let i = 0; i < 8; i++) {
|
|
600
|
+
const a = i / 8 * Math.PI * 2;
|
|
601
|
+
const px = fx + Math.cos(a) * 3.2;
|
|
602
|
+
const py = fy + Math.sin(a) * 3.2;
|
|
603
|
+
petals.push(
|
|
604
|
+
`<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)})" />`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return [
|
|
608
|
+
// stem (tucked behind)
|
|
609
|
+
`<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" />`,
|
|
610
|
+
// leaf
|
|
611
|
+
`<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" />`,
|
|
612
|
+
...petals,
|
|
613
|
+
// center disc
|
|
614
|
+
`<circle cx="${fx}" cy="${fy}" r="2" fill="#92400E" stroke="${ink}" stroke-width="0.4" />`,
|
|
615
|
+
// texture dots on disc
|
|
616
|
+
`<circle cx="${fx - 0.6}" cy="${fy - 0.5}" r="0.4" fill="#451A03" />`,
|
|
617
|
+
`<circle cx="${fx + 0.7}" cy="${fy + 0.3}" r="0.4" fill="#451A03" />`,
|
|
618
|
+
`<circle cx="${fx - 0.4}" cy="${fy + 0.8}" r="0.4" fill="#451A03" />`
|
|
619
|
+
].join("");
|
|
620
|
+
}
|
|
621
|
+
case "necklace":
|
|
622
|
+
return [
|
|
623
|
+
// chain (curve from collarbone left → drop → right)
|
|
624
|
+
`<path d="M${cx - 10} ${cy} Q${cx} ${cy + 8} ${cx + 10} ${cy}" stroke="${accent}" stroke-width="0.8" fill="none" stroke-linecap="round" />`,
|
|
625
|
+
// pendant
|
|
626
|
+
`<circle cx="${cx}" cy="${cy + 7}" r="1.6" fill="${accent}" stroke="${ink}" stroke-width="0.5" />`,
|
|
627
|
+
`<circle cx="${cx}" cy="${cy + 7}" r="0.7" fill="${palette.blush}" />`
|
|
628
|
+
].join("");
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
562
632
|
// src/parts/index.ts
|
|
563
633
|
var BODY_IDS = [
|
|
564
634
|
"orb",
|
|
@@ -657,6 +727,7 @@ function selectAvatar(seed2, options = {}) {
|
|
|
657
727
|
accessory,
|
|
658
728
|
background,
|
|
659
729
|
topper,
|
|
730
|
+
outfit: "none",
|
|
660
731
|
hueShift,
|
|
661
732
|
bodyScale,
|
|
662
733
|
eyeGapShift,
|
|
@@ -740,10 +811,13 @@ function renderAvatarInner(spec, options = {}) {
|
|
|
740
811
|
const antennaWrapped = antennaSvg ? `<g${transformAntenna(spec.antennaTilt ?? 0, anchor)}><g class="antenna">${antennaSvg}</g></g>` : "";
|
|
741
812
|
const tileBg = resolveTileBg(options.tileBg, spec.palette);
|
|
742
813
|
const tileCircle = tileBg ? `<circle cx="50" cy="50" r="50" fill="${tileBg}" />` : "";
|
|
814
|
+
const outfitSvg = renderOutfit(spec.outfit ?? "none", anchor, spec.palette);
|
|
743
815
|
const parts = [
|
|
744
816
|
tileCircle,
|
|
745
817
|
renderBackground(spec.background, spec.palette, bgOverride),
|
|
746
818
|
bodyWrapped,
|
|
819
|
+
// outfit sits on the body but below the face, so face features stay readable
|
|
820
|
+
outfitSvg,
|
|
747
821
|
renderTopper(spec.topper, anchor, spec.palette),
|
|
748
822
|
wrap("eyes", renderEyes(spec.eyes, spec.palette, anchor)),
|
|
749
823
|
renderMouth(spec.mouth, spec.palette, anchor, spec.mouthCurveScale ?? 1),
|
|
@@ -859,6 +933,7 @@ function build(spec = {}, options = {}) {
|
|
|
859
933
|
accessory: spec.accessory ?? "none",
|
|
860
934
|
background: spec.background ?? "none",
|
|
861
935
|
topper: spec.topper ?? "none",
|
|
936
|
+
outfit: spec.outfit ?? "none",
|
|
862
937
|
hueShift: spec.hueShift ?? 0,
|
|
863
938
|
bodyScale: spec.bodyScale ?? 1,
|
|
864
939
|
eyeGapShift: spec.eyeGapShift ?? 0,
|
|
@@ -875,8 +950,18 @@ function createAvatar(seed2, options = {}) {
|
|
|
875
950
|
}
|
|
876
951
|
return renderAvatar(selectAvatar(seed2, options), options);
|
|
877
952
|
}
|
|
953
|
+
function random(options = {}) {
|
|
954
|
+
const seed2 = randomSeed();
|
|
955
|
+
return { svg: createAvatar(seed2, options), seed: seed2 };
|
|
956
|
+
}
|
|
957
|
+
function randomSeed() {
|
|
958
|
+
const c = globalThis.crypto;
|
|
959
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
960
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
961
|
+
}
|
|
878
962
|
var Navii = {
|
|
879
963
|
create: createAvatar,
|
|
964
|
+
random,
|
|
880
965
|
render: renderAvatar,
|
|
881
966
|
select: selectAvatar,
|
|
882
967
|
group: renderGroup,
|
|
@@ -889,6 +974,7 @@ exports.build = build;
|
|
|
889
974
|
exports.createAvatar = createAvatar;
|
|
890
975
|
exports.createRng = createRng;
|
|
891
976
|
exports.cyrb53 = cyrb53;
|
|
977
|
+
exports.random = random;
|
|
892
978
|
exports.renderAvatar = renderAvatar;
|
|
893
979
|
exports.renderAvatarInner = renderAvatarInner;
|
|
894
980
|
exports.renderGroup = renderGroup;
|