diff --git a/frontend/js/labs/_fx_core.js b/frontend/js/labs/_fx_core.js
index 0ce0c0a..1433947 100644
--- a/frontend/js/labs/_fx_core.js
+++ b/frontend/js/labs/_fx_core.js
@@ -4,6 +4,28 @@
/* ── namespace (cooperative init) ── */
global.LabFX = global.LabFX || {};
+ /* ─────────────────────────────────────────────
+ REDUCED MOTION / ECONOMY MODE
+ Уважает системный prefers-reduced-motion + ручной тумблер
+ (localStorage 'labfx-economy'). Когда LabFX.reduced === true:
+ тряска отключается, частицы эмитятся в разы меньше — это и
+ доступность, и экономия на слабых устройствах.
+ ───────────────────────────────────────────── */
+ (function () {
+ var mq = null;
+ try { mq = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)'); } catch (e) {}
+ var manual = null;
+ try { var v = localStorage.getItem('labfx-economy'); if (v === '1') manual = true; else if (v === '0') manual = false; } catch (e) {}
+ function compute() { return manual != null ? manual : !!(mq && mq.matches); }
+ global.LabFX.reduced = compute();
+ global.LabFX.setEconomy = function (on) {
+ manual = !!on;
+ try { localStorage.setItem('labfx-economy', on ? '1' : '0'); } catch (e) {}
+ global.LabFX.reduced = compute();
+ };
+ if (mq && mq.addEventListener) mq.addEventListener('change', function () { global.LabFX.reduced = compute(); });
+ })();
+
/* ─────────────────────────────────────────────
GLOW — Canvas 2D bloom helper
───────────────────────────────────────────── */
@@ -59,6 +81,7 @@
* @param {number} opts.durMs - duration ms (default 200)
*/
global.LabFX.shake = function(elementOrCanvas, opts) {
+ if (global.LabFX.reduced) return; // эконом/reduced-motion — без тряски
opts = opts || {};
var intensity = opts.intensity != null ? opts.intensity : 5;
var durMs = opts.durMs != null ? opts.durMs : 200;
diff --git a/frontend/js/labs/_fx_particles.js b/frontend/js/labs/_fx_particles.js
index 1a8568c..75d118a 100644
--- a/frontend/js/labs/_fx_particles.js
+++ b/frontend/js/labs/_fx_particles.js
@@ -72,6 +72,9 @@
var size = opts.size != null ? opts.size : 3;
var sizeFade = opts.sizeFade != null ? opts.sizeFade : true;
+ // Эконом/reduced-motion — декоративных частиц в разы меньше
+ if (global.LabFX.reduced) count = Math.max(1, Math.round(count * 0.25));
+
for (var i = 0; i < count; i++) {
var p = acquire();
if (!p) break;
diff --git a/frontend/lab.html b/frontend/lab.html
index 2c6ea77..35f6351 100644
--- a/frontend/lab.html
+++ b/frontend/lab.html
@@ -362,6 +362,19 @@
+
+
+