Skip to content

feat: auto-switch background effects on device graphics capability - #395

Open
QSchlegel wants to merge 1 commit into
preprodfrom
claude/animation-hardware-detection-dede61
Open

feat: auto-switch background effects on device graphics capability#395
QSchlegel wants to merge 1 commit into
preprodfrom
claude/animation-hardware-detection-dede61

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

Alternative to #390. That PR removes the aurora, the MarbleField shader and the three.js globe for everyone; this one keeps them and decides per device whether they run.

How it decides

src/lib/graphics-tier.ts scores the signals a browser actually exposes — hardwareConcurrency, deviceMemory, the unmasked WebGL renderer string, screen area × dpr², coarse-pointer/handheld, prefers-reduced-motion, saveData — into three tiers:

tier aurora layers aurora keyframes pointer parallax MarbleField (WebGL) three.js globe
high
medium
low ✅ (painted once)

Reduced-motion or data-saver short-circuit straight to low, and high is never returned when the GPU is missing or software-rasterized (SwiftShader / llvmpipe / Basic Render Driver), whatever the CPU and memory say. Integrated Intel parts land in weak: they run the CSS aurora fine and stutter on a full-viewport fragment shader plus a globe, which is the complaint behind #390.

Heuristics can be wrong, so src/hooks/useGraphicsTier.ts backs them with measurements:

  • a ~1s frame-rate probe 1.5s after load — under 45fps drops one tier, under 30fps drops to low, and it re-probes once after a downgrade to see whether that was enough. The cap is cached in sessionStorage, so later navigations start already capped.
  • a battery cap: discharging below 20% never runs the WebGL surfaces, and it lifts again on plug-in.
  • a live prefers-reduced-motion listener.

State lives at module scope, so layout, homepage, api-docs and the new-wallet flow share one detection pass, one probe and one answer. The effective tier is published as data-gfx-tier on <html> so purely-CSS keyframes degrade with it instead of every component threading a class down.

Other changes

  • api-docs imported the globe statically, shipping three.js + three-globe to every visitor. It is now a dynamic() import, so devices that will not draw it never download it — and its full-page backdrop-filter: blur(16px) is dropped when there is nothing behind it to blur.
  • Appearance setting goes from an on/off switch to Auto / Full / Reduced / Off (persist version: 2, old backgroundEnabled: falseoff, trueauto). Auto shows what was detected and why (8 CPU cores, discrete or modern GPU) and says so when the frame-rate probe stepped it down.
  • Nothing renders differently before hydration: until detection resolves, every consumer sees the same conservative feature set the server rendered.

Test plan

  • npm test — 1292 passed / 2 skipped, plus 87 in the ESM project. Includes a new src/__tests__/graphicsTier.test.ts covering renderer classification and the device profiles the weights are calibrated against (modern desktop → high, good-GPU phone → medium, old integrated laptop → low, software rasterizer → never WebGL).
  • npm run build (webpack) clean.
  • Pixel- and DOM-verified against next start:
    • auto on this machine (10 cores, 32GB, Apple GPU) → data-gfx-tier="high", 10 animated aurora nodes, MarbleField canvas present, globe + backdrop-filter on /api-docs
    • mode reduced → tier medium, 0 canvases, aurora still animating
    • simulated frame-rate cap → tier low, 0 animated nodes, background layers still painted
    • mode off → no background layers, no canvas, no backdrop-filter
  • The Appearance card itself is behind wallet auth, so it is verified at code/type level only.

🤖 Generated with Claude Code

The three decorative surfaces (CSS aurora, WebGL marble field, three.js
globe) rendered unconditionally for everyone, which is what motivated
removing them outright. Instead, detect what the device can afford and
render that much.

- src/lib/graphics-tier.ts: pure classification of CPU cores, device
  memory, unmasked GPU renderer, framebuffer size, handheld-ness,
  reduced-motion and data-saver into high/medium/low, plus the feature
  set each tier may render. Split from the browser reads so it is unit
  testable; `high` is never returned without a usable GPU.
- src/hooks/useGraphicsTier.ts: shared runtime state — one detection
  pass, a post-load frame-rate probe that steps the tier down when the
  page is not keeping budget (cached per session), a battery cap below
  20% discharging, and a live reduced-motion listener. Publishes
  data-gfx-tier on <html> so CSS keyframes degrade with it.
- Aurora keeps its layers at every tier but stops animating at low; the
  marble field and globe only mount at high. api-docs now loads the
  globe dynamically instead of shipping three.js to every visitor, and
  drops its full-page backdrop-filter when there is nothing behind it.
- Appearance setting becomes Auto / Full / Reduced / Off (persist v2
  migration from the old on/off switch) and shows what was detected and
  why, including an automatic step-down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
multisig Ready Ready Preview Sep 11, 2026 8:11am UTC

Request Review

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.

1 participant