privateboard 0.1.37 → 0.1.40
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/dist/boot.js +1415 -91
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +1415 -91
- package/dist/cli.js.map +1 -1
- package/dist/server.js +1271 -81
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/public/__avatar3d_test.html +156 -0
- package/public/adjourn-overlay.css +2 -2
- package/public/agent-overlay.css +27 -15
- package/public/agent-overlay.js +3 -1
- package/public/agent-profile.css +331 -41
- package/public/agent-profile.js +499 -75
- package/public/app-updater.css +1 -1
- package/public/app.js +2090 -547
- package/public/avatar-3d-snap.js +205 -0
- package/public/avatar-3d.js +792 -0
- package/public/avatar-customizer.html +274 -0
- package/public/avatar3d-editor.css +240 -0
- package/public/avatar3d-editor.js +481 -0
- package/public/avatars/3d/chair.png +0 -0
- package/public/avatars/3d/first-principles.png +0 -0
- package/public/avatars/3d/historian.png +0 -0
- package/public/avatars/3d/long-horizon.png +0 -0
- package/public/avatars/3d/phenomenologist.png +0 -0
- package/public/avatars/3d/socrates.png +0 -0
- package/public/avatars/3d/user-empathy.png +0 -0
- package/public/avatars/3d/value-investor.png +0 -0
- package/public/core-avatars.js +86 -0
- package/public/home-3d-loader.js +15 -4
- package/public/home-3d-mock.js +18 -7
- package/public/home.html +80 -18
- package/public/i18n.js +279 -4
- package/public/icons/avatar_1779855104027.glb +0 -0
- package/public/icons/logo.png +0 -0
- package/public/icons/new-style.glb +0 -0
- package/public/icons/new-style2.glb +0 -0
- package/public/icons/new-style3.glb +0 -0
- package/public/icons/new-style4.glb +0 -0
- package/public/icons/new-style5.glb +0 -0
- package/public/icons/office.glb +0 -0
- package/public/icons/stuff.glb +0 -0
- package/public/index.html +203 -182
- package/public/mention-picker.js +1 -1
- package/public/new-agent.css +7 -7
- package/public/new-agent.js +46 -20
- package/public/office-viewer.html +340 -0
- package/public/onboarding.css +5 -5
- package/public/quote-cta.css +5 -4
- package/public/quote-cta.js +50 -5
- package/public/room-settings.css +24 -9
- package/public/stuff-viewer.html +330 -0
- package/public/thread.css +1211 -0
- package/public/user-settings.css +16 -19
- package/public/user-settings.js +86 -78
- package/public/vendor/BufferGeometryUtils.js +1434 -0
- package/public/vendor/DRACOLoader.js +739 -0
- package/public/vendor/GLTFLoader.js +4860 -0
- package/public/vendor/RoomEnvironment.js +185 -0
- package/public/vendor/SkeletonUtils.js +496 -0
- package/public/vendor/draco/draco_decoder.js +34 -0
- package/public/vendor/draco/draco_decoder.wasm +0 -0
- package/public/vendor/draco/draco_encoder.js +33 -0
- package/public/vendor/draco/draco_wasm_wrapper.js +117 -0
- package/public/vendor/meshopt_decoder.module.js +196 -0
- package/public/voice-3d-banner.js +12 -0
- package/public/voice-3d.js +1407 -432
- package/public/voice-clone.css +875 -0
- package/public/voice-clone.js +1351 -0
- package/public/voice-replay.css +3 -3
- package/public/voice-replay.js +21 -0
- package/public/avatar-skill.js +0 -629
- package/public/icons/folded-sidebar.png +0 -0
package/public/voice-replay.css
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
.vr-kicker-glyph {
|
|
61
61
|
font-family: var(--font-human);
|
|
62
|
-
font-size:
|
|
62
|
+
font-size: 14px;
|
|
63
63
|
letter-spacing: 0;
|
|
64
64
|
}
|
|
65
65
|
.vr-head-actions {
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
}
|
|
275
275
|
.vr-avatar-placeholder {
|
|
276
276
|
font-family: var(--mono);
|
|
277
|
-
font-size:
|
|
277
|
+
font-size: 14px;
|
|
278
278
|
font-weight: 700;
|
|
279
279
|
color: var(--text-soft);
|
|
280
280
|
}
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
}
|
|
428
428
|
.vr-key-title {
|
|
429
429
|
font-family: var(--font-human, var(--mono));
|
|
430
|
-
font-size:
|
|
430
|
+
font-size: 14px;
|
|
431
431
|
font-weight: 700;
|
|
432
432
|
color: var(--text);
|
|
433
433
|
letter-spacing: -0.005em;
|
package/public/voice-replay.js
CHANGED
|
@@ -378,6 +378,15 @@
|
|
|
378
378
|
close();
|
|
379
379
|
return;
|
|
380
380
|
}
|
|
381
|
+
// Stop button mirrored into the room header (.head-actions) ·
|
|
382
|
+
// app.js renders it statically + toggles its visibility on the
|
|
383
|
+
// replay-* events, but the stop ACTION stays owned here so all
|
|
384
|
+
// replay controls funnel through close().
|
|
385
|
+
if (target.closest("[data-vr-header-stop]")) {
|
|
386
|
+
ev.preventDefault();
|
|
387
|
+
close();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
381
390
|
if (target.closest("[data-vr-inline-expand]")) {
|
|
382
391
|
ev.preventDefault();
|
|
383
392
|
toggleCollapsed();
|
|
@@ -405,6 +414,17 @@
|
|
|
405
414
|
else removeInlineExpand();
|
|
406
415
|
}
|
|
407
416
|
|
|
417
|
+
/** Force the floating panel into its collapsed posture (idempotent).
|
|
418
|
+
* Exposed on the public API so the recorder can tuck the player
|
|
419
|
+
* out of the captured stage region · the panel floats bottom-right
|
|
420
|
+
* over the stage, so leaving it expanded would bleed into the
|
|
421
|
+
* recording. Collapsing surfaces the inline controls in the
|
|
422
|
+
* input-bar (below the captured region) instead. */
|
|
423
|
+
function collapse() {
|
|
424
|
+
if (!STATE.overlay) return;
|
|
425
|
+
if (!STATE.overlay.classList.contains("is-collapsed")) toggleCollapsed();
|
|
426
|
+
}
|
|
427
|
+
|
|
408
428
|
/** Slot the inline replay control group into the bottom-bar
|
|
409
429
|
* action group right after the existing Voice Replay anchor.
|
|
410
430
|
* Three buttons: Next (skip current message), Pause/Resume
|
|
@@ -974,6 +994,7 @@
|
|
|
974
994
|
getActiveAudio: getActiveAudio,
|
|
975
995
|
getRoomId: getRoomId,
|
|
976
996
|
togglePause: togglePause,
|
|
997
|
+
collapse: collapse,
|
|
977
998
|
// Exposed for testing.
|
|
978
999
|
_internals: { buildPlaylist, PROCEDURAL_KINDS },
|
|
979
1000
|
};
|
package/public/avatar-skill.js
DELETED
|
@@ -1,629 +0,0 @@
|
|
|
1
|
-
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
-
AVATAR SKILL · BRICK pet generator
|
|
3
|
-
─────────────────────────────────────────────────────────────
|
|
4
|
-
Composable 8-bit pixel-art mascots based on the BRICK silhouette
|
|
5
|
-
(chunky terracotta block + stub legs + tool belt). Five dimensions
|
|
6
|
-
combined by seed:
|
|
7
|
-
|
|
8
|
-
· body color (12) — terracotta default; chair always terracotta
|
|
9
|
-
· glasses (9) — none, round, horn, monocle, sun, aviator,
|
|
10
|
-
cyber-visor, pince-nez, 3d
|
|
11
|
-
· mustache (10) — clean, handlebar, walrus, pencil, chevron,
|
|
12
|
-
horseshoe, imperial, soul-patch, goatee, full
|
|
13
|
-
· expression (7) — default, focused, wink, surprised, sleepy,
|
|
14
|
-
side-look, happy
|
|
15
|
-
· prop (9) — none, coffee, notebook, magnifier, lightbulb,
|
|
16
|
-
gavel, scroll, pen, lantern
|
|
17
|
-
(held in front of body, doesn't break silhouette)
|
|
18
|
-
|
|
19
|
-
Hats were deliberately removed — hatted vs. un-hatted avatars made
|
|
20
|
-
the rendered head size inconsistent (per-avatar viewBox includes
|
|
21
|
-
the hat in its bbox, so the body shrank inside the fixed-size
|
|
22
|
-
container). Now every avatar has identical content bounds → body
|
|
23
|
-
size is stable across the whole sidebar.
|
|
24
|
-
|
|
25
|
-
Public API · window.AvatarSkill:
|
|
26
|
-
generate(seed?, opts?) → SVG markup string
|
|
27
|
-
generateDataUrl(seed?, opts?) → "data:image/svg+xml;…"
|
|
28
|
-
randomSeed() → fresh seed string
|
|
29
|
-
attach({ frame, button, onSeed }) → wire a UI in one line.
|
|
30
|
-
|
|
31
|
-
opts.placeholder = true → grey silhouette w/ "?".
|
|
32
|
-
opts.variant = "classic" → forces CLASSIC (default expression + clean
|
|
33
|
-
+ no glasses / mustache / prop, terracotta).
|
|
34
|
-
Used by chair.svg.
|
|
35
|
-
|
|
36
|
-
Grid: 32×32 cells, 16px each, viewBox computed per-avatar.
|
|
37
|
-
═══════════════════════════════════════════════════════════════ */
|
|
38
|
-
|
|
39
|
-
(function () {
|
|
40
|
-
if (window.AvatarSkill) return;
|
|
41
|
-
|
|
42
|
-
// ── Grid constants ───────────────────────────────────────
|
|
43
|
-
const GRID = 32;
|
|
44
|
-
const PX = 16;
|
|
45
|
-
const SIZE = GRID * PX; // 512
|
|
46
|
-
|
|
47
|
-
// ── Body color palettes (12) ─────────────────────────────
|
|
48
|
-
// Each: { name, base, hi, sh, deep, eye, belt, buckle }
|
|
49
|
-
const BODY_COLORS = [
|
|
50
|
-
{ name: "terracotta", base: "#a35a32", hi: "#ce8a5a", sh: "#6e3814", deep: "#3e1c08", eye: "#1a0805", belt: "#5a2a10", buckle: "#cfb56a" },
|
|
51
|
-
{ name: "slate", base: "#5a6f88", hi: "#8294aa", sh: "#3a4e64", deep: "#202d3e", eye: "#15101c", belt: "#2a3848", buckle: "#c0c8d8" },
|
|
52
|
-
{ name: "moss", base: "#5a7a3a", hi: "#8aaa5a", sh: "#3a5418", deep: "#1a2a08", eye: "#15140c", belt: "#2a3a18", buckle: "#d4c870" },
|
|
53
|
-
{ name: "plum", base: "#6a3a6a", hi: "#9a5a9a", sh: "#3a1a3a", deep: "#1a081a", eye: "#15081a", belt: "#2a142a", buckle: "#d4a8d8" },
|
|
54
|
-
{ name: "dusty-rose", base: "#c47a7a", hi: "#e8a8a8", sh: "#8a4848", deep: "#5a2828", eye: "#2a1414", belt: "#6a3a3a", buckle: "#e8c8a8" },
|
|
55
|
-
{ name: "mustard", base: "#c4a040", hi: "#e8c870", sh: "#886820", deep: "#4a3808", eye: "#2a1c05", belt: "#685428", buckle: "#e8d8a8" },
|
|
56
|
-
{ name: "charcoal", base: "#4a4a52", hi: "#6a6a78", sh: "#2a2a32", deep: "#15151a", eye: "#08080c", belt: "#1a1a22", buckle: "#a8a8b0" },
|
|
57
|
-
{ name: "copper", base: "#b86a3a", hi: "#e08a5a", sh: "#7a3a18", deep: "#3a1a08", eye: "#1a0805", belt: "#5a2810", buckle: "#f5d870" },
|
|
58
|
-
{ name: "teal", base: "#3a8a8a", hi: "#5aaaaa", sh: "#1a4848", deep: "#082828", eye: "#051818", belt: "#1a3838", buckle: "#c8e8d8" },
|
|
59
|
-
{ name: "burgundy", base: "#7a2828", hi: "#a85050", sh: "#4a1010", deep: "#1a0808", eye: "#15050a", belt: "#380a0a", buckle: "#d8a8a8" },
|
|
60
|
-
{ name: "amber", base: "#d4923a", hi: "#f5b85a", sh: "#8a5818", deep: "#4a2808", eye: "#1c0d05", belt: "#6a3a10", buckle: "#fff0a8" },
|
|
61
|
-
{ name: "navy", base: "#2a4a78", hi: "#4a6aa8", sh: "#15284a", deep: "#08152a", eye: "#050a18", belt: "#15203a", buckle: "#c0d0e8" },
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
// ── RNG ──────────────────────────────────────────────────
|
|
65
|
-
function makeRng(seed) {
|
|
66
|
-
let s = 0;
|
|
67
|
-
const str = String(seed || "default");
|
|
68
|
-
for (let i = 0; i < str.length; i++) s = (s * 31 + str.charCodeAt(i)) >>> 0;
|
|
69
|
-
return function () {
|
|
70
|
-
s = (s * 1664525 + 1013904223) >>> 0;
|
|
71
|
-
return s / 0xFFFFFFFF;
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
const pick = (rng, arr) => arr[Math.floor(rng() * arr.length)];
|
|
75
|
-
function weighted(rng, items, weights) {
|
|
76
|
-
let total = 0;
|
|
77
|
-
for (const w of weights) total += w;
|
|
78
|
-
let r = rng() * total;
|
|
79
|
-
for (let i = 0; i < items.length; i++) {
|
|
80
|
-
r -= weights[i];
|
|
81
|
-
if (r <= 0) return items[i];
|
|
82
|
-
}
|
|
83
|
-
return items[items.length - 1];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── Pixel grid ───────────────────────────────────────────
|
|
87
|
-
function makeGrid() {
|
|
88
|
-
const g = new Array(GRID);
|
|
89
|
-
for (let y = 0; y < GRID; y++) g[y] = new Array(GRID).fill(null);
|
|
90
|
-
return g;
|
|
91
|
-
}
|
|
92
|
-
function px(g, x, y, c) {
|
|
93
|
-
if (x < 0 || x >= GRID || y < 0 || y >= GRID) return;
|
|
94
|
-
g[y][x] = c;
|
|
95
|
-
}
|
|
96
|
-
function row(g, r, c1, c2, c) {
|
|
97
|
-
for (let x = c1; x <= c2; x++) px(g, x, r, c);
|
|
98
|
-
}
|
|
99
|
-
function colp(g, c, r1, r2, color) {
|
|
100
|
-
for (let y = r1; y <= r2; y++) px(g, c, y, color);
|
|
101
|
-
}
|
|
102
|
-
function rect(g, x, y, w, h, c) {
|
|
103
|
-
for (let dy = 0; dy < h; dy++)
|
|
104
|
-
for (let dx = 0; dx < w; dx++) px(g, x + dx, y + dy, c);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ════════════════════════════════════════════════════════
|
|
108
|
-
// BRICK BODY (rows 10-22, cols 10-21)
|
|
109
|
-
// ════════════════════════════════════════════════════════
|
|
110
|
-
|
|
111
|
-
const BODY_SHAPE = [
|
|
112
|
-
[10, 12, 19], [11, 11, 20], [12, 10, 21], [13, 10, 21],
|
|
113
|
-
[14, 10, 21], [15, 10, 21], [16, 10, 21], [17, 10, 21],
|
|
114
|
-
[18, 10, 21], [19, 10, 21], [20, 11, 20],
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
function drawBody(g, p) {
|
|
118
|
-
for (const [r, c1, c2] of BODY_SHAPE) row(g, r, c1, c2, p.base);
|
|
119
|
-
// Stub legs
|
|
120
|
-
rect(g, 12, 21, 2, 2, p.base);
|
|
121
|
-
rect(g, 18, 21, 2, 2, p.base);
|
|
122
|
-
// Foot bottom
|
|
123
|
-
row(g, 22, 12, 13, p.deep);
|
|
124
|
-
row(g, 22, 18, 19, p.deep);
|
|
125
|
-
// Top-left highlight
|
|
126
|
-
[[12,10],[13,10],[11,11],[12,11],[10,12],[11,12]].forEach(([x,y])=>px(g,x,y,p.hi));
|
|
127
|
-
// Bottom-right shadow
|
|
128
|
-
[[20,17],[21,17],[20,18],[21,18],[19,19],[20,19],[20,20]].forEach(([x,y])=>px(g,x,y,p.sh));
|
|
129
|
-
px(g, 19, 21, p.sh);
|
|
130
|
-
// Tool belt
|
|
131
|
-
row(g, 17, 10, 21, p.belt);
|
|
132
|
-
px(g, 15, 17, p.buckle);
|
|
133
|
-
px(g, 16, 17, p.buckle);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ════════════════════════════════════════════════════════
|
|
137
|
-
// EXPRESSIONS (eyes + brows; row 12-14)
|
|
138
|
-
// ════════════════════════════════════════════════════════
|
|
139
|
-
|
|
140
|
-
const EXPRESSIONS = {
|
|
141
|
-
// Default: angled inward brows + dot eyes (the OG BRICK look)
|
|
142
|
-
default(g, p) {
|
|
143
|
-
px(g, 12, 12, p.eye); px(g, 13, 13, p.eye);
|
|
144
|
-
px(g, 19, 12, p.eye); px(g, 18, 13, p.eye);
|
|
145
|
-
px(g, 13, 14, p.eye); px(g, 18, 14, p.eye);
|
|
146
|
-
},
|
|
147
|
-
// Focused: flat brows + narrowed eyes
|
|
148
|
-
focused(g, p) {
|
|
149
|
-
row(g, 12, 12, 14, p.eye);
|
|
150
|
-
row(g, 12, 17, 19, p.eye);
|
|
151
|
-
px(g, 13, 14, p.eye); px(g, 14, 14, p.eye);
|
|
152
|
-
px(g, 17, 14, p.eye); px(g, 18, 14, p.eye);
|
|
153
|
-
},
|
|
154
|
-
// Wink: left eye dot, right eye closed line
|
|
155
|
-
wink(g, p) {
|
|
156
|
-
px(g, 12, 12, p.eye); px(g, 13, 13, p.eye);
|
|
157
|
-
px(g, 19, 12, p.eye); px(g, 18, 13, p.eye);
|
|
158
|
-
px(g, 13, 14, p.eye);
|
|
159
|
-
row(g, 14, 17, 19, p.eye); // closed wink line
|
|
160
|
-
},
|
|
161
|
-
// Surprised: raised brows + bigger eyes (2x2)
|
|
162
|
-
surprised(g, p) {
|
|
163
|
-
row(g, 11, 12, 14, p.eye);
|
|
164
|
-
row(g, 11, 17, 19, p.eye);
|
|
165
|
-
rect(g, 13, 13, 1, 2, p.eye);
|
|
166
|
-
rect(g, 18, 13, 1, 2, p.eye);
|
|
167
|
-
},
|
|
168
|
-
// Sleepy: low brows + closed-line eyes
|
|
169
|
-
sleepy(g, p) {
|
|
170
|
-
px(g, 12, 13, p.eye); px(g, 13, 13, p.eye);
|
|
171
|
-
px(g, 18, 13, p.eye); px(g, 19, 13, p.eye);
|
|
172
|
-
row(g, 14, 12, 14, p.eye);
|
|
173
|
-
row(g, 14, 17, 19, p.eye);
|
|
174
|
-
},
|
|
175
|
-
// Side-look: eyes shifted right
|
|
176
|
-
sideLook(g, p) {
|
|
177
|
-
px(g, 12, 12, p.eye); px(g, 13, 13, p.eye);
|
|
178
|
-
px(g, 19, 12, p.eye); px(g, 18, 13, p.eye);
|
|
179
|
-
px(g, 14, 14, p.eye); px(g, 19, 14, p.eye);
|
|
180
|
-
},
|
|
181
|
-
// Happy: ^_^ curved-up eyes
|
|
182
|
-
happy(g, p) {
|
|
183
|
-
// brow tips up
|
|
184
|
-
px(g, 12, 12, p.eye); px(g, 13, 12, p.eye);
|
|
185
|
-
px(g, 18, 12, p.eye); px(g, 19, 12, p.eye);
|
|
186
|
-
// ^_^
|
|
187
|
-
px(g, 12, 14, p.eye); px(g, 14, 14, p.eye); px(g, 13, 13, p.eye);
|
|
188
|
-
px(g, 17, 14, p.eye); px(g, 19, 14, p.eye); px(g, 18, 13, p.eye);
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// ════════════════════════════════════════════════════════
|
|
193
|
-
// GLASSES (rows 12-15)
|
|
194
|
-
// ════════════════════════════════════════════════════════
|
|
195
|
-
|
|
196
|
-
const GLASSES = {
|
|
197
|
-
none(g, p) {},
|
|
198
|
-
|
|
199
|
-
round(g, p) {
|
|
200
|
-
const F = "#c9a040";
|
|
201
|
-
px(g, 13, 13, F); px(g, 13, 15, F); px(g, 12, 14, F); px(g, 14, 14, F);
|
|
202
|
-
px(g, 18, 13, F); px(g, 18, 15, F); px(g, 17, 14, F); px(g, 19, 14, F);
|
|
203
|
-
px(g, 15, 14, F); px(g, 16, 14, F);
|
|
204
|
-
px(g, 13, 13, "#ffd870"); px(g, 18, 13, "#ffd870");
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
horn(g, p) {
|
|
208
|
-
const F = "#0a0512";
|
|
209
|
-
row(g, 13, 12, 14, F); row(g, 15, 12, 14, F);
|
|
210
|
-
px(g, 12, 14, F); px(g, 14, 14, F);
|
|
211
|
-
row(g, 13, 17, 19, F); row(g, 15, 17, 19, F);
|
|
212
|
-
px(g, 17, 14, F); px(g, 19, 14, F);
|
|
213
|
-
row(g, 14, 15, 16, F);
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
monocle(g, p) {
|
|
217
|
-
const F = "#c9a040";
|
|
218
|
-
row(g, 12, 17, 18, F); row(g, 16, 17, 18, F);
|
|
219
|
-
colp(g, 16, 13, 15, F); colp(g, 19, 13, 15, F);
|
|
220
|
-
px(g, 17, 12, "#ffd870");
|
|
221
|
-
// Chain hanging down
|
|
222
|
-
px(g, 20, 14, "#7a5a18");
|
|
223
|
-
px(g, 20, 15, F);
|
|
224
|
-
px(g, 21, 15, "#7a5a18");
|
|
225
|
-
px(g, 21, 16, F);
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
sun(g, p) {
|
|
229
|
-
const F = "#0a0512";
|
|
230
|
-
rect(g, 12, 13, 3, 3, "#15101c");
|
|
231
|
-
rect(g, 17, 13, 3, 3, "#15101c");
|
|
232
|
-
row(g, 12, 12, 14, F); row(g, 12, 17, 19, F);
|
|
233
|
-
row(g, 14, 15, 16, F);
|
|
234
|
-
px(g, 12, 13, "#3ad4e0"); px(g, 17, 13, "#3ad4e0");
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
aviator(g, p) {
|
|
238
|
-
// Teardrop shape, gold
|
|
239
|
-
const F = "#c9a040";
|
|
240
|
-
// Left lens
|
|
241
|
-
row(g, 13, 12, 14, F);
|
|
242
|
-
px(g, 12, 14, F); px(g, 14, 14, F);
|
|
243
|
-
px(g, 13, 15, F); px(g, 14, 15, F);
|
|
244
|
-
// Right lens
|
|
245
|
-
row(g, 13, 17, 19, F);
|
|
246
|
-
px(g, 17, 14, F); px(g, 19, 14, F);
|
|
247
|
-
px(g, 17, 15, F); px(g, 18, 15, F);
|
|
248
|
-
// Bridge
|
|
249
|
-
px(g, 15, 13, F); px(g, 16, 13, F);
|
|
250
|
-
// Sheen
|
|
251
|
-
px(g, 13, 13, "#ffd870"); px(g, 18, 13, "#ffd870");
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
visor(g, p) {
|
|
255
|
-
// Cyber horizontal slit
|
|
256
|
-
rect(g, 11, 13, 10, 3, "#15101c");
|
|
257
|
-
row(g, 14, 12, 19, "#3ad4e0");
|
|
258
|
-
px(g, 13, 14, "#aaf0f8"); px(g, 18, 14, "#aaf0f8");
|
|
259
|
-
px(g, 11, 13, "#3a3445"); px(g, 20, 15, "#3a3445");
|
|
260
|
-
},
|
|
261
|
-
|
|
262
|
-
pince(g, p) {
|
|
263
|
-
// Pince-nez: small lenses on nose, no temples, with chain
|
|
264
|
-
px(g, 13, 14, "#5a5a5a"); px(g, 13, 13, "#5a5a5a");
|
|
265
|
-
px(g, 14, 14, "#5a5a5a");
|
|
266
|
-
px(g, 17, 14, "#5a5a5a"); px(g, 18, 13, "#5a5a5a");
|
|
267
|
-
px(g, 18, 14, "#5a5a5a");
|
|
268
|
-
px(g, 15, 14, "#5a5a5a"); px(g, 16, 14, "#5a5a5a");
|
|
269
|
-
// Small chain to side
|
|
270
|
-
px(g, 11, 15, "#5a5a5a");
|
|
271
|
-
px(g, 11, 16, "#5a5a5a");
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
"3d"(g, p) {
|
|
275
|
-
// Red + blue 3D glasses
|
|
276
|
-
rect(g, 12, 13, 3, 3, "#c8281a");
|
|
277
|
-
rect(g, 17, 13, 3, 3, "#3a78c8");
|
|
278
|
-
row(g, 12, 12, 14, "#0a0512"); row(g, 12, 17, 19, "#0a0512");
|
|
279
|
-
row(g, 14, 15, 16, "#0a0512");
|
|
280
|
-
px(g, 12, 13, "#f25a3a"); px(g, 17, 13, "#7ba8e8");
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
// ════════════════════════════════════════════════════════
|
|
285
|
-
// MUSTACHES / BEARDS (rows 15-16, careful w/ belt at 17)
|
|
286
|
-
// ════════════════════════════════════════════════════════
|
|
287
|
-
|
|
288
|
-
const MUSTACHES = {
|
|
289
|
-
clean(g, p) {},
|
|
290
|
-
|
|
291
|
-
handlebar(g, p) {
|
|
292
|
-
const c = "#2a1408";
|
|
293
|
-
row(g, 16, 13, 18, c);
|
|
294
|
-
px(g, 14, 15, c); px(g, 17, 15, c);
|
|
295
|
-
px(g, 12, 15, c); px(g, 11, 15, c);
|
|
296
|
-
px(g, 19, 15, c); px(g, 20, 15, c);
|
|
297
|
-
px(g, 15, 16, "#5a3018"); px(g, 16, 16, "#5a3018");
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
walrus(g, p) {
|
|
301
|
-
const c = "#2a1408", cd = "#15080a";
|
|
302
|
-
row(g, 15, 12, 19, c);
|
|
303
|
-
row(g, 16, 11, 20, c);
|
|
304
|
-
px(g, 11, 17, c); px(g, 20, 17, c);
|
|
305
|
-
row(g, 16, 14, 17, cd);
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
pencil(g, p) {
|
|
309
|
-
row(g, 15, 14, 17, "#2a1408");
|
|
310
|
-
},
|
|
311
|
-
|
|
312
|
-
chevron(g, p) {
|
|
313
|
-
const c = "#2a1408", cd = "#15080a";
|
|
314
|
-
row(g, 15, 13, 18, c);
|
|
315
|
-
row(g, 16, 13, 18, c);
|
|
316
|
-
px(g, 13, 16, cd); px(g, 18, 16, cd);
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
horseshoe(g, p) {
|
|
320
|
-
const c = "#2a1408";
|
|
321
|
-
row(g, 15, 13, 18, c);
|
|
322
|
-
row(g, 16, 13, 18, c);
|
|
323
|
-
px(g, 13, 17, c); px(g, 18, 17, c);
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
imperial(g, p) {
|
|
327
|
-
// Big curls way up at the ends
|
|
328
|
-
const c = "#2a1408";
|
|
329
|
-
row(g, 16, 14, 17, c);
|
|
330
|
-
// Curls up to row 14 / 13
|
|
331
|
-
px(g, 13, 15, c); px(g, 12, 14, c); px(g, 11, 13, c); px(g, 11, 14, c);
|
|
332
|
-
px(g, 18, 15, c); px(g, 19, 14, c); px(g, 20, 13, c); px(g, 20, 14, c);
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
soulPatch(g, p) {
|
|
336
|
-
// Small tuft just under lip
|
|
337
|
-
const c = "#2a1408";
|
|
338
|
-
row(g, 16, 15, 16, c);
|
|
339
|
-
px(g, 15, 15, c);
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
goatee(g, p) {
|
|
343
|
-
// Chin + jaw line
|
|
344
|
-
const c = "#2a1408";
|
|
345
|
-
row(g, 16, 14, 17, c);
|
|
346
|
-
px(g, 13, 16, c); px(g, 18, 16, c);
|
|
347
|
-
// Sides going down to body bottom edge
|
|
348
|
-
colp(g, 13, 16, 19, c);
|
|
349
|
-
colp(g, 18, 16, 19, c);
|
|
350
|
-
row(g, 19, 14, 17, c);
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
fullBeard(g, p) {
|
|
354
|
-
// Covers lower face cols 11-20, rows 15-19
|
|
355
|
-
const c = "#2a1408", cd = "#15080a";
|
|
356
|
-
row(g, 15, 12, 19, c);
|
|
357
|
-
row(g, 16, 11, 20, c);
|
|
358
|
-
// Sideburns
|
|
359
|
-
colp(g, 11, 15, 18, c);
|
|
360
|
-
colp(g, 20, 15, 18, c);
|
|
361
|
-
// Chin extension
|
|
362
|
-
row(g, 18, 13, 18, c);
|
|
363
|
-
row(g, 19, 14, 17, c);
|
|
364
|
-
// Mustache shadow
|
|
365
|
-
row(g, 16, 14, 17, cd);
|
|
366
|
-
},
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
// ════════════════════════════════════════════════════════
|
|
370
|
-
// PROPS (held in front of body, rows 17-21 area)
|
|
371
|
-
// Drawn LAST so they appear on top.
|
|
372
|
-
// ════════════════════════════════════════════════════════
|
|
373
|
-
|
|
374
|
-
const PROPS = {
|
|
375
|
-
none(g, p) {},
|
|
376
|
-
|
|
377
|
-
coffee(g, p) {
|
|
378
|
-
// Mug w/ handle, in front of belt
|
|
379
|
-
rect(g, 13, 18, 4, 4, "#2a1a14"); // mug body
|
|
380
|
-
rect(g, 13, 18, 4, 1, "#5a3a2a"); // top rim
|
|
381
|
-
// Coffee surface (steam-y dark brown)
|
|
382
|
-
px(g, 14, 18, "#1a0a05"); px(g, 15, 18, "#1a0a05");
|
|
383
|
-
// Handle
|
|
384
|
-
px(g, 17, 19, "#2a1a14"); px(g, 17, 20, "#2a1a14");
|
|
385
|
-
// Steam
|
|
386
|
-
px(g, 14, 17, "#dad5c8"); px(g, 15, 16, "#dad5c8");
|
|
387
|
-
// Highlight
|
|
388
|
-
px(g, 13, 19, "#5a3a2a");
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
notebook(g, p) {
|
|
392
|
-
// Open notebook / clipboard
|
|
393
|
-
rect(g, 12, 18, 8, 4, "#f1dfc4"); // paper
|
|
394
|
-
// Lines on paper
|
|
395
|
-
row(g, 19, 13, 18, "#7a5a3a");
|
|
396
|
-
row(g, 20, 13, 18, "#7a5a3a");
|
|
397
|
-
// Clip on top
|
|
398
|
-
rect(g, 14, 18, 4, 1, "#5a5a5a");
|
|
399
|
-
px(g, 15, 17, "#5a5a5a"); px(g, 16, 17, "#5a5a5a");
|
|
400
|
-
// Edge shadow
|
|
401
|
-
colp(g, 19, 18, 21, "#c9b58a");
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
magnifier(g, p) {
|
|
405
|
-
// Circle lens + handle
|
|
406
|
-
// Lens ring
|
|
407
|
-
row(g, 17, 13, 15, "#5a3a18");
|
|
408
|
-
row(g, 19, 13, 15, "#5a3a18");
|
|
409
|
-
px(g, 12, 18, "#5a3a18"); px(g, 16, 18, "#5a3a18");
|
|
410
|
-
// Lens glass
|
|
411
|
-
px(g, 13, 18, "#aaf0f8"); px(g, 14, 18, "#aaf0f8"); px(g, 15, 18, "#aaf0f8");
|
|
412
|
-
px(g, 13, 17, "#ffffff"); // sparkle
|
|
413
|
-
// Handle (going down-right)
|
|
414
|
-
px(g, 16, 19, "#5a3a18"); px(g, 17, 20, "#5a3a18"); px(g, 18, 21, "#5a3a18");
|
|
415
|
-
},
|
|
416
|
-
|
|
417
|
-
lightbulb(g, p) {
|
|
418
|
-
// Bulb above body w/ glow
|
|
419
|
-
// Glow
|
|
420
|
-
px(g, 16, 16, "#fff5b0");
|
|
421
|
-
px(g, 14, 17, "#fff5b0"); px(g, 18, 17, "#fff5b0");
|
|
422
|
-
// Bulb
|
|
423
|
-
rect(g, 15, 18, 3, 3, "#ffe070");
|
|
424
|
-
px(g, 14, 19, "#ffe070"); px(g, 18, 19, "#ffe070");
|
|
425
|
-
// Bulb highlight
|
|
426
|
-
px(g, 15, 18, "#ffffff");
|
|
427
|
-
// Base / screw cap
|
|
428
|
-
row(g, 21, 15, 17, "#5a5a5a");
|
|
429
|
-
},
|
|
430
|
-
|
|
431
|
-
gavel(g, p) {
|
|
432
|
-
// Wooden gavel
|
|
433
|
-
// Hammer head (horizontal block)
|
|
434
|
-
rect(g, 12, 18, 4, 2, "#7a5a3a");
|
|
435
|
-
px(g, 12, 18, "#a87a4a"); px(g, 13, 18, "#a87a4a");
|
|
436
|
-
px(g, 15, 19, "#3a2a18");
|
|
437
|
-
// Handle (diagonal)
|
|
438
|
-
px(g, 16, 20, "#5a3a18"); px(g, 17, 21, "#5a3a18");
|
|
439
|
-
// Strike plate small block underneath
|
|
440
|
-
px(g, 14, 21, "#3a2a18"); px(g, 15, 21, "#3a2a18");
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
scroll(g, p) {
|
|
444
|
-
// Rolled paper held diagonally
|
|
445
|
-
rect(g, 13, 18, 6, 3, "#f1dfc4"); // paper
|
|
446
|
-
row(g, 19, 14, 17, "#a87a4a"); // text line
|
|
447
|
-
// Roll ends (darker)
|
|
448
|
-
colp(g, 13, 18, 20, "#c9b58a");
|
|
449
|
-
colp(g, 18, 18, 20, "#c9b58a");
|
|
450
|
-
// Cap end caps (rounded curls)
|
|
451
|
-
px(g, 12, 18, "#a87a4a"); px(g, 12, 20, "#a87a4a");
|
|
452
|
-
px(g, 19, 18, "#a87a4a"); px(g, 19, 20, "#a87a4a");
|
|
453
|
-
},
|
|
454
|
-
|
|
455
|
-
pen(g, p) {
|
|
456
|
-
// Diagonal pen
|
|
457
|
-
px(g, 12, 21, "#15101c");
|
|
458
|
-
px(g, 13, 20, "#15101c");
|
|
459
|
-
px(g, 14, 19, "#15101c");
|
|
460
|
-
px(g, 15, 18, "#3a3a44"); // body
|
|
461
|
-
px(g, 16, 17, "#3a3a44");
|
|
462
|
-
px(g, 17, 16, "#cfa040"); // gold cap end
|
|
463
|
-
px(g, 18, 16, "#cfa040");
|
|
464
|
-
// Tip ink dot
|
|
465
|
-
px(g, 11, 21, "#15101c");
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
lantern(g, p) {
|
|
469
|
-
// Box w/ handle
|
|
470
|
-
rect(g, 13, 18, 4, 4, "#3a3a44"); // frame
|
|
471
|
-
rect(g, 14, 19, 2, 2, "#ffe070"); // glow
|
|
472
|
-
px(g, 14, 19, "#ffffff");
|
|
473
|
-
// Top handle
|
|
474
|
-
row(g, 17, 14, 15, "#5a5a5a");
|
|
475
|
-
px(g, 14, 18, "#5a5a5a"); px(g, 15, 18, "#5a5a5a");
|
|
476
|
-
// Bottom shadow
|
|
477
|
-
row(g, 22, 13, 16, "#15101c");
|
|
478
|
-
},
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
// ════════════════════════════════════════════════════════
|
|
482
|
-
// PLACEHOLDER (grey BRICK + "?")
|
|
483
|
-
// ════════════════════════════════════════════════════════
|
|
484
|
-
|
|
485
|
-
function drawPlaceholder(g) {
|
|
486
|
-
const p = { name: "placeholder", base: "#5a5a5a", hi: "#7a7a7a", sh: "#3a3a3a", deep: "#1a1a1a", eye: "#15151a", belt: "#2a2a2a", buckle: "#a0a0a0" };
|
|
487
|
-
drawBody(g, p);
|
|
488
|
-
// "?" centered on face area (rows 12-15)
|
|
489
|
-
const q = ["XXXX", "X X", " XX", " X ", " ", " X "];
|
|
490
|
-
const ox = 13, oy = 11;
|
|
491
|
-
for (let dy = 0; dy < q.length; dy++)
|
|
492
|
-
for (let dx = 0; dx < q[dy].length; dx++)
|
|
493
|
-
if (q[dy][dx] === "X") px(g, ox + dx, oy + dy, "#fafafa");
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ════════════════════════════════════════════════════════
|
|
497
|
-
// GENERATE
|
|
498
|
-
// ════════════════════════════════════════════════════════
|
|
499
|
-
|
|
500
|
-
const GLASSES_NAMES = Object.keys(GLASSES);
|
|
501
|
-
const MUSTACHE_NAMES = Object.keys(MUSTACHES);
|
|
502
|
-
const EXPRESSION_NAMES = Object.keys(EXPRESSIONS);
|
|
503
|
-
const PROP_NAMES = Object.keys(PROPS);
|
|
504
|
-
|
|
505
|
-
function generate(seed, opts) {
|
|
506
|
-
const o = opts || {};
|
|
507
|
-
const grid = makeGrid();
|
|
508
|
-
|
|
509
|
-
if (o.placeholder) {
|
|
510
|
-
drawPlaceholder(grid);
|
|
511
|
-
return svgFromGrid(grid);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// CLASSIC: locked terracotta + no accessories (chair)
|
|
515
|
-
if (o.variant === "classic") {
|
|
516
|
-
const pal = BODY_COLORS[0]; // terracotta
|
|
517
|
-
drawBody(grid, pal);
|
|
518
|
-
EXPRESSIONS.default(grid, pal);
|
|
519
|
-
return svgFromGrid(grid);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const rng = makeRng(seed);
|
|
523
|
-
|
|
524
|
-
// Body color — bias slightly toward terracotta + warm earthy tones
|
|
525
|
-
const bodyWeights = [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3];
|
|
526
|
-
const palette = weighted(rng, BODY_COLORS, bodyWeights);
|
|
527
|
-
|
|
528
|
-
// Glasses — slight bias toward none / common shapes
|
|
529
|
-
const glassWeights = [4, 2, 2, 1, 2, 1, 1, 1, 1];
|
|
530
|
-
const glassName = weighted(rng, GLASSES_NAMES, glassWeights);
|
|
531
|
-
|
|
532
|
-
// Mustache — most have none
|
|
533
|
-
const stachWeights = [5, 2, 2, 2, 2, 1, 1, 1, 2, 1];
|
|
534
|
-
const stachName = weighted(rng, MUSTACHE_NAMES, stachWeights);
|
|
535
|
-
|
|
536
|
-
// Expression — default common, others rare
|
|
537
|
-
const exprWeights = [5, 2, 1, 1, 1, 1, 2];
|
|
538
|
-
const exprName = weighted(rng, EXPRESSION_NAMES, exprWeights);
|
|
539
|
-
|
|
540
|
-
// Prop — most have none
|
|
541
|
-
const propWeights = [10, 1, 1, 1, 1, 1, 1, 1, 1];
|
|
542
|
-
const propName = weighted(rng, PROP_NAMES, propWeights);
|
|
543
|
-
|
|
544
|
-
// ── Compose · order matters ───────────────────────────
|
|
545
|
-
drawBody(grid, palette);
|
|
546
|
-
|
|
547
|
-
// Expression (eyes + brows) — drawn before glasses so glasses overlay
|
|
548
|
-
EXPRESSIONS[exprName](grid, palette);
|
|
549
|
-
|
|
550
|
-
// Glasses sit on top of eyes
|
|
551
|
-
GLASSES[glassName](grid, palette);
|
|
552
|
-
|
|
553
|
-
// Mustache below eyes
|
|
554
|
-
MUSTACHES[stachName](grid, palette);
|
|
555
|
-
|
|
556
|
-
// Prop drawn very last — held in front of body
|
|
557
|
-
PROPS[propName](grid, palette);
|
|
558
|
-
|
|
559
|
-
return svgFromGrid(grid);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// ════════════════════════════════════════════════════════
|
|
563
|
-
// SVG SERIALIZATION
|
|
564
|
-
// ════════════════════════════════════════════════════════
|
|
565
|
-
|
|
566
|
-
// Per-avatar viewBox: scan the drawn pixels for their actual bounding
|
|
567
|
-
// box, then emit a square viewBox centered on that content with 1-cell
|
|
568
|
-
// padding. This auto-centers every avatar regardless of hat presence
|
|
569
|
-
// (CLASSIC chair fills its frame, wizard-hatted seats fit hat + body
|
|
570
|
-
// without the body being shoved against the bottom edge).
|
|
571
|
-
function svgFromGrid(grid) {
|
|
572
|
-
let minX = GRID, maxX = -1, minY = GRID, maxY = -1;
|
|
573
|
-
let rects = "";
|
|
574
|
-
for (let y = 0; y < GRID; y++) {
|
|
575
|
-
for (let x = 0; x < GRID; x++) {
|
|
576
|
-
const c = grid[y][x];
|
|
577
|
-
if (!c) continue;
|
|
578
|
-
rects += `<rect x="${x * PX}" y="${y * PX}" width="${PX}" height="${PX}" fill="${c}"/>`;
|
|
579
|
-
if (x < minX) minX = x;
|
|
580
|
-
if (x > maxX) maxX = x;
|
|
581
|
-
if (y < minY) minY = y;
|
|
582
|
-
if (y > maxY) maxY = y;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (maxX < 0) {
|
|
586
|
-
return `<svg viewBox="0 0 ${SIZE} ${SIZE}" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges"/>`;
|
|
587
|
-
}
|
|
588
|
-
const w = maxX - minX + 1;
|
|
589
|
-
const h = maxY - minY + 1;
|
|
590
|
-
const sz = Math.max(w, h) + 4; // +4 cells = 2 cells padding each side
|
|
591
|
-
const cx = (minX + maxX + 1) / 2; // content center in cell coords
|
|
592
|
-
const cy = (minY + maxY + 1) / 2;
|
|
593
|
-
const vbX = (cx - sz / 2) * PX;
|
|
594
|
-
const vbY = (cy - sz / 2) * PX;
|
|
595
|
-
const vbSize = sz * PX;
|
|
596
|
-
return `<svg viewBox="${vbX} ${vbY} ${vbSize} ${vbSize}" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" preserveAspectRatio="xMidYMid meet">${rects}</svg>`;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function generateDataUrl(seed, opts) {
|
|
600
|
-
return "data:image/svg+xml;utf8," + encodeURIComponent(generate(seed, opts));
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function randomSeed() {
|
|
604
|
-
return Math.random().toString(36).slice(2, 12);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function attach({ frame, button, onSeed, initialSeed }) {
|
|
608
|
-
if (!frame || !button) return;
|
|
609
|
-
let seed = initialSeed || null;
|
|
610
|
-
function paintFrame(nextSeed, opts) {
|
|
611
|
-
seed = nextSeed;
|
|
612
|
-
frame.innerHTML = generate(nextSeed, opts);
|
|
613
|
-
if (typeof onSeed === "function") onSeed(seed);
|
|
614
|
-
}
|
|
615
|
-
if (seed) paintFrame(seed);
|
|
616
|
-
else paintFrame("__placeholder__", { placeholder: true });
|
|
617
|
-
button.addEventListener("click", (e) => {
|
|
618
|
-
e.preventDefault();
|
|
619
|
-
paintFrame(randomSeed());
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
window.AvatarSkill = {
|
|
624
|
-
generate,
|
|
625
|
-
generateDataUrl,
|
|
626
|
-
randomSeed,
|
|
627
|
-
attach,
|
|
628
|
-
};
|
|
629
|
-
})();
|