diff --git a/backend/tests/math6-page.test.js b/backend/tests/math6-page.test.js index 34d200e..1084c5d 100644 --- a/backend/tests/math6-page.test.js +++ b/backend/tests/math6-page.test.js @@ -74,6 +74,21 @@ test('hub: 6 карточек глав', async () => { assert.equal(doc.querySelectorAll('.ch-grid .ch-card').length, 6, '6 глав'); }); +test('ch1 Волна 1: интерактивы §1–§3 монтируются без ошибок', async () => { + const { doc, errors } = await loadDom('math_6_ch1.html'); + const win = doc.defaultView; + // §1 строится при загрузке + assert.ok(doc.querySelector('#p1-iv1 #p1-c'), 'разрядный конструктор §1'); + assert.ok(doc.querySelector('#p1-iv2 #p1-qq'), 'разряд-квиз §1'); + win.goTo('p2'); await wait(80); + assert.ok(doc.querySelector('#p2-cfig svg'), 'числовая прямая сравнения §2'); + assert.ok(doc.querySelector('#p2-iv2 #p2-rq'), 'тренажёр округления §2'); + win.goTo('p3'); await wait(80); + assert.ok(doc.querySelector('#p3-afig svg'), 'координатный луч §3'); + assert.ok(doc.querySelectorAll('#p3-iv2 [data-pt]').length === 4, 'выбор точки A–D §3'); + assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); +}); + test('навигация и прогресс: переход на § и отметка прочтения', async () => { const { doc, errors } = await loadDom('math_6_ch1.html'); const win = doc.defaultView; diff --git a/frontend/textbooks/math_6_ch1.html b/frontend/textbooks/math_6_ch1.html index 12401b1..03f9e11 100644 --- a/frontend/textbooks/math_6_ch1.html +++ b/frontend/textbooks/math_6_ch1.html @@ -100,6 +100,243 @@ window.M6 = { sidebars: {}, tips: [], glossary: [], builders: {}, footer: 'Интерактивный учебник «Математика 6» · Глава 1 · Десятичные дроби · LearnSpace' }; + +/* ===================== ВСПОМОГАТЕЛЬНОЕ ===================== */ +function _ri(a,b){ return a + Math.floor(Math.random()*(b-a+1)); } +function _pick(a){ return a[_ri(0,a.length-1)]; } +function _kf(x){ return String(x).replace('.','{,}'); } /* число → KaTeX с запятой */ +function _round(n,d){ var p=Math.pow(10,d); return Math.round(n*p)/p; } + +/* ===================== § 1. ЗАПИСЬ И РАЗРЯДЫ ===================== */ +function buildP1(){ + var box=document.getElementById('p1-body'); var h=''; + h+=makeCard('theory','Что такое десятичная дробь','1.1', + '

Десятичная дробь — это дробь со знаменателем $10$, $100$, $1000$ и т. д. Такие дроби записывают без знаменателя, отделяя целую часть от дробной запятой.

' + +'

$\\dfrac{7}{10}=0{,}7$  ·  $\\dfrac{25}{100}=0{,}25$  ·  $3\\dfrac{4}{100}=3{,}04$

' + +'

Слева от запятой — целая часть, справа — дробная. Сколько нулей в знаменателе, столько цифр после запятой.

'); + h+=makeCard('rule','Разряды десятичной дроби','1.2', + '

После запятой идут разряды: десятые $\\bigl(\\tfrac{1}{10}\\bigr)$, сотые $\\bigl(\\tfrac{1}{100}\\bigr)$, тысячные $\\bigl(\\tfrac{1}{1000}\\bigr)$ и далее.

' + +'' + +'
дес.ед.,десятыесотыетысячные
12,305
' + +'

Это число $12{,}305$ — «двенадцать целых триста пять тысячных».

'); + h+=makeCard('example','Читаем по разрядам','1.3', + '

$0{,}7$ — семь десятых;   $0{,}09$ — девять сотых;   $5{,}28$ — пять целых двадцать восемь сотых.

' + +'

Приписывание нулей справа не меняет дробь: $0{,}5=0{,}50=0{,}500$.

'); + h+='
Интерактив 1
Разрядный конструктор
' + +'
Двигай ползунки разрядов — число собирается из целой части и десятых, сотых, тысячных.
' + +'
' + +'' + +'' + +'' + +'' + +'
'; + h+='
Интерактив 2
Какая цифра в разряде?
' + +'
Назови цифру в указанном разряде данного числа. Введи одну цифру (0–9).
' + +'
Вопрос 1 / 6Очки: 0 / 6
' + +'
' + +'
' + +'
'; + h+=secNav(null,'p2')+readBtn('p1'); + box.innerHTML=h; renderMath(box); + + (function(){ + var c=document.getElementById('p1-c'),t=document.getElementById('p1-t'),hh=document.getElementById('p1-h'),th=document.getElementById('p1-th'),out=document.getElementById('p1-iv1-out'); + function upd(){ + document.getElementById('p1-cv').textContent=c.value; document.getElementById('p1-tv').textContent=t.value; + document.getElementById('p1-hv').textContent=hh.value; document.getElementById('p1-thv').textContent=th.value; + var ci=+c.value,ti=+t.value,hi=+hh.value,thi=+th.value, total=ci*1000+ti*100+hi*10+thi, val=total/1000; + out.innerHTML='
$'+_kf(val)+'$
' + +'
$'+ci+' + '+ti+'\\cdot 0{,}1 + '+hi+'\\cdot 0{,}01 + '+thi+'\\cdot 0{,}001 = \\dfrac{'+total+'}{1000}$
'; + renderMath(out); + } + c.oninput=t.oninput=hh.oninput=th.oninput=upd; upd(); + })(); + + (function(){ + var places=[['десятков',2],['единиц',1],['десятых',-1],['сотых',-2],['тысячных',-3]]; + var i=0,score=0,cur=null; + function gen(){ var ws=String(_ri(10,99)), ds=String(_ri(0,999)).padStart(3,'0'), pl=_pick(places), dg; + if(pl[1]===2)dg=+ws[0]; else if(pl[1]===1)dg=+ws[1]; else if(pl[1]===-1)dg=+ds[0]; else if(pl[1]===-2)dg=+ds[1]; else dg=+ds[2]; + cur={num:ws+','+ds, place:pl[0], digit:dg}; } + function show(){ + if(i>=6){ document.getElementById('p1-qq').innerHTML='Готово! Результат: '+score+' / 6'; if(score>=5){addXp(15,'p1-iv2');bumpProgress('p1',30);}else if(score>=3){addXp(8,'p1-iv2');bumpProgress('p1',18);} return; } + gen(); document.getElementById('p1-qi').textContent=i+1; + document.getElementById('p1-qq').innerHTML='Число '+cur.num+'. Какая цифра в разряде '+cur.place+'?'; + document.getElementById('p1-qa').value=''; document.getElementById('p1-qfb').style.display='none'; + } + function go(){ if(i>=6)return; var fb=document.getElementById('p1-qfb'), a=parseInt(document.getElementById('p1-qa').value,10); + if(isNaN(a)){ feedback(fb,false,'Введи одну цифру 0–9.'); return; } + if(a===cur.digit){ score++; feedback(fb,true,'✓ Верно! В разряде '+cur.place+' стоит '+cur.digit+'.'); } else feedback(fb,false,'✗ Нет. Правильный ответ: '+cur.digit+'.'); + document.getElementById('p1-qs').textContent=score; i++; setTimeout(show,1100); } + document.getElementById('p1-qgo').addEventListener('click',go); + document.getElementById('p1-qa').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); + show(); + })(); +} + +/* ===================== § 2. СРАВНЕНИЕ И ОКРУГЛЕНИЕ ===================== */ +function buildP2(){ + var box=document.getElementById('p2-body'); var h=''; + h+=makeCard('rule','Как сравнивать десятичные дроби','2.1', + '

1) Сначала сравнивают целые части: больше та дробь, у которой целая часть больше.

' + +'

2) Если целые части равны — сравнивают поразрядно слева направо: десятые, потом сотые и т. д.

