RayMelius Claude Opus 4.6 commited on
Commit
5cb46c4
·
1 Parent(s): 4d6af04

Fix FP/VR visibility in iframe, speed control, pointer lock

Browse files

- Add FP and JOIN buttons to index.html controls bar
- Forward toggle-fp and show-join messages to 3d iframe
- Add pointer-lock, xr-spatial-tracking, fullscreen to iframe allow
- Click overlay for pointer lock (user gesture required)
- Fix speed control: set simSpeed immediately before async API call
- Prevent speed flickering at 10x/50x

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. web/3d.html +17 -2
  2. web/index.html +7 -11
web/3d.html CHANGED
@@ -192,6 +192,13 @@
192
  <div id="fp-hint" style="display:none;position:fixed;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:50;text-align:center">
193
  WASD — move &middot; Mouse — look &middot; ESC — exit &middot; E — interact
194
  </div>
 
 
 
 
 
 
 
195
 
196
  <div id="player-login">
197
  <h2>JOIN SOCI CITY</h2>
@@ -2902,7 +2909,7 @@ fpControls.addEventListener('lock', () => {
2902
  fpControls.addEventListener('unlock', () => {
2903
  document.getElementById('fp-crosshair').style.display = 'none';
2904
  if (fpMode) {
2905
- setTimeout(() => { if (fpMode) fpControls.lock(); }, 200);
2906
  }
2907
  });
2908
 
@@ -2912,10 +2919,15 @@ function enterFPMode() {
2912
  fpCamera.aspect = window.innerWidth / window.innerHeight;
2913
  fpCamera.updateProjectionMatrix();
2914
  controls.enabled = false;
2915
- fpControls.lock();
2916
  fpClock.start();
2917
  document.getElementById('btn-fp').classList.add('active');
2918
  document.getElementById('btn-fp').title = 'Exit first-person';
 
 
 
 
 
 
2919
  }
2920
 
2921
  function exitFPMode() {
@@ -2925,6 +2937,7 @@ function exitFPMode() {
2925
  fpClock.stop();
2926
  document.getElementById('fp-crosshair').style.display = 'none';
2927
  document.getElementById('fp-hint').style.display = 'none';
 
2928
  document.getElementById('btn-fp').classList.remove('active');
2929
  document.getElementById('btn-fp').title = 'First-person view';
2930
  }
@@ -3131,6 +3144,8 @@ if (isEmbedded) {
3131
  if (e.data?.type === 'zoom-out') zoomOut();
3132
  if (e.data?.type === 'reset-camera') resetCamera();
3133
  if (e.data?.type === 'set-speed') setDemoSpeed(e.data.multiplier);
 
 
3134
  });
3135
  }
3136
  connectWebSocket();
 
192
  <div id="fp-hint" style="display:none;position:fixed;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:50;text-align:center">
193
  WASD — move &middot; Mouse — look &middot; ESC — exit &middot; E — interact
194
  </div>
195
+ <div id="fp-click-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:250;cursor:pointer;display:none;align-items:center;justify-content:center">
196
+ <div style="text-align:center;color:#fff">
197
+ <div style="font-size:48px;margin-bottom:16px">&#128065;</div>
198
+ <div style="font-size:20px;font-weight:300;letter-spacing:2px">CLICK TO ENTER FIRST-PERSON</div>
199
+ <div style="font-size:13px;color:#aaa;margin-top:8px">WASD — move &middot; Mouse — look &middot; ESC — exit</div>
200
+ </div>
201
+ </div>
202
 
203
  <div id="player-login">
204
  <h2>JOIN SOCI CITY</h2>
 
2909
  fpControls.addEventListener('unlock', () => {
2910
  document.getElementById('fp-crosshair').style.display = 'none';
2911
  if (fpMode) {
2912
+ document.getElementById('fp-click-overlay').style.display = 'flex';
2913
  }
2914
  });
2915
 
 
2919
  fpCamera.aspect = window.innerWidth / window.innerHeight;
2920
  fpCamera.updateProjectionMatrix();
2921
  controls.enabled = false;
 
2922
  fpClock.start();
2923
  document.getElementById('btn-fp').classList.add('active');
2924
  document.getElementById('btn-fp').title = 'Exit first-person';
2925
+ const overlay = document.getElementById('fp-click-overlay');
2926
+ overlay.style.display = 'flex';
2927
+ overlay.onclick = () => {
2928
+ overlay.style.display = 'none';
2929
+ fpControls.lock();
2930
+ };
2931
  }
