Skip to content

feat(v2): add reduced-effects mode for low-power devices - #3766

Merged
gantoine merged 7 commits into
masterfrom
posthog-code/v2-reduced-effects
Jul 16, 2026
Merged

feat(v2): add reduced-effects mode for low-power devices#3766
gantoine merged 7 commits into
masterfrom
posthog-code/v2-reduced-effects

Conversation

@gantoine

Copy link
Copy Markdown
Member

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:

  1. A full-viewport 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.
  2. A per-cover filter: blur(16px) blur-up reveal (GameCover.vue), run on ~100 covers at once when a full grid paints.

Implementation (mirrors the existing useCrtMode pattern):

  • New useReducedEffects composable backed by a settings.v2.reducedEffects localStorage flag.
  • AppLayout mirrors 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.
  • Global CSS and GameCover.vue drop 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 existing prefers-reduced-motion block already applies).
  • A SettingsToggleRow in Settings → User interface, under Theme next to CRT mode.
  • i18n keys reduced-effects / reduced-effects-desc added to all 18 locales (translated for fr/de/es/it/pt-BR, English placeholder elsewhere per the i18n contribution rule). check_i18n_locales.py and check_i18n_sorted.py both 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.

  • I've tested the changes locally
  • I've updated relevant comments
  • I've assigned reviewers for this PR
  • I've added unit tests that cover the changes

Screenshots (if applicable)

N/A (behavior/perf toggle; visual difference is the absence of background/cover blur when enabled).


Created with PostHog Code

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
Copilot AI review requested due to automatic review settings July 16, 2026 12:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a per-device Reduced effects mode to the v2 interface. The main changes are:

  • A localStorage-backed preference and settings toggle.
  • A document-root class synchronized by the v2 layout.
  • CSS overrides for background and cover-image blur effects.
  • New labels and descriptions across all supported locales.

Confidence Score: 4/5

The cover-blur selector needs a contained fix before merging.

  • The persistence, settings binding, and document-class synchronization follow existing patterns.
  • The global background override removes its target blur correctly.
  • The scoped cover selectors cannot reach the document root, so that part of the toggle does not work.

frontend/src/v2/components/shared/GameCover.vue

Important Files Changed

Filename Overview
frontend/src/v2/components/shared/GameCover.vue Adds cover reveal overrides, but the scoped document-root selectors cannot match.
frontend/src/v2/composables/useReducedEffects/index.ts Adds a shared localStorage-backed preference using the existing singleton composable pattern.
frontend/src/v2/layouts/AppLayout.vue Mirrors the preference onto the document root and removes the class on layout unmount.
frontend/src/v2/styles/global.css Removes the background blur while preserving theme-specific brightness and saturation.
frontend/src/v2/views/Settings/UserInterface.vue Adds the Reduced effects control using the existing settings-row model contract.
frontend/src/locales/en_US/settings.json Adds the source strings used by the new settings control, mirrored across the other locale files.

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(v2): add reduced-effects mode for l..." | Re-trigger Greptile

Comment thread frontend/src/v2/components/shared/GameCover.vue Outdated
gantoine and others added 6 commits July 16, 2026 09:19
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
@gantoine
gantoine merged commit cb7a67a into master Jul 16, 2026
12 checks passed
@gantoine
gantoine deleted the posthog-code/v2-reduced-effects branch July 16, 2026 17:13
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