privateboard 0.1.38 → 0.1.41

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 (78) hide show
  1. package/dist/boot.js +327 -50
  2. package/dist/boot.js.map +1 -1
  3. package/dist/cli.js +327 -50
  4. package/dist/cli.js.map +1 -1
  5. package/dist/server.js +201 -40
  6. package/dist/server.js.map +1 -1
  7. package/dist/version.d.ts +1 -1
  8. package/dist/version.js +1 -1
  9. package/dist/version.js.map +1 -1
  10. package/package.json +1 -1
  11. package/public/__avatar3d_test.html +156 -0
  12. package/public/agent-overlay.css +14 -6
  13. package/public/agent-overlay.js +6 -6
  14. package/public/agent-profile.css +9 -12
  15. package/public/agent-profile.js +91 -38
  16. package/public/app.js +471 -528
  17. package/public/avatar-3d-snap.js +205 -0
  18. package/public/avatar-3d.js +836 -0
  19. package/public/avatar-customizer.html +274 -0
  20. package/public/avatar3d-editor.css +240 -0
  21. package/public/avatar3d-editor.js +484 -0
  22. package/public/avatars/3d/chair.png +0 -0
  23. package/public/avatars/3d/first-principles.png +0 -0
  24. package/public/avatars/3d/historian.png +0 -0
  25. package/public/avatars/3d/long-horizon.png +0 -0
  26. package/public/avatars/3d/phenomenologist.png +0 -0
  27. package/public/avatars/3d/socrates.png +0 -0
  28. package/public/avatars/3d/user-empathy.png +0 -0
  29. package/public/avatars/3d/value-investor.png +0 -0
  30. package/public/core-avatars.js +86 -0
  31. package/public/home-3d-loader.js +15 -4
  32. package/public/home-3d-mock.js +25 -14
  33. package/public/home.html +78 -16
  34. package/public/i18n.js +8 -4
  35. package/public/icons/avatar_1779855104027.glb +0 -0
  36. package/public/icons/logo.png +0 -0
  37. package/public/icons/new-style.glb +0 -0
  38. package/public/icons/new-style2.glb +0 -0
  39. package/public/icons/new-style3.glb +0 -0
  40. package/public/icons/new-style4.glb +0 -0
  41. package/public/icons/new-style5.glb +0 -0
  42. package/public/icons/new-style6.glb +0 -0
  43. package/public/icons/office.glb +0 -0
  44. package/public/icons/stuff.glb +0 -0
  45. package/public/index.html +169 -141
  46. package/public/magazine.html +1 -1
  47. package/public/new-agent.js +46 -20
  48. package/public/newspaper.html +1 -1
  49. package/public/office-viewer.html +340 -0
  50. package/public/ppt.html +1 -1
  51. package/public/stuff-viewer.html +330 -0
  52. package/public/thread.css +16 -15
  53. package/public/user-settings.css +7 -31
  54. package/public/user-settings.js +75 -89
  55. package/public/vendor/BufferGeometryUtils.js +1434 -0
  56. package/public/vendor/DRACOLoader.js +739 -0
  57. package/public/vendor/GLTFLoader.js +4860 -0
  58. package/public/vendor/RoomEnvironment.js +185 -0
  59. package/public/vendor/SkeletonUtils.js +496 -0
  60. package/public/vendor/draco/draco_decoder.js +34 -0
  61. package/public/vendor/draco/draco_decoder.wasm +0 -0
  62. package/public/vendor/draco/draco_encoder.js +33 -0
  63. package/public/vendor/draco/draco_wasm_wrapper.js +117 -0
  64. package/public/vendor/meshopt_decoder.module.js +196 -0
  65. package/public/voice-3d-banner.js +19 -7
  66. package/public/voice-3d.js +1407 -432
  67. package/public/voice-replay.js +21 -0
  68. package/public/avatar-skill.js +0 -629
  69. package/public/avatars/chair-blink.svg +0 -1
  70. package/public/avatars/chair.svg +0 -1
  71. package/public/avatars/first-principles.svg +0 -1
  72. package/public/avatars/historian.svg +0 -1
  73. package/public/avatars/long-horizon.svg +0 -1
  74. package/public/avatars/phenomenologist.svg +0 -1
  75. package/public/avatars/socrates.svg +0 -1
  76. package/public/avatars/user-empathy.svg +0 -1
  77. package/public/avatars/value-investor.svg +0 -1
  78. 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
