Apple-design audit: fluid globe drag, interruptible fly-to, press feedback, OS transparency/contrast settings - #1133
Merged
Merged
Conversation
OrbitControls' damping was lag, not inertia: each frame paid out 8% of the drag still owed, so the ground trailed the cursor by ~200 ms and a flick travelled no further than a slow drag of the same length (and settled twice as fast at 120 Hz). Damping is now off, so the drag tracks the pointer 1:1, and GlobeMomentum supplies real momentum. On release it reads the pointer's velocity from its own event history (exact at any frame rate, including 9 fps SwiftShader), maps it through OrbitControls' pixels-to-radians rule, and coasts with Apple's scroll-view decay (0.995/ms, the same 200 ms time constant as before). A pointer that rested before letting go doesn't coast. Reduced motion gets no coast. A search fly-to held the controls off for its full 1.4 s. A press, wheel or arrow key on the globe now takes the camera back from its live position mid-flight. A flight that ends also re-asserts an armed region drawer's hold on the drag; before, landing handed it back to OrbitControls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the globe's grab cursor responded to pointer-down; every button waited for the click. Buttons now scale to 0.96 on :active and ease back over 100 ms. The scale uses the individual `scale` property so it composes with existing transforms, and it never reflows, so the HUD geometry the e2e suite measures is untouched. Dragging off a pressed button still cancels it natively. Under reduced motion the press dims instead of moving. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every panel is a translucent layer blurred over a globe that never stops moving, and the app ignored the two OS settings that ask for less of that. Both are now handled on the tokens, so every panel that paints --panel-bg follows without per-panel rules: - prefers-reduced-transparency: reduce makes the panels solid and removes the backdrop blur (!important, because a user setting outranks the per-panel backdrop-filter declarations). - prefers-contrast: more makes the panels near-solid, raises control borders from 11-14% to 45-50% opacity, and lifts --muted toward the foreground, in both themes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tracking should depend on size: display type wants it slightly negative, because large letters read as spaced apart. The 22-32px wordmark was the only large text in the app, and it was set wide at +0.02em. It's now -0.02em. The line gets narrower but no taller, so nothing below it moves. Small labels keep their positive tracking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An audit of https://roamingeye.org against the
apple-designskill (Apple's Designing Fluid Interfaces and related WWDC guidance, adapted for the web). This PR fixes the findings that could ship without changing product decisions or HUD geometry. The rest are listed at the bottom with the reason each was deferred.What changes
1. The globe tracks the pointer 1:1 and a flick has real momentum (§2, §5, §6, §11)
controls.enableDamping = truelooked like inertia but acted as lag. Each frame it paid out 8% of the drag still owed, so:Damping is now off, and the new
src/scene/GlobeMomentum.tsprovides the momentum:2π · px · rotateSpeed / height), so the throw continues the drag's actual speed at any zoom.0.995per ms, integrated exactly so the path is the same at any frame rate. It has the same 200 ms time constant the old damping had at 60 Hz, so the globe feels as heavy as before. The difference is that a flick now carries it further than a slow drag.2. A search fly-to can be interrupted (§3: input is never locked during a transition)
CameraFlyersetcontrols.enabled = falsefor the full 1.4 s flight, so pressing, scrolling or arrowing the globe mid-flight did nothing. Now any of those takes the camera back from its current position, with no jump, and the gesture that interrupted it continues. The listeners use the capture phase onwindow, so they run before OrbitControls ignores the event.It also fixes a small pre-existing bug. When a flight ended while Draw region was armed, it set
controls.enabled = trueand handed the drag back to OrbitControls, so the globe rotated under the box being drawn. Ending a flight now keeps the drawer in control of the drag.3. Buttons respond on pointer-down (§1)
The only
:activerule in the app was the globe's grab cursor. Every<button>now scales to0.96while pressed and eases back over 100 ms. It uses the individualscaleproperty, so it combines with existing transforms and never reflows. Dragging off a pressed button still cancels it. Under reduced motion the press dims to 0.75 opacity instead.4. Reduce Transparency and Increase Contrast are honoured (§12, §14)
The app has 21
backdrop-filtersurfaces and handled neither setting. Both are now handled on the tokens:--mutedmoves toward the foreground colour. Applies in both themes.5. Size-specific tracking on the wordmark (§15)
The only large text in the app, the 22–32 px wordmark, had +0.02em letter-spacing. It is now −0.02em. The line gets narrower but not taller, so nothing below it moves.
Evidence
Before/after, scripted Playwright on the live site vs this branch. Chromium with SwiftShader at 9 fps, 1280×720; the view is read back from the URL hash.
Checked in the browser against the live
vitebuild:scale: 0.96; releasing givesnone; dragging off cancels (aria-pressedunchanged).prefers-contrast: more, panels are at 0.95 opacity, borders at 0.5,--mutedis#35425a(light theme).prefers-reduced-transparency: reduce, the toolbar, search and share panels are solid withbackdrop-filter: none.Local gates:
npm run verifypasses (typecheck, lint, format, unit, catalog, build, bundle budget). The main chunk is 55.1 kB of its 60 kB budget.GlobeMomentumtests cover: the projection formula; frame-rate independence at 60 Hz, 120 Hz and in one step; a 9 fps event cadence; rest-before-release; grab mid-coast; reduced motion; drawer-held controls; pinch; the speed cap; coalesced events.CameraFlyer.canceltests.Deliberately not in this PR: recommendations for the owner
These came up in the audit but are either product decisions or would move HUD geometry that about 30 e2e specs measure.
MODIS_Terra_L3_NDVI_Monthly · Aug 2026sits under the timeline, and the legend carries a three-line caveat paragraph at about 11 px in muted grey on translucent glass. That runs against the caveat budget, and the wording is a science-owner call.transform-originat the trigger), and sheets should fade and blur in. Deferred because a scale-in makesgetBoundingClientRectmeasurements taken right after opening flaky. The safe version is opacity plus backdrop blur only, with no geometry change.blur(10px)over 0.7-alpha glass, so small chips and the large HUD carry equal visual weight. Suggestion: two material tiers with asaturate()vibrancy boost. Profile first, because blur is recomputed every frame over a WebGL canvas that is always redrawing.Test plan
🤖 Generated with Claude Code