solfaces 2.1.2 → 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 +61 -35
- package/SKILL.md +4 -5
- package/dist/agent/index.cjs +14 -15
- package/dist/agent/index.js +4 -5
- package/dist/agent/mcp-server.cjs +333 -339
- 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-4YFZ4W2Y.cjs → chunk-BI3GHRKQ.cjs} +22 -23
- 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-66PSL4YO.js → chunk-O2IIBSQH.js} +9 -10
- 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-N5DDJ3WQ.js → chunk-UTT764K6.js} +10 -9
- package/dist/{chunk-N5DDJ3WQ.js.map → chunk-UTT764K6.js.map} +1 -1
- package/dist/{chunk-F54WHRCE.cjs → chunk-W2U6ITMR.cjs} +328 -110
- package/dist/chunk-W2U6ITMR.cjs.map +1 -0
- package/dist/{chunk-XXJJH56O.cjs → chunk-YGD7EQEX.cjs} +10 -9
- package/dist/{chunk-XXJJH56O.cjs.map → chunk-YGD7EQEX.cjs.map} +1 -1
- 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 +61 -58
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -6
- package/dist/names/index.cjs +9 -9
- package/dist/names/index.d.cts +4 -2
- package/dist/names/index.d.ts +4 -2
- package/dist/names/index.js +1 -1
- package/dist/react/index.cjs +112 -125
- 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 +96 -109
- 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 +108 -122
- package/reference/integrations.md +1 -1
- package/dist/chunk-3CE7Q44S.js.map +0 -1
- package/dist/chunk-4YFZ4W2Y.cjs.map +0 -1
- package/dist/chunk-66PSL4YO.js.map +0 -1
- package/dist/chunk-6QRDULAO.cjs.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-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
package/python/solfaces.py
CHANGED
|
@@ -26,15 +26,15 @@ import struct
|
|
|
26
26
|
class SolFaceTraits:
|
|
27
27
|
face_shape: int # 0-3 (consumed for PRNG ordering, all render as squircle)
|
|
28
28
|
skin_color: int # 0-9
|
|
29
|
-
eye_style: int # 0-
|
|
30
|
-
eye_color: int # 0-
|
|
31
|
-
eyebrows: int # 0-
|
|
32
|
-
nose: int # 0-
|
|
29
|
+
eye_style: int # 0-8
|
|
30
|
+
eye_color: int # 0-7
|
|
31
|
+
eyebrows: int # 0-7
|
|
32
|
+
nose: int # 0-7
|
|
33
33
|
mouth: int # 0-7
|
|
34
34
|
hair_style: int # 0-9
|
|
35
35
|
hair_color: int # 0-9
|
|
36
|
-
accessory: int # 0-
|
|
37
|
-
bg_color: int # 0-
|
|
36
|
+
accessory: int # 0-11
|
|
37
|
+
bg_color: int # 0-11
|
|
38
38
|
|
|
39
39
|
def to_dict(self) -> Dict[str, int]:
|
|
40
40
|
return {
|
|
@@ -58,7 +58,10 @@ SKIN_COLORS = [
|
|
|
58
58
|
"#faeae5", "#efd6c8", "#e4c5aa", "#d5b590", "#c59e77",
|
|
59
59
|
"#b4875f", "#9d6d4d", "#805742", "#654134", "#4b2d25",
|
|
60
60
|
]
|
|
61
|
-
EYE_COLORS = [
|
|
61
|
+
EYE_COLORS = [
|
|
62
|
+
"#382414", "#3868A8", "#38784C", "#808838", "#586878",
|
|
63
|
+
"#A06830", "#685898", "#889898",
|
|
64
|
+
]
|
|
62
65
|
HAIR_COLORS = [
|
|
63
66
|
"#1A1A24", "#4C3428", "#887058", "#D4B868", "#A84830",
|
|
64
67
|
"#C0C0CC", "#484858", "#783850", "#D8B0A0", "#C08048",
|
|
@@ -66,6 +69,7 @@ HAIR_COLORS = [
|
|
|
66
69
|
BG_COLORS = [
|
|
67
70
|
"#b98387", "#a9a360", "#9eb785", "#69ab79", "#81bbb0",
|
|
68
71
|
"#6499af", "#7f8bbd", "#8869ab", "#b785b3", "#ab6984",
|
|
72
|
+
"#a07ab5", "#74b5a0",
|
|
69
73
|
]
|
|
70
74
|
|
|
71
75
|
|
|
@@ -105,6 +109,24 @@ def luminance(h: str) -> float:
|
|
|
105
109
|
return (r + g + b) / 3
|
|
106
110
|
|
|
107
111
|
|
|
112
|
+
def _relative_luminance(h: str) -> float:
|
|
113
|
+
"""WCAG 2.0 relative luminance."""
|
|
114
|
+
r, g, b = hex_to_rgb(h)
|
|
115
|
+
def _lin(c: float) -> float:
|
|
116
|
+
s = c / 255
|
|
117
|
+
return s / 12.92 if s <= 0.03928 else ((s + 0.055) / 1.055) ** 2.4
|
|
118
|
+
return 0.2126 * _lin(r) + 0.7152 * _lin(g) + 0.0722 * _lin(b)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def contrast_ratio(hex1: str, hex2: str) -> float:
|
|
122
|
+
"""WCAG 2.0 contrast ratio between two hex colors (1-21)."""
|
|
123
|
+
l1 = _relative_luminance(hex1)
|
|
124
|
+
l2 = _relative_luminance(hex2)
|
|
125
|
+
lighter = max(l1, l2)
|
|
126
|
+
darker = min(l1, l2)
|
|
127
|
+
return (lighter + 0.05) / (darker + 0.05)
|
|
128
|
+
|
|
129
|
+
|
|
108
130
|
def derive_skin_colors(skin: str) -> dict:
|
|
109
131
|
sL = luminance(skin)
|
|
110
132
|
is_dark = sL < 100
|
|
@@ -136,6 +158,11 @@ def derive_skin_colors(skin: str) -> dict:
|
|
|
136
158
|
lr, lg, lb = hex_to_rgb(lip_raw)
|
|
137
159
|
lip_d = abs(sr - lr) + abs(sg - lg) + abs(sb - lb)
|
|
138
160
|
lip_color = blend(skin, lip_base, 0.78) if lip_d < 60 else lip_raw
|
|
161
|
+
# Ensure minimum lip/skin contrast for readability
|
|
162
|
+
attempts = 0
|
|
163
|
+
while contrast_ratio(lip_color, skin) < 1.8 and attempts < 12:
|
|
164
|
+
lip_color = darken(lip_color, 0.06)
|
|
165
|
+
attempts += 1
|
|
139
166
|
|
|
140
167
|
brow_color = lighten(skin, 0.35 if sL < 80 else 0.25) if is_dark else darken(skin, 0.55)
|
|
141
168
|
nose_fill = lighten(skin, 0.20) if is_dark else darken(skin, 0.20)
|
|
@@ -160,12 +187,6 @@ def derive_skin_colors(skin: str) -> dict:
|
|
|
160
187
|
}
|
|
161
188
|
|
|
162
189
|
|
|
163
|
-
def buzz_opacity(hair_col: str, skin: str) -> float:
|
|
164
|
-
hr, hg, hb = hex_to_rgb(hair_col)
|
|
165
|
-
sr, sg, sb = hex_to_rgb(skin)
|
|
166
|
-
return 0.70 if abs(hr - sr) + abs(hg - sg) + abs(hb - sb) < 80 else 0.50
|
|
167
|
-
|
|
168
|
-
|
|
169
190
|
# ─── Hashing (djb2) — exact JS parity ────────────────────────
|
|
170
191
|
|
|
171
192
|
def _djb2(s: str) -> int:
|
|
@@ -215,23 +236,20 @@ def generate_traits(wallet_address: str) -> SolFaceTraits:
|
|
|
215
236
|
return SolFaceTraits(
|
|
216
237
|
face_shape=int(rand() * 4),
|
|
217
238
|
skin_color=int(rand() * 10),
|
|
218
|
-
eye_style=int(rand() *
|
|
219
|
-
eye_color=int(rand() *
|
|
220
|
-
eyebrows=int(rand() *
|
|
221
|
-
nose=int(rand() *
|
|
239
|
+
eye_style=int(rand() * 9),
|
|
240
|
+
eye_color=int(rand() * 8),
|
|
241
|
+
eyebrows=int(rand() * 8),
|
|
242
|
+
nose=int(rand() * 8),
|
|
222
243
|
mouth=int(rand() * 8),
|
|
223
244
|
hair_style=int(rand() * 10),
|
|
224
245
|
hair_color=int(rand() * 10),
|
|
225
|
-
accessory=int(rand() *
|
|
226
|
-
bg_color=int(rand() *
|
|
246
|
+
accessory=int(rand() * 12),
|
|
247
|
+
bg_color=int(rand() * 12),
|
|
227
248
|
)
|
|
228
249
|
|
|
229
250
|
|
|
230
251
|
def effective_accessory(t: SolFaceTraits) -> int:
|
|
231
|
-
ai = t.accessory %
|
|
232
|
-
hi = t.hair_style % 10
|
|
233
|
-
if (ai == 4 or ai == 7) and (hi == 5 or hi == 6):
|
|
234
|
-
return 0
|
|
252
|
+
ai = t.accessory % 12
|
|
235
253
|
return ai
|
|
236
254
|
|
|
237
255
|
|
|
@@ -243,19 +261,21 @@ def trait_hash(wallet_address: str) -> str:
|
|
|
243
261
|
|
|
244
262
|
SKIN_LABELS = ["Porcelain", "Ivory", "Fair", "Light", "Sand",
|
|
245
263
|
"Golden", "Warm", "Caramel", "Brown", "Deep"]
|
|
246
|
-
EYE_STYLE_LABELS = ["Round", "Minimal", "Almond", "Wide", "Relaxed", "Joyful", "Bright", "Gentle"]
|
|
247
|
-
EYE_COLOR_LABELS = ["Chocolate", "Sky", "Emerald", "Hazel", "Storm"]
|
|
248
|
-
BROW_LABELS = ["Wispy", "Straight", "Natural", "Arched", "Angled"]
|
|
249
|
-
NOSE_LABELS = ["Shadow", "Button", "Soft", "Nostrils"]
|
|
264
|
+
EYE_STYLE_LABELS = ["Round", "Minimal", "Almond", "Wide", "Relaxed", "Joyful", "Bright", "Gentle", "Side-look"]
|
|
265
|
+
EYE_COLOR_LABELS = ["Chocolate", "Sky", "Emerald", "Hazel", "Storm", "Amber", "Violet", "Gray"]
|
|
266
|
+
BROW_LABELS = ["Wispy", "Straight", "Natural", "Arched", "Angled", "Worried", "Bushy", "Thin"]
|
|
267
|
+
NOSE_LABELS = ["Shadow", "Button", "Soft", "Nostrils", "Pointed", "Wide", "Bridge", "Snub"]
|
|
250
268
|
MOUTH_LABELS = ["Smile", "Calm", "Happy", "Oh", "Smirk", "Grin", "Flat", "Pout"]
|
|
251
|
-
HAIR_STYLE_LABELS = ["Bald", "
|
|
252
|
-
"
|
|
269
|
+
HAIR_STYLE_LABELS = ["Bald", "Crew", "Curly", "Side Part", "Long",
|
|
270
|
+
"Buzz", "Beanie", "Cap", "Mohawk", "Bun"]
|
|
253
271
|
HAIR_COLOR_LABELS = ["Black", "Espresso", "Walnut", "Honey", "Copper",
|
|
254
272
|
"Silver", "Charcoal", "Burgundy", "Strawberry", "Ginger"]
|
|
255
273
|
ACC_LABELS = ["None", "Beauty Mark", "Round Glasses", "Rect Glasses", "Earring",
|
|
256
|
-
"Headband", "Freckles", "Stud Earrings", "Aviators", "Band-Aid"
|
|
274
|
+
"Headband", "Freckles", "Stud Earrings", "Aviators", "Band-Aid",
|
|
275
|
+
"Left Eyebrow Slit", "Right Eyebrow Slit"]
|
|
257
276
|
BG_COLOR_LABELS = ["Rose", "Olive", "Sage", "Fern", "Mint",
|
|
258
|
-
"Ocean", "Sky", "Lavender", "Orchid", "Blush"
|
|
277
|
+
"Ocean", "Sky", "Lavender", "Orchid", "Blush",
|
|
278
|
+
"Lilac", "Seafoam"]
|
|
259
279
|
|
|
260
280
|
|
|
261
281
|
def get_trait_labels(t: SolFaceTraits) -> Dict[str, str]:
|
|
@@ -296,12 +316,9 @@ def _build_defs(gid: str, skin: str, skin_hi: str, skin_lo: str,
|
|
|
296
316
|
d = "<defs>"
|
|
297
317
|
d += f'<linearGradient id="{gid}sg" x1="0" y1="0" x2="0" y2="1">'
|
|
298
318
|
d += f'<stop offset="0%" stop-color="{skin_hi}"/>'
|
|
319
|
+
d += f'<stop offset="50%" stop-color="{skin}"/>'
|
|
299
320
|
d += f'<stop offset="100%" stop-color="{skin_lo}"/>'
|
|
300
321
|
d += "</linearGradient>"
|
|
301
|
-
d += f'<linearGradient id="{gid}hg" x1="0" y1="0" x2="0" y2="1">'
|
|
302
|
-
d += f'<stop offset="0%" stop-color="{lighten(hair_col, 0.15)}"/>'
|
|
303
|
-
d += f'<stop offset="100%" stop-color="{darken(hair_col, 0.15)}"/>'
|
|
304
|
-
d += "</linearGradient>"
|
|
305
322
|
d += f'<linearGradient id="{gid}bg" x1="0" y1="0" x2="1" y2="1">'
|
|
306
323
|
d += f'<stop offset="0%" stop-color="{lighten(bg_col, 0.12)}"/>'
|
|
307
324
|
d += f'<stop offset="100%" stop-color="{darken(bg_col, 0.12)}"/>'
|
|
@@ -328,10 +345,6 @@ def _build_defs(gid: str, skin: str, skin_hi: str, skin_lo: str,
|
|
|
328
345
|
|
|
329
346
|
|
|
330
347
|
def _render_hair_back(hi: int, gid: str, flat: bool) -> str:
|
|
331
|
-
fill = "currentColor" if flat else f"url(#{gid}hg)"
|
|
332
|
-
if hi == 5: return f'<rect x="10" y="14" width="44" height="42" rx="6" fill="{fill}"/>'
|
|
333
|
-
if hi == 6: return f'<rect x="12" y="14" width="40" height="32" rx="8" fill="{fill}"/>'
|
|
334
|
-
if hi == 8: return f'<rect x="11" y="14" width="42" height="38" rx="8" fill="{fill}"/>'
|
|
335
348
|
return ""
|
|
336
349
|
|
|
337
350
|
|
|
@@ -360,33 +373,6 @@ def _render_face_overlays(gid: str) -> str:
|
|
|
360
373
|
|
|
361
374
|
|
|
362
375
|
def _render_hair_front(hi: int, gid: str, hair_col: str, skin: str, flat: bool) -> str:
|
|
363
|
-
fill = hair_col if flat else f"url(#{gid}hg)"
|
|
364
|
-
if hi == 0: return ""
|
|
365
|
-
if hi == 1: return f'<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}"/>'
|
|
366
|
-
if hi == 2:
|
|
367
|
-
return (f'<g fill="{fill}">'
|
|
368
|
-
'<circle cx="20" cy="14" r="5"/><circle cx="28" cy="11" r="5.5"/>'
|
|
369
|
-
'<circle cx="36" cy="11" r="5.5"/><circle cx="44" cy="14" r="5"/>'
|
|
370
|
-
'<circle cx="16" cy="20" r="4"/><circle cx="48" cy="20" r="4"/></g>')
|
|
371
|
-
if hi == 3:
|
|
372
|
-
return (f'<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}"/>'
|
|
373
|
-
f'<path d="M14 20 Q8 16 10 8 Q14 10 20 16 Z" fill="{fill}"/>')
|
|
374
|
-
if hi == 4: return f'<ellipse cx="32" cy="10" rx="14" ry="8" fill="{fill}"/>'
|
|
375
|
-
if hi == 5: return f'<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}"/>'
|
|
376
|
-
if hi == 6:
|
|
377
|
-
return (f'<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}"/>'
|
|
378
|
-
f'<rect x="10" y="28" width="8" height="14" rx="4" fill="{fill}"/>'
|
|
379
|
-
f'<rect x="46" y="28" width="8" height="14" rx="4" fill="{fill}"/>')
|
|
380
|
-
if hi == 7:
|
|
381
|
-
bop = buzz_opacity(hair_col, skin)
|
|
382
|
-
return f'<rect x="15" y="13" width="34" height="16" rx="10" ry="8" fill="{hair_col}" opacity="{bop:.2f}"/>'
|
|
383
|
-
if hi == 8:
|
|
384
|
-
return (f'<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}"/>'
|
|
385
|
-
f'<path d="M12 30 Q10 20 14 16" fill="none" stroke="{fill}" stroke-width="4" stroke-linecap="round"/>'
|
|
386
|
-
f'<path d="M52 30 Q54 20 50 16" fill="none" stroke="{fill}" stroke-width="4" stroke-linecap="round"/>')
|
|
387
|
-
if hi == 9:
|
|
388
|
-
return (f'<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}"/>'
|
|
389
|
-
f'<ellipse cx="32" cy="6" rx="6" ry="5" fill="{fill}"/>')
|
|
390
376
|
return ""
|
|
391
377
|
|
|
392
378
|
|
|
@@ -421,17 +407,16 @@ def _render_eyes(ei: int, eye_col: str, eye_white: str, lid_color: str, full: bo
|
|
|
421
407
|
elif ei == 6:
|
|
422
408
|
s += f'<circle cx="{lx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{lx+0.5}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
423
409
|
s += f'<circle cx="{lx+1.5}" cy="{y-1}" r="1" fill="white" opacity="0.9"/>'
|
|
424
|
-
if full:
|
|
425
|
-
s += f'<line x1="{lx+2.5}" y1="{y-3.5}" x2="{lx+4}" y2="{y-5}" stroke="{eye_col}" stroke-width="0.8" stroke-linecap="round"/>'
|
|
426
|
-
s += f'<line x1="{lx+3.5}" y1="{y-2.5}" x2="{lx+5}" y2="{y-3.5}" stroke="{eye_col}" stroke-width="0.8" stroke-linecap="round"/>'
|
|
427
410
|
s += f'<circle cx="{rx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{rx+0.5}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
428
411
|
s += f'<circle cx="{rx+1.5}" cy="{y-1}" r="1" fill="white" opacity="0.9"/>'
|
|
429
|
-
if full:
|
|
430
|
-
s += f'<line x1="{rx+2.5}" y1="{y-3.5}" x2="{rx+4}" y2="{y-5}" stroke="{eye_col}" stroke-width="0.8" stroke-linecap="round"/>'
|
|
431
|
-
s += f'<line x1="{rx+3.5}" y1="{y-2.5}" x2="{rx+5}" y2="{y-3.5}" stroke="{eye_col}" stroke-width="0.8" stroke-linecap="round"/>'
|
|
432
412
|
elif ei == 7:
|
|
433
413
|
s += f'<ellipse cx="{lx}" cy="{y}" rx="4.5" ry="1.5" fill="{eye_white}"/><ellipse cx="{lx+0.5}" cy="{y}" rx="2.2" ry="1.2" fill="{eye_col}"/>'
|
|
434
414
|
s += f'<ellipse cx="{rx}" cy="{y}" rx="4.5" ry="1.5" fill="{eye_white}"/><ellipse cx="{rx+0.5}" cy="{y}" rx="2.2" ry="1.2" fill="{eye_col}"/>'
|
|
415
|
+
elif ei == 8: # Side-look — pupils shifted left
|
|
416
|
+
s += f'<circle cx="{lx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{lx-1}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
417
|
+
if full: s += f'<circle cx="{lx-0.3}" cy="{y-0.8}" r="0.7" fill="white" opacity="0.8"/>'
|
|
418
|
+
s += f'<circle cx="{rx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{rx-1}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
419
|
+
if full: s += f'<circle cx="{rx-0.3}" cy="{y-0.8}" r="0.7" fill="white" opacity="0.8"/>'
|
|
435
420
|
else:
|
|
436
421
|
s += f'<circle cx="{lx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{lx+0.8}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
437
422
|
s += f'<circle cx="{rx}" cy="{y}" r="3.5" fill="{eye_white}"/><circle cx="{rx+0.8}" cy="{y}" r="2" fill="{eye_col}"/>'
|
|
@@ -459,6 +444,15 @@ def _render_eyebrows(bi: int, brow_color: str) -> str:
|
|
|
459
444
|
if bi == 4:
|
|
460
445
|
return (f'<polyline points="{lx-3},{y+1} {lx},{y-2} {lx+3},{y}" fill="none" stroke="{brow_color}" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>'
|
|
461
446
|
f'<polyline points="{rx-3},{y} {rx},{y-2} {rx+3},{y+1}" fill="none" stroke="{brow_color}" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>')
|
|
447
|
+
if bi == 5: # Worried
|
|
448
|
+
return (f'<line x1="{lx-3}" y1="{y-1}" x2="{lx+3}" y2="{y+1}" stroke="{brow_color}" stroke-width="1.1" stroke-linecap="round"/>'
|
|
449
|
+
f'<line x1="{rx-3}" y1="{y+1}" x2="{rx+3}" y2="{y-1}" stroke="{brow_color}" stroke-width="1.1" stroke-linecap="round"/>')
|
|
450
|
+
if bi == 6: # Bushy
|
|
451
|
+
return (f'<path d="M{lx-4} {y+0.5} Q{lx} {y-2} {lx+4} {y+0.5}" fill="none" stroke="{brow_color}" stroke-width="2.0" stroke-linecap="round"/>'
|
|
452
|
+
f'<path d="M{rx-4} {y+0.5} Q{rx} {y-2} {rx+4} {y+0.5}" fill="none" stroke="{brow_color}" stroke-width="2.0" stroke-linecap="round"/>')
|
|
453
|
+
if bi == 7: # Thin
|
|
454
|
+
return (f'<path d="M{lx-3.5} {y} Q{lx} {y-1.5} {lx+3.5} {y}" fill="none" stroke="{brow_color}" stroke-width="0.5" stroke-linecap="round"/>'
|
|
455
|
+
f'<path d="M{rx-3.5} {y} Q{rx} {y-1.5} {rx+3.5} {y}" fill="none" stroke="{brow_color}" stroke-width="0.5" stroke-linecap="round"/>')
|
|
462
456
|
return ""
|
|
463
457
|
|
|
464
458
|
|
|
@@ -468,6 +462,11 @@ def _render_nose(ni: int, nose_fill: str) -> str:
|
|
|
468
462
|
if ni == 1: return f'<circle cx="{cx}" cy="{y}" r="1.8" fill="{nose_fill}" opacity="0.5"/>'
|
|
469
463
|
if ni == 2: return f'<path d="M{cx-2} {y+1} Q{cx} {y-2} {cx+2} {y+1}" fill="none" stroke="{nose_fill}" stroke-width="1" stroke-linecap="round" opacity="0.5"/>'
|
|
470
464
|
if ni == 3: return f'<circle cx="{cx-1.8}" cy="{y}" r="1.2" fill="{nose_fill}" opacity="0.4"/><circle cx="{cx+1.8}" cy="{y}" r="1.2" fill="{nose_fill}" opacity="0.4"/>'
|
|
465
|
+
if ni == 4: return f'<path d="M{cx} {y-2} L{cx-2} {y+1.5} L{cx+2} {y+1.5} Z" fill="{nose_fill}" opacity="0.4"/>'
|
|
466
|
+
if ni == 5: return f'<ellipse cx="{cx}" cy="{y}" rx="3.5" ry="1.5" fill="{nose_fill}" opacity="0.35"/>'
|
|
467
|
+
if ni == 6: return f'<line x1="{cx}" y1="{y-3}" x2="{cx}" y2="{y+1}" stroke="{nose_fill}" stroke-width="1.2" stroke-linecap="round" opacity="0.4"/>'
|
|
468
|
+
if ni == 7: return (f'<circle cx="{cx}" cy="{y+0.5}" r="2" fill="{nose_fill}" opacity="0.35"/>'
|
|
469
|
+
f'<ellipse cx="{cx}" cy="{y-0.5}" rx="1" ry="0.5" fill="{nose_fill}" opacity="0.15"/>')
|
|
471
470
|
return f'<ellipse cx="{cx}" cy="{y}" rx="2" ry="1.2" fill="{nose_fill}" opacity="0.35"/>'
|
|
472
471
|
|
|
473
472
|
|
|
@@ -482,14 +481,14 @@ def _render_mouth(mi: int, lip_color: str, is_dark: bool) -> str:
|
|
|
482
481
|
if mi == 5:
|
|
483
482
|
return (f'<path d="M{cx-5} {y} Q{cx} {y+6} {cx+5} {y}" fill="{tc}" stroke="{lip_color}" stroke-width="1"/>'
|
|
484
483
|
f'<line x1="{cx-4}" y1="{y+1.5}" x2="{cx+4}" y2="{y+1.5}" stroke="{lip_color}" stroke-width="0.3" opacity="0.3"/>')
|
|
485
|
-
if mi == 6: return f'<
|
|
484
|
+
if mi == 6: return f'<path d="M{cx-4} {y+0.5} Q{cx} {y+1.5} {cx+4} {y+0.5}" fill="none" stroke="{lip_color}" stroke-width="1.4" stroke-linecap="round"/>'
|
|
486
485
|
if mi == 7:
|
|
487
486
|
return (f'<ellipse cx="{cx}" cy="{y+1}" rx="3.5" ry="2" fill="{lip_color}" opacity="0.25"/>'
|
|
488
487
|
f'<path d="M{cx-3} {y} Q{cx} {y+2.5} {cx+3} {y}" fill="none" stroke="{lip_color}" stroke-width="1.2" stroke-linecap="round"/>')
|
|
489
488
|
return f'<path d="M{cx-4} {y} Q{cx} {y+4} {cx+4} {y}" fill="none" stroke="{lip_color}" stroke-width="1.4" stroke-linecap="round"/>'
|
|
490
489
|
|
|
491
490
|
|
|
492
|
-
def _render_accessory(ai: int, acc_color: str, glasses_color: str, earring_color: str, headband_color: str) -> str:
|
|
491
|
+
def _render_accessory(ai: int, acc_color: str, glasses_color: str, earring_color: str, headband_color: str, skin_color: str = "#E8BA8B") -> str:
|
|
493
492
|
if ai == 0: return ""
|
|
494
493
|
if ai == 1: return '<circle cx="40" cy="44" r="0.8" fill="#3a2a2a"/>'
|
|
495
494
|
if ai == 2:
|
|
@@ -527,10 +526,14 @@ def _render_accessory(ai: int, acc_color: str, glasses_color: str, earring_color
|
|
|
527
526
|
'<line x1="45" y1="31" x2="50" y2="29"/></g>')
|
|
528
527
|
if ai == 9:
|
|
529
528
|
return ('<g>'
|
|
530
|
-
'<rect x="38" y="38" width="
|
|
531
|
-
'<
|
|
532
|
-
'<
|
|
533
|
-
'
|
|
529
|
+
'<rect x="38" y="38" width="9" height="4.5" rx="1.2" fill="#f0d0a0" transform="rotate(-15 42 40)"/>'
|
|
530
|
+
'<rect x="39.5" y="38.5" width="6" height="3.5" rx="0.8" fill="#f5ddb5" transform="rotate(-15 42 40)"/>'
|
|
531
|
+
'<circle cx="42.5" cy="40.25" r="0.5" fill="#d4b898" transform="rotate(-15 42 40)"/>'
|
|
532
|
+
'</g>')
|
|
533
|
+
if ai == 10: # Left Eyebrow Slit
|
|
534
|
+
return f'<line x1="23" y1="24.8" x2="23.8" y2="29.2" stroke="{skin_color}" stroke-width="1.3" stroke-linecap="butt"/>'
|
|
535
|
+
if ai == 11: # Right Eyebrow Slit
|
|
536
|
+
return f'<line x1="41" y1="24.8" x2="40.2" y2="29.2" stroke="{skin_color}" stroke-width="1.3" stroke-linecap="butt"/>'
|
|
534
537
|
return ""
|
|
535
538
|
|
|
536
539
|
|
|
@@ -571,15 +574,15 @@ def render_svg(
|
|
|
571
574
|
parts.append(_render_face(gid, skin, flat))
|
|
572
575
|
if full:
|
|
573
576
|
parts.append(_render_face_overlays(gid))
|
|
574
|
-
if ai == 5:
|
|
575
|
-
parts.append(_render_accessory(5, derived["acc_color"], glasses_color, earring_color, headband_color))
|
|
576
577
|
parts.append(_render_hair_front(hi, gid, hair_col, skin, flat))
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
parts.append(
|
|
578
|
+
if ai == 5:
|
|
579
|
+
parts.append(_render_accessory(5, derived["acc_color"], glasses_color, earring_color, headband_color, skin))
|
|
580
|
+
parts.append(_render_eyes(t.eye_style % 9, eye_col, derived["eye_white"], derived["lid_color"], full))
|
|
581
|
+
parts.append(_render_eyebrows(t.eyebrows % 8, derived["brow_color"]))
|
|
582
|
+
parts.append(_render_nose(t.nose % 8, derived["nose_fill"]))
|
|
580
583
|
parts.append(_render_mouth(t.mouth % 8, derived["lip_color"], derived["is_dark"]))
|
|
581
584
|
if ai != 0 and ai != 5:
|
|
582
|
-
parts.append(_render_accessory(ai, derived["acc_color"], glasses_color, earring_color, headband_color))
|
|
585
|
+
parts.append(_render_accessory(ai, derived["acc_color"], glasses_color, earring_color, headband_color, skin))
|
|
583
586
|
parts.append("</svg>")
|
|
584
587
|
return "".join(p for p in parts if p)
|
|
585
588
|
|
|
@@ -596,26 +599,25 @@ _SKIN_DESC = {0: "porcelain", 1: "ivory", 2: "fair", 3: "light", 4: "sand",
|
|
|
596
599
|
5: "golden", 6: "warm", 7: "caramel", 8: "brown", 9: "deep"}
|
|
597
600
|
_EYE_STYLE_DESC = {0: "round, wide-open", 1: "small and minimal", 2: "almond-shaped",
|
|
598
601
|
3: "wide and expressive", 4: "relaxed, half-lidded", 5: "joyful, crescent-shaped",
|
|
599
|
-
6: "bright and sparkling", 7: "gentle and narrow"}
|
|
600
|
-
_EYE_COLOR_DESC = {0: "dark brown", 1: "blue", 2: "green", 3: "hazel", 4: "gray"
|
|
601
|
-
|
|
602
|
+
6: "bright and sparkling", 7: "gentle and narrow", 8: "side-looking, shifted"}
|
|
603
|
+
_EYE_COLOR_DESC = {0: "dark brown", 1: "blue", 2: "green", 3: "hazel", 4: "gray",
|
|
604
|
+
5: "amber", 6: "violet", 7: "steel gray"}
|
|
605
|
+
_BROW_DESC = {0: "wispy", 1: "straight", 2: "natural", 3: "elegantly arched", 4: "sharply angled",
|
|
606
|
+
5: "worried, furrowed", 6: "bushy", 7: "thin, delicate"}
|
|
602
607
|
_NOSE_DESC = {0: "a subtle shadow nose", 1: "a small button nose", 2: "a soft curved nose",
|
|
603
|
-
3: "a button nose with visible nostrils"
|
|
608
|
+
3: "a button nose with visible nostrils", 4: "a pointed nose",
|
|
609
|
+
5: "a wide, broad nose", 6: "a nose with a visible bridge",
|
|
610
|
+
7: "a small snub nose"}
|
|
604
611
|
_MOUTH_DESC = {0: "a gentle smile", 1: "a calm, neutral expression", 2: "a happy grin",
|
|
605
612
|
3: "a surprised O-shaped mouth", 4: "a confident smirk", 5: "a wide, toothy grin",
|
|
606
613
|
6: "a flat, straight expression", 7: "a soft pout"}
|
|
607
|
-
_HAIR_STYLE_DESC = {0: "bald, with no hair", 1: "short, neatly cropped hair", 2: "bouncy, curly hair",
|
|
608
|
-
3: "side-swept hair", 4: "a voluminous puff",
|
|
609
|
-
5: "long hair that falls past the shoulders", 6: "a clean bob cut",
|
|
610
|
-
7: "a close buzz cut", 8: "flowing, wavy hair", 9: "a neat topknot"}
|
|
611
|
-
_HAIR_COLOR_DESC = {0: "jet black", 1: "espresso brown", 2: "walnut", 3: "honey blonde",
|
|
612
|
-
4: "copper red", 5: "silver", 6: "charcoal", 7: "burgundy",
|
|
613
|
-
8: "strawberry", 9: "ginger"}
|
|
614
614
|
_ACC_DESC = {0: "", 1: "a beauty mark", 2: "round glasses", 3: "rectangular glasses",
|
|
615
615
|
4: "a dangling earring", 5: "a headband", 6: "freckles", 7: "stud earrings",
|
|
616
|
-
8: "aviator sunglasses", 9: "a band-aid"
|
|
616
|
+
8: "aviator sunglasses", 9: "a band-aid",
|
|
617
|
+
10: "a left eyebrow slit", 11: "a right eyebrow slit"}
|
|
617
618
|
_BG_DESC = {0: "rose", 1: "olive", 2: "sage", 3: "fern", 4: "mint",
|
|
618
|
-
5: "ocean", 6: "sky", 7: "lavender", 8: "orchid", 9: "blush"
|
|
619
|
+
5: "ocean", 6: "sky", 7: "lavender", 8: "orchid", 9: "blush",
|
|
620
|
+
10: "lilac", 11: "seafoam"}
|
|
619
621
|
|
|
620
622
|
|
|
621
623
|
def _build_paragraph(
|
|
@@ -642,15 +644,8 @@ def _build_paragraph(
|
|
|
642
644
|
if brows:
|
|
643
645
|
parts.append(f"{brows} eyebrows")
|
|
644
646
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
else:
|
|
648
|
-
hc = _HAIR_COLOR_DESC.get(t.hair_color, "")
|
|
649
|
-
hs = _HAIR_STYLE_DESC.get(t.hair_style, "")
|
|
650
|
-
if hs.startswith("a "):
|
|
651
|
-
parts.append(f"and a {hc} {hs[2:]}")
|
|
652
|
-
else:
|
|
653
|
-
parts.append(f"and {hc} {hs}")
|
|
647
|
+
# All styles render as bald
|
|
648
|
+
parts.append("and am bald" if perspective == "first" else "and is bald")
|
|
654
649
|
|
|
655
650
|
desc = parts[0]
|
|
656
651
|
if len(parts) > 2:
|
|
@@ -690,12 +685,7 @@ def _build_structured(t, ai: int, include_background: bool = True) -> str:
|
|
|
690
685
|
if nose:
|
|
691
686
|
lines.append(f"Nose: {nose.lstrip('a ')}")
|
|
692
687
|
lines.append(f"Mouth: {_MOUTH_DESC.get(t.mouth, 'smile')}")
|
|
693
|
-
|
|
694
|
-
lines.append("Hair: bald")
|
|
695
|
-
else:
|
|
696
|
-
hs = _HAIR_STYLE_DESC.get(t.hair_style, "")
|
|
697
|
-
hc = _HAIR_COLOR_DESC.get(t.hair_color, "")
|
|
698
|
-
lines.append(f"Hair: {hc} {hs[2:] if hs.startswith('a ') else hs}")
|
|
688
|
+
lines.append("Hair: bald")
|
|
699
689
|
acc = _ACC_DESC.get(ai, "")
|
|
700
690
|
if acc:
|
|
701
691
|
lines.append(f"Accessory: {acc}")
|
|
@@ -709,13 +699,7 @@ def _build_compact(t, ai: int) -> str:
|
|
|
709
699
|
parts.append("squircle face")
|
|
710
700
|
parts.append(f"{_SKIN_DESC.get(t.skin_color, 'warm')} skin")
|
|
711
701
|
parts.append(f"{_EYE_COLOR_DESC.get(t.eye_color, 'dark')} {_EYE_STYLE_DESC.get(t.eye_style, 'round')} eyes")
|
|
712
|
-
|
|
713
|
-
parts.append("bald")
|
|
714
|
-
else:
|
|
715
|
-
raw = _HAIR_STYLE_DESC.get(t.hair_style, "hair")
|
|
716
|
-
hs = raw.split(",")[-1].strip() if "," in raw else raw
|
|
717
|
-
hc = _HAIR_COLOR_DESC.get(t.hair_color, "")
|
|
718
|
-
parts.append(f"{hc} {hs[2:] if hs.startswith('a ') else hs}")
|
|
702
|
+
parts.append("bald")
|
|
719
703
|
acc = _ACC_DESC.get(ai, "")
|
|
720
704
|
if acc:
|
|
721
705
|
parts.append(acc)
|
|
@@ -1004,11 +988,13 @@ def derive_identity(wallet: str) -> dict:
|
|
|
1004
988
|
# Discriminator from bytes 8-9
|
|
1005
989
|
discriminator = hex_str[16:20]
|
|
1006
990
|
|
|
991
|
+
sep = " "
|
|
992
|
+
|
|
1007
993
|
return {
|
|
1008
994
|
"short": adj1,
|
|
1009
|
-
"name": adj1 + noun1,
|
|
1010
|
-
"tag": adj1 + noun1 + "#" + discriminator,
|
|
1011
|
-
"full": adj1 + noun1 + "-" + adj2 + noun2,
|
|
995
|
+
"name": adj1 + sep + noun1,
|
|
996
|
+
"tag": adj1 + sep + noun1 + "#" + discriminator,
|
|
997
|
+
"full": adj1 + sep + noun1 + "-" + adj2 + sep + noun2,
|
|
1012
998
|
"adjective": adj1,
|
|
1013
999
|
"noun": noun1,
|
|
1014
1000
|
"hash": hex_str,
|