solfaces 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -19
- package/SKILL.md +2 -3
- package/dist/agent/index.cjs +13 -14
- package/dist/agent/index.js +3 -4
- package/dist/agent/mcp-server.cjs +326 -333
- package/dist/{chunk-5DT27HMT.js → chunk-2KW35VRI.js} +3 -3
- package/dist/{chunk-5DT27HMT.js.map → chunk-2KW35VRI.js.map} +1 -1
- package/dist/{chunk-PVJR3SFG.cjs → chunk-BI3GHRKQ.cjs} +14 -15
- package/dist/chunk-BI3GHRKQ.cjs.map +1 -0
- package/dist/{chunk-DRUSCLEF.js → chunk-MYUSB4LA.js} +28 -8
- package/dist/chunk-MYUSB4LA.js.map +1 -0
- package/dist/{chunk-3CE7Q44S.js → chunk-N5GDLCCL.js} +324 -110
- package/dist/chunk-N5GDLCCL.js.map +1 -0
- package/dist/{chunk-74CSG63X.js → chunk-O2IIBSQH.js} +6 -7
- package/dist/chunk-O2IIBSQH.js.map +1 -0
- package/dist/{chunk-6QRDULAO.cjs → chunk-PCSRDAWQ.cjs} +28 -7
- package/dist/chunk-PCSRDAWQ.cjs.map +1 -0
- package/dist/{chunk-WIXGHS77.cjs → chunk-T7HEUW2O.cjs} +6 -6
- package/dist/{chunk-WIXGHS77.cjs.map → chunk-T7HEUW2O.cjs.map} +1 -1
- package/dist/{chunk-F54WHRCE.cjs → chunk-W2U6ITMR.cjs} +328 -110
- package/dist/chunk-W2U6ITMR.cjs.map +1 -0
- package/dist/core/index.cjs +43 -44
- package/dist/core/index.d.cts +39 -5
- package/dist/core/index.d.ts +39 -5
- package/dist/core/index.js +2 -3
- package/dist/index.cjs +53 -50
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -5
- package/dist/react/index.cjs +107 -120
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +23 -1
- package/dist/react/index.d.ts +23 -1
- package/dist/react/index.js +95 -108
- package/dist/react/index.js.map +1 -1
- package/dist/solfaces.cdn.global.js +2 -2
- package/dist/solfaces.cdn.global.js.map +1 -1
- package/dist/themes/index.cjs +18 -14
- package/dist/themes/index.d.cts +30 -4
- package/dist/themes/index.d.ts +30 -4
- package/dist/themes/index.js +1 -1
- package/dist/traits-D4tbtZIr.d.cts +259 -0
- package/dist/traits-D4tbtZIr.d.ts +259 -0
- package/dist/vanilla/index.cjs +6 -7
- package/dist/vanilla/index.cjs.map +1 -1
- package/dist/vanilla/index.d.cts +1 -1
- package/dist/vanilla/index.d.ts +1 -1
- package/dist/vanilla/index.js +2 -3
- package/dist/vanilla/index.js.map +1 -1
- package/package.json +4 -1
- package/python/solfaces.py +103 -119
- package/reference/integrations.md +1 -1
- package/dist/chunk-3CE7Q44S.js.map +0 -1
- package/dist/chunk-6QRDULAO.cjs.map +0 -1
- package/dist/chunk-74CSG63X.js.map +0 -1
- package/dist/chunk-CKHLRORB.js +0 -239
- package/dist/chunk-CKHLRORB.js.map +0 -1
- package/dist/chunk-DRUSCLEF.js.map +0 -1
- package/dist/chunk-F54WHRCE.cjs.map +0 -1
- package/dist/chunk-PVJR3SFG.cjs.map +0 -1
- package/dist/chunk-TYTBYDQU.cjs +0 -244
- package/dist/chunk-TYTBYDQU.cjs.map +0 -1
- package/dist/traits-sfe7rM9C.d.cts +0 -106
- 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() *
|
|
71
|
-
eyeColor: Math.floor(rand() *
|
|
72
|
-
eyebrows: Math.floor(rand() *
|
|
73
|
-
nose: Math.floor(rand() *
|
|
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() *
|
|
78
|
-
bgColor: Math.floor(rand() *
|
|
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 %
|
|
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
|
-
"
|
|
111
|
+
"Crew",
|
|
109
112
|
"Curly",
|
|
110
|
-
"Side
|
|
111
|
-
"Puff",
|
|
113
|
+
"Side Part",
|
|
112
114
|
"Long",
|
|
113
|
-
"Bob",
|
|
114
115
|
"Buzz",
|
|
115
|
-
"
|
|
116
|
-
"
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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(
|
|
319
|
-
|
|
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(
|
|
344
|
-
|
|
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="${
|
|
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="
|
|
415
|
-
s += `<path d="M${rx - 4} ${y} Q${rx} ${y + 4} ${rx + 4} ${y}" fill="none" stroke="${eyeCol}" stroke-width="
|
|
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 `<
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
|
|
768
|
+
const altText = solFaceAltText(walletAddress).replace(/"/g, """);
|
|
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 (
|
|
588
|
-
|
|
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 %
|
|
798
|
+
svg += renderEyes(traits.eyeStyle % 9, eyeCol, eyeWhite, theme?.lidColor ?? derived.lidColor, full);
|
|
596
799
|
if (blinkEnabled) svg += `</g>`;
|
|
597
|
-
svg += renderEyebrows(traits.eyebrows %
|
|
598
|
-
svg += renderNose(traits.nose %
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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: {
|