From 0bb48d3f040f4361a45e5f4925ff6c38da92fff5 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Tue, 2 Jun 2026 20:11:15 +0300 Subject: [PATCH] =?UTF-8?q?feat(math6):=20=D0=BA=D1=83=D1=80=D1=81=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B9=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=85=D0=B0=D0=B1=D0=B5=20+=20=D0=B7=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=C2=AB=D0=9C=D0=B0=D1=82=D0=B5=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D0=BA=206=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0?= =?UTF-8?q?=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Капстоун-бой из 6 испытаний (по одному из каждой главы: десятичные, проценты, множества, рациональные, координаты, геометрия) с HP-баром. Победа 5/6 → +150 XP (LS.xp) + звание «Математик 6 класса» (зажигается ачивка-strip, флаг localStorage math6_course_done). Тесты math6: 17/17. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/math6-page.test.js | 4 +- frontend/textbooks/math_6_hub.html | 92 ++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/backend/tests/math6-page.test.js b/backend/tests/math6-page.test.js index a918613..52b035f 100644 --- a/backend/tests/math6-page.test.js +++ b/backend/tests/math6-page.test.js @@ -159,10 +159,12 @@ test('ch6: наглядная геометрия — интерактивы §1 assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); }); -test('hub: 6 карточек глав', async () => { +test('hub: 6 карточек глав + курсовой финал', async () => { const { doc, errors } = await loadDom('math_6_hub.html'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('.ch-grid .ch-card').length, 6, '6 глав'); + assert.ok(doc.querySelector('#cf-wrap') && doc.querySelector('#cf-go'), 'секция курсового финала'); + assert.ok(doc.querySelector('#cf-q').textContent.length > 0, 'первое испытание показано'); }); test('ch1 Волна 1: интерактивы §1–§3 монтируются без ошибок', async () => { diff --git a/frontend/textbooks/math_6_hub.html b/frontend/textbooks/math_6_hub.html index 8c65af6..004c20c 100644 --- a/frontend/textbooks/math_6_hub.html +++ b/frontend/textbooks/math_6_hub.html @@ -110,6 +110,32 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px} .ach-sub{font-size:.85rem;color:var(--muted);margin-top:2px} .ach-strip.lit .ach-title{color:#92400e} +/* COURSE FINAL */ +.cf-wrap{background:var(--card);border:1.5px solid var(--border);border-radius:18px;overflow:hidden;margin-bottom:28px;box-shadow:var(--sh)} +.cf-head{padding:18px 22px;background:linear-gradient(135deg,#312e81,#4f46e5 55%,#7c3aed);color:#fff;cursor:pointer;display:flex;align-items:center;gap:14px;user-select:none} +.cf-head:hover{filter:brightness(1.07)} +.cf-hi{width:46px;height:46px;border-radius:12px;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0} +.cf-hi svg{width:26px;height:26px;stroke:#fff;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round} +.cf-ht{flex:1;min-width:0} +.cf-tag{display:inline-block;padding:3px 9px;background:rgba(255,255,255,.24);border-radius:99px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px} +.cf-title{font-family:'Outfit',sans-serif;font-size:1.18rem;font-weight:800;line-height:1.25} +.cf-sub{font-size:.84rem;opacity:.9;margin-top:2px} +.cf-chev{flex-shrink:0;transition:transform .25s} +.cf-chev svg{width:24px;height:24px;stroke:#fff;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round} +.cf-wrap.open .cf-chev{transform:rotate(180deg)} +.cf-body{display:none;padding:22px} +.cf-wrap.open .cf-body{display:block} +.cf-hp{height:14px;background:rgba(220,38,38,.12);border-radius:9px;overflow:hidden;border:1px solid #fecaca;margin:6px 0 12px} +.cf-hp-fill{height:100%;background:linear-gradient(90deg,#dc2626,#f59e0b);border-radius:9px;transition:width .5s} +.cf-meta{display:flex;gap:16px;font-size:.9rem;color:var(--muted);margin-bottom:10px;font-weight:600} +.cf-name{font-weight:800;color:#4338ca;text-align:center;margin-bottom:6px} +.cf-q{font-size:1.05rem;line-height:1.5;padding:12px 14px;background:var(--pri-soft);border-radius:9px;margin-bottom:10px;text-align:center} +.cf-row{display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap} +.cf-inp{padding:9px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);width:140px;text-align:center;font-family:'Outfit',monospace;font-size:1rem} +.cf-btn{padding:9px 18px;border-radius:9px;background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;font-weight:700;border:none;cursor:pointer} +.cf-fb{margin-top:10px;padding:10px 14px;border-radius:9px;font-weight:600;font-size:.9rem;display:none} +.cf-fb.ok{display:block;background:#d1fae5;color:#065f46} +.cf-fb.bad{display:block;background:#fee2e2;color:#7f1d1d} .foot{text-align:center;padding:24px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border)} @@ -230,6 +256,26 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px} +
+
+
+
+ Курсовой финал +
6 испытаний — по одному из каждой главы
+
Победи 5 из 6 и получи звание «Математик 6 класса» (+150 XP)
+
+
+
+
+
+
Испытание 1 / 6Пройдено: 0 / 6
+
+
+
+
+
+
+
@@ -284,6 +330,52 @@ function loadProgress(){ } if(document.readyState==='loading') document.addEventListener('DOMContentLoaded', loadProgress); else loadProgress(); window.addEventListener('focus', loadProgress); + +/* ===================== КУРСОВОЙ ФИНАЛ ===================== */ +(function(){ + function _ri(a,b){ return a + Math.floor(Math.random()*(b-a+1)); } + function _pick(a){ return a[_ri(0,a.length-1)]; } + function comma(x){ return String(x).replace('.',','); } + var head=document.getElementById('cf-head'), wrap=document.getElementById('cf-wrap'); + if(!head) return; + head.addEventListener('click', function(){ wrap.classList.toggle('open'); }); + + function litAch(text){ var s=document.getElementById('ach-strip'), sub=document.getElementById('ach-sub'); if(s)s.classList.add('lit'); if(sub)sub.textContent=text||'Звание «Математик 6 класса» получено!'; } + if(localStorage.getItem('math6_course_done')==='1') litAch(); + + var bosses=[ + function(){ var p=_pick([[1.2,0.5,0.6],[2.5,0.4,1],[0.3,6,1.8],[0.2,0.4,0.08],[1.5,0.2,0.3]]); return {name:'Глава 1 · Десятичные дроби', q:'Вычисли '+comma(p[0])+' · '+comma(p[1])+'.', ans:p[2]}; }, + function(){ var n=_pick([40,60,80,200]), m=_pick([10,20,25,5]); return {name:'Глава 2 · Проценты', q:'Найди '+m+'% от '+n+'.', ans:n*m/100}; }, + function(){ var c=_ri(2,6), a=c+_ri(3,8), b=c+_ri(3,8); return {name:'Глава 3 · Множество', q:'|A|='+a+', |B|='+b+', |A∩B|='+c+'. Найди |A∪B|.', ans:a+b-c}; }, + function(){ var a=_pick([-7,-4,-9,8,-12]), b=_pick([12,15,-3,-6,20]); return {name:'Глава 4 · Рациональные числа', q:'Вычисли '+a+' + ('+b+').', ans:a+b}; }, + function(){ var k=_pick([2,3,4,5]), a=_pick([2,3,4]); return {name:'Глава 5 · Координаты', q:'Прямая y=kx проходит через ('+a+'; '+(k*a)+'). Найди k.', ans:k}; }, + function(){ var r=_pick([2,3,4,5]); return {name:'Глава 6 · Геометрия', q:'Площадь круга S=πr² при r='+r+' (π=3,14).', ans:Math.round(3.14*r*r*100)/100}; } + ]; + var i=0, score=0, cur=null, done=false; + function show(){ + if(i>=6){ done=true; + document.getElementById('cf-name').textContent=''; + document.getElementById('cf-q').innerHTML=(score>=5?'Победа! Звание «Математик 6 класса» ваше!':'Бой окончен. Пройдено '+score+' / 6. Попробуйте ещё раз!'); + document.getElementById('cf-hp').style.width=(score>=5?0:35)+'%'; + document.querySelector('.cf-row').style.display='none'; + if(score>=5){ if(localStorage.getItem('math6_course_done')!=='1'){ localStorage.setItem('math6_course_done','1'); if(window.LS&&window.LS.xp)window.LS.xp.add(150,'math6-course'); } litAch(); } + return; } + cur=bosses[i](); document.getElementById('cf-i').textContent=i+1; document.getElementById('cf-s').textContent=score; + document.getElementById('cf-name').textContent=cur.name; + document.getElementById('cf-hp').style.width=(100-i*100/6)+'%'; + document.getElementById('cf-q').textContent=cur.q; + document.getElementById('cf-a').value=''; document.getElementById('cf-fb').style.display='none'; + } + function go(){ if(done||i>=6)return; var fb=document.getElementById('cf-fb'), v=parseFloat(document.getElementById('cf-a').value.replace(',','.').trim()); + if(isNaN(v)){ fb.className='cf-fb bad'; fb.textContent='Введите число.'; return; } + if(Math.abs(v-cur.ans)<0.011){ score++; fb.className='cf-fb ok'; fb.textContent='✓ Верно! Ответ '+comma(cur.ans)+'.'; } + else { fb.className='cf-fb bad'; fb.textContent='✗ Неверно. Правильно: '+comma(cur.ans)+'.'; } + document.getElementById('cf-s').textContent=score; i++; setTimeout(show,1300); + } + document.getElementById('cf-go').addEventListener('click', go); + document.getElementById('cf-a').addEventListener('keydown', function(e){ if(e.key==='Enter')go(); }); + show(); +})();