feat(v2): add reduced-effects mode for low-power devices - #3766
Merged
Conversation
The v2 UI leans on two GPU-heavy decorative effects that are cheap on a desktop GPU but punishing on weak hardware (TV boxes, older phones): - a full-viewport filter: blur(28px) background-art layer (always on), which the code's own Firefox note already flags as pegging weak rendering backends; and - a per-cover filter: blur(16px) blur-up reveal, run on ~100 covers at once when a full grid paints. Add a per-device "Reduced effects" toggle (Settings > User interface, under Theme) backed by a localStorage flag, mirroring useCrtMode. When on, AppLayout sets `r-v2-reduced-effects` on <html> and the global / GameCover CSS drops the blur filters (keeping tone and the opacity fade), so low-power devices composite sharp, unblurred visuals. Per-device rather than backend-synced on purpose: whether effects are too heavy depends on the machine RomM is viewed on, not the account. Generated-By: PostHog Code Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
Contributor
Greptile SummaryThis PR adds a per-device Reduced effects mode to the v2 interface. The main changes are:
Confidence Score: 4/5The cover-blur selector needs a contained fix before merging.
frontend/src/v2/components/shared/GameCover.vue Important Files Changed
Reviews (1): Last reviewed commit: "feat(v2): add reduced-effects mode for l..." | Re-trigger Greptile |
Reduced-effects mode now defaults to the OS `prefers-reduced-motion` setting instead of always starting off: someone who asks their system to reduce motion generally wants fewer heavy effects too, so the mode turns on for them with no per-app opt-in. The Settings toggle still records an explicit choice that overrides the system default from then on. The stored value becomes tri-state: null (no explicit choice, follow the system) or true/false (user override), with an explicit serializer since vueuse's null-default path would otherwise read booleans back as strings. Add unit tests covering the system default, override precedence, and persistence. Generated-By: PostHog Code Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
reduced-effects only dropped blur, leaving every animation running. The OS prefers-reduced-motion query is honored per-component, but a CSS class can't trigger those media blocks and the toggle must work even when the OS setting is off, so a user enabling low-power mode on a TV box kept the CRT flicker, disc spins, skeleton shimmer, spinners and scan pulses that tax a weak GPU. Make reduced-effects a superset of reduced motion: - global.css neutralizes animation/transition under html.r-v2-reduced-effects (near-zero durations so transitionend/animationend still fire; !important overrides inline JS-set transitions such as RExpandTransition). - Wire the JS-driven motion gates that CSS can't reach to the mode: useCoverAnimation (the per-card CD-spin rAF loop) and useViewTransition (shared-element morphs). Both keep the independent OS prefers-reduced- motion check so an explicit "off" never re-enables motion for a user whose system asks to reduce it. Lib primitives (RExpandTransition, RBox3D) are covered by the global CSS rule rather than importing app state, per the primitive tier boundary; RBox3D's JS auto-spin stays OS-gated. Description updated across locales. Generated-By: PostHog Code Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Four files hand-rolled their own window.matchMedia("(prefers-reduced-
motion: reduce)") check (useCoverAnimation, useViewTransition,
RExpandTransition, RBox3D) and useReducedEffects held a fifth via
useMediaQuery. Consolidate into one useReducedMotion composable, a
module-level singleton returning a reactive Ref<boolean> (same pattern as
useBreakpoint; wraps what vueuse's usePreferredReducedMotion exposes but
as the plain boolean every call site wants).
Side benefit: the two composable call sites that read the old function
inside a computed are now reactive, so cover motion and the 3D-box idle
drift stop/resume live when the OS setting changes, no remount. It stays
generic (no store/service/domain), so the two primitives may use it
within the tier boundary.
Generated-By: PostHog Code
Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
Two hooks modelled overlapping concerns and useReducedEffects imported useReducedMotion for its default, coupling them. Collapse to a single useReducedMotion that owns the whole "reduce motion / effects" axis: enabled = override ?? OS prefers-reduced-motion (+ toggle()) It drives the one r-v2-reduced-motion root class (background blur, cover blur-up, the global animation/transition neutralize) and the JS motion gates (cover CD-spin, view-transition morphs, RExpandTransition, RBox3D). One hook, one Settings toggle, one source of truth. - Delete useReducedEffects; move its override tests onto useReducedMotion. - Rename the class r-v2-reduced-effects -> r-v2-reduced-motion and the localStorage key to settings.v2.reducedMotion. - Rename the i18n keys reduced-effects(-desc) -> reduced-motion(-desc) across all locales; title is now "Reduced motion" (the description already covered both blur and animation). Generated-By: PostHog Code Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
Reduced-motion drops the 28px backdrop blur, which exposed two rough
edges the blur had been hiding:
- The fallback backdrop art (auth_background.svg) is authored to be seen
only through the blur; unblurred it shows a hard seam ("weird bar") and
a bright diagonal wedge. Drop the whole fixed art + overlay stack in
this mode and let the solid page background show through — cheaper on
weak GPUs and no seam.
- The top nav bar's only surface is a backdrop-filter glass. Against the
now-solid background that reads as murky, and the blur is expensive on
weak GPUs. Swap it for a flat opaque surface so the fixed bar stays a
clean solid strip as content scrolls under it.
Generated-By: PostHog Code
Task-Id: 6690877a-9c32-4c91-a78b-3d6fd3851fb7
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.
Description
Explain the changes or enhancements you are proposing with this pull request.
The v2 UI leans on two GPU-heavy decorative effects that are cheap on a desktop GPU but punishing on weak hardware (TV boxes, older phones). This adds a per-device Reduced effects toggle to disable them, complementing the existing "Disable animations" setting (which does not cover the always-on background blur).
The effects gated:
filter: blur(28px)background-art layer (v2/styles/global.css), always on regardless of animation settings. The code's own Firefox note already documents this as re-rasterizing every frame and "pegging the WebRender backend", exactly the situation on a weak Mali GPU.filter: blur(16px)blur-up reveal (GameCover.vue), run on ~100 covers at once when a full grid paints.Implementation (mirrors the existing
useCrtModepattern):useReducedEffectscomposable backed by asettings.v2.reducedEffectslocalStorage flag.AppLayoutmirrors the flag onto<html class="r-v2-reduced-effects">(same rationale as the theme classes, so it reaches Vuetify-teleported overlays), and clears it on unmount.GameCover.vuedrop the blur filters under that class, keeping tone and the opacity fade so covers still fade in, just without the bloom/scale flourish (same treatment the existingprefers-reduced-motionblock already applies).SettingsToggleRowin Settings → User interface, under Theme next to CRT mode.reduced-effects/reduced-effects-descadded to all 18 locales (translated for fr/de/es/it/pt-BR, English placeholder elsewhere per the i18n contribution rule).check_i18n_locales.pyandcheck_i18n_sorted.pyboth pass.Why per-device (localStorage) rather than backend-synced: whether effects are too heavy depends on the machine RomM is viewed on, not the user's account. A user with both a TV box and a desktop wants reduced effects only on the box. This matches how CRT mode and debug mode are stored.
Follow-up to the gallery cover-fetch batching PR; both came out of diagnosing very slow v2 gallery loads on an Amlogic S905W2.
AI assistance disclosure: This change was authored with AI assistance (PostHog Code / Claude). Diagnosis, code, and comments were AI-generated and human-reviewed. Non-English translations beyond the source were left as English placeholders for native translators.
Checklist
Please check all that apply.
Screenshots (if applicable)
N/A (behavior/perf toggle; visual difference is the absence of background/cover blur when enabled).
Created with PostHog Code