multimodalart HF Staff commited on
Commit
0d0b8f7
·
verified ·
1 Parent(s): 8728a6f

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +131 -27
index.html CHANGED
@@ -107,12 +107,33 @@
107
  .ghost:hover{color:var(--text);border-color:var(--accent)}
108
  button:disabled{opacity:.45;cursor:not-allowed;filter:none}
109
 
110
- .controls-help{margin-top:16px;border-top:1px solid var(--edge);padding-top:14px}
111
- .keys{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
112
- .keycap{min-width:30px;text-align:center;padding:5px 8px;border-radius:8px;background:#0d1220;
113
- border:1px solid var(--edge);font-size:12px;color:var(--muted);font-weight:600;transition:.1s}
114
- .keycap.active{background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#fff;border-color:transparent}
115
- .hint-line{font-size:11.5px;color:var(--muted);margin-top:10px;line-height:1.55}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  .active-tags{font-size:12px;color:var(--good);min-height:16px;margin-top:8px}
117
  .footnote{font-size:11px;color:var(--muted);margin-top:12px;line-height:1.5}
118
  </style>
@@ -124,7 +145,7 @@
124
  <div class="logo">🌍</div>
125
  <div>
126
  <h1>ABot-World — Interactive World Rollout</h1>
127
- <p>Upload a starting frame, then steer a live action-conditioned world with WASD / IJKL.</p>
128
  </div>
129
  </div>
130
  <div class="links">
@@ -143,7 +164,7 @@
143
  <div class="chip"><span class="dot" id="status-dot"></span><span id="status-label">Idle</span></div>
144
  <div class="chip"><span id="fps-value">0</span> fps · <span id="frame-value">0</span> blk</div>
145
  </div>
146
- <div class="capture-hint" id="capture-hint">Click the view to capture keyboard · Esc to release</div>
147
  <div class="overlay" id="overlay">
148
  <div>
149
  <div class="spinner" id="spinner" style="display:none"></div>
@@ -183,26 +204,36 @@
183
  <button class="primary" id="start-btn" disabled>Start world</button>
184
  <button class="ghost" id="stop-btn" disabled>Stop</button>
185
  </div>
 
 
186
 
187
- <div class="controls-help">
188
- <div class="label">Live controls</div>
189
- <div class="keys" id="keys">
 
 
 
 
190
  <span class="keycap" data-k="W">W</span>
191
  <span class="keycap" data-k="A">A</span>
192
  <span class="keycap" data-k="S">S</span>
193
  <span class="keycap" data-k="D">D</span>
194
- <span class="keycap" data-k="I">I</span>
195
- <span class="keycap" data-k="J">J</span>
196
- <span class="keycap" data-k="K">K</span>
197
- <span class="keycap" data-k="L">L</span>
198
  </div>
199
- <div class="hint-line">
200
- <b>WASD</b> — move &amp; turn · <b>IJKL</b> — look / pan.
201
- Hold keys to steer the next block. Click the view to capture input.
 
 
 
202
  </div>
203
- <div class="active-tags" id="active-tags"></div>
204
  </div>
205
  </div>
 
 
 
 
 
206
  </div>
207
  </div>
208
 
@@ -213,10 +244,12 @@ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.m
213
  const SESSION_ID = (crypto.randomUUID && crypto.randomUUID()) ||
214
  (`s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`);
215
 
216
- const VALID_KEYS = new Set(["W","A","S","D","I","J","K","L"]);
 
 
 
217
  const KEY_FROM_CODE = {
218
  KeyW:"W", KeyA:"A", KeyS:"S", KeyD:"D",
219
- KeyI:"I", KeyJ:"J", KeyK:"K", KeyL:"L",
220
  };
221
 
222
  const state = {
@@ -242,7 +275,8 @@ const presetsEl = $("presets");
242
  const promptEl = $("prompt"), seedEl = $("seed");
243
  const startBtn = $("start-btn"), stopBtn = $("stop-btn");
244
  const activeTags = $("active-tags");
245
- const keycaps = Array.from(document.querySelectorAll(".keycap"));
 
246
 
247
  // --- UI helpers ---
248
  function setStatus(kind, label){
@@ -500,24 +534,94 @@ function startCtrlLoop(){
500
  function stopCtrlLoop(){ if(ctrlTimer){ clearInterval(ctrlTimer); ctrlTimer = null; } }
501
 
502
  function renderKeys(){
 
503
  keycaps.forEach(c=>c.classList.toggle("active", state.pressed.has(c.dataset.k)));
 
 
 
 
 
 
 
 
504
  const held = Array.from(state.pressed);
505
- activeTags.textContent = held.length ? "Holding: " + held.join(" + ") : "";
506
  }
507
 
508
- // --- Keyboard capture ---
509
- stage.addEventListener("click", ()=>{ state.capturing = true; stage.focus(); });
 
 
 
 
 
 
 
 
 
 
 
510
  document.addEventListener("keydown",(e)=>{
511
  if(!state.capturing) return;
512
- if(e.code === "Escape"){ state.capturing=false; state.pressed.clear(); renderKeys(); return; }
 
 
 
 
513
  const k = KEY_FROM_CODE[e.code];
514
- if(k && VALID_KEYS.has(k)){ e.preventDefault(); state.pressed.add(k); renderKeys(); }
515
  });
516
  document.addEventListener("keyup",(e)=>{
517
  const k = KEY_FROM_CODE[e.code];
518
  if(k){ state.pressed.delete(k); renderKeys(); }
519
  });
520
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  promptEl.addEventListener("input", ()=>{ state.prompt = promptEl.value; });
522
 
523
  // --- Start / Stop ---
 
107
  .ghost:hover{color:var(--text);border-color:var(--accent)}
108
  button:disabled{opacity:.45;cursor:not-allowed;filter:none}
109
 
110
+ /* Controls section (below the stage) */
111
+ .controls-section{margin-top:20px}
112
+ .controls-grid{display:grid;grid-template-columns:auto 1fr;gap:28px;align-items:center;margin-top:12px}
113
+ @media (max-width:640px){.controls-grid{grid-template-columns:1fr;gap:20px}}
114
+
115
+ /* Visual on-screen WASD keyboard */
116
+ .keyboard{display:grid;grid-template-columns:repeat(3,56px);grid-template-rows:repeat(2,56px);
117
+ gap:8px;justify-content:start}
118
+ .keycap{display:grid;place-items:center;border-radius:12px;background:#0d1220;
119
+ border:1px solid var(--edge);font-size:18px;color:var(--muted);font-weight:700;
120
+ transition:.08s;user-select:none}
121
+ .keycap.spacer{background:transparent;border:none}
122
+ .keycap.active{background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#fff;
123
+ border-color:transparent;box-shadow:0 4px 16px rgba(91,140,255,.45);transform:translateY(1px)}
124
+ /* WASD cross placement inside the 3x2 grid */
125
+ .keycap[data-k="W"]{grid-column:2;grid-row:1}
126
+ .keycap[data-k="A"]{grid-column:1;grid-row:2}
127
+ .keycap[data-k="S"]{grid-column:2;grid-row:2}
128
+ .keycap[data-k="D"]{grid-column:3;grid-row:2}
129
+
130
+ .look-info{font-size:12.5px;color:var(--muted);line-height:1.6}
131
+ .look-info b{color:var(--text)}
132
+ .look-badge{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--good);
133
+ border:1px solid var(--edge);border-radius:999px;padding:4px 10px;margin-top:8px}
134
+ .look-badge .d{width:7px;height:7px;border-radius:50%;background:var(--muted)}
135
+ .look-badge.active .d{background:var(--good);box-shadow:0 0 8px var(--good)}
136
+ .hint-line{font-size:11.5px;color:var(--muted);margin-top:14px;line-height:1.55}
137
  .active-tags{font-size:12px;color:var(--good);min-height:16px;margin-top:8px}
138
  .footnote{font-size:11px;color:var(--muted);margin-top:12px;line-height:1.5}
139
  </style>
 
145
  <div class="logo">🌍</div>
146
  <div>
147
  <h1>ABot-World — Interactive World Rollout</h1>
148
+ <p>Upload a starting frame, then steer a live action-conditioned world with WASD + mouse-look.</p>
149
  </div>
150
  </div>
151
  <div class="links">
 
164
  <div class="chip"><span class="dot" id="status-dot"></span><span id="status-label">Idle</span></div>
165
  <div class="chip"><span id="fps-value">0</span> fps · <span id="frame-value">0</span> blk</div>
166
  </div>
167
+ <div class="capture-hint" id="capture-hint">Click to capture · WASD to move · mouse to look · Esc to release</div>
168
  <div class="overlay" id="overlay">
169
  <div>
170
  <div class="spinner" id="spinner" style="display:none"></div>
 
204
  <button class="primary" id="start-btn" disabled>Start world</button>
205
  <button class="ghost" id="stop-btn" disabled>Stop</button>
206
  </div>
207
+ </div>
208
+ </div>
209
 
210
+ <!-- Controls (below the video output) -->
211
+ <div class="card controls-section">
212
+ <div class="label">Live controls</div>
213
+ <div class="controls-grid">
214
+ <div>
215
+ <div class="label" style="margin-bottom:8px">Movement</div>
216
+ <div class="keyboard" id="keys">
217
  <span class="keycap" data-k="W">W</span>
218
  <span class="keycap" data-k="A">A</span>
219
  <span class="keycap" data-k="S">S</span>
220
  <span class="keycap" data-k="D">D</span>
 
 
 
 
221
  </div>
222
+ </div>
223
+ <div>
224
+ <div class="label" style="margin-bottom:8px">Look / pan</div>
225
+ <div class="look-info">
226
+ Move the world camera with your <b>mouse</b>. Click the view to capture
227
+ the pointer, then move the mouse to look &amp; pan. Press <b>Esc</b> to release.
228
  </div>
229
+ <div class="look-badge" id="look-badge"><span class="d"></span><span id="look-badge-text">Mouse-look idle</span></div>
230
  </div>
231
  </div>
232
+ <div class="hint-line">
233
+ <b>W A S D</b> — move &amp; turn (hold to steer the next block) ·
234
+ <b>Mouse</b> — look / pan. Click the video to capture input.
235
+ </div>
236
+ <div class="active-tags" id="active-tags"></div>
237
  </div>
238
  </div>
239
 
 
244
  const SESSION_ID = (crypto.randomUUID && crypto.randomUUID()) ||
245
  (`s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`);
246
 
247
+ // Movement keys are driven by the keyboard (WASD); look/pan (I J K L) is now
248
+ // driven by mouse movement (pointer-lock mouse-look) — see mouse-look block.
249
+ const MOVE_KEYS = new Set(["W","A","S","D"]);
250
+ const LOOK_KEYS = new Set(["I","J","K","L"]);
251
  const KEY_FROM_CODE = {
252
  KeyW:"W", KeyA:"A", KeyS:"S", KeyD:"D",
 
253
  };
254
 
255
  const state = {
 
275
  const promptEl = $("prompt"), seedEl = $("seed");
276
  const startBtn = $("start-btn"), stopBtn = $("stop-btn");
277
  const activeTags = $("active-tags");
278
+ const keycaps = Array.from(document.querySelectorAll(".keycap[data-k]"));
279
+ const lookBadge = $("look-badge"), lookBadgeText = $("look-badge-text");
280
 
281
  // --- UI helpers ---
282
  function setStatus(kind, label){
 
534
  function stopCtrlLoop(){ if(ctrlTimer){ clearInterval(ctrlTimer); ctrlTimer = null; } }
535
 
536
  function renderKeys(){
537
+ // Highlight the on-screen WASD keys that are currently held.
538
  keycaps.forEach(c=>c.classList.toggle("active", state.pressed.has(c.dataset.k)));
539
+ // Look/pan status badge reflects live mouse-look direction(s).
540
+ const look = Array.from(state.pressed).filter(k=>LOOK_KEYS.has(k));
541
+ const looking = look.length > 0;
542
+ lookBadge.classList.toggle("active", looking);
543
+ const LOOK_LABEL = { I:"up", K:"down", J:"left", L:"right" };
544
+ lookBadgeText.textContent = looking
545
+ ? "Looking " + look.map(k=>LOOK_LABEL[k]).join(" + ")
546
+ : (state.capturing ? "Mouse-look ready" : "Mouse-look idle");
547
  const held = Array.from(state.pressed);
548
+ activeTags.textContent = held.length ? "Active: " + held.join(" + ") : "";
549
  }
550
 
551
+ // --- Movement keyboard capture (WASD) + mouse-look pointer capture ---
552
+ // Clicking the stage captures input. We request Pointer Lock so raw mouse
553
+ // movement drives look/pan (I J K L) instead of the old IJKL keys.
554
+ function requestCapture(){
555
+ state.capturing = true;
556
+ stage.focus();
557
+ if(stage.requestPointerLock){
558
+ try{ stage.requestPointerLock(); }catch{}
559
+ }
560
+ renderKeys();
561
+ }
562
+ stage.addEventListener("click", requestCapture);
563
+
564
  document.addEventListener("keydown",(e)=>{
565
  if(!state.capturing) return;
566
+ if(e.code === "Escape"){
567
+ state.capturing=false; state.pressed.clear();
568
+ if(document.exitPointerLock) try{ document.exitPointerLock(); }catch{}
569
+ renderKeys(); return;
570
+ }
571
  const k = KEY_FROM_CODE[e.code];
572
+ if(k && MOVE_KEYS.has(k)){ e.preventDefault(); state.pressed.add(k); renderKeys(); }
573
  });
574
  document.addEventListener("keyup",(e)=>{
575
  const k = KEY_FROM_CODE[e.code];
576
  if(k){ state.pressed.delete(k); renderKeys(); }
577
  });
578
 
579
+ // --- Mouse-look: convert pointer movement into look/pan keys (I J K L) ---
580
+ // I = look up, K = look down, J = look left, L = look right. A small deadzone
581
+ // avoids jitter; each look direction is held briefly after motion then released
582
+ // so a continuous drag/move keeps looking while a still mouse stops looking.
583
+ const LOOK_DEADZONE = 2; // px of movement before a look direction fires
584
+ let lookReleaseTimer = null;
585
+ function applyMouseLook(dx, dy){
586
+ if(!state.capturing) return;
587
+ // Clear previous look keys; movement (WASD) is untouched.
588
+ let changed = false;
589
+ for(const k of LOOK_KEYS){ if(state.pressed.delete(k)) changed = true; }
590
+
591
+ if(Math.abs(dx) > LOOK_DEADZONE){
592
+ state.pressed.add(dx > 0 ? "L" : "J"); changed = true; // right / left
593
+ }
594
+ if(Math.abs(dy) > LOOK_DEADZONE){
595
+ state.pressed.add(dy > 0 ? "K" : "I"); changed = true; // down / up
596
+ }
597
+ if(changed) renderKeys();
598
+
599
+ // Auto-release look keys shortly after the mouse stops moving.
600
+ if(lookReleaseTimer) clearTimeout(lookReleaseTimer);
601
+ lookReleaseTimer = setTimeout(()=>{
602
+ let cleared = false;
603
+ for(const k of LOOK_KEYS){ if(state.pressed.delete(k)) cleared = true; }
604
+ if(cleared) renderKeys();
605
+ }, 120);
606
+ }
607
+ document.addEventListener("mousemove",(e)=>{
608
+ if(!state.capturing) return;
609
+ // Prefer raw movement deltas (available under pointer lock); fall back to
610
+ // client-space deltas for browsers that deny/lack pointer lock.
611
+ const dx = (e.movementX !== undefined) ? e.movementX : 0;
612
+ const dy = (e.movementY !== undefined) ? e.movementY : 0;
613
+ applyMouseLook(dx, dy);
614
+ });
615
+ // Track pointer-lock state so the badge reflects capture accurately.
616
+ document.addEventListener("pointerlockchange", ()=>{
617
+ if(document.pointerLockElement !== stage){
618
+ // Pointer released (e.g. Esc pressed by the browser): drop look keys.
619
+ let cleared = false;
620
+ for(const k of LOOK_KEYS){ if(state.pressed.delete(k)) cleared = true; }
621
+ if(cleared) renderKeys();
622
+ }
623
+ });
624
+
625
  promptEl.addEventListener("input", ()=>{ state.prompt = promptEl.value; });
626
 
627
  // --- Start / Stop ---