feat(trainer): тема «Окружность» + режим «читать условие с чертежа»

Окружность (новая геом-тема g-circle, 9 кл, π ≈ 3,14 → ответ — конечная
десятичная дробь, ученик вводит её): 4 генератора — длина окружности по радиусу
(2πr), по диаметру (πd), площадь круга (πr²), длина дуги ((n/360)·2πr, n=45·k,
require r·k чётно → дробь конечная). Новые типы фигур: circle (радиус/диаметр/
заливка) и circle-arc (два радиуса под центральным углом + выделенная дуга).

Режим «читать значения с чертежа»: у всех 19 геом-генераторов добавлено
figurePrompt (краткое условие); переключатель «Текст / На чертеже» (#tr-figmode)
на странице, выбор сохраняется в localStorage. В режиме чертежа числа берутся с
фигуры, текст минимальный. Движок прокидывает figurePrompt; showStatement
выбирает полный текст или промпт; renderFigureToggle показан только для задач с
чертежом; для текстовых/алгебраических задач режим скрыт, проверка ответа от
режима не зависит. На чертеж n-угольника выведено число сторон (n = …).

Верификация: headless-смоук 6968 проверок / 1140 рендеров; ответы окружности
конечные и принимаются движком (1600 экземпляров, округление до 2 знаков ok);
inline-скрипт парсится; адверсариал-ревью — circle clean, toggle без high/medium
(2 low устранены: скрытие тумблера при неудаче генерации + подпись n сторон).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-26 12:49:15 +03:00
parent ff9900bdcc
commit 1dcde8790a
4 changed files with 175 additions and 5 deletions
+38 -4
View File
@@ -230,6 +230,13 @@
.tr-diff-btn:hover { border-color: var(--g1); color: var(--accent-ink); }
.tr-diff-btn.on { color: #fff; border-color: transparent; background: linear-gradient(135deg, var(--g1), var(--g2)); box-shadow: 0 8px 16px -6px rgba(99,102,241,.5); }
/* ── переключатель «условие текстом / на чертеже» (геометрия) ── */
.tr-figmode { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center; margin-bottom: 16px; }
.tr-fm-label { font-size: .68rem; font-weight: 800; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .07em; margin-right: 4px; }
.tr-fm-btn { font: inherit; font-size: .8rem; font-weight: 800; cursor: pointer; padding: 6px 14px; border-radius: 99px; border: 1px solid var(--line); background: #fff; color: var(--ink-soft); transition: .14s var(--ease); }
.tr-fm-btn:hover { border-color: var(--g1); color: var(--accent-ink); }
.tr-fm-btn.on { color: #fff; border-color: transparent; background: linear-gradient(135deg, var(--g1), var(--g2)); box-shadow: 0 8px 16px -6px rgba(99,102,241,.5); }
/* строка ответа */
.tr-inrow { display: flex; gap: 10px; align-items: stretch; max-width: 460px; margin: 0 auto; }
#tr-eqx { font-family: 'Cambria Math', serif; font-size: 1.55rem; font-weight: 600; color: var(--accent-ink); align-self: center; padding-left: 4px; }
@@ -448,6 +455,7 @@
<div class="tr-figure" id="tr-figure" style="display:none"></div>
</div>
<div class="tr-work">
<div class="tr-figmode" id="tr-figmode" style="display:none"></div>
<div class="tr-difficulty" id="tr-difficulty"></div>
<div id="tr-answerbox">
@@ -600,6 +608,22 @@
if (svg) { box.innerHTML = svg; box.style.display = ''; }
else { box.innerHTML = ''; box.style.display = 'none'; }
}
// Условие: полный текст ИЛИ краткий промпт (числа читаются с чертежа).
function showStatement(problem) {
var eq = $('tr-eq'); if (!eq || !problem) return;
var useFig = figureMode && problem.figure && problem.figurePrompt;
if (useFig) { eq.classList.add('tr-eq-text'); setMath(eq, null, problem.figurePrompt, true); }
else { eq.classList.toggle('tr-eq-text', !problem.latex); setMath(eq, problem.latex, problem.display, true); }
}
// Переключатель «Текст / На чертеже» — только для задач с чертежом и кратким условием.
function renderFigureToggle() {
var box = $('tr-figmode'); if (!box) return;
if (!(cur && cur.figure && cur.figurePrompt)) { box.style.display = 'none'; box.innerHTML = ''; return; }
box.style.display = '';
box.innerHTML = '<span class="tr-fm-label">Условие</span>' +
'<button type="button" class="tr-fm-btn' + (!figureMode ? ' on' : '') + '" data-fm="0">Текст</button>' +
'<button type="button" class="tr-fm-btn' + (figureMode ? ' on' : '') + '" data-fm="1">На чертеже</button>';
}
var topics = (TG.topics ? TG.topics() : [{ key: null, label: 'Задачи' }]).concat([{ key: 'word', label: 'Текстовые задачи', word: true }]);
var isTeacher = !!(ip && ip.isTeacher);
@@ -608,6 +632,8 @@
var diffMode = 'auto'; // уровень сложности: 'auto' | 1 | 2 | 3 (= структурный вариант)
var pinned = null; // закреплённый навык (id) при явном клике по чипу
var customGens = []; // пользовательские генераторы (P13), тема «Авторские»
var figureMode = false; // «читать условие с чертежа» (числа на фигуре, текст краткий)
try { figureMode = localStorage.getItem('tr-figure-mode') === '1'; } catch (e) {}
function skillKey(g) { return g.skill || g.id; }
function skillsOf(topicKey) {
if (topicKey === 'custom') return customGens;
@@ -641,6 +667,7 @@
function serveWordProblem() {
var eq = $('tr-eq'); eq.classList.add('tr-eq-text');
renderFigure(null); // текстовые задачи — без чертежа
var fmb = $('tr-figmode'); if (fmb) { fmb.style.display = 'none'; fmb.innerHTML = ''; }
$('tr-solution').style.display = 'none'; $('tr-solution').innerHTML = '';
var fb = $('tr-feedback'); fb.className = 'tr-feedback'; fb.textContent = '';
if (!wordPool.length) {
@@ -937,14 +964,13 @@
// strict:false + несколько попыток на случай редкой неудачи с ограничениями
cur = null;
for (var i = 0; i < 6 && !cur; i++) cur = TE.instantiate(curGen, { seed: randSeed(), strict: false });
if (!cur) { $('tr-eq').textContent = 'Не удалось сгенерировать задачу'; return; }
if (!cur) { $('tr-eq').textContent = 'Не удалось сгенерировать задачу'; renderFigure(null); renderFigureToggle(); return; }
renderSkills(); // подсветить активный навык (мог смениться вместе с уровнем)
$('tr-skill').textContent = curGen.title;
var eq = $('tr-eq');
eq.classList.toggle('tr-eq-text', !cur.latex); // текстовый prompt (проценты/упрощение) — другим шрифтом
setMath(eq, cur.latex, cur.display, true);
showStatement(cur); // условие: полный текст ИЛИ краткий промпт (числа на чертеже)
renderFigure(cur); // чертёж для геометрии (иначе скрыт)
renderFigureToggle(); // переключатель «Текст / На чертеже» (если уместен)
applyInputMode();
var inp = $('tr-input');
inp.value = ''; inp.disabled = false;
@@ -1202,6 +1228,14 @@
$('tr-teacher').addEventListener('click', function (e) { if (e.target === $('tr-teacher')) $('tr-teacher').style.display = 'none'; });
$('tr-analytics-btn').addEventListener('click', openAnalytics);
$('tr-builder-btn').addEventListener('click', function () { location.href = '/trainer-builder'; });
$('tr-figmode').addEventListener('click', function (e) {
var b = e.target.closest('.tr-fm-btn'); if (!b) return;
var v = b.getAttribute('data-fm') === '1';
if (v === figureMode) return;
figureMode = v;
try { localStorage.setItem('tr-figure-mode', v ? '1' : '0'); } catch (_) {}
showStatement(cur); renderFigureToggle();
});
$('tr-difficulty').addEventListener('click', function (e) {
var b = e.target.closest('.tr-diff-btn'); if (!b) return;
var d = b.getAttribute('data-d');