' + +'

Чтобы было удобно, число знаков после запятой уравнивают нулями: $0{,}5$ и $0{,}48 \\Rightarrow 0{,}50$ и $0{,}48$, значит $0{,}5>0{,}48$.

'); + h+=makeCard('rule','Округление до разряда','2.2', + '

Чтобы округлить дробь до некоторого разряда, смотрят на следующую цифру:

' + +'' + +'

$3{,}472 \\approx 3{,}5$ (до десятых), $\\;3{,}472 \\approx 3$ (до целых).

'); + h+=makeCard('example','Примеры','2.3', + '

$0{,}9>0{,}89$, ведь $0{,}90>0{,}89$.   $2{,}1<2{,}15$.   $7{,}0=7$.

' + +'

$12{,}96 \\approx 13{,}0$ (до десятых) — при округлении $9$ превратилось в $10$, перенос в целые.

'); + h+='
Интерактив 1
Что больше?
' + +'
Сравни два числа на координатной прямой и выбери верный знак.
' + +'
Вопрос 1 / 6Очки: 0 / 6
' + +'
' + +'
' + +'
'; + h+='
Интерактив 2
Округли число
' + +'
Округли данное число до указанного разряда. Десятичную запятую вводи как точку или запятую.
' + +'
Вопрос 1 / 5Очки: 0 / 5
' + +'
' + +'
' + +'
'; + h+=secNav('p1','p3')+readBtn('p2'); + box.innerHTML=h; renderMath(box); + + (function(){ + var i=0,score=0,cur=null; + function gen(){ var a=_ri(0,49)/10, b=_ri(0,49)/10; cur={a:a,b:b}; } + function show(){ + if(i>=6){ document.getElementById('p2-cq').innerHTML='Готово! Результат: '+score+' / 6'; document.getElementById('p2-cfig').innerHTML=''; if(score>=5){addXp(15,'p2-iv1');bumpProgress('p2',30);}else if(score>=3){addXp(8,'p2-iv1');bumpProgress('p2',16);} return; } + gen(); document.getElementById('p2-ci').textContent=i+1; + document.getElementById('p2-cfig').innerHTML=Math6.numberLine({min:0,max:5,minor:0.5,major:1,width:560,marks:[{v:cur.a,label:'a',color:'#4f46e5'},{v:cur.b,label:'b',color:'#e11d48',above:false}]}); + document.getElementById('p2-cq').innerHTML='$a='+_kf(cur.a)+'$,   $b='+_kf(cur.b)+'$. Что верно?'; renderMath(document.getElementById('p2-cq')); + document.getElementById('p2-cfb').style.display='none'; + } + function ans(sym){ if(i>=6)return; var fb=document.getElementById('p2-cfb'), correct=cur.a>cur.b?'>':(cur.a=5){ document.getElementById('p2-rq').innerHTML='Готово! Результат: '+score+' / 5'; if(score>=4){addXp(15,'p2-iv2');bumpProgress('p2',30);}else if(score>=2){addXp(8,'p2-iv2');bumpProgress('p2',16);} return; } + gen(); document.getElementById('p2-ri').textContent=i+1; + document.getElementById('p2-rq').innerHTML='Округли $'+_kf(n_str(cur.n))+'$ до '+cur.place+'.'; renderMath(document.getElementById('p2-rq')); + document.getElementById('p2-ra').value=''; document.getElementById('p2-rfb').style.display='none'; + } + function n_str(x){ return x; } + function go(){ if(i>=5)return; var fb=document.getElementById('p2-rfb'), raw=document.getElementById('p2-ra').value.replace(',','.').trim(); var v=parseFloat(raw); + if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; } + if(Math.abs(v-cur.ans)<1e-9){ score++; feedback(fb,true,'✓ Верно: $'+_kf(cur.ans)+'$.'); } else feedback(fb,false,'✗ Нет. Правильно: $'+_kf(cur.ans)+'$.'); + document.getElementById('p2-rs').textContent=score; i++; setTimeout(show,1200); } + document.getElementById('p2-rgo').addEventListener('click',go); + document.getElementById('p2-ra').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); + show(); + })(); +} + +/* ===================== § 3. КООРДИНАТНЫЙ ЛУЧ ===================== */ +function buildP3(){ + var box=document.getElementById('p3-body'); var h=''; + h+=makeCard('theory','Координатный луч','3.1', + '

