diff --git a/backend/tests/math6-page.test.js b/backend/tests/math6-page.test.js
index ada1534..8832898 100644
--- a/backend/tests/math6-page.test.js
+++ b/backend/tests/math6-page.test.js
@@ -195,6 +195,21 @@ test('анимации: canvas-демо монтируются (headless-safe)',
assert.deepEqual(r5.errors, [], 'ch5 без ошибок: ' + r5.errors.join(' | '));
});
+test('stepPlayer: «Разбор по шагам» становится интерактивным плеером', async () => {
+ // Глава 5 §1 — есть карточка «Разбор по шагам»
+ const r5 = await loadDom('math_6_ch5.html');
+ r5.doc.defaultView.goTo('p1'); await wait(120);
+ assert.ok(r5.doc.querySelector('#p1-body .m6-step-view'), 'плеер шагов §5.1');
+ assert.ok(r5.doc.querySelectorAll('#p1-body [data-act="next"]').length >= 1, 'кнопка «Дальше» §5.1');
+ assert.ok(r5.doc.querySelectorAll('#p1-body .m6-step-dots span').length >= 3, 'точки шагов §5.1');
+ assert.deepEqual(r5.errors, [], 'ch5 без ошибок: ' + r5.errors.join(' | '));
+ // Глава 2 §1
+ const r2 = await loadDom('math_6_ch2.html');
+ r2.doc.defaultView.goTo('p1'); await wait(120);
+ assert.ok(r2.doc.querySelector('#p1-body .m6-step-view'), 'плеер шагов §2.1');
+ assert.deepEqual(r2.errors, [], 'ch2 без ошибок: ' + r2.errors.join(' | '));
+});
+
test('hub: 6 карточек глав + курсовой финал', async () => {
const { doc, errors } = await loadDom('math_6_hub.html');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
diff --git a/frontend/js/math6_anim.js b/frontend/js/math6_anim.js
index 9daef37..2508745 100644
--- a/frontend/js/math6_anim.js
+++ b/frontend/js/math6_anim.js
@@ -278,4 +278,65 @@ M.plotLive = function (host, opts) {
};
};
+/* ============================ КОМПОНЕНТ: ПОШАГОВЫЙ ПЛЕЕР (DOM, не canvas) ============================ */
+M.stepPlayer = function (host, opts) {
+ opts = opts || {}; var steps = opts.steps || []; if (!steps.length) return { stop: function () {} };
+ var i = 0, auto = null;
+ host.innerHTML = '';
+ var wrap = D.createElement('div');
+ wrap.innerHTML =
+ '
'
+ + ''
+ + ''
+ + ''
+ + ''
+ + '
';
+ host.appendChild(wrap);
+ var view = wrap.querySelector('.m6-step-view'), dots = wrap.querySelector('.m6-step-dots');
+ var prevB = wrap.querySelector('[data-act="prev"]'), nextB = wrap.querySelector('[data-act="next"]'), autoB = wrap.querySelector('[data-act="auto"]');
+ for (var k = 0; k < steps.length; k++) { var dt = D.createElement('span'); dt.style.cssText = 'width:9px;height:9px;border-radius:50%;background:var(--border,#cbd5e1);transition:background .2s'; dots.appendChild(dt); }
+ function render() {
+ view.innerHTML = steps[i] || '';
+ if (W.renderMathInElement) try { W.renderMathInElement(view, { delimiters: [{ left: '$$', right: '$$', display: true }, { left: '$', right: '$', display: false }, { left: '\\[', right: '\\]', display: true }, { left: '\\(', right: '\\)', display: false }], throwOnError: false }); } catch (e) {}
+ var ds = dots.children; for (var k2 = 0; k2 < ds.length; k2++) ds[k2].style.background = (k2 === i ? 'var(--pri,#4f46e5)' : (k2 < i ? 'var(--ok,#10b981)' : 'var(--border,#cbd5e1)'));
+ prevB.disabled = i <= 0; nextB.disabled = i >= steps.length - 1;
+ prevB.style.opacity = prevB.disabled ? 0.5 : 1; nextB.style.opacity = nextB.disabled ? 0.5 : 1;
+ }
+ function go(d) { i = Math.max(0, Math.min(steps.length - 1, i + d)); render(); }
+ function stopAuto() { if (auto) { try { clearInterval(auto); } catch (e) {} auto = null; autoB.textContent = 'Авто'; } }
+ prevB.addEventListener('click', function () { stopAuto(); go(-1); });
+ nextB.addEventListener('click', function () { stopAuto(); go(1); });
+ autoB.addEventListener('click', function () {
+ if (auto) { stopAuto(); return; }
+ if (i >= steps.length - 1) { i = 0; render(); }
+ autoB.textContent = 'Пауза';
+ auto = setInterval(function () { if (i >= steps.length - 1) { stopAuto(); return; } go(1); }, 1500);
+ });
+ render();
+ return { stop: stopAuto };
+};
+
+/* Превратить карточки «Разбор по шагам» (.card с в теле) в интерактивный stepPlayer. */
+M.stepifyExamples = function (root) {
+ if (!root) return;
+ var cards = root.querySelectorAll('.card');
+ for (var ci = 0; ci < cards.length; ci++) {
+ var card = cards[ci];
+ var titleEl = card.querySelector('.card-title'); if (!titleEl) continue;
+ if (!/шаг/i.test(titleEl.textContent || '')) continue;
+ var body = card.querySelector('.card-body'); if (!body || body.__stepified) continue;
+ var ol = body.querySelector('ol'); if (!ol) continue;
+ var lis = ol.querySelectorAll('li'); if (lis.length < 2) continue;
+ var steps = [], intro = '', outro = '', node = body.firstChild;
+ while (node && node !== ol) { intro += (node.nodeType === 1 ? node.outerHTML : (node.nodeType === 3 ? node.textContent : '')); node = node.nextSibling; }
+ node = ol.nextSibling; while (node) { outro += (node.nodeType === 1 ? node.outerHTML : (node.nodeType === 3 ? node.textContent : '')); node = node.nextSibling; }
+ if (intro.replace(/<[^>]*>/g, '').trim()) steps.push('' + intro + '
');
+ for (var li = 0; li < lis.length; li++) steps.push('Шаг ' + (li + 1) + '. ' + lis[li].innerHTML + '
');
+ if (outro.replace(/<[^>]*>/g, '').trim()) steps.push(outro);
+ body.__stepified = true; body.innerHTML = '';
+ var hostEl = D.createElement('div'); body.appendChild(hostEl);
+ M.stepPlayer(hostEl, { steps: steps });
+ }
+};
+
})(window);
diff --git a/frontend/js/math6_engine.js b/frontend/js/math6_engine.js
index 2982aa9..9b83535 100644
--- a/frontend/js/math6_engine.js
+++ b/frontend/js/math6_engine.js
@@ -184,6 +184,7 @@ function goTo(id) {
buildSidebar(id);
window.scrollTo({ top: 0, behavior: 'smooth' });
if ((STATE.progress[id] || 0) < 10) bumpProgress(id, 10);
+ if (el && window.Math6Anim && window.Math6Anim.stepifyExamples) { try { window.Math6Anim.stepifyExamples(el); } catch (e) {} }
if (el && window.renderMathInElement) setTimeout(function () { renderMath(el); }, 0);
setTimeout(function () { try { wrapGlossary(el); } catch (e) {} }, 60);
markLastPara(id);
diff --git a/frontend/textbooks/math_6_ch2.html b/frontend/textbooks/math_6_ch2.html
index 607a834..0644b34 100644
--- a/frontend/textbooks/math_6_ch2.html
+++ b/frontend/textbooks/math_6_ch2.html
@@ -17,6 +17,7 @@
+
diff --git a/frontend/textbooks/math_6_ch3.html b/frontend/textbooks/math_6_ch3.html
index 60d5715..003ebaf 100644
--- a/frontend/textbooks/math_6_ch3.html
+++ b/frontend/textbooks/math_6_ch3.html
@@ -17,6 +17,7 @@
+