Skip to content

feat(orbital): Responsive mobile layout and fix globe zoom on first load#13

Merged
rahulchhabria merged 6 commits intomasterfrom
responsive-mobile-globe
Mar 5, 2026
Merged

feat(orbital): Responsive mobile layout and fix globe zoom on first load#13
rahulchhabria merged 6 commits intomasterfrom
responsive-mobile-globe

Conversation

@rahulchhabria
Copy link
Contributor

Summary

  • Add CSS media query (≤600px) to reflow the event feed as a full-width bottom bar and compact the header/stats for narrow screens — the globe is no longer obscured by UI panels on mobile
  • Set initial camera distance based on viewport width so the globe fits fully on screen at first load instead of being 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

Test plan

  • Open on desktop — globe should appear smaller/more zoomed out than before
  • Open in Chrome DevTools mobile emulation (≤600px width) — globe should be fully visible and positioned in the upper portion of the screen
  • Event feed should appear as a compact full-width bar at the bottom on mobile
  • Rotating the globe and pinch-to-zoom should still work on mobile

🤖 Generated with Claude Code

…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>
@rahulchhabria rahulchhabria requested a review from BYK March 5, 2026 18:12
rahulchhabria and others added 2 commits March 5, 2026 12:19
…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>
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +84 to +85
camera.position.z = window.innerWidth <= 600 ? 8.0 : 5.6;
camera.position.y = window.innerWidth <= 600 ? -0.65 : 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

// ── Resize ────────────────────────────────────────────────────────────────────

window.addEventListener('resize', () => {
const mobile = window.innerWidth <= 600;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

rahulchhabria and others added 2 commits March 5, 2026 12:33
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>
@rahulchhabria rahulchhabria merged commit aa5a810 into master Mar 5, 2026
5 checks passed
45, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.z = 2.8;
const mobileQuery = window.matchMedia('(max-width: 600px)');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

45, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.z = 2.8;
const mobileQuery = window.matchMedia('(max-width: 600px)');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Additional Locations (2)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants