From 28db2de74f8cae85ed3a4a930be617bf2b4267a5 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 13 Jun 2026 10:33:50 +0300 Subject: [PATCH] =?UTF-8?q?feat(labs):=20=D0=A4=D0=B0=D0=B7=D0=B00=20?= =?UTF-8?q?=E2=80=94=20=D1=8D=D0=BA=D0=BE=D0=BD=D0=BE=D0=BC-=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B8=D0=BC=20FX=20+=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D1=81=D0=B8=D0=BC=D1=83=D0=BB=D1=8F=D1=86=D0=B8=D0=B8=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D0=B2=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit План улучшения симуляций — plans/simulations-improvement/README.md. - LabFX: reduced-motion/эконом-режим (prefers-reduced-motion + тумблер localStorage labfx-economy). Тряска отключается, частицы ×0.25 — доступность и экономия на слабых устройствах сразу для всех ~50 симуляций. Кнопка-тумблер в lab.html рядом со звуком. - lesson-editor: блок «Симуляция» — выпадающий список из /api/lab/sims (сгруппирован по предметам) вместо сырого ввода simId; неизвестный id не теряется, помечается «(не найдена)». Закрывает хрупкую вставку в урок. Co-Authored-By: Claude Opus 4.8 --- frontend/js/labs/_fx_core.js | 23 ++++++++ frontend/js/labs/_fx_particles.js | 3 + frontend/lab.html | 25 ++++++++ frontend/lesson-editor.html | 44 ++++++++++++-- plans/simulations-improvement/README.md | 77 +++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 plans/simulations-improvement/README.md 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 @@ + + +