Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.
Phase 1 closes every hole.
Backend (source of truth):
• migration 029 seeds feature_gamification_enabled=1
• new isGamificationEnabled() helper in gamification/_shared.js with a
30s cache + invalidateGamificationCache() for instant admin toggles
• awardXP / awardCoins / updateStreak / unlockAchievement /
checkAchievements all bail out when the flag is off
• /api/gamification/* and /api/shop/* (user routes) return 404 when
disabled; admin routes remain open so the switch itself is reachable
• adminController.updateFeatures gains 'gamification' in the allow-list
and invalidates the cache on flip
Frontend:
• LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
so xp.js + applyCosmetics can bail without a round-trip
• xp.js load/add/flush become no-ops when the flag is off
• applyCosmetics skips the round-trip when off
• CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
.xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
hook for future blocks
Textbooks (Variant 2 of the plan):
• backend/scripts/wrap_textbook_xp.py — idempotent script that adds
data-gamified to 167 XP tags across 63 textbook files (chapters +
hubs, all subjects/grades). Single CSS rule now hides everything.
Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В SIDEBARS p1 (line 516, шпаргалка боковой панели) у формулы числа
диагоналей \$\dfrac{n(n-3)}{2} не было закрывающего \$.
KaTeX видел незакрытый блок $...$ — отображал как сырой текст:
'Число диагоналей — $\dfrac{n(n-3)}{2}'.
Исправлено: добавлен закрывающий $.
Полный аудит KaTeX по всем 4 главам Геометрии 8 — это была
единственная найденная ошибка. Остальные $...$ блоки чисты.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§4.1 'Параллелограмм + диагональ':
- Tick-марки для пары AB/CD рисовались на (116,103)-(130,97)
и (216,103)-(230,97). Но midpoint AB = (77,100), а не (123,100)
как указано в комментарии — агент ошибся в арифметике.
- Пересчитаны точно через перпендикуляр к каждому сегменту:
AB tick at midpoint (77,100); CD tick at midpoint (223,100).
Двойные tick'и для пары AB=CD=b, одиночные для BC=AD=a.
- Метки сторон 'a' и 'b' перепутаны: AB была помечена 'a' вместо 'b',
AD помечена 'b' вместо 'a'. Исправлено по правилу:
a = горизонтальная пара (BC, AD), b = наклонная пара (AB, CD).
§4.2 'Основные свойства':
- Дуги углов B и D использовали sweep=1 (большая 245° внешняя дуга
через ВНЕШНЮЮ область параллелограмма). Должно быть sweep=0
(короткая 115° внутренняя дуга через ВНУТРЕННОСТЬ).
- Подписи β сдвинуты ближе к дугам внутри полигона.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes applied (§1-§7 buildP1-buildP7 only):
§1.1 (Fix 11): Pentagon viewBox 170→185; C/D vertex labels at y=176 and
side label 'c' at y=174 were clipped — now visible.
§1.3 (Fix 1): Hexagon — added 3 missing diagonals (only 6 of 9 drawn);
expanded viewBox 160→175; caption moved from y=170 (clipped) to y=171.
§1.5 (Fix 2): Octagon — stray vertex circle and diagonal endpoints at
(140,16) replaced with actual 8th vertex (74,26); corrected two diagonal
endpoints accordingly.
§2.1 (Fix 12): Pentagon triangulation viewBox 165→178; A₃/A₄ vertex
labels at y=166 clipped → moved to y=172; caption moved y=156→y=174.
§2.2 (Fix 9): Equilateral triangle was isosceles (sides ~70,66,70);
replaced points to make all sides ≈62.4.
§2.3 (Fix 3): Nonagon viewBox 160→185; bottom vertices at y=170 were
clipped; caption moved to y=180.
§3.1 (Fix 10): Fixed misleading comment ("beyond A" → "beyond B").
§3.2 (Fix 4): Hexagon external angle extension line and arc were outside
viewBox width=280; redesigned to extend upward within bounds; viewBox
height expanded to 172.
§4.2 (Fix 5): Parallelogram angle arcs — C and D arcs were completely
swapped (drawn at each other's vertices); recalculated all arc endpoints
from unit vectors along polygon sides.
§4.3 (Fix 6): Side labels 8 and 5 swapped on example parallelogram
(AB=CD=8, BC=DA=5); corrected positions.
§4.3 (Fix 7): Angle arcs at A and C misplaced; recalculated endpoints
to correctly span each corner angle.
§6.1 (Fix 8): Признак 2 SVG used undefined marker #arr causing invisible
arrows; replaced with inline tick + polyline chevron marks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Корневая причина: каждый redraw() заменял SVG через innerHTML,
уничтожая элемент svgEl который onMove захватил в замыкании через
const svgEl = wrap.querySelector('svg'). На следующем pointermove
svgEl.getBoundingClientRect() возвращал {left:0,top:0,w:0,h:0} —
вершина прыгала в начало координат SVG, drag разваливался.
Применено к 5 интерактивам:
1. §4 Конструктор параллелограмма
2. §5 Живой параллелограмм — все свойства
3. §7 Живой прямоугольник — равенство диагоналей
4. §8 Признак прямоугольника — живая демонстрация
5. §9 Живой ромб
Что изменилось:
- Состояние (p4Active, p4Vname, p4OffX/Y и т.д.) вынесено на уровень
модуля, ВНЕ redraw().
- Один pointerdown-listener на wrapper-div через делегирование событий
(ev.target.closest('[data-v]')).
- clientToSvg() делает свежий document.getElementById(SVG_ID) на
каждый вызов — не закрепляется на устаревшем DOM-узле.
- SVG получают стабильный id.
- viewBox.baseVal для точного coordinate scaling.
- Offset capture на pointerdown (нет snap-to-pointer).
- touch-action:none на SVG root.
- Hit area r=16 (visible r=8) — легче попасть на touch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drag-фикс (12 интерактивов):
Корневая причина — el.setPointerCapture(ev.pointerId) вызывался при
pointerdown, потом redraw() заменял innerHTML, удаляя элемент
с захваченным pointer. На touch-устройствах поток событий терялся.
Применено ко всем drag-обработчикам §1, §4, §5, §8, §9, §10, §11,
§12, §13, §14, §15, §16:
- Удалён setPointerCapture (бесполезен после innerHTML replace)
- Добавлен ev.preventDefault() после проверки кнопки
- Добавлен e.preventDefault() в начале onMove
- window.addEventListener('pointermove', onMove, {passive: false})
- Флаг active для защиты от stale events
§7 «Живой прямоугольник — равенство диагоналей» — полностью переписан:
- A фикс, C draggable (13px hit area, cursor:grab)
- Прямоугольник всегда axis-aligned
- Обе диагонали dashed разного цвета (зелёная AC, янтарная BD)
- Двойные риски равенства на каждой диагонали
- Подписи длин у каждой диагонали в реал-тайме
- Хелпер sqMark() рисует правильные L-маркеры прямого угла во всех
4 углах прямоугольника, направленные внутрь
- Info-панель: AB, BC, периметр, площадь + постоянно зелёная карточка
'Диагонали AC = BD' с обоими значениями
§16 Интерактив 3 'Доказательство признака 1 пошагово' — переписан:
5 шагов с чёткими SVG-состояниями: Дано → опустить высоты DH₁,CH₂ →
равные углы при основании + равные высоты → конгруэнтность по
'угол-катет' → вывод AD=BC. Подсветки треугольников, штрихи равных
сторон, маркеры прямого угла у оснований высот.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В сообщениях feedback (после Проверить) формулы с $...$ показывались
как сырой LaTeX-источник, например 'Повтори: $S_1/S_2 = k^2.$'.
Причина: feedback() устанавливал innerHTML но не вызывал renderMath()
на этом элементе, поэтому KaTeX не обрабатывал формулы.
Добавлен try{renderMath(elm);}catch(e){} после установки innerHTML
во всех 3 файлах (ch1, ch2, ch3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§7 Прямоугольник:
- Card 7.1 (теория): 4 path-маркера которые тянулись ПО кромке
прямоугольника заменены на правильные polyline L-формы (9px),
направленные строго внутрь.
- Card 7.2 (свойство диагоналей): не было ни одного маркера прямого
угла — добавлены 4 на всех вершинах.
- Интерактив 1 «Живой прямоугольник»: маркеры стояли только в 2 углах
через <rect> которые частично выходили за прямоугольник. Заменены
на 4 правильных polyline вычисляемых из Math.min/max границ —
работают при любом направлении перетаскивания вершины B.
§10 Квадрат:
- Card 10.1 (определение): 4 path-маркера трассировавшие по кромке
заменены на правильные L-формы.
- Card 10.2 (свойства): то же.
- Card 10.3 (формулы): добавлены маркеры на все 3 квадрата (6-7px,
в цвет каждого квадрата).
- Интерактив 1 (слайдер): один <rect>-маркер в углу A заменён на
4 правильных polyline-маркера на всех вершинах ABCD, пересчёт
по каждому изменению слайдера.
Геометрия маркера: для угла V с направлениями u,w внутрь —
polyline V+9u → V+9u+9w → V+9w. Маркер всегда внутри фигуры,
оба сегмента перпендикулярны кромкам.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Баг: у элементов .feedback стоит inline style='display:none' и CSS-класс
.feedback с display:none. Класс .feedback.ok должен переключать на
display:block, но inline-стиль имеет ВЫСШУЮ специфичность и перекрывает
классовый display:block.
В итоге onclick-обработчики работали корректно (вызывали feedback()),
но сообщение оставалось скрытым из-за inline display:none.
Симптом: 'нажимаешь Проверить — ничего не происходит' в боссах, DnD,
тренажёрах, квизах — везде где есть .feedback элемент.
Фикс: функция feedback() теперь явно сбрасывает elm.style.display='block'
после установки класса. Добавлен null-check на elm.
Затронуто 3 файла (ch1, ch2, ch3). Все feedback-элементы во всех
параграфах теперь показываются после клика по Проверить.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В концах доказательств использовался LaTeX-маркер \square (или
\blacksquare) для QED. KaTeX рендерит его как пустой квадрат U+25A1
который во многих браузерах отображается как 'тофу' (битый глиф).
Заменены во всех 3 главах геометрии:
- \$\square\$ → <b>ч.т.д.</b> (HTML текст)
- \$\blacksquare\$ → <b>ч.т.д.</b>
- \quad\square в $$ → закрытие $$ + 'ч.т.д.'
- \square ABCD (как символ параллелограмма) → просто ABCD
Затронуто: 29 в ch1 + 26 в ch2 + 1 в ch3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Системный аудит 62 статических SVG в теоретических карточках выявил
2 мелких косяка:
Ch1 §10 (квадрат, карточка 10.2): не хватало прямоугольных меток в
двух верхних углах — у квадрата были обозначены только нижние.
Добавлены маркеры в (68,24) и (168,24).
Ch2 §2 (прямоугольник, карточка 2.2 — периметр): на верхней стороне
у стрелки была ссылка marker-end='url(#a2)', но сам marker #a2 в SVG
не определён → битая ссылка. Убрана для консистентности с остальными
тремя сторонами.
KaTeX-форматирование: проверено во всех 24 buildP-функциях обеих глав —
везде используются корректные $...$ / $$...$$ / \[...\] делиметры.
Конвертаций не потребовалось.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Было: продолжение рисовалось от next-vertex назад через v, дуга центрировалась
у next-vertex с углом из произвольного направления — углы отображались
неправильно (не у тех вершин, не в тех направлениях).
Стало: для каждой вершины v вычисляются prev/next, направления u=(v-prev)/|·|
(входящая сторона), w=(next-v)/|·| (исходящая). Продолжение u рисуется от v
дальше. Дуга — сектор у v от u-направления до w-направления, sweep
определяется через знак векторного произведения (u×w). Подпись угла —
по биссектрисе дуги на радиусе Rlabel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drag (12 SVG-интерактивов): pointermove/pointerup/pointercancel слушались на
самом vertex-элементе. При выходе курсора за пределы маленького круга drag
обрывался — отсюда эффект 'нажал, чуть-чуть потянулось, и всё'. Перенесены
на window — теперь работают как нативный drag.
§7 (Прямоугольник): info-карточка показывала 'AC = BD' с одним значением.
Теперь две отдельные карточки AC и BD + индикатор равенства (зелёная плашка
'Диагонали равны' / красная 'Не равны' с Δ).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В функции drawProof пошагового доказательства §4 использовалась переменная
cy без определения (была только cx). Это приводило к ReferenceError при
вызове buildP4, и из-за throw в ensureBuilt секция §4 не открывалась
при клике на карточку в селекторе параграфов.
Проверено: все 17 параграфов главы (p1-p16, final1) теперь строятся без ошибок.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Часть 1: 9 mini-cards с формулами всех 16 параграфов (KaTeX).
Часть 2: интерактивная SVG-карта иерархии четырёхугольников
(клик по узлу — подсветка свойств).
Часть 3: 7 интегрированных боссов (по 10 XP):
- Босс 1: многоугольник из суммы углов 1620°
- Босс 2: параллелограмм через треугольник ABD
- Босс 3: средние линии прямоугольника → ромб
- Босс 4: ромб 60° → диагонали (Пифагор)
- Босс 5: теорема Фалеса, 3 подзадачи
- Босс 6: треугольник 12-16-20 — средняя линия + медиана + центроид
- Босс 7: равнобедренная трапеция 20/8/10
Часть 4: при победе над всеми — achievement 'Мастер многоугольников Главы 1',
+50 XP бонус, confetti, кнопка перехода к Главе 2.
File: 5194 → 5558 LOC. Глава 1 полностью наполнена.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>