feat(trainer): геометрические чертежи задач — движок фигур + иллюстрации во всех геом. темах

TrainerFigures (frontend/js/trainer/figures.js) — безопасный SVG-рендер
«фигуры как данные» (модель SimForge): 11 типов — прямоугольный треугольник,
углы треугольника/смежные/внешний, прямоугольник, квадрат, треугольник по
основанию и высоте, трапеция, параллелограмм, ромб, правильный n-угольник,
подобные треугольники. Чертёж строится из чисел (params),  без eval/Function,
подписи экранируются, искомая величина — «?». Белые штрихи под индиго-сцену.

- generators.js: figure-спека на всех 15 геом-генераторах (Углы, Пифагор,
  Площади, Многоугольники, Подобие) — привязка размеров к параметрам задачи.
- _trainer_engine.js: figure прокидывается в problem.
- trainer.html: контейнер #tr-figure в шапке-герое, renderFigure() в newProblem,
  скрыт для текстовых задач, скрипт-тег, CSS.

Верификация: headless-смоук 5489 проверок / 900 рендеров (нет NaN/<script>/
обработчиков, «?» на искомой); адверсариал-ревью 4/4 группы clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-26 12:23:03 +03:00
parent 393de56c42
commit ff9900bdcc
4 changed files with 482 additions and 0 deletions
+16
View File
@@ -217,6 +217,10 @@
/* текстовый prompt (проценты/упрощение) — компактнее уравнения, на сцене белым */
.tr-eq.tr-eq-text { font-family: 'Manrope', sans-serif; font-weight: 600; font-size: clamp(1.2rem, 3vw, 1.7rem); line-height: 1.45; color: #fff; }
/* чертёж геометрической задачи — белыми штрихами на сцене-герое */
.tr-figure { margin: 16px auto 0; max-width: 320px; }
.tr-figure .tr-fig-svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 4px 14px rgba(0,0,0,.18)); }
.tr-work { padding: 24px 28px 28px; }
/* ── уровни сложности ── */
@@ -441,6 +445,7 @@
<div class="tr-stage">
<div class="tr-skill" id="tr-skill"></div>
<div class="tr-eq" id="tr-eq"></div>
<div class="tr-figure" id="tr-figure" style="display:none"></div>
</div>
<div class="tr-work">
<div class="tr-difficulty" id="tr-difficulty"></div>
@@ -538,6 +543,7 @@
<script src="/js/labs/_sim_expr.js"></script>
<script src="/js/trainer/_trainer_engine.js"></script>
<script src="/js/trainer/generators.js"></script>
<script src="/js/trainer/figures.js"></script>
<script src="/js/trainer/adaptive.js"></script>
<!-- KaTeX для рендера уравнений и шагов решения -->
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
@@ -586,6 +592,14 @@
var h = kat(latex, display);
if (h) el.innerHTML = h; else el.textContent = fallbackText;
}
// Чертёж задачи (геометрия): TrainerFigures строит безопасный SVG по cur.figure + params.
function renderFigure(problem) {
var box = $('tr-figure'); if (!box) return;
var svg = (problem && problem.figure && window.TrainerFigures)
? window.TrainerFigures.render(problem.figure, problem.params) : null;
if (svg) { box.innerHTML = svg; box.style.display = ''; }
else { box.innerHTML = ''; box.style.display = 'none'; }
}
var topics = (TG.topics ? TG.topics() : [{ key: null, label: 'Задачи' }]).concat([{ key: 'word', label: 'Текстовые задачи', word: true }]);
var isTeacher = !!(ip && ip.isTeacher);
@@ -626,6 +640,7 @@
}
function serveWordProblem() {
var eq = $('tr-eq'); eq.classList.add('tr-eq-text');
renderFigure(null); // текстовые задачи — без чертежа
$('tr-solution').style.display = 'none'; $('tr-solution').innerHTML = '';
var fb = $('tr-feedback'); fb.className = 'tr-feedback'; fb.textContent = '';
if (!wordPool.length) {
@@ -929,6 +944,7 @@
var eq = $('tr-eq');
eq.classList.toggle('tr-eq-text', !cur.latex); // текстовый prompt (проценты/упрощение) — другим шрифтом
setMath(eq, cur.latex, cur.display, true);
renderFigure(cur); // чертёж для геометрии (иначе скрыт)
applyInputMode();
var inp = $('tr-input');
inp.value = ''; inp.disabled = false;