privateboard 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/cli.js +10502 -0
  4. package/dist/cli.js.map +1 -0
  5. package/package.json +63 -0
  6. package/public/adjourn-overlay.css +253 -0
  7. package/public/agent-overlay.css +444 -0
  8. package/public/agent-overlay.js +604 -0
  9. package/public/agent-profile.css +3230 -0
  10. package/public/agent-profile.js +3329 -0
  11. package/public/app.js +6629 -0
  12. package/public/auto-hide-scroll.js +90 -0
  13. package/public/avatar-skill.js +793 -0
  14. package/public/avatars/chair.svg +98 -0
  15. package/public/avatars/first-principles.svg +122 -0
  16. package/public/avatars/long-horizon.svg +147 -0
  17. package/public/avatars/open_ai.png +0 -0
  18. package/public/avatars/phenomenologist.svg +130 -0
  19. package/public/avatars/socrates.svg +187 -0
  20. package/public/avatars/user-empathy.svg +117 -0
  21. package/public/avatars/value-investor.svg +117 -0
  22. package/public/favicon.svg +10 -0
  23. package/public/fonts/agent-Italic.woff2 +0 -0
  24. package/public/fonts/human-sans.woff2 +0 -0
  25. package/public/icons.css +103 -0
  26. package/public/models-cache.js +57 -0
  27. package/public/new-agent.css +1359 -0
  28. package/public/new-agent.js +675 -0
  29. package/public/onboarding.css +628 -0
  30. package/public/onboarding.js +782 -0
  31. package/public/prototype-dashboard.html +7596 -0
  32. package/public/report/spines/a16z-thesis.css +1055 -0
  33. package/public/report/spines/anthropic-essay.css +556 -0
  34. package/public/report/spines/boardroom-dark.css +1082 -0
  35. package/public/report/spines/gartner-note.css +538 -0
  36. package/public/report/spines/mckinsey-deck.css +523 -0
  37. package/public/report/spines/openai-paper.css +516 -0
  38. package/public/report.html +1417 -0
  39. package/public/room-settings.css +895 -0
  40. package/public/room-settings.js +1039 -0
  41. package/public/themes.css +338 -0
  42. package/public/user-settings.css +1236 -0
  43. package/public/user-settings.js +1291 -0
