diff --git a/backend/tests/math6-page.test.js b/backend/tests/math6-page.test.js index 8832898..cbfcc9d 100644 --- a/backend/tests/math6-page.test.js +++ b/backend/tests/math6-page.test.js @@ -184,6 +184,8 @@ test('анимации: canvas-демо монтируются (headless-safe)', const r4 = await loadDom('math_6_ch4.html'); r4.doc.defaultView.goTo('p4'); await wait(100); assert.ok(r4.doc.querySelector('#p4-walk canvas'), 'canvas «прыжки по прямой» §4.4'); + r4.doc.defaultView.goTo('p1'); await wait(100); + assert.ok(r4.doc.querySelector('#p1-therm-fig canvas'), 'canvas «термометр» §4.1'); assert.deepEqual(r4.errors, [], 'ch4 без ошибок: ' + r4.errors.join(' | ')); // Глава 5 §2: машинка + график const r5 = await loadDom('math_6_ch5.html'); diff --git a/frontend/js/math6_anim.js b/frontend/js/math6_anim.js index 2508745..7e44f60 100644 --- a/frontend/js/math6_anim.js +++ b/frontend/js/math6_anim.js @@ -278,6 +278,33 @@ M.plotLive = function (host, opts) { }; }; +/* ============================ ДЕМО 7: ТЕРМОМЕТР (±числа, модуль) ============================ */ +M.thermometer = function (host, opts) { + opts = opts || {}; var W0 = 220, H0 = 320; var sc = sceneCanvas(host, W0, H0); var cap = caption(host, ''); + var st = { v: opts.value != null ? opts.value : 5, target: opts.value != null ? opts.value : 5 }; + var MIN = -10, MAX = 10, cx = 64, top = 26, bot = H0 - 56, bulbR = 18; + function Y(v) { return bot - (v - MIN) / (MAX - MIN) * (bot - top); } + function draw() { + var ctx = sc.ctx; if (!ctx) return; st.v += (st.target - st.v) * 0.15; + var mut = cssVar('--muted', '#64748b'), txt = cssVar('--text', '#0f172a'); + ctx.clearRect(0, 0, W0, H0); + ctx.strokeStyle = mut; ctx.lineWidth = 2; + ctx.beginPath(); ctx.moveTo(cx - 9, top); ctx.arc(cx, top, 9, Math.PI, 0); ctx.lineTo(cx + 9, bot); ctx.moveTo(cx - 9, top); ctx.lineTo(cx - 9, bot); ctx.stroke(); + ctx.beginPath(); ctx.arc(cx, bot + bulbR - 4, bulbR, 0, 2 * Math.PI); ctx.fillStyle = '#fee2e2'; ctx.fill(); ctx.strokeStyle = mut; ctx.stroke(); + ctx.font = '11px JetBrains Mono, monospace'; ctx.textAlign = 'left'; + for (var v = MIN; v <= MAX; v += 2) { var y = Y(v); ctx.strokeStyle = mut; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx + 12, y); ctx.lineTo(cx + 18, y); ctx.stroke(); ctx.fillStyle = (v === 0 ? txt : mut); ctx.fillText(v + '°', cx + 22, y + 4); } + var col = st.v >= 0 ? '#dc2626' : '#2563eb', y0 = Y(0), yv = Y(st.v); + ctx.fillStyle = col; ctx.fillRect(cx - 5, Math.min(y0, yv), 10, Math.abs(yv - y0)); + ctx.beginPath(); ctx.arc(cx, bot + bulbR - 4, bulbR - 3, 0, 2 * Math.PI); ctx.fill(); + ctx.beginPath(); ctx.arc(cx, yv, 4, 0, 2 * Math.PI); ctx.fill(); + ctx.fillStyle = txt; ctx.font = 'bold 17px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.fillText(Math.round(st.v) + '°', cx, H0 - 14); + } + var L = loop(host, draw); + function setCap(v) { if (!cap) return; cap.innerHTML = '$' + v + '°$ — это ' + (v > 0 ? 'тепло, выше нуля' : (v < 0 ? 'мороз, ниже нуля' : 'ровно ноль')) + '. Модуль $|' + v + '| = ' + Math.abs(v) + '$ — это расстояние до нуля.'; if (W.renderMathInElement) try { W.renderMathInElement(cap, { delimiters: [{ left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {} } + setCap(st.target); + return { stop: L.stop, set: function (v) { st.target = v; setCap(v); } }; +}; + /* ============================ КОМПОНЕНТ: ПОШАГОВЫЙ ПЛЕЕР (DOM, не canvas) ============================ */ M.stepPlayer = function (host, opts) { opts = opts || {}; var steps = opts.steps || []; if (!steps.length) return { stop: function () {} }; diff --git a/frontend/textbooks/math_6_ch4.html b/frontend/textbooks/math_6_ch4.html index 337275e..b794a6d 100644 --- a/frontend/textbooks/math_6_ch4.html +++ b/frontend/textbooks/math_6_ch4.html @@ -125,6 +125,10 @@ function buildP1(){ +'
Отрицательные числа официально признали математики лишь в XVII веке — до этого их называли «абсурдными» или «мнимыми долгами». Индийский математик Брахмагупта ещё в VII веке описал правила работы с ними, но в Европе их долго отвергали!
'); + h+='