@clawlabz/clawskin 1.0.4 → 1.1.1

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/public/app.html CHANGED
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  /* ── Pixel HUD (KOF style) ── */
31
- .hud { position: fixed; font-family: 'Press Start 2P', monospace; z-index: 10; pointer-events: none; }
31
+ .hud { position: fixed; font-family: 'Press Start 2P', monospace; z-index: 10; pointer-events: none; zoom: var(--ui-scale, 1); }
32
32
  .hud * { pointer-events: auto; }
33
33
 
34
34
  /* Top-left: logo + mode */
@@ -121,6 +121,7 @@
121
121
  padding: 24px;
122
122
  width: 380px;
123
123
  max-width: 90vw;
124
+ zoom: var(--ui-scale, 1);
124
125
  }
125
126
  .conn-box h2 {
126
127
  font-size: 10px; color: #00f0ff; margin-bottom: 16px;
@@ -153,28 +154,39 @@
153
154
  /* ── Character Editor (overlay) ── */
154
155
  .editor-overlay {
155
156
  position: fixed; top: 0; right: 0; bottom: 0;
156
- width: 280px; max-width: 90vw;
157
+ width: 360px; max-width: 90vw;
157
158
  background: rgba(10,10,26,0.95);
158
159
  border-left: 2px solid rgba(0,240,255,0.3);
159
160
  z-index: 50;
160
161
  display: none;
161
162
  flex-direction: column;
162
163
  font-family: 'Press Start 2P', monospace;
163
- overflow-y: auto;
164
+ overflow: hidden;
164
165
  transform: translateX(100%);
165
166
  transition: transform 0.3s ease;
167
+ zoom: var(--ui-scale, 1);
166
168
  }
167
169
  .editor-overlay.show { display: flex; transform: translateX(0); }
168
170
  .editor-overlay .ed-header {
169
- padding: 12px; font-size: 8px; color: #ffcc00;
171
+ padding: 8px 12px; font-size: 8px; color: #ffcc00;
170
172
  border-bottom: 1px solid rgba(255,255,255,0.1);
171
173
  display: flex; justify-content: space-between; align-items: center;
172
174
  text-shadow: 0 0 6px rgba(255,204,0,0.4);
175
+ flex-shrink: 0;
173
176
  }
177
+ .editor-overlay .ed-header-btns {
178
+ display: flex; gap: 6px; align-items: center;
179
+ }
180
+ .editor-overlay .ed-header-btn {
181
+ font-family: 'Press Start 2P', monospace; font-size: 6px;
182
+ padding: 3px 8px; background: #0a0a1a; border: 1px solid #333;
183
+ color: #aaa; cursor: pointer; transition: all 0.2s;
184
+ }
185
+ .editor-overlay .ed-header-btn:hover { border-color: #00ff88; color: #00ff88; }
174
186
  .editor-overlay .ed-close {
175
187
  background: none; border: none; color: #666; font-size: 12px; cursor: pointer;
176
188
  }
177
- .editor-overlay .ed-body { padding: 12px; flex: 1; }
189
+ .editor-overlay .ed-body { padding: 12px; flex-shrink: 0; overflow-y: auto; max-height: 45%; }
178
190
  .editor-overlay .ed-row { margin-bottom: 10px; }
179
191
  .editor-overlay .ed-row label { display: block; font-size: 6px; color: #888; margin-bottom: 4px; }
180
192
  .editor-overlay .swatches { display: flex; gap: 4px; flex-wrap: wrap; }
@@ -201,6 +213,70 @@
201
213
  }
202
214
  .editor-overlay .ed-act:hover { border-color: #00ff88; color: #00ff88; }
203
215
 
216
+ /* ── Chat Panel (inside editor overlay) ── */
217
+ .ed-chat {
218
+ border-top: 1px solid rgba(0,240,255,0.2);
219
+ display: flex; flex-direction: column;
220
+ flex: 1; min-height: 0; overflow: hidden;
221
+ }
222
+ .ed-chat-header {
223
+ padding: 8px 12px; font-size: 7px; color: #00f0ff;
224
+ border-bottom: 1px solid rgba(255,255,255,0.05);
225
+ }
226
+ .ed-chat-messages {
227
+ flex: 1; overflow-y: auto; padding: 10px;
228
+ display: flex; flex-direction: column; gap: 8px;
229
+ }
230
+ .ed-chat-msg {
231
+ font-size: 8px; line-height: 1.8; padding: 8px 10px;
232
+ max-width: 90%; word-break: break-word;
233
+ }
234
+ .ed-chat-msg.agent {
235
+ align-self: flex-start;
236
+ background: rgba(180,74,255,0.1); border: 1px solid rgba(180,74,255,0.3);
237
+ color: #ccc;
238
+ }
239
+ .ed-chat-msg.user {
240
+ align-self: flex-end;
241
+ background: rgba(0,240,255,0.1); border: 1px solid rgba(0,240,255,0.3);
242
+ color: #e0e0f0;
243
+ }
244
+ .ed-chat-msg .msg-role {
245
+ font-size: 6px; color: #666; margin-bottom: 3px;
246
+ }
247
+ .ed-chat-msg.agent .msg-role { color: #b44aff; }
248
+ .ed-chat-msg.user .msg-role { color: #00f0ff; }
249
+ .ed-chat-typing {
250
+ align-self: flex-start; font-size: 8px; color: #b44aff;
251
+ padding: 8px 10px; animation: blink 1s infinite;
252
+ }
253
+ .ed-chat-empty {
254
+ font-size: 8px; color: #444; text-align: center; padding: 20px 8px;
255
+ }
256
+ .ed-chat-input-row {
257
+ display: flex; padding: 0;
258
+ border-top: 1px solid rgba(0,240,255,0.2);
259
+ flex-shrink: 0;
260
+ }
261
+ .ed-chat-input {
262
+ flex: 1; min-width: 0; width: 100%; padding: 10px 12px;
263
+ background: rgba(0,0,0,0.4); border: none; border-right: 1px solid rgba(0,240,255,0.15);
264
+ color: #e0e0f0; font-family: 'Press Start 2P', monospace; font-size: 8px;
265
+ outline: none; box-shadow: none; -webkit-appearance: none; border-radius: 0;
266
+ }
267
+ .ed-chat-input::placeholder { color: #444; }
268
+ .ed-chat-input:focus { background: rgba(0,240,255,0.05); outline: none; box-shadow: none; }
269
+ .ed-chat-send {
270
+ padding: 10px 14px; background: rgba(0,240,255,0.1);
271
+ border: none; color: #00f0ff;
272
+ font-family: 'Press Start 2P', monospace; font-size: 8px;
273
+ cursor: pointer; transition: all 0.2s; flex-shrink: 0;
274
+ }
275
+ .ed-chat-send:hover { background: rgba(0,240,255,0.25); color: #fff; }
276
+ cursor: pointer; transition: all 0.2s;
277
+ }
278
+ .ed-chat-send:hover { background: rgba(0,240,255,0.2); }
279
+
204
280
  /* ── Scene Picker (bottom-right) ── */
205
281
  .hud-scenes {
206
282
  bottom: 12px; right: 16px;
@@ -222,7 +298,7 @@
222
298
 
223
299
  <!-- ══ HUD: Top-Left ══ -->
224
300
  <div class="hud hud-top-left">
225
- <div class="hud-logo">CLAWSKIN</div>
301
+ <a href="https://github.com/clawlabz/clawskin" target="_blank" class="hud-logo" style="text-decoration:none;color:inherit;">CLAWSKIN</a>
226
302
  <div class="hud-mode demo" id="hud-mode">DEMO</div>
227
303
  </div>
228
304
 
@@ -267,12 +343,19 @@
267
343
  <div class="editor-overlay" id="editor-overlay">
268
344
  <div class="ed-header">
269
345
  <span id="ed-title">🎨 CUSTOMIZE</span>
270
- <button class="ed-close" onclick="closeEditor()">✕</button>
346
+ <div class="ed-header-btns">
347
+ <button class="ed-header-btn" onclick="edRandom()">🎲 RANDOM</button>
348
+ <button class="ed-close" onclick="closeEditor()">✕</button>
349
+ </div>
271
350
  </div>
272
351
  <div class="ed-body" id="ed-body"></div>
273
- <div class="ed-actions">
274
- <button class="ed-act" onclick="edRandom()">🎲 RANDOM</button>
275
- <button class="ed-act" onclick="edSave()">💾 SAVE</button>
352
+ <div class="ed-chat" id="ed-chat" style="display:none;">
353
+ <div class="ed-chat-header">💬 CHAT</div>
354
+ <div class="ed-chat-messages" id="ed-chat-messages"></div>
355
+ <div class="ed-chat-input-row">
356
+ <input type="text" class="ed-chat-input" id="ed-chat-input" placeholder="Message..." onkeydown="if(event.key==='Enter')sendChatMsg()" />
357
+ <button class="ed-chat-send" onclick="sendChatMsg()">▶</button>
358
+ </div>
276
359
  </div>
277
360
  </div>
278
361
 
@@ -317,6 +400,7 @@
317
400
 
318
401
  // ── Globals ──
319
402
  let app, editingSlot = null;
403
+ let chatSessionKey = null, chatStreamingEl = null;
320
404
 
321
405
  // ── Fullscreen canvas sizing ──
322
406
  // Fixed logical resolution — CSS scales it up with pixelated rendering.
@@ -324,6 +408,16 @@
324
408
  const LOGICAL_W = 960;
325
409
  const LOGICAL_H = 600;
326
410
 
411
+ // ── UI scaling for HUD/panels on large screens ──
412
+ function updateUIScale() {
413
+ // Only scale UI on screens wider than 2K (2560px).
414
+ // Below 2K, keep native size (scale=1) to avoid bloated HUD/panels.
415
+ const scale = window.innerWidth > 2560 ? window.innerWidth / 2560 : 1;
416
+ document.documentElement.style.setProperty('--ui-scale', scale.toFixed(2));
417
+ }
418
+ updateUIScale();
419
+ window.addEventListener('resize', updateUIScale);
420
+
327
421
  function resizeCanvas() {
328
422
  const c = document.getElementById('app-canvas');
329
423
  c.width = LOGICAL_W;
@@ -365,10 +459,15 @@
365
459
  document.getElementById('ed-title').textContent = '🎨 ' + slot.name;
366
460
  renderEditorBody(slot.character.config);
367
461
  document.getElementById('editor-overlay').classList.add('show');
462
+ loadChat(slot);
368
463
  }
369
464
  function closeEditor() {
370
465
  document.getElementById('editor-overlay').classList.remove('show');
371
466
  editingSlot = null;
467
+ chatSessionKey = null;
468
+ chatStreamingEl = null;
469
+ const chatEl = document.getElementById('ed-chat');
470
+ if (chatEl) chatEl.style.display = 'none';
372
471
  }
373
472
  function renderEditorBody(cfg) {
374
473
  const skins = SpriteGenerator.SKIN_TONES;
@@ -416,7 +515,134 @@
416
515
  editingSlot.updateConfig(cfg);
417
516
  renderEditorBody(cfg);
418
517
  }
419
- function edSave() { closeEditor(); }
518
+ // ── Agent Chat ──
519
+ function loadChat(slot) {
520
+ const chatEl = document.getElementById('ed-chat');
521
+ const msgsEl = document.getElementById('ed-chat-messages');
522
+ chatSessionKey = null;
523
+ chatStreamingEl = null;
524
+
525
+ // Only show chat in live mode with valid sessionKeys
526
+ if (!app || app.mode !== 'live' || !app.gateway?.connected) {
527
+ chatEl.style.display = 'none';
528
+ return;
529
+ }
530
+ const keys = slot.stateMapper ? [...slot.stateMapper.sessionKeys] : [];
531
+ if (keys.length === 0 || keys[0] === 'main') {
532
+ chatEl.style.display = 'none';
533
+ return;
534
+ }
535
+
536
+ chatSessionKey = keys[0];
537
+ chatEl.style.display = 'flex';
538
+ msgsEl.innerHTML = '<div class="ed-chat-empty">Loading...</div>';
539
+
540
+ app.gateway.getChatHistory(chatSessionKey, 50).then(result => {
541
+ if (editingSlot !== slot) return; // user switched agents
542
+ const messages = result?.messages || result || [];
543
+ if (!Array.isArray(messages) || messages.length === 0) {
544
+ msgsEl.innerHTML = '<div class="ed-chat-empty">No messages yet</div>';
545
+ return;
546
+ }
547
+ msgsEl.innerHTML = '';
548
+ for (const msg of messages) {
549
+ const role = msg.role === 'user' ? 'user' : 'agent';
550
+ const text = extractMsgText(msg);
551
+ if (text) appendChatMessage(role, text);
552
+ }
553
+ }).catch(() => {
554
+ if (editingSlot !== slot) return;
555
+ msgsEl.innerHTML = '<div class="ed-chat-empty">Could not load history</div>';
556
+ });
557
+ }
558
+
559
+ function extractMsgText(msg) {
560
+ if (!msg) return null;
561
+ if (typeof msg.text === 'string') return msg.text;
562
+ if (typeof msg.content === 'string') return msg.content;
563
+ if (Array.isArray(msg.content)) {
564
+ for (const block of msg.content) {
565
+ if (block.type === 'text' && block.text) return block.text;
566
+ }
567
+ }
568
+ if (typeof msg.message === 'object') return extractMsgText(msg.message);
569
+ return null;
570
+ }
571
+
572
+ function appendChatMessage(role, text) {
573
+ const msgsEl = document.getElementById('ed-chat-messages');
574
+ // Remove empty placeholder if present
575
+ const empty = msgsEl.querySelector('.ed-chat-empty');
576
+ if (empty) empty.remove();
577
+
578
+ const div = document.createElement('div');
579
+ div.className = 'ed-chat-msg ' + role;
580
+ const roleLabel = role === 'user' ? 'YOU' : (editingSlot?.name || 'AGENT');
581
+ div.innerHTML = '<div class="msg-role">' + esc(roleLabel) + '</div>' + esc(text);
582
+ msgsEl.appendChild(div);
583
+ msgsEl.scrollTop = msgsEl.scrollHeight;
584
+ }
585
+
586
+ function sendChatMsg() {
587
+ const input = document.getElementById('ed-chat-input');
588
+ const text = (input.value || '').trim();
589
+ if (!text || !chatSessionKey || !app?.gateway?.connected) return;
590
+ input.value = '';
591
+
592
+ appendChatMessage('user', text);
593
+ app.gateway.sendChat(chatSessionKey, text).catch(err => {
594
+ appendChatMessage('agent', 'Error: ' + (err.message || 'Send failed'));
595
+ });
596
+ }
597
+
598
+ function handleEditorChatEvent(agentId, payload) {
599
+ if (!editingSlot || !payload) return;
600
+ // Only process events for the agent we're currently editing
601
+ if (editingSlot.agentId !== agentId) return;
602
+
603
+ const msgsEl = document.getElementById('ed-chat-messages');
604
+ if (!msgsEl) return;
605
+
606
+ switch (payload.state) {
607
+ case 'delta': {
608
+ if (!chatStreamingEl) {
609
+ // Remove empty placeholder
610
+ const empty = msgsEl.querySelector('.ed-chat-empty');
611
+ if (empty) empty.remove();
612
+ chatStreamingEl = document.createElement('div');
613
+ chatStreamingEl.className = 'ed-chat-typing';
614
+ chatStreamingEl.textContent = '...';
615
+ msgsEl.appendChild(chatStreamingEl);
616
+ msgsEl.scrollTop = msgsEl.scrollHeight;
617
+ }
618
+ break;
619
+ }
620
+ case 'final': {
621
+ if (chatStreamingEl) {
622
+ chatStreamingEl.remove();
623
+ chatStreamingEl = null;
624
+ }
625
+ const text = extractMsgText(payload.message || payload);
626
+ if (text) appendChatMessage('agent', text);
627
+ break;
628
+ }
629
+ case 'error': {
630
+ if (chatStreamingEl) {
631
+ chatStreamingEl.remove();
632
+ chatStreamingEl = null;
633
+ }
634
+ appendChatMessage('agent', 'Something went wrong...');
635
+ break;
636
+ }
637
+ case 'aborted': {
638
+ if (chatStreamingEl) {
639
+ chatStreamingEl.remove();
640
+ chatStreamingEl = null;
641
+ }
642
+ break;
643
+ }
644
+ }
645
+ }
420
646
 
421
647
  // ── Pet Manager ──
422
648
  function togglePetPanel() {
@@ -519,6 +745,99 @@
519
745
  if (slot) openEditor(slot);
520
746
  }
521
747
 
748
+ // ── Easter Egg Helpers ──
749
+ function showWeatherToast(weather) {
750
+ const labels = { sunny:'☀️ Sunny', night:'🌙 Night', rain:'🌧️ Rain', snow:'❄️ Snow',
751
+ purple:'💜 Neon', red_alert:'🔴 Alert', matrix:'💚 Matrix', blackout:'🌑 Blackout',
752
+ fog:'🌫️ Fog' };
753
+ showDecoToast(labels[weather] || weather);
754
+ }
755
+ function showDecoToast(msg) {
756
+ const toast = document.createElement('div');
757
+ toast.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:200;' +
758
+ 'font-family:"Press Start 2P",monospace;font-size:10px;color:#fff;background:rgba(10,10,26,0.9);' +
759
+ 'border:1px solid rgba(0,240,255,0.4);padding:10px 18px;pointer-events:none;text-align:center;' +
760
+ 'text-shadow:0 0 6px rgba(0,240,255,0.4);opacity:0;transition:opacity 0.3s;';
761
+ toast.textContent = msg;
762
+ document.body.appendChild(toast);
763
+ requestAnimationFrame(() => toast.style.opacity = '1');
764
+ setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 1800);
765
+ }
766
+ function handleClockClick(scene, cx, cy, w, wallSafe) {
767
+ let clockX, clockY;
768
+ if (scene.name === 'office') { clockX = w - 45; clockY = wallSafe + 8; }
769
+ else if (scene.name === 'hacker') { clockX = w - 30; clockY = wallSafe + 8; }
770
+ else if (scene.name === 'cafe') { clockX = w - 40; clockY = wallSafe + 8; }
771
+ else return false;
772
+ if (Math.hypot(cx - clockX, cy - (clockY + 10)) < 16) {
773
+ const now = new Date();
774
+ const h = now.getHours().toString().padStart(2,'0');
775
+ const m = now.getMinutes().toString().padStart(2,'0');
776
+ const s = now.getSeconds().toString().padStart(2,'0');
777
+ showDecoToast('🕐 ' + h + ':' + m + ':' + s);
778
+ return true;
779
+ }
780
+ return false;
781
+ }
782
+ function handlePlantClick(cx, cy, w, floorY, h) {
783
+ const floorH = h - floorY;
784
+ // Left plant areas
785
+ if (cx < 30 && cy > floorY + floorH * 0.25 && cy < floorY + floorH * 0.40) {
786
+ showDecoToast(['🌱 *wiggle*', '🌿 Water me!', '🪴 I\'m growing!'][Math.floor(Math.random()*3)]);
787
+ return true;
788
+ }
789
+ // Right plant areas
790
+ if (cx > w - 30 && cy > floorY + floorH * 0.60 && cy < floorY + floorH * 0.80) {
791
+ showDecoToast(['🌵 Don\'t touch!', '🌻 Hello!', '🍀 Lucky!'][Math.floor(Math.random()*3)]);
792
+ return true;
793
+ }
794
+ return false;
795
+ }
796
+ function getRandomQuote() {
797
+ const quotes = [
798
+ '📚 "Code is poetry"',
799
+ '📖 "Ship it!"',
800
+ '📚 "RTFM" - Ancient proverb',
801
+ '📖 "It works on my machine"',
802
+ '📚 "Hello, World!"',
803
+ '📖 "There are only 2 hard things..."',
804
+ ];
805
+ return quotes[Math.floor(Math.random() * quotes.length)];
806
+ }
807
+ function getRandomIdea() {
808
+ const ideas = [
809
+ '💡 TODO: Fix everything',
810
+ '📝 Sprint #42: Ship it',
811
+ '💡 Refactor the refactor',
812
+ '📝 Q3 Goal: ????→Profit',
813
+ '💡 "Just one more feature"',
814
+ '📝 Bug ≠ Feature ...or is it?',
815
+ ];
816
+ return ideas[Math.floor(Math.random() * ideas.length)];
817
+ }
818
+ function getRandomArcade() {
819
+ const msgs = [
820
+ '🕹️ INSERT COIN',
821
+ '👾 HIGH SCORE: 99999',
822
+ '🎮 GAME OVER',
823
+ '🕹️ PLAYER 1 READY',
824
+ '👾 LEVEL UP!',
825
+ '🎮 CONTINUE? 9...8...7...',
826
+ ];
827
+ return msgs[Math.floor(Math.random() * msgs.length)];
828
+ }
829
+ function getRandomMenu() {
830
+ const items = [
831
+ '☕ Espresso ....... $4',
832
+ '🧋 Matcha Latte ... $6',
833
+ '🍰 Cheesecake ..... $7',
834
+ '🥐 Croissant ...... $3',
835
+ '🫖 Earl Grey ...... $3',
836
+ '🍪 Cookie ......... $2',
837
+ ];
838
+ return items[Math.floor(Math.random() * items.length)];
839
+ }
840
+
522
841
  // ── Scene Picker ──
523
842
  function initScenePicker() {
524
843
  const scenes = app.scenes;
@@ -569,23 +888,100 @@
569
888
  updateHUD();
570
889
  };
571
890
 
572
- // Canvas click open editor for clicked agent
891
+ // Hook Gateway events to also feed the editor chat panel
892
+ const origGatewayEvent = app._onGatewayEvent.bind(app);
893
+ app._onGatewayEvent = (event) => {
894
+ origGatewayEvent(event);
895
+ // Route chat events to the editor chat panel
896
+ if (event?.event === 'chat' && editingSlot) {
897
+ const sessionKey = event.payload?.sessionKey;
898
+ if (sessionKey) {
899
+ const match = sessionKey.match(/^agent:([^:]+):/);
900
+ const agentId = match ? match[1] : 'main';
901
+ handleEditorChatEvent(agentId, event.payload);
902
+ }
903
+ }
904
+ };
905
+
906
+ // Canvas click → agents, pets, weather, treats, decorations
573
907
  document.getElementById('app-canvas').addEventListener('click', (e) => {
574
- if (app.mode !== 'live') return;
575
908
  const c = document.getElementById('app-canvas');
576
909
  const rect = c.getBoundingClientRect();
577
910
  const sx = app.width / rect.width;
578
911
  const sy = app.height / rect.height;
579
912
  const cx = (e.clientX - rect.left) * sx;
580
913
  const cy = (e.clientY - rect.top) * sy;
581
- for (const slot of app.agents) {
582
- if (slot.hitTest(cx, cy)) {
583
- openEditor(slot);
914
+
915
+ // Live mode: agent click → open editor
916
+ if (app.mode === 'live') {
917
+ for (const slot of app.agents) {
918
+ if (slot.hitTest(cx, cy)) {
919
+ openEditor(slot);
920
+ return;
921
+ }
922
+ }
923
+ }
924
+
925
+ // Pet click → reaction bubble
926
+ if (app.petManager && app.petManager.handleClick(cx, cy)) return;
927
+
928
+ // Window/weather click — cycle weather
929
+ if (app.currentScene && app.currentScene.getWindowRect) {
930
+ const wr = app.currentScene.getWindowRect();
931
+ if (cx >= wr.x && cx <= wr.x + wr.w && cy >= wr.y && cy <= wr.y + wr.h) {
932
+ const newWeather = app.currentScene.cycleWeather();
933
+ showWeatherToast(newWeather);
934
+ return;
935
+ }
936
+ }
937
+
938
+ // Decoration clicks
939
+ if (app.currentScene) {
940
+ const scene = app.currentScene;
941
+ const h = app.height, w = app.width;
942
+ const floorY = Math.round(h * 0.40);
943
+ const wallSafe = Math.round(h * 0.12);
944
+
945
+ // Clock click → show time
946
+ if (handleClockClick(scene, cx, cy, w, wallSafe)) return;
947
+
948
+ // Plant click → wiggle emoji
949
+ if (handlePlantClick(cx, cy, w, floorY, h)) return;
950
+
951
+ // Bookshelf click → random quote
952
+ if (scene.name === 'office' && cx < 65 && cy >= wallSafe && cy <= wallSafe + 64) {
953
+ showDecoToast(getRandomQuote());
954
+ return;
955
+ }
956
+
957
+ // Whiteboard click — office
958
+ if (scene.name === 'office' && cx >= w*0.15 && cx <= w*0.15+70 && cy >= wallSafe+2 && cy <= wallSafe+46) {
959
+ showDecoToast(getRandomIdea());
584
960
  return;
585
961
  }
962
+
963
+ // Arcade click — hacker
964
+ if (scene.name === 'hacker' && cx >= w - 28 && cy >= floorY + (h-floorY)*0.30 && cy <= floorY + (h-floorY)*0.30 + 68) {
965
+ showDecoToast(getRandomArcade());
966
+ return;
967
+ }
968
+
969
+ // Menu board click — cafe
970
+ if (scene.name === 'cafe' && cx >= 25 && cx <= 90 && cy >= wallSafe && cy <= wallSafe + 44) {
971
+ showDecoToast(getRandomMenu());
972
+ return;
973
+ }
974
+ }
975
+
976
+ // Floor click (below floorY) → drop treat
977
+ const floorY = Math.round(app.height * 0.40);
978
+ if (cy > floorY + 20 && app.petManager && app.petManager.pets.length > 0) {
979
+ app.petManager.dropTreat(cx, cy);
980
+ return;
586
981
  }
587
- // Clicked empty space → close editor
588
- closeEditor();
982
+
983
+ // Empty space → close editor
984
+ if (app.mode === 'live') closeEditor();
589
985
  });
590
986
 
591
987
  initScenePicker();
package/public/index.html CHANGED
@@ -29,48 +29,6 @@
29
29
  </div>
30
30
  </section>
31
31
 
32
- <!-- ═══ Features ═══ -->
33
- <section class="section">
34
- <h2 class="section-title">✨ Features</h2>
35
- <p class="section-desc">Everything you need to bring your AI agent to life.</p>
36
- <div class="feature-grid">
37
- <div class="feature-card">
38
- <div class="feature-icon">🎨</div>
39
- <div class="feature-title">Character Creator</div>
40
- <div class="feature-desc">5 skin tones, 5 hairstyles, 5 outfits, accessories, and pets. Mix and match to create your unique pixel agent.</div>
41
- </div>
42
- <div class="feature-card">
43
- <div class="feature-icon">🏠</div>
44
- <div class="feature-title">Multiple Scenes</div>
45
- <div class="feature-desc">Cozy office, hacker den, or warm café — each with ambient animations, particles, and mood lighting.</div>
46
- </div>
47
- <div class="feature-card">
48
- <div class="feature-icon">🔄</div>
49
- <div class="feature-title">Live State Sync</div>
50
- <div class="feature-desc">Connect to OpenClaw Gateway via WebSocket. Your pixel agent reflects real AI status — typing, thinking, sleeping.</div>
51
- </div>
52
- <div class="feature-card">
53
- <div class="feature-icon">💬</div>
54
- <div class="feature-title">Speech Bubbles</div>
55
- <div class="feature-desc">Typewriter-effect dialogue bubbles show what your agent is working on. Thought bubbles for thinking mode.</div>
56
- </div>
57
- <div class="feature-card">
58
- <div class="feature-icon">🐱</div>
59
- <div class="feature-title">Pixel Pets</div>
60
- <div class="feature-desc">Add a cat, dog, or robot companion that hangs out with your agent. Because everyone needs a sidekick.</div>
61
- </div>
62
- </div>
63
- </section>
64
-
65
- <!-- ═══ Customizer ═══ -->
66
- <section class="section">
67
- <div>
68
- <h2 class="section-title">🎨 Customize Your Agent</h2>
69
- <p class="section-desc">Click options below to customize in real-time. Changes are reflected instantly in the demo above.</p>
70
- <div id="character-editor"></div>
71
- </div>
72
- </section>
73
-
74
32
  <!-- ═══ Get Started ═══ -->
75
33
  <section class="section">
76
34
  <h2 class="section-title">🚀 Get Started</h2>
@@ -103,23 +61,53 @@
103
61
  </div>
104
62
  </section>
105
63
 
106
- <!-- ═══ Requirements ═══ -->
107
- <section class="section" style="padding-top:0;">
108
- <h2 class="section-title">📋 What You Need</h2>
109
- <div class="feature-grid" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
64
+ <!-- ═══ Features ═══ -->
65
+ <section class="section">
66
+ <h2 class="section-title">✨ Features</h2>
67
+ <p class="section-desc">Everything you need to bring your AI agent to life.</p>
68
+ <div class="feature-grid">
69
+ <div class="feature-card">
70
+ <div class="feature-icon">🎨</div>
71
+ <div class="feature-title">Character Creator</div>
72
+ <div class="feature-desc">5 skin tones, 5 hairstyles, 5 outfits, accessories, and pets. Mix and match to create your unique pixel agent.</div>
73
+ </div>
74
+ <div class="feature-card">
75
+ <div class="feature-icon">🏠</div>
76
+ <div class="feature-title">Multiple Scenes</div>
77
+ <div class="feature-desc">Cozy office, hacker den, or warm café — each with ambient animations, particles, and mood lighting.</div>
78
+ </div>
79
+ <div class="feature-card">
80
+ <div class="feature-icon">🔄</div>
81
+ <div class="feature-title">Live State Sync</div>
82
+ <div class="feature-desc">Connect to OpenClaw Gateway via WebSocket. Your pixel agent reflects real AI status — typing, thinking, sleeping.</div>
83
+ </div>
110
84
  <div class="feature-card">
111
- <div class="feature-icon">🧩</div>
112
- <div class="feature-title">OpenClaw Gateway</div>
113
- <div class="feature-desc">Any running OpenClaw instance (v1.0+). <a href="https://docs.openclaw.ai" target="_blank" style="color:var(--accent-cyan);">Install OpenClaw →</a></div>
85
+ <div class="feature-icon">💬</div>
86
+ <div class="feature-title">Agent Chat</div>
87
+ <div class="feature-desc">Click any agent to open a chat panel. Talk to your AI directly with real-time streaming responses — per-agent sessions, right from the pixel world.</div>
88
+ </div>
89
+ <div class="feature-card">
90
+ <div class="feature-icon">🐱</div>
91
+ <div class="feature-title">Pixel Pets</div>
92
+ <div class="feature-desc">5 pet types — cat, dog, robot, bird, hamster — each with autonomous AI behavior. Click to interact, drop treats, and watch them roam freely.</div>
114
93
  </div>
115
94
  <div class="feature-card">
116
- <div class="feature-icon">🔐</div>
117
- <div class="feature-title">Auth Token</div>
118
- <div class="feature-desc">If your Gateway has auth enabled, grab the token from <code style="font-size:0.7rem;">~/.openclaw/openclaw.json</code> or run <code style="font-size:0.7rem;">openclaw dashboard</code></div>
95
+ <div class="feature-icon">🎯</div>
96
+ <div class="feature-title">Easter Eggs</div>
97
+ <div class="feature-desc">Click around to discover hidden interactions — cycle weather on windows, check the clock, read the whiteboard, or try the arcade cabinet.</div>
119
98
  </div>
120
99
  </div>
121
100
  </section>
122
101
 
102
+ <!-- ═══ Customizer ═══ -->
103
+ <section class="section">
104
+ <div>
105
+ <h2 class="section-title">🎨 Customize Your Agent</h2>
106
+ <p class="section-desc">Click options below to customize in real-time. Changes are reflected instantly in the demo above.</p>
107
+ <div id="character-editor"></div>
108
+ </div>
109
+ </section>
110
+
123
111
  <!-- ═══ Footer ═══ -->
124
112
  <footer class="footer">
125
113
  <p>
@@ -127,7 +115,7 @@
127
115
  — Part of the <a href="https://github.com/clawlabz/openclaw" target="_blank">OpenClaw</a> ecosystem
128
116
  </p>
129
117
  <p style="margin-top:0.5rem;">
130
- Built with Canvas 2D · Zero Dependencies · <a href="https://github.com/clawlabz/clawskin" target="_blank">⭐ Star on GitHub</a>
118
+ Built by <a href="https://clawlabz.xyz" target="_blank">ClawLabz</a> · <a href="https://github.com/clawlabz/clawskin" target="_blank">⭐ Star on GitHub</a>
131
119
  </p>
132
120
  </footer>
133
121
 
@@ -282,6 +282,9 @@ class GatewayClient {
282
282
  async getChatHistory(sessionKey, limit = 50) {
283
283
  return this.request('chat.history', { sessionKey, limit });
284
284
  }
285
+ async sendChat(sessionKey, message) {
286
+ return this.request('chat.send', { sessionKey, message, idempotencyKey: this._uuid() });
287
+ }
285
288
  async getSessionsList(opts = {}) {
286
289
  return this.request('sessions.list', { activeMinutes: 120, ...opts });
287
290
  }