@@ -0,0 +1,793 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ AVATAR SKILL · 48×56 chibi pixel-art SVG generator
3
+ ─────────────────────────────────────────────────────────────
4
+ Head-only chibi sprites in the same hand-crafted style as the
5
+ seeded directors (avatars/socrates.svg, first-principles.svg, etc.):
6
+ · rounded face (chamfered top + chin)
7
+ · multi-tone skin shading (highlight / mid / shadow / outline)
8
+ · varied hair styles + colours, glasses, eyebrows, mouth
9
+ · neck stub at the bottom (no body / clothes)
10
+ viewBox is `0 -4 48 56` so the head sits with breathing room
11
+ at the top and the neck stub fades into the canvas bottom.
12
+
13
+ Public API · window.AvatarSkill:
14
+ generate(seed?, opts?) → SVG markup string
15
+ generateDataUrl(seed?, opts?) → "data:image/svg+xml;…"
16
+ randomSeed() → fresh seed string
17
+ attach({ frame, button, onSeed }) → wire a UI in one line.
18
+
19
+ opts.placeholder = true → neutral grey silhouette w/ "?".
20
+ ═══════════════════════════════════════════════════════════════ */
21
+
22
+ (function () {
23
+ if (window.AvatarSkill) return;
24
+
25
+ // ── Constants ────────────────────────────────────────────
26
+ const W = 48;
27
+ const H = 56;
28
+ // The visible Y range is -4..52. Internally we operate on 0..H
29
+ // and serialize with viewBox="0 -4 48 56".
30
+
31
+ // ── Palettes (multi-tone) ────────────────────────────────
32
+ const SKIN = [
33
+ { hl: "#FFE5C5", mid: "#F2B585", shadow: "#D69570", deep: "#A8704A", outline: "#5A3520" }, // peach
34
+ { hl: "#FFCFA2", mid: "#E8B084", shadow: "#D69970", deep: "#B58050", outline: "#6E4830" }, // warm tan
35
+ { hl: "#EFD0AC", mid: "#D6A878", shadow: "#B58858", deep: "#6E4830", outline: "#3F2818" }, // olive (chair)
36
+ { hl: "#E5BFA0", mid: "#C68863", shadow: "#A86B47", deep: "#6F4528", outline: "#2A1A12" }, // medium
37
+ { hl: "#C68863", mid: "#A86B47", shadow: "#8B5A3C", deep: "#5A3520", outline: "#1A0A05" }, // bronze
38
+ { hl: "#A86B47", mid: "#8B5A3C", shadow: "#6F4528", deep: "#3A2014", outline: "#1A0A05" }, // deep
39
+ ];
40
+
41
+ // Each hair colour ships highlight / mid / shadow / outline.
42
+ const HAIR = [
43
+ { name: "black-blue", hl: "#3A5A9C", mid: "#1F2A4A", shadow: "#0A0A1A", outline: "#0A0A1A", brow: "#0A0A1A" },
44
+ { name: "dark-brown", hl: "#5C3A22", mid: "#3A2418", shadow: "#1F1006", outline: "#0E0501", brow: "#1F1006" },
45
+ { name: "chestnut", hl: "#A05A2C", mid: "#7B4F2A", shadow: "#5C3A22", outline: "#2A1A0A", brow: "#5C3A22" },
46
+ { name: "auburn", hl: "#F5904A", mid: "#E26336", shadow: "#A8482A", outline: "#5A1808", brow: "#A8482A" },
47
+ { name: "blonde", hl: "#FFE072", mid: "#F2C037", shadow: "#C99826", outline: "#7A5A14", brow: "#A07820" },
48
+ { name: "silver", hl: "#FFFFFF", mid: "#C8C5BE", shadow: "#9C9890", outline: "#3A3A3A", brow: "#6E665F" },
49
+ { name: "white", hl: "#FFFFFF", mid: "#F0EDE5", shadow: "#DAD5C8", outline: "#6E665F", brow: "#DAD5C8" },
50
+ { name: "red", hl: "#FF6A38", mid: "#D44820", shadow: "#A8281A", outline: "#5A1808", brow: "#A8281A" },
51
+ { name: "plum", hl: "#A88BD1", mid: "#7B5AA8", shadow: "#5A3D8F", outline: "#2A1A4A", brow: "#5A3D8F" },
52
+ { name: "navy", hl: "#3E78C8", mid: "#2D3E66", shadow: "#1A2540", outline: "#0A1228", brow: "#1A2540" },
53
+ { name: "ginger", hl: "#F5A05A", mid: "#C46A2C", shadow: "#8E4818", outline: "#3F1F08", brow: "#8E4818" },
54
+ { name: "mint", hl: "#A8E5C8", mid: "#5DBE9A", shadow: "#2F7A60", outline: "#0E3A28", brow: "#2F7A60" },
55
+ ];
56
+
57
+ // Eye iris colors (shown when chance hits)
58
+ const IRIS = [
59
+ "#3E78C8", // sky blue
60
+ "#7BA0E8", // pale blue
61
+ "#5A8A4D", // green
62
+ "#7A5A3A", // hazel
63
+ "#C99826", // amber
64
+ "#6A9B97", // cyan
65
+ "#5C3A22", // brown
66
+ ];
67
+
68
+ // Glasses · 4 distinct shapes + 5 frame colors
69
+ const GLASSES_FRAMES = [
70
+ { rim: "#1A1A1A", hi: "#3A3A3A", name: "black" },
71
+ { rim: "#2C4AAB", hi: "#7BA0E8", name: "royal-blue" },
72
+ { rim: "#A87818", hi: "#F2C037", name: "gold" },
73
+ { rim: "#7A4838", hi: "#A86B47", name: "tortoise" },
74
+ { rim: "#9C9890", hi: "#E0DDD3", name: "silver" },
75
+ ];
76
+
77
+ // Beanie palettes (multi-tone)
78
+ const BEANIES = [
79
+ { name: "red", hl: "#F5604A", mid: "#E03020", shadow: "#A82820", outline: "#5A0E08" },
80
+ { name: "yellow", hl: "#FFE560", mid: "#FFC830", shadow: "#C98818", outline: "#7A4F0A" },
81
+ { name: "navy", hl: "#3E78C8", mid: "#2D3E66", shadow: "#1A2540", outline: "#0A1228" },
82
+ { name: "forest", hl: "#5DBE3F", mid: "#2F7A24", shadow: "#1A4818", outline: "#0E2A0E" },
83
+ { name: "plum", hl: "#A88BD1", mid: "#7B5AA8", shadow: "#5A3D8F", outline: "#2A1A4A" },
84
+ { name: "charcoal",hl: "#9C948C", mid: "#5A5A5A", shadow: "#3A3A3A", outline: "#1A1A1A" },
85
+ ];
86
+
87
+ // ── Pixel grid + RNG ─────────────────────────────────────
88
+ function makeGrid() {
89
+ const g = new Array(H);
90
+ for (let y = 0; y < H; y++) g[y] = new Array(W).fill(null);
91
+ return g;
92
+ }
93
+ function px(grid, x, y, color) {
94
+ if (x < 0 || x >= W || y < 0 || y >= H) return;
95
+ grid[y][x] = color;
96
+ }
97
+ function fillRect(grid, x, y, w, h, color) {
98
+ for (let dy = 0; dy < h; dy++) {
99
+ for (let dx = 0; dx < w; dx++) px(grid, x + dx, y + dy, color);
100
+ }
101
+ }
102
+
103
+ function makeRng(seed) {
104
+ let s = 0;
105
+ const str = String(seed || "default");
106
+ for (let i = 0; i < str.length; i++) s = (s * 31 + str.charCodeAt(i)) >>> 0;
107
+ return function () {
108
+ s = (s * 1664525 + 1013904223) >>> 0;
109
+ return s / 0xFFFFFFFF;
110
+ };
111
+ }
112
+ function pick(rng, arr) { return arr[Math.floor(rng() * arr.length)]; }
113
+ function chance(rng, p) { return rng() < p; }
114
+ function weighted(rng, items, weights) {
115
+ let total = 0;
116
+ for (const w of weights) total += w;
117
+ let r = rng() * total;
118
+ for (let i = 0; i < items.length; i++) {
119
+ r -= weights[i];
120
+ if (r <= 0) return items[i];
121
+ }
122
+ return items[items.length - 1];
123
+ }
124
+
125
+ // ── ROUNDED HEAD · skin fill + outline ───────────────────
126
+ // Shape:
127
+ // y=10: 22w (top chamfer) x=13..34
128
+ // y=11: 26w x=11..36
129
+ // y=12: 30w x=9..38
130
+ // y=13..29: full 30w main x=9..38
131
+ // y=30: 30w
132
+ // y=31: 26w (chin chamfer)
133
+ // y=32: 22w (chin tip)
134
+ function drawHead(grid, skin) {
135
+ // outline silhouette
136
+ fillRect(grid, 13, 10, 22, 1, skin.outline);
137
+ fillRect(grid, 11, 11, 2, 1, skin.outline);
138
+ fillRect(grid, 35, 11, 2, 1, skin.outline);
139
+ fillRect(grid, 9, 12, 2, 1, skin.outline);
140
+ fillRect(grid, 37, 12, 2, 1, skin.outline);
141
+ fillRect(grid, 8, 13, 1, 17, skin.outline);
142
+ fillRect(grid, 39, 13, 1, 17, skin.outline);
143
+ fillRect(grid, 9, 30, 2, 1, skin.outline);
144
+ fillRect(grid, 37, 30, 2, 1, skin.outline);
145
+ fillRect(grid, 11, 31, 2, 1, skin.outline);
146
+ fillRect(grid, 35, 31, 2, 1, skin.outline);
147
+ fillRect(grid, 13, 32, 22, 1, skin.outline);
148
+ // skin fill (rounded interior)
149
+ fillRect(grid, 13, 10, 22, 1, skin.mid);
150
+ fillRect(grid, 11, 11, 26, 1, skin.mid);
151
+ fillRect(grid, 9, 12, 30, 1, skin.mid);
152
+ fillRect(grid, 9, 13, 30, 17, skin.mid);
153
+ fillRect(grid, 9, 30, 30, 1, skin.mid);
154
+ fillRect(grid, 11, 31, 26, 1, skin.mid);
155
+ fillRect(grid, 13, 32, 22, 1, skin.mid);
156
+ // forehead highlight
157
+ fillRect(grid, 13, 11, 22, 2, skin.hl);
158
+ fillRect(grid, 14, 12, 20, 1, skin.hl);
159
+ // temple shadow
160
+ fillRect(grid, 9, 18, 2, 9, skin.deep);
161
+ fillRect(grid, 37, 18, 2, 9, skin.deep);
162
+ // cheekbone highlight
163
+ fillRect(grid, 11, 22, 3, 3, skin.hl);
164
+ fillRect(grid, 34, 22, 3, 3, skin.hl);
165
+ // jaw shadow
166
+ fillRect(grid, 11, 27, 26, 2, skin.shadow);
167
+ fillRect(grid, 13, 29, 22, 2, skin.deep);
168
+ }
169
+
170
+ // ── HAIR STYLES ──────────────────────────────────────────
171
+ function drawShortHair(grid, hair) {
172
+ fillRect(grid, 14, 3, 20, 1, hair.outline);
173
+ fillRect(grid, 11, 4, 26, 2, hair.shadow);
174
+ fillRect(grid, 9, 6, 30, 2, hair.shadow);
175
+ fillRect(grid, 9, 8, 30, 2, hair.mid);
176
+ // sideburns (rounded around head edge)
177
+ fillRect(grid, 9, 10, 2, 3, hair.shadow);
178
+ fillRect(grid, 37, 10, 2, 3, hair.shadow);
179
+ fillRect(grid, 11, 10, 2, 2, hair.shadow);
180
+ fillRect(grid, 35, 10, 2, 2, hair.shadow);
181
+ // sheen highlights
182
+ fillRect(grid, 14, 5, 6, 1, hair.hl);
183
+ fillRect(grid, 24, 5, 6, 1, hair.hl);
184
+ fillRect(grid, 11, 7, 5, 1, hair.hl);
185
+ fillRect(grid, 28, 7, 5, 1, hair.hl);
186
+ }
187
+
188
+ function drawSidePartHair(grid, hair) {
189
+ fillRect(grid, 12, 3, 24, 1, hair.outline);
190
+ fillRect(grid, 10, 4, 28, 1, hair.shadow);
191
+ fillRect(grid, 9, 5, 30, 1, hair.shadow);
192
+ fillRect(grid, 9, 6, 30, 2, hair.mid);
193
+ fillRect(grid, 9, 8, 30, 2, hair.mid);
194
+ fillRect(grid, 9, 10, 2, 3, hair.shadow);
195
+ fillRect(grid, 37, 10, 2, 3, hair.shadow);
196
+ fillRect(grid, 11, 10, 2, 2, hair.shadow);
197
+ fillRect(grid, 35, 10, 2, 2, hair.shadow);
198
+ // side parting line (clear strip)
199
+ fillRect(grid, 16, 5, 1, 5, hair.hl);
200
+ fillRect(grid, 17, 5, 6, 1, hair.hl);
201
+ fillRect(grid, 25, 5, 5, 1, hair.hl);
202
+ }
203
+
204
+ function drawLongHair(grid, hair) {
205
+ // top mass
206
+ fillRect(grid, 11, 2, 26, 1, hair.shadow);
207
+ fillRect(grid, 9, 3, 30, 1, hair.mid);
208
+ fillRect(grid, 8, 4, 32, 2, hair.mid);
209
+ fillRect(grid, 7, 6, 34, 3, hair.mid);
210
+ fillRect(grid, 7, 9, 34, 2, hair.shadow);
211
+ // side hair flowing past head
212
+ fillRect(grid, 6, 11, 3, 20, hair.mid);
213
+ fillRect(grid, 39, 11, 3, 20, hair.mid);
214
+ fillRect(grid, 5, 14, 2, 17, hair.shadow);
215
+ fillRect(grid, 41, 14, 2, 17, hair.shadow);
216
+ // outline
217
+ fillRect(grid, 4, 17, 1, 14, hair.outline);
218
+ fillRect(grid, 43, 17, 1, 14, hair.outline);
219
+ fillRect(grid, 11, 2, 1, 9, hair.outline);
220
+ fillRect(grid, 36, 2, 1, 9, hair.outline);
221
+ // highlights
222
+ fillRect(grid, 13, 4, 4, 1, hair.hl);
223
+ fillRect(grid, 22, 4, 5, 1, hair.hl);
224
+ fillRect(grid, 32, 4, 3, 1, hair.hl);
225
+ }
226
+
227
+ function drawCurlyHair(grid, hair) {
228
+ // top curl bumps (5 separate clusters)
229
+ fillRect(grid, 11, 2, 3, 1, hair.outline);
230
+ fillRect(grid, 16, 2, 3, 1, hair.outline);
231
+ fillRect(grid, 22, 2, 3, 1, hair.outline);
232
+ fillRect(grid, 28, 2, 3, 1, hair.outline);
233
+ fillRect(grid, 33, 2, 3, 1, hair.outline);
234
+ fillRect(grid, 9, 3, 30, 1, hair.shadow);
235
+ fillRect(grid, 8, 4, 32, 2, hair.shadow);
236
+ fillRect(grid, 7, 6, 34, 3, hair.mid);
237
+ fillRect(grid, 7, 9, 34, 2, hair.hl);
238
+ // side curls flowing past head
239
+ fillRect(grid, 6, 11, 3, 18, hair.mid);
240
+ fillRect(grid, 39, 11, 3, 18, hair.mid);
241
+ fillRect(grid, 5, 14, 2, 14, hair.hl);
242
+ fillRect(grid, 41, 14, 2, 14, hair.hl);
243
+ // curl outline
244
+ fillRect(grid, 4, 14, 1, 14, hair.outline);
245
+ fillRect(grid, 43, 14, 1, 14, hair.outline);
246
+ fillRect(grid, 5, 28, 2, 3, hair.outline);
247
+ fillRect(grid, 41, 28, 2, 3, hair.outline);
248
+ // texture bumps on sides
249
+ px(grid, 5, 17, hair.hl);
250
+ px(grid, 5, 19, hair.hl);
251
+ px(grid, 5, 22, hair.hl);
252
+ px(grid, 5, 24, hair.hl);
253
+ px(grid, 42, 17, hair.hl);
254
+ px(grid, 42, 19, hair.hl);
255
+ px(grid, 42, 22, hair.hl);
256
+ px(grid, 42, 24, hair.hl);
257
+ }
258
+
259
+ function drawAfroHair(grid, hair) {
260
+ // big round puffy mass
261
+ fillRect(grid, 13, 1, 22, 1, hair.outline);
262
+ fillRect(grid, 11, 2, 26, 2, hair.shadow);
263
+ fillRect(grid, 8, 4, 32, 2, hair.shadow);
264
+ fillRect(grid, 6, 6, 36, 3, hair.mid);
265
+ fillRect(grid, 6, 9, 36, 2, hair.mid);
266
+ fillRect(grid, 5, 11, 4, 6, hair.mid);
267
+ fillRect(grid, 39, 11, 4, 6, hair.mid);
268
+ // bumps for texture
269
+ fillRect(grid, 4, 13, 1, 4, hair.outline);
270
+ fillRect(grid, 43, 13, 1, 4, hair.outline);
271
+ fillRect(grid, 5, 17, 4, 1, hair.outline);
272
+ fillRect(grid, 39, 17, 4, 1, hair.outline);
273
+ // highlights (catching light at the top)
274
+ fillRect(grid, 14, 3, 4, 1, hair.hl);
275
+ fillRect(grid, 22, 3, 4, 1, hair.hl);
276
+ fillRect(grid, 30, 3, 4, 1, hair.hl);
277
+ }
278
+
279
+ function drawBunHair(grid, hair) {
280
+ // base hair around head
281
+ fillRect(grid, 12, 4, 24, 1, hair.outline);
282
+ fillRect(grid, 10, 5, 28, 1, hair.shadow);
283
+ fillRect(grid, 9, 6, 30, 2, hair.mid);
284
+ fillRect(grid, 9, 8, 30, 2, hair.mid);
285
+ fillRect(grid, 9, 10, 2, 3, hair.shadow);
286
+ fillRect(grid, 37, 10, 2, 3, hair.shadow);
287
+ fillRect(grid, 11, 10, 2, 2, hair.shadow);
288
+ fillRect(grid, 35, 10, 2, 2, hair.shadow);
289
+ // bun on top
290
+ fillRect(grid, 19, 0, 10, 1, hair.outline);
291
+ fillRect(grid, 17, 1, 14, 2, hair.shadow);
292
+ fillRect(grid, 18, 1, 12, 1, hair.mid);
293
+ fillRect(grid, 18, 3, 12, 1, hair.shadow);
294
+ fillRect(grid, 20, 1, 4, 1, hair.hl);
295
+ }
296
+
297
+ // Bald = nothing drawn (head shows through)
298
+ function drawBaldHair(_grid, _hair) {}
299
+
300
+ const HAIR_STYLES = {
301
+ short: drawShortHair,
302
+ sidepart: drawSidePartHair,
303
+ long: drawLongHair,
304
+ curly: drawCurlyHair,
305
+ afro: drawAfroHair,
306
+ bun: drawBunHair,
307
+ bald: drawBaldHair,
308
+ };
309
+
310
+ // ── ACCESSORIES ──────────────────────────────────────────
311
+ function drawBeanie(grid, beanie) {
312
+ fillRect(grid, 14, 5, 20, 1, beanie.outline);
313
+ fillRect(grid, 11, 6, 26, 1, beanie.shadow);
314
+ fillRect(grid, 9, 7, 30, 1, beanie.shadow);
315
+ fillRect(grid, 9, 8, 30, 3, beanie.mid);
316
+ fillRect(grid, 9, 11, 30, 2, beanie.hl);
317
+ fillRect(grid, 9, 13, 30, 3, beanie.shadow);
318
+ // ribbing bars
319
+ for (const x of [11, 14, 17, 20, 23, 26, 29, 32, 35]) {
320
+ fillRect(grid, x, 13, 1, 3, beanie.outline);
321
+ }
322
+ fillRect(grid, 9, 6, 1, 10, beanie.outline);
323
+ fillRect(grid, 38, 6, 1, 10, beanie.outline);
324
+ fillRect(grid, 13, 9, 4, 1, beanie.hl);
325
+ fillRect(grid, 22, 9, 4, 1, beanie.hl);
326
+ // pom-pom
327
+ fillRect(grid, 22, 1, 4, 1, "#8E8675");
328
+ fillRect(grid, 21, 2, 6, 2, "#FFFFFF");
329
+ fillRect(grid, 22, 4, 4, 1, "#E5E2D8");
330
+ px(grid, 20, 3, "#B8B0A0");
331
+ px(grid, 27, 3, "#B8B0A0");
332
+ }
333
+
334
+ function drawLaurel(grid) {
335
+ // top leaves
336
+ fillRect(grid, 22, 2, 4, 1, "#1A4818");
337
+ fillRect(grid, 22, 3, 4, 1, "#2F7A24");
338
+ fillRect(grid, 23, 4, 2, 1, "#5DBE3F");
339
+ fillRect(grid, 16, 3, 3, 1, "#1A4818");
340
+ fillRect(grid, 15, 4, 4, 1, "#2F7A24");
341
+ fillRect(grid, 16, 5, 3, 1, "#4DA53D");
342
+ fillRect(grid, 29, 3, 3, 1, "#1A4818");
343
+ fillRect(grid, 29, 4, 4, 1, "#2F7A24");
344
+ fillRect(grid, 29, 5, 3, 1, "#4DA53D");
345
+ fillRect(grid, 9, 5, 3, 1, "#1A4818");
346
+ fillRect(grid, 9, 6, 4, 1, "#2F7A24");
347
+ fillRect(grid, 10, 7, 3, 1, "#4DA53D");
348
+ fillRect(grid, 36, 5, 3, 1, "#1A4818");
349
+ fillRect(grid, 35, 6, 4, 1, "#2F7A24");
350
+ fillRect(grid, 35, 7, 3, 1, "#4DA53D");
351
+ // circlet band
352
+ fillRect(grid, 8, 8, 32, 1, "#0E2A0E");
353
+ fillRect(grid, 7, 9, 34, 1, "#1A4818");
354
+ fillRect(grid, 7, 10, 34, 2, "#2F7A24");
355
+ fillRect(grid, 7, 12, 34, 1, "#4DA53D");
356
+ fillRect(grid, 9, 11, 3, 1, "#5DBE3F");
357
+ fillRect(grid, 14, 11, 3, 1, "#7CD850");
358
+ fillRect(grid, 20, 11, 3, 1, "#5DBE3F");
359
+ fillRect(grid, 26, 11, 3, 1, "#7CD850");
360
+ fillRect(grid, 32, 11, 3, 1, "#5DBE3F");
361
+ // berries
362
+ for (const bx of [13, 22, 31]) {
363
+ fillRect(grid, bx, 9, 2, 2, "#7A5A14");
364
+ fillRect(grid, bx, 9, 2, 1, "#F2C037");
365
+ px(grid, bx, 9, "#FFE072");
366
+ }
367
+ }
368
+
369
+ function drawHeadband(grid, color) {
370
+ fillRect(grid, 9, 10, 30, 2, color);
371
+ fillRect(grid, 9, 10, 30, 1, "#FFFFFF");
372
+ }
373
+
374
+ function drawHat(grid, hair) {
375
+ // top brim
376
+ fillRect(grid, 11, 4, 26, 1, hair.outline);
377
+ fillRect(grid, 9, 5, 30, 1, hair.shadow);
378
+ // hat body
379
+ fillRect(grid, 12, 6, 24, 5, hair.mid);
380
+ fillRect(grid, 12, 6, 24, 1, hair.hl);
381
+ // hat band
382
+ fillRect(grid, 12, 9, 24, 1, hair.outline);
383
+ // brim wide
384
+ fillRect(grid, 6, 11, 36, 1, hair.outline);
385
+ fillRect(grid, 7, 12, 34, 1, hair.shadow);
386
+ }
387
+
388
+ function drawHood(grid, color) {
389
+ fillRect(grid, 9, 4, 30, 1, color);
390
+ fillRect(grid, 7, 5, 34, 2, color);
391
+ fillRect(grid, 6, 7, 36, 4, color);
392
+ fillRect(grid, 5, 11, 4, 18, color);
393
+ fillRect(grid, 39, 11, 4, 18, color);
394
+ // outline
395
+ fillRect(grid, 5, 4, 1, 25, "#1A1A1A");
396
+ fillRect(grid, 42, 4, 1, 25, "#1A1A1A");
397
+ // inner shadow
398
+ fillRect(grid, 9, 10, 2, 18, "#000000");
399
+ fillRect(grid, 37, 10, 2, 18, "#000000");
400
+ }
401
+
402
+ // ── EYEBROWS ─────────────────────────────────────────────
403
+ function drawEyebrows(grid, hair, kind) {
404
+ const c = hair.brow || hair.shadow;
405
+ if (kind === "bushy") {
406
+ fillRect(grid, 13, 19, 9, 1, c);
407
+ fillRect(grid, 26, 19, 9, 1, c);
408
+ fillRect(grid, 13, 20, 9, 1, hair.shadow);
409
+ fillRect(grid, 26, 20, 9, 1, hair.shadow);
410
+ // straggler hairs
411
+ px(grid, 14, 18, hair.shadow);
412
+ px(grid, 20, 18, hair.shadow);
413
+ px(grid, 28, 18, hair.shadow);
414
+ px(grid, 33, 18, hair.shadow);
415
+ } else if (kind === "sharp") {
416
+ fillRect(grid, 13, 19, 9, 1, c);
417
+ fillRect(grid, 26, 19, 9, 1, c);
418
+ fillRect(grid, 20, 20, 2, 1, c);
419
+ fillRect(grid, 26, 20, 2, 1, c);
420
+ } else if (kind === "raised") {
421
+ fillRect(grid, 13, 19, 7, 1, c);
422
+ fillRect(grid, 28, 19, 7, 1, c);
423
+ fillRect(grid, 28, 18, 3, 1, c); // right brow lifted
424
+ } else {
425
+ // "soft" default
426
+ fillRect(grid, 13, 19, 7, 1, c);
427
+ fillRect(grid, 28, 19, 7, 1, c);
428
+ }
429
+ }
430
+
431
+ // ── EYES ─────────────────────────────────────────────────
432
+ function drawEyes(grid, irisColor, hasLashes) {
433
+ // sclera
434
+ fillRect(grid, 14, 21, 6, 3, "#FFFFFF");
435
+ fillRect(grid, 28, 21, 6, 3, "#FFFFFF");
436
+ // pupils
437
+ fillRect(grid, 15, 21, 4, 3, "#1F1F1F");
438
+ fillRect(grid, 29, 21, 4, 3, "#1F1F1F");
439
+ // iris (smaller circle inside pupil)
440
+ if (irisColor) {
441
+ fillRect(grid, 15, 21, 2, 2, irisColor);
442
+ fillRect(grid, 29, 21, 2, 2, irisColor);
443
+ }
444
+ // catchlight
445
+ px(grid, 15, 21, "#FFFFFF");
446
+ px(grid, 29, 21, "#FFFFFF");
447
+ // upper lid line
448
+ fillRect(grid, 14, 20, 6, 1, "#5A3520");
449
+ fillRect(grid, 28, 20, 6, 1, "#5A3520");
450
+ // lashes
451
+ if (hasLashes) {
452
+ px(grid, 13, 20, "#1F1F1F");
453
+ px(grid, 19, 20, "#1F1F1F");
454
+ px(grid, 27, 20, "#1F1F1F");
455
+ px(grid, 33, 20, "#1F1F1F");
456
+ }
457
+ }
458
+
459
+ // ── GLASSES ──────────────────────────────────────────────
460
+ function drawSquareGlasses(grid, frame) {
461
+ // left lens outline
462
+ fillRect(grid, 11, 18, 11, 1, frame.rim);
463
+ fillRect(grid, 11, 24, 11, 1, frame.rim);
464
+ fillRect(grid, 11, 19, 1, 5, frame.rim);
465
+ fillRect(grid, 21, 19, 1, 5, frame.rim);
466
+ // right lens
467
+ fillRect(grid, 26, 18, 11, 1, frame.rim);
468
+ fillRect(grid, 26, 24, 11, 1, frame.rim);
469
+ fillRect(grid, 26, 19, 1, 5, frame.rim);
470
+ fillRect(grid, 36, 19, 1, 5, frame.rim);
471
+ // bridge
472
+ fillRect(grid, 22, 20, 4, 1, frame.rim);
473
+ // temple arms
474
+ fillRect(grid, 9, 20, 2, 1, frame.rim);
475
+ fillRect(grid, 37, 20, 2, 1, frame.rim);
476
+ // lens highlights
477
+ fillRect(grid, 13, 19, 3, 1, frame.hi);
478
+ fillRect(grid, 28, 19, 3, 1, frame.hi);
479
+ }
480
+
481
+ function drawRoundGlasses(grid, frame) {
482
+ // big round Lennon-style
483
+ // left lens (circular outline)
484
+ fillRect(grid, 13, 20, 6, 1, frame.rim);
485
+ fillRect(grid, 13, 24, 6, 1, frame.rim);
486
+ fillRect(grid, 12, 21, 1, 3, frame.rim);
487
+ fillRect(grid, 19, 21, 1, 3, frame.rim);
488
+ // right lens
489
+ fillRect(grid, 28, 20, 6, 1, frame.rim);
490
+ fillRect(grid, 28, 24, 6, 1, frame.rim);
491
+ fillRect(grid, 27, 21, 1, 3, frame.rim);
492
+ fillRect(grid, 34, 21, 1, 3, frame.rim);
493
+ // bridge
494
+ fillRect(grid, 20, 22, 7, 1, frame.rim);
495
+ // temple arms
496
+ fillRect(grid, 9, 22, 3, 1, frame.rim);
497
+ fillRect(grid, 35, 22, 3, 1, frame.rim);
498
+ // lens shine
499
+ px(grid, 14, 21, frame.hi);
500
+ px(grid, 29, 21, frame.hi);
501
+ }
502
+
503
+ function drawWireRimGlasses(grid, frame) {
504
+ // thin gold half-moon scholar style
505
+ fillRect(grid, 14, 18, 6, 1, frame.rim);
506
+ fillRect(grid, 14, 22, 6, 1, frame.rim);
507
+ fillRect(grid, 13, 19, 1, 3, frame.rim);
508
+ fillRect(grid, 20, 19, 1, 3, frame.rim);
509
+ fillRect(grid, 28, 18, 6, 1, frame.rim);
510
+ fillRect(grid, 28, 22, 6, 1, frame.rim);
511
+ fillRect(grid, 27, 19, 1, 3, frame.rim);
512
+ fillRect(grid, 34, 19, 1, 3, frame.rim);
513
+ fillRect(grid, 21, 20, 6, 1, frame.rim);
514
+ fillRect(grid, 11, 20, 2, 1, frame.rim);
515
+ fillRect(grid, 35, 20, 2, 1, frame.rim);
516
+ // shine
517
+ fillRect(grid, 15, 18, 2, 1, frame.hi);
518
+ fillRect(grid, 29, 18, 2, 1, frame.hi);
519
+ px(grid, 14, 19, frame.hi);
520
+ px(grid, 28, 19, frame.hi);
521
+ }
522
+
523
+ function drawTortoiseGlasses(grid, frame) {
524
+ // rectangular thick frames
525
+ fillRect(grid, 11, 18, 11, 1, "#2A1A12");
526
+ fillRect(grid, 11, 23, 11, 1, "#2A1A12");
527
+ fillRect(grid, 11, 19, 1, 4, "#2A1A12");
528
+ fillRect(grid, 21, 19, 1, 4, "#2A1A12");
529
+ fillRect(grid, 26, 18, 11, 1, "#2A1A12");
530
+ fillRect(grid, 26, 23, 11, 1, "#2A1A12");
531
+ fillRect(grid, 26, 19, 1, 4, "#2A1A12");
532
+ fillRect(grid, 36, 19, 1, 4, "#2A1A12");
533
+ fillRect(grid, 12, 18, 9, 1, frame.rim);
534
+ fillRect(grid, 27, 18, 9, 1, frame.rim);
535
+ fillRect(grid, 12, 19, 1, 4, frame.rim);
536
+ fillRect(grid, 20, 19, 1, 4, frame.rim);
537
+ fillRect(grid, 27, 19, 1, 4, frame.rim);
538
+ fillRect(grid, 35, 19, 1, 4, frame.rim);
539
+ fillRect(grid, 22, 20, 4, 1, frame.rim);
540
+ fillRect(grid, 9, 20, 2, 1, "#2A1A12");
541
+ fillRect(grid, 37, 20, 2, 1, "#2A1A12");
542
+ fillRect(grid, 14, 19, 2, 1, frame.hi);
543
+ fillRect(grid, 29, 19, 2, 1, frame.hi);
544
+ }
545
+
546
+ // ── NOSE ─────────────────────────────────────────────────
547
+ function drawNose(grid, skin) {
548
+ fillRect(grid, 22, 20, 4, 6, skin.mid);
549
+ fillRect(grid, 22, 20, 1, 6, skin.hl);
550
+ fillRect(grid, 22, 20, 1, 3, skin.hl);
551
+ fillRect(grid, 25, 22, 1, 4, skin.shadow);
552
+ fillRect(grid, 22, 26, 4, 1, skin.deep);
553
+ px(grid, 21, 26, skin.deep);
554
+ px(grid, 26, 26, skin.deep);
555
+ px(grid, 23, 26, skin.outline);
556
+ }
557
+
558
+ // ── MOUTH ────────────────────────────────────────────────
559
+ function drawMouth(grid, kind) {
560
+ if (kind === "smile") {
561
+ px(grid, 14, 27, "#A85040");
562
+ px(grid, 33, 27, "#A85040");
563
+ fillRect(grid, 15, 28, 18, 1, "#A85040");
564
+ fillRect(grid, 16, 29, 16, 1, "#D67F5C");
565
+ fillRect(grid, 22, 28, 4, 1, "#FFFFFF"); // tooth shine
566
+ } else if (kind === "frown") {
567
+ fillRect(grid, 18, 28, 12, 1, "#5A3520");
568
+ px(grid, 17, 29, "#5A3520");
569
+ px(grid, 30, 29, "#5A3520");
570
+ } else if (kind === "smirk") {
571
+ fillRect(grid, 17, 28, 14, 1, "#7A4838");
572
+ fillRect(grid, 29, 29, 3, 1, "#7A4838");
573
+ fillRect(grid, 18, 29, 13, 1, "#D67F5C");
574
+ } else {
575
+ // neutral
576
+ fillRect(grid, 18, 28, 12, 1, "#5A4838");
577
+ }
578
+ }
579
+
580
+ // ── BEARDS ───────────────────────────────────────────────
581
+ function drawBeard(grid, kind) {
582
+ if (kind === "white-full") {
583
+ // mustache layer
584
+ fillRect(grid, 13, 27, 9, 1, "#8E8675");
585
+ fillRect(grid, 26, 27, 9, 1, "#8E8675");
586
+ fillRect(grid, 12, 28, 11, 1, "#DAD5C8");
587
+ fillRect(grid, 25, 28, 11, 1, "#DAD5C8");
588
+ fillRect(grid, 11, 29, 13, 1, "#FFFFFF");
589
+ fillRect(grid, 24, 29, 13, 1, "#FFFFFF");
590
+ // beard cascading
591
+ fillRect(grid, 9, 30, 1, 14, "#4D4538");
592
+ fillRect(grid, 38, 30, 1, 14, "#4D4538");
593
+ fillRect(grid, 11, 44, 2, 1, "#4D4538");
594
+ fillRect(grid, 35, 44, 2, 1, "#4D4538");
595
+ fillRect(grid, 13, 45, 22, 1, "#4D4538");
596
+ fillRect(grid, 10, 30, 28, 14, "#F0EDE5");
597
+ fillRect(grid, 13, 45, 22, 1, "#F0EDE5");
598
+ fillRect(grid, 11, 30, 26, 2, "#FFFFFF");
599
+ fillRect(grid, 10, 38, 28, 2, "#DAD5C8");
600
+ fillRect(grid, 10, 40, 28, 2, "#B8B0A0");
601
+ fillRect(grid, 10, 42, 28, 2, "#8E8675");
602
+ // strands
603
+ for (const sx of [13, 17, 20, 24, 27, 31, 34]) {
604
+ fillRect(grid, sx, 32, 1, 12, sx % 7 === 0 ? "#DAD5C8" : "#B8B0A0");
605
+ }
606
+ fillRect(grid, 15, 31, 1, 3, "#FFFFFF");
607
+ fillRect(grid, 22, 31, 1, 3, "#FFFFFF");
608
+ fillRect(grid, 29, 31, 1, 3, "#FFFFFF");
609
+ } else if (kind === "stubble") {
610
+ // scattered shadow pixels along jaw
611
+ for (let x = 12; x < 36; x += 2) {
612
+ if ((x % 3) !== 0) px(grid, x, 27, "#5A3520");
613
+ if ((x % 5) === 0) px(grid, x, 29, "#5A3520");
614
+ }
615
+ fillRect(grid, 12, 30, 24, 1, "#5A3520");
616
+ } else if (kind === "goatee") {
617
+ fillRect(grid, 19, 29, 10, 1, "#3A2418");
618
+ fillRect(grid, 20, 30, 8, 2, "#3A2418");
619
+ fillRect(grid, 21, 32, 6, 1, "#3A2418");
620
+ }
621
+ // null = no beard
622
+ }
623
+
624
+ // ── NECK STUB ────────────────────────────────────────────
625
+ function drawNeck(grid, skin) {
626
+ fillRect(grid, 19, 33, 10, 6, skin.mid);
627
+ fillRect(grid, 19, 33, 10, 1, skin.deep);
628
+ fillRect(grid, 19, 33, 1, 6, skin.deep);
629
+ fillRect(grid, 28, 33, 1, 6, skin.deep);
630
+ fillRect(grid, 20, 34, 8, 1, skin.hl);
631
+ // collar shadow at bottom (suggesting clothing off-frame)
632
+ for (let x = 14; x < 34; x++) px(grid, x, 39, skin.deep);
633
+ fillRect(grid, 13, 41, 22, 3, skin.outline);
634
+ }
635
+
636
+ // ── PLACEHOLDER (?) ──────────────────────────────────────
637
+ function drawPlaceholder(grid) {
638
+ const placeholderSkin = { hl: "#6E6C63", mid: "#5E5C53", shadow: "#48463F", deep: "#38362F", outline: "#1F1E18" };
639
+ drawHead(grid, placeholderSkin);
640
+ drawNeck(grid, placeholderSkin);
641
+ // "?" centered on the face
642
+ const q = [
643
+ " XXXX ",
644
+ " X X ",
645
+ " X ",
646
+ " XX ",
647
+ " X ",
648
+ " ",
649
+ " X ",
650
+ ];
651
+ const ox = 20, oy = 17;
652
+ for (let dy = 0; dy < q.length; dy++) {
653
+ for (let dx = 0; dx < q[dy].length; dx++) {
654
+ if (q[dy][dx] === "X") px(grid, ox + dx, oy + dy, "#1A1A18");
655
+ }
656
+ }
657
+ }
658
+
659
+ // ── MAIN GENERATE ────────────────────────────────────────
660
+ function generate(seed, opts) {
661
+ const placeholder = !!(opts && opts.placeholder);
662
+ const grid = makeGrid();
663
+
664
+ if (placeholder) {
665
+ drawPlaceholder(grid);
666
+ return svgFromGrid(grid);
667
+ }
668
+
669
+ const rng = makeRng(seed);
670
+
671
+ // ── Pick features deterministically ────────────────
672
+ const skin = pick(rng, SKIN);
673
+ const hair = pick(rng, HAIR);
674
+
675
+ // Hair style — bald rare, beanie/laurel/hat are accessories that
676
+ // OVERRIDE hair, so they're picked separately.
677
+ const hairStyles = ["short", "sidepart", "long", "curly", "afro", "bun"];
678
+ const hairWeights = [4, 4, 2, 3, 1, 1];
679
+ let hairStyle = weighted(rng, hairStyles, hairWeights);
680
+ if (chance(rng, 0.05)) hairStyle = "bald";
681
+
682
+ // Accessory roll · most agents have nothing on top.
683
+ const accessoryRoll = rng();
684
+ let accessory = null;
685
+ if (accessoryRoll < 0.10) accessory = "beanie";
686
+ else if (accessoryRoll < 0.16) accessory = "laurel";
687
+ else if (accessoryRoll < 0.22) accessory = "headband";
688
+ else if (accessoryRoll < 0.27) accessory = "hat";
689
+ else if (accessoryRoll < 0.30) accessory = "hood";
690
+
691
+ // Eyewear — varied, slight bias toward having glasses.
692
+ const eyewearRoll = rng();
693
+ let eyewear = null;
694
+ if (eyewearRoll < 0.20) eyewear = "square";
695
+ else if (eyewearRoll < 0.36) eyewear = "round";
696
+ else if (eyewearRoll < 0.48) eyewear = "wire";
697
+ else if (eyewearRoll < 0.58) eyewear = "tortoise";
698
+ const glassesFrame = pick(rng, GLASSES_FRAMES);
699
+
700
+ // Eyebrow style
701
+ const browKinds = ["soft", "sharp", "bushy", "raised"];
702
+ const browWeights = [4, 3, 2, 2];
703
+ const brow = weighted(rng, browKinds, browWeights);
704
+
705
+ // Mouth
706
+ const mouth = weighted(rng, ["smile", "smile", "neutral", "frown", "smirk"], [3, 2, 3, 2, 2]);
707
+
708
+ // Beard (only for some agents — bias to none)
709
+ const beardKinds = [null, null, null, null, "stubble", "goatee", "white-full"];
710
+ const beard = pick(rng, beardKinds);
711
+ const mouthCovered = beard === "white-full";
712
+
713
+ // Iris color
714
+ const irisColor = chance(rng, 0.55) ? pick(rng, IRIS) : null;
715
+ const hasLashes = chance(rng, 0.30);
716
+
717
+ // ── Compose ───────────────────────────────────────
718
+ drawHead(grid, skin);
719
+
720
+ // Hair drawn first (so the head shape covers the face area)
721
+ if (accessory === "beanie") {
722
+ drawBeanie(grid, pick(rng, BEANIES));
723
+ } else if (accessory === "hat") {
724
+ drawHat(grid, hair);
725
+ } else if (accessory === "hood") {
726
+ drawHood(grid, hair);
727
+ } else {
728
+ const drawHair = HAIR_STYLES[hairStyle] || HAIR_STYLES.short;
729
+ drawHair(grid, hair);
730
+ if (accessory === "laurel") drawLaurel(grid);
731
+ if (accessory === "headband") drawHeadband(grid, "#" + ((Math.floor(rng() * 0xFFFFFF)).toString(16)).padStart(6, "0"));
732
+ }
733
+
734
+ drawEyebrows(grid, hair, brow);
735
+
736
+ if (eyewear === "square") drawSquareGlasses(grid, glassesFrame);
737
+ else if (eyewear === "round") drawRoundGlasses(grid, glassesFrame);
738
+ else if (eyewear === "wire") drawWireRimGlasses(grid, glassesFrame);
739
+ else if (eyewear === "tortoise") drawTortoiseGlasses(grid, glassesFrame);
740
+
741
+ drawEyes(grid, irisColor, hasLashes);
742
+ drawNose(grid, skin);
743
+ if (!mouthCovered) drawMouth(grid, mouth);
744
+ drawBeard(grid, beard);
745
+ drawNeck(grid, skin);
746
+
747
+ return svgFromGrid(grid);
748
+ }
749
+
750
+ function svgFromGrid(grid) {
751
+ let rects = "";
752
+ for (let y = 0; y < H; y++) {
753
+ for (let x = 0; x < W; x++) {
754
+ const c = grid[y][x];
755
+ if (c) rects += `<rect x="${x}" y="${y}" width="1" height="1" fill="${c}"/>`;
756
+ }
757
+ }
758
+ return `<svg viewBox="0 -4 48 56" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" preserveAspectRatio="xMidYMid meet">${rects}</svg>`;
759
+ }
760
+
761
+ function generateDataUrl(seed, opts) {
762
+ return "data:image/svg+xml;utf8," + encodeURIComponent(generate(seed, opts));
763
+ }
764
+
765
+ function randomSeed() {
766
+ return Math.random().toString(36).slice(2, 12);
767
+ }
768
+
769
+ /** Wire a UI in one line:
770
+ * AvatarSkill.attach({ frame, button, onSeed }) */
771
+ function attach({ frame, button, onSeed, initialSeed }) {
772
+ if (!frame || !button) return;
773
+ let seed = initialSeed || null;
774
+ function paintFrame(nextSeed, opts) {
775
+ seed = nextSeed;
776
+ frame.innerHTML = generate(nextSeed, opts);
777
+ if (typeof onSeed === "function") onSeed(seed);
778
+ }
779
+ if (seed) paintFrame(seed);
780
+ else paintFrame("__placeholder__", { placeholder: true });
781
+ button.addEventListener("click", (e) => {
782
+ e.preventDefault();
783
+ paintFrame(randomSeed());
784
+ });
785
+ }
786
+
787
+ window.AvatarSkill = {
788
+ generate,
789
+ generateDataUrl,
790
+ randomSeed,
791
+ attach,
792
+ };
793
+ })();