feat(trainer): НОД/НОК с нормальными числами; универсальная шапка; НОК теперь появляется

НОД/НОК (числа больше + без степеней):
- движок: фича factorize ({name, of}) кладёт в шаги решения СТРОКУ разложения на простые множители без степеней (36 -> «2*2*3*3»); helper primeFactorString
- генераторы: a=g·m, b=g·n (g,m,n из 2..9) -> нормальные числа (14, 35, 16, 112…), общий множитель гарантирован; решение показывает разложение обоих + НОД/НОК = произведение множителей
- пример: 16 = 2·2·2·2, НОК = 2·2·2·2·7 = 112

НОК теперь появляется (раньше показывался только НОД):
- причина: smart-подбор брал первый неосвоенный навык ГЛОБАЛЬНО -> из НОД прыгал на lin-basic, НОК не доходил
- фикс: умная тренировка теперь адаптируется В ПРЕДЕЛАХ выбранной темы (pickNext scope = skillsOf(curTopic)) -> в теме «НОД и НОК» ведёт по обоим навыкам; тему выбирает ученик в рейле

Шапка: пилюля стала универсальной и динамической (updateSubjectPill: «Алгебра · 5–9 класс» / «Геометрия · 7–8 класс» по текущему предмету), вместо статичной «Алгебра · 7–8 класс».

Смоук движка 1154/1154, страница 42/42; эмодзи 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-25 17:57:30 +03:00
parent 2a50ff740a
commit fb16821b0a
3 changed files with 53 additions and 26 deletions
+18 -4
View File
@@ -361,7 +361,7 @@
<span class="tr-brand-mark"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.6L18.5 9l-4.7 1.4L12 15l-1.8-4.6L5.5 9l4.7-1.4z"/><path d="M19 14l.7 1.8L21.5 16.5l-1.8.7L19 19l-.7-1.8L16.5 16.5l1.8-.7z"/></svg></span>
<div>
<h1 class="tr-h1">Тренажёр</h1>
<div class="tr-brand-sub"><span class="tr-pill" id="tr-subject">Алгебра · 78 класс</span></div>
<div class="tr-brand-sub"><span class="tr-pill" id="tr-subject">Математика · 59 класс</span></div>
</div>
</div>
<div class="tr-modes">
@@ -685,13 +685,24 @@
topics.forEach(function (t) { if (t.subject && !seen[t.subject]) { seen[t.subject] = 1; out.push(t.subject); } });
return out;
}
var SUBJ_LBL = { algebra: 'Алгебра', geometry: 'Геометрия' };
// подпись в шапке: предмет + диапазон классов текущего предмета (универсально 5–9)
function updateSubjectPill() {
var pill = $('tr-subject'); if (!pill) return;
var gr = topics.filter(function (t) { return (t.subject || 'algebra') === curSubject && t.grade; })
.map(function (t) { return t.grade; });
var name = SUBJ_LBL[curSubject] || 'Математика';
if (!gr.length) { pill.textContent = name; return; }
var lo = Math.min.apply(null, gr), hi = Math.max.apply(null, gr);
pill.textContent = name + ' · ' + (lo === hi ? (lo + ' класс') : (lo + '' + hi + ' класс'));
}
function renderSubjects() {
updateSubjectPill();
var el = $('tr-subjects'); if (!el) return;
var subs = presentSubjects();
if (subs.length <= 1) { el.innerHTML = ''; return; }
var LBL = { algebra: 'Алгебра', geometry: 'Геометрия' };
el.innerHTML = subs.map(function (s) {
return '<button class="tr-subbtn' + (s === curSubject ? ' on' : '') + '" type="button" data-sub="' + s + '">' + esc(LBL[s] || s) + '</button>';
return '<button class="tr-subbtn' + (s === curSubject ? ' on' : '') + '" type="button" data-sub="' + s + '">' + esc(SUBJ_LBL[s] || s) + '</button>';
}).join('');
}
function skillPanelHeader() {
@@ -943,7 +954,10 @@
function pickNext(lastSkill) {
if (!TA) return;
var last = (lastSkill !== undefined) ? lastSkill : (curGen ? skillKey(curGen) : null);
var id = TA.nextSkill({ ordered: ordered, progress: prog, queue: reviewQ, answered: sessAnswered, last: last });
// адаптив в пределах ВЫБРАННОЙ темы: ведёт по её навыкам (простое→сложное) и
// возвращает ошибки, не перепрыгивая в другие темы (тему выбирает ученик в рейле).
var scope = skillsOf(curTopic); if (!scope || !scope.length) scope = ordered;
var id = TA.nextSkill({ ordered: scope, progress: prog, queue: reviewQ, answered: sessAnswered, last: last });
var g = id ? gens.filter(function (x) { return skillKey(x) === id; })[0] : null;
if (g) { curGen = g; curTopic = g.topic; if (g.subject) curSubject = g.subject; renderSubjects(); renderTopics(); renderSkills(); }
}