fix(gamification): kill-switch не доходил до учебников (нет ls.css)

Учебники (frontend/textbooks/*.html, 112 шт.) грузят api.js, но НЕ ls.css. api.js ставил
класс .no-gamification на <html>, но сами правила kill-switch (`[data-gamified]`, попап XP)
живут в ls.css → до учебников не доходили, и встроенная XP-механика (бейдж/карточка XP,
ачивки, level-up попап #ach-popup) продолжала отображаться при выключенной геймификации.

Фикс — централизованно в _applyFeatureCss: при gamification=false дублируем правила
`.no-gamification [data-gamified],#ach-popup{display:none}` в инъектируемый <style>, поэтому
kill-switch работает на ЛЮБОЙ странице с api.js, без ls.css. Плюс на страницах без сайдбара
(учебники/embed) теперь авторитетно дёргаем loadFeatures() (только для залогиненных,
in-memory-дедуп) — кэш фич там не обновлялся. Админ по-прежнему видит всё (admin-override).

Verified vm-смоук на реальном api.js 7/7 (student+off → правило инъектится; student+on → нет;
admin → ничего).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-23 23:25:45 +03:00
parent 43df41287f
commit 8027d9fda0
+16 -1
View File
@@ -903,7 +903,14 @@ function _applyFeatureCss(feats) {
JSON.parse(localStorage.getItem('ls_examhide') || '[]')
.forEach(h => sels.push(`[href="${h}"]`));
} catch { /* пусто */ }
const css = sels.length ? sels.join(',') + '{display:none !important}' : '';
let css = sels.length ? sels.join(',') + '{display:none !important}' : '';
// Геймификация: дублируем kill-switch в инъекцию — для страниц БЕЗ ls.css.
// Учебники (frontend/textbooks/*.html) грузят api.js, но НЕ ls.css, поэтому правила
// .no-gamification из ls.css туда не доходят, и встроенная XP-механика (data-gamified,
// #ach-popup) оставалась видимой. Инъекция работает на любой странице с api.js.
if (feats && feats.gamification === false) {
css += '.no-gamification [data-gamified],.no-gamification #ach-popup{display:none!important}';
}
let el = document.getElementById('ls-feat-hide');
if (!el) {
el = document.createElement('style');
@@ -921,6 +928,14 @@ try {
_applyFeatureCss(_cachedFeats); // применит и кэш фич, и кэш скрытых exam-prep ссылок
} catch { /* нет кэша / приватный режим — просто ждём async */ }
/* Авторитетно подтянуть фичи на страницах БЕЗ сайдбара (учебники, embed): там
sidebar.js/hideDisabledFeatures не вызывают loadFeatures, и кэш мог устареть.
loadFeatures() кэширует in-memory (дубль-вызов = один fetch) и сам зовёт _applyFeatureCss.
Только для залогиненных — иначе на /login apiFetch поймает 401 и зациклит редирект. */
try {
if (isLoggedIn()) { loadFeatures().catch(() => {}); }
} catch { /* defensive */ }
/* Прячет группы сайдбара (.sb-group), у которых не осталось ни одного видимого пункта,
чтобы не висел пустой заголовок-аккордеон (напр. «Практика и игры», когда все
модули отключены). Зовётся после построения сайдбара и после hideDisabledFeatures. */