solfaces 2.2.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +45 -19
  2. package/SKILL.md +2 -3
  3. package/dist/agent/index.cjs +13 -14
  4. package/dist/agent/index.js +3 -4
  5. package/dist/agent/mcp-server.cjs +326 -333
  6. package/dist/{chunk-5DT27HMT.js → chunk-2KW35VRI.js} +3 -3
  7. package/dist/{chunk-5DT27HMT.js.map → chunk-2KW35VRI.js.map} +1 -1
  8. package/dist/{chunk-PVJR3SFG.cjs → chunk-BI3GHRKQ.cjs} +14 -15
  9. package/dist/chunk-BI3GHRKQ.cjs.map +1 -0
  10. package/dist/{chunk-DRUSCLEF.js → chunk-MYUSB4LA.js} +28 -8
  11. package/dist/chunk-MYUSB4LA.js.map +1 -0
  12. package/dist/{chunk-3CE7Q44S.js → chunk-N5GDLCCL.js} +324 -110
  13. package/dist/chunk-N5GDLCCL.js.map +1 -0
  14. package/dist/{chunk-74CSG63X.js → chunk-O2IIBSQH.js} +6 -7
  15. package/dist/chunk-O2IIBSQH.js.map +1 -0
  16. package/dist/{chunk-6QRDULAO.cjs → chunk-PCSRDAWQ.cjs} +28 -7
  17. package/dist/chunk-PCSRDAWQ.cjs.map +1 -0
  18. package/dist/{chunk-WIXGHS77.cjs → chunk-T7HEUW2O.cjs} +6 -6
  19. package/dist/{chunk-WIXGHS77.cjs.map → chunk-T7HEUW2O.cjs.map} +1 -1
  20. package/dist/{chunk-F54WHRCE.cjs → chunk-W2U6ITMR.cjs} +328 -110
  21. package/dist/chunk-W2U6ITMR.cjs.map +1 -0
  22. package/dist/core/index.cjs +43 -44
  23. package/dist/core/index.d.cts +39 -5
  24. package/dist/core/index.d.ts +39 -5
  25. package/dist/core/index.js +2 -3
  26. package/dist/index.cjs +53 -50
  27. package/dist/index.d.cts +3 -3
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +4 -5
  30. package/dist/react/index.cjs +107 -120
  31. package/dist/react/index.cjs.map +1 -1
  32. package/dist/react/index.d.cts +23 -1
  33. package/dist/react/index.d.ts +23 -1
  34. package/dist/react/index.js +95 -108
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/solfaces.cdn.global.js +2 -2
  37. package/dist/solfaces.cdn.global.js.map +1 -1
  38. package/dist/themes/index.cjs +18 -14
  39. package/dist/themes/index.d.cts +30 -4
  40. package/dist/themes/index.d.ts +30 -4
  41. package/dist/themes/index.js +1 -1
  42. package/dist/traits-D4tbtZIr.d.cts +259 -0
  43. package/dist/traits-D4tbtZIr.d.ts +259 -0
  44. package/dist/vanilla/index.cjs +6 -7
  45. package/dist/vanilla/index.cjs.map +1 -1
  46. package/dist/vanilla/index.d.cts +1 -1
  47. package/dist/vanilla/index.d.ts +1 -1
  48. package/dist/vanilla/index.js +2 -3
  49. package/dist/vanilla/index.js.map +1 -1
  50. package/package.json +4 -1
  51. package/python/solfaces.py +103 -119
  52. package/reference/integrations.md +1 -1
  53. package/dist/chunk-3CE7Q44S.js.map +0 -1
  54. package/dist/chunk-6QRDULAO.cjs.map +0 -1
  55. package/dist/chunk-74CSG63X.js.map +0 -1
  56. package/dist/chunk-CKHLRORB.js +0 -239
  57. package/dist/chunk-CKHLRORB.js.map +0 -1
  58. package/dist/chunk-DRUSCLEF.js.map +0 -1
  59. package/dist/chunk-F54WHRCE.cjs.map +0 -1
  60. package/dist/chunk-PVJR3SFG.cjs.map +0 -1
  61. package/dist/chunk-TYTBYDQU.cjs +0 -244
  62. package/dist/chunk-TYTBYDQU.cjs.map +0 -1
  63. package/dist/traits-sfe7rM9C.d.cts +0 -106
  64. package/dist/traits-sfe7rM9C.d.ts +0 -106
@@ -19,7 +19,10 @@ var EYE_COLORS = [
19
19
  "#3868A8",
20
20
  "#38784C",
21
21
  "#808838",
22
- "#586878"
22
+ "#586878",
23
+ "#A06830",
24
+ "#685898",
25
+ "#889898"
23
26
  ];
