Phone fixes: fit 3D camera to narrow views, hide [hidden] buttons, touch hints
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zsyegMfQnzJEzgMiFLzew
2 changed files+21−9
src/style.cssmodified+1−0View file
@@ -60,6 +60,7 @@
6060 }
6161
6262 * { box-sizing: border-box; }
63+[hidden] { display: none !important; }
6364 body {
6465 margin: 0;
6566 background: var(--page);
src/ui/scene3d.tsmodified+20−9View file
@@ -40,6 +40,8 @@ export class BeadScene {
4040 private tabs: HTMLElement;
4141
4242 private mode: Mode = 'setup';
43+ private view: View = 'persp';
44+ private fitted = false;
4345 private setupSpec: SceneSpec | null = null;
4446 private replaySpec: ReplaySpec | null = null;
4547 private exaggeration = 1;
@@ -156,14 +158,18 @@ export class BeadScene {
156158 }
157159
158160 private setView(v: View) {
159- const d = 1.45;
160- const pos: Record<View, [number, number, number]> = {
161+ this.view = v;
162+ const dirs: Record<View, [number, number, number]> = {
161163 persp: [-0.45, 0.42, 1.05],
162- front: [0, 0, d],
163- top: [0, d, 0.0001],
164- end: [d, 0, 0],
164+ front: [0, 0, 1],
165+ top: [0, 1, 0.0001],
166+ end: [1, 0, 0],
165167 };
166- this.camera.position.set(...pos[v]);
168+ // back off far enough that the model (about 1.4 wide with its supports) fits horizontally
169+ const half = Math.tan(THREE.MathUtils.degToRad(this.camera.fov / 2));
170+ const fit = 0.75 / (half * Math.min(this.camera.aspect, 1)) + 0.3;
171+ const dist = Math.max(this.view === 'persp' ? 1.2 : 1.45, fit);
172+ this.camera.position.set(...dirs[v]).setLength(dist);
167173 this.camera.up.set(0, 1, 0);
168174 if (v === 'top') this.camera.up.set(0, 0, -1);
169175 this.controls?.target.set(0, 0, 0);
@@ -178,9 +184,14 @@ export class BeadScene {
178184 const h = this.wrap.clientHeight;
179185 if (w === 0 || h === 0) return;
180186 this.renderer.setSize(w, h);
187+ const narrowBefore = this.camera.aspect < 1;
181188 this.camera.aspect = w / h;
182189 this.camera.updateProjectionMatrix();
183- this.render();
190+ // refit when the view turns portrait or landscape (phone rotation, first layout)
191+ if (narrowBefore !== this.camera.aspect < 1 || !this.fitted) {
192+ this.fitted = true;
193+ this.setView(this.view);
194+ } else this.render();
184195 }
185196
186197 private render() {
@@ -447,9 +458,9 @@ export class BeadScene {
447458 if (this.mode === 'replay' && frames) {
448459 this.note.textContent =
449460 `Displacements drawn ×${fmtNum(this.exaggeration)} (all three components, so longitudinal motion is exaggerated too). ` +
450- `Drag to rotate, scroll to zoom, right-drag to pan.`;
461+ `Drag to rotate; pinch or scroll to zoom; two-finger or right-drag to pan.`;
451462 } else {
452- this.note.textContent = 'Beads at rest. Drag to rotate, scroll to zoom, right-drag to pan.';
463+ this.note.textContent = 'Beads at rest. Drag to rotate; pinch or scroll to zoom; two-finger or right-drag to pan.';
453464 }
454465 this.render();
455466 }