2932
 
2933
  function exitFPMode() {
 
2937
  fpClock.stop();
2938
  document.getElementById('fp-crosshair').style.display = 'none';
2939
  document.getElementById('fp-hint').style.display = 'none';
2940
+ document.getElementById('fp-click-overlay').style.display = 'none';
2941
  document.getElementById('btn-fp').classList.remove('active');
2942
  document.getElementById('btn-fp').title = 'First-person view';
2943
  }
 
3144
  if (e.data?.type === 'zoom-out') zoomOut();
3145
  if (e.data?.type === 'reset-camera') resetCamera();
3146
  if (e.data?.type === 'set-speed') setDemoSpeed(e.data.multiplier);
3147
+ if (e.data?.type === 'toggle-fp') window._toggleFP();
3148
+ if (e.data?.type === 'show-join') window._showJoin();
3149
  });
3150
  }
3151
  connectWebSocket();
web/index.html CHANGED
@@ -293,6 +293,9 @@
293
  <button class="ctrl-btn" onclick="send3d('zoom-in')" title="Zoom In (scroll in 3D view)">+</button>
294
  <button class="ctrl-btn" onclick="send3d('zoom-out')" title="Zoom Out (scroll in 3D view)">-</button>
295
  <button class="ctrl-btn" onclick="send3d('reset-camera')" title="Reset 3D camera">Fit</button>
 
 
 
296
  </span>
297
  <span id="api-calls">API: 0</span>
298
  <span id="cost">$0.00</span>
@@ -301,7 +304,7 @@
301
  </div>
302
  <div id="main">
303
  <div id="viewport-3d">
304
- <iframe id="city3d" allow="autoplay"></iframe>
305
  <script>document.getElementById('city3d').src = location.protocol === 'file:' ? '3d.html' : '/3d';</script>
306
  <div id="tooltip" style="z-index:200"></div>
307
  <div id="toast-container"></div>
@@ -3249,19 +3252,12 @@ async function togglePause() {
3249
  }
3250
 
3251
  async function setSpeed(mult) {
3252
- try {
3253
- const res = await fetch(`${API_BASE}/controls/speed?multiplier=${mult}`, { method: 'POST' });
3254
- if (res.ok) {
3255
- const data = await res.json();
3256
- simSpeed = data.speed;
3257
- updateControlsUI();
3258
- send3dSpeed(simSpeed);
3259
- return;
3260
- }
3261
- } catch(e) {}
3262
  simSpeed = mult;
3263
  updateControlsUI();
3264
  send3dSpeed(mult);
 
 
 
3265
  }
3266
 
3267
  function updateControlsUI() {
 
293
  <button class="ctrl-btn" onclick="send3d('zoom-in')" title="Zoom In (scroll in 3D view)">+</button>
294
  <button class="ctrl-btn" onclick="send3d('zoom-out')" title="Zoom Out (scroll in 3D view)">-</button>
295
  <button class="ctrl-btn" onclick="send3d('reset-camera')" title="Reset 3D camera">Fit</button>
296
+ <span style="color:#1a3a6e;margin:0 4px">│</span>
297
+ <button class="ctrl-btn" id="btn-fp-main" onclick="send3d('toggle-fp')" title="First-person view">&#128065; FP</button>
298
+ <button class="ctrl-btn" id="btn-join-main" onclick="send3d('show-join')" title="Join as player">JOIN</button>
299
  </span>
300
  <span id="api-calls">API: 0</span>
301
  <span id="cost">$0.00</span>
 
304
  </div>
305
  <div id="main">
306
  <div id="viewport-3d">
307
+ <iframe id="city3d" allow="autoplay; pointer-lock; xr-spatial-tracking; fullscreen"></iframe>
308
  <script>document.getElementById('city3d').src = location.protocol === 'file:' ? '3d.html' : '/3d';</script>
309
  <div id="tooltip" style="z-index:200"></div>
310
  <div id="toast-container"></div>
 
3252
  }
3253
 
3254
  async function setSpeed(mult) {
 
 
 
 
 
 
 
 
 
 
3255
  simSpeed = mult;
3256
  updateControlsUI();
3257
  send3dSpeed(mult);
3258
+ try {
3259
+ await fetch(`${API_BASE}/controls/speed?multiplier=${mult}`, { method: 'POST' });
3260
+ } catch(e) {}
3261
  }
3262
 
3263
  function updateControlsUI() {