feat(orbital): Responsive mobile layout and fix globe zoom on first load#13
feat(orbital): Responsive mobile layout and fix globe zoom on first load#13rahulchhabria merged 6 commits intomasterfrom
Conversation
…st load - Add CSS media query (≤600px) to reflow the event feed as a full-width bottom bar and compact the header/stats for narrow screens - Set initial camera distance based on viewport width so the globe is fully visible on first load instead of zoomed in (mobile: z=8, desktop: z=5.6) - Offset camera y on mobile to position the globe ~20% higher, giving clearance above the event feed panel - Raise OrbitControls maxDistance to 10 to accommodate the wider zoom range Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pdate on resize Without calling controls.update() after setting camera.position, the next animation frame's controls.update() recomputes position from OrbitControls' stale internal spherical coordinates, snapping the camera back and breaking responsive layout on resize/device rotation. Co-Authored-By: Claude <noreply@anthropic.com>
…ed distance The previous handler set only y and z, leaving x unchanged. Since autoRotate keeps x non-zero, the resulting camera distance was sqrt(x^2 + y^2 + z^2) instead of the intended value, causing a visible globe size jump on every resize or device orientation change. Use camera.position.set(0, y, z) to mirror initial-load behaviour and guarantee the correct distance regardless of rotation state. Co-Authored-By: Claude <noreply@anthropic.com>
static/orbital.js
Outdated
| camera.aspect = window.innerWidth / window.innerHeight; | ||
| camera.updateProjectionMatrix(); | ||
| renderer.setSize(window.innerWidth, window.innerHeight); | ||
| camera.position.set(0, mobile ? -0.65 : 0, mobile ? 8.0 : 5.6); |
There was a problem hiding this comment.
Bug: The resize event handler unconditionally resets camera position, which interrupts and discards user interactions like pinch-to-zoom if they coincide with a resize event on mobile.
Severity: MEDIUM
Suggested Fix
Add a conditional check within the resize event handler to prevent the camera position from being reset if a user interaction is in progress. The existing resumeTimer variable, which tracks interaction state, could be used for this check. The camera position should only be reset when the user is not actively manipulating the controls.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: static/orbital.js#L479
Potential issue: The `resize` event handler unconditionally resets the camera's position
using `camera.position.set(...)` and then calls `controls.update()`. If a resize event
fires while a user is actively interacting with the globe (e.g., pinching to zoom), this
sequence interrupts the action. The call to `camera.position.set()` discards the user's
current zoom level, and the subsequent `controls.update()` synchronizes the
`OrbitControls` internal state to this reset position. This causes the camera to snap
back to a default distance, losing the user's input mid-gesture. This is particularly
problematic on mobile devices where resizing can be triggered frequently by browser UI
changes or orientation flips.
static/orbital.js
Outdated
| camera.position.z = window.innerWidth <= 600 ? 8.0 : 5.6; | ||
| camera.position.y = window.innerWidth <= 600 ? -0.65 : 0; |
There was a problem hiding this comment.
I'd prefer matchMedia() for a more responsive and guaranteed system. The event listener can be "turned off" after we know the page is in a stable state (or we can keep it to react potential resize events which can also happen on mobile when the orientation changes)
static/orbital.js
Outdated
| // ── Resize ──────────────────────────────────────────────────────────────────── | ||
|
|
||
| window.addEventListener('resize', () => { | ||
| const mobile = window.innerWidth <= 600; |
There was a problem hiding this comment.
This kind of mobile check is not robust. A person can reduce the window size on a desktop machine too. Also see my comment about watchMedia() API instead of resize.
Replace ad-hoc window.innerWidth <= 600 checks with a single
matchMedia('(max-width: 600px)') query. The MediaQueryList is
created once at init and reused in the resize handler, ensuring
the mobile check stays consistent with the CSS media query system.
Co-Authored-By: Claude <noreply@anthropic.com>
…esize Split the resize handler into two concerns: - resize: viewport geometry only (aspect ratio, renderer size) - mobileQuery change: camera position when the breakpoint crosses This means camera layout only adjusts when the device genuinely crosses the mobile/desktop threshold, not on every pixel of window resizing — avoiding false triggers from desktop window dragging. Co-Authored-By: Claude <noreply@anthropic.com>
| 45, window.innerWidth / window.innerHeight, 0.1, 1000 | ||
| ); | ||
| camera.position.z = 2.8; | ||
| const mobileQuery = window.matchMedia('(max-width: 600px)'); |
There was a problem hiding this comment.
Bug: A duplicate const mobileQuery declaration is introduced, which will cause a SyntaxError and prevent the script from loading.
Severity: CRITICAL
Suggested Fix
Remove the newly introduced const mobileQuery declaration on line 84. The existing declaration should be used for all logic, though its media query value may need to be adjusted to consolidate the intended logic from both the new and old code.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: static/orbital.js#L84
Potential issue: The pull request introduces a new `const` variable named `mobileQuery`
at the top-level scope of `static/orbital.js`. However, a variable with the same name is
already declared using `const` in the same scope later in the file. This redeclaration
will cause a `SyntaxError` when the JavaScript engine parses the file, preventing the
entire script from loading and executing. As a result, any JavaScript-driven
functionality on the page will fail.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| camera.position.z = 2.8; | ||
| const mobileQuery = window.matchMedia('(max-width: 600px)'); | ||
| camera.position.z = mobileQuery.matches ? 8.0 : 5.6; | ||
| camera.position.y = mobileQuery.matches ? -0.65 : 0; |
There was a problem hiding this comment.
Initial camera position immediately overwritten by breakpoint function
Medium Severity
The camera position set at lines 85–86 (z=8.0/5.6, y=-0.65/0) is immediately overwritten by applyCameraBreakpoint at line 556, which applies CAMERA_DESKTOP (dist=2.8) or CAMERA_MOBILE (dist=3.5). The intended "fix globe zoom on first load" from the PR description would have no effect since the later call replaces these values with smaller distances.
Additional Locations (1)
| 45, window.innerWidth / window.innerHeight, 0.1, 1000 | ||
| ); | ||
| camera.position.z = 2.8; | ||
| const mobileQuery = window.matchMedia('(max-width: 600px)'); |
There was a problem hiding this comment.
Mismatched mobile breakpoints between CSS and JS queries
Medium Severity
The new mobileQuery at line 84 uses max-width: 600px (matching the CSS media query), but the existing mobileQuery at line 551 uses max-width: 768px. This means the CSS layout switches at 600px while the camera breakpoint handler fires at 768px. Devices between 601–768px get the mobile camera positioning but desktop CSS layout, creating an inconsistent experience.


Summary
maxDistanceto 10 to accommodate the wider zoom rangeTest plan
🤖 Generated with Claude Code