Координатный луч — это луч с началом в точке $O$, которой соответствует число $0$, выбранным единичным отрезком и направлением.

' + +'

Каждой точке луча соответствует координата — число, показывающее, на каком расстоянии (в единичных отрезках) от начала она находится.

'); + h+=makeCard('rule','Как отметить десятичную дробь','3.2', + '

Чтобы отметить десятые, единичный отрезок делят на 10 равных частей. Тогда $0{,}7$ — это $7$ маленьких делений от нуля.

' + +'

$1{,}3$ лежит между $1$ и $2$, ближе к $1$; $2{,}5$ — ровно посередине между $2$ и $3$.

'); + h+=makeCard('example','Пример','3.3', + '

Точка с координатой $0{,}4$ — четвёртое деление после нуля. Точка с координатой $1{,}8$ — восьмое деление после единицы.

'); + h+='
Интерактив 1
Прочитай координату
' + +'
Определи координату отмеченной точки. Запятую вводи как точку или запятую.
' + +'
Вопрос 1 / 5Очки: 0 / 5
' + +'
' + +'
' + +'
'; + h+='
Интерактив 2
Найди точку
' + +'
Какая из точек $A, B, C, D$ имеет указанную координату?
' + +'
Вопрос 1 / 5Очки: 0 / 5
' + +'
' + +'
' + +'
'; + h+=secNav('p2','p4')+readBtn('p3'); + box.innerHTML=h; renderMath(box); + + (function(){ + var i=0,score=0,cur=0; + function show(){ + if(i>=5){ document.getElementById('p3-afig').innerHTML='Готово! Результат: '+score+' / 5'; if(score>=4){addXp(15,'p3-iv1');bumpProgress('p3',30);}else if(score>=2){addXp(8,'p3-iv1');bumpProgress('p3',16);} return; } + cur=_ri(1,29)/10; document.getElementById('p3-ai').textContent=i+1; + document.getElementById('p3-afig').innerHTML=Math6.numberLine({min:0,max:3,minor:0.1,major:1,ray:true,width:560,marks:[{v:cur,label:'?',color:'#e11d48'}]}); + document.getElementById('p3-aa').value=''; document.getElementById('p3-afb').style.display='none'; + } + function go(){ if(i>=5)return; var fb=document.getElementById('p3-afb'), v=parseFloat(document.getElementById('p3-aa').value.replace(',','.').trim()); + if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; } + if(Math.abs(v-cur)<0.001){ score++; feedback(fb,true,'✓ Верно: $'+_kf(cur)+'$.'); } else feedback(fb,false,'✗ Нет. Координата: $'+_kf(cur)+'$.'); + document.getElementById('p3-as').textContent=score; i++; setTimeout(show,1200); } + document.getElementById('p3-ago').addEventListener('click',go); + document.getElementById('p3-aa').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); + show(); + })(); + + (function(){ + var i=0,score=0,cur=null; + function gen(){ var set={}; var vals=[]; while(vals.length<4){ var v=_ri(2,48)/10; if(!set[v]){ set[v]=1; vals.push(v); } } vals.sort(function(a,b){return a-b;}); + var labels=['A','B','C','D']; var marks=vals.map(function(v,k){ return {v:v,label:labels[k],color:'#4f46e5'}; }); + var ti=_ri(0,3); cur={marks:marks, target:vals[ti], label:labels[ti]}; } + function show(){ + if(i>=5){ document.getElementById('p3-bfig').innerHTML=''; document.getElementById('p3-bq').innerHTML='Готово! Результат: '+score+' / 5'; if(score>=4){addXp(15,'p3-iv2');bumpProgress('p3',30);}else if(score>=2){addXp(8,'p3-iv2');bumpProgress('p3',16);} return; } + gen(); document.getElementById('p3-bi').textContent=i+1; + document.getElementById('p3-bfig').innerHTML=Math6.numberLine({min:0,max:5,minor:0.5,major:1,width:560,marks:cur.marks}); + document.getElementById('p3-bq').innerHTML='Точка с координатой $'+_kf(cur.target)+'$ — это ...?'; renderMath(document.getElementById('p3-bq')); + document.getElementById('p3-bfb').style.display='none'; + } + function ans(lab){ if(i>=5)return; var fb=document.getElementById('p3-bfb'); + if(lab===cur.label){ score++; feedback(fb,true,'✓ Верно — это точка '+cur.label+'.'); } else feedback(fb,false,'✗ Нет. Это точка '+cur.label+'.'); + document.getElementById('p3-bs').textContent=score; i++; setTimeout(show,1100); } + document.querySelectorAll('#p3-iv2 [data-pt]').forEach(function(b){ b.addEventListener('click',function(){ ans(b.getAttribute('data-pt')); }); }); + show(); + })(); +} + +/* ===================== ДАННЫЕ САЙДБАРА / ГЛОССАРИЯ ===================== */ +var SIDEBARS = { + p1:{ title:'Шпаргалка § 1', rows:[ + ['Десятичная дробь','знаменатель $10,100,1000\\ldots$, пишут через запятую'], + ['Целая часть','слева от запятой'], + ['Дробная часть','справа от запятой'], + ['Разряды','десятые, сотые, тысячные …'], + ['Нули справа','$0{,}5=0{,}50=0{,}500$'] ]}, + p2:{ title:'Шпаргалка § 2', rows:[ + ['Сравнение','сначала целые части, потом поразрядно'], + ['Уравнять знаки','дописать нули: $0{,}5=0{,}50$'], + ['Округление','следующая цифра $<5$ — отбросить'], + ['','следующая цифра $\\ge 5$ — прибавить 1'] ]}, + p3:{ title:'Шпаргалка § 3', rows:[ + ['Координатный луч','начало $O$, $0$, единичный отрезок'], + ['Координата','расстояние от $0$ в единичных отрезках'], + ['Десятые','единичный отрезок делим на 10'], + ['$2{,}5$','посередине между 2 и 3'] ]} +}; +var TIPS = [ + { sec:'p1', html:'Число цифр после запятой = числу нулей в знаменателе. У $0{,}305$ три цифры → знаменатель $1000$.' }, + { sec:'p2', html:'Перед сравнением мысленно уравняй число знаков нулями: $0{,}5$ это $0{,}50$, и сразу видно, что $0{,}50>0{,}48$.' }, + { sec:'p3', html:'Чтобы отметить десятые, дели единичный отрезок на 10. $1{,}7$ — это $1$ и ещё $7$ маленьких делений.' } +]; +var GLOSSARY = [ + { term:'десятичная дробь', def:'Дробь со знаменателем $10,100,1000,\\ldots$, записанная через запятую.', sec:'p1', aliases:['десятичная дробь','десятичной дроби','десятичные дроби','десятичных дробей','десятичную дробь'] }, + { term:'разряд', def:'Место цифры в записи числа: десятые, сотые, тысячные и т. д.', sec:'p1', aliases:['разряд','разряда','разряде','разряды','разрядов'] }, + { term:'координатный луч', def:'Луч с началом $O$ (число $0$), единичным отрезком и направлением.', sec:'p3', aliases:['координатный луч','координатном луче','координатного луча'] }, + { term:'координата', def:'Число, показывающее расстояние точки от начала в единичных отрезках.', sec:'p3', aliases:['координата','координату','координаты','координатой'] }, + { term:'округление', def:'Замена числа близким с меньшим числом разрядов по правилу: следующая цифра $\\ge5$ — разряд увеличивают.', sec:'p2', aliases:['округление','округления','округлить','округлении'] } +]; +var BUILDERS = { p1:buildP1, p2:buildP2, p3:buildP3 }; +Object.assign(window.M6, { sidebars:SIDEBARS, tips:TIPS, glossary:GLOSSARY, builders:BUILDERS });