24
27
  var HAIR_COLORS = [
25
28
  "#1A1A24",
@@ -43,7 +46,9 @@ var BG_COLORS = [
43
46
  "#7f8bbd",
44
47
  "#8869ab",
45
48
  "#b785b3",
46
- "#ab6984"
49
+ "#ab6984",
50
+ "#a07ab5",
51
+ "#74b5a0"
47
52
  ];
48
53
  function djb2(str) {
49
54
  let hash = 5381;
@@ -67,22 +72,20 @@ function generateTraits(walletAddress, overrides) {
67
72
  const traits = {
68
73
  faceShape: Math.floor(rand() * 4),
69
74
  skinColor: Math.floor(rand() * 10),
70
- eyeStyle: Math.floor(rand() * 8),
71
- eyeColor: Math.floor(rand() * 5),
72
- eyebrows: Math.floor(rand() * 5),
73
- nose: Math.floor(rand() * 4),
75
+ eyeStyle: Math.floor(rand() * 9),
76
+ eyeColor: Math.floor(rand() * 8),
77
+ eyebrows: Math.floor(rand() * 8),
78
+ nose: Math.floor(rand() * 8),
74
79
  mouth: Math.floor(rand() * 8),
75
80
  hairStyle: Math.floor(rand() * 10),
76
81
  hairColor: Math.floor(rand() * 10),
77
- accessory: Math.floor(rand() * 10),
78
- bgColor: Math.floor(rand() * 10)
82
+ accessory: Math.floor(rand() * 12),
83
+ bgColor: Math.floor(rand() * 12)
79
84
  };
80
85
  return overrides ? { ...traits, ...overrides } : traits;
81
86
  }
82
87
  function effectiveAccessory(traits) {
83
- const ai = traits.accessory % 10;
84
- const hi = traits.hairStyle % 10;
85
- if ((ai === 4 || ai === 7) && (hi === 5 || hi === 6)) return 0;
88
+ const ai = traits.accessory % 12;
86
89
  return ai;
87
90
  }
88
91
  var FACE_LABELS = ["Squircle"];
@@ -98,22 +101,22 @@ var SKIN_LABELS = [
98
101
  "Brown",
99
102
  "Deep"
100
103
  ];
101
- var EYE_STYLE_LABELS = ["Round", "Minimal", "Almond", "Wide", "Relaxed", "Joyful", "Bright", "Gentle"];
102
- var EYE_COLOR_LABELS = ["Chocolate", "Sky", "Emerald", "Hazel", "Storm"];
103
- var BROW_LABELS = ["Wispy", "Straight", "Natural", "Arched", "Angled"];
104
- var NOSE_LABELS = ["Shadow", "Button", "Soft", "Nostrils"];
104
+ var EYE_STYLE_LABELS = ["Round", "Minimal", "Almond", "Wide", "Relaxed", "Joyful", "Bright", "Gentle", "Side-look"];
105
+ var EYE_COLOR_LABELS = ["Chocolate", "Sky", "Emerald", "Hazel", "Storm", "Amber", "Violet", "Gray"];
106
+ var BROW_LABELS = ["Wispy", "Straight", "Natural", "Arched", "Angled", "Worried", "Bushy", "Thin"];
107
+ var NOSE_LABELS = ["Shadow", "Button", "Soft", "Nostrils", "Pointed", "Wide", "Bridge", "Snub"];
105
108
  var MOUTH_LABELS = ["Smile", "Calm", "Happy", "Oh", "Smirk", "Grin", "Flat", "Pout"];
106
109
  var HAIR_STYLE_LABELS = [
107
110
  "Bald",
108
- "Short",
111
+ "Crew",
109
112
  "Curly",
110
- "Side Sweep",
111
- "Puff",
113
+ "Side Part",
112
114
  "Long",
113
- "Bob",
114
115
  "Buzz",
115
- "Wavy",
116
- "Topknot"
116
+ "Beanie",
117
+ "Cap",
118
+ "Mohawk",
119
+ "Bun"
117
120
  ];
118
121
  var HAIR_COLOR_LABELS = [
119
122
  "Black",
@@ -137,7 +140,9 @@ var ACCESSORY_LABELS = [
137
140
  "Freckles",
138
141
  "Stud Earrings",
139
142
  "Aviators",
140
- "Band-Aid"
143
+ "Band-Aid",
144
+ "Left Eyebrow Slit",
145
+ "Right Eyebrow Slit"
141
146
  ];
142
147
  var BG_COLOR_LABELS = [
143
148
  "Rose",
@@ -149,7 +154,9 @@ var BG_COLOR_LABELS = [
149
154
  "Sky",
150
155
  "Lavender",
151
156
  "Orchid",
152
- "Blush"
157
+ "Blush",
158
+ "Lilac",
159
+ "Seafoam"
153
160
  ];
154
161
  function getTraitLabels(traits) {
155
162
  return {
@@ -203,7 +210,21 @@ function blend(a, b, t = 0.5) {
203
210
  }
204
211
  function luminance(hex) {
205
212
  const [r, g, b] = hexToRgb(hex);
206
- return (r + g + b) / 3;
213
+ return 0.299 * r + 0.587 * g + 0.114 * b;
214
+ }
215
+ function relativeLuminance(hex) {
216
+ const [r, g, b] = hexToRgb(hex).map((c) => {
217
+ const s = c / 255;
218
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
219
+ });
220
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
221
+ }
222
+ function contrastRatio(hex1, hex2) {
223
+ const l1 = relativeLuminance(hex1);
224
+ const l2 = relativeLuminance(hex2);
225
+ const lighter = Math.max(l1, l2);
226
+ const darker = Math.min(l1, l2);
227
+ return (lighter + 0.05) / (darker + 0.05);
207
228
  }
208
229
  function deriveSkinColors(skin) {
209
230
  const sL = luminance(skin);
@@ -237,11 +258,20 @@ function deriveSkinColors(skin) {
237
258
  const lipRaw = blend(skin, lipBase, Math.min(0.82, lipBlend));
238
259
  const [lr, lg, lb] = hexToRgb(lipRaw);
239
260
  const lipD = Math.abs(sr - lr) + Math.abs(sg - lg) + Math.abs(sb - lb);
240
- const lipColor = lipD < 60 ? blend(skin, lipBase, 0.78) : lipRaw;
241
- const browColor = isDark ? lighten(skin, sL < 80 ? 0.35 : 0.25) : darken(skin, 0.55);
242
- const noseFill = isDark ? lighten(skin, 0.2) : darken(skin, 0.2);
261
+ let lipColor = lipD < 60 ? blend(skin, lipBase, 0.78) : lipRaw;
262
+ let attempts = 0;
263
+ while (contrastRatio(lipColor, skin) < 1.8 && attempts < 12) {
264
+ lipColor = darken(lipColor, 0.06);
265
+ attempts++;
266
+ }
267
+ const browColor = isDark ? lighten(skin, sL < 80 ? 0.35 : sL < 100 ? 0.32 : 0.25) : darken(skin, 0.55);
268
+ const noseShift = 0.2 + 0.06 * (1 - Math.abs(sL - 100) / 140);
269
+ const noseFill = isDark ? lighten(skin, noseShift) : darken(skin, noseShift);
243
270
  const earT = Math.max(0, Math.min(1, (sL - 100) / 60));
244
- const earFill = blend(lighten(skin, 0.08), skinMid, earT);
271
+ let earFill = blend(lighten(skin, 0.08), skinMid, earT);
272
+ if (contrastRatio(earFill, skin) < 1.05) {
273
+ earFill = isDark ? lighten(earFill, 0.06) : darken(earFill, 0.06);
274
+ }
245
275
  const earShadow = darken(skin, 0.1 + 0.06 * (1 - Math.min(1, sL / 160)));
246
276
  const lidColor = isDark ? lighten(skin, 0.18) : darken(skin, 0.15);
247
277
  const ewT = Math.max(0, Math.min(1, (sL - 60) / 180));
@@ -265,10 +295,198 @@ function deriveSkinColors(skin) {
265
295
  accessoryColor
266
296
  };
267
297
  }
268
- function buzzOpacity(hairCol, skin) {
269
- const [hr, hg, hb] = hexToRgb(hairCol);
270
- const [sr, sg, sb] = hexToRgb(skin);
271
- return Math.abs(hr - sr) + Math.abs(hg - sg) + Math.abs(hb - sb) < 80 ? 0.7 : 0.5;
298
+
299
+ // src/core/describe.ts
300
+ var SKIN_TONES = {
301
+ 0: "porcelain",
302
+ 1: "ivory",
303
+ 2: "fair",
304
+ 3: "light",
305
+ 4: "sand",
306
+ 5: "golden",
307
+ 6: "warm",
308
+ 7: "caramel",
309
+ 8: "brown",
310
+ 9: "deep"
311
+ };
312
+ var EYE_STYLES = {
313
+ 0: "round, wide-open",
314
+ 1: "small and minimal",
315
+ 2: "almond-shaped",
316
+ 3: "wide and expressive",
317
+ 4: "relaxed, half-lidded",
318
+ 5: "joyful, crescent-shaped",
319
+ 6: "bright and sparkling",
320
+ 7: "gentle and narrow",
321
+ 8: "side-looking, shifted"
322
+ };
323
+ var EYE_COLORS_DESC = {
324
+ 0: "dark brown",
325
+ 1: "blue",
326
+ 2: "green",
327
+ 3: "hazel",
328
+ 4: "gray",
329
+ 5: "amber",
330
+ 6: "violet",
331
+ 7: "steel gray"
332
+ };
333
+ var EYEBROW_STYLES = {
334
+ 0: "wispy",
335
+ 1: "straight",
336
+ 2: "natural",
337
+ 3: "elegantly arched",
338
+ 4: "sharply angled",
339
+ 5: "worried, furrowed",
340
+ 6: "bushy",
341
+ 7: "thin, delicate"
342
+ };
343
+ var NOSE_STYLES = {
344
+ 0: "a subtle shadow nose",
345
+ 1: "a small button nose",
346
+ 2: "a soft curved nose",
347
+ 3: "a button nose with visible nostrils",
348
+ 4: "a pointed nose",
349
+ 5: "a wide, broad nose",
350
+ 6: "a nose with a visible bridge",
351
+ 7: "a small snub nose"
352
+ };
353
+ var MOUTH_STYLES = {
354
+ 0: "a gentle smile",
355
+ 1: "a calm, neutral expression",
356
+ 2: "a happy grin",
357
+ 3: "a surprised O-shaped mouth",
358
+ 4: "a confident smirk",
359
+ 5: "a wide, toothy grin",
360
+ 6: "a flat, straight expression",
361
+ 7: "a soft pout"
362
+ };
363
+ var ACCESSORY_DESC = {
364
+ 0: "",
365
+ 1: "a beauty mark",
366
+ 2: "round glasses",
367
+ 3: "rectangular glasses",
368
+ 4: "a dangling earring",
369
+ 5: "a headband",
370
+ 6: "freckles",
371
+ 7: "stud earrings",
372
+ 8: "aviator sunglasses",
373
+ 9: "a band-aid",
374
+ 10: "a left eyebrow slit",
375
+ 11: "a right eyebrow slit"
376
+ };
377
+ var BG_COLORS_DESC = {
378
+ 0: "rose",
379
+ 1: "olive",
380
+ 2: "sage",
381
+ 3: "fern",
382
+ 4: "mint",
383
+ 5: "ocean",
384
+ 6: "sky",
385
+ 7: "lavender",
386
+ 8: "orchid",
387
+ 9: "blush",
388
+ 10: "lilac",
389
+ 11: "seafoam"
390
+ };
391
+ function describeAppearance(walletAddress, options) {
392
+ const traits = generateTraits(walletAddress);
393
+ const {
394
+ includeBackground = true,
395
+ format = "paragraph",
396
+ perspective = "third",
397
+ name
398
+ } = options ?? {};
399
+ if (format === "structured") return buildStructured(traits, includeBackground);
400
+ if (format === "compact") return buildCompact(traits);
401
+ return buildParagraph(traits, perspective, name, includeBackground);
402
+ }
403
+ function buildParagraph(t, perspective, name, includeBg) {
404
+ const parts = [];
405
+ const ai = effectiveAccessory(t);
406
+ const subject = perspective === "first" ? name ? `I'm ${name}. I have` : "I have" : name ? `${name} has` : "This SolFace has";
407
+ const im = perspective === "first" ? "I'm" : "They're";
408
+ parts.push(`${subject} a squircle face with ${SKIN_TONES[t.skinColor] ?? "warm"} skin`);
409
+ const eyeStyle = EYE_STYLES[t.eyeStyle] ?? "round";
410
+ const eyeColor = EYE_COLORS_DESC[t.eyeColor] ?? "dark";
411
+ parts.push(`${eyeStyle} ${eyeColor} eyes`);
412
+ const brows = EYEBROW_STYLES[t.eyebrows];
413
+ if (brows) parts.push(`${brows} eyebrows`);
414
+ parts.push(perspective === "first" ? "and am bald" : "and is bald");
415
+ let desc = parts[0];
416
+ if (parts.length > 2) {
417
+ desc += ", " + parts.slice(1, -1).join(", ") + ", " + parts[parts.length - 1];
418
+ } else if (parts.length === 2) {
419
+ desc += " and " + parts[1];
420
+ }
421
+ desc += ".";
422
+ const nose = NOSE_STYLES[t.nose];
423
+ if (nose) {
424
+ const noseSubject = perspective === "first" ? "I have" : (name ?? "They") + (name ? " has" : " have");
425
+ desc += ` ${noseSubject} ${nose}.`;
426
+ }
427
+ const acc = ACCESSORY_DESC[ai];
428
+ if (acc) {
429
+ desc += ` ${im} wearing ${acc}.`;
430
+ }
431
+ const mouth = MOUTH_STYLES[t.mouth] ?? "a smile";
432
+ const mouthVerb = perspective === "first" ? "I have" : (name ?? "They") + (name ? " has" : " have");
433
+ desc += ` ${mouthVerb} ${mouth}.`;
434
+ if (includeBg) {
435
+ const bg = BG_COLORS_DESC[t.bgColor] ?? "colorful";
436
+ desc += ` The background is ${bg}.`;
437
+ }
438
+ return desc;
439
+ }
440
+ function buildStructured(t, includeBg) {
441
+ const ai = effectiveAccessory(t);
442
+ const lines = [
443
+ `Face: squircle`,
444
+ `Skin: ${SKIN_TONES[t.skinColor] ?? "warm"}`,
445
+ `Eyes: ${EYE_STYLES[t.eyeStyle] ?? "round"}, ${EYE_COLORS_DESC[t.eyeColor] ?? "dark"}`,
446
+ `Eyebrows: ${EYEBROW_STYLES[t.eyebrows] ?? "wispy"}`
447
+ ];
448
+ const nose = NOSE_STYLES[t.nose];
449
+ if (nose) lines.push(`Nose: ${nose.replace(/^a /, "")}`);
450
+ lines.push(`Mouth: ${MOUTH_STYLES[t.mouth] ?? "smile"}`);
451
+ lines.push("Hair: bald");
452
+ const acc = ACCESSORY_DESC[ai];
453
+ if (acc) lines.push(`Accessory: ${acc}`);
454
+ if (includeBg) {
455
+ lines.push(`Background: ${BG_COLORS_DESC[t.bgColor] ?? "colorful"}`);
456
+ }
457
+ return lines.join("\n");
458
+ }
459
+ function buildCompact(t) {
460
+ const ai = effectiveAccessory(t);
461
+ const parts = [];
462
+ parts.push(`squircle face`);
463
+ parts.push(`${SKIN_TONES[t.skinColor] ?? "warm"} skin`);
464
+ parts.push(`${EYE_COLORS_DESC[t.eyeColor] ?? "dark"} ${EYE_STYLES[t.eyeStyle] ?? "round"} eyes`);
465
+ parts.push("bald");
466
+ const acc = ACCESSORY_DESC[ai];
467
+ if (acc) parts.push(acc);
468
+ parts.push((MOUTH_STYLES[t.mouth] ?? "smiling").replace(/^a /, ""));
469
+ return parts.join(", ");
470
+ }
471
+ function solFaceAltText(walletAddress) {
472
+ return `SolFace avatar: ${describeAppearance(walletAddress, { format: "compact", includeBackground: false })}`;
473
+ }
474
+ function agentAppearancePrompt(walletAddress, agentName) {
475
+ const desc = describeAppearance(walletAddress, {
476
+ perspective: "first",
477
+ name: agentName,
478
+ includeBackground: false,
479
+ format: "paragraph"
480
+ });
481
+ const hash = (djb2Simple(walletAddress) >>> 0).toString(16).padStart(8, "0");
482
+ return `My visual identity is a SolFace avatar (ID: ${hash}) derived from my wallet address. ${desc} This appearance is deterministic \u2014 anyone who looks up my wallet will see the same face.`;
483
+ }
484
+ function djb2Simple(str) {
485
+ let hash = 5381;
486
+ for (let i = 0; i < str.length; i++) {
487
+ hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
488
+ }
489
+ return hash >>> 0;
272
490
  }
273
491
 
274
492
  // src/core/renderer.ts
@@ -284,12 +502,9 @@ function buildDefs(id, skin, skinHi, skinLo, hairCol, bgCol, cheekColor, cheekOp
284
502
  let d = "<defs>";
285
503
  d += `<linearGradient id="${id}sg" x1="0" y1="0" x2="0" y2="1">`;
286
504
  d += `<stop offset="0%" stop-color="${skinHi}"/>`;
505
+ d += `<stop offset="50%" stop-color="${skin}"/>`;
287
506
  d += `<stop offset="100%" stop-color="${skinLo}"/>`;
288
507
  d += `</linearGradient>`;
289
- d += `<linearGradient id="${id}hg" x1="0" y1="0" x2="0" y2="1">`;
290
- d += `<stop offset="0%" stop-color="${lighten(hairCol, 0.15)}"/>`;
291
- d += `<stop offset="100%" stop-color="${darken(hairCol, 0.15)}"/>`;
292
- d += `</linearGradient>`;
293
508
  d += `<linearGradient id="${id}bg" x1="0" y1="0" x2="1" y2="1">`;
294
509
  d += `<stop offset="0%" stop-color="${lighten(bgCol, 0.12)}"/>`;
295
510
  d += `<stop offset="100%" stop-color="${darken(bgCol, 0.12)}"/>`;
@@ -315,18 +530,8 @@ function buildDefs(id, skin, skinHi, skinLo, hairCol, bgCol, cheekColor, cheekOp
315
530
  d += "</defs>";
316
531
  return d;
317
532
  }
318
- function renderHairBack(hi, id, flat) {
319
- const fill = flat ? "currentColor" : `url(#${id}hg)`;
320
- switch (hi) {
321
- case 5:
322
- return `<g fill="${fill}"><rect x="10" y="14" width="44" height="42" rx="6"/></g>`;
323
- case 6:
324
- return `<g fill="${fill}"><rect x="12" y="14" width="40" height="32" rx="8"/></g>`;
325
- case 8:
326
- return `<g fill="${fill}"><rect x="11" y="14" width="42" height="38" rx="8"/></g>`;
327
- default:
328
- return "";
329
- }
533
+ function renderHairBack(_hi, _id, _flat) {
534
+ return "";
330
535
  }
331
536
  function renderEars(earFill, earShadow) {
332
537
  return `<ellipse cx="11" cy="34" rx="4" ry="5" fill="${earFill}"/><ellipse cx="11" cy="34" rx="2.5" ry="3.5" fill="${earShadow}" opacity="0.3"/><ellipse cx="53" cy="34" rx="4" ry="5" fill="${earFill}"/><ellipse cx="53" cy="34" rx="2.5" ry="3.5" fill="${earShadow}" opacity="0.3"/>`;
@@ -340,35 +545,8 @@ function renderFaceOverlays(id, flat) {
340
545
  if (flat) return "";
341
546
  return `<rect x="14" y="16" width="36" height="38" rx="12" ry="12" fill="url(#${id}glow)"/><rect x="14" y="16" width="36" height="38" rx="12" ry="12" fill="url(#${id}chin)"/><ellipse cx="22" cy="42" rx="5" ry="3.5" fill="url(#${id}cL)"/><ellipse cx="42" cy="42" rx="5" ry="3.5" fill="url(#${id}cR)"/><line x1="20" y1="50" x2="44" y2="50" stroke="currentColor" stroke-width="0.3" opacity="0.08" stroke-linecap="round"/>`;
342
547
  }
343
- function renderHairFront(hi, id, hairCol, skin, flat) {
344
- const fill = flat ? hairCol : `url(#${id}hg)`;
345
- switch (hi) {
346
- case 0:
347
- return "";
348
- // Bald
349
- case 1:
350
- return `<path d="M14 28 Q14 14 32 12 Q50 14 50 28 L50 22 Q50 12 32 10 Q14 12 14 22 Z" fill="${fill}"/>`;
351
- case 2:
352
- return `<g fill="${fill}"><circle cx="20" cy="14" r="5"/><circle cx="28" cy="11" r="5.5"/><circle cx="36" cy="11" r="5.5"/><circle cx="44" cy="14" r="5"/><circle cx="16" cy="20" r="4"/><circle cx="48" cy="20" r="4"/></g>`;
353
- case 3:
354
- return `<path d="M14 26 Q14 12 32 10 Q50 12 50 26 L50 20 Q50 10 32 8 Q14 10 14 20 Z" fill="${fill}"/><path d="M14 20 Q8 16 10 8 Q14 10 20 16 Z" fill="${fill}"/>`;
355
- case 4:
356
- return `<ellipse cx="32" cy="10" rx="14" ry="8" fill="${fill}"/>`;
357
- case 5:
358
- return `<path d="M14 28 Q14 12 32 10 Q50 12 50 28 L50 20 Q50 10 32 8 Q14 10 14 20 Z" fill="${fill}"/>`;
359
- case 6:
360
- return `<path d="M14 28 Q14 12 32 10 Q50 12 50 28 L50 20 Q50 10 32 8 Q14 10 14 20 Z" fill="${fill}"/><rect x="10" y="28" width="8" height="14" rx="4" fill="${fill}"/><rect x="46" y="28" width="8" height="14" rx="4" fill="${fill}"/>`;
361
- case 7: {
362
- const bOp = buzzOpacity(hairCol, skin);
363
- return `<rect x="15" y="13" width="34" height="16" rx="10" ry="8" fill="${hairCol}" opacity="${bOp.toFixed(2)}"/>`;
364
- }
365
- case 8:
366
- return `<path d="M14 28 Q14 12 32 10 Q50 12 50 28 L50 20 Q50 10 32 8 Q14 10 14 20 Z" fill="${fill}"/><path d="M12 30 Q10 20 14 16" fill="none" stroke="${fill}" stroke-width="4" stroke-linecap="round"/><path d="M52 30 Q54 20 50 16" fill="none" stroke="${fill}" stroke-width="4" stroke-linecap="round"/>`;
367
- case 9:
368
- return `<path d="M14 28 Q14 14 32 12 Q50 14 50 28 L50 22 Q50 12 32 10 Q14 12 14 22 Z" fill="${fill}"/><ellipse cx="32" cy="6" rx="6" ry="5" fill="${fill}"/>`;
369
- default:
370
- return "";
371
- }
548
+ function renderHairFront(_hi, _id, _hairCol, _skin, _flat) {
549
+ return "";
372
550
  }
373
551
  function renderEyes(ei, eyeCol, eyeWhite, lidColor, full) {
374
552
  const lx = 25, rx = 39, y = 33;
@@ -383,8 +561,10 @@ function renderEyes(ei, eyeCol, eyeWhite, lidColor, full) {
383
561
  if (full) s += `<circle cx="${rx + 1.5}" cy="${y - 1}" r="0.7" fill="white" opacity="0.8"/>`;
384
562
  break;
385
563
  case 1:
386
- s += `<circle cx="${lx}" cy="${y}" r="2" fill="${eyeCol}"/>`;
387
- s += `<circle cx="${rx}" cy="${y}" r="2" fill="${eyeCol}"/>`;
564
+ s += `<circle cx="${lx}" cy="${y}" r="2.2" fill="${eyeCol}"/>`;
565
+ if (full) s += `<circle cx="${lx + 0.5}" cy="${y - 0.5}" r="0.5" fill="white" opacity="0.4"/>`;
566
+ s += `<circle cx="${rx}" cy="${y}" r="2.2" fill="${eyeCol}"/>`;
567
+ if (full) s += `<circle cx="${rx + 0.5}" cy="${y - 0.5}" r="0.5" fill="white" opacity="0.4"/>`;
388
568
  break;
389
569
  case 2:
390
570
  s += `<ellipse cx="${lx}" cy="${y}" rx="4.5" ry="2.8" fill="${eyeWhite}"/>`;
@@ -411,24 +591,16 @@ function renderEyes(ei, eyeCol, eyeWhite, lidColor, full) {
411
591
  if (full) s += `<line x1="${rx - 4.5}" y1="${y - 0.5}" x2="${rx + 4.5}" y2="${y - 0.5}" stroke="${lidColor}" stroke-width="0.8" stroke-linecap="round"/>`;
412
592
  break;
413
593
  case 5:
414
- s += `<path d="M${lx - 4} ${y} Q${lx} ${y + 4} ${lx + 4} ${y}" fill="none" stroke="${eyeCol}" stroke-width="1.8" stroke-linecap="round"/>`;
415
- s += `<path d="M${rx - 4} ${y} Q${rx} ${y + 4} ${rx + 4} ${y}" fill="none" stroke="${eyeCol}" stroke-width="1.8" stroke-linecap="round"/>`;
594
+ s += `<path d="M${lx - 4} ${y} Q${lx} ${y + 4.5} ${lx + 4} ${y}" fill="none" stroke="${eyeCol}" stroke-width="2" stroke-linecap="round"/>`;
595
+ s += `<path d="M${rx - 4} ${y} Q${rx} ${y + 4.5} ${rx + 4} ${y}" fill="none" stroke="${eyeCol}" stroke-width="2" stroke-linecap="round"/>`;
416
596
  break;
417
597
  case 6:
418
598
  s += `<circle cx="${lx}" cy="${y}" r="3.5" fill="${eyeWhite}"/>`;
419
599
  s += `<circle cx="${lx + 0.5}" cy="${y}" r="2" fill="${eyeCol}"/>`;
420
600
  s += `<circle cx="${lx + 1.5}" cy="${y - 1}" r="1" fill="white" opacity="0.9"/>`;
421
- if (full) {
422
- s += `<line x1="${lx + 2.5}" y1="${y - 3.5}" x2="${lx + 4}" y2="${y - 5}" stroke="${eyeCol}" stroke-width="0.8" stroke-linecap="round"/>`;
423
- s += `<line x1="${lx + 3.5}" y1="${y - 2.5}" x2="${lx + 5}" y2="${y - 3.5}" stroke="${eyeCol}" stroke-width="0.8" stroke-linecap="round"/>`;
424
- }
425
601
  s += `<circle cx="${rx}" cy="${y}" r="3.5" fill="${eyeWhite}"/>`;
426
602
  s += `<circle cx="${rx + 0.5}" cy="${y}" r="2" fill="${eyeCol}"/>`;
427
603
  s += `<circle cx="${rx + 1.5}" cy="${y - 1}" r="1" fill="white" opacity="0.9"/>`;
428
- if (full) {
429
- s += `<line x1="${rx + 2.5}" y1="${y - 3.5}" x2="${rx + 4}" y2="${y - 5}" stroke="${eyeCol}" stroke-width="0.8" stroke-linecap="round"/>`;
430
- s += `<line x1="${rx + 3.5}" y1="${y - 2.5}" x2="${rx + 5}" y2="${y - 3.5}" stroke="${eyeCol}" stroke-width="0.8" stroke-linecap="round"/>`;
431
- }
432
604
  break;
433
605
  case 7:
434
606
  s += `<ellipse cx="${lx}" cy="${y}" rx="4.5" ry="1.5" fill="${eyeWhite}"/>`;
@@ -436,6 +608,14 @@ function renderEyes(ei, eyeCol, eyeWhite, lidColor, full) {
436
608
  s += `<ellipse cx="${rx}" cy="${y}" rx="4.5" ry="1.5" fill="${eyeWhite}"/>`;
437
609
  s += `<ellipse cx="${rx + 0.5}" cy="${y}" rx="2.2" ry="1.2" fill="${eyeCol}"/>`;
438
610
  break;
611
+ case 8:
612
+ s += `<circle cx="${lx}" cy="${y}" r="3.5" fill="${eyeWhite}"/>`;
613
+ s += `<circle cx="${lx - 1}" cy="${y}" r="2" fill="${eyeCol}"/>`;
614
+ if (full) s += `<circle cx="${lx - 0.3}" cy="${y - 0.8}" r="0.7" fill="white" opacity="0.8"/>`;
615
+ s += `<circle cx="${rx}" cy="${y}" r="3.5" fill="${eyeWhite}"/>`;
616
+ s += `<circle cx="${rx - 1}" cy="${y}" r="2" fill="${eyeCol}"/>`;
617
+ if (full) s += `<circle cx="${rx - 0.3}" cy="${y - 0.8}" r="0.7" fill="white" opacity="0.8"/>`;
618
+ break;
439
619
  default:
440
620
  s += `<circle cx="${lx}" cy="${y}" r="3.5" fill="${eyeWhite}"/>`;
441
621
  s += `<circle cx="${lx + 0.8}" cy="${y}" r="2" fill="${eyeCol}"/>`;
@@ -448,7 +628,7 @@ function renderEyes(ei, eyeCol, eyeWhite, lidColor, full) {
448
628
  }
449
629
  return s;
450
630
  }
451
- function renderEyebrows(bi, browColor) {
631
+ function renderEyebrows(bi, browColor, full = true) {
452
632
  const lx = 25, rx = 39, y = 27;
453
633
  switch (bi) {
454
634
  case 0:
@@ -461,6 +641,14 @@ function renderEyebrows(bi, browColor) {
461
641
  return `<path d="M${lx - 4} ${y + 1} Q${lx} ${y - 3} ${lx + 4} ${y + 1}" fill="none" stroke="${browColor}" stroke-width="1" stroke-linecap="round"/><path d="M${rx - 4} ${y + 1} Q${rx} ${y - 3} ${rx + 4} ${y + 1}" fill="none" stroke="${browColor}" stroke-width="1" stroke-linecap="round"/>`;
462
642
  case 4:
463
643
  return `<polyline points="${lx - 3},${y + 1} ${lx},${y - 2} ${lx + 3},${y}" fill="none" stroke="${browColor}" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><polyline points="${rx - 3},${y} ${rx},${y - 2} ${rx + 3},${y + 1}" fill="none" stroke="${browColor}" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>`;
644
+ case 5:
645
+ return `<line x1="${lx - 3}" y1="${y - 1}" x2="${lx + 3}" y2="${y + 1}" stroke="${browColor}" stroke-width="1.1" stroke-linecap="round"/><line x1="${rx - 3}" y1="${y + 1}" x2="${rx + 3}" y2="${y - 1}" stroke="${browColor}" stroke-width="1.1" stroke-linecap="round"/>`;
646
+ case 6: {
647
+ const bw = full ? "2.0" : "1.5";
648
+ return `<path d="M${lx - 4} ${y + 0.5} Q${lx} ${y - 2} ${lx + 4} ${y + 0.5}" fill="none" stroke="${browColor}" stroke-width="${bw}" stroke-linecap="round"/><path d="M${rx - 4} ${y + 0.5} Q${rx} ${y - 2} ${rx + 4} ${y + 0.5}" fill="none" stroke="${browColor}" stroke-width="${bw}" stroke-linecap="round"/>`;
649
+ }
650
+ case 7:
651
+ return `<path d="M${lx - 3.5} ${y} Q${lx} ${y - 1.5} ${lx + 3.5} ${y}" fill="none" stroke="${browColor}" stroke-width="0.5" stroke-linecap="round"/><path d="M${rx - 3.5} ${y} Q${rx} ${y - 1.5} ${rx + 3.5} ${y}" fill="none" stroke="${browColor}" stroke-width="0.5" stroke-linecap="round"/>`;
464
652
  default:
465
653
  return "";
466
654
  }
@@ -476,6 +664,14 @@ function renderNose(ni, noseFill) {
476
664
  return `<path d="M${cx - 2} ${y + 1} Q${cx} ${y - 2} ${cx + 2} ${y + 1}" fill="none" stroke="${noseFill}" stroke-width="1" stroke-linecap="round" opacity="0.5"/>`;
477
665
  case 3:
478
666
  return `<circle cx="${cx - 1.8}" cy="${y}" r="1.2" fill="${noseFill}" opacity="0.4"/><circle cx="${cx + 1.8}" cy="${y}" r="1.2" fill="${noseFill}" opacity="0.4"/>`;
667
+ case 4:
668
+ return `<path d="M${cx} ${y - 2} L${cx - 2} ${y + 1.5} L${cx + 2} ${y + 1.5} Z" fill="${noseFill}" opacity="0.4"/>`;
669
+ case 5:
670
+ return `<ellipse cx="${cx}" cy="${y}" rx="3.5" ry="1.5" fill="${noseFill}" opacity="0.35"/>`;
671
+ case 6:
672
+ return `<line x1="${cx}" y1="${y - 3}" x2="${cx}" y2="${y + 1}" stroke="${noseFill}" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>`;
673
+ case 7:
674
+ return `<circle cx="${cx}" cy="${y + 0.5}" r="2" fill="${noseFill}" opacity="0.35"/><ellipse cx="${cx}" cy="${y - 0.5}" rx="1" ry="0.5" fill="${noseFill}" opacity="0.15"/>`;
479
675
  default:
480
676
  return `<ellipse cx="${cx}" cy="${y}" rx="2" ry="1.2" fill="${noseFill}" opacity="0.35"/>`;
481
677
  }
@@ -497,20 +693,20 @@ function renderMouth(mi, lipColor, isDark) {
497
693
  case 5:
498
694
  return `<path d="M${cx - 5} ${y} Q${cx} ${y + 6} ${cx + 5} ${y}" fill="${teethCol}" stroke="${lipColor}" stroke-width="1"/><line x1="${cx - 4}" y1="${y + 1.5}" x2="${cx + 4}" y2="${y + 1.5}" stroke="${lipColor}" stroke-width="0.3" opacity="0.3"/>`;
499
695
  case 6:
500
- return `<line x1="${cx - 4}" y1="${y + 1}" x2="${cx + 4}" y2="${y + 1}" stroke="${lipColor}" stroke-width="1.5" stroke-linecap="round"/>`;
696
+ return `<path d="M${cx - 4} ${y + 0.5} Q${cx} ${y + 1.5} ${cx + 4} ${y + 0.5}" fill="none" stroke="${lipColor}" stroke-width="1.4" stroke-linecap="round"/>`;
501
697
  case 7:
502
698
  return `<ellipse cx="${cx}" cy="${y + 1}" rx="3.5" ry="2" fill="${lipColor}" opacity="0.25"/><path d="M${cx - 3} ${y} Q${cx} ${y + 2.5} ${cx + 3} ${y}" fill="none" stroke="${lipColor}" stroke-width="1.2" stroke-linecap="round"/>`;
503
699
  default:
504
700
  return `<path d="M${cx - 4} ${y} Q${cx} ${y + 4} ${cx + 4} ${y}" fill="none" stroke="${lipColor}" stroke-width="1.4" stroke-linecap="round"/>`;
505
701
  }
506
702
  }
507
- function renderAccessory(ai, accessoryColor, glassesColor, earringColor, headbandColor) {
703
+ function renderAccessory(ai, accessoryColor, glassesColor, earringColor, headbandColor, beautyMarkColor = "#3a2a2a", freckleColor = "#a0785a", skinColor = "#E8BA8B") {
508
704
  switch (ai) {
509
705
  case 0:
510
706
  return "";
511
707
  // None
512
708
  case 1:
513
- return `<circle cx="40" cy="44" r="0.8" fill="#3a2a2a"/>`;
709
+ return `<circle cx="40" cy="44" r="0.8" fill="${beautyMarkColor}"/>`;
514
710
  case 2:
515
711
  return `<g fill="none" stroke="${glassesColor}" stroke-width="1"><circle cx="25" cy="33" r="5.5"/><circle cx="39" cy="33" r="5.5"/><line x1="30.5" y1="33" x2="33.5" y2="33"/><line x1="19.5" y1="33" x2="14" y2="31"/><line x1="44.5" y1="33" x2="50" y2="31"/></g>`;
516
712
  case 3:
@@ -520,13 +716,17 @@ function renderAccessory(ai, accessoryColor, glassesColor, earringColor, headban
520
716
  case 5:
521
717
  return `<rect x="13" y="20" width="38" height="3.5" rx="1.5" fill="${headbandColor}" opacity="0.85"/>`;
522
718
  case 6:
523
- return `<g fill="#a0785a" opacity="0.35"><circle cx="21" cy="40" r="0.6"/><circle cx="23" cy="42" r="0.5"/><circle cx="19" cy="41.5" r="0.5"/><circle cx="43" cy="40" r="0.6"/><circle cx="41" cy="42" r="0.5"/><circle cx="45" cy="41.5" r="0.5"/></g>`;
719
+ return `<g fill="${freckleColor}" opacity="0.35"><circle cx="21" cy="40" r="0.6"/><circle cx="23" cy="42" r="0.5"/><circle cx="19" cy="41.5" r="0.5"/><circle cx="43" cy="40" r="0.6"/><circle cx="41" cy="42" r="0.5"/><circle cx="45" cy="41.5" r="0.5"/></g>`;
524
720
  case 7:
525
721
  return `<circle cx="10" cy="37" r="1.2" fill="${earringColor}"/><circle cx="54" cy="37" r="1.2" fill="${earringColor}"/>`;
526
722
  case 8:
527
723
  return `<g fill="none" stroke="${glassesColor}" stroke-width="1.2"><path d="M19 30 Q19 28 25 28 Q31 28 31 33 Q31 38 25 38 Q19 38 19 33 Z" fill="${glassesColor}" fill-opacity="0.15"/><path d="M33 30 Q33 28 39 28 Q45 28 45 33 Q45 38 39 38 Q33 38 33 33 Z" fill="${glassesColor}" fill-opacity="0.15"/><line x1="31" y1="32" x2="33" y2="32"/><line x1="19" y1="31" x2="14" y2="29"/><line x1="45" y1="31" x2="50" y2="29"/></g>`;
528
724
  case 9:
529
- return `<g><rect x="38" y="38" width="8" height="4" rx="1" fill="#f0d0a0" transform="rotate(-15 42 40)"/><line x1="40" y1="39" x2="40" y2="41" stroke="#c0a080" stroke-width="0.4" transform="rotate(-15 42 40)"/><line x1="42" y1="39" x2="42" y2="41" stroke="#c0a080" stroke-width="0.4" transform="rotate(-15 42 40)"/><line x1="44" y1="39" x2="44" y2="41" stroke="#c0a080" stroke-width="0.4" transform="rotate(-15 42 40)"/></g>`;
725
+ return `<g><rect x="38" y="38" width="9" height="4.5" rx="1.2" fill="#f0d0a0" transform="rotate(-15 42 40)"/><rect x="39.5" y="38.5" width="6" height="3.5" rx="0.8" fill="#f5ddb5" transform="rotate(-15 42 40)"/><circle cx="42.5" cy="40.25" r="0.5" fill="#d4b898" transform="rotate(-15 42 40)"/></g>`;
726
+ case 10:
727
+ return `<line x1="23" y1="24.8" x2="23.8" y2="29.2" stroke="${skinColor}" stroke-width="1.3" stroke-linecap="butt"/>`;
728
+ case 11:
729
+ return `<line x1="41" y1="24.8" x2="40.2" y2="29.2" stroke="${skinColor}" stroke-width="1.3" stroke-linecap="butt"/>`;
530
730
  default:
531
731
  return "";
532
732
  }
@@ -556,6 +756,8 @@ function renderSolFaceSVG(walletAddress, options) {
556
756
  const glassesColor = theme?.glassesColor ?? "#4a4a5a";
557
757
  const earringColor = theme?.earringColor ?? blend(skin, "#d4a840", 0.4);
558
758
  const headbandColor = theme?.headbandColor ?? blend(hairCol, "#c04040", 0.5);
759
+ const beautyMarkColor = theme?.beautyMarkColor ?? "#3a2a2a";
760
+ const freckleColor = theme?.freckleColor ?? "#a0785a";
559
761
  const id = "sf" + djb22(walletAddress).toString(36);
560
762
  const cheekEnabled = theme?.cheekEnabled ?? true;
561
763
  const cheekColor = theme?.cheekColor ?? derived.cheekColor;
@@ -563,7 +765,8 @@ function renderSolFaceSVG(walletAddress, options) {
563
765
  const hi = traits.hairStyle % 10;
564
766
  const ai = effectiveAccessory(traits);
565
767
  const classAttr = className ? ` class="${className}"` : "";
566
- let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="${size}" height="${size}"${classAttr}>`;
768
+ const altText = solFaceAltText(walletAddress).replace(/"/g, "&quot;");
769
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="${size}" height="${size}"${classAttr} role="img" aria-label="${altText}">`;
567
770
  const glowIntensity = theme?.glowIntensity ?? 0.1;
568
771
  svg += buildDefs(id, skin, derived.skinHi, derived.skinLo, hairCol, bgCol, cheekColor, cheekOpacity, flat, full && cheekEnabled, glowIntensity);
569
772
  const blinkEnabled = !!enableBlink;
@@ -576,29 +779,29 @@ function renderSolFaceSVG(walletAddress, options) {
576
779
  }
577
780
  const bgFill = flat ? bgCol : `url(#${id}bg)`;
578
781
  svg += `<rect x="0" y="0" width="64" height="64" fill="${bgFill}" opacity="${bgOpacity}" rx="${bgRadius}"/>`;
579
- svg += renderHairBack(hi, id, flat);
580
- svg += renderEars(derived.earFill, derived.earShadow);
782
+ if (theme?.hairEnabled !== false) svg += renderHairBack(hi, id, flat);
783
+ if (theme?.earsEnabled !== false) svg += renderEars(theme?.earColor ?? derived.earFill, derived.earShadow);
581
784
  const skinOpacity = theme?.skinOpacity ?? 1;
582
785
  svg += renderFace(id, skin, flat, skinOpacity);
583
786
  const shadowEnabled = theme?.shadowEnabled ?? true;
584
787
  if (full && cheekEnabled && shadowEnabled) {
585
788
  svg += renderFaceOverlays(id, flat);
586
789
  }
587
- if (ai === 5) {
588
- svg += renderAccessory(5, accColor, glassesColor, earringColor, headbandColor);
790
+ if (theme?.hairEnabled !== false) svg += renderHairFront(hi, id, hairCol, skin, flat);
791
+ if (ai === 5 && theme?.accessoriesEnabled !== false) {
792
+ svg += renderAccessory(5, accColor, glassesColor, earringColor, headbandColor, beautyMarkColor, freckleColor, skin);
589
793
  }
590
- svg += renderHairFront(hi, id, hairCol, skin, flat);
591
794
  if (blinkEnabled) {
592
795
  const uid = `sf-${walletAddress.slice(0, 8)}`;
593
796
  svg += `<g class="${uid}-eyes">`;
594
797
  }
595
- svg += renderEyes(traits.eyeStyle % 8, eyeCol, eyeWhite, derived.lidColor, full);
798
+ svg += renderEyes(traits.eyeStyle % 9, eyeCol, eyeWhite, theme?.lidColor ?? derived.lidColor, full);
596
799
  if (blinkEnabled) svg += `</g>`;
597
- svg += renderEyebrows(traits.eyebrows % 5, browColor);
598
- svg += renderNose(traits.nose % 4, noseFill);
800
+ if (theme?.eyebrowsEnabled !== false) svg += renderEyebrows(traits.eyebrows % 8, browColor, full);
801
+ if (theme?.noseEnabled !== false) svg += renderNose(traits.nose % 8, noseFill);
599
802
  svg += renderMouth(traits.mouth % 8, lipColor, derived.isDark);
600
- if (ai !== 0 && ai !== 5) {
601
- svg += renderAccessory(ai, accColor, glassesColor, earringColor, headbandColor);
803
+ if (ai !== 0 && ai !== 5 && theme?.accessoriesEnabled !== false) {
804
+ svg += renderAccessory(ai, accColor, glassesColor, earringColor, headbandColor, beautyMarkColor, freckleColor, skin);
602
805
  }
603
806
  if (theme?.border) {
604
807
  svg += `<rect x="0" y="0" width="64" height="64" fill="none" stroke="${theme.border.color}" stroke-width="${theme.border.width}" rx="${bgRadius}"/>`;
@@ -607,226 +810,6 @@ function renderSolFaceSVG(walletAddress, options) {
607
810
  return svg;
608
811
  }
609
812
 
610
- // src/core/describe.ts
611
- var SKIN_TONES = {
612
- 0: "porcelain",
613
- 1: "ivory",
614
- 2: "fair",
615
- 3: "light",
616
- 4: "sand",
617
- 5: "golden",
618
- 6: "warm",
619
- 7: "caramel",
620
- 8: "brown",
621
- 9: "deep"
622
- };
623
- var EYE_STYLES = {
624
- 0: "round, wide-open",
625
- 1: "small and minimal",
626
- 2: "almond-shaped",
627
- 3: "wide and expressive",
628
- 4: "relaxed, half-lidded",
629
- 5: "joyful, crescent-shaped",
630
- 6: "bright and sparkling",
631
- 7: "gentle and narrow"
632
- };
633
- var EYE_COLORS_DESC = {
634
- 0: "dark brown",
635
- 1: "blue",
636
- 2: "green",
637
- 3: "hazel",
638
- 4: "gray"
639
- };
640
- var EYEBROW_STYLES = {
641
- 0: "wispy",
642
- 1: "straight",
643
- 2: "natural",
644
- 3: "elegantly arched",
645
- 4: "sharply angled"
646
- };
647
- var NOSE_STYLES = {
648
- 0: "a subtle shadow nose",
649
- 1: "a small button nose",
650
- 2: "a soft curved nose",
651
- 3: "a button nose with visible nostrils"
652
- };
653
- var MOUTH_STYLES = {
654
- 0: "a gentle smile",
655
- 1: "a calm, neutral expression",
656
- 2: "a happy grin",
657
- 3: "a surprised O-shaped mouth",
658
- 4: "a confident smirk",
659
- 5: "a wide, toothy grin",
660
- 6: "a flat, straight expression",
661
- 7: "a soft pout"
662
- };
663
- var HAIR_STYLES = {
664
- 0: "bald, with no hair",
665
- 1: "short, neatly cropped hair",
666
- 2: "bouncy, curly hair",
667
- 3: "side-swept hair",
668
- 4: "a voluminous puff",
669
- 5: "long hair that falls past the shoulders",
670
- 6: "a clean bob cut",
671
- 7: "a close buzz cut",
672
- 8: "flowing, wavy hair",
673
- 9: "a neat topknot"
674
- };
675
- var HAIR_COLORS_DESC = {
676
- 0: "jet black",
677
- 1: "espresso brown",
678
- 2: "walnut",
679
- 3: "honey blonde",
680
- 4: "copper red",
681
- 5: "silver",
682
- 6: "charcoal",
683
- 7: "burgundy",
684
- 8: "strawberry",
685
- 9: "ginger"
686
- };
687
- var ACCESSORY_DESC = {
688
- 0: "",
689
- 1: "a beauty mark",
690
- 2: "round glasses",
691
- 3: "rectangular glasses",
692
- 4: "a dangling earring",
693
- 5: "a headband",
694
- 6: "freckles",
695
- 7: "stud earrings",
696
- 8: "aviator sunglasses",
697
- 9: "a band-aid"
698
- };
699
- var BG_COLORS_DESC = {
700
- 0: "rose",
701
- 1: "olive",
702
- 2: "sage",
703
- 3: "fern",
704
- 4: "mint",
705
- 5: "ocean",
706
- 6: "sky",
707
- 7: "lavender",
708
- 8: "orchid",
709
- 9: "blush"
710
- };
711
- function describeAppearance(walletAddress, options) {
712
- const traits = generateTraits(walletAddress);
713
- const {
714
- includeBackground = true,
715
- format = "paragraph",
716
- perspective = "third",
717
- name
718
- } = options ?? {};
719
- if (format === "structured") return buildStructured(traits, includeBackground);
720
- if (format === "compact") return buildCompact(traits);
721
- return buildParagraph(traits, perspective, name, includeBackground);
722
- }
723
- function buildParagraph(t, perspective, name, includeBg) {
724
- const parts = [];
725
- const ai = effectiveAccessory(t);
726
- const subject = perspective === "first" ? name ? `I'm ${name}. I have` : "I have" : name ? `${name} has` : "This SolFace has";
727
- const im = perspective === "first" ? "I'm" : "They're";
728
- parts.push(`${subject} a squircle face with ${SKIN_TONES[t.skinColor] ?? "warm"} skin`);
729
- const eyeStyle = EYE_STYLES[t.eyeStyle] ?? "round";
730
- const eyeColor = EYE_COLORS_DESC[t.eyeColor] ?? "dark";
731
- parts.push(`${eyeStyle} ${eyeColor} eyes`);
732
- const brows = EYEBROW_STYLES[t.eyebrows];
733
- if (brows) parts.push(`${brows} eyebrows`);
734
- const hairStyle = HAIR_STYLES[t.hairStyle] ?? "";
735
- const hairColor = HAIR_COLORS_DESC[t.hairColor] ?? "";
736
- if (t.hairStyle === 0) {
737
- parts.push(perspective === "first" ? "and am bald" : "and is bald");
738
- } else if (hairStyle.startsWith("a ")) {
739
- parts.push(`and a ${hairColor} ${hairStyle.slice(2)}`);
740
- } else {
741
- parts.push(`and ${hairColor} ${hairStyle}`);
742
- }
743
- let desc = parts[0];
744
- if (parts.length > 2) {
745
- desc += ", " + parts.slice(1, -1).join(", ") + ", " + parts[parts.length - 1];
746
- } else if (parts.length === 2) {
747
- desc += " and " + parts[1];
748
- }
749
- desc += ".";
750
- const nose = NOSE_STYLES[t.nose];
751
- if (nose) {
752
- const noseSubject = perspective === "first" ? "I have" : (name ?? "They") + (name ? " has" : " have");
753
- desc += ` ${noseSubject} ${nose}.`;
754
- }
755
- const acc = ACCESSORY_DESC[ai];
756
- if (acc) {
757
- desc += ` ${im} wearing ${acc}.`;
758
- }
759
- const mouth = MOUTH_STYLES[t.mouth] ?? "a smile";
760
- const mouthVerb = perspective === "first" ? "I have" : (name ?? "They") + (name ? " has" : " have");
761
- desc += ` ${mouthVerb} ${mouth}.`;
762
- if (includeBg) {
763
- const bg = BG_COLORS_DESC[t.bgColor] ?? "colorful";
764
- desc += ` The background is ${bg}.`;
765
- }
766
- return desc;
767
- }
768
- function buildStructured(t, includeBg) {
769
- const ai = effectiveAccessory(t);
770
- const lines = [
771
- `Face: squircle`,
772
- `Skin: ${SKIN_TONES[t.skinColor] ?? "warm"}`,
773
- `Eyes: ${EYE_STYLES[t.eyeStyle] ?? "round"}, ${EYE_COLORS_DESC[t.eyeColor] ?? "dark"}`,
774
- `Eyebrows: ${EYEBROW_STYLES[t.eyebrows] ?? "wispy"}`
775
- ];
776
- const nose = NOSE_STYLES[t.nose];
777
- if (nose) lines.push(`Nose: ${nose.replace(/^a /, "")}`);
778
- lines.push(`Mouth: ${MOUTH_STYLES[t.mouth] ?? "smile"}`);
779
- if (t.hairStyle === 0) {
780
- lines.push("Hair: bald");
781
- } else {
782
- const hs = HAIR_STYLES[t.hairStyle] ?? "";
783
- const hc = HAIR_COLORS_DESC[t.hairColor] ?? "";
784
- lines.push(`Hair: ${hc} ${hs.startsWith("a ") ? hs.slice(2) : hs}`);
785
- }
786
- const acc = ACCESSORY_DESC[ai];
787
- if (acc) lines.push(`Accessory: ${acc}`);
788
- if (includeBg) {
789
- lines.push(`Background: ${BG_COLORS_DESC[t.bgColor] ?? "colorful"}`);
790
- }
791
- return lines.join("\n");
792
- }
793
- function buildCompact(t) {
794
- const ai = effectiveAccessory(t);
795
- const parts = [];
796
- parts.push(`squircle face`);
797
- parts.push(`${SKIN_TONES[t.skinColor] ?? "warm"} skin`);
798
- parts.push(`${EYE_COLORS_DESC[t.eyeColor] ?? "dark"} ${EYE_STYLES[t.eyeStyle] ?? "round"} eyes`);
799
- if (t.hairStyle === 0) {
800
- parts.push("bald");
801
- } else {
802
- const raw = HAIR_STYLES[t.hairStyle] ?? "hair";
803
- const hs = raw.includes(",") ? raw.replace(/.*,\s*/, "") : raw;
804
- const hc = HAIR_COLORS_DESC[t.hairColor] ?? "";
805
- parts.push(`${hc} ${hs.startsWith("a ") ? hs.slice(2) : hs}`);
806
- }
807
- const acc = ACCESSORY_DESC[ai];
808
- if (acc) parts.push(acc);
809
- parts.push((MOUTH_STYLES[t.mouth] ?? "smiling").replace(/^a /, ""));
810
- return parts.join(", ");
811
- }
812
- function agentAppearancePrompt(walletAddress, agentName) {
813
- const desc = describeAppearance(walletAddress, {
814
- perspective: "first",
815
- name: agentName,
816
- includeBackground: false,
817
- format: "paragraph"
818
- });
819
- const hash = (djb2Simple(walletAddress) >>> 0).toString(16).padStart(8, "0");
820
- return `My visual identity is a SolFace avatar (ID: ${hash}) derived from my wallet address. ${desc} This appearance is deterministic \u2014 anyone who looks up my wallet will see the same face.`;
821
- }
822
- function djb2Simple(str) {
823
- let hash = 5381;
824
- for (let i = 0; i < str.length; i++) {
825
- hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
826
- }
827
- return hash >>> 0;
828
- }
829
-
830
813
  // src/names/sha256.ts
831
814
  var K = [
832
815
  1116352408,
@@ -3044,7 +3027,9 @@ var darkTheme = {
3044
3027
  "#18122a",
3045
3028
  "#1a2020",
3046
3029
  "#221822",
3047
- "#1e1020"
3030
+ "#1e1020",
3031
+ "#1c1228",
3032
+ "#0e201a"
3048
3033
  ],
3049
3034
  eyeWhiteColor: "#d8d0c8",
3050
3035
  bgOpacity: 1,
@@ -3062,7 +3047,9 @@ var lightTheme = {
3062
3047
  "#e0e4f0",
3063
3048
  "#e0d8f0",
3064
3049
  "#f0e0ee",
3065
- "#f0d8e4"
3050
+ "#f0d8e4",
3051
+ "#e8d8f0",
3052
+ "#d8f0e8"
3066
3053
  ],
3067
3054
  bgOpacity: 1,
3068
3055
  bgRadius: 8
@@ -3103,7 +3090,9 @@ var monoTheme = {
3103
3090
  "#d4d4e0",
3104
3091
  "#d4d0e0",
3105
3092
  "#e0d8e0",
3106
- "#e0d0d8"
3093
+ "#e0d0d8",
3094
+ "#d8d0e0",
3095
+ "#d0e0d8"
3107
3096
  ],
3108
3097
  mouthColor: "#666",
3109
3098
  eyebrowColor: "#555",
@@ -3135,7 +3124,9 @@ var glassTheme = {
3135
3124
  _specularEnd: 50,
3136
3125
  _lightAngle: 135,
3137
3126
  _rimIntensity: 0.08,
3138
- _shadow: "0 8px 32px rgba(0,0,0,0.12)"
3127
+ _shadowY: 8,
3128
+ _shadowBlur: 32,
3129
+ _shadowOpacity: 0.12
3139
3130
  };
3140
3131
  var glassDarkTheme = {
3141
3132
  bgOpacity: 0.2,
@@ -3153,7 +3144,9 @@ var glassDarkTheme = {
3153
3144
  _specularEnd: 40,
3154
3145
  _lightAngle: 135,
3155
3146
  _rimIntensity: 0.05,
3156
- _shadow: "0 8px 32px rgba(0,0,0,0.25)"
3147
+ _shadowY: 8,
3148
+ _shadowBlur: 32,
3149
+ _shadowOpacity: 0.25
3157
3150
  };
3158
3151
  var pixelTheme = {
3159
3152
  flat: true,
@@ -3208,7 +3201,7 @@ function getPresetTheme(name, overrides) {
3208
3201
  // src/agent/tools.ts
3209
3202
  var generateSolfaceSvg = {
3210
3203
  name: "generate_solface_svg",
3211
- description: "Generate a deterministic SVG avatar for a Solana wallet address. Returns an SVG string with gradient-rich rendering, skin-luminance-driven colors, and 10 accessory types. The same wallet always produces the same face. ~2.56 billion unique combinations.",
3204
+ description: "Generate a deterministic SVG avatar for a Solana wallet address. Returns an SVG string with gradient-rich rendering, skin-luminance-driven colors, and 12 accessory types. The same wallet always produces the same face. ~53 million unique combinations.",
3212
3205
  parameters: {
3213
3206
  type: "object",
3214
3207
  properties: {
@@ -3249,7 +3242,7 @@ var generateSolfaceSvg = {
3249
3242
  };
3250
3243
  var describeSolface = {
3251
3244
  name: "describe_solface",
3252
- description: "Generate a natural language description of a wallet's SolFace avatar. Useful for alt text, profile bios, system prompts, and accessibility. Describes squircle face, skin tone, eye style/color, hair, accessories, and expression.",
3245
+ description: "Generate a natural language description of a wallet's SolFace avatar. Useful for alt text, profile bios, system prompts, and accessibility. Describes squircle face, skin tone, eye style/color, eyebrows, nose, accessories, and expression.",
3253
3246
  parameters: {
3254
3247
  type: "object",
3255
3248
  properties: {