feat(math6): Глава 2 — Проценты и пропорции (§1–§9 + финал)

§1 процент наглядно (сетка 100) + конвертер %↔дробь↔десятичная;
§2 три типа задач (классификатор + тренажёр % от числа);
§3 пропорция (найди член крест-накрест + проверка свойства);
§4 прямая/обратная зависимость (классификатор + таблица);
§5 решение пропорцией (прямые и обратные задачи);
§6 масштаб (карта↔местность); §7 круговые диаграммы (Math6.pie +
%↔градусы); §9 прикладной; финал — 5 боссов. Тесты math6: 15/15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-02 19:58:48 +03:00
parent c5a7803e34
commit a7835659d5
3 changed files with 545 additions and 0 deletions
+17
View File
@@ -68,6 +68,23 @@ for (const ch of CHAPTERS) {
}); });
} }
test('ch2: проценты и пропорции — интерактивы + финал', async () => {
const { doc, errors } = await loadDom('math_6_ch2.html');
const win = doc.defaultView;
assert.ok(doc.querySelector('#p1-fig svg rect') && doc.querySelector('#p1-q'), 'сетка 100 и конвертер §1');
win.goTo('p2'); await wait(80);
assert.ok(doc.querySelectorAll('#p2-iv1 [data-t]').length === 3 && doc.querySelector('#p2-cq'), 'типы задач §2');
win.goTo('p3'); await wait(80);
assert.ok(doc.querySelector('#p3-q') && doc.querySelectorAll('#p3-iv2 [data-v]').length === 2, 'пропорция §3');
win.goTo('p7'); await wait(80);
assert.ok(doc.querySelector('#p7-fig svg') && doc.querySelector('#p7-pick [data-k]'), 'круговая диаграмма §7');
win.goTo('final'); await wait(80);
assert.ok(doc.querySelector('#fin-go'), 'арена боссов §2');
win.bumpProgress('final', 100); await wait(20);
assert.ok(win.M6STATE.achievements.has('ch2_done'), 'достижение «Глава 2 пройдена»');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch5: координатная плоскость — интерактивы §1–§3 + финал', async () => { test('ch5: координатная плоскость — интерактивы §1–§3 + финал', async () => {
const { doc, errors } = await loadDom('math_6_ch5.html'); const { doc, errors } = await loadDom('math_6_ch5.html');
const win = doc.defaultView; const win = doc.defaultView;
+24
View File
@@ -153,4 +153,28 @@ M.plane = function (opts) {
return M.box(S, S, s, { maxw: opts.maxw || S, bg: opts.bg }); return M.box(S, S, s, { maxw: opts.maxw || S, bg: opts.bg });
}; };
/* === КРУГОВАЯ ДИАГРАММА ===
* segs: [{value,label,color}]. opts: {size, r}. Возвращает boxed SVG (проценты в секторах).
*/
M.pie = function (segs, opts) {
opts = opts || {};
var S = opts.size || 220, cx = S / 2, cy = S / 2, r = opts.r || (S / 2 - 8);
var total = segs.reduce(function (a, s) { return a + s.value; }, 0) || 1;
var palette = ['#4f46e5', '#0891b2', '#e11d48', '#059669', '#d97706', '#7c3aed', '#db2777'];
var ang = -Math.PI / 2, s = '';
segs.forEach(function (seg, i) {
var frac = seg.value / total, a2 = ang + frac * 2 * Math.PI;
var col = seg.color || palette[i % palette.length];
if (frac >= 0.999) { s += '<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="' + col + '"/>'; }
else {
var x1 = cx + r * Math.cos(ang), y1 = cy + r * Math.sin(ang), x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2), large = frac > 0.5 ? 1 : 0;
s += '<path d="M ' + cx + ' ' + cy + ' L ' + x1 + ' ' + y1 + ' A ' + r + ' ' + r + ' 0 ' + large + ' 1 ' + x2 + ' ' + y2 + ' Z" fill="' + col + '" stroke="var(--card,#fff)" stroke-width="1.5"/>';
}
var mid = ang + frac * Math.PI, lx = cx + r * 0.62 * Math.cos(mid), ly = cy + r * 0.62 * Math.sin(mid);
if (frac > 0.05) s += '<text x="' + lx + '" y="' + (ly + 4) + '" text-anchor="middle" font-size="12" font-weight="700" fill="#fff">' + Math.round(frac * 100) + '%</text>';
ang = a2;
});
return M.box(S, S, s, { maxw: opts.maxw || S });
};
})(); })();
+504
View File
@@ -96,6 +96,510 @@ window.M6 = {
sidebars: {}, tips: [], glossary: [], builders: {}, sidebars: {}, tips: [], glossary: [], builders: {},
footer: 'Интерактивный учебник «Математика 6» · Глава 2 · Проценты и пропорции · LearnSpace' footer: 'Интерактивный учебник «Математика 6» · Глава 2 · Проценты и пропорции · 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('.','{,}'); }
function _round(n,d){ var p=Math.pow(10,d); return Math.round(n*p)/p; }
function svgWrap(w,h,inner){ return '<div class="m6-fig" style="max-width:'+w+'px;margin:10px auto"><svg viewBox="0 0 '+w+' '+h+'" style="width:100%;height:auto;display:block;background:var(--card,#fff);border-radius:10px;border:1px solid var(--border)">'+inner+'</svg></div>'; }
function grid100(p){ var s=''; for(var r=0;r<10;r++)for(var c=0;c<10;c++){ var idx=r*10+c, fill=idx<p?'#0891b2':'var(--card,#fff)'; s+='<rect x="'+(c*16+3)+'" y="'+(r*16+3)+'" width="14" height="14" rx="2" fill="'+fill+'" stroke="#cbd5e1" stroke-width="0.8"/>'; } return svgWrap(166,166,s); }
/* ===================== § 1. ПРОЦЕНТЫ ===================== */
function buildP1(){
var box=document.getElementById('p1-body'); var h='';
h+=makeCard('theory','Что такое процент','1.1',
'<p><b>Процент</b> — это сотая доля числа. $1\\% = \\dfrac{1}{100} = 0{,}01$. Знак процента — $\\%$.</p>'
+'<p>Всё число — это $100\\%$. Половина — $50\\%$, четверть — $25\\%$, пятая часть — $20\\%$.</p>');
h+=makeCard('rule','Перевод процентов','1.2',
'<p>Проценты $\\to$ десятичная дробь: делим на $100$ (запятая влево на 2 знака): $35\\% = 0{,}35$.</p>'
+'<p>Десятичная дробь $\\to$ проценты: умножаем на $100$: $0{,}7 = 70\\%$.</p>'
+'<p>Обыкновенная дробь $\\to$ проценты: $\\dfrac{1}{4} = 0{,}25 = 25\\%$.</p>');
h+='<div class="wg" id="p1-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Процент наглядно</div></div>'
+'<div class="wg-help">Двигай ползунок — закрашенные клетки из 100 показывают процент.</div>'
+'<div class="sliders"><label>Процент = <b id="p1-pv">35</b>%<input type="range" id="p1-p" min="0" max="100" value="35"></label></div>'
+'<div id="p1-fig"></div><div id="p1-out" class="qbox"></div></div>';
h+='<div class="wg" id="p1-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Переводи проценты</div></div>'
+'<div class="wg-help">Переведи между процентами и десятичной дробью. Ответ — число.</div>'
+'<div class="score-display"><span>Вопрос <b id="p1-i">1</b> / 6</span><span>Очки: <b id="p1-s">0</b> / 6</span></div>'
+'<div id="p1-q" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p1-a" class="tinp" style="width:110px;text-align:center"><button class="btn primary" id="p1-go">Проверить</button></div>'
+'<div class="feedback" id="p1-fb"></div></div>';
h+=secNav(null,'p2')+readBtn('p1');
box.innerHTML=h; renderMath(box);
(function(){
var sl=document.getElementById('p1-p'), fig=document.getElementById('p1-fig'), out=document.getElementById('p1-out');
function render(){ var p=+sl.value; document.getElementById('p1-pv').textContent=p; fig.innerHTML=grid100(p);
out.innerHTML='<div style="font-size:1.2rem;font-weight:800;color:var(--pri2)">$'+p+'\\% = \\dfrac{'+p+'}{100} = '+_kf(p/100)+'$</div>'; renderMath(out); }
sl.oninput=render; render();
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ var t=_pick(['p2d','d2p','f2p']);
if(t==='p2d'){ var p=_pick([5,10,25,40,75,8,150]); cur={q:'$'+p+'\\%$ = ? (десятичная дробь)', ans:p/100}; }
else if(t==='d2p'){ var d=_pick([0.3,0.07,0.5,1.2,0.25,0.9]); cur={q:'$'+_kf(d)+'$ = ? $\\%$', ans:_round(d*100,2)}; }
else { var f=_pick([[1,4,25],[1,2,50],[1,5,20],[3,4,75],[1,10,10],[1,20,5]]); cur={q:'$\\dfrac{'+f[0]+'}{'+f[1]+'}$ = ? $\\%$', ans:f[2]}; } }
function show(){ if(i>=6){ document.getElementById('p1-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p1-iv2');bumpProgress('p1',30);}else if(score>=3){addXp(8,'p1-iv2');bumpProgress('p1',16);} return; }
gen(); document.getElementById('p1-i').textContent=i+1; document.getElementById('p1-q').innerHTML=cur.q; renderMath(document.getElementById('p1-q'));
document.getElementById('p1-a').value=''; document.getElementById('p1-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p1-fb'), v=parseFloat(document.getElementById('p1-a').value.replace(',','.').replace('%','').trim());
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('p1-s').textContent=score; i++; setTimeout(show,1200); }
document.getElementById('p1-go').addEventListener('click',go);
document.getElementById('p1-a').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',
'<p><b>1) Процент от числа.</b> Найти $m\\%$ от $a$: $\\;b = \\dfrac{a}{100}\\cdot m$.</p>'
+'<p><b>2) Число по его проценту.</b> $b$ — это $m\\%$ от $a$, найти $a$: $\\;a = \\dfrac{b}{m}\\cdot 100$.</p>'
+'<p><b>3) Процентное отношение.</b> Какой процент $b$ от $a$: $\\;m\\% = \\dfrac{b}{a}\\cdot 100\\%$.</p>');
h+=makeCard('example','Примеры','2.2',
'<p>$20\\%$ от $150$: $\\dfrac{150}{100}\\cdot 20 = 30$. &nbsp; $15$ — это $30\\%$ от $\\dfrac{15}{30}\\cdot 100 = 50$. &nbsp; $30$ от $120$: $\\dfrac{30}{120}\\cdot 100 = 25\\%$.</p>');
h+='<div class="wg" id="p2-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Определи тип задачи</div></div>'
+'<div class="wg-help">К какому из трёх типов относится задача?</div>'
+'<div class="score-display"><span>Вопрос <b id="p2-i">1</b> / 5</span><span>Очки: <b id="p2-s">0</b> / 5</span></div>'
+'<div id="p2-q" class="qbox" style="font-size:1rem"></div>'
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" data-t="1">1 — % от числа</button><button class="btn primary" data-t="2">2 — число по %</button><button class="btn primary" data-t="3">3 — % отношение</button></div>'
+'<div class="feedback" id="p2-fb"></div></div>';
h+='<div class="wg" id="p2-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Найди процент от числа</div></div>'
+'<div class="wg-help">Вычисли $m\\%$ от числа $a$ по формуле $\\dfrac{a}{100}\\cdot m$.</div>'
+'<div class="score-display"><span>Задача <b id="p2-ci">1</b> / 6</span><span>Очки: <b id="p2-cs">0</b> / 6</span></div>'
+'<div id="p2-cq" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p2-ca" class="tinp" style="width:110px;text-align:center"><button class="btn primary" id="p2-cgo">Проверить</button></div>'
+'<div class="feedback" id="p2-cfb"></div></div>';
h+=secNav('p1','p3')+readBtn('p2');
box.innerHTML=h; renderMath(box);
(function(){
var Q=[
{t:1,q:'В классе 25 учеников, 40% из них — девочки. Сколько девочек?'},
{t:2,q:'12 страниц — это 30% книги. Сколько страниц в книге?'},
{t:3,q:'Из 50 задач решено 30. Какой процент задач решён?'},
{t:1,q:'Цена 800 руб., скидка 15%. Какова величина скидки в рублях?'},
{t:2,q:'Со счёта сняли 200 руб., это 25% вклада. Каков был вклад?'},
{t:3,q:'Из 200 семян взошло 180. Какой процент семян взошёл?'}
];
var order=Q.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){var k=_ri(0,j),t=order[j];order[j]=order[k];order[k]=t;}
var i=0,score=0,cur=null;
function show(){ if(i>=5){ document.getElementById('p2-q').innerHTML='<b>Готово!</b> '+score+' / 5'; if(score>=4){addXp(15,'p2-iv1');bumpProgress('p2',30);}else if(score>=2){addXp(8,'p2-iv1');bumpProgress('p2',16);} return; }
cur=Q[order[i]]; document.getElementById('p2-i').textContent=i+1; document.getElementById('p2-q').innerHTML=cur.q; document.getElementById('p2-fb').style.display='none'; }
function ans(t){ if(i>=5)return; var fb=document.getElementById('p2-fb'), names={1:'процент от числа',2:'число по проценту',3:'процентное отношение'};
if(t===cur.t){ score++; feedback(fb,true,'✓ Верно — тип '+cur.t+' ('+names[cur.t]+').'); } else feedback(fb,false,'✗ Нет. Это тип '+cur.t+' ('+names[cur.t]+').');
document.getElementById('p2-s').textContent=score; i++; setTimeout(show,1400); }
document.querySelectorAll('#p2-iv1 [data-t]').forEach(function(b){ b.addEventListener('click',function(){ ans(+b.getAttribute('data-t')); }); }); show();
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ var a=_pick([20,40,60,80,100,120,200,500]), m=_pick([5,10,15,20,25,50]); cur={a:a,m:m,ans:a*m/100}; }
function show(){ if(i>=6){ document.getElementById('p2-cq').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p2-iv2');bumpProgress('p2',30);}else if(score>=3){addXp(8,'p2-iv2');bumpProgress('p2',16);} return; }
gen(); document.getElementById('p2-ci').textContent=i+1; document.getElementById('p2-cq').innerHTML='Найди $'+cur.m+'\\%$ от $'+cur.a+'$.'; renderMath(document.getElementById('p2-cq'));
document.getElementById('p2-ca').value=''; document.getElementById('p2-cfb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p2-cfb'), v=parseFloat(document.getElementById('p2-ca').value.replace(',','.').trim());
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,'✗ Нет. $\\dfrac{'+cur.a+'}{100}\\cdot'+cur.m+' = '+_kf(cur.ans)+'$.');
document.getElementById('p2-cs').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('p2-cgo').addEventListener('click',go);
document.getElementById('p2-ca').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',
'<p><b>Отношение</b> двух чисел — их частное: $a:b=\\dfrac{a}{b}$. <b>Пропорция</b> — равенство двух отношений: $a:b = c:d$ (читается «$a$ относится к $b$, как $c$ к $d$»).</p>'
+'<p>$a$ и $d$ — <b>крайние</b> члены, $b$ и $c$ — <b>средние</b>.</p>');
h+=makeCard('rule','Основное свойство пропорции','3.2',
'<p>Произведение крайних членов равно произведению средних: $\\;a\\cdot d = b\\cdot c$ («крест-накрест»).</p>'
+'<p>Отсюда находят неизвестный член: из $a:b=c:x$ получаем $x = \\dfrac{b\\cdot c}{a}$.</p>');
h+='<div class="wg" id="p3-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Найди неизвестный член</div></div>'
+'<div class="wg-help">Реши пропорцию «крест-накрест»: $x = \\dfrac{b\\cdot c}{a}$.</div>'
+'<div class="score-display"><span>Задача <b id="p3-i">1</b> / 6</span><span>Очки: <b id="p3-s">0</b> / 6</span></div>'
+'<div id="p3-q" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p3-a" class="tinp" style="width:100px;text-align:center" placeholder="x"><button class="btn primary" id="p3-go">Проверить</button></div>'
+'<div class="feedback" id="p3-fb"></div></div>';
h+='<div class="wg" id="p3-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Верна ли пропорция?</div></div>'
+'<div class="wg-help">Проверь основное свойство: равны ли произведения крайних и средних членов?</div>'
+'<div class="score-display"><span>Вопрос <b id="p3-vi">1</b> / 5</span><span>Очки: <b id="p3-vs">0</b> / 5</span></div>'
+'<div id="p3-vq" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap"><button class="btn primary" data-v="1">Верна</button><button class="btn primary" data-v="0">Неверна</button></div>'
+'<div class="feedback" id="p3-vfb"></div></div>';
h+=secNav('p2','p4')+readBtn('p3');
box.innerHTML=h; renderMath(box);
(function(){
var i=0,score=0,cur=null;
function gen(){ var a=_ri(2,6), b=_ri(2,9), m=_ri(2,6); cur={a:a,b:b,c:a*m,x:b*m}; }
function show(){ if(i>=6){ document.getElementById('p3-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p3-iv1');bumpProgress('p3',30);}else if(score>=3){addXp(8,'p3-iv1');bumpProgress('p3',16);} return; }
gen(); document.getElementById('p3-i').textContent=i+1; document.getElementById('p3-q').innerHTML='$'+cur.a+' : '+cur.b+' = '+cur.c+' : x$. Найди $x$.'; renderMath(document.getElementById('p3-q'));
document.getElementById('p3-a').value=''; document.getElementById('p3-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p3-fb'), v=parseFloat(document.getElementById('p3-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.x)<1e-9){ score++; feedback(fb,true,'✓ Верно: $x = \\dfrac{'+cur.b+'\\cdot'+cur.c+'}{'+cur.a+'} = '+cur.x+'$.'); } else feedback(fb,false,'✗ Нет. $x = \\dfrac{'+cur.b+'\\cdot'+cur.c+'}{'+cur.a+'} = '+cur.x+'$.');
document.getElementById('p3-s').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('p3-go').addEventListener('click',go);
document.getElementById('p3-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ var a=_ri(2,6),b=_ri(2,6),m=_ri(2,5),c=a*m,d=b*m, ok=_pick([true,true,false]); if(!ok){ d+=_pick([1,2,-1,3]); if(a*d===b*c) d+=1; } cur={a:a,b:b,c:c,d:d,ok:a*d===b*c}; }
function show(){ if(i>=5){ document.getElementById('p3-vq').innerHTML='<b>Готово!</b> '+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-vi').textContent=i+1; document.getElementById('p3-vq').innerHTML='Верна ли пропорция $'+cur.a+':'+cur.b+' = '+cur.c+':'+cur.d+'$?'; renderMath(document.getElementById('p3-vq'));
document.getElementById('p3-vfb').style.display='none'; }
function ans(v){ if(i>=5)return; var fb=document.getElementById('p3-vfb'), correct=cur.ok?1:0;
if(v===correct){ score++; feedback(fb,true,'✓ Верно! $'+cur.a+'\\cdot'+cur.d+' = '+(cur.a*cur.d)+'$, $'+cur.b+'\\cdot'+cur.c+' = '+(cur.b*cur.c)+'$.'); } else feedback(fb,false,'✗ Нет. $'+cur.a+'\\cdot'+cur.d+'='+(cur.a*cur.d)+'$, $'+cur.b+'\\cdot'+cur.c+'='+(cur.b*cur.c)+'$ — '+(cur.ok?'равны':'не равны')+'.');
document.getElementById('p3-vs').textContent=score; i++; setTimeout(show,1600); }
document.querySelectorAll('#p3-iv2 [data-v]').forEach(function(b){ b.addEventListener('click',function(){ ans(+b.getAttribute('data-v')); }); }); show();
})();
}
/* ===================== § 4. ПРЯМАЯ И ОБРАТНАЯ ЗАВИСИМОСТИ ===================== */
function buildP4(){
var box=document.getElementById('p4-body'); var h='';
h+=makeCard('theory','Прямая пропорциональность','4.1',
'<p>Величины <b>прямо пропорциональны</b>, если при увеличении одной в несколько раз другая увеличивается во <b>столько же</b> раз. Их отношение постоянно: $\\dfrac{y}{x}=k$.</p>'
+'<p>Пример: при постоянной цене стоимость покупки прямо пропорциональна количеству товара.</p>');
h+=makeCard('theory','Обратная пропорциональность','4.2',
'<p>Величины <b>обратно пропорциональны</b>, если при увеличении одной в несколько раз другая во <b>столько же</b> раз уменьшается. Их произведение постоянно: $x\\cdot y=k$.</p>'
+'<p>Пример: при постоянном пути время обратно пропорционально скорости.</p>');
h+='<div class="wg" id="p4-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Прямая или обратная?</div></div>'
+'<div class="wg-help">Определи вид зависимости в ситуации.</div>'
+'<div class="score-display"><span>Вопрос <b id="p4-i">1</b> / 6</span><span>Очки: <b id="p4-s">0</b> / 6</span></div>'
+'<div id="p4-q" class="qbox" style="font-size:1rem"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap"><button class="btn primary" data-t="d">прямая</button><button class="btn primary" data-t="o">обратная</button></div>'
+'<div class="feedback" id="p4-fb"></div></div>';
h+='<div class="wg" id="p4-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Достройте таблицу (прямая)</div></div>'
+'<div class="wg-help">Величины прямо пропорциональны ($y=kx$). Найди недостающее значение.</div>'
+'<div class="score-display"><span>Задача <b id="p4-ti">1</b> / 5</span><span>Очки: <b id="p4-ts">0</b> / 5</span></div>'
+'<div id="p4-tq" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p4-ta" class="tinp" style="width:100px;text-align:center"><button class="btn primary" id="p4-tgo">Проверить</button></div>'
+'<div class="feedback" id="p4-tfb"></div></div>';
h+=secNav('p3','p5')+readBtn('p4');
box.innerHTML=h; renderMath(box);
(function(){
var Q=[
{t:'d',q:'Количество тетрадей и их общая стоимость (цена постоянна).'},
{t:'o',q:'Скорость автомобиля и время в пути (расстояние постоянно).'},
{t:'o',q:'Число рабочих и время выполнения работы (объём постоянен).'},
{t:'d',q:'Время движения и пройденный путь (скорость постоянна).'},
{t:'o',q:'Число друзей и доля пирога каждому (пирог один).'},
{t:'d',q:'Масса покупки и её стоимость (цена за кг постоянна).'},
{t:'d',q:'Число одинаковых коробок и общее число книг в них.'},
{t:'o',q:'Длина шага и число шагов на одну и ту же дистанцию.'}
];
var order=Q.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){var k=_ri(0,j),t=order[j];order[j]=order[k];order[k]=t;}
var i=0,score=0,cur=null;
function show(){ if(i>=6){ document.getElementById('p4-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p4-iv1');bumpProgress('p4',30);}else if(score>=3){addXp(8,'p4-iv1');bumpProgress('p4',16);} return; }
cur=Q[order[i]]; document.getElementById('p4-i').textContent=i+1; document.getElementById('p4-q').innerHTML=cur.q; document.getElementById('p4-fb').style.display='none'; }
function ans(t){ if(i>=6)return; var fb=document.getElementById('p4-fb');
if(t===cur.t){ score++; feedback(fb,true,'✓ Верно — '+(cur.t==='d'?'прямая':'обратная')+' пропорциональность.'); } else feedback(fb,false,'✗ Нет. Здесь '+(cur.t==='d'?'прямая':'обратная')+' зависимость.');
document.getElementById('p4-s').textContent=score; i++; setTimeout(show,1400); }
document.querySelectorAll('#p4-iv1 [data-t]').forEach(function(b){ b.addEventListener('click',function(){ ans(b.getAttribute('data-t')); }); }); show();
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ var k=_ri(2,9), x1=_ri(2,6), x2=_ri(2,8); cur={k:k,x1:x1,y1:k*x1,x2:x2,y2:k*x2}; }
function show(){ if(i>=5){ document.getElementById('p4-tq').innerHTML='<b>Готово!</b> '+score+' / 5'; if(score>=4){addXp(15,'p4-iv2');bumpProgress('p4',30);}else if(score>=2){addXp(8,'p4-iv2');bumpProgress('p4',16);} return; }
gen(); document.getElementById('p4-ti').textContent=i+1;
document.getElementById('p4-tq').innerHTML='<table class="tbl" style="max-width:260px;margin:0 auto"><tr><th>$x$</th><td>'+cur.x1+'</td><td>'+cur.x2+'</td></tr><tr><th>$y$</th><td>'+cur.y1+'</td><td>?</td></tr></table><div style="margin-top:6px">Найди недостающее $y$.</div>'; renderMath(document.getElementById('p4-tq'));
document.getElementById('p4-ta').value=''; document.getElementById('p4-tfb').style.display='none'; }
function go(){ if(i>=5)return; var fb=document.getElementById('p4-tfb'), v=parseFloat(document.getElementById('p4-ta').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.y2)<1e-9){ score++; feedback(fb,true,'✓ Верно: $k='+cur.k+'$, $y='+cur.k+'\\cdot'+cur.x2+'='+cur.y2+'$.'); } else feedback(fb,false,'✗ Нет. $k=\\dfrac{'+cur.y1+'}{'+cur.x1+'}='+cur.k+'$, значит $y='+cur.y2+'$.');
document.getElementById('p4-ts').textContent=score; i++; setTimeout(show,1500); }
document.getElementById('p4-tgo').addEventListener('click',go);
document.getElementById('p4-ta').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== § 5. РЕШЕНИЕ ЗАДАЧ С ПОМОЩЬЮ ПРОПОРЦИЙ ===================== */
function buildP5(){
var box=document.getElementById('p5-body'); var h='';
h+=makeCard('rule','Как решать задачи пропорцией','5.1',
'<p>1) Обозначают неизвестное буквой $x$. 2) Записывают условие в две строки. 3) Если зависимость <b>прямая</b> — стрелки в одну сторону, составляют пропорцию напрямую; если <b>обратная</b> — одно из отношений переворачивают. 4) Решают «крест-накрест».</p>');
h+=makeCard('example','Пример','5.2',
'<p>За $3$ кг яблок заплатили $12$ руб. Сколько за $5$ кг? (прямая) $\\;3:12 = 5:x$, $x=\\dfrac{12\\cdot5}{3}=20$ руб.</p>');
h+='<div class="wg" id="p5-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Задачи на прямую пропорцию</div></div>'
+'<div class="wg-help">Реши задачу с помощью прямой пропорции. Ответ — число.</div>'
+'<div class="score-display"><span>Задача <b id="p5-i">1</b> / 6</span><span>Очки: <b id="p5-s">0</b> / 6</span></div>'
+'<div id="p5-q" class="qbox" style="font-size:1rem"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p5-a" class="tinp" style="width:100px;text-align:center"><button class="btn primary" id="p5-go">Проверить</button></div>'
+'<div class="feedback" id="p5-fb"></div></div>';
h+='<div class="wg" id="p5-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Задачи на обратную пропорцию</div></div>'
+'<div class="wg-help">Здесь зависимость обратная: произведение величин постоянно.</div>'
+'<div class="score-display"><span>Задача <b id="p5-ii">1</b> / 5</span><span>Очки: <b id="p5-is">0</b> / 5</span></div>'
+'<div id="p5-iq" class="qbox" style="font-size:1rem"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p5-ia" class="tinp" style="width:100px;text-align:center"><button class="btn primary" id="p5-igo">Проверить</button></div>'
+'<div class="feedback" id="p5-ifb"></div></div>';
h+=secNav('p4','p6')+readBtn('p5');
box.innerHTML=h; renderMath(box);
(function(){
var P=[
{q:'За 3 кг яблок заплатили 12 руб. Сколько рублей за 5 кг?',a:20},
{q:'2 л сока стоят 10 руб. Сколько стоят 7 л?',a:35},
{q:'4 одинаковых коробки весят 20 кг. Сколько весят 6 коробок?',a:30},
{q:'За 5 ручек заплатили 15 руб. Сколько за 8 ручек?',a:24},
{q:'3 м ткани стоят 9 руб. Сколько стоят 7 м?',a:21},
{q:'За 2 ч турист прошёл 8 км. Сколько км за 9 ч (тот же темп)?',a:36}
];
var order=P.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){var k=_ri(0,j),t=order[j];order[j]=order[k];order[k]=t;}
var i=0,score=0,cur=null;
function show(){ if(i>=6){ document.getElementById('p5-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p5-iv1');bumpProgress('p5',30);}else if(score>=3){addXp(8,'p5-iv1');bumpProgress('p5',16);} return; }
cur=P[order[i]]; document.getElementById('p5-i').textContent=i+1; document.getElementById('p5-q').innerHTML=cur.q;
document.getElementById('p5-a').value=''; document.getElementById('p5-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p5-fb'), v=parseFloat(document.getElementById('p5-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.a)<1e-9){ score++; feedback(fb,true,'✓ Верно: '+cur.a+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+cur.a+'.');
document.getElementById('p5-s').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('p5-go').addEventListener('click',go);
document.getElementById('p5-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
(function(){
var P=[
{q:'5 рабочих выполнят заказ за 12 дней. За сколько дней — 6 рабочих?',a:10},
{q:'Автомобиль прошёл путь за 4 ч со скоростью 60 км/ч. За сколько часов при 80 км/ч?',a:3},
{q:'8 одинаковых насосов наполнят бассейн за 6 ч. За сколько часов — 4 насоса?',a:12},
{q:'6 человек съедят запас за 8 дней. На сколько дней хватит 4 людям?',a:12},
{q:'3 трубы наполнят бак за 10 мин. За сколько минут — 5 таких труб?',a:6}
];
var order=P.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){var k=_ri(0,j),t=order[j];order[j]=order[k];order[k]=t;}
var i=0,score=0,cur=null;
function show(){ if(i>=5){ document.getElementById('p5-iq').innerHTML='<b>Готово!</b> '+score+' / 5'; if(score>=4){addXp(15,'p5-iv2');bumpProgress('p5',30);}else if(score>=2){addXp(8,'p5-iv2');bumpProgress('p5',16);} return; }
cur=P[order[i]]; document.getElementById('p5-ii').textContent=i+1; document.getElementById('p5-iq').innerHTML=cur.q;
document.getElementById('p5-ia').value=''; document.getElementById('p5-ifb').style.display='none'; }
function go(){ if(i>=5)return; var fb=document.getElementById('p5-ifb'), v=parseFloat(document.getElementById('p5-ia').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.a)<1e-9){ score++; feedback(fb,true,'✓ Верно: '+cur.a+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+cur.a+' (произведение величин постоянно).');
document.getElementById('p5-is').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('p5-igo').addEventListener('click',go);
document.getElementById('p5-ia').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== § 6. МАСШТАБ ===================== */
function buildP6(){
var box=document.getElementById('p6-body'); var h='';
h+=makeCard('theory','Что такое масштаб','6.1',
'<p><b>Масштаб</b> — отношение длины отрезка на чертеже (карте) к длине соответствующего отрезка в реальности. Запись $1:N$ означает: $1$ см на карте соответствует $N$ см на местности.</p>'
+'<p>Карта: реальное расстояние $=$ расстояние на карте $\\times N$. Чертёж детали может быть и крупнее ($N<1$).</p>');
h+=makeCard('example','Пример','6.2',
'<p>Масштаб $1:1000$. На карте $3$ см. Реально $3\\cdot 1000 = 3000$ см $= 30$ м.</p>');
h+='<div class="wg" id="p6-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Карта → местность</div></div>'
+'<div class="wg-help">По расстоянию на карте найди реальное расстояние (ответ в метрах).</div>'
+'<div class="score-display"><span>Задача <b id="p6-i">1</b> / 6</span><span>Очки: <b id="p6-s">0</b> / 6</span></div>'
+'<div id="p6-q" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p6-a" class="tinp" style="width:110px;text-align:center" placeholder="метры"><button class="btn primary" id="p6-go">Проверить</button></div>'
+'<div class="feedback" id="p6-fb"></div></div>';
h+='<div class="wg" id="p6-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Местность → карта</div></div>'
+'<div class="wg-help">По реальному расстоянию найди длину на карте (ответ в сантиметрах).</div>'
+'<div class="score-display"><span>Задача <b id="p6-ri">1</b> / 5</span><span>Очки: <b id="p6-rs">0</b> / 5</span></div>'
+'<div id="p6-rq" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p6-ra" class="tinp" style="width:110px;text-align:center" placeholder="см"><button class="btn primary" id="p6-rgo">Проверить</button></div>'
+'<div class="feedback" id="p6-rfb"></div></div>';
h+=secNav('p5','p7')+readBtn('p6');
box.innerHTML=h; renderMath(box);
(function(){
var i=0,score=0,cur=null;
function gen(){ var N=_pick([200,500,1000,2000,10000]), d=_ri(2,9); cur={N:N,d:d,ans:d*N/100}; }
function show(){ if(i>=6){ document.getElementById('p6-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p6-iv1');bumpProgress('p6',30);}else if(score>=3){addXp(8,'p6-iv1');bumpProgress('p6',16);} return; }
gen(); document.getElementById('p6-i').textContent=i+1; document.getElementById('p6-q').innerHTML='Масштаб $1:'+cur.N+'$. На карте $'+cur.d+'$ см. Реальное расстояние в метрах?'; renderMath(document.getElementById('p6-q'));
document.getElementById('p6-a').value=''; document.getElementById('p6-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p6-fb'), v=parseFloat(document.getElementById('p6-a').value.replace(',','.').trim());
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,'✗ Нет. '+cur.d+'·'+cur.N+'='+(cur.d*cur.N)+' см = '+_kf(cur.ans)+' м.');
document.getElementById('p6-s').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('p6-go').addEventListener('click',go);
document.getElementById('p6-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ var N=_pick([100,200,500,1000]), m=_pick([10,20,50,100,5]); cur={N:N,m:m,ans:m*100/N}; }
function show(){ if(i>=5){ document.getElementById('p6-rq').innerHTML='<b>Готово!</b> '+score+' / 5'; if(score>=4){addXp(15,'p6-iv2');bumpProgress('p6',30);}else if(score>=2){addXp(8,'p6-iv2');bumpProgress('p6',16);} return; }
gen(); document.getElementById('p6-ri').textContent=i+1; document.getElementById('p6-rq').innerHTML='Масштаб $1:'+cur.N+'$. Реальное расстояние $'+cur.m+'$ м. Длина на карте в см?'; renderMath(document.getElementById('p6-rq'));
document.getElementById('p6-ra').value=''; document.getElementById('p6-rfb').style.display='none'; }
function go(){ if(i>=5)return; var fb=document.getElementById('p6-rfb'), v=parseFloat(document.getElementById('p6-ra').value.replace(',','.').trim());
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,'✗ Нет. '+cur.m+' м = '+(cur.m*100)+' см; делим на '+cur.N+' → '+_kf(cur.ans)+' см.');
document.getElementById('p6-rs').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('p6-rgo').addEventListener('click',go);
document.getElementById('p6-ra').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== § 7. КРУГОВЫЕ ДИАГРАММЫ ===================== */
function buildP7(){
var box=document.getElementById('p7-body'); var h='';
h+=makeCard('theory','Круговая диаграмма','7.1',
'<p><b>Круговая диаграмма</b> наглядно показывает, как целое делится на части. Весь круг — это $100\\%$, или $360°$.</p>'
+'<p>Сектор в $p\\%$ занимает угол $p\\%\\cdot 360° = 3{,}6\\cdot p$ градусов. Например, $25\\%$ — это $90°$ (четверть круга).</p>');
h+='<div class="wg" id="p7-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Диаграмма данных</div></div>'
+'<div class="wg-help">Выбери набор данных — построится круговая диаграмма с долями.</div>'
+'<div id="p7-pick" style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-bottom:8px"></div>'
+'<div style="display:flex;gap:12px;justify-content:center;align-items:center;flex-wrap:wrap"><div id="p7-fig"></div><div id="p7-leg"></div></div></div>';
h+='<div class="wg" id="p7-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Проценты и градусы</div></div>'
+'<div class="wg-help">Переведи долю сектора между процентами и градусами ($1\\% = 3{,}6°$).</div>'
+'<div class="score-display"><span>Вопрос <b id="p7-i">1</b> / 6</span><span>Очки: <b id="p7-s">0</b> / 6</span></div>'
+'<div id="p7-q" class="qbox"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="p7-a" class="tinp" style="width:110px;text-align:center"><button class="btn primary" id="p7-go">Проверить</button></div>'
+'<div class="feedback" id="p7-fb"></div></div>';
h+=secNav('p6','app')+readBtn('p7');
box.innerHTML=h; renderMath(box);
(function(){
var DS=[
{name:'Рацион (БЖУ)', segs:[{label:'Углеводы',value:66},{label:'Жиры',value:17},{label:'Белки',value:17}]},
{name:'Оценки класса', segs:[{label:'«910»',value:30},{label:'«78»',value:45},{label:'«56»',value:25}]},
{name:'Сутки школьника', segs:[{label:'Сон',value:33},{label:'Учёба',value:25},{label:'Отдых',value:42}]}
];
var pick=document.getElementById('p7-pick'), fig=document.getElementById('p7-fig'), leg=document.getElementById('p7-leg');
var pal=['#4f46e5','#0891b2','#e11d48','#059669','#d97706'];
pick.innerHTML=DS.map(function(d,k){ return '<button class="btn'+(k===0?' primary':'')+'" data-k="'+k+'">'+d.name+'</button>'; }).join('');
function render(k){ var d=DS[k]; fig.innerHTML=Math6.pie(d.segs,{size:200});
leg.innerHTML=d.segs.map(function(s,j){ return '<div style="display:flex;align-items:center;gap:8px;margin:4px 0;font-size:.9rem"><span style="width:14px;height:14px;border-radius:3px;background:'+pal[j%pal.length]+';display:inline-block"></span>'+s.label+' — <b>'+s.value+'%</b></div>'; }).join(''); }
pick.querySelectorAll('[data-k]').forEach(function(b){ b.addEventListener('click',function(){ pick.querySelectorAll('button').forEach(function(x){x.classList.remove('primary');}); b.classList.add('primary'); render(+b.getAttribute('data-k')); }); });
render(0);
})();
(function(){
var i=0,score=0,cur=null;
function gen(){ if(_pick([true,false])){ var p=_pick([10,20,25,50,75]); cur={q:'Сектор $'+p+'\\%$ — сколько это градусов?', ans:_round(p*3.6,1)}; } else { var g=_pick([36,72,90,180,270]); cur={q:'Сектор $'+g+'°$ — сколько это процентов?', ans:_round(g/3.6,1)}; } }
function show(){ if(i>=6){ document.getElementById('p7-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'p7-iv2');bumpProgress('p7',30);}else if(score>=3){addXp(8,'p7-iv2');bumpProgress('p7',16);} return; }
gen(); document.getElementById('p7-i').textContent=i+1; document.getElementById('p7-q').innerHTML=cur.q; renderMath(document.getElementById('p7-q'));
document.getElementById('p7-a').value=''; document.getElementById('p7-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('p7-fb'), v=parseFloat(document.getElementById('p7-a').value.replace(',','.').replace('%','').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.ans)<0.051){ score++; feedback(fb,true,'✓ Верно: '+_kf(cur.ans)+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+_kf(cur.ans)+'.');
document.getElementById('p7-s').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('p7-go').addEventListener('click',go);
document.getElementById('p7-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== § 9. МАТЕМАТИКА ВОКРУГ НАС ===================== */
function buildApp(){
var box=document.getElementById('app-body'); var h='';
h+=makeCard('theory','Проценты вокруг нас','9.1',
'<p>Скидки и распродажи, наценки, банковские вклады под проценты, состав продуктов, статистика и опросы — везде встречаются проценты и пропорции.</p>');
h+='<div class="wg" id="app-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Задачи из жизни</div></div>'
+'<div class="wg-help">Реши практическую задачу на проценты. Ответ — число (рубли, штуки, проценты).</div>'
+'<div class="score-display"><span>Задача <b id="app-i">1</b> / 6</span><span>Очки: <b id="app-s">0</b> / 6</span></div>'
+'<div id="app-q" class="qbox" style="font-size:1rem;text-align:left"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="app-a" class="tinp" style="width:110px;text-align:center"><button class="btn primary" id="app-go">Проверить</button></div>'
+'<div class="feedback" id="app-fb"></div></div>';
h+=secNav('p7','final')+readBtn('app');
box.innerHTML=h; renderMath(box);
(function(){
var P=[
{q:'Товар стоил 200 руб. Скидка 10%. Новая цена (руб.)?',a:180},
{q:'Куртка 800 руб., скидка 25%. Сколько рублей скидка?',a:200},
{q:'Вклад 1000 руб. под 5% годовых. Сколько рублей процентов за год?',a:50},
{q:'В магазине 60 товаров, 20% распроданы. Сколько товаров продано?',a:12},
{q:'Цена выросла с 50 руб. на 20%. Новая цена (руб.)?',a:60},
{q:'Из 40 учеников 30% занимаются спортом. Сколько это человек?',a:12},
{q:'Билет 500 руб., детям скидка 40%. Сколько платит ребёнок (руб.)?',a:300}
];
var order=P.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){var k=_ri(0,j),t=order[j];order[j]=order[k];order[k]=t;}
var i=0,score=0,cur=null;
function show(){ if(i>=6){ document.getElementById('app-q').innerHTML='<b>Готово!</b> '+score+' / 6'; if(score>=5){addXp(15,'app-iv1');bumpProgress('app',35);}else if(score>=3){addXp(8,'app-iv1');bumpProgress('app',18);} return; }
cur=P[order[i]]; document.getElementById('app-i').textContent=i+1; document.getElementById('app-q').innerHTML=cur.q;
document.getElementById('app-a').value=''; document.getElementById('app-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('app-fb'), v=parseFloat(document.getElementById('app-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.a)<1e-9){ score++; feedback(fb,true,'✓ Верно: '+cur.a+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+cur.a+'.');
document.getElementById('app-s').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('app-go').addEventListener('click',go);
document.getElementById('app-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== ФИНАЛ ГЛАВЫ — БОССЫ ===================== */
function buildFinal(){
var box=document.getElementById('final-body'); var h='';
h+=makeCard('theory','Финал главы 2','★','<p>Пять боссов проверят проценты, пропорции, масштаб и круговые диаграммы. Победи всех!</p>');
h+='<div class="wg" id="fin"><div class="wg-header"><span class="wg-badge">Боссы</span><div class="wg-title">Сразись с главой 2</div></div>'
+'<div class="hp-boss"><div class="hp-boss-fill" id="fin-hp" style="width:100%"></div></div>'
+'<div class="score-display"><span>Босс <b id="fin-i">1</b> / 5</span><span>Побеждено: <b id="fin-s">0</b> / 5</span></div>'
+'<div id="fin-name" style="font-weight:800;color:#b91c1c;text-align:center;margin-bottom:6px"></div>'
+'<div id="fin-q" class="boss-q"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="fin-a" class="tinp" style="width:140px;text-align:center" placeholder="ответ"><button class="btn primary" id="fin-go">Удар!</button></div>'
+'<div class="feedback" id="fin-fb"></div></div>';
h+=secNav('app',null)+readBtn('final','Завершить главу 2 (+10 XP)');
box.innerHTML=h; renderMath(box);
(function(){
var bosses=[
function(){ var a=_pick([40,60,80,200]), m=_pick([10,20,25,5]); return {name:'Процентщик', q:'Найди $'+m+'\\%$ от $'+a+'$.', ans:a*m/100}; },
function(){ var b=_pick([10,20,30]), a=_pick([40,50,200]); return {name:'Аналитик', q:'Какой процент число $'+b+'$ составляет от $'+a+'$?', ans:_round(b/a*100,2)}; },
function(){ var aa=_ri(2,6),bb=_ri(2,8),m=_ri(2,5); return {name:'Пропорция', q:'Реши пропорцию $'+aa+':'+bb+' = '+(aa*m)+':x$. Найди $x$.', ans:bb*m}; },
function(){ var N=_pick([500,1000,2000]), d=_ri(2,8); return {name:'Картограф', q:'Масштаб $1:'+N+'$. На карте $'+d+'$ см. Реальное расстояние в метрах?', ans:d*N/100}; },
function(){ var p=_pick([10,20,25,50]); return {name:'Диаграмма', q:'Сектор $'+p+'\\%$ — сколько это градусов?', ans:_round(p*3.6,1)}; }
];
var i=0,score=0,cur=null,done=false;
function show(){ if(i>=5){ done=true; document.getElementById('fin-name').textContent=''; document.getElementById('fin-q').innerHTML=(score>=4?'<b>Победа!</b> Глава 2 пройдена. ':'<b>Бой окончен.</b> ')+'Побеждено боссов: '+score+' / 5.'; document.getElementById('fin-hp').style.width=(score>=4?0:40)+'%';
if(score>=4){ addXp(40,'final'); bumpProgress('final',100); if(window.confetti)try{confetti();}catch(e){} } else bumpProgress('final',60); return; }
cur=bosses[i](); document.getElementById('fin-i').textContent=i+1; document.getElementById('fin-s').textContent=score;
document.getElementById('fin-name').textContent='Босс '+(i+1)+': '+cur.name; document.getElementById('fin-hp').style.width=(100-i*20)+'%';
document.getElementById('fin-q').innerHTML=cur.q; renderMath(document.getElementById('fin-q'));
document.getElementById('fin-a').value=''; document.getElementById('fin-fb').style.display='none'; }
function go(){ if(done||i>=5)return; var fb=document.getElementById('fin-fb'), v=parseFloat(document.getElementById('fin-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.ans)<0.051){ score++; feedback(fb,true,'✓ Босс повержен! Ответ '+_kf(cur.ans)+'.'); } else feedback(fb,false,'✗ Босс устоял. Верно: '+_kf(cur.ans)+'.');
document.getElementById('fin-s').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('fin-go').addEventListener('click',go);
document.getElementById('fin-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== ДАННЫЕ ===================== */
var SIDEBARS = {
p1:{ title:'Шпаргалка § 1', rows:[ ['Процент','$1\\%=\\frac{1}{100}=0{,}01$'], ['$\\%\\to$ дробь','делим на 100'], ['дробь $\\to\\%$','умножаем на 100'], ['Всё число','$100\\%$'] ]},
p2:{ title:'Шпаргалка § 2', rows:[ ['% от числа','$b=\\frac{a}{100}\\cdot m$'], ['число по %','$a=\\frac{b}{m}\\cdot100$'], ['% отношение','$m\\%=\\frac{b}{a}\\cdot100\\%$'] ]},
p3:{ title:'Шпаргалка § 3', rows:[ ['Отношение','$a:b=\\frac{a}{b}$'], ['Пропорция','$a:b=c:d$'], ['Осн. свойство','$a\\cdot d=b\\cdot c$'], ['Неизвестный','$x=\\frac{b\\cdot c}{a}$'] ]},
p4:{ title:'Шпаргалка § 4', rows:[ ['Прямая','$\\frac{y}{x}=k$ (вместе растут)'], ['Обратная','$x\\cdot y=k$ (одна растёт — другая падает)'] ]},
p5:{ title:'Шпаргалка § 5', rows:[ ['Прямая','стрелки в одну сторону'], ['Обратная','одно отношение переворачиваем'], ['Решаем','крест-накрест'] ]},
p6:{ title:'Шпаргалка § 6', rows:[ ['Масштаб $1:N$','1 см карты = $N$ см реально'], ['Карта → реал','$\\times N$'], ['Реал → карта','$\\div N$'], ['1 м','$=100$ см'] ]},
p7:{ title:'Шпаргалка § 7', rows:[ ['Весь круг','$100\\%=360°$'], ['$p\\%$ сектора','$=3{,}6\\cdot p$ градусов'], ['$25\\%$','$=90°$'] ]},
app:{ title:'Шпаргалка § 9', rows:[ ['Скидка','цена $\\times$ (1 %/100)'], ['Наценка','цена $\\times$ (1 + %/100)'], ['Проценты вклада','$\\frac{вклад}{100}\\cdot$ ставка'] ]},
final:{ title:'Финал главы 2', rows:[ ['5 боссов','проценты, пропорции, масштаб, диаграммы'], ['Победа','4 из 5 и больше'], ['Награда','+40 XP и достижение «Глава 2 пройдена»'] ]}
};
var TIPS = [
{ sec:'p1', html:'Процент — это сотая. Чтобы перевести проценты в дробь, просто сдвинь запятую на 2 знака влево: $7\\%=0{,}07$.' },
{ sec:'p2', html:'Определи тип задачи: ищешь часть от числа (тип 1), само число (тип 2) или сколько процентов (тип 3) — и примени нужную формулу.' },
{ sec:'p3', html:'Основное свойство: перемножь «крест-накрест». Неизвестный член $x=\\frac{b\\cdot c}{a}$ — произведение средних делить на известный крайний.' },
{ sec:'p4', html:'Прямая: «больше — больше» (отношение постоянно). Обратная: «больше — меньше» (произведение постоянно).' },
{ sec:'p5', html:'Запиши условие в две строки. Если зависимость обратная — переверни одно из отношений перед тем, как решать крест-накрест.' },
{ sec:'p6', html:'$1:N$ — карта в $N$ раз меньше. Из карты в реальность умножай на $N$; не забудь перевести см в метры (делить на 100).' },
{ sec:'p7', html:'Весь круг = $360°$ = $100\\%$. Значит $1\\%=3{,}6°$. Чтобы из процентов получить градусы — умножь на $3{,}6$.' }
];
var GLOSSARY = [
{ term:'процент', def:'Сотая доля числа: $1\\%=\\frac{1}{100}$.', sec:'p1', aliases:['процент','процента','процентов','проценты','процентах'] },
{ term:'пропорция', def:'Равенство двух отношений: $a:b=c:d$.', sec:'p3', aliases:['пропорция','пропорции','пропорцию','пропорций'] },
{ term:'отношение', def:'Частное двух чисел $a:b=\\frac{a}{b}$.', sec:'p3', aliases:['отношение','отношения','отношении'] },
{ term:'крайние члены', def:'Первый и последний члены пропорции ($a$ и $d$).', sec:'p3', aliases:['крайние члены','крайних членов','крайние'] },
{ term:'средние члены', def:'Второй и третий члены пропорции ($b$ и $c$).', sec:'p3', aliases:['средние члены','средних членов','средние'] },
{ term:'прямая пропорциональность', def:'Зависимость, при которой отношение величин постоянно ($y/x=k$).', sec:'p4', aliases:['прямая пропорциональность','прямо пропорциональны','прямой пропорциональности'] },
{ term:'обратная пропорциональность', def:'Зависимость, при которой произведение величин постоянно ($xy=k$).', sec:'p4', aliases:['обратная пропорциональность','обратно пропорциональны','обратной пропорциональности'] },
{ term:'масштаб', def:'Отношение длины на карте к реальной длине; $1:N$.', sec:'p6', aliases:['масштаб','масштаба','масштабе','масштабом'] },
{ term:'круговая диаграмма', def:'Диаграмма-круг, показывающая доли целого; весь круг $=360°=100\\%$.', sec:'p7', aliases:['круговая диаграмма','круговой диаграммы','круговую диаграмму','диаграмма','диаграммы'] }
];
var BUILDERS = { p1:buildP1, p2:buildP2, p3:buildP3, p4:buildP4, p5:buildP5, p6:buildP6, p7:buildP7, app:buildApp, final:buildFinal };
Object.assign(window.M6, { sidebars:SIDEBARS, tips:TIPS, glossary:GLOSSARY, builders:BUILDERS });
</script> </script>
</body> </body>