privateboard 0.1.38 → 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 +323 -50
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +323 -50
- package/dist/cli.js.map +1 -1
- package/dist/server.js +200 -40
- 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/agent-profile.css +3 -9
- package/public/agent-profile.js +85 -32
- package/public/app.js +469 -526
- 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 +78 -16
- package/public/i18n.js +12 -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 +148 -121
- package/public/new-agent.js +46 -20
- package/public/office-viewer.html +340 -0
- package/public/stuff-viewer.html +330 -0
- package/public/thread.css +8 -8
- package/public/user-settings.css +10 -13
- 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-replay.js +21 -0
- package/public/avatar-skill.js +0 -629
- package/public/icons/folded-sidebar.png +0 -0
package/public/index.html
CHANGED
|
@@ -1107,68 +1107,19 @@
|
|
|
1107
1107
|
mask here also overrides the generic .mini-btn::before, which —
|
|
1108
1108
|
lacking an --icon — would otherwise paint a solid square. */
|
|
1109
1109
|
.mini-logo { position: relative; }
|
|
1110
|
-
/*
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
position: relative;
|
|
1115
|
-
width: 26px;
|
|
1116
|
-
height: 26px;
|
|
1117
|
-
transition: opacity 0.12s;
|
|
1118
|
-
animation: chair-hop 7s ease-in-out infinite;
|
|
1119
|
-
}
|
|
1120
|
-
.mini-logo-av img {
|
|
1121
|
-
position: absolute;
|
|
1122
|
-
inset: 0;
|
|
1123
|
-
width: 100%;
|
|
1124
|
-
height: 100%;
|
|
1125
|
-
image-rendering: pixelated;
|
|
1126
|
-
image-rendering: crisp-edges;
|
|
1127
|
-
}
|
|
1128
|
-
/* Closed-eye frame · hidden at rest, briefly opaque on the blink
|
|
1129
|
-
keyframe. A different cycle length from the hop keeps the two from
|
|
1130
|
-
locking into a robotic sync. */
|
|
1131
|
-
.mini-logo-av .cl-blink {
|
|
1132
|
-
opacity: 0;
|
|
1133
|
-
animation: chair-blink 5.7s ease-in-out infinite;
|
|
1134
|
-
}
|
|
1135
|
-
.mini-logo:hover .mini-logo-av { opacity: 0; }
|
|
1136
|
-
/* Occasional double-bounce hop · still ~85% of the cycle, then a
|
|
1137
|
-
quick two-step hop so the chair reads as "alive" without being
|
|
1138
|
-
busy. */
|
|
1139
|
-
@keyframes chair-hop {
|
|
1140
|
-
0%, 60%, 100% { transform: translateY(0); }
|
|
1141
|
-
66% { transform: translateY(-4px); }
|
|
1142
|
-
72% { transform: translateY(0); }
|
|
1143
|
-
77% { transform: translateY(-2px); }
|
|
1144
|
-
82% { transform: translateY(0); }
|
|
1145
|
-
}
|
|
1146
|
-
/* Single crisp blink near the end of the cycle (~170ms closed). */
|
|
1147
|
-
@keyframes chair-blink {
|
|
1148
|
-
0%, 93.5%, 100% { opacity: 0; }
|
|
1149
|
-
94%, 96.5% { opacity: 1; }
|
|
1150
|
-
97% { opacity: 0; }
|
|
1151
|
-
}
|
|
1152
|
-
/* Respect reduced-motion · hold the open-eye frame, no hop / blink. */
|
|
1153
|
-
@media (prefers-reduced-motion: reduce) {
|
|
1154
|
-
.mini-logo-av,
|
|
1155
|
-
.mini-logo-av .cl-blink { animation: none; }
|
|
1156
|
-
}
|
|
1110
|
+
/* Fold glyph · same Lucide PanelLeft mark the in-sidebar collapse
|
|
1111
|
+
button uses. Always visible (no chair avatar / hover swap),
|
|
1112
|
+
mirrored on X so the rows flip to the right edge — telegraphs
|
|
1113
|
+
"the panel expands out from here". */
|
|
1157
1114
|
.mini-logo::before {
|
|
1158
1115
|
content: "";
|
|
1159
1116
|
position: absolute;
|
|
1160
1117
|
top: 50%;
|
|
1161
1118
|
left: 50%;
|
|
1162
|
-
/* scaleX(-1) mirrors the fold so the rows flip to the right edge —
|
|
1163
|
-
the same direction the in-sidebar collapse button uses in its
|
|
1164
|
-
collapsed state to telegraph "the panel expands out from here". */
|
|
1165
1119
|
transform: translate(-50%, -50%) scaleX(-1);
|
|
1166
1120
|
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
|
|
1167
1121
|
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
|
|
1168
|
-
opacity: 0;
|
|
1169
|
-
transition: opacity 0.12s;
|
|
1170
1122
|
}
|
|
1171
|
-
.mini-logo:hover::before { opacity: 1; }
|
|
1172
1123
|
/* Divider between the action group and the rooms/agents switchers. */
|
|
1173
1124
|
.mini-sep {
|
|
1174
1125
|
width: 20px;
|
|
@@ -2059,11 +2010,11 @@
|
|
|
2059
2010
|
position: relative;
|
|
2060
2011
|
overflow: hidden;
|
|
2061
2012
|
}
|
|
2062
|
-
/*
|
|
2063
|
-
swaps the initial-letter chip for
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2013
|
+
/* 3D portrait variant · when prefs.avatarSeed exists,
|
|
2014
|
+
app.renderUserBlock swaps the initial-letter chip for the
|
|
2015
|
+
Avatar3DSnap rendered PNG. The image fills the chip; we drop the
|
|
2016
|
+
filled background so the portrait reads cleanly against the
|
|
2017
|
+
surface behind it. */
|
|
2067
2018
|
.user-av.has-pixel-av {
|
|
2068
2019
|
background: transparent;
|
|
2069
2020
|
}
|
|
@@ -2229,6 +2180,17 @@
|
|
|
2229
2180
|
and leaving an empty band below. */
|
|
2230
2181
|
.main-view[hidden] { display: none !important; }
|
|
2231
2182
|
|
|
2183
|
+
/* Boot guard · on refresh, the sidebar restore tick has to wait for
|
|
2184
|
+
the agent rows to mount before openAgentProfile fires, but app.js's
|
|
2185
|
+
handleRoute paints the new-room composer into the room view straight
|
|
2186
|
+
away on an empty hash. Mask the room view while a saved agent profile
|
|
2187
|
+
is being restored so the composer doesn't flash under the profile.
|
|
2188
|
+
The guard class is set SYNCHRONOUSLY during parse (top of the sidebar
|
|
2189
|
+
IIFE, before app.js's deferred init runs) and cleared the moment the
|
|
2190
|
+
profile opens or the restore gives up. `!important` beats app.js's
|
|
2191
|
+
own removeAttribute("hidden") on the room view during that window. */
|
|
2192
|
+
html.bb-restoring-agent .main-view[data-main-view="room"] { display: none !important; }
|
|
2193
|
+
|
|
2232
2194
|
/* ─── All Reports · clean vertical reading list ──────────────────
|
|
2233
2195
|
Single-hierarchy archive view · header → filter chips → date-
|
|
2234
2196
|
grouped reading rows. Each row uses pure typography (kicker /
|
|
@@ -4202,6 +4164,34 @@
|
|
|
4202
4164
|
letter-spacing: 0.1em;
|
|
4203
4165
|
}
|
|
4204
4166
|
.resume-btn:hover { background: var(--bg); color: var(--lime); }
|
|
4167
|
+
/* Stop voice replay · same compact mono chrome as pause/resume, but in
|
|
4168
|
+
red so it reads as a distinct "terminate playback" action. Rendered
|
|
4169
|
+
by app.js only while a replay is active in this room, and it sits in
|
|
4170
|
+
the pause/resume slot (those are hidden on adjourned rooms, where
|
|
4171
|
+
replay runs) so it lands exactly where the eye looks for transport. */
|
|
4172
|
+
.replay-stop-btn {
|
|
4173
|
+
padding: 4px 10px;
|
|
4174
|
+
background: transparent;
|
|
4175
|
+
color: var(--text);
|
|
4176
|
+
border: 0.5px solid var(--red, #B5706A);
|
|
4177
|
+
font-family: var(--mono);
|
|
4178
|
+
font-size: 10px;
|
|
4179
|
+
font-weight: 700;
|
|
4180
|
+
cursor: pointer;
|
|
4181
|
+
text-decoration: none;
|
|
4182
|
+
text-transform: uppercase;
|
|
4183
|
+
letter-spacing: 0.1em;
|
|
4184
|
+
transition: all 0.12s;
|
|
4185
|
+
}
|
|
4186
|
+
.replay-stop-btn .replay-stop-icon {
|
|
4187
|
+
color: var(--red, #B5706A);
|
|
4188
|
+
margin: 0 1px;
|
|
4189
|
+
letter-spacing: -0.05em;
|
|
4190
|
+
}
|
|
4191
|
+
.replay-stop-btn:hover {
|
|
4192
|
+
background: rgba(181, 112, 106, 0.12);
|
|
4193
|
+
color: var(--red, #B5706A);
|
|
4194
|
+
}
|
|
4205
4195
|
/* ─── Empty state ───
|
|
4206
4196
|
No room loaded (zero-room first-run, or after a navigation). The
|
|
4207
4197
|
starter panel renders inside .chat; the input bar, speaking queue,
|
|
@@ -7357,11 +7347,10 @@
|
|
|
7357
7347
|
border: 0.5px solid var(--line-strong);
|
|
7358
7348
|
font-family: var(--mono);
|
|
7359
7349
|
}
|
|
7360
|
-
/*
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
the chip edge-to-edge. */
|
|
7350
|
+
/* Legacy pixel-av variant · the renderChat path no longer mounts
|
|
7351
|
+
`msg-av-pixel` (Avatar3DSnap PNG fills `<img class="msg-av">`
|
|
7352
|
+
directly), but the CSS is kept for any third-party plugins that
|
|
7353
|
+
still write into that node. */
|
|
7365
7354
|
.msg.user .msg-av.msg-av-pixel {
|
|
7366
7355
|
border: none;
|
|
7367
7356
|
background: var(--bg);
|
|
@@ -7547,7 +7536,10 @@
|
|
|
7547
7536
|
-webkit-box-orient: vertical;
|
|
7548
7537
|
overflow: hidden;
|
|
7549
7538
|
}
|
|
7550
|
-
|
|
7539
|
+
/* Flows inline right after the badges (no longer pinned far-right via
|
|
7540
|
+
margin-left:auto) so the far-right end of the meta line is free for
|
|
7541
|
+
the hover thread toolbar to sit at the row's end. */
|
|
7542
|
+
.msg-time { color: var(--text-faint); }
|
|
7551
7543
|
|
|
7552
7544
|
.msg-bubble {
|
|
7553
7545
|
padding: 0;
|
|
@@ -8480,23 +8472,16 @@
|
|
|
8480
8472
|
--rt-bottom-reserve: 130px;
|
|
8481
8473
|
}
|
|
8482
8474
|
|
|
8483
|
-
/* ─── brainstorm ·
|
|
8484
|
-
128×
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
fill; scattered 1×2 lighter pixels read as grass blades
|
|
8490
|
-
catching light; pebbles + dust spots inside the dirt patches
|
|
8491
|
-
add ground-texture detail. JRPG outdoor-tile vocabulary for
|
|
8492
|
-
the "yes-and" creative tone — open ground, room to grow.
|
|
8493
|
-
|
|
8494
|
-
Replaces the earlier muted-earth grid mosaic which still read
|
|
8495
|
-
as an indoor floor pattern even at the larger tile size. */
|
|
8475
|
+
/* ─── brainstorm · WARM-OAK PLANK floor ───────────────────────
|
|
8476
|
+
128×64 light warm-oak planks · alternating plank tones, darker
|
|
8477
|
+
seams, staggered plank-end joints, a soft catch-light under each
|
|
8478
|
+
seam, and a few faint grain dashes. Pairs with the 3D room's cozy
|
|
8479
|
+
modern interior (voice-3d.js · brainstorm = window / chest / sage
|
|
8480
|
+
sofa) so the directors sit on a warm wood floor. Bright + cozy. */
|
|
8496
8481
|
.roundtable-stage[data-floor="brainstorm"] {
|
|
8497
|
-
--floor-bg: #
|
|
8482
|
+
--floor-bg: #B0976E;
|
|
8498
8483
|
--rt-chair-name: var(--lime, #6FB572);
|
|
8499
|
-
--floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='128' height='
|
|
8484
|
+
--floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='128' height='64' shape-rendering='crispEdges'><rect width='128' height='64' fill='%23B0976E'/><rect y='16' width='128' height='16' fill='%23A68C62'/><rect y='48' width='128' height='16' fill='%23A68C62'/><rect y='0' width='128' height='1' fill='%23C2A87E'/><rect y='16' width='128' height='1' fill='%23C2A87E'/><rect y='32' width='128' height='1' fill='%23C2A87E'/><rect y='48' width='128' height='1' fill='%23C2A87E'/><rect y='15' width='128' height='1' fill='%23877045'/><rect y='31' width='128' height='1' fill='%23877045'/><rect y='47' width='128' height='1' fill='%23877045'/><rect y='63' width='128' height='1' fill='%23877045'/><rect x='40' y='0' width='1' height='16' fill='%23877045'/><rect x='96' y='16' width='1' height='16' fill='%23877045'/><rect x='20' y='32' width='1' height='16' fill='%23877045'/><rect x='72' y='48' width='1' height='16' fill='%23877045'/><rect x='10' y='6' width='14' height='1' fill='%23A38755'/><rect x='60' y='22' width='18' height='1' fill='%23A38755'/><rect x='100' y='40' width='14' height='1' fill='%23A38755'/><rect x='30' y='56' width='16' height='1' fill='%23A38755'/></svg>");
|
|
8500
8485
|
}
|
|
8501
8486
|
/* ─── constructive · BLACK-WALNUT STAGGERED PLANK ─────────────
|
|
8502
8487
|
192×192 tileable boardroom floor · 8 rows of horizontal black-
|
|
@@ -8555,10 +8540,16 @@
|
|
|
8555
8540
|
arms in antique brass, polished-gold corner dots. Smaller tile
|
|
8556
8541
|
than the burgundy carpet so the pattern reads as dense metallic
|
|
8557
8542
|
weave at room scale (executive boardroom polish). */
|
|
8543
|
+
/* ─── critique · DEEP-GREEN WOOL CARPET ───────────────────────
|
|
8544
|
+
64×64 near-solid deep forest-green carpet · only a whisper of
|
|
8545
|
+
low-contrast wool flecks (texture, not pattern). Replaced the
|
|
8546
|
+
busy brass-on-gunmetal grid, which read too loud. Deep green is
|
|
8547
|
+
the classic gentleman's-study pairing for the mahogany +
|
|
8548
|
+
brass executive panel walls — calm, grounding, high-end. */
|
|
8558
8549
|
.roundtable-stage[data-floor="critique"] {
|
|
8559
|
-
--floor-bg: #
|
|
8550
|
+
--floor-bg: #2F3A30;
|
|
8560
8551
|
--rt-chair-name: #E8C788;
|
|
8561
|
-
--floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='
|
|
8552
|
+
--floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='64' height='64' shape-rendering='crispEdges'><rect width='64' height='64' fill='%232F3A30'/><rect x='6' y='8' width='1' height='1' fill='%233C493C'/><rect x='24' y='5' width='1' height='1' fill='%233C493C'/><rect x='42' y='12' width='1' height='1' fill='%233C493C'/><rect x='58' y='7' width='1' height='1' fill='%233C493C'/><rect x='14' y='26' width='1' height='1' fill='%233C493C'/><rect x='34' y='22' width='1' height='1' fill='%233C493C'/><rect x='50' y='30' width='1' height='1' fill='%233C493C'/><rect x='9' y='44' width='1' height='1' fill='%233C493C'/><rect x='28' y='50' width='1' height='1' fill='%233C493C'/><rect x='46' y='46' width='1' height='1' fill='%233C493C'/><rect x='56' y='56' width='1' height='1' fill='%233C493C'/><rect x='18' y='60' width='1' height='1' fill='%233C493C'/><rect x='12' y='14' width='1' height='1' fill='%23283227'/><rect x='30' y='9' width='1' height='1' fill='%23283227'/><rect x='48' y='6' width='1' height='1' fill='%23283227'/><rect x='60' y='18' width='1' height='1' fill='%23283227'/><rect x='8' y='34' width='1' height='1' fill='%23283227'/><rect x='36' y='30' width='1' height='1' fill='%23283227'/><rect x='52' y='40' width='1' height='1' fill='%23283227'/><rect x='20' y='52' width='1' height='1' fill='%23283227'/><rect x='40' y='58' width='1' height='1' fill='%23283227'/><rect x='26' y='36' width='1' height='1' fill='%23283227'/><rect x='54' y='24' width='1' height='1' fill='%23283227'/><rect x='15' y='42' width='1' height='1' fill='%23283227'/></svg>");
|
|
8562
8553
|
}
|
|
8563
8554
|
|
|
8564
8555
|
/* 8-bit pixel-art conference table · sized to occupy the
|
|
@@ -9360,8 +9351,8 @@
|
|
|
9360
9351
|
|
|
9361
9352
|
/* Initial-letter avatar chip · fallback when the user has no
|
|
9362
9353
|
`prefs.avatarSeed` configured. Mirrors the sidebar foot's
|
|
9363
|
-
|
|
9364
|
-
|
|
9354
|
+
placeholder chip so the seat reads as "the user, no custom
|
|
9355
|
+
avatar" rather than empty space. */
|
|
9365
9356
|
.rt-avatar-user.rt-avatar-initial {
|
|
9366
9357
|
display: inline-flex;
|
|
9367
9358
|
align-items: center;
|
|
@@ -9374,14 +9365,14 @@
|
|
|
9374
9365
|
font-weight: 700;
|
|
9375
9366
|
text-transform: uppercase;
|
|
9376
9367
|
}
|
|
9377
|
-
/*
|
|
9378
|
-
|
|
9379
|
-
to the same box as a regular
|
|
9380
|
-
wrapper itself stays transparent
|
|
9381
|
-
its own surface, and the chair
|
|
9382
|
-
letterboxed area (the
|
|
9383
|
-
portrait 48×56, so without transparent
|
|
9384
|
-
above + below the
|
|
9368
|
+
/* Portrait avatar wrapper · when an avatar seed IS present, an
|
|
9369
|
+
Avatar3DSnap PNG is injected by app.renderRoundTable; this
|
|
9370
|
+
wrapper constrains it to the same box as a regular
|
|
9371
|
+
`<img class="rt-avatar">`. The wrapper itself stays transparent
|
|
9372
|
+
— the rendered PNG carries its own surface, and the chair
|
|
9373
|
+
sprite shows through any letterboxed area (the snap is square,
|
|
9374
|
+
the seat-avatar box is portrait 48×56, so without transparent
|
|
9375
|
+
bg a dark block paints above + below the image). */
|
|
9385
9376
|
.rt-avatar-user.has-pixel-av {
|
|
9386
9377
|
display: flex;
|
|
9387
9378
|
align-items: center;
|
|
@@ -11422,14 +11413,14 @@
|
|
|
11422
11413
|
display: grid !important;
|
|
11423
11414
|
align-content: center !important;
|
|
11424
11415
|
justify-items: stretch !important;
|
|
11425
|
-
/* `.chat`
|
|
11426
|
-
`.
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
padding
|
|
11416
|
+
/* `.chat` carries gutter padding meant for the room view (top
|
|
11417
|
+
clears the frosted `.room-head`, bottom reserves space for
|
|
11418
|
+
the floating `.ib-stack`). Neither applies to the new-room /
|
|
11419
|
+
new-agent composers — `.room-head:empty` and there's no
|
|
11420
|
+
`.ib-stack`, so the inherited padding just throws off centring
|
|
11421
|
+
and steals horizontal width from the cmp card. Zero it out;
|
|
11422
|
+
`.cmp` carries its own breathing room. */
|
|
11423
|
+
padding: 0 !important;
|
|
11433
11424
|
}
|
|
11434
11425
|
.chat.chat--composer > [data-chat-messages] {
|
|
11435
11426
|
width: 100% !important;
|
|
@@ -11450,8 +11441,11 @@
|
|
|
11450
11441
|
`50vh - 110px` puts the hero+input vertical midpoint at 50vh.
|
|
11451
11442
|
`max(32px, …)` keeps the original padding floor on tiny viewports
|
|
11452
11443
|
where the calc would go negative.
|
|
11453
|
-
Toggled by JS (updateComposerOverflow) on render + resize.
|
|
11454
|
-
|
|
11444
|
+
Toggled by JS (updateComposerOverflow) on render + resize.
|
|
11445
|
+
Scoped to NOT apply when the new-agent composer (`.ag-cmp`) is
|
|
11446
|
+
mounted — the agent composer keeps the default grid-centre
|
|
11447
|
+
layout regardless of natural height (per user spec). */
|
|
11448
|
+
.chat.chat--composer.chat--composer-overflow:not(:has(.ag-cmp)) {
|
|
11455
11449
|
display: block !important;
|
|
11456
11450
|
align-content: initial !important;
|
|
11457
11451
|
overflow-y: auto;
|
|
@@ -12322,7 +12316,7 @@
|
|
|
12322
12316
|
.cmp-celeb-card {
|
|
12323
12317
|
position: relative;
|
|
12324
12318
|
display: grid;
|
|
12325
|
-
grid-template-columns:
|
|
12319
|
+
grid-template-columns: 64px 1fr;
|
|
12326
12320
|
gap: 14px;
|
|
12327
12321
|
align-items: stretch;
|
|
12328
12322
|
width: 100%;
|
|
@@ -12343,12 +12337,8 @@
|
|
|
12343
12337
|
display: flex;
|
|
12344
12338
|
align-items: center;
|
|
12345
12339
|
justify-content: center;
|
|
12346
|
-
width:
|
|
12347
|
-
height:
|
|
12348
|
-
border-radius: 4px;
|
|
12349
|
-
overflow: hidden;
|
|
12350
|
-
background: var(--panel);
|
|
12351
|
-
border: 1px solid var(--line);
|
|
12340
|
+
width: 64px;
|
|
12341
|
+
height: 64px;
|
|
12352
12342
|
}
|
|
12353
12343
|
.cmp-celeb-img {
|
|
12354
12344
|
width: 100%;
|
|
@@ -15764,11 +15754,18 @@
|
|
|
15764
15754
|
<script src="room-settings.js" defer></script>
|
|
15765
15755
|
<link rel="stylesheet" href="adjourn-overlay.css">
|
|
15766
15756
|
<!-- Avatar skill must load before any UI that calls it (new-agent, user-settings). -->
|
|
15767
|
-
|
|
15757
|
+
<!-- avatar-3d-snap · shared head-and-shoulders 3D portrait helper
|
|
15758
|
+
(replaces the retired AvatarSkill 8-bit SVG generator). Loads as
|
|
15759
|
+
a classic script so window.Avatar3DSnap is available before app.js
|
|
15760
|
+
init(). Internally lazy-loads three.js + avatar-3d.js only when a
|
|
15761
|
+
portrait is actually requested. -->
|
|
15762
|
+
<script src="avatar-3d-snap.js" defer></script>
|
|
15768
15763
|
<link rel="stylesheet" href="agent-profile.css">
|
|
15769
15764
|
<script src="agent-profile.js" defer></script>
|
|
15770
15765
|
<link rel="stylesheet" href="new-agent.css">
|
|
15771
15766
|
<script src="new-agent.js" defer></script>
|
|
15767
|
+
<link rel="stylesheet" href="avatar3d-editor.css">
|
|
15768
|
+
<script type="module" src="avatar3d-editor.js"></script>
|
|
15772
15769
|
<link rel="stylesheet" href="user-settings.css">
|
|
15773
15770
|
<script type="module" src="keys-store.js"></script>
|
|
15774
15771
|
<script src="key-validators.js" defer></script>
|
|
@@ -15778,6 +15775,7 @@
|
|
|
15778
15775
|
<script src="mention-picker.js" defer></script>
|
|
15779
15776
|
<script src="auto-hide-scroll.js" defer></script>
|
|
15780
15777
|
<link rel="stylesheet" href="onboarding.css">
|
|
15778
|
+
<script src="core-avatars.js" defer></script>
|
|
15781
15779
|
<script src="voice-3d-banner.js" defer></script>
|
|
15782
15780
|
<script src="onboarding.js" defer></script>
|
|
15783
15781
|
<link rel="stylesheet" href="voice-onboarding.css">
|
|
@@ -15795,9 +15793,9 @@
|
|
|
15795
15793
|
<!-- voice-3d · voxel pixel-art 3D round-table renderer. ES module
|
|
15796
15794
|
(imports three.js from /vendor/three.module.min.js). Loads in
|
|
15797
15795
|
parallel with app.js · no ordering dependency, app.js feature-
|
|
15798
|
-
detects window.VoiceStage3D at render time.
|
|
15799
|
-
|
|
15800
|
-
|
|
15796
|
+
detects window.VoiceStage3D at render time. The voice room is
|
|
15797
|
+
3D-only as of 2026-05; the legacy 2D SVG stage + the user-
|
|
15798
|
+
settings toggle have been retired. -->
|
|
15801
15799
|
<script type="module" src="voice-3d.js"></script>
|
|
15802
15800
|
<!-- voice-recorder · meeting capture. Lazy module · only allocates
|
|
15803
15801
|
anything on first start(). Loaded BEFORE app.js so the global
|
|
@@ -16078,12 +16076,7 @@
|
|
|
16078
16076
|
sidebar uses); only the logo + rooms + agents expand. -->
|
|
16079
16077
|
<aside class="mini-sidebar" aria-label="Collapsed sidebar navigation">
|
|
16080
16078
|
<div class="mini-top">
|
|
16081
|
-
<button type="button" class="mini-btn mini-logo" data-sidebar-expand data-i18n-tip="sidebar_expand" data-tip="Expand sidebar" aria-label="Expand sidebar">
|
|
16082
|
-
<span class="mini-logo-av">
|
|
16083
|
-
<img class="cl-open" src="/avatars/chair.svg" alt="" aria-hidden="true">
|
|
16084
|
-
<img class="cl-blink" src="/avatars/chair-blink.svg" alt="" aria-hidden="true">
|
|
16085
|
-
</span>
|
|
16086
|
-
</button>
|
|
16079
|
+
<button type="button" class="mini-btn mini-logo" data-sidebar-expand data-i18n-tip="sidebar_expand" data-tip="Expand sidebar" aria-label="Expand sidebar"></button>
|
|
16087
16080
|
<button type="button" class="mini-btn mini-new-room" data-convene-trigger data-i18n-tip="sidebar_new_room" data-tip="New room" aria-label="New room"></button>
|
|
16088
16081
|
<button type="button" class="mini-btn mini-new-agent" data-agent-composer-trigger data-i18n-tip="sidebar_new_agent" data-tip="New agent" aria-label="New agent"></button>
|
|
16089
16082
|
<button type="button" class="mini-btn mini-search" data-search-trigger data-tip="Search" aria-label="Search"></button>
|
|
@@ -17018,6 +17011,22 @@
|
|
|
17018
17011
|
const lsGet = (k) => { try { return localStorage.getItem(k); } catch (_) { return null; } };
|
|
17019
17012
|
const lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch (_) {} };
|
|
17020
17013
|
|
|
17014
|
+
/* Anti-flash boot guard · runs SYNCHRONOUSLY during parse, before
|
|
17015
|
+
app.js's deferred init paints the new-room composer. If this
|
|
17016
|
+
refresh will restore a saved agent profile, mask the room view
|
|
17017
|
+
now (see the `.bb-restoring-agent` CSS rule) so the composer never
|
|
17018
|
+
flashes under the profile. Cleared by clearAgentRestoreGuard once
|
|
17019
|
+
the profile opens or the restore gives up. */
|
|
17020
|
+
if (lsGet(TAB_KEY) === "agents") {
|
|
17021
|
+
const _savedAgent = lsGet(AGENTS_KEY);
|
|
17022
|
+
if (_savedAgent && _savedAgent !== "new") {
|
|
17023
|
+
document.documentElement.classList.add("bb-restoring-agent");
|
|
17024
|
+
}
|
|
17025
|
+
}
|
|
17026
|
+
function clearAgentRestoreGuard() {
|
|
17027
|
+
document.documentElement.classList.remove("bb-restoring-agent");
|
|
17028
|
+
}
|
|
17029
|
+
|
|
17021
17030
|
/* Truth source for "does this room exist?" is `window.app.rooms`,
|
|
17022
17031
|
not the DOM. The sidebar may not have rendered yet on first
|
|
17023
17032
|
restore (app.init's loadInitial is async and runs concurrently
|
|
@@ -17124,14 +17133,25 @@
|
|
|
17124
17133
|
|
|
17125
17134
|
function applyAgentsSubState(sub) {
|
|
17126
17135
|
if (!sub || sub === "new") {
|
|
17136
|
+
clearAgentRestoreGuard();
|
|
17127
17137
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
17128
17138
|
window.app.setComposerMode("agent");
|
|
17129
17139
|
}
|
|
17130
17140
|
return;
|
|
17131
17141
|
}
|
|
17132
17142
|
if (typeof window.openAgentProfile === "function") {
|
|
17133
|
-
try {
|
|
17143
|
+
try {
|
|
17144
|
+
window.openAgentProfile(sub);
|
|
17145
|
+
// Drop the boot guard only once the profile view is actually
|
|
17146
|
+
// visible. open() early-returns (leaving the agent view
|
|
17147
|
+
// hidden) when the agent record hasn't loaded yet — in that
|
|
17148
|
+
// case the retry tick calls us again and the room view stays
|
|
17149
|
+
// masked until the profile genuinely opens.
|
|
17150
|
+
const agentView = document.querySelector('[data-main-view="agent"]');
|
|
17151
|
+
if (agentView && !agentView.hasAttribute("hidden")) clearAgentRestoreGuard();
|
|
17152
|
+
}
|
|
17134
17153
|
catch (_) {
|
|
17154
|
+
clearAgentRestoreGuard();
|
|
17135
17155
|
lsSet(AGENTS_KEY, "new");
|
|
17136
17156
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
17137
17157
|
window.app.setComposerMode("agent");
|
|
@@ -17402,10 +17422,17 @@
|
|
|
17402
17422
|
const tick = () => {
|
|
17403
17423
|
attempts += 1;
|
|
17404
17424
|
const id = lsGet(AGENTS_KEY);
|
|
17405
|
-
if (!id || id === "new") return;
|
|
17425
|
+
if (!id || id === "new") { clearAgentRestoreGuard(); return; }
|
|
17406
17426
|
const haveAny = document.querySelector(".agents-scroll .agent-row[data-agent-profile]");
|
|
17407
|
-
if (haveAny) {
|
|
17427
|
+
if (haveAny) {
|
|
17428
|
+
applyAgentsSubState(id);
|
|
17429
|
+
// If the profile opened, the guard is already cleared and we
|
|
17430
|
+
// stop. If open() couldn't resolve the agent yet (guard still
|
|
17431
|
+
// set), keep retrying instead of leaving the room view masked.
|
|
17432
|
+
if (!document.documentElement.classList.contains("bb-restoring-agent")) return;
|
|
17433
|
+
}
|
|
17408
17434
|
if (attempts < 10) setTimeout(tick, 250);
|
|
17435
|
+
else clearAgentRestoreGuard(); // give up · reveal the composer
|
|
17409
17436
|
};
|
|
17410
17437
|
setTimeout(tick, 250);
|
|
17411
17438
|
}
|
package/public/new-agent.js
CHANGED
|
@@ -141,12 +141,15 @@
|
|
|
141
141
|
const rng = makeRng(seed);
|
|
142
142
|
return [0,0,0,0].map(() => Math.floor(rng() * 16).toString(16)).join("");
|
|
143
143
|
}
|
|
144
|
-
// Avatar generation delegates to the shared
|
|
145
|
-
// (
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
// Avatar generation delegates to the shared Avatar3DSnap helper
|
|
145
|
+
// (public/avatar-3d-snap.js). One source of truth for the 3D
|
|
146
|
+
// voxel head-and-shoulders portrait used everywhere a director
|
|
147
|
+
// face appears. The legacy AvatarSkill 8-bit SVG generator was
|
|
148
|
+
// retired in favour of this 3D pipeline.
|
|
149
|
+
async function generateAvatarDataUrl(seed) {
|
|
150
|
+
const snap = window.Avatar3DSnap;
|
|
151
|
+
if (!snap || typeof snap.generate !== "function") return "";
|
|
152
|
+
return snap.generate(seed);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
function modalHTML() {
|
|
@@ -612,16 +615,37 @@
|
|
|
612
615
|
if (!frame) return;
|
|
613
616
|
if (avatarState.placeholder) {
|
|
614
617
|
frame.classList.add("placeholder");
|
|
615
|
-
frame
|
|
618
|
+
// Empty frame · the wrapper's CSS placeholder (initial letter
|
|
619
|
+
// / texture) is the resting state until the user clicks
|
|
620
|
+
// regenerate. The legacy 8-bit SVG silhouette was retired.
|
|
621
|
+
frame.innerHTML = "";
|
|
616
622
|
if (seedEl) seedEl.textContent = "—";
|
|
617
623
|
if (rollEl) rollEl.textContent = "";
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
frame.classList.remove("placeholder");
|
|
627
|
+
const seedKey = avatarState.seed + "::" + avatarState.roll;
|
|
628
|
+
if (seedEl) seedEl.textContent = shortHash(avatarState.seed);
|
|
629
|
+
if (rollEl) rollEl.textContent = " · #" + avatarState.roll;
|
|
630
|
+
const snap = window.Avatar3DSnap;
|
|
631
|
+
const cached = snap && typeof snap.cacheGet === "function" ? snap.cacheGet(seedKey) : null;
|
|
632
|
+
if (cached) {
|
|
633
|
+
frame.innerHTML = `<img src="${cached}" alt="">`;
|
|
634
|
+
return;
|
|
624
635
|
}
|
|
636
|
+
frame.innerHTML = '<div class="na-avatar-loading" aria-hidden="true">…</div>';
|
|
637
|
+
if (!snap || typeof snap.generate !== "function") return;
|
|
638
|
+
const expected = seedKey;
|
|
639
|
+
snap.generate(seedKey).then((dataUrl) => {
|
|
640
|
+
if (!dataUrl) return;
|
|
641
|
+
// Bail if the user has already rolled again — only the latest
|
|
642
|
+
// seed's render should land in the frame.
|
|
643
|
+
const cur = avatarState.placeholder ? null : (avatarState.seed + "::" + avatarState.roll);
|
|
644
|
+
if (cur !== expected) return;
|
|
645
|
+
const f2 = modal.querySelector("[data-na-avatar]");
|
|
646
|
+
if (!f2) return;
|
|
647
|
+
f2.innerHTML = `<img src="${dataUrl}" alt="">`;
|
|
648
|
+
}).catch(() => { /* */ });
|
|
625
649
|
}
|
|
626
650
|
|
|
627
651
|
function positionDropdown() {
|
|
@@ -665,10 +689,10 @@
|
|
|
665
689
|
}
|
|
666
690
|
|
|
667
691
|
/** Regenerate the avatar by asking the LLM for a "vibe seed" derived
|
|
668
|
-
* from the director's name + bio, then painting the
|
|
669
|
-
* shared
|
|
670
|
-
* endpoint errors (no key, network, etc.) so the button
|
|
671
|
-
* produces a fresh face. */
|
|
692
|
+
* from the director's name + bio, then painting the 3D portrait
|
|
693
|
+
* via the shared Avatar3DSnap. Falls back to a local random seed
|
|
694
|
+
* if the endpoint errors (no key, network, etc.) so the button
|
|
695
|
+
* always produces a fresh face. */
|
|
672
696
|
async function regenerateAvatar() {
|
|
673
697
|
const name = modal.querySelector(".na-name-input").value.trim();
|
|
674
698
|
const desc = modal.querySelector(".na-desc-input").value.trim();
|
|
@@ -682,7 +706,7 @@
|
|
|
682
706
|
// Without a name, just produce a random seed locally — no point
|
|
683
707
|
// burning an LLM call on an empty form.
|
|
684
708
|
if (!name) {
|
|
685
|
-
avatarState.seed = (window.
|
|
709
|
+
avatarState.seed = (window.Avatar3DSnap?.randomSeed?.() || ("anon|" + Date.now()));
|
|
686
710
|
if (vibeEl) vibeEl.textContent = "";
|
|
687
711
|
paintAvatar();
|
|
688
712
|
return;
|
|
@@ -800,14 +824,16 @@
|
|
|
800
824
|
|
|
801
825
|
// Avatar → data URL. If the user never clicked "regenerate", we
|
|
802
826
|
// build one off the form values now so the agent has a real face.
|
|
827
|
+
// The render is async (3D portrait via Avatar3DSnap); falls back
|
|
828
|
+
// to an empty string when WebGL isn't available — the backend
|
|
829
|
+
// already tolerates a missing avatarPath.
|
|
803
830
|
let avatarSeed = avatarState.seed;
|
|
804
831
|
let avatarRoll = avatarState.roll || 1;
|
|
805
832
|
if (avatarState.placeholder) {
|
|
806
833
|
avatarSeed = (name + "|" + bio) || "anon";
|
|
807
834
|
avatarRoll = 1;
|
|
808
835
|
}
|
|
809
|
-
const
|
|
810
|
-
const avatarPath = "data:image/svg+xml;utf8," + encodeURIComponent(svg);
|
|
836
|
+
const avatarPath = (await generateAvatarDataUrl(avatarSeed + "::" + avatarRoll)) || "";
|
|
811
837
|
|
|
812
838
|
// Lock the button while the request is in flight.
|
|
813
839
|
create.disabled = true;
|