Skip to content

3D replay: dark / light / auto theme with an off-white light mode#230

Merged
pokle merged 3 commits into
masterfrom
claude/3dvis-replay-metrics-cdj8nu
Jul 3, 2026
Merged

3D replay: dark / light / auto theme with an off-white light mode#230
pokle merged 3 commits into
masterfrom
claude/3dvis-replay-metrics-cdj8nu

Conversation

@pokle

@pokle pokle commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Follow-up to #229. The replay gets a light mode with a Dark / Light / Auto switch at the top of the View section. Light mode uses an off-white background (#f2f0e9), deliberately not full white.

Behaviour

  • The choice is stored as theme in the shared glidecomp:preferences record ('light' | 'dark' | 'system' — the field already existed in UserPreferences), so it persists and cloud-syncs like the units.
  • Auto ('system') follows prefers-color-scheme live via matchMedia — flipping the OS scheme re-themes the page without a reload.
  • The saved theme is applied to the chrome at the very top of main() (and the meta[name=theme-color] updated), so a light-mode user never sees a dark flash while the tracks load.

Mechanics

  • main.ts toggles a light class on <html>. The page's own <style> defines --rp-* theme tokens plus scoped remaps of the dark slate utilities the markup uses — the page is standalone, so remapping ~20 utilities in its own style block beats duplicating a light class next to every one of them (no other page is affected).
  • The vario gauge canvas reads the same --rp-* tokens (needle/dial colours re-read and the dial repainted on switch), and the climb digit colours pick up higher-contrast lime/sky shades in light mode via the same remap.
  • In-scene furniture follows the theme only on the abstract backdrop: WebGL clear colour, grid colours, ground-label and gaggle-label halos (inverted to dark-text-on-light-halo), and the vario-ramp "zero" — now a uVarioZero shader uniform, darkened in light mode so near-level trail segments don't vanish into the off-white. The scene rebuilds in place on switch (same path as backdrop switching). On the terrain backdrop the scene always builds dark-styled — there the backdrop is map imagery, not the UI theme.
  • The vario colour-scale legend mirrors the themed ramp centre.

Light-mode contrast pass (from device feedback)

  • Turnpoint rings/walls use deeper shades in light mode — emerald-700 start, red-600 goal, amber-700 turnpoints (the dark-backdrop pastels washed out on the off-white); wall opacity drops a notch to compensate for the darker colour stacking across DoubleSide layers.
  • Every pilot cone gets a dark silhouette rim — an inverted-hull second InstancedMesh (~16% larger, back-face-only, matrices mirroring the markers) — so pale pilot colours (light lime, yellow) read crisply without changing the identity colour that matches the legend chips and badges.

Bug fix: touch pan/orbit while following on the map backdrop

In map mode on a phone, pan and orbit were impossible while following a pilot. Root cause: the follow calls map.setCenter every frame; setCenter wraps jumpTo, whose first act is stop() — which cancels Mapbox's active gesture handlers. Mouse drags mostly survived (they re-establish per mousemove); touch gestures are stateful and died continuously. It even fired with playback paused, since the zero-delta case still called setCenter.

