@usenavii/core 0.4.0 → 0.5.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/CHANGELOG.md +133 -0
- package/README.md +3 -3
- package/dist/index.cjs +816 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -3
- package/dist/index.d.ts +151 -3
- package/dist/index.js +814 -62
- package/dist/index.js.map +1 -1
- package/dist/parts.cjs +262 -43
- package/dist/parts.cjs.map +1 -1
- package/dist/parts.d.cts +16 -6
- package/dist/parts.d.ts +16 -6
- package/dist/parts.js +262 -43
- package/dist/parts.js.map +1 -1
- package/dist/types-CF0rfKly.d.cts +120 -0
- package/dist/types-CF0rfKly.d.ts +120 -0
- package/package.json +3 -2
- package/dist/types-BfsKZ8zK.d.cts +0 -66
- package/dist/types-BfsKZ8zK.d.ts +0 -66
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,63 @@ var ANCHORS = {
|
|
|
189
189
|
groundY: 94,
|
|
190
190
|
cheekY: 53,
|
|
191
191
|
cheekOffset: 12
|
|
192
|
+
},
|
|
193
|
+
// Squircle — FULL-BLEED. Body fills 0-100, face composed as ID portrait:
|
|
194
|
+
// upper-third eye line, lower-third mouth, generous cheek/eye spread.
|
|
195
|
+
squircle: {
|
|
196
|
+
cx: 50,
|
|
197
|
+
eyeY: 44,
|
|
198
|
+
eyeOffset: 13,
|
|
199
|
+
eyeScale: 1,
|
|
200
|
+
mouthY: 62,
|
|
201
|
+
mouthSpan: 8,
|
|
202
|
+
topperX: 50,
|
|
203
|
+
topperY: 8,
|
|
204
|
+
groundY: 98,
|
|
205
|
+
cheekY: 52,
|
|
206
|
+
cheekOffset: 24
|
|
207
|
+
},
|
|
208
|
+
// Pumpkin — round body w/ slight horizontal lobing. Face mid-low, stem on top.
|
|
209
|
+
pumpkin: {
|
|
210
|
+
cx: 50,
|
|
211
|
+
eyeY: 52,
|
|
212
|
+
eyeOffset: 11,
|
|
213
|
+
eyeScale: 1,
|
|
214
|
+
mouthY: 66,
|
|
215
|
+
mouthSpan: 9,
|
|
216
|
+
topperX: 50,
|
|
217
|
+
topperY: 18,
|
|
218
|
+
groundY: 88,
|
|
219
|
+
cheekY: 60,
|
|
220
|
+
cheekOffset: 20
|
|
221
|
+
},
|
|
222
|
+
// Ghost — tall wavy silhouette. Face high (under the "hood"), wavy hem below.
|
|
223
|
+
ghost: {
|
|
224
|
+
cx: 50,
|
|
225
|
+
eyeY: 42,
|
|
226
|
+
eyeOffset: 8,
|
|
227
|
+
eyeScale: 1.05,
|
|
228
|
+
mouthY: 54,
|
|
229
|
+
mouthSpan: 6,
|
|
230
|
+
topperX: 50,
|
|
231
|
+
topperY: 12,
|
|
232
|
+
groundY: 92,
|
|
233
|
+
cheekY: 50,
|
|
234
|
+
cheekOffset: 14
|
|
235
|
+
},
|
|
236
|
+
// SkullHead — slightly elongated egg w/ deep eye sockets.
|
|
237
|
+
skullHead: {
|
|
238
|
+
cx: 50,
|
|
239
|
+
eyeY: 50,
|
|
240
|
+
eyeOffset: 10,
|
|
241
|
+
eyeScale: 1,
|
|
242
|
+
mouthY: 70,
|
|
243
|
+
mouthSpan: 7,
|
|
244
|
+
topperX: 50,
|
|
245
|
+
topperY: 16,
|
|
246
|
+
groundY: 90,
|
|
247
|
+
cheekY: 60,
|
|
248
|
+
cheekOffset: 16
|
|
192
249
|
}
|
|
193
250
|
};
|
|
194
251
|
|
|
@@ -209,27 +266,49 @@ var BODY_PATHS = {
|
|
|
209
266
|
// Taro — gourd shape: small head bulge, fuller bottom
|
|
210
267
|
taro: "M50 14 C58 14 64 22 64 30 C64 36 60 40 60 46 C60 54 76 60 78 76 C80 88 66 91 50 91 C34 91 20 88 22 76 C24 60 40 54 40 46 C40 40 36 36 36 30 C36 22 42 14 50 14 Z",
|
|
211
268
|
// Wisp — tall narrow body, slight bottom flare, ghost-like
|
|
212
|
-
wisp: "M50 12 C60 12 66 24 66 40 C66 60 74 78 70 90 C64 96 36 96 30 90 C26 78 34 60 34 40 C34 24 40 12 50 12 Z"
|
|
269
|
+
wisp: "M50 12 C60 12 66 24 66 40 C66 60 74 78 70 90 C64 96 36 96 30 90 C26 78 34 60 34 40 C34 24 40 12 50 12 Z",
|
|
270
|
+
// Squircle — FULL-BLEED corporate plate. Fills entire viewport with tight
|
|
271
|
+
// corner radius (~4px). Reads as a tile / ID photo not a contained avatar.
|
|
272
|
+
// Pairs with `flat: true` packs (Office) — face floats on a wall of color.
|
|
273
|
+
squircle: "M4 0 C2 0 0 2 0 4 C0 35 0 65 0 96 C0 98 2 100 4 100 C35 100 65 100 96 100 C98 100 100 98 100 96 C100 65 100 35 100 4 C100 2 98 0 96 0 C65 0 35 0 4 0 Z",
|
|
274
|
+
// Pumpkin — round, slightly wider than tall, with subtle horizontal "lobes"
|
|
275
|
+
// for the iconic carved-pumpkin gourd shape. Stem area kept clear at top.
|
|
276
|
+
pumpkin: "M50 18 C70 18 86 30 86 52 C86 74 70 88 50 88 C30 88 14 74 14 52 C14 30 30 18 50 18 Z",
|
|
277
|
+
// Ghost — soft rounded top with wavy bottom hem (3 humps), evoking a sheet.
|
|
278
|
+
ghost: "M50 12 C68 12 78 24 78 42 C78 60 80 76 80 88 L74 84 L68 90 L62 84 L56 90 L50 84 L44 90 L38 84 L32 90 L26 84 L20 88 C20 76 22 60 22 42 C22 24 32 12 50 12 Z",
|
|
279
|
+
// SkullHead — egg-ish shape, slight pinch at jaw for skull silhouette.
|
|
280
|
+
skullHead: "M50 16 C68 16 80 30 80 50 C80 64 76 72 70 78 L68 86 L60 88 L60 82 L40 82 L40 88 L32 86 L30 78 C24 72 20 64 20 50 C20 30 32 16 50 16 Z"
|
|
213
281
|
};
|
|
214
|
-
function renderBodyDefs(_id,
|
|
282
|
+
function renderBodyDefs(_id, palette, gradId, opts) {
|
|
283
|
+
if (opts?.flat) {
|
|
284
|
+
return `<radialGradient id="${gradId}"><stop offset="0%" stop-color="${palette.bodyFrom}" /><stop offset="100%" stop-color="${palette.bodyFrom}" /></radialGradient>`;
|
|
285
|
+
}
|
|
215
286
|
return `
|
|
216
287
|
<radialGradient id="${gradId}" cx="42%" cy="32%" r="68%">
|
|
217
|
-
<stop offset="0%" stop-color="${
|
|
218
|
-
<stop offset="100%" stop-color="${
|
|
288
|
+
<stop offset="0%" stop-color="${palette.bodyFrom}" />
|
|
289
|
+
<stop offset="100%" stop-color="${palette.bodyTo}" />
|
|
219
290
|
</radialGradient>`.trim();
|
|
220
291
|
}
|
|
221
|
-
function renderBody(id, palette, gradId) {
|
|
292
|
+
function renderBody(id, palette, gradId, opts) {
|
|
222
293
|
const d = BODY_PATHS[id];
|
|
223
294
|
const a = ANCHORS[id];
|
|
295
|
+
const flat = opts?.flat === true;
|
|
224
296
|
const outlineColor = withAlpha(palette.ink, 0.18);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
297
|
+
const parts = [];
|
|
298
|
+
if (!flat) {
|
|
299
|
+
parts.push(
|
|
300
|
+
`<ellipse cx="${a.cx}" cy="${a.groundY + 4}" rx="22" ry="2.6" fill="${palette.ink}" opacity="0.16" />`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
parts.push(
|
|
304
|
+
flat ? `<path d="${d}" fill="url(#${gradId})" />` : `<path d="${d}" fill="url(#${gradId})" stroke="${outlineColor}" stroke-width="0.7" />`
|
|
305
|
+
);
|
|
306
|
+
if (!flat) {
|
|
307
|
+
parts.push(
|
|
308
|
+
`<ellipse cx="${a.cx - 12}" cy="${a.eyeY - 14}" rx="11" ry="7" fill="#FFFFFF" opacity="0.22" transform="rotate(-18 ${a.cx - 12} ${a.eyeY - 14})" />`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return parts.join("");
|
|
233
312
|
}
|
|
234
313
|
function withAlpha(hex, alpha) {
|
|
235
314
|
const h = hex.replace("#", "");
|
|
@@ -242,12 +321,13 @@ function withAlpha(hex, alpha) {
|
|
|
242
321
|
}
|
|
243
322
|
|
|
244
323
|
// src/parts/eyes.ts
|
|
245
|
-
function renderEyes(id, palette, anchor) {
|
|
324
|
+
function renderEyes(id, palette, anchor, opts) {
|
|
246
325
|
const lx = anchor.cx - anchor.eyeOffset;
|
|
247
326
|
const rx = anchor.cx + anchor.eyeOffset;
|
|
248
327
|
const y = anchor.eyeY;
|
|
249
328
|
const s = anchor.eyeScale;
|
|
250
329
|
const ink = palette.ink;
|
|
330
|
+
const sw = opts?.strokeMul ?? 1;
|
|
251
331
|
switch (id) {
|
|
252
332
|
case "round":
|
|
253
333
|
return [
|
|
@@ -269,24 +349,24 @@ function renderEyes(id, palette, anchor) {
|
|
|
269
349
|
].join("");
|
|
270
350
|
case "squint":
|
|
271
351
|
return [
|
|
272
|
-
arc(lx - 4.5, y, lx, y - 3.5, lx + 4.5, y, ink, 1.8),
|
|
273
|
-
arc(rx - 4.5, y, rx, y - 3.5, rx + 4.5, y, ink, 1.8)
|
|
352
|
+
arc(lx - 4.5, y, lx, y - 3.5, lx + 4.5, y, ink, 1.8 * sw),
|
|
353
|
+
arc(rx - 4.5, y, rx, y - 3.5, rx + 4.5, y, ink, 1.8 * sw)
|
|
274
354
|
].join("");
|
|
275
355
|
case "wink":
|
|
276
356
|
return [
|
|
277
357
|
sclera(lx, y, 4 * s, 4.5 * s),
|
|
278
358
|
pupil(lx, y, 2.2 * s, ink),
|
|
279
359
|
glint(lx + 1, y - 1),
|
|
280
|
-
arc(rx - 4, y, rx, y - 3.5, rx + 4, y, ink, 1.8)
|
|
360
|
+
arc(rx - 4, y, rx, y - 3.5, rx + 4, y, ink, 1.8 * sw)
|
|
281
361
|
].join("");
|
|
282
362
|
case "sleepy":
|
|
283
363
|
return [
|
|
284
364
|
// Heavier upper lid — half-closed
|
|
285
|
-
`<path d="M${lx - 4} ${y - 0.5} Q${lx} ${y + 2} ${lx + 4} ${y - 0.5}" stroke="${ink}" stroke-width="1.7" stroke-linecap="round" fill="none" />`,
|
|
286
|
-
`<path d="M${rx - 4} ${y - 0.5} Q${rx} ${y + 2} ${rx + 4} ${y - 0.5}" stroke="${ink}" stroke-width="1.7" stroke-linecap="round" fill="none" />`,
|
|
365
|
+
`<path d="M${lx - 4} ${y - 0.5} Q${lx} ${y + 2} ${lx + 4} ${y - 0.5}" stroke="${ink}" stroke-width="${1.7 * sw}" stroke-linecap="round" fill="none" />`,
|
|
366
|
+
`<path d="M${rx - 4} ${y - 0.5} Q${rx} ${y + 2} ${rx + 4} ${y - 0.5}" stroke="${ink}" stroke-width="${1.7 * sw}" stroke-linecap="round" fill="none" />`,
|
|
287
367
|
// tiny visible pupils
|
|
288
|
-
`<circle cx="${lx}" cy="${y + 0.5}" r="0.9" fill="${ink}" />`,
|
|
289
|
-
`<circle cx="${rx}" cy="${y + 0.5}" r="0.9" fill="${ink}" />`
|
|
368
|
+
`<circle cx="${lx}" cy="${y + 0.5}" r="${0.9 * sw}" fill="${ink}" />`,
|
|
369
|
+
`<circle cx="${rx}" cy="${y + 0.5}" r="${0.9 * sw}" fill="${ink}" />`
|
|
290
370
|
].join("");
|
|
291
371
|
case "star":
|
|
292
372
|
return [starEye(lx, y, ink), starEye(rx, y, ink)].join("");
|
|
@@ -303,20 +383,20 @@ function renderEyes(id, palette, anchor) {
|
|
|
303
383
|
].join("");
|
|
304
384
|
case "dot":
|
|
305
385
|
return [
|
|
306
|
-
`<circle cx="${lx}" cy="${y}" r="${1.4 * s}" fill="${ink}" />`,
|
|
307
|
-
`<circle cx="${rx}" cy="${y}" r="${1.4 * s}" fill="${ink}" />`
|
|
386
|
+
`<circle cx="${lx}" cy="${y}" r="${1.4 * s * sw}" fill="${ink}" />`,
|
|
387
|
+
`<circle cx="${rx}" cy="${y}" r="${1.4 * s * sw}" fill="${ink}" />`
|
|
308
388
|
].join("");
|
|
309
389
|
case "cross":
|
|
310
|
-
return [crossEye(lx, y, ink), crossEye(rx, y, ink)].join("");
|
|
390
|
+
return [crossEye(lx, y, ink, sw), crossEye(rx, y, ink, sw)].join("");
|
|
311
391
|
}
|
|
312
392
|
}
|
|
313
393
|
function heartEye(cx, cy, color) {
|
|
314
394
|
const s = 2;
|
|
315
395
|
return `<path d="M${cx} ${cy + s * 1.4} L${cx - s * 1.8} ${cy - s * 0.2} A${s} ${s} 0 0 1 ${cx} ${cy - s * 0.6} A${s} ${s} 0 0 1 ${cx + s * 1.8} ${cy - s * 0.2} Z" fill="${color}" />`;
|
|
316
396
|
}
|
|
317
|
-
function crossEye(cx, cy, color) {
|
|
397
|
+
function crossEye(cx, cy, color, sw = 1) {
|
|
318
398
|
const s = 2.4;
|
|
319
|
-
return `<g stroke="${color}" stroke-width="1.6" stroke-linecap="round"><line x1="${cx - s}" y1="${cy - s}" x2="${cx + s}" y2="${cy + s}" /><line x1="${cx - s}" y1="${cy + s}" x2="${cx + s}" y2="${cy - s}" /></g>`;
|
|
399
|
+
return `<g stroke="${color}" stroke-width="${1.6 * sw}" stroke-linecap="round"><line x1="${cx - s}" y1="${cy - s}" x2="${cx + s}" y2="${cy + s}" /><line x1="${cx - s}" y1="${cy + s}" x2="${cx + s}" y2="${cy - s}" /></g>`;
|
|
320
400
|
}
|
|
321
401
|
function sclera(cx, cy, rx, ry) {
|
|
322
402
|
return `<ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" fill="#FFFFFF" />`;
|
|
@@ -336,41 +416,68 @@ function starEye(cx, cy, color) {
|
|
|
336
416
|
}
|
|
337
417
|
|
|
338
418
|
// src/parts/mouth.ts
|
|
339
|
-
function renderMouth(id, palette, anchor, curveScale = 1) {
|
|
419
|
+
function renderMouth(id, palette, anchor, curveScale = 1, opts) {
|
|
340
420
|
const cx = anchor.cx;
|
|
341
421
|
const y = anchor.mouthY;
|
|
342
422
|
const w = anchor.mouthSpan * curveScale;
|
|
343
423
|
const ink = palette.ink;
|
|
424
|
+
const sw = opts?.strokeMul ?? 1;
|
|
425
|
+
const base = 1.8 * sw;
|
|
344
426
|
switch (id) {
|
|
345
427
|
case "smile":
|
|
346
|
-
return `<path d="M${cx - w} ${y} Q${cx} ${y + 5} ${cx + w} ${y}" stroke="${ink}" stroke-width="
|
|
428
|
+
return `<path d="M${cx - w} ${y} Q${cx} ${y + 5} ${cx + w} ${y}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`;
|
|
347
429
|
case "grin":
|
|
348
|
-
return `<path d="M${cx - w - 1} ${y - 2} Q${cx} ${y + 7} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="
|
|
430
|
+
return `<path d="M${cx - w - 1} ${y - 2} Q${cx} ${y + 7} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`;
|
|
349
431
|
case "open":
|
|
350
432
|
return [
|
|
351
|
-
`<path d="M${cx - w - 1} ${y - 2} Q${cx} ${y + 9} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="
|
|
433
|
+
`<path d="M${cx - w - 1} ${y - 2} Q${cx} ${y + 9} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="${ink}" fill-opacity="0.55" />`,
|
|
352
434
|
`<ellipse cx="${cx}" cy="${y + 3}" rx="${w * 0.55}" ry="1.8" fill="#F472B6" opacity="0.75" />`
|
|
353
435
|
].join("");
|
|
354
436
|
case "flat":
|
|
355
|
-
return `<path d="M${cx - w + 1} ${y} L${cx + w - 1} ${y}" stroke="${ink}" stroke-width="
|
|
437
|
+
return `<path d="M${cx - w + 1} ${y} L${cx + w - 1} ${y}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`;
|
|
356
438
|
case "smirk":
|
|
357
|
-
return `<path d="M${cx - w} ${y} Q${cx} ${y + 3} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="
|
|
439
|
+
return `<path d="M${cx - w} ${y} Q${cx} ${y + 3} ${cx + w + 1} ${y - 2}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`;
|
|
358
440
|
case "awe":
|
|
359
441
|
return `<ellipse cx="${cx}" cy="${y + 1}" rx="${w * 0.45}" ry="3.2" fill="${ink}" opacity="0.85" />`;
|
|
360
442
|
case "tongue":
|
|
361
443
|
return [
|
|
362
|
-
`<path d="M${cx - w} ${y} Q${cx} ${y + 6} ${cx + w} ${y}" stroke="${ink}" stroke-width="
|
|
363
|
-
`<path d="M${cx - 2} ${y + 4} Q${cx} ${y + 9} ${cx + 2} ${y + 4} Z" fill="#F472B6" stroke="${ink}" stroke-width="0.6" />`
|
|
444
|
+
`<path d="M${cx - w} ${y} Q${cx} ${y + 6} ${cx + w} ${y}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`,
|
|
445
|
+
`<path d="M${cx - 2} ${y + 4} Q${cx} ${y + 9} ${cx + 2} ${y + 4} Z" fill="#F472B6" stroke="${ink}" stroke-width="${0.6 * sw}" />`
|
|
364
446
|
].join("");
|
|
365
447
|
case "tooth":
|
|
366
448
|
return [
|
|
367
|
-
`<path d="M${cx - w} ${y} Q${cx} ${y + 5} ${cx + w} ${y}" stroke="${ink}" stroke-width="
|
|
368
|
-
`<rect x="${cx - 1.2}" y="${y + 0.4}" width="2.4" height="2.6" rx="0.4" fill="#FFFFFF" stroke="${ink}" stroke-width="0.4" />`
|
|
449
|
+
`<path d="M${cx - w} ${y} Q${cx} ${y + 5} ${cx + w} ${y}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`,
|
|
450
|
+
`<rect x="${cx - 1.2}" y="${y + 0.4}" width="2.4" height="2.6" rx="0.4" fill="#FFFFFF" stroke="${ink}" stroke-width="${0.4 * sw}" />`
|
|
369
451
|
].join("");
|
|
370
452
|
case "wave":
|
|
371
|
-
return `<path d="M${cx - w} ${y + 1} Q${cx - w / 2} ${y - 1.5} ${cx} ${y + 1} Q${cx + w / 2} ${y + 3.5} ${cx + w} ${y + 1}" stroke="${ink}" stroke-width="
|
|
453
|
+
return `<path d="M${cx - w} ${y + 1} Q${cx - w / 2} ${y - 1.5} ${cx} ${y + 1} Q${cx + w / 2} ${y + 3.5} ${cx + w} ${y + 1}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`;
|
|
372
454
|
case "dot":
|
|
373
|
-
return `<circle cx="${cx}" cy="${y + 1}" r="1.2" fill="${ink}" />`;
|
|
455
|
+
return `<circle cx="${cx}" cy="${y + 1}" r="${1.2 * sw}" fill="${ink}" />`;
|
|
456
|
+
case "jagged": {
|
|
457
|
+
const half = w + 1;
|
|
458
|
+
const top = y - 1;
|
|
459
|
+
const bot = y + 5;
|
|
460
|
+
const step = half * 2 / 8;
|
|
461
|
+
const x0 = cx - half;
|
|
462
|
+
const points = [];
|
|
463
|
+
points.push(`${x0} ${top}`);
|
|
464
|
+
for (let i = 1; i <= 8; i++) {
|
|
465
|
+
const px = x0 + step * i;
|
|
466
|
+
const py = i % 2 === 1 ? bot : top;
|
|
467
|
+
points.push(`${px.toFixed(2)} ${py}`);
|
|
468
|
+
}
|
|
469
|
+
return `<path d="M${points.join(" L")} L${cx + half} ${top} Z" fill="${ink}" />`;
|
|
470
|
+
}
|
|
471
|
+
case "fangs": {
|
|
472
|
+
const half = w - 1;
|
|
473
|
+
return [
|
|
474
|
+
`<path d="M${cx - half} ${y} L${cx + half} ${y}" stroke="${ink}" stroke-width="${base}" stroke-linecap="round" fill="none" />`,
|
|
475
|
+
// Left fang
|
|
476
|
+
`<path d="M${cx - 2.5} ${y + 0.4} L${cx - 1.2} ${y + 4.5} L${cx - 0.2} ${y + 0.4} Z" fill="#FFFFFF" stroke="${ink}" stroke-width="${0.5 * sw}" />`,
|
|
477
|
+
// Right fang
|
|
478
|
+
`<path d="M${cx + 0.2} ${y + 0.4} L${cx + 1.2} ${y + 4.5} L${cx + 2.5} ${y + 0.4} Z" fill="#FFFFFF" stroke="${ink}" stroke-width="${0.5 * sw}" />`
|
|
479
|
+
].join("");
|
|
480
|
+
}
|
|
374
481
|
}
|
|
375
482
|
}
|
|
376
483
|
|
|
@@ -407,7 +514,8 @@ function renderAntenna(id, anchor, palette) {
|
|
|
407
514
|
}
|
|
408
515
|
|
|
409
516
|
// src/parts/accessory.ts
|
|
410
|
-
function renderAccessory(id, palette, anchor) {
|
|
517
|
+
function renderAccessory(id, palette, anchor, opts) {
|
|
518
|
+
const sw = opts?.strokeMul ?? 1;
|
|
411
519
|
switch (id) {
|
|
412
520
|
case "none":
|
|
413
521
|
return "";
|
|
@@ -442,10 +550,11 @@ function renderAccessory(id, palette, anchor) {
|
|
|
442
550
|
const rx = anchor.cx + anchor.eyeOffset;
|
|
443
551
|
const y = anchor.eyeY;
|
|
444
552
|
const r = 6;
|
|
553
|
+
const gw = 1.2 * sw;
|
|
445
554
|
return [
|
|
446
|
-
`<circle cx="${lx}" cy="${y}" r="${r}" fill="none" stroke="${palette.ink}" stroke-width="
|
|
447
|
-
`<circle cx="${rx}" cy="${y}" r="${r}" fill="none" stroke="${palette.ink}" stroke-width="
|
|
448
|
-
`<line x1="${lx + r}" y1="${y}" x2="${rx - r}" y2="${y}" stroke="${palette.ink}" stroke-width="
|
|
555
|
+
`<circle cx="${lx}" cy="${y}" r="${r}" fill="none" stroke="${palette.ink}" stroke-width="${gw}" />`,
|
|
556
|
+
`<circle cx="${rx}" cy="${y}" r="${r}" fill="none" stroke="${palette.ink}" stroke-width="${gw}" />`,
|
|
557
|
+
`<line x1="${lx + r}" y1="${y}" x2="${rx - r}" y2="${y}" stroke="${palette.ink}" stroke-width="${gw}" />`,
|
|
449
558
|
// subtle lens fill
|
|
450
559
|
`<circle cx="${lx}" cy="${y}" r="${r - 1}" fill="#FFFFFF" opacity="0.18" />`,
|
|
451
560
|
`<circle cx="${rx}" cy="${y}" r="${r - 1}" fill="#FFFFFF" opacity="0.18" />`
|
|
@@ -463,6 +572,20 @@ function renderAccessory(id, palette, anchor) {
|
|
|
463
572
|
case "mole": {
|
|
464
573
|
return `<circle cx="${anchor.cx - anchor.cheekOffset * 0.6}" cy="${anchor.cheekY + 2}" r="0.9" fill="${palette.ink}" />`;
|
|
465
574
|
}
|
|
575
|
+
case "earring": {
|
|
576
|
+
const ex = anchor.cheekOffset + 4;
|
|
577
|
+
const ey = anchor.cheekY + 4;
|
|
578
|
+
const lx = anchor.cx - ex;
|
|
579
|
+
const rx = anchor.cx + ex;
|
|
580
|
+
return [
|
|
581
|
+
// Left earring — small stud + drop
|
|
582
|
+
`<circle cx="${lx}" cy="${ey}" r="${1.1 * sw}" fill="${palette.accent}" stroke="${palette.ink}" stroke-width="${0.4 * sw}" />`,
|
|
583
|
+
`<ellipse cx="${lx}" cy="${ey + 3.2}" rx="${1.3 * sw}" ry="${2 * sw}" fill="${palette.accent}" stroke="${palette.ink}" stroke-width="${0.4 * sw}" />`,
|
|
584
|
+
// Right earring
|
|
585
|
+
`<circle cx="${rx}" cy="${ey}" r="${1.1 * sw}" fill="${palette.accent}" stroke="${palette.ink}" stroke-width="${0.4 * sw}" />`,
|
|
586
|
+
`<ellipse cx="${rx}" cy="${ey + 3.2}" rx="${1.3 * sw}" ry="${2 * sw}" fill="${palette.accent}" stroke="${palette.ink}" stroke-width="${0.4 * sw}" />`
|
|
587
|
+
].join("");
|
|
588
|
+
}
|
|
466
589
|
}
|
|
467
590
|
function dot(cx, cy, color) {
|
|
468
591
|
return `<circle cx="${cx}" cy="${cy}" r="0.85" fill="${color}" opacity="0.55" />`;
|
|
@@ -556,6 +679,84 @@ function renderTopper(id, anchor, palette) {
|
|
|
556
679
|
`<path d="M${cx - 5} ${topY + 4} L${cx - 6} ${topY - 4} M${cx - 6} ${topY - 4} L${cx - 10} ${topY - 6} M${cx - 6} ${topY - 4} L${cx - 6} ${topY - 9} M${cx - 6} ${topY - 9} L${cx - 8} ${topY - 11} M${cx - 6} ${topY - 9} L${cx - 3} ${topY - 11}" stroke="${ink}" stroke-width="1.3" stroke-linecap="round" fill="none" />`,
|
|
557
680
|
`<path d="M${cx + 5} ${topY + 4} L${cx + 6} ${topY - 4} M${cx + 6} ${topY - 4} L${cx + 10} ${topY - 6} M${cx + 6} ${topY - 4} L${cx + 6} ${topY - 9} M${cx + 6} ${topY - 9} L${cx + 8} ${topY - 11} M${cx + 6} ${topY - 9} L${cx + 3} ${topY - 11}" stroke="${ink}" stroke-width="1.3" stroke-linecap="round" fill="none" />`
|
|
558
681
|
].join("");
|
|
682
|
+
case "bob": {
|
|
683
|
+
const eyeY = anchor.eyeY;
|
|
684
|
+
const tt = topY - 4;
|
|
685
|
+
const bt = eyeY + 6;
|
|
686
|
+
return [
|
|
687
|
+
// Main hair cap — slightly asymmetric for soft look
|
|
688
|
+
`<path d="M${cx - 24} ${bt} Q${cx - 26} ${eyeY - 4} ${cx - 22} ${tt + 4} Q${cx - 14} ${tt - 2} ${cx} ${tt - 3} Q${cx + 14} ${tt - 2} ${cx + 22} ${tt + 4} Q${cx + 26} ${eyeY - 4} ${cx + 24} ${bt} Q${cx + 18} ${eyeY + 2} ${cx + 14} ${eyeY - 2} Q${cx} ${eyeY - 8} ${cx - 14} ${eyeY - 2} Q${cx - 18} ${eyeY + 2} ${cx - 24} ${bt} Z" fill="${ink}" opacity="0.92" />`,
|
|
689
|
+
// Subtle highlight strand
|
|
690
|
+
`<path d="M${cx - 14} ${tt + 4} Q${cx - 6} ${tt + 2} ${cx + 2} ${tt + 6}" stroke="${palette.accent}" stroke-width="0.6" fill="none" opacity="0.25" />`
|
|
691
|
+
].join("");
|
|
692
|
+
}
|
|
693
|
+
case "bun": {
|
|
694
|
+
const baseY = topY + 4;
|
|
695
|
+
const bunY = topY - 8;
|
|
696
|
+
return [
|
|
697
|
+
// Hair base on crown
|
|
698
|
+
`<path d="M${cx - 16} ${baseY} Q${cx} ${topY - 4} ${cx + 16} ${baseY} Q${cx + 12} ${baseY - 4} ${cx} ${baseY - 6} Q${cx - 12} ${baseY - 4} ${cx - 16} ${baseY} Z" fill="${ink}" opacity="0.92" />`,
|
|
699
|
+
// Bun disc
|
|
700
|
+
`<ellipse cx="${cx}" cy="${bunY}" rx="6" ry="5" fill="${ink}" opacity="0.95" />`,
|
|
701
|
+
// Bun wrap detail
|
|
702
|
+
`<ellipse cx="${cx}" cy="${bunY - 0.5}" rx="3.5" ry="2.5" fill="none" stroke="${palette.accent}" stroke-width="0.4" opacity="0.4" />`
|
|
703
|
+
].join("");
|
|
704
|
+
}
|
|
705
|
+
case "witchHat": {
|
|
706
|
+
const tipY = topY - 26;
|
|
707
|
+
const baseY = topY + 2;
|
|
708
|
+
return [
|
|
709
|
+
// Cone — slight curve, tilts right
|
|
710
|
+
`<path d="M${cx - 14} ${baseY} Q${cx - 4} ${baseY - 10} ${cx + 4} ${tipY} Q${cx + 2} ${baseY - 4} ${cx + 14} ${baseY} Z" fill="${ink}" opacity="0.96" />`,
|
|
711
|
+
// Brim — wide flat oval w/ slight curve
|
|
712
|
+
`<ellipse cx="${cx}" cy="${baseY + 2}" rx="22" ry="3.4" fill="${ink}" opacity="0.96" />`,
|
|
713
|
+
// Band across cone base
|
|
714
|
+
`<rect x="${cx - 14}" y="${baseY - 4}" width="28" height="3" fill="${palette.accent}" opacity="0.85" />`,
|
|
715
|
+
// Buckle
|
|
716
|
+
`<rect x="${cx - 2}" y="${baseY - 4}" width="4" height="3" fill="${palette.bodyFrom}" stroke="${ink}" stroke-width="0.4" />`,
|
|
717
|
+
// Star/moon sparkle near tip
|
|
718
|
+
`<circle cx="${cx + 2}" cy="${tipY + 6}" r="0.9" fill="${palette.accent}" opacity="0.9" />`
|
|
719
|
+
].join("");
|
|
720
|
+
}
|
|
721
|
+
case "pumpkinStem": {
|
|
722
|
+
return [
|
|
723
|
+
// Main stem — slightly curved
|
|
724
|
+
`<path d="M${cx - 2} ${topY + 4} Q${cx} ${topY - 2} ${cx + 1} ${topY - 8} L${cx + 3} ${topY - 8} Q${cx + 4} ${topY} ${cx + 2} ${topY + 4} Z" fill="#3F6F2C" stroke="${ink}" stroke-width="0.5" />`,
|
|
725
|
+
// Leaf curling off
|
|
726
|
+
`<path d="M${cx + 3} ${topY - 4} Q${cx + 9} ${topY - 8} ${cx + 12} ${topY - 4} Q${cx + 8} ${topY - 2} ${cx + 3} ${topY - 2} Z" fill="#4A8035" stroke="${ink}" stroke-width="0.4" />`,
|
|
727
|
+
// Vein on leaf
|
|
728
|
+
`<path d="M${cx + 5} ${topY - 3} L${cx + 11} ${topY - 5}" stroke="#2D5020" stroke-width="0.4" />`
|
|
729
|
+
].join("");
|
|
730
|
+
}
|
|
731
|
+
case "ghostSheet": {
|
|
732
|
+
return [
|
|
733
|
+
// Sheet cap — wider than body, hangs lower at sides
|
|
734
|
+
`<path d="M${cx - 22} ${topY + 8} Q${cx - 26} ${topY - 4} ${cx - 14} ${topY - 10} Q${cx} ${topY - 14} ${cx + 14} ${topY - 10} Q${cx + 26} ${topY - 4} ${cx + 22} ${topY + 8} Q${cx + 12} ${topY + 4} ${cx} ${topY + 6} Q${cx - 12} ${topY + 4} ${cx - 22} ${topY + 8} Z" fill="${palette.accent}" stroke="${ink}" stroke-width="0.6" opacity="0.9" />`,
|
|
735
|
+
// Fold shadows
|
|
736
|
+
`<path d="M${cx - 12} ${topY - 6} Q${cx - 10} ${topY - 2} ${cx - 14} ${topY + 4}" stroke="${ink}" stroke-width="0.45" fill="none" opacity="0.35" />`,
|
|
737
|
+
`<path d="M${cx + 12} ${topY - 6} Q${cx + 10} ${topY - 2} ${cx + 14} ${topY + 4}" stroke="${ink}" stroke-width="0.45" fill="none" opacity="0.35" />`
|
|
738
|
+
].join("");
|
|
739
|
+
}
|
|
740
|
+
case "ponytail": {
|
|
741
|
+
const eyeY = anchor.eyeY;
|
|
742
|
+
const fh = eyeY - 7;
|
|
743
|
+
const crownY = topY;
|
|
744
|
+
const baseX = cx + 18;
|
|
745
|
+
const baseY = crownY + 6;
|
|
746
|
+
return [
|
|
747
|
+
// Sleek hair cap — narrower than bob, hugs the crown, soft hairline.
|
|
748
|
+
`<path d="M${cx - 22} ${fh} Q${cx - 24} ${crownY - 2} ${cx - 12} ${crownY - 4} L${cx + 14} ${crownY - 4} Q${cx + 24} ${crownY} ${cx + 22} ${fh} Q${cx + 10} ${fh - 1} ${cx} ${fh + 2} Q${cx - 10} ${fh - 1} ${cx - 22} ${fh} Z" fill="${ink}" opacity="0.94" />`,
|
|
749
|
+
// Subtle highlight sweeping back toward the tie
|
|
750
|
+
`<path d="M${cx - 12} ${crownY - 2} Q${cx} ${crownY - 3} ${baseX - 2} ${baseY - 2}" stroke="${palette.accent}" stroke-width="0.5" fill="none" opacity="0.3" />`,
|
|
751
|
+
// Ponytail tie — small ring where the hair gathers
|
|
752
|
+
`<ellipse cx="${baseX}" cy="${baseY}" rx="3" ry="2.4" fill="${ink}" opacity="0.95" />`,
|
|
753
|
+
`<ellipse cx="${baseX}" cy="${baseY}" rx="1.4" ry="1.1" fill="${palette.accent}" opacity="0.32" />`,
|
|
754
|
+
// Tail — long tapered strand curving down and slightly out
|
|
755
|
+
`<path d="M${baseX - 1} ${baseY + 2} Q${baseX + 5} ${baseY + 10} ${baseX + 8} ${baseY + 20} Q${baseX + 9} ${baseY + 28} ${baseX + 4} ${baseY + 30} Q${baseX + 1} ${baseY + 22} ${baseX - 3} ${baseY + 12} Z" fill="${ink}" opacity="0.92" />`,
|
|
756
|
+
// Inner highlight following the tail's flow direction
|
|
757
|
+
`<path d="M${baseX + 2} ${baseY + 6} Q${baseX + 5} ${baseY + 16} ${baseX + 6} ${baseY + 24}" stroke="${palette.accent}" stroke-width="0.5" fill="none" opacity="0.25" />`
|
|
758
|
+
].join("");
|
|
759
|
+
}
|
|
559
760
|
}
|
|
560
761
|
}
|
|
561
762
|
|
|
@@ -626,6 +827,20 @@ function renderOutfit(id, anchor, palette) {
|
|
|
626
827
|
`<circle cx="${cx}" cy="${cy + 7}" r="1.6" fill="${accent}" stroke="${ink}" stroke-width="0.5" />`,
|
|
627
828
|
`<circle cx="${cx}" cy="${cy + 7}" r="0.7" fill="${palette.blush}" />`
|
|
628
829
|
].join("");
|
|
830
|
+
case "tie": {
|
|
831
|
+
const knotTop = cy - 3;
|
|
832
|
+
const knotBot = cy + 1;
|
|
833
|
+
return [
|
|
834
|
+
// Shirt-collar peek behind the tie (so tie reads as worn over a shirt)
|
|
835
|
+
`<path d="M${cx - 11} ${cy - 2} L${cx - 3} ${knotBot} L${cx + 3} ${knotBot} L${cx + 11} ${cy - 2} L${cx + 6} ${cy + 6} L${cx - 6} ${cy + 6} Z" fill="${accent}" stroke="${ink}" stroke-width="0.55" />`,
|
|
836
|
+
// Knot — small trapezoid centered
|
|
837
|
+
`<path d="M${cx - 3.2} ${knotTop} L${cx + 3.2} ${knotTop} L${cx + 2.4} ${knotBot} L${cx - 2.4} ${knotBot} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.5" />`,
|
|
838
|
+
// Blade — narrower at top, widens, then pointed tip at bottom
|
|
839
|
+
`<path d="M${cx - 2.4} ${knotBot} L${cx + 2.4} ${knotBot} L${cx + 3.4} ${cy + 6} L${cx + 2.8} ${cy + 12} L${cx} ${cy + 15} L${cx - 2.8} ${cy + 12} L${cx - 3.4} ${cy + 6} Z" fill="${palette.bodyTo}" stroke="${ink}" stroke-width="0.5" />`,
|
|
840
|
+
// Subtle highlight stripe down the blade
|
|
841
|
+
`<path d="M${cx} ${knotBot + 0.5} L${cx} ${cy + 13.5}" stroke="${ink}" stroke-width="0.35" opacity="0.35" />`
|
|
842
|
+
].join("");
|
|
843
|
+
}
|
|
629
844
|
}
|
|
630
845
|
}
|
|
631
846
|
|
|
@@ -639,6 +854,9 @@ var BODY_IDS = [
|
|
|
639
854
|
"dumpling",
|
|
640
855
|
"taro",
|
|
641
856
|
"wisp"
|
|
857
|
+
// NOTE: 'squircle' is intentionally NOT in the base pool — pack-only body so
|
|
858
|
+
// existing seeds keep their original picks (no determinism shift). Packs can
|
|
859
|
+
// opt-in via `picks.body: ['squircle']`.
|
|
642
860
|
];
|
|
643
861
|
var EYE_IDS = [
|
|
644
862
|
"round",
|
|
@@ -692,31 +910,550 @@ var TOPPER_IDS = [
|
|
|
692
910
|
"antlers"
|
|
693
911
|
];
|
|
694
912
|
|
|
913
|
+
// src/packs/office.ts
|
|
914
|
+
var palettes = [
|
|
915
|
+
// Near-grayscale with cool blue undertone
|
|
916
|
+
{ id: "office:graphite", bodyFrom: "#989BA2", bodyTo: "#696D72", accent: "#FFFFFF", ink: "#1A1C1F", blush: "#A09D99" },
|
|
917
|
+
// Warm stone — barely tinted neutral
|
|
918
|
+
{ id: "office:stone", bodyFrom: "#A5A29D", bodyTo: "#74726F", accent: "#FFFFFF", ink: "#1C1B19", blush: "#A8A5A0" },
|
|
919
|
+
// Muted sage — barely-green grey
|
|
920
|
+
{ id: "office:sage", bodyFrom: "#9EA299", bodyTo: "#6F726B", accent: "#FFFFFF", ink: "#1B1D19", blush: "#A3A099" },
|
|
921
|
+
// Taupe — desaturated warm grey
|
|
922
|
+
{ id: "office:taupe", bodyFrom: "#ACA098", bodyTo: "#78716C", accent: "#FFFFFF", ink: "#1E1B18", blush: "#AFA59C" },
|
|
923
|
+
// Cool slate — neutral grey with hint of blue
|
|
924
|
+
{ id: "office:slate", bodyFrom: "#9DA2AA", bodyTo: "#6C7077", accent: "#FFFFFF", ink: "#1A1C20", blush: "#A0A2A6" }
|
|
925
|
+
];
|
|
926
|
+
var officePack = {
|
|
927
|
+
id: "office",
|
|
928
|
+
name: "Office",
|
|
929
|
+
description: "Corporate ID-badge. Flat, muted, square silhouette. Mix of professional outfits + hairstyles.",
|
|
930
|
+
emoji: "\u{1F4BC}",
|
|
931
|
+
palettes,
|
|
932
|
+
paletteExclusive: true,
|
|
933
|
+
flat: true,
|
|
934
|
+
bgColor: "#FFFFFF",
|
|
935
|
+
featureStroke: 1.35,
|
|
936
|
+
picks: {
|
|
937
|
+
body: ["squircle"],
|
|
938
|
+
eyes: ["dot", "sleepy", "squint"],
|
|
939
|
+
mouth: ["flat", "dot"],
|
|
940
|
+
antenna: ["none"],
|
|
941
|
+
// Gender + style variety comes from accessory + outfit mix, NOT hair toppers
|
|
942
|
+
// (hair rendered as solid silhouettes reads as helmets at small sizes).
|
|
943
|
+
accessory: ["none", "glasses", "mole", "freckles", "earring", "earring"],
|
|
944
|
+
topper: ["none"],
|
|
945
|
+
background: ["solid"],
|
|
946
|
+
// Outfit mix — tie reads masc, necklace/bowtie/collar neutral-or-femme. No scarf (too bulky).
|
|
947
|
+
outfit: ["tie", "necklace", "collar", "bowtie"]
|
|
948
|
+
},
|
|
949
|
+
styleHints: {
|
|
950
|
+
masc: {
|
|
951
|
+
outfit: ["tie", "bowtie"],
|
|
952
|
+
accessory: ["none", "glasses", "mole", "freckles"]
|
|
953
|
+
},
|
|
954
|
+
femme: {
|
|
955
|
+
outfit: ["necklace"],
|
|
956
|
+
accessory: ["earring", "glasses", "mole"]
|
|
957
|
+
},
|
|
958
|
+
neutral: {
|
|
959
|
+
outfit: ["collar", "bowtie", "necklace"],
|
|
960
|
+
accessory: ["none", "glasses", "freckles"]
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/packs/office-bright.ts
|
|
966
|
+
var palettes2 = [
|
|
967
|
+
// Cobalt — vibrant deep blue
|
|
968
|
+
{ id: "office-bright:cobalt", bodyFrom: "#4F8DFF", bodyTo: "#1E40AF", accent: "#FFFFFF", ink: "#0A1638", blush: "#7BA7FF" },
|
|
969
|
+
// Emerald — vivid green
|
|
970
|
+
{ id: "office-bright:emerald", bodyFrom: "#3CCB85", bodyTo: "#047857", accent: "#FFFFFF", ink: "#062418", blush: "#76DAA8" },
|
|
971
|
+
// Fuchsia — hot magenta-pink
|
|
972
|
+
{ id: "office-bright:fuchsia", bodyFrom: "#F0529C", bodyTo: "#BE185D", accent: "#FFFFFF", ink: "#3A0820", blush: "#F58FBC" },
|
|
973
|
+
// Amber — vivid orange-yellow
|
|
974
|
+
{ id: "office-bright:amber", bodyFrom: "#FFB347", bodyTo: "#C2700B", accent: "#FFFFFF", ink: "#3B1F03", blush: "#FFC97A" },
|
|
975
|
+
// Violet — saturated purple
|
|
976
|
+
{ id: "office-bright:violet", bodyFrom: "#9F7AEA", bodyTo: "#6D28D9", accent: "#FFFFFF", ink: "#1A0A3A", blush: "#BFA0F0" }
|
|
977
|
+
];
|
|
978
|
+
var officeBrightPack = {
|
|
979
|
+
id: "office-bright",
|
|
980
|
+
name: "Office Bright",
|
|
981
|
+
description: "Vivid corporate variant of Office. Saturated brand-style palettes, same flat ID-badge composition.",
|
|
982
|
+
emoji: "\u{1F3A8}",
|
|
983
|
+
palettes: palettes2,
|
|
984
|
+
paletteExclusive: true,
|
|
985
|
+
flat: true,
|
|
986
|
+
bgColor: "#FFFFFF",
|
|
987
|
+
featureStroke: 1.35,
|
|
988
|
+
picks: {
|
|
989
|
+
body: ["squircle"],
|
|
990
|
+
eyes: ["dot", "sleepy", "squint"],
|
|
991
|
+
mouth: ["flat", "dot"],
|
|
992
|
+
antenna: ["none"],
|
|
993
|
+
accessory: ["none", "glasses", "mole", "freckles", "earring", "earring"],
|
|
994
|
+
topper: ["none"],
|
|
995
|
+
background: ["solid"],
|
|
996
|
+
outfit: ["tie", "necklace", "collar", "bowtie"]
|
|
997
|
+
},
|
|
998
|
+
styleHints: {
|
|
999
|
+
masc: {
|
|
1000
|
+
outfit: ["tie", "bowtie"],
|
|
1001
|
+
accessory: ["none", "glasses", "mole", "freckles"]
|
|
1002
|
+
},
|
|
1003
|
+
femme: {
|
|
1004
|
+
outfit: ["necklace"],
|
|
1005
|
+
accessory: ["earring", "glasses", "mole"]
|
|
1006
|
+
},
|
|
1007
|
+
neutral: {
|
|
1008
|
+
outfit: ["collar", "bowtie", "necklace"],
|
|
1009
|
+
accessory: ["none", "glasses", "freckles"]
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// src/packs/halloween.ts
|
|
1015
|
+
var palettes3 = [
|
|
1016
|
+
// Vivid pumpkin orange — high saturation against dark plate
|
|
1017
|
+
{ id: "halloween:pumpkin", bodyFrom: "#FF8C2A", bodyTo: "#D85A00", accent: "#FFE9C4", ink: "#1A0700", blush: "#FFA76A" },
|
|
1018
|
+
// Electric witch purple
|
|
1019
|
+
{ id: "halloween:witch", bodyFrom: "#B266FF", bodyTo: "#6A1FB8", accent: "#F0D9FF", ink: "#0E031F", blush: "#D6A0FF" },
|
|
1020
|
+
// Acid slime — radioactive Halloween green
|
|
1021
|
+
{ id: "halloween:slime", bodyFrom: "#B3F23A", bodyTo: "#5E9F00", accent: "#E8FFB8", ink: "#091300", blush: "#D8FF7C" },
|
|
1022
|
+
// Blood crimson — deep saturated red
|
|
1023
|
+
{ id: "halloween:blood", bodyFrom: "#E83248", bodyTo: "#8E0014", accent: "#FFD6DA", ink: "#1F0004", blush: "#FF8A98" },
|
|
1024
|
+
// Bone — pale warm white, ghost-like
|
|
1025
|
+
{ id: "halloween:bone", bodyFrom: "#F5EEDB", bodyTo: "#C8B89A", accent: "#FFFEF5", ink: "#1A1612", blush: "#E8D9B8" }
|
|
1026
|
+
];
|
|
1027
|
+
var halloweenPack = {
|
|
1028
|
+
id: "halloween",
|
|
1029
|
+
name: "Halloween",
|
|
1030
|
+
description: "Spooky themed pack. Pumpkin/ghost/skull bodies, jagged grins, witch hats, dark night plate.",
|
|
1031
|
+
emoji: "\u{1F383}",
|
|
1032
|
+
palettes: palettes3,
|
|
1033
|
+
paletteExclusive: true,
|
|
1034
|
+
flat: true,
|
|
1035
|
+
bgColor: "#0E0A1A",
|
|
1036
|
+
// deep night-purple
|
|
1037
|
+
featureStroke: 1.4,
|
|
1038
|
+
picks: {
|
|
1039
|
+
body: ["pumpkin", "ghost", "skullHead"],
|
|
1040
|
+
eyes: ["cross", "dot", "sleepy", "wide"],
|
|
1041
|
+
mouth: ["jagged", "fangs", "flat", "dot"],
|
|
1042
|
+
antenna: ["none"],
|
|
1043
|
+
accessory: ["none", "sparkle", "eyepatch", "mole"],
|
|
1044
|
+
topper: ["none", "witchHat", "pumpkinStem", "ghostSheet", "horns", "antlers"],
|
|
1045
|
+
background: ["solid"],
|
|
1046
|
+
outfit: ["none", "collar", "scarf"]
|
|
1047
|
+
},
|
|
1048
|
+
styleHints: {
|
|
1049
|
+
// 'masc' → creepy / skeletal vibe
|
|
1050
|
+
masc: {
|
|
1051
|
+
outfit: ["none", "collar"],
|
|
1052
|
+
accessory: ["none", "eyepatch"],
|
|
1053
|
+
topper: ["horns", "antlers", "witchHat"]
|
|
1054
|
+
},
|
|
1055
|
+
// 'femme' → witchy / sparkly vibe
|
|
1056
|
+
femme: {
|
|
1057
|
+
outfit: ["none", "scarf"],
|
|
1058
|
+
accessory: ["sparkle", "mole"],
|
|
1059
|
+
topper: ["witchHat", "ghostSheet", "pumpkinStem"]
|
|
1060
|
+
},
|
|
1061
|
+
// 'neutral' → balanced classic Halloween — pumpkin or none topper
|
|
1062
|
+
neutral: {
|
|
1063
|
+
outfit: ["none"],
|
|
1064
|
+
accessory: ["none", "sparkle"],
|
|
1065
|
+
topper: ["none", "witchHat", "pumpkinStem"]
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// src/packs/pastel.ts
|
|
1071
|
+
var palettes4 = [
|
|
1072
|
+
// Cotton candy — soft pink, the most kawaii signal
|
|
1073
|
+
{ id: "pastel:cotton-candy", bodyFrom: "#FFB4D4", bodyTo: "#FF7AAB", accent: "#FFF0F6", ink: "#5E1C40", blush: "#FFC6DC" },
|
|
1074
|
+
// Butter — warm pastel yellow
|
|
1075
|
+
{ id: "pastel:butter", bodyFrom: "#FFE07F", bodyTo: "#E6B12E", accent: "#FFFBE6", ink: "#5C4810", blush: "#FFE5A0" },
|
|
1076
|
+
// Mint cream — soft green
|
|
1077
|
+
{ id: "pastel:mint-cream", bodyFrom: "#A8E9C8", bodyTo: "#5BC499", accent: "#EFFEF5", ink: "#194433", blush: "#C2EED4" },
|
|
1078
|
+
// Lavender — gentle purple
|
|
1079
|
+
{ id: "pastel:lavender", bodyFrom: "#C8B5F0", bodyTo: "#9275D4", accent: "#F6F0FE", ink: "#321F58", blush: "#D8C6F0" },
|
|
1080
|
+
// Peach bloom — warm coral peach
|
|
1081
|
+
{ id: "pastel:peach-bloom", bodyFrom: "#FFC1A0", bodyTo: "#F08F6A", accent: "#FFF1E8", ink: "#6E2F12", blush: "#FFC6AC" },
|
|
1082
|
+
// Sky drop — pastel baby blue
|
|
1083
|
+
{ id: "pastel:sky-drop", bodyFrom: "#A8D9F5", bodyTo: "#6BB1E0", accent: "#EFF8FE", ink: "#15324A", blush: "#BCDDF0" }
|
|
1084
|
+
];
|
|
1085
|
+
var pastelPack = {
|
|
1086
|
+
id: "pastel",
|
|
1087
|
+
name: "Pastel",
|
|
1088
|
+
description: "Kawaii cozy companion. Round chubby bodies, big eyes, blushy cheeks, soft cream plate. Finch-like.",
|
|
1089
|
+
emoji: "\u{1F338}",
|
|
1090
|
+
palettes: palettes4,
|
|
1091
|
+
paletteExclusive: true,
|
|
1092
|
+
flat: true,
|
|
1093
|
+
bgColor: "#FBF6F0",
|
|
1094
|
+
// warm cream plate
|
|
1095
|
+
featureStroke: 1.3,
|
|
1096
|
+
picks: {
|
|
1097
|
+
body: ["dumpling", "orb", "pebble"],
|
|
1098
|
+
// Big expressive eyes — wide/round read as kawaii, sleepy/wink soften it.
|
|
1099
|
+
// Drop oval (too anime) + cross (too aggressive) for cohesion.
|
|
1100
|
+
eyes: ["round", "wide", "sleepy", "wink", "heart", "star"],
|
|
1101
|
+
// Tiny mouths — smile/dot/tongue. No grin/awe (too loud).
|
|
1102
|
+
mouth: ["smile", "dot", "tongue", "smirk"],
|
|
1103
|
+
antenna: ["none", "classic", "curl"],
|
|
1104
|
+
// Blush weighted heavy — every pastel avatar gets blushy by default
|
|
1105
|
+
accessory: ["blush", "blush", "blush", "freckles", "sparkle", "none"],
|
|
1106
|
+
// Round ears / headband / halo / tuft give character variety
|
|
1107
|
+
topper: ["none", "roundEars", "ears", "headband", "halo", "tuft"],
|
|
1108
|
+
background: ["solid"],
|
|
1109
|
+
// Outfit stays minimal — collar gives "shirt" peek
|
|
1110
|
+
outfit: ["none", "collar", "sunflower", "necklace"]
|
|
1111
|
+
},
|
|
1112
|
+
styleHints: {
|
|
1113
|
+
// 'masc' → softer kawaii: minimal toppers, dot/sleepy eyes
|
|
1114
|
+
masc: {
|
|
1115
|
+
eyes: ["round", "sleepy", "wink"],
|
|
1116
|
+
accessory: ["blush", "freckles", "none"],
|
|
1117
|
+
topper: ["none", "tuft", "cap"],
|
|
1118
|
+
outfit: ["none", "collar"]
|
|
1119
|
+
},
|
|
1120
|
+
// 'femme' → full kawaii: heart/star eyes, ears+halo, sparkles
|
|
1121
|
+
femme: {
|
|
1122
|
+
eyes: ["heart", "star", "wide"],
|
|
1123
|
+
accessory: ["blush", "sparkle"],
|
|
1124
|
+
topper: ["roundEars", "ears", "halo", "headband"],
|
|
1125
|
+
outfit: ["none", "sunflower", "necklace"]
|
|
1126
|
+
},
|
|
1127
|
+
// 'neutral' → balanced
|
|
1128
|
+
neutral: {
|
|
1129
|
+
eyes: ["round", "wide", "sleepy"],
|
|
1130
|
+
accessory: ["blush", "freckles"],
|
|
1131
|
+
topper: ["none", "roundEars", "tuft"],
|
|
1132
|
+
outfit: ["none", "collar"]
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/packs/neon.ts
|
|
1138
|
+
var palettes5 = [
|
|
1139
|
+
// Hot pink — synthwave signature
|
|
1140
|
+
{ id: "neon:pink", bodyFrom: "#FF6FD8", bodyTo: "#D6168C", accent: "#FFE5F5", ink: "#1F0214", blush: "#FFB3DD" },
|
|
1141
|
+
// Acid lime — radioactive green
|
|
1142
|
+
{ id: "neon:lime", bodyFrom: "#D9FF3D", bodyTo: "#5FB800", accent: "#F5FFD0", ink: "#0F1500", blush: "#E5FF8A" },
|
|
1143
|
+
// Electric cyan — Tron blue
|
|
1144
|
+
{ id: "neon:cyan", bodyFrom: "#4DEFFF", bodyTo: "#0099C9", accent: "#D6FBFF", ink: "#00141C", blush: "#A5EEFA" },
|
|
1145
|
+
// Sodium — bright street-lamp yellow-orange
|
|
1146
|
+
{ id: "neon:sodium", bodyFrom: "#FFEC4D", bodyTo: "#E59700", accent: "#FFFADB", ink: "#221400", blush: "#FFE082" },
|
|
1147
|
+
// Magenta violet — saturated purple
|
|
1148
|
+
{ id: "neon:violet", bodyFrom: "#C66DFF", bodyTo: "#6B14D6", accent: "#EFDDFF", ink: "#0F0024", blush: "#D8B0FF" },
|
|
1149
|
+
// Plasma orange-red — fiery
|
|
1150
|
+
{ id: "neon:plasma", bodyFrom: "#FF6A3D", bodyTo: "#C42500", accent: "#FFD9CC", ink: "#1F0500", blush: "#FFA98A" }
|
|
1151
|
+
];
|
|
1152
|
+
var neonPack = {
|
|
1153
|
+
id: "neon",
|
|
1154
|
+
name: "Neon",
|
|
1155
|
+
description: "Cyberpunk gaming PFP. Vivid neon bodies on a dark plate with soft glow halo. Sharp + intense.",
|
|
1156
|
+
emoji: "\u26A1",
|
|
1157
|
+
palettes: palettes5,
|
|
1158
|
+
paletteExclusive: true,
|
|
1159
|
+
flat: true,
|
|
1160
|
+
bgColor: "#0A0A14",
|
|
1161
|
+
// deep night plate
|
|
1162
|
+
featureStroke: 1.5,
|
|
1163
|
+
glow: true,
|
|
1164
|
+
picks: {
|
|
1165
|
+
body: ["tall", "wisp", "taro"],
|
|
1166
|
+
eyes: ["star", "wide", "cross", "dot"],
|
|
1167
|
+
mouth: ["grin", "awe", "tooth", "smirk"],
|
|
1168
|
+
antenna: ["spike", "double", "none"],
|
|
1169
|
+
accessory: ["sparkle", "glasses", "eyepatch", "none"],
|
|
1170
|
+
topper: ["none", "antlers", "horn", "horns", "tuft", "cap"],
|
|
1171
|
+
background: ["solid"],
|
|
1172
|
+
outfit: ["none", "scarf", "necklace"]
|
|
1173
|
+
},
|
|
1174
|
+
styleHints: {
|
|
1175
|
+
// 'masc' → aggressive: cross eyes, horns, spike antenna, eyepatch
|
|
1176
|
+
masc: {
|
|
1177
|
+
accessory: ["eyepatch", "glasses", "none"],
|
|
1178
|
+
topper: ["horns", "horn", "antlers"],
|
|
1179
|
+
outfit: ["none"]
|
|
1180
|
+
},
|
|
1181
|
+
// 'femme' → glamour: sparkle, halo, soft tuft
|
|
1182
|
+
femme: {
|
|
1183
|
+
accessory: ["sparkle", "glasses"],
|
|
1184
|
+
topper: ["tuft", "halo"],
|
|
1185
|
+
outfit: ["necklace", "scarf", "none"]
|
|
1186
|
+
},
|
|
1187
|
+
// 'neutral' → balanced edgy
|
|
1188
|
+
neutral: {
|
|
1189
|
+
accessory: ["glasses", "sparkle", "none"],
|
|
1190
|
+
topper: ["none", "cap", "tuft"],
|
|
1191
|
+
outfit: ["none"]
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
// src/packs/mono.ts
|
|
1197
|
+
var palettes6 = [
|
|
1198
|
+
// Obsidian — near-black body, WHITE face features for contrast
|
|
1199
|
+
{ id: "mono:obsidian", bodyFrom: "#2A2A2A", bodyTo: "#0A0A0A", accent: "#FAFAFA", ink: "#F2F2F2", blush: "#5A5A5A" },
|
|
1200
|
+
// Graphite — dark warm grey body, light face
|
|
1201
|
+
{ id: "mono:graphite", bodyFrom: "#4A4A4A", bodyTo: "#222222", accent: "#F4F4F4", ink: "#EBEBEB", blush: "#7A7A7A" },
|
|
1202
|
+
// Slate — mid-grey body, dark face (good contrast either way; going dark for editorial)
|
|
1203
|
+
{ id: "mono:slate", bodyFrom: "#8E8E8E", bodyTo: "#5A5A5A", accent: "#F8F8F8", ink: "#0A0A0A", blush: "#A8A8A8" },
|
|
1204
|
+
// Silver — mid-light grey, dark face
|
|
1205
|
+
{ id: "mono:silver", bodyFrom: "#B8B8B8", bodyTo: "#888888", accent: "#FCFCFC", ink: "#1A1A1A", blush: "#D0D0D0" },
|
|
1206
|
+
// Fog — palest grey, dark face
|
|
1207
|
+
{ id: "mono:fog", bodyFrom: "#E0E0E0", bodyTo: "#B0B0B0", accent: "#FFFFFF", ink: "#2A2A2A", blush: "#EDEDED" },
|
|
1208
|
+
// Ash — warm sand-grey, dark face with brown hint
|
|
1209
|
+
{ id: "mono:ash", bodyFrom: "#A39A8E", bodyTo: "#6A6055", accent: "#F8F4EF", ink: "#1A1612", blush: "#B8AFA5" }
|
|
1210
|
+
];
|
|
1211
|
+
var monoPack = {
|
|
1212
|
+
id: "mono",
|
|
1213
|
+
name: "Mono",
|
|
1214
|
+
description: "Editorial minimal. Clean grayscale silhouette on warm white plate, thin lines, no decoration.",
|
|
1215
|
+
emoji: "\u{1F5A4}",
|
|
1216
|
+
palettes: palettes6,
|
|
1217
|
+
paletteExclusive: true,
|
|
1218
|
+
flat: true,
|
|
1219
|
+
bgColor: "#FAFAF8",
|
|
1220
|
+
// warm near-white plate
|
|
1221
|
+
featureStroke: 1.15,
|
|
1222
|
+
// subtle but visible (vs Office 1.35, Neon 1.5)
|
|
1223
|
+
picks: {
|
|
1224
|
+
body: ["squircle"],
|
|
1225
|
+
eyes: ["dot"],
|
|
1226
|
+
mouth: ["flat", "dot"],
|
|
1227
|
+
antenna: ["none"],
|
|
1228
|
+
accessory: ["none", "none", "mole", "glasses", "freckles"],
|
|
1229
|
+
topper: ["none"],
|
|
1230
|
+
background: ["solid"],
|
|
1231
|
+
outfit: ["none"]
|
|
1232
|
+
},
|
|
1233
|
+
styleHints: {
|
|
1234
|
+
// 'masc' → bare silhouette, nothing
|
|
1235
|
+
masc: {
|
|
1236
|
+
accessory: ["none"],
|
|
1237
|
+
outfit: ["none"],
|
|
1238
|
+
topper: ["none"]
|
|
1239
|
+
},
|
|
1240
|
+
// 'femme' → one subtle accent (mole or freckles)
|
|
1241
|
+
femme: {
|
|
1242
|
+
accessory: ["mole", "freckles"],
|
|
1243
|
+
outfit: ["none"],
|
|
1244
|
+
topper: ["none"]
|
|
1245
|
+
},
|
|
1246
|
+
// 'neutral' → glasses option
|
|
1247
|
+
neutral: {
|
|
1248
|
+
accessory: ["none", "glasses"],
|
|
1249
|
+
outfit: ["none"],
|
|
1250
|
+
topper: ["none"]
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// src/packs/earth.ts
|
|
1256
|
+
var palettes7 = [
|
|
1257
|
+
// Sage — calm soft green
|
|
1258
|
+
{ id: "earth:sage", bodyFrom: "#A8C49A", bodyTo: "#6D8E60", accent: "#F2F7EA", ink: "#1F2E18", blush: "#C8D7B4" },
|
|
1259
|
+
// Clay — terracotta warmth
|
|
1260
|
+
{ id: "earth:clay", bodyFrom: "#D49072", bodyTo: "#9C4F2E", accent: "#FBEBDD", ink: "#3D1A0B", blush: "#E5BCA5" },
|
|
1261
|
+
// Sand — warm beige
|
|
1262
|
+
{ id: "earth:sand", bodyFrom: "#E1CC9F", bodyTo: "#A88B5A", accent: "#FFF5E0", ink: "#4A3818", blush: "#EBD8B0" },
|
|
1263
|
+
// Moss — deeper forest green
|
|
1264
|
+
{ id: "earth:moss", bodyFrom: "#82A570", bodyTo: "#3F6038", accent: "#EAF4DC", ink: "#0D1F08", blush: "#B9D2A1" },
|
|
1265
|
+
// Terracotta — fiery clay, more saturated
|
|
1266
|
+
{ id: "earth:terracotta", bodyFrom: "#D87555", bodyTo: "#A53F22", accent: "#FFE5D8", ink: "#380F02", blush: "#EBA188" },
|
|
1267
|
+
// Mushroom — warm dusty mauve-brown
|
|
1268
|
+
{ id: "earth:mushroom", bodyFrom: "#B59B8A", bodyTo: "#7D6353", accent: "#F4ECE3", ink: "#2C1C13", blush: "#C6B0A0" }
|
|
1269
|
+
];
|
|
1270
|
+
var earthPack = {
|
|
1271
|
+
id: "earth",
|
|
1272
|
+
name: "Earth",
|
|
1273
|
+
description: "Wellness companion. Warm earthy palettes, soft round body, sleepy meditative face, leaf + halo toppers.",
|
|
1274
|
+
emoji: "\u{1F33F}",
|
|
1275
|
+
palettes: palettes7,
|
|
1276
|
+
paletteExclusive: true,
|
|
1277
|
+
flat: true,
|
|
1278
|
+
bgColor: "#FBF6EE",
|
|
1279
|
+
// warm cream plate
|
|
1280
|
+
featureStroke: 1.2,
|
|
1281
|
+
picks: {
|
|
1282
|
+
body: ["orb", "dumpling", "pebble"],
|
|
1283
|
+
eyes: ["sleepy", "sleepy", "dot"],
|
|
1284
|
+
mouth: ["smile", "dot", "flat"],
|
|
1285
|
+
antenna: ["none"],
|
|
1286
|
+
accessory: ["blush", "blush", "freckles", "mole", "sparkle", "none"],
|
|
1287
|
+
topper: ["none", "leaf", "halo", "headband", "tuft"],
|
|
1288
|
+
background: ["solid"],
|
|
1289
|
+
outfit: ["none", "collar", "scarf", "necklace"]
|
|
1290
|
+
},
|
|
1291
|
+
styleHints: {
|
|
1292
|
+
// 'masc' → minimal earth: dot eyes, no topper, no accessory
|
|
1293
|
+
masc: {
|
|
1294
|
+
accessory: ["none", "freckles", "mole"],
|
|
1295
|
+
topper: ["none", "tuft"],
|
|
1296
|
+
outfit: ["none", "collar"]
|
|
1297
|
+
},
|
|
1298
|
+
// 'femme' → wellness teacher: sleepy + leaf/halo + blush + sparkle
|
|
1299
|
+
femme: {
|
|
1300
|
+
accessory: ["blush", "sparkle"],
|
|
1301
|
+
topper: ["leaf", "halo", "headband"],
|
|
1302
|
+
outfit: ["none", "scarf", "necklace"]
|
|
1303
|
+
},
|
|
1304
|
+
// 'neutral' → balanced calm
|
|
1305
|
+
neutral: {
|
|
1306
|
+
accessory: ["blush", "freckles"],
|
|
1307
|
+
topper: ["none", "leaf", "tuft"],
|
|
1308
|
+
outfit: ["none", "collar"]
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1313
|
+
// src/packs/index.ts
|
|
1314
|
+
var BUILT_IN_PACKS = [
|
|
1315
|
+
officePack,
|
|
1316
|
+
officeBrightPack,
|
|
1317
|
+
halloweenPack,
|
|
1318
|
+
pastelPack,
|
|
1319
|
+
neonPack,
|
|
1320
|
+
monoPack,
|
|
1321
|
+
earthPack
|
|
1322
|
+
];
|
|
1323
|
+
var PACK_REGISTRY = Object.fromEntries(
|
|
1324
|
+
BUILT_IN_PACKS.map((p) => [p.id, p])
|
|
1325
|
+
);
|
|
1326
|
+
function resolvePacks(ids) {
|
|
1327
|
+
if (!ids || ids.length === 0) return [];
|
|
1328
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1329
|
+
const result = [];
|
|
1330
|
+
for (const id of ids) {
|
|
1331
|
+
if (seen.has(id)) continue;
|
|
1332
|
+
const pack = PACK_REGISTRY[id];
|
|
1333
|
+
if (pack) {
|
|
1334
|
+
seen.add(id);
|
|
1335
|
+
result.push(pack);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return result;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
695
1341
|
// src/select.ts
|
|
1342
|
+
function applyStyleHint(pool, packs, hint, partKey) {
|
|
1343
|
+
for (const pack of packs) {
|
|
1344
|
+
const subset = pack.styleHints?.[hint]?.[partKey];
|
|
1345
|
+
if (subset && subset.length > 0) {
|
|
1346
|
+
const narrowed = pool.filter((id) => subset.includes(id));
|
|
1347
|
+
if (narrowed.length > 0) return narrowed;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return pool;
|
|
1351
|
+
}
|
|
1352
|
+
function resolvePartPool(basePool, packs, partKey) {
|
|
1353
|
+
const constraints = packs.map((p) => p.picks?.[partKey]).filter((list) => Array.isArray(list) && list.length > 0);
|
|
1354
|
+
if (constraints.length === 0) return basePool;
|
|
1355
|
+
let pool = constraints[0];
|
|
1356
|
+
for (let i = 1; i < constraints.length; i++) {
|
|
1357
|
+
pool = pool.filter((id) => constraints[i].includes(id));
|
|
1358
|
+
}
|
|
1359
|
+
if (pool.length > 0) return pool;
|
|
1360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1361
|
+
const union = [];
|
|
1362
|
+
for (const list of constraints) {
|
|
1363
|
+
for (const id of list) {
|
|
1364
|
+
if (!seen.has(id)) {
|
|
1365
|
+
seen.add(id);
|
|
1366
|
+
union.push(id);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return union.length > 0 ? union : basePool;
|
|
1371
|
+
}
|
|
696
1372
|
function selectAvatar(seed2, options = {}) {
|
|
697
1373
|
const rng = createRng(seed2);
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1374
|
+
const enabledPacks = resolvePacks(options.packs);
|
|
1375
|
+
const packPalettes = [];
|
|
1376
|
+
let exclusivePackPalettes = null;
|
|
1377
|
+
for (const pack of enabledPacks) {
|
|
1378
|
+
if (pack.palettes && pack.palettes.length > 0) {
|
|
1379
|
+
packPalettes.push(...pack.palettes);
|
|
1380
|
+
if (pack.paletteExclusive) {
|
|
1381
|
+
exclusivePackPalettes = exclusivePackPalettes ?? [];
|
|
1382
|
+
exclusivePackPalettes.push(...pack.palettes);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
const palettePool = exclusivePackPalettes ? exclusivePackPalettes : packPalettes.length > 0 ? [...PALETTES, ...packPalettes] : PALETTES;
|
|
1387
|
+
const paletteByIdLookup = options.paletteId ? PALETTE_BY_ID[options.paletteId] ?? packPalettes.find((p) => p.id === options.paletteId) : void 0;
|
|
1388
|
+
const paletteOverride = options.palette ?? paletteByIdLookup;
|
|
1389
|
+
const palette = paletteOverride ?? rng.pick(palettePool);
|
|
1390
|
+
const bodyPool = resolvePartPool(BODY_IDS, enabledPacks, "body");
|
|
1391
|
+
const eyesPool = resolvePartPool(EYE_IDS, enabledPacks, "eyes");
|
|
1392
|
+
const mouthPool = resolvePartPool(MOUTH_IDS, enabledPacks, "mouth");
|
|
1393
|
+
const antennaPool = resolvePartPool(ANTENNA_IDS, enabledPacks, "antenna");
|
|
1394
|
+
let accessoryPool = resolvePartPool(ACCESSORY_IDS, enabledPacks, "accessory");
|
|
1395
|
+
const backgroundPool = resolvePartPool(BACKGROUND_IDS, enabledPacks, "background");
|
|
1396
|
+
let topperPool = resolvePartPool(TOPPER_IDS, enabledPacks, "topper");
|
|
1397
|
+
const styleHint = options.style;
|
|
1398
|
+
if (styleHint) {
|
|
1399
|
+
accessoryPool = applyStyleHint(accessoryPool, enabledPacks, styleHint, "accessory");
|
|
1400
|
+
topperPool = applyStyleHint(topperPool, enabledPacks, styleHint, "topper");
|
|
1401
|
+
}
|
|
1402
|
+
const body = rng.pick(bodyPool);
|
|
1403
|
+
const eyes = rng.pick(eyesPool);
|
|
1404
|
+
const mouth = rng.pick(mouthPool);
|
|
1405
|
+
const antenna = rng.pick(antennaPool);
|
|
1406
|
+
const accessory = rng.pick(accessoryPool);
|
|
705
1407
|
let background;
|
|
706
1408
|
if (typeof options.background === "string") {
|
|
707
1409
|
background = options.background;
|
|
708
1410
|
} else if (options.background && typeof options.background === "object") {
|
|
709
1411
|
background = "solid";
|
|
710
1412
|
} else {
|
|
711
|
-
background = rng.pick(
|
|
1413
|
+
background = rng.pick(backgroundPool);
|
|
712
1414
|
}
|
|
713
|
-
const topperRaw = rng.pick(
|
|
1415
|
+
const topperRaw = rng.pick(topperPool);
|
|
714
1416
|
const topper = antenna !== "none" && topperRaw !== "none" && topperRaw !== "leaf" ? "none" : topperRaw;
|
|
715
1417
|
const hueShift = Math.round(rng.range(-30, 30));
|
|
716
1418
|
const bodyScale = Number(rng.range(0.92, 1.08).toFixed(3));
|
|
717
1419
|
const eyeGapShift = Number(rng.range(-2, 2).toFixed(2));
|
|
718
1420
|
const mouthCurveScale = Number(rng.range(0.85, 1.15).toFixed(3));
|
|
719
1421
|
const antennaTilt = Math.round(rng.range(-8, 8));
|
|
1422
|
+
const outfitConstraints = enabledPacks.map((p) => p.picks?.outfit).filter((list) => Array.isArray(list) && list.length > 0);
|
|
1423
|
+
let outfit = "none";
|
|
1424
|
+
if (outfitConstraints.length > 0) {
|
|
1425
|
+
let pool = outfitConstraints[0];
|
|
1426
|
+
for (let i = 1; i < outfitConstraints.length; i++) {
|
|
1427
|
+
pool = pool.filter((id) => outfitConstraints[i].includes(id));
|
|
1428
|
+
}
|
|
1429
|
+
if (pool.length === 0) {
|
|
1430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1431
|
+
const union = [];
|
|
1432
|
+
for (const list of outfitConstraints) {
|
|
1433
|
+
for (const id of list) {
|
|
1434
|
+
if (!seen.has(id)) {
|
|
1435
|
+
seen.add(id);
|
|
1436
|
+
union.push(id);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
pool = union;
|
|
1441
|
+
}
|
|
1442
|
+
if (styleHint) {
|
|
1443
|
+
pool = applyStyleHint(pool, enabledPacks, styleHint, "outfit");
|
|
1444
|
+
}
|
|
1445
|
+
outfit = pool.length > 0 ? rng.pick(pool) : "none";
|
|
1446
|
+
}
|
|
1447
|
+
let flat;
|
|
1448
|
+
let bgColor;
|
|
1449
|
+
let featureStroke;
|
|
1450
|
+
let glow;
|
|
1451
|
+
for (const pack of enabledPacks) {
|
|
1452
|
+
if (pack.flat) flat = true;
|
|
1453
|
+
if (pack.bgColor) bgColor = pack.bgColor;
|
|
1454
|
+
if (pack.featureStroke) featureStroke = pack.featureStroke;
|
|
1455
|
+
if (pack.glow) glow = true;
|
|
1456
|
+
}
|
|
720
1457
|
return {
|
|
721
1458
|
seed: seed2,
|
|
722
1459
|
palette,
|
|
@@ -727,12 +1464,16 @@ function selectAvatar(seed2, options = {}) {
|
|
|
727
1464
|
accessory,
|
|
728
1465
|
background,
|
|
729
1466
|
topper,
|
|
730
|
-
outfit
|
|
1467
|
+
outfit,
|
|
731
1468
|
hueShift,
|
|
732
1469
|
bodyScale,
|
|
733
1470
|
eyeGapShift,
|
|
734
1471
|
mouthCurveScale,
|
|
735
|
-
antennaTilt
|
|
1472
|
+
antennaTilt,
|
|
1473
|
+
...flat ? { flat: true } : {},
|
|
1474
|
+
...bgColor ? { bgColor } : {},
|
|
1475
|
+
...featureStroke ? { featureStroke } : {},
|
|
1476
|
+
...glow ? { glow: true } : {}
|
|
736
1477
|
};
|
|
737
1478
|
}
|
|
738
1479
|
|
|
@@ -802,31 +1543,42 @@ function renderAvatarInner(spec, options = {}) {
|
|
|
802
1543
|
...baseAnchor,
|
|
803
1544
|
eyeOffset: baseAnchor.eyeOffset + (spec.eyeGapShift ?? 0)
|
|
804
1545
|
};
|
|
1546
|
+
const strokeMul = spec.featureStroke ?? 1;
|
|
805
1547
|
const antennaSvg = renderAntenna(spec.antenna, anchor, spec.palette);
|
|
806
|
-
const accessorySvg = renderAccessory(spec.accessory, spec.palette, anchor);
|
|
807
|
-
const
|
|
808
|
-
const
|
|
1548
|
+
const accessorySvg = renderAccessory(spec.accessory, spec.palette, anchor, { strokeMul });
|
|
1549
|
+
const flat = spec.flat === true;
|
|
1550
|
+
const glow = spec.glow === true;
|
|
1551
|
+
const glowId = `navii-glow-${id}`;
|
|
1552
|
+
const bodyMarkup = renderBody(spec.body, spec.palette, gradId, { flat });
|
|
1553
|
+
const effectiveBodyScale = flat ? 1 : spec.bodyScale ?? 1;
|
|
1554
|
+
const bodyTransform = transformBody(effectiveBodyScale, anchor);
|
|
809
1555
|
const bodyFilter = spec.hueShift && spec.hueShift !== 0 ? ` filter="url(#${hueId})"` : "";
|
|
810
1556
|
const bodyWrapped = `<g${bodyTransform}${bodyFilter}><g class="body">${bodyMarkup}</g></g>`;
|
|
1557
|
+
const glowLayer = glow ? `<g${bodyTransform} filter="url(#${glowId})" opacity="0.85"><g class="body-glow">${bodyMarkup.replace(/fill="url\(#[^"]+\)"/g, `fill="${spec.palette.bodyFrom}"`)}</g></g>` : "";
|
|
811
1558
|
const antennaWrapped = antennaSvg ? `<g${transformAntenna(spec.antennaTilt ?? 0, anchor)}><g class="antenna">${antennaSvg}</g></g>` : "";
|
|
812
1559
|
const tileBg = resolveTileBg(options.tileBg, spec.palette);
|
|
813
1560
|
const tileCircle = tileBg ? `<circle cx="50" cy="50" r="50" fill="${tileBg}" />` : "";
|
|
814
1561
|
const outfitSvg = renderOutfit(spec.outfit ?? "none", anchor, spec.palette);
|
|
1562
|
+
const packPlate = spec.bgColor ? `<rect x="0" y="0" width="100" height="100" fill="${spec.bgColor}" />` : "";
|
|
1563
|
+
const backgroundMarkup = spec.bgColor ? "" : renderBackground(spec.background, spec.palette, bgOverride);
|
|
815
1564
|
const parts = [
|
|
816
1565
|
tileCircle,
|
|
817
|
-
|
|
1566
|
+
packPlate,
|
|
1567
|
+
backgroundMarkup,
|
|
1568
|
+
glowLayer,
|
|
818
1569
|
bodyWrapped,
|
|
819
1570
|
// outfit sits on the body but below the face, so face features stay readable
|
|
820
1571
|
outfitSvg,
|
|
821
1572
|
renderTopper(spec.topper, anchor, spec.palette),
|
|
822
|
-
wrap("eyes", renderEyes(spec.eyes, spec.palette, anchor)),
|
|
823
|
-
renderMouth(spec.mouth, spec.palette, anchor, spec.mouthCurveScale ?? 1),
|
|
1573
|
+
wrap("eyes", renderEyes(spec.eyes, spec.palette, anchor, { strokeMul })),
|
|
1574
|
+
renderMouth(spec.mouth, spec.palette, anchor, spec.mouthCurveScale ?? 1, { strokeMul }),
|
|
824
1575
|
antennaWrapped,
|
|
825
1576
|
accessorySvg && spec.accessory === "sparkle" ? wrap("sparkle", accessorySvg) : accessorySvg
|
|
826
1577
|
].join("");
|
|
827
1578
|
const defs = [
|
|
828
|
-
renderBodyDefs(spec.body, spec.palette, gradId),
|
|
829
|
-
spec.hueShift && spec.hueShift !== 0 ? `<filter id="${hueId}" color-interpolation-filters="sRGB"><feColorMatrix type="hueRotate" values="${spec.hueShift}" /></filter>` : ""
|
|
1579
|
+
renderBodyDefs(spec.body, spec.palette, gradId, { flat }),
|
|
1580
|
+
spec.hueShift && spec.hueShift !== 0 ? `<filter id="${hueId}" color-interpolation-filters="sRGB"><feColorMatrix type="hueRotate" values="${spec.hueShift}" /></filter>` : "",
|
|
1581
|
+
glow ? `<filter id="${glowId}" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="4" /></filter>` : ""
|
|
830
1582
|
].join("");
|
|
831
1583
|
return [
|
|
832
1584
|
`<defs>${defs}</defs>`,
|
|
@@ -969,7 +1721,9 @@ var Navii = {
|
|
|
969
1721
|
build
|
|
970
1722
|
};
|
|
971
1723
|
|
|
1724
|
+
exports.BUILT_IN_PACKS = BUILT_IN_PACKS;
|
|
972
1725
|
exports.Navii = Navii;
|
|
1726
|
+
exports.PACK_REGISTRY = PACK_REGISTRY;
|
|
973
1727
|
exports.build = build;
|
|
974
1728
|
exports.createAvatar = createAvatar;
|
|
975
1729
|
exports.createRng = createRng;
|
|
@@ -978,6 +1732,7 @@ exports.random = random;
|
|
|
978
1732
|
exports.renderAvatar = renderAvatar;
|
|
979
1733
|
exports.renderAvatarInner = renderAvatarInner;
|
|
980
1734
|
exports.renderGroup = renderGroup;
|
|
1735
|
+
exports.resolvePacks = resolvePacks;
|
|
981
1736
|
exports.seed = seed;
|
|
982
1737
|
exports.selectAvatar = selectAvatar;
|
|
983
1738
|
//# sourceMappingURL=index.cjs.map
|