Releases: Delido/signalrgb-wallpaper
v2.3.13-beta — water sRGB color-space fix + Configurator i18n sweep
Fixed — Water mode shifted the whole BG, not just the ring band
The v2.3.12 scale=0 fix stopped the activation jump, but during an active ripple the BG still appeared shifted across the entire viewport — not just where the ring band crossed.
Root cause: SVG filter color-space mismatch. Chromium runs feDisplacementMap in linear-RGB by default, but our displacement map canvas exports as sRGB-encoded PNG. The sRGB → linear conversion remaps R=128 from 0.502 down to ~0.215, which means the "neutral grey" the canvas paint treats as 0-displacement actually encodes (0.215 - 0.5) × 80 = -22.8 px displacement across the entire non-ring area of #bg. Net visual: the whole BG slid -22 px up-left whenever a ripple was in flight.
Fix: color-interpolation-filters="sRGB" on the filter element. Filter chain operates in sRGB throughout, matching the PNG encoding. R=128 produces true 0 displacement at neutral; only the ring band's painted gradients actually bend pixels. No whole-BG shift, no edge gap, no ghost overlay.
Configurator i18n sweep
User reported large chunks of the configurator still rendering in English when DE was selected:
- applyI18n attribute-targeting rewrite — the pre-v2.3.13
data-i18n-attrmechanism collected the attribute name as a{attr}template parameter instead of using it as the target attribute. The Library search placeholder stayed English regardless. Added independentdata-i18n-title/data-i18n-placeholder/data-i18n-aria-label/data-i18n-altslots that each carry their own translation key, on top of the maindata-i18nfor textContent. - Quick Looks bundles (10 × name + desc = 20 entries) now i18n-driven. Each
LOOK_BUNDLESentry carriesnameKey/descKey, routed throught()in renderLookBundles + applyLookBundle's confirm dialog. - Widget catalog labels (13 widget type names) now i18n-driven via a new
_widgetLabel(def)helper. Five directdef.labelreads converted (layout preview, Add-Widget grid, widget list, modal title, remove toast) + two toast templates ("Added X" / "Removed X"). - Static dropdown options got
data-i18nattributes + matching translation rows: glow layout (5), grid renderer (2), glass quality (3), frame rate (3). - Header tooltips for Live preview / Show tour / Open library via the new
data-i18n-titleslot. renderAllnow re-builds the Add-Widget grid + Quick-Looks grid on language switch — both were built once at boot.
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.12-beta — real fix for water-mode BG shift
The v2.3.11-beta pre-seed via toDataURL didn't work. Data: URLs load asynchronously, so the SVG filter's first paint still saw an empty in2 input and shifted #bg by -40 px until the load completed a frame or two later.
Fixed — water-shift, for real this time
Flipped the strategy. feDisplacementMap scale now starts at 0, which makes the displacement provably 0 px no matter what in2 resolves to:
splash()bumpsscaleto ACTIVE_SCALE (80) before the next tick paints- The tail-tick that runs after the last ripple expires drops
scaleback to 0 disable()also resets scale to 0 as belt-and-suspenders
No more activation jump. The BG only moves when a wave is actually in flight.
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.11-beta — water-shift fix + drop All + on-screen lock pill
Three follow-up fixes off v2.3.10-beta's water rebuild.
Fixed — Water mode shifted the wallpaper image on activation
feDisplacementMap treats a missing in2 input as R=G=B=0, which yields a uniform (0/255 - 0.5) × scale shift — at scale=80 that's -40 px across the whole BG. The wallpaper visibly jumped up-left as soon as you picked water mode, then snapped back only after the first click painted a real displacement map.
Fix: pre-seed feImage with the neutral-grey canvas (rgb(128,128,0)) inside ensureFilter() so the displacement starts at zero from frame 1.
Changed — "All" mode removed from Pixelfx
Stacking trail + glow + click ripple at once was visually muddy and weakened the single-select intent of the mode picker. Removed:
- The "All" tile from the Configurator (
PIXELFX_MODES) - The "all" value from the wallpaper-side allow-list in
setMode - The
mode === "all"branches inwantsTrail/wantsGlow/wantsRipple(now strict equality) - The "All" entry in the tray menu's
PIXELFX_MODES_TRAY
Bonus: the tray menu gained the "Water ripple" entry it was missing. Any persisted pixelfx="all" falls through to "off" — pick a new single mode in the Configurator.
Added — On-screen lock pill
While widgets are unlocked (edit mode), a small floating pill appears at top-center of the wallpaper:
[Widgets unlocked] [🔒 Lock]
Click the lock button and widgets re-lock without going back to the Configurator. The pill disappears immediately. Reuses the pre-existing #widgets-banner CSS scaffolding (which was previously dead code).
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.10-beta — Water ripple is now actual BG refraction
The v2.3.3-beta "Water ripple" mode was three concentric outline strokes per click. Visually it read as an inflated click ring, not as water. Rebuilt from the ground up to use real SVG-displacement refraction on the wallpaper image.
Changed — Water ripple uses SVG feDisplacementMap on #bg
New water IIFE module owns its own SVG <filter id="pixelfx-water-filter"> with feImage + feDisplacementMap (scale=80), applied via body.fx-pixelfx-water #bg. Only the BG image gets the filter — the glow bars stay stable so they don't read as part of the wave.
Per-click behavior:
- Two staggered ripples spawn per click (the second 0.12 s behind the first → single click produces a visible follow-up wave)
- rAF chain paints expanding ring fronts into a 256×256 displacement-map canvas
- 48 angular slices per ripple: outer band encodes radial outward push (R/G shifted by
+cos θ × amp / +sin θ × amp), inner band encodes radial inward pull (opposite sign) - Activity-gated
toDataURL+setAttribute("href")— encoding stops when the last ripple expires (gotcha-uncapped-raf-dpr pattern, same memory discipline Liquid Distortion already uses)
Architecture mirrors the existing Liquid Distortion (mousefx.ripple) which uses the same SVG-displacement technique for cursor-driven ripples — but with a separate filter ID so both effects can run independently.
The pixelfx canvas is stopped (backing buffer → 1×1) in water mode since it no longer participates in the render.
Visual result
The wallpaper image actually bends along a propagating wave front. Reads as water on the surface of your screen, not as a colored halo on top.
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.9-beta — Pixelfx (Cursor) section → fx-tile grid
Configurator polish — the last .seg button row in the Effects tab moves to the same tile layout the rest of the tab already uses.
Changed — Pixelfx (Cursor) → fx-tile grid
The Pixelfx mode row (Off / Trail / Hover glow / Click ripple / Water ripple / All) was the last segmented-button strip in the Effects tab. Every neighbouring control (ambient presets, cursor distortion effects) had already moved to a .fx-tile grid; this brings Pixelfx in line.
Each mode now gets:
- An emoji icon (🚫 / ✨ / 💡 / 💫 / 💧 / 🎆)
- A short label
- A one-line description (DE + EN)
Active state is the same .active class the other tile grids use. Selector behaviour is unchanged — Pixelfx is single-select (radio), unlike the stackable mousefx grid. Existing pixelfx setting values migrate 1:1; nothing in the wallpaper bundle changes.
Fixed — mousefx tile collector was unscoped
Bonus correctness fix that fell out of the above. The mousefx tile click handler used an unscoped document.querySelectorAll(".fx-tile.active") to gather mouseEffects state. Once the pixelfx grid also used .fx-tile, an active pixelfx tile would have been swept into the array as an undefined entry. Scoped both the bind loop and the renderAll loop to #mousefx-tiles.
Upgrade
Configurator-only change — no wallpaper bundle re-import needed. WALLPAPER_VERSION stays at 2.3.8-beta.
Beta — no winget submission, GitHub Releases only.
v2.3.8-beta — Three more GPU-budget wins
Three more GPU-budget wins on top of v2.3.7-beta's Repulsion fix.
Fixed — Stripes/Pills layouts held N in-flight CSS transitions
Same regression class as the Repulsion fix (and the v1.0-era grid-zone transition before it). The Vertical Stripes / Horizontal Stripes / Centered Pills bar layouts each carried a transition: background 0.08s linear on their .zone elements. JS rewrites the --c CSS var per zone every render tick (~30 Hz), restarting the 80 ms transition every 33 ms — N background transitions juggling continuously. Typical 16-30 zone stripes layout = 16-30 in-flight CSS animations for no visible benefit, since JS updates were already smooth. Dropped the transition; visuals unchanged.
Fixed — Audio-spectrum widget ran at 1 fps
Generic 1 Hz widget setInterval was redrawing the FFT bars — once per second — even though lastAudio was refreshed at the host's ~30 Hz FFT cadence. Added a reg.fastTick opt-in plus a counter-gated rAF loop (self-stops at 0 fast widgets, pause-guarded, resume-rearmed) that walks fast widgets at RENDER_INTERVAL_MS. The 1 Hz path skips fast widgets so there's no double redraw.
Changed — Pixelfx + audioglow now use SCALE × DPR (like ambient)
The ambient particle canvas already routed through _qualityScale() (0.5/0.75/1.0 per bucket) on top of _qualityDpr(). Pixelfx (cursor trail / glow / ripple / water) and audioglow (radial pulse / wide spectrum / wave) only had the DPR multiplier — full viewport in Performance bucket.
Both canvases now apply the SCALE multiplier too: a 5120×1440 surface drops from 7.4 Mpx to 1.85 Mpx in Performance bucket (4×) and 4.2 Mpx in Balanced (~1.75×). Content is soft (alpha gradients, blurred sprites) so bilinear upscale is visually indistinguishable. Quality bucket stays at native 1:1 × DPR 2.
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.7-beta — Drop Repulsion CSS transition (GPU perf)
GPU-side performance fix on top of v2.3.6-beta's memory pass.
Fixed — Widget Float repulsion kept N×2 CSS animations in flight
The Widget Float effect wrote --repel-x / --repel-y CSS custom properties on every widget every 33 ms (30 Hz tick). A separate body.fx-repulsion .widget rule wrapped both vars in a 0.18 s cubic-bezier transition — so every 33 ms rewrite restarted the 180 ms transition.
With N widgets the compositor kept 2N CSS animations in flight at all times, juggling them every frame. Position output was already smoothed twice (once by the JS dt-based interpolation, once by the CSS easing), so the GPU was paying for an N-widget animation set that didn't need to exist. Removed the transition; the 30 Hz JS updates are perceptually smooth on their own.
Same class of regression gotcha-perf-transitions warned about (the v1.0-era grid-zone background transition was the original culprit).
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.6-beta — Collapse idle canvases (≈ -150 MB on 2-monitor setups)
Memory-footprint optimisation pass.
Changed — idle canvases collapse to 1×1
The wallpaper page hosts four full-viewport canvases:
- ${'
'}#bars-canvas${''} — glow grid in canvas-renderer mode - ${'
'}#ambient-canvas${''} — snow / rain / sparks / aurora / storm / … - ${'
'}#pixelfx-canvas${''} — trail / hover-glow / click ripple / water - ${'
'}#audioglow-canvas${''} — pulse / spectrum bars / waveform
Pre-v2.3.6-beta every one of them kept a viewport-sized backing buffer even when the effect was off. At 5120×1440 × DPR=1 × 4 bytes per pixel that's ~30 MB per canvas → ≈ 100 MB resident per wallpaper instance with three of the four idle. Multi-monitor setups doubled or quadrupled that because each monitor runs its own WebView2 instance.
Each effect's ${''}stop()${''} (plus the canvas-off branch in ${''}_syncGridCanvasVisibility()${''} for the bars canvas) now sets ${''}canvas.width = canvas.height = 1${''} after clearing. ${''}start()${''} already calls ${''}resize()${''} to set the canvas back to full viewport on re-enable, so the trade-off is one allocation per off→on transition.
Realistic ceiling on a 2-monitor 5120×1440 setup with three of the four effects idle: ≈ -150 MB RAM.
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Beta — no winget submission, GitHub Releases only.
v2.3.5-beta — Cursor effects UX + fullscreen pause fix
Three quick UX fixes on top of v2.3.4-beta after the first round of real-world feedback.
Fixed — cursor effects ignored fullscreen pause
The four MouseFx ticks (Liquid Distortion, Chromatic Halo, Spotlight, Widget Float) were the only rAF loops in the wallpaper page that didn't short-circuit on isPaused. Every other tick (ambient, audio-glow, pixelfx, parallax, renderFrame) already bailed and the wallpaper-resume event re-armed them on unpause. Now all four follow the same pattern, and a fresh resume listener kicks them back to life when fullscreen pause clears — no more silent CPU drain while you game or watch a film.
Changed — moved from Widgets tab to Effects tab
The four cursor distortion effects lived in the Widgets card because that's where they shipped from, but they alter the wallpaper visuals, not widget layout. Moved into the Effects card right under the existing Pixelfx (cursor) row where they conceptually belong.
Changed — 4 checkboxes → tile grid
The old labeled-checkbox row was bland and didn't surface what each effect actually does. New tile grid: emoji + name + one-line description on each tile. Active tiles get a blue tint + border so the state reads at a glance. Same four effects, same mouseEffects array persistence — UI only.
Upgrade
Wallpaper bundle re-import required for the fullscreen-pause fix to land (Lively / Wallpaper Engine cache the bundle in a random-hash folder). Tray → Re-import wallpaper bundles or re-add the freshly installed zips manually.
Beta — no winget submission, GitHub Releases only.
v2.3.0 — In-app pack browser, atmosphere effects, widget skins, cursor overhaul, GPU/RAM pass
Six months of beta iteration consolidated into one stable release. v2.2.1 → v2.3.0 covers v2.3.0-beta through v2.3.13-beta.
Highlights
In-app wallpaper pack browser
The Library tab browses curated pack manifests hosted on GitHub Pages. Tiles preview every bundled wallpaper; click adds the whole pack to the local library. New packs ship without a Configurator update.
Atmosphere effects
- Storm preset — rain particles + a lightning flash subsystem with random strikes and occasional doubles
- Water-ripple pixelfx — clicks bend the wallpaper image along a propagating SVG-displacement wave (real BG refraction, not an outline overlay)
- Weather-reactive ambient — picks the right preset from the live WMO code (Open-Meteo)
Widget skin system
Every widget type can ship alternate skins. Weather launches with three: default, compact, hexagon. Skin picker lives in the per-widget edit dialog.
Cursor-effect overhaul
- Liquid Distortion / Chromatic Halo / Spotlight / Widget Float moved to the Effects tab as a tile grid (replacing the v1.6-era checkbox row)
- Pixelfx mode picker also became a tile grid in v2.3.9
- New on-screen lock pill: re-lock widgets without going back to the Configurator
GPU + memory optimisation pass
- Idle full-viewport canvases (ambient / pixelfx / audio-glow / bars) collapse to 1×1 backing buffer (~30 MB saved per canvas per WebView2 instance on a 5120×1440 setup)
- Pixelfx + audio-glow gained the same SCALE × DPR bucket the ambient canvas already had — Performance bucket = 4× fewer pixels per frame
- Widget Float repulsion no longer juggles 2N in-flight CSS transitions every render tick
- Stripes/Pills layouts no longer hold N background transitions for the same reason
- Audio-spectrum widget redraws at 30 Hz via a new counter-gated rAF path (was stuck at 1 fps)
Configurator i18n sweep
applyI18ngained independent attribute translation slots (data-i18n-title,data-i18n-placeholder,data-i18n-aria-label,data-i18n-alt)- All Quick Looks bundles, widget catalog labels, glow / glass-quality / frame-rate / grid-renderer dropdowns, and header tooltips now follow the active language
- Add-Widget grid + Quick-Looks grid rebuild on language switch
Notable fixes from the beta line
- Water-mode whole-BG shift (v2.3.13) — SVG filter color-space mismatch. Chromium runs
feDisplacementMapin linear-RGB by default but our displacement map canvas exports as sRGB PNG. R=128 sRGB → 0.215 linear → constant -22 px shift across the non-ring area. Addedcolor-interpolation-filters="sRGB"on the filter. - Memory leaks (v2.3.4 / v2.3.5) — Liquid Distortion's per-frame
toDataURLwas leaking ~1 MB/s of decoded SVG-filter bitmaps; Spotlight allocated a fresh CSS gradient string per frame - Cursor effects ignored fullscreen pause (v2.3.5)
- Library bulk-select / tag-filter edge cases (v2.3.1 / v2.3.2)
Upgrade
Wallpaper bundle re-import required. Tray → Re-import wallpaper bundles.
Full per-beta history in CHANGELOG.md.