solfaces 1.0.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/dist/agent/index.cjs +51 -0
  4. package/dist/agent/index.cjs.map +1 -0
  5. package/dist/agent/index.d.cts +65 -0
  6. package/dist/agent/index.d.ts +65 -0
  7. package/dist/agent/index.js +6 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/agent/mcp-server.cjs +836 -0
  10. package/dist/chunk-2DIKGLXZ.cjs +126 -0
  11. package/dist/chunk-2DIKGLXZ.cjs.map +1 -0
  12. package/dist/chunk-A6N3RPEA.cjs +111 -0
  13. package/dist/chunk-A6N3RPEA.cjs.map +1 -0
  14. package/dist/chunk-CVFO7YHY.cjs +97 -0
  15. package/dist/chunk-CVFO7YHY.cjs.map +1 -0
  16. package/dist/chunk-H3SK3MNX.cjs +409 -0
  17. package/dist/chunk-H3SK3MNX.cjs.map +1 -0
  18. package/dist/chunk-KSGFMW33.js +401 -0
  19. package/dist/chunk-KSGFMW33.js.map +1 -0
  20. package/dist/chunk-LQWJRHGC.js +86 -0
  21. package/dist/chunk-LQWJRHGC.js.map +1 -0
  22. package/dist/chunk-RX6D5FGH.js +211 -0
  23. package/dist/chunk-RX6D5FGH.js.map +1 -0
  24. package/dist/chunk-SNJABBAT.js +107 -0
  25. package/dist/chunk-SNJABBAT.js.map +1 -0
  26. package/dist/chunk-VMNATBH3.cjs +222 -0
  27. package/dist/chunk-VMNATBH3.cjs.map +1 -0
  28. package/dist/chunk-WURY4QGH.js +117 -0
  29. package/dist/chunk-WURY4QGH.js.map +1 -0
  30. package/dist/core/index.cjs +82 -0
  31. package/dist/core/index.cjs.map +1 -0
  32. package/dist/core/index.d.cts +104 -0
  33. package/dist/core/index.d.ts +104 -0
  34. package/dist/core/index.js +5 -0
  35. package/dist/core/index.js.map +1 -0
  36. package/dist/index.cjs +100 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +4 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +7 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/react/index.cjs +543 -0
  43. package/dist/react/index.cjs.map +1 -0
  44. package/dist/react/index.d.cts +28 -0
  45. package/dist/react/index.d.ts +28 -0
  46. package/dist/react/index.js +541 -0
  47. package/dist/react/index.js.map +1 -0
  48. package/dist/solfaces.cdn.global.js +3 -0
  49. package/dist/solfaces.cdn.global.js.map +1 -0
  50. package/dist/themes/index.cjs +48 -0
  51. package/dist/themes/index.cjs.map +1 -0
  52. package/dist/themes/index.d.cts +14 -0
  53. package/dist/themes/index.d.ts +14 -0
  54. package/dist/themes/index.js +3 -0
  55. package/dist/themes/index.js.map +1 -0
  56. package/dist/traits-DAFZnXeS.d.cts +61 -0
  57. package/dist/traits-DAFZnXeS.d.ts +61 -0
  58. package/dist/vanilla/index.cjs +43 -0
  59. package/dist/vanilla/index.cjs.map +1 -0
  60. package/dist/vanilla/index.d.cts +7 -0
  61. package/dist/vanilla/index.d.ts +7 -0
  62. package/dist/vanilla/index.js +39 -0
  63. package/dist/vanilla/index.js.map +1 -0
  64. package/package.json +100 -0
  65. package/python/solfaces.py +475 -0
  66. package/skill.md +463 -0