- /* The chair avatar is a two-layer stack: open-eye frame always
1111
- visible, closed-eye frame flashed in by the blink keyframe. The
1112
- wrapper carries an occasional idle hop. */
1113
- .mini-logo-av {
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;
@@ -1358,6 +1309,7 @@
1358
1309
  .agent-row .agent-row-av {
1359
1310
  width: 32px;
1360
1311
  height: 32px;
1312
+ margin-top: -10px;
1361
1313
  image-rendering: pixelated;
1362
1314
  image-rendering: crisp-edges;
1363
1315
  flex-shrink: 0;
@@ -2059,11 +2011,11 @@
2059
2011
  position: relative;
2060
2012
  overflow: hidden;
2061
2013
  }
2062
- /* Pixel-art variant · when prefs.avatarSeed exists, app.renderUserBlock
2063
- swaps the initial-letter chip for an AvatarSkill SVG. The pixel SVG
2064
- is transparent, so we drop the fill entirely (was --lime, then
2065
- --bg) and let it sit directly on the surface behind it no boxed
2066
- square around the character. */
2014
+ /* 3D portrait variant · when prefs.avatarSeed exists,
2015
+ app.renderUserBlock swaps the initial-letter chip for the
2016
+ Avatar3DSnap rendered PNG. The image fills the chip; we drop the
2017
+ filled background so the portrait reads cleanly against the
2018
+ surface behind it. */
2067
2019
  .user-av.has-pixel-av {
2068
2020
  background: transparent;
2069
2021
  }
@@ -2229,6 +2181,17 @@
2229
2181
  and leaving an empty band below. */
2230
2182
  .main-view[hidden] { display: none !important; }
2231
2183
 
2184
+ /* Boot guard · on refresh, the sidebar restore tick has to wait for
2185
+ the agent rows to mount before openAgentProfile fires, but app.js's
2186
+ handleRoute paints the new-room composer into the room view straight
2187
+ away on an empty hash. Mask the room view while a saved agent profile
2188
+ is being restored so the composer doesn't flash under the profile.
2189
+ The guard class is set SYNCHRONOUSLY during parse (top of the sidebar
2190
+ IIFE, before app.js's deferred init runs) and cleared the moment the
2191
+ profile opens or the restore gives up. `!important` beats app.js's
2192
+ own removeAttribute("hidden") on the room view during that window. */
2193
+ html.bb-restoring-agent .main-view[data-main-view="room"] { display: none !important; }
2194
+
2232
2195
  /* ─── All Reports · clean vertical reading list ──────────────────
2233
2196
  Single-hierarchy archive view · header → filter chips → date-
2234
2197
  grouped reading rows. Each row uses pure typography (kicker /
@@ -4202,6 +4165,34 @@
4202
4165
  letter-spacing: 0.1em;
4203
4166
  }
4204
4167
  .resume-btn:hover { background: var(--bg); color: var(--lime); }
4168
+ /* Stop voice replay · same compact mono chrome as pause/resume, but in
4169
+ red so it reads as a distinct "terminate playback" action. Rendered
4170
+ by app.js only while a replay is active in this room, and it sits in
4171
+ the pause/resume slot (those are hidden on adjourned rooms, where
4172
+ replay runs) so it lands exactly where the eye looks for transport. */
4173
+ .replay-stop-btn {
4174
+ padding: 4px 10px;
4175
+ background: transparent;
4176
+ color: var(--text);
4177
+ border: 0.5px solid var(--red, #B5706A);
4178
+ font-family: var(--mono);
4179
+ font-size: 10px;
4180
+ font-weight: 700;
4181
+ cursor: pointer;
4182
+ text-decoration: none;
4183
+ text-transform: uppercase;
4184
+ letter-spacing: 0.1em;
4185
+ transition: all 0.12s;
4186
+ }
4187
+ .replay-stop-btn .replay-stop-icon {
4188
+ color: var(--red, #B5706A);
4189
+ margin: 0 1px;
4190
+ letter-spacing: -0.05em;
4191
+ }
4192
+ .replay-stop-btn:hover {
4193
+ background: rgba(181, 112, 106, 0.12);
4194
+ color: var(--red, #B5706A);
4195
+ }
4205
4196
  /* ─── Empty state ───
4206
4197
  No room loaded (zero-room first-run, or after a navigation). The
4207
4198
  starter panel renders inside .chat; the input bar, speaking queue,
@@ -7357,11 +7348,10 @@
7357
7348
  border: 0.5px solid var(--line-strong);
7358
7349
  font-family: var(--mono);
7359
7350
  }
7360
- /* Pixel-art variant · when prefs.avatarSeed is set, renderChat swaps
7361
- the initial-letter chip for the AvatarSkill SVG (same seed the
7362
- sidebar foot + preference overlay use). The SVG provides its own
7363
- surface, so drop the border + bg + flex centering and let it fill
7364
- the chip edge-to-edge. */
7351
+ /* Legacy pixel-av variant · the renderChat path no longer mounts
7352
+ `msg-av-pixel` (Avatar3DSnap PNG fills `<img class="msg-av">`
7353
+ directly), but the CSS is kept for any third-party plugins that
7354
+ still write into that node. */
7365
7355
  .msg.user .msg-av.msg-av-pixel {
7366
7356
  border: none;
7367
7357
  background: var(--bg);
@@ -7547,7 +7537,10 @@
7547
7537
  -webkit-box-orient: vertical;
7548
7538
  overflow: hidden;
7549
7539
  }
7550
- .msg-time { color: var(--text-faint); margin-left: auto; }
7540
+ /* Flows inline right after the badges (no longer pinned far-right via
7541
+ margin-left:auto) so the far-right end of the meta line is free for
7542
+ the hover thread toolbar to sit at the row's end. */
7543
+ .msg-time { color: var(--text-faint); }
7551
7544
 
7552
7545
  .msg-bubble {
7553
7546
  padding: 0;
@@ -8480,23 +8473,16 @@
8480
8473
  --rt-bottom-reserve: 130px;
8481
8474
  }
8482
8475
 
8483
- /* ─── brainstorm · SOIL + GRASS floor ────────────────────────
8484
- 128×128 tileable terrain · grass base with two irregular dirt
8485
- patches placed inside the tile bounds so when the pattern
8486
- repeats the patches read as organic "islands" of bare earth in
8487
- a grass field rather than a gridded indoor layout. Subtle
8488
- tonal blobs in the grass (darker / lighter) break the flat
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. */
8476
+ /* ─── brainstorm · WARM-OAK PLANK floor ───────────────────────
8477
+ 128×64 light warm-oak planks · alternating plank tones, darker
8478
+ seams, staggered plank-end joints, a soft catch-light under each
8479
+ seam, and a few faint grain dashes. Pairs with the 3D room's cozy
8480
+ modern interior (voice-3d.js · brainstorm = window / chest / sage
8481
+ sofa) so the directors sit on a warm wood floor. Bright + cozy. */
8496
8482
  .roundtable-stage[data-floor="brainstorm"] {
8497
- --floor-bg: #5E6B47;
8483
+ --floor-bg: #B0976E;
8498
8484
  --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='128' shape-rendering='crispEdges'><rect width='128' height='128' fill='%235E6B47'/><rect x='0' y='8' width='20' height='16' fill='%234F5C3D'/><rect x='104' y='20' width='20' height='20' fill='%234F5C3D'/><rect x='88' y='64' width='24' height='16' fill='%234F5C3D'/><rect x='0' y='64' width='12' height='20' fill='%234F5C3D'/><rect x='44' y='64' width='16' height='8' fill='%234F5C3D'/><rect x='72' y='8' width='12' height='8' fill='%236E7A52'/><rect x='120' y='80' width='8' height='12' fill='%236E7A52'/><rect x='48' y='84' width='8' height='8' fill='%236E7A52'/><rect x='48' y='24' width='32' height='32' fill='%235C4838'/><rect x='44' y='28' width='4' height='24' fill='%235C4838'/><rect x='80' y='28' width='4' height='24' fill='%235C4838'/><rect x='52' y='20' width='24' height='4' fill='%235C4838'/><rect x='52' y='56' width='20' height='4' fill='%235C4838'/><rect x='56' y='16' width='12' height='4' fill='%235C4838'/><rect x='56' y='32' width='8' height='8' fill='%234A3A28'/><rect x='68' y='40' width='4' height='8' fill='%234A3A28'/><rect x='60' y='24' width='4' height='4' fill='%236E5A48'/><rect x='68' y='48' width='4' height='4' fill='%236E5A48'/><rect x='58' y='44' width='2' height='2' fill='%236B6258'/><rect x='72' y='32' width='2' height='2' fill='%236B6258'/><rect x='50' y='38' width='2' height='2' fill='%236B6258'/><rect x='16' y='96' width='24' height='20' fill='%235C4838'/><rect x='12' y='100' width='4' height='12' fill='%235C4838'/><rect x='40' y='100' width='4' height='12' fill='%235C4838'/><rect x='20' y='92' width='16' height='4' fill='%235C4838'/><rect x='22' y='104' width='8' height='4' fill='%234A3A28'/><rect x='20' y='100' width='4' height='4' fill='%236E5A48'/><rect x='24' y='108' width='2' height='2' fill='%236B6258'/><rect x='32' y='98' width='2' height='2' fill='%236B6258'/><rect x='8' y='40' width='1' height='2' fill='%238FA068'/><rect x='24' y='72' width='1' height='2' fill='%238FA068'/><rect x='104' y='56' width='1' height='2' fill='%238FA068'/><rect x='120' y='104' width='1' height='2' fill='%238FA068'/><rect x='88' y='44' width='1' height='2' fill='%238FA068'/><rect x='4' y='88' width='1' height='2' fill='%238FA068'/><rect x='64' y='88' width='1' height='2' fill='%238FA068'/><rect x='92' y='24' width='1' height='2' fill='%238FA068'/><rect x='44' y='62' width='1' height='2' fill='%238FA068'/><rect x='80' y='120' width='1' height='2' fill='%238FA068'/><rect x='12' y='12' width='1' height='2' fill='%238FA068'/><rect x='112' y='60' width='1' height='2' fill='%238FA068'/></svg>");
8485
+ --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
8486
  }
8501
8487
  /* ─── constructive · BLACK-WALNUT STAGGERED PLANK ─────────────
8502
8488
  192×192 tileable boardroom floor · 8 rows of horizontal black-
@@ -8555,10 +8541,16 @@
8555
8541
  arms in antique brass, polished-gold corner dots. Smaller tile
8556
8542
  than the burgundy carpet so the pattern reads as dense metallic
8557
8543
  weave at room scale (executive boardroom polish). */
8544
+ /* ─── critique · DEEP-GREEN WOOL CARPET ───────────────────────
8545
+ 64×64 near-solid deep forest-green carpet · only a whisper of
8546
+ low-contrast wool flecks (texture, not pattern). Replaced the
8547
+ busy brass-on-gunmetal grid, which read too loud. Deep green is
8548
+ the classic gentleman's-study pairing for the mahogany +
8549
+ brass executive panel walls — calm, grounding, high-end. */
8558
8550
  .roundtable-stage[data-floor="critique"] {
8559
- --floor-bg: #3E362C;
8551
+ --floor-bg: #2F3A30;
8560
8552
  --rt-chair-name: #E8C788;
8561
- --floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' shape-rendering='crispEdges'><rect width='16' height='16' fill='%233E362C'/><rect x='6' y='7' width='4' height='2' fill='%23BA9050'/><rect x='7' y='6' width='2' height='4' fill='%23BA9050'/><rect x='7' y='2' width='2' height='1' fill='%239F7C42'/><rect x='7' y='13' width='2' height='1' fill='%239F7C42'/><rect x='2' y='7' width='1' height='2' fill='%239F7C42'/><rect x='13' y='7' width='1' height='2' fill='%239F7C42'/><rect x='1' y='1' width='1' height='1' fill='%23E8C788'/><rect x='14' y='1' width='1' height='1' fill='%23E8C788'/><rect x='1' y='14' width='1' height='1' fill='%23E8C788'/><rect x='14' y='14' width='1' height='1' fill='%23E8C788'/></svg>");
8553
+ --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
8554
  }
8563
8555
 
8564
8556
  /* 8-bit pixel-art conference table · sized to occupy the
@@ -9360,8 +9352,8 @@
9360
9352
 
9361
9353
  /* Initial-letter avatar chip · fallback when the user has no
9362
9354
  `prefs.avatarSeed` configured. Mirrors the sidebar foot's
9363
- pre-AvatarSkill chip so the seat reads as "the user, no
9364
- custom avatar" rather than empty space. */
9355
+ placeholder chip so the seat reads as "the user, no custom
9356
+ avatar" rather than empty space. */
9365
9357
  .rt-avatar-user.rt-avatar-initial {
9366
9358
  display: inline-flex;
9367
9359
  align-items: center;
@@ -9374,14 +9366,14 @@
9374
9366
  font-weight: 700;
9375
9367
  text-transform: uppercase;
9376
9368
  }
9377
- /* Pixel-art avatar wrapper · when an avatar seed IS present, the
9378
- SVG is injected by AvatarSkill; this wrapper just constrains it
9379
- to the same box as a regular `<img class="rt-avatar">`. The
9380
- wrapper itself stays transparent — the AvatarSkill SVG carries
9381
- its own surface, and the chair sprite shows through any
9382
- letterboxed area (the SVG is square, the seat-avatar box is
9383
- portrait 48×56, so without transparent bg a dark block paints
9384
- above + below the SVG). */
9369
+ /* Portrait avatar wrapper · when an avatar seed IS present, an
9370
+ Avatar3DSnap PNG is injected by app.renderRoundTable; this
9371
+ wrapper constrains it to the same box as a regular
9372
+ `<img class="rt-avatar">`. The wrapper itself stays transparent
9373
+ — the rendered PNG carries its own surface, and the chair
9374
+ sprite shows through any letterboxed area (the snap is square,
9375
+ the seat-avatar box is portrait 48×56, so without transparent
9376
+ bg a dark block paints above + below the image). */
9385
9377
  .rt-avatar-user.has-pixel-av {
9386
9378
  display: flex;
9387
9379
  align-items: center;
@@ -11422,14 +11414,14 @@
11422
11414
  display: grid !important;
11423
11415
  align-content: center !important;
11424
11416
  justify-items: stretch !important;
11425
- /* `.chat` reserves 140 px of bottom padding for the floating
11426
- `.ib-stack` (input-bar + speaking-queue strips). The
11427
- new-room / new-agent composers don't mount an `.ib-stack`
11428
- the composer's own `.cmp-toolbar` IS the input row — so
11429
- inheriting 140 px just pushes the composer off-centre by
11430
- ~70 px. Reset to a balanced gutter that matches the top
11431
- padding for visual symmetry. */
11432
- padding-bottom: 14px !important;
11417
+ /* `.chat` carries gutter padding meant for the room view (top
11418
+ clears the frosted `.room-head`, bottom reserves space for
11419
+ the floating `.ib-stack`). Neither applies to the new-room /
11420
+ new-agent composers `.room-head:empty` and there's no
11421
+ `.ib-stack`, so the inherited padding just throws off centring
11422
+ and steals horizontal width from the cmp card. Zero it out;
11423
+ `.cmp` carries its own breathing room. */
11424
+ padding: 0 !important;
11433
11425
  }
11434
11426
  .chat.chat--composer > [data-chat-messages] {
11435
11427
  width: 100% !important;
@@ -11450,8 +11442,11 @@
11450
11442
  `50vh - 110px` puts the hero+input vertical midpoint at 50vh.
11451
11443
  `max(32px, …)` keeps the original padding floor on tiny viewports
11452
11444
  where the calc would go negative.
11453
- Toggled by JS (updateComposerOverflow) on render + resize. */
11454
- .chat.chat--composer.chat--composer-overflow {
11445
+ Toggled by JS (updateComposerOverflow) on render + resize.
11446
+ Scoped to NOT apply when the new-agent composer (`.ag-cmp`) is
11447
+ mounted — the agent composer keeps the default grid-centre
11448
+ layout regardless of natural height (per user spec). */
11449
+ .chat.chat--composer.chat--composer-overflow:not(:has(.ag-cmp)) {
11455
11450
  display: block !important;
11456
11451
  align-content: initial !important;
11457
11452
  overflow-y: auto;
@@ -12322,7 +12317,7 @@
12322
12317
  .cmp-celeb-card {
12323
12318
  position: relative;
12324
12319
  display: grid;
12325
- grid-template-columns: 56px 1fr;
12320
+ grid-template-columns: 64px 1fr;
12326
12321
  gap: 14px;
12327
12322
  align-items: stretch;
12328
12323
  width: 100%;
@@ -12343,12 +12338,8 @@
12343
12338
  display: flex;
12344
12339
  align-items: center;
12345
12340
  justify-content: center;
12346
- width: 56px;
12347
- height: 56px;
12348
- border-radius: 4px;
12349
- overflow: hidden;
12350
- background: var(--panel);
12351
- border: 1px solid var(--line);
12341
+ width: 64px;
12342
+ height: 64px;
12352
12343
  }
12353
12344
  .cmp-celeb-img {
12354
12345
  width: 100%;
@@ -15764,11 +15755,18 @@
15764
15755
  <script src="room-settings.js" defer></script>
15765
15756
  <link rel="stylesheet" href="adjourn-overlay.css">
15766
15757
  <!-- Avatar skill must load before any UI that calls it (new-agent, user-settings). -->
15767
- <script src="avatar-skill.js" defer></script>
15758
+ <!-- avatar-3d-snap · shared head-and-shoulders 3D portrait helper
15759
+ (replaces the retired AvatarSkill 8-bit SVG generator). Loads as
15760
+ a classic script so window.Avatar3DSnap is available before app.js
15761
+ init(). Internally lazy-loads three.js + avatar-3d.js only when a
15762
+ portrait is actually requested. -->
15763
+ <script src="avatar-3d-snap.js" defer></script>
15768
15764
  <link rel="stylesheet" href="agent-profile.css">
15769
15765
  <script src="agent-profile.js" defer></script>
15770
15766
  <link rel="stylesheet" href="new-agent.css">
15771
15767
  <script src="new-agent.js" defer></script>
15768
+ <link rel="stylesheet" href="avatar3d-editor.css">
15769
+ <script type="module" src="avatar3d-editor.js"></script>
15772
15770
  <link rel="stylesheet" href="user-settings.css">
15773
15771
  <script type="module" src="keys-store.js"></script>
15774
15772
  <script src="key-validators.js" defer></script>
@@ -15778,6 +15776,7 @@
15778
15776
  <script src="mention-picker.js" defer></script>
15779
15777
  <script src="auto-hide-scroll.js" defer></script>
15780
15778
  <link rel="stylesheet" href="onboarding.css">
15779
+ <script src="core-avatars.js" defer></script>
15781
15780
  <script src="voice-3d-banner.js" defer></script>
15782
15781
  <script src="onboarding.js" defer></script>
15783
15782
  <link rel="stylesheet" href="voice-onboarding.css">
@@ -15795,9 +15794,9 @@
15795
15794
  <!-- voice-3d · voxel pixel-art 3D round-table renderer. ES module
15796
15795
  (imports three.js from /vendor/three.module.min.js). Loads in
15797
15796
  parallel with app.js · no ordering dependency, app.js feature-
15798
- detects window.VoiceStage3D at render time. Toggle in user-
15799
- settings (or localStorage["boardroom.stage3d"]) decides whether
15800
- to use it · default on, falls back to the legacy 2D SVG path. -->
15797
+ detects window.VoiceStage3D at render time. The voice room is
15798
+ 3D-only as of 2026-05; the legacy 2D SVG stage + the user-
15799
+ settings toggle have been retired. -->
15801
15800
  <script type="module" src="voice-3d.js"></script>
15802
15801
  <!-- voice-recorder · meeting capture. Lazy module · only allocates
15803
15802
  anything on first start(). Loaded BEFORE app.js so the global
@@ -16078,12 +16077,7 @@
16078
16077
  sidebar uses); only the logo + rooms + agents expand. -->
16079
16078
  <aside class="mini-sidebar" aria-label="Collapsed sidebar navigation">
16080
16079
  <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>
16080
+ <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
16081
  <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
16082
  <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
16083
  <button type="button" class="mini-btn mini-search" data-search-trigger data-tip="Search" aria-label="Search"></button>
@@ -17018,6 +17012,22 @@
17018
17012
  const lsGet = (k) => { try { return localStorage.getItem(k); } catch (_) { return null; } };
17019
17013
  const lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch (_) {} };
17020
17014
 
17015
+ /* Anti-flash boot guard · runs SYNCHRONOUSLY during parse, before
17016
+ app.js's deferred init paints the new-room composer. If this
17017
+ refresh will restore a saved agent profile, mask the room view
17018
+ now (see the `.bb-restoring-agent` CSS rule) so the composer never
17019
+ flashes under the profile. Cleared by clearAgentRestoreGuard once
17020
+ the profile opens or the restore gives up. */
17021
+ if (lsGet(TAB_KEY) === "agents") {
17022
+ const _savedAgent = lsGet(AGENTS_KEY);
17023
+ if (_savedAgent && _savedAgent !== "new") {
17024
+ document.documentElement.classList.add("bb-restoring-agent");
17025
+ }
17026
+ }
17027
+ function clearAgentRestoreGuard() {
17028
+ document.documentElement.classList.remove("bb-restoring-agent");
17029
+ }
17030
+
17021
17031
  /* Truth source for "does this room exist?" is `window.app.rooms`,
17022
17032
  not the DOM. The sidebar may not have rendered yet on first
17023
17033
  restore (app.init's loadInitial is async and runs concurrently
@@ -17124,14 +17134,25 @@
17124
17134
 
17125
17135
  function applyAgentsSubState(sub) {
17126
17136
  if (!sub || sub === "new") {
17137
+ clearAgentRestoreGuard();
17127
17138
  if (window.app && typeof window.app.setComposerMode === "function") {
17128
17139
  window.app.setComposerMode("agent");
17129
17140
  }
17130
17141
  return;
17131
17142
  }
17132
17143
  if (typeof window.openAgentProfile === "function") {
17133
- try { window.openAgentProfile(sub); }
17144
+ try {
17145
+ window.openAgentProfile(sub);
17146
+ // Drop the boot guard only once the profile view is actually
17147
+ // visible. open() early-returns (leaving the agent view
17148
+ // hidden) when the agent record hasn't loaded yet — in that
17149
+ // case the retry tick calls us again and the room view stays
17150
+ // masked until the profile genuinely opens.
17151
+ const agentView = document.querySelector('[data-main-view="agent"]');
17152
+ if (agentView && !agentView.hasAttribute("hidden")) clearAgentRestoreGuard();
17153
+ }
17134
17154
  catch (_) {
17155
+ clearAgentRestoreGuard();
17135
17156
  lsSet(AGENTS_KEY, "new");
17136
17157
  if (window.app && typeof window.app.setComposerMode === "function") {
17137
17158
  window.app.setComposerMode("agent");
@@ -17402,10 +17423,17 @@
17402
17423
  const tick = () => {
17403
17424
  attempts += 1;
17404
17425
  const id = lsGet(AGENTS_KEY);
17405
- if (!id || id === "new") return;
17426
+ if (!id || id === "new") { clearAgentRestoreGuard(); return; }
17406
17427
  const haveAny = document.querySelector(".agents-scroll .agent-row[data-agent-profile]");
17407
- if (haveAny) { applyAgentsSubState(id); return; }
17428
+ if (haveAny) {
17429
+ applyAgentsSubState(id);
17430
+ // If the profile opened, the guard is already cleared and we
17431
+ // stop. If open() couldn't resolve the agent yet (guard still
17432
+ // set), keep retrying instead of leaving the room view masked.
17433
+ if (!document.documentElement.classList.contains("bb-restoring-agent")) return;
17434
+ }
17408
17435
  if (attempts < 10) setTimeout(tick, 250);
17436
+ else clearAgentRestoreGuard(); // give up · reveal the composer
17409
17437
  };
17410
17438
  setTimeout(tick, 250);
17411
17439
  }
@@ -17595,27 +17623,27 @@
17595
17623
  <svg class="rt-plant rt-plant-2" viewBox="0 0 48 60"><use href="#vrp-plant-2"/></svg>
17596
17624
  <div class="rt-seat rt-seat-chair" style="left:50%; top:82%;">
17597
17625
  <svg class="rt-chair rt-chair--mod" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair-mod"/></svg>
17598
- <img class="rt-avatar" src="avatars/chair.svg" alt="">
17626
+ <img class="rt-avatar" src="avatars/3d/chair.png" alt="">
17599
17627
  <div class="rt-name">CHAIR</div>
17600
17628
  </div>
17601
17629
  <div class="rt-seat" style="left:14.89%; top:43.11%;">
17602
17630
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17603
- <img class="rt-avatar" src="avatars/socrates.svg" alt="">
17631
+ <img class="rt-avatar" src="avatars/3d/socrates.png" alt="">
17604
17632
  <div class="rt-name">SOCRATES</div>
17605
17633
  </div>
17606
17634
  <div class="rt-seat rt-seat-speaking" style="left:35.45%; top:33.37%;">
17607
17635
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17608
- <img class="rt-avatar" src="avatars/first-principles.svg" alt="">
17636
+ <img class="rt-avatar" src="avatars/3d/first-principles.png" alt="">
17609
17637
  <div class="rt-bubble">Speaking</div>
17610
17638
  </div>
17611
17639
  <div class="rt-seat" style="left:64.55%; top:33.37%;">
17612
17640
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17613
- <img class="rt-avatar" src="avatars/value-investor.svg" alt="">
17641
+ <img class="rt-avatar" src="avatars/3d/value-investor.png" alt="">
17614
17642
  <div class="rt-name">INVESTOR</div>
17615
17643
  </div>
17616
17644
  <div class="rt-seat" style="left:85.11%; top:43.11%;">
17617
17645
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17618
- <img class="rt-avatar" src="avatars/historian.svg" alt="">
17646
+ <img class="rt-avatar" src="avatars/3d/historian.png" alt="">
17619
17647
  <div class="rt-name">HISTORIAN</div>
17620
17648
  </div>
17621
17649
  </div>
@@ -17633,32 +17661,32 @@
17633
17661
  <svg class="rt-plant rt-plant-2" viewBox="0 0 48 60"><use href="#vrp-plant-2"/></svg>
17634
17662
  <div class="rt-seat rt-seat-chair" style="left:50%; top:82%;">
17635
17663
  <svg class="rt-chair rt-chair--mod" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair-mod"/></svg>
17636
- <img class="rt-avatar" src="avatars/chair.svg" alt="">
17664
+ <img class="rt-avatar" src="avatars/3d/chair.png" alt="">
17637
17665
  <div class="rt-name">CHAIR</div>
17638
17666
  </div>
17639
17667
  <div class="rt-seat" style="left:13.86%; top:44.44%;">
17640
17668
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17641
- <img class="rt-avatar" src="avatars/socrates.svg" alt="">
17669
+ <img class="rt-avatar" src="avatars/3d/socrates.png" alt="">
17642
17670
  <div class="rt-name">SOCRATES</div>
17643
17671
  </div>
17644
17672
  <div class="rt-seat" style="left:27.66%; top:35.44%;">
17645
17673
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17646
- <img class="rt-avatar" src="avatars/first-principles.svg" alt="">
17674
+ <img class="rt-avatar" src="avatars/3d/first-principles.png" alt="">
17647
17675
  <div class="rt-name">PRINCIPLES</div>
17648
17676
  </div>
17649
17677
  <div class="rt-seat" style="left:50%; top:32%;">
17650
17678
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17651
- <img class="rt-avatar" src="avatars/value-investor.svg" alt="">
17679
+ <img class="rt-avatar" src="avatars/3d/value-investor.png" alt="">
17652
17680
  <div class="rt-name">INVESTOR</div>
17653
17681
  </div>
17654
17682
  <div class="rt-seat rt-seat-speaking" style="left:72.34%; top:35.44%;">
17655
17683
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17656
- <img class="rt-avatar" src="avatars/historian.svg" alt="">
17684
+ <img class="rt-avatar" src="avatars/3d/historian.png" alt="">
17657
17685
  <div class="rt-bubble">Speaking</div>
17658
17686
  </div>
17659
17687
  <div class="rt-seat" style="left:86.14%; top:44.44%;">
17660
17688
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17661
- <img class="rt-avatar" src="avatars/long-horizon.svg" alt="">
17689
+ <img class="rt-avatar" src="avatars/3d/long-horizon.png" alt="">
17662
17690
  <div class="rt-name">HORIZON</div>
17663
17691
  </div>
17664
17692
  </div>
@@ -17676,22 +17704,22 @@
17676
17704
  <svg class="rt-plant rt-plant-2" viewBox="0 0 48 60"><use href="#vrp-plant-2"/></svg>
17677
17705
  <div class="rt-seat rt-seat-chair" style="left:50%; top:82%;">
17678
17706
  <svg class="rt-chair rt-chair--mod" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair-mod"/></svg>
17679
- <img class="rt-avatar" src="avatars/chair.svg" alt="">
17707
+ <img class="rt-avatar" src="avatars/3d/chair.png" alt="">
17680
17708
  <div class="rt-name">CHAIR</div>
17681
17709
  </div>
17682
17710
  <div class="rt-seat" style="left:17.09%; top:41%;">
17683
17711
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17684
- <img class="rt-avatar" src="avatars/socrates.svg" alt="">
17712
+ <img class="rt-avatar" src="avatars/3d/socrates.png" alt="">
17685
17713
  <div class="rt-name">SOCRATES</div>
17686
17714
  </div>
17687
17715
  <div class="rt-seat rt-seat-speaking" style="left:50%; top:32%;">
17688
17716
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17689
- <img class="rt-avatar" src="avatars/first-principles.svg" alt="">
17717
+ <img class="rt-avatar" src="avatars/3d/first-principles.png" alt="">
17690
17718
  <div class="rt-bubble">Speaking</div>
17691
17719
  </div>
17692
17720
  <div class="rt-seat" style="left:82.91%; top:41%;">
17693
17721
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17694
- <img class="rt-avatar" src="avatars/value-investor.svg" alt="">
17722
+ <img class="rt-avatar" src="avatars/3d/value-investor.png" alt="">
17695
17723
  <div class="rt-name">INVESTOR</div>
17696
17724
  </div>
17697
17725
  </div>
@@ -17709,27 +17737,27 @@
17709
17737
  <svg class="rt-plant rt-plant-2" viewBox="0 0 48 60"><use href="#vrp-plant-2"/></svg>
17710
17738
  <div class="rt-seat rt-seat-chair" style="left:50%; top:82%;">
17711
17739
  <svg class="rt-chair rt-chair--mod" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair-mod"/></svg>
17712
- <img class="rt-avatar" src="avatars/chair.svg" alt="">
17740
+ <img class="rt-avatar" src="avatars/3d/chair.png" alt="">
17713
17741
  <div class="rt-name">CHAIR</div>
17714
17742
  </div>
17715
17743
  <div class="rt-seat rt-seat-speaking" style="left:14.89%; top:43.11%;">
17716
17744
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17717
- <img class="rt-avatar" src="avatars/socrates.svg" alt="">
17745
+ <img class="rt-avatar" src="avatars/3d/socrates.png" alt="">
17718
17746
  <div class="rt-bubble">Speaking</div>
17719
17747
  </div>
17720
17748
  <div class="rt-seat" style="left:35.45%; top:33.37%;">
17721
17749
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17722
- <img class="rt-avatar" src="avatars/first-principles.svg" alt="">
17750
+ <img class="rt-avatar" src="avatars/3d/first-principles.png" alt="">
17723
17751
  <div class="rt-name">PRINCIPLES</div>
17724
17752
  </div>
17725
17753
  <div class="rt-seat" style="left:64.55%; top:33.37%;">
17726
17754
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17727
- <img class="rt-avatar" src="avatars/value-investor.svg" alt="">
17755
+ <img class="rt-avatar" src="avatars/3d/value-investor.png" alt="">
17728
17756
  <div class="rt-name">INVESTOR</div>
17729
17757
  </div>
17730
17758
  <div class="rt-seat" style="left:85.11%; top:43.11%;">
17731
17759
  <svg class="rt-chair" viewBox="0 0 32 40" preserveAspectRatio="xMidYMid meet"><use href="#vrp-chair"/></svg>
17732
- <img class="rt-avatar" src="avatars/phenomenologist.svg" alt="">
17760
+ <img class="rt-avatar" src="avatars/3d/phenomenologist.png" alt="">
17733
17761
  <div class="rt-name">PHENOM</div>
17734
17762
  </div>
17735
17763
  </div>
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=900, initial-scale=1">
6
6
  <title>Magazine · PrivateBoard</title>
7
- <link rel="icon" href="/avatars/chair.svg" type="image/svg+xml">
7
+ <link rel="icon" href="/avatars/3d/chair.png" type="image/svg+xml">
8
8
  <!-- Playfair Display for the Vogue-style high-contrast Didot
9
9
  logotype + serif headlines. Free Google font · no fallback
10
10
  drama since the system Tiempos / Bodoni stack picks up. -->