followTo now keeps its anchor fresh every frame but skips setCenter when (a) the pilot hasn't moved, (b) any pointer is down on the map (container pointerdown / window pointerup+pointercancel counter, leak-proof for fingers released off-map), or (c) map.isEasing() — so the orientation presets' easeTo survives too (isEasing, not isMoving, which could report the follow's own jumpTo and starve it). The anchor keeps tracking during a gesture, so the follow resumes from the pilot's live position with no jump. The abstract backend needed no change — OrbitControls deltas compose with the follow shift naturally. Unit-tested with a mocked mapbox-gl (terrain-follow.test.ts: anchor, zero-delta, gesture yield/resume, multi-touch, easing, re-anchor).

Verification

  • bun run typecheck:all passes; bun test web/frontend/src/replay/terrain-follow.test.ts — 6/6 pass.
  • Playwright: default stays dark (scene corner luma 17); clicking Light applies the class, turns the body rgb(242, 240, 233), updates meta[theme-color], stores theme: "light", and the rendered WebGL scene goes off-white (screenshot-sampled luma 238); badges and the callout still work after the in-place scene rebuild; the theme persists across reload; Auto resolves an emulated dark OS scheme to dark and follows a live flip to light (stored as "system"); the full dark-mode regression suite still passes with no console errors.
  • Docs updated (docs/3d-flight-replay-notes.md §5.12 + §5.15).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DZpFYsCEyR7ck3bWmQNhCr

claude added 3 commits July 3, 2026 16:20
A Theme switch (Dark / Light / Auto) at the top of the View section,
stored as 'theme' in the shared glidecomp:preferences record ('system'
= auto, following prefers-color-scheme live via matchMedia).

- Light mode uses an off-white background (#f2f0e9), not full white.
- main.ts toggles a 'light' class on <html>; the page's own <style>
  defines --rp-* theme tokens plus scoped remaps of the dark slate
  utilities the standalone page uses. The gauge canvas reads the same
  tokens (needle/dial colours re-read + dial repainted on switch).
- In-scene furniture follows the theme only on the abstract backdrop
  (clear colour, grid, ground + gaggle label halos, and the vario-ramp
  zero via a new uVarioZero uniform so near-level trail segments don't
  vanish into the off-white). The scene rebuilds in place on switch;
  on terrain the backdrop is map imagery so the scene stays map-styled.
- The vario colour-scale legend mirrors the themed ramp; the meta
  theme-color and the saved theme are applied before load so light
  users don't get a dark flash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZpFYsCEyR7ck3bWmQNhCr
The dark-backdrop pastels washed out on the off-white:

- Turnpoint rings/walls use deeper shades in light mode (emerald-700
  start, red-600 goal, amber-700 turnpoints); wall opacity drops a
  notch to compensate for the darker colour stacking across DoubleSide
  layers.
- Every pilot cone gets a dark inverted-hull silhouette (a ~16% larger
  back-face-only InstancedMesh mirroring the marker matrices), so pale
  pilot colours — light lime, yellow — read crisply without changing
  the identity colour that matches the legend chips and badges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZpFYsCEyR7ck3bWmQNhCr
…works

In map mode on a phone, pan and orbit were impossible while following a
pilot: the follow calls map.setCenter every frame, setCenter wraps
jumpTo, and jumpTo's first act is stop() — which cancels the active
gesture handlers. Mouse drags mostly survived (they re-establish per
mousemove); touch gestures are stateful and died continuously. It even
fired with playback paused, since the zero-delta case still called
setCenter.

followTo now keeps the follow anchor fresh every frame but skips
setCenter when (a) the pilot hasn't moved, (b) any pointer is down on
the map (container pointerdown / window pointerup+pointercancel
counter, so a finger released off-map can't leak), or (c)
map.isEasing() — so the orientation presets' easeTo survives too
(isEasing, not isMoving, which could report the follow's own jumpTo
and starve it). Because the anchor keeps tracking during a gesture the
follow resumes from the pilot's live position with no jump.

The abstract backend is untouched — OrbitControls deltas compose with
the follow shift naturally. Unit-tested with a mocked mapbox-gl in
terrain-follow.test.ts (anchor, zero-delta, gesture yield + resume,
multi-touch, easing, re-anchor cases).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZpFYsCEyR7ck3bWmQNhCr
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

Preview Deployment
https://5f6594d1.glidecomp.pages.dev
Commit: 3e5c8b1

@pokle pokle marked this pull request as ready for review July 3, 2026 21:20
@pokle pokle merged commit 7a1cf60 into master Jul 3, 2026
6 checks passed
@pokle pokle deleted the claude/3dvis-replay-metrics-cdj8nu branch July 3, 2026 21:21
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