package/skill.md ADDED
@@ -0,0 +1,463 @@
1
+ # SolFaces — AI Agent Skill
2
+
3
+ You have access to SolFaces, a fully customizable, deterministic avatar generator for Solana wallets. Every wallet address produces a unique, consistent face — same wallet = same face, always. No API calls, no database, no randomness. ~221M unique combinations across 11 traits.
4
+
5
+ SolFaces eliminates empty avatar slots and generic placeholders — every wallet gets a unique face instantly, elevating any dApp or website UI even when users never upload a profile picture. Every visual element (skin, eyes, hair, mouth, nose, eyebrows, accessories, background, border, eye whites, teeth) is fully customizable through themes and per-instance color overrides, so it can match any brand or UI.
6
+
7
+ Use these tools whenever a user mentions wallet avatars, profile pictures, visual identity, or when you need to represent a Solana wallet visually.
8
+
9
+ ---
10
+
11
+ ## Available Tools
12
+
13
+ ### `generate_solface_svg`
14
+ Render an SVG avatar from a wallet address.
15
+
16
+ **When to use:** User wants to see an avatar, embed one in a page, save an image, or display a wallet's face.
17
+
18
+ | Parameter | Type | Required | Default | Description |
19
+ |-----------|------|----------|---------|-------------|
20
+ | `wallet` | string | Yes | — | Solana wallet address (base58) |
21
+ | `size` | number | No | 64 | Width/height in pixels |
22
+ | `theme` | string | No | — | Preset: solana, dark, light, mono, neon, jupiter, phantom, circle |
23
+ | `enableBlink` | boolean | No | false | CSS eye-blink animation |
24
+
25
+ Returns: SVG string.
26
+
27
+ ### `describe_solface`
28
+ Generate a natural language description of a wallet's avatar.
29
+
30
+ **When to use:** User wants alt text, a profile bio, accessibility text, or to know what a face looks like.
31
+
32
+ | Parameter | Type | Required | Default | Description |
33
+ |-----------|------|----------|---------|-------------|
34
+ | `wallet` | string | Yes | — | Solana wallet address |
35
+ | `format` | string | No | paragraph | paragraph, structured, or compact |
36
+ | `perspective` | string | No | third | third or first |
37
+ | `name` | string | No | — | Name to use instead of "This SolFace" |
38
+
39
+ Returns: Description string.
40
+
41
+ ### `get_solface_traits`
42
+ Get raw trait data with human-readable labels and a deterministic hash.
43
+
44
+ **When to use:** User wants structured data, wants to compare wallets, or needs the trait hash.
45
+
46
+ | Parameter | Type | Required | Description |
47
+ |-----------|------|----------|-------------|
48
+ | `wallet` | string | Yes | Solana wallet address |
49
+
50
+ Returns: `{ traits, labels, hash }`
51
+
52
+ ### `get_agent_identity`
53
+ Generate a system prompt snippet giving an AI agent a visual identity.
54
+
55
+ **When to use:** Setting up an AI agent's persona or bot profile.
56
+
57
+ | Parameter | Type | Required | Description |
58
+ |-----------|------|----------|-------------|
59
+ | `wallet` | string | Yes | Agent's wallet address |
60
+ | `agentName` | string | No | Agent name for personalization |
61
+
62
+ Returns: First-person identity prompt string.
63
+
64
+ ### `list_solface_themes`
65
+ List available preset themes with descriptions.
66
+
67
+ **When to use:** User asks what themes exist or you need to recommend one.
68
+
69
+ No parameters. Returns: Array of `{ name, description }`.
70
+
71
+ ---
72
+
73
+ ## Installation
74
+
75
+ ### npm / yarn / pnpm
76
+ ```bash
77
+ npm install solfaces
78
+ ```
79
+
80
+ ### CDN (no build step)
81
+ ```html
82
+ <script src="https://unpkg.com/solfaces/dist/solfaces.cdn.global.js"></script>
83
+ ```
84
+
85
+ ### Python (zero dependencies)
86
+ ```bash
87
+ curl -O https://raw.githubusercontent.com/jorger3301/solfaces/main/python/solfaces.py
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Import Paths — Choose the Right One
93
+
94
+ | Project Type | Import Path | What You Get |
95
+ |---|---|---|
96
+ | React / Next.js app | `solfaces/react` | `<SolFace>` component with JSX |
97
+ | Vanilla JS / Vue / Svelte | `solfaces/vanilla` | `mountSolFace()`, `setSolFaceImg()`, `autoInit()` |
98
+ | Node.js server / API route | `solfaces` | `renderSolFaceSVG()`, `renderSolFacePNG()`, descriptions |
99
+ | No build step / CDN | `<script>` tag | `window.SolFaces` global |
100
+ | AI agent framework | `solfaces/agent` | Tool schemas + format adapters |
101
+ | Theme presets only | `solfaces/themes` | 8 preset theme objects |
102
+ | Python backend | `from solfaces import ...` | Full Python port |
103
+
104
+ ---
105
+
106
+ ## React Integration
107
+
108
+ ### Basic usage
109
+ ```tsx
110
+ import { SolFace } from "solfaces/react";
111
+
112
+ <SolFace walletAddress="7xKXtg..." size={48} />
113
+ ```
114
+
115
+ ### With theme
116
+ ```tsx
117
+ import { SolFace } from "solfaces/react";
118
+ import { darkTheme } from "solfaces/themes";
119
+
120
+ <SolFace walletAddress="7xKXtg..." size={48} theme={darkTheme} />
121
+ ```
122
+
123
+ ### With blink animation
124
+ ```tsx
125
+ <SolFace walletAddress="7xKXtg..." enableBlink />
126
+ ```
127
+
128
+ ### With trait overrides (pin specific traits)
129
+ ```tsx
130
+ <SolFace walletAddress="7xKXtg..." traitOverrides={{ hairStyle: 0 }} />
131
+ ```
132
+
133
+ ### With custom styling
134
+ ```tsx
135
+ <SolFace
136
+ walletAddress="7xKXtg..."
137
+ size={48}
138
+ className="my-avatar"
139
+ style={{ borderRadius: "50%" }}
140
+ onClick={handleClick}
141
+ />
142
+ ```
143
+
144
+ ### All props
145
+ ```tsx
146
+ interface SolFaceProps {
147
+ walletAddress: string; // Required
148
+ size?: number; // Default: 64
149
+ enableBlink?: boolean | { duration?: number; delay?: number }; // Blink animation
150
+ theme?: SolFaceTheme; // Optional theme object
151
+ traitOverrides?: Partial<SolFaceTraits>; // Pin specific traits
152
+ colorOverrides?: { // Override individual colors per instance
153
+ skin?: string;
154
+ eyes?: string;
155
+ hair?: string;
156
+ bg?: string;
157
+ mouth?: string;
158
+ eyebrow?: string;
159
+ accessory?: string;
160
+ nose?: string;
161
+ eyeWhite?: string;
162
+ };
163
+ className?: string;
164
+ style?: React.CSSProperties;
165
+ // ...all SVG element props
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Server-Side / API Routes
172
+
173
+ ### Next.js App Router
174
+ ```ts
175
+ import { renderSolFaceSVG } from "solfaces";
176
+
177
+ export async function GET(req: Request) {
178
+ const url = new URL(req.url);
179
+ const wallet = url.searchParams.get("wallet");
180
+ const svg = renderSolFaceSVG(wallet, { size: 256 });
181
+ return new Response(svg, {
182
+ headers: { "Content-Type": "image/svg+xml" },
183
+ });
184
+ }
185
+ ```
186
+
187
+ ### PNG for Discord/Telegram (requires `sharp` or `@resvg/resvg-js`)
188
+ ```ts
189
+ import { renderSolFacePNG } from "solfaces";
190
+
191
+ const pngBuffer = await renderSolFacePNG("7xKXtg...", { pngSize: 512 });
192
+
193
+ // Save to file
194
+ fs.writeFileSync("avatar.png", pngBuffer);
195
+
196
+ // HTTP response
197
+ return new Response(pngBuffer, {
198
+ headers: { "Content-Type": "image/png" },
199
+ });
200
+ ```
201
+
202
+ ### Express
203
+ ```ts
204
+ import { renderSolFaceSVG } from "solfaces";
205
+
206
+ app.get("/avatar/:wallet", (req, res) => {
207
+ const svg = renderSolFaceSVG(req.params.wallet, { size: 256 });
208
+ res.type("image/svg+xml").send(svg);
209
+ });
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Bot Integration
215
+
216
+ ### Telegram Bot (grammy)
217
+ ```ts
218
+ import { renderSolFacePNG } from "solfaces";
219
+
220
+ bot.command("avatar", async (ctx) => {
221
+ const wallet = ctx.match;
222
+ const png = await renderSolFacePNG(wallet, { pngSize: 256 });
223
+ await ctx.replyWithPhoto(new InputFile(png, "solface.png"));
224
+ });
225
+ ```
226
+
227
+ ### Discord Bot (discord.js)
228
+ ```ts
229
+ import { renderSolFacePNG } from "solfaces";
230
+ import { AttachmentBuilder } from "discord.js";
231
+
232
+ const png = await renderSolFacePNG(wallet, { pngSize: 256 });
233
+ const attachment = new AttachmentBuilder(png, { name: "solface.png" });
234
+ await interaction.reply({ files: [attachment] });
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Custom Themes — Fully Customizable to Any UI
240
+
241
+ SolFaces is designed to be fully customizable. Every color, every feature — adapt it to match any brand, any design system, any user preference. Use the theme system for global styling, `colorOverrides` for per-instance control, `traitOverrides` to pin specific features, and `enableBlink` with custom timing for animations. No visual element is locked — if you can see it, you can customize it.
242
+
243
+ ### Use a preset theme
244
+ ```ts
245
+ import { renderSolFaceSVG } from "solfaces";
246
+ import { darkTheme } from "solfaces/themes";
247
+
248
+ const svg = renderSolFaceSVG(wallet, { theme: darkTheme });
249
+ ```
250
+
251
+ ### Extend a preset
252
+ ```ts
253
+ import { getPresetTheme } from "solfaces/themes";
254
+
255
+ const myTheme = getPresetTheme("dark", {
256
+ bgRadius: 999, // Make it circular
257
+ border: { color: "#14F195", width: 2 }, // Add Solana green border
258
+ });
259
+ ```
260
+
261
+ ### Build a fully custom theme
262
+ ```ts
263
+ import type { SolFaceTheme } from "solfaces";
264
+
265
+ const brandTheme: SolFaceTheme = {
266
+ // Override color palettes (arrays — one per trait variant)
267
+ skinColors: ["#ffd5b0", "#f4c794", "#e0a370", "#c68642", "#8d5524", "#4a2c17"],
268
+ eyeColors: ["#333", "#4a80c4", "#5a9a5a", "#c89430", "#8a8a8a"],
269
+ hairColors: ["#1a1a1a", "#6b3a2a", "#d4a844", "#c44a20", "#c8e64a", "#6090e0", "#14F195", "#e040c0"],
270
+ bgColors: ["#0a0f1a", "#1a0a2e", "#0a1628"], // Your brand backgrounds
271
+
272
+ // Override single colors
273
+ mouthColor: "#e06070",
274
+ eyebrowColor: "#aaa",
275
+ accessoryColor: "#888",
276
+ eyeWhiteColor: "#e0e0e0", // Sclera + teeth color (important for dark themes)
277
+ noseColor: "#c68642aa", // Nose color (defaults to skin + transparency)
278
+
279
+ // Layout
280
+ bgOpacity: 1, // 0-1, background fill opacity
281
+ bgRadius: 999, // Border radius (999 = circle)
282
+ border: { color: "#14F195", width: 2 }, // Optional border
283
+ };
284
+ ```
285
+
286
+ ### Theme field reference
287
+
288
+ | Field | Type | What it controls |
289
+ |-------|------|-----------------|
290
+ | `skinColors` | `string[]` | Face/skin fill colors (6 variants) |
291
+ | `eyeColors` | `string[]` | Iris/pupil colors (5 variants) |
292
+ | `hairColors` | `string[]` | Hair fill colors (8 variants) |
293
+ | `bgColors` | `string[]` | Background fill colors (5 variants) |
294
+ | `mouthColor` | `string` | Mouth stroke/fill color |
295
+ | `eyebrowColor` | `string` | Eyebrow stroke color |
296
+ | `accessoryColor` | `string` | Glasses/earring/bandana color |
297
+ | `eyeWhiteColor` | `string` | Sclera (eye white) and teeth color — set for dark themes |
298
+ | `noseColor` | `string` | Nose color (defaults to skin + transparency) |
299
+ | `bgOpacity` | `number` | Background opacity (0-1) |
300
+ | `bgRadius` | `number` | SVG rect border radius |
301
+ | `border` | `{ color, width }` | Optional border around avatar |
302
+
303
+ ### Per-instance color overrides
304
+
305
+ Override any color on a specific avatar without changing the global theme. Useful for highlighting, user preferences, or special states:
306
+
307
+ ```tsx
308
+ // React
309
+ <SolFace walletAddress="7xKXtg..." colorOverrides={{ hair: "#ff0000", bg: "#000" }} />
310
+
311
+ // String renderer
312
+ renderSolFaceSVG("7xKXtg...", {
313
+ theme: darkTheme,
314
+ colorOverrides: { skin: "#ffd5b0", eyes: "#00ff00" },
315
+ });
316
+ ```
317
+
318
+ Available keys: `skin`, `eyes`, `hair`, `bg`, `mouth`, `eyebrow`, `accessory`, `nose`, `eyeWhite`.
319
+
320
+ ### Theme recommendations by context
321
+
322
+ | Context | Theme | Why |
323
+ |---------|-------|-----|
324
+ | Dark UI / dark mode | `dark` | Dark backgrounds, muted tones |
325
+ | Light UI / white background | `light` | Soft pastels |
326
+ | Solana-branded | `solana` | Vibrant #14F195, #9945FF |
327
+ | Minimal / clean | `mono` | Full grayscale |
328
+ | Gaming / cyberpunk | `neon` | High-contrast neon + green border |
329
+ | Jupiter DEX | `jupiter` | Matches Jupiter palette |
330
+ | Phantom wallet | `phantom` | Purple tones |
331
+ | Circular profile pic | `circle` | border-radius: 999 |
332
+
333
+ ---
334
+
335
+ ## Agent Framework Integration
336
+
337
+ ### OpenAI Function Calling
338
+ ```ts
339
+ import { allToolsOpenAI, handleToolCall } from "solfaces/agent";
340
+
341
+ const response = await openai.chat.completions.create({
342
+ model: "gpt-4",
343
+ messages,
344
+ tools: allToolsOpenAI(),
345
+ });
346
+
347
+ // Handle tool calls
348
+ for (const call of response.choices[0].message.tool_calls) {
349
+ const result = await handleToolCall(call.function.name, JSON.parse(call.function.arguments));
350
+ }
351
+ ```
352
+
353
+ ### Claude / Anthropic Tool Use
354
+ ```ts
355
+ import { allToolsAnthropic, handleToolCall } from "solfaces/agent";
356
+
357
+ const response = await anthropic.messages.create({
358
+ model: "claude-sonnet-4-20250514",
359
+ messages,
360
+ tools: allToolsAnthropic(),
361
+ });
362
+
363
+ // Handle tool use blocks
364
+ for (const block of response.content) {
365
+ if (block.type === "tool_use") {
366
+ const result = await handleToolCall(block.name, block.input);
367
+ }
368
+ }
369
+ ```
370
+
371
+ ### Vercel AI SDK
372
+ ```ts
373
+ import { allToolsVercelAI } from "solfaces/agent";
374
+ import { generateText } from "ai";
375
+
376
+ const { text } = await generateText({
377
+ model: yourModel,
378
+ prompt: "Show me the avatar for wallet 7xKXtg...",
379
+ tools: allToolsVercelAI(),
380
+ });
381
+ ```
382
+
383
+ ### MCP Server (Claude Code / Cursor)
384
+ Add to your MCP config (`~/.claude/settings.json` or `.cursor/mcp.json`):
385
+ ```json
386
+ {
387
+ "mcpServers": {
388
+ "solfaces": {
389
+ "command": "npx",
390
+ "args": ["solfaces-mcp"]
391
+ }
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### Universal dispatcher (any framework)
397
+ ```ts
398
+ import { handleToolCall } from "solfaces/agent";
399
+
400
+ const svg = await handleToolCall("generate_solface_svg", {
401
+ wallet: "7xKXtg...",
402
+ theme: "dark",
403
+ size: 128,
404
+ });
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Accessibility
410
+
411
+ Always provide alt text for SolFace avatars:
412
+
413
+ ```ts
414
+ import { solFaceAltText } from "solfaces";
415
+
416
+ const alt = solFaceAltText("7xKXtg...");
417
+ // → "SolFace avatar: angular, hexagonal face, light peach skin, green round, wide-open eyes, ..."
418
+ ```
419
+
420
+ Or use the `describe_solface` tool with `format: "compact"` for the same result.
421
+
422
+ In React:
423
+ ```tsx
424
+ <img src={dataUri} alt={solFaceAltText(wallet)} />
425
+ ```
426
+
427
+ ---
428
+
429
+ ## API Reference
430
+
431
+ | Function | Import | Returns | Description |
432
+ |----------|--------|---------|-------------|
433
+ | `generateTraits(wallet, overrides?)` | `solfaces` | `SolFaceTraits` | Deterministic traits from wallet |
434
+ | `renderSolFaceSVG(wallet, options?)` | `solfaces` | `string` | Raw SVG markup |
435
+ | `renderSolFaceDataURI(wallet, options?)` | `solfaces` | `string` | Data URI for `<img>` tags |
436
+ | `renderSolFaceBase64(wallet, options?)` | `solfaces` | `string` | Base64 data URI |
437
+ | `renderSolFacePNG(wallet, options?)` | `solfaces` | `Promise<Buffer>` | PNG buffer (Node, needs sharp/resvg) |
438
+ | `renderSolFacePNGBrowser(wallet, options?)` | `solfaces` | `Promise<Blob>` | PNG blob (browser) |
439
+ | `describeAppearance(wallet, options?)` | `solfaces` | `string` | Natural language description |
440
+ | `agentAppearancePrompt(wallet, name?)` | `solfaces` | `string` | System prompt for AI agents |
441
+ | `solFaceAltText(wallet)` | `solfaces` | `string` | Accessible alt text |
442
+ | `getTraitLabels(traits)` | `solfaces` | `Record<string, string>` | Human-readable trait names |
443
+ | `traitHash(wallet)` | `solfaces` | `string` | 8-char hex hash |
444
+ | `getPresetTheme(name, overrides?)` | `solfaces/themes` | `SolFaceTheme` | Get/extend a preset theme |
445
+ | `SOLFACE_TOOLS` | `solfaces/agent` | `SolFaceTool[]` | All 5 tool definitions |
446
+ | `handleToolCall(name, params)` | `solfaces/agent` | `unknown` | Universal tool dispatcher |
447
+ | `allToolsOpenAI()` | `solfaces/agent` | `OpenAITool[]` | Tools in OpenAI format |
448
+ | `allToolsAnthropic()` | `solfaces/agent` | `AnthropicTool[]` | Tools in Anthropic format |
449
+ | `allToolsVercelAI()` | `solfaces/agent` | `Record<string, VercelAITool>` | Tools in Vercel AI format |
450
+ | `allToolsMCP()` | `solfaces/agent` | `MCPTool[]` | Tools in MCP format |
451
+
452
+ ---
453
+
454
+ ## Key Facts
455
+
456
+ - **Deterministic**: Same wallet = same face, always. Guaranteed by djb2 hash + mulberry32 PRNG.
457
+ - **11 traits**: Face shape (4), skin (6), eye style (8), eye color (5), eyebrows (5), nose (4), mouth (6), hair style (8), hair color (8), accessory (6), background (5) = ~221M unique combinations.
458
+ - **Fully customizable**: Every visual element is customizable — 12 theme fields for global styling, 9 colorOverride keys for per-instance control, trait pinning, and animation timing. No visual element is locked.
459
+ - **Eliminates dead space**: Instantly fills empty avatar slots with unique, deterministic faces — elevates any dApp or website UI even when users don't upload a profile picture.
460
+ - **Cross-language parity**: JavaScript and Python produce identical output.
461
+ - **Zero dependencies**: Core engine has no runtime deps.
462
+ - **Sub-millisecond**: Trait generation and SVG rendering are nearly instant.
463
+ - **SVG viewBox**: All avatars use a 64x64 viewBox, scaled by the `size` parameter.