feat(math6): курсовой финал на хабе + звание «Математик 6 класса»
Капстоун-бой из 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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)}
|
||||
</style>
|
||||
</head>
|
||||
@@ -230,6 +256,26 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="cf-wrap" id="cf-wrap">
|
||||
<div class="cf-head" id="cf-head">
|
||||
<div class="cf-hi"><svg viewBox="0 0 24 24"><polygon points="12 2 15 9 22 9 17 14 19 22 12 17 5 22 7 14 2 9 9 9"/></svg></div>
|
||||
<div class="cf-ht">
|
||||
<span class="cf-tag">Курсовой финал</span>
|
||||
<div class="cf-title">6 испытаний — по одному из каждой главы</div>
|
||||
<div class="cf-sub">Победи 5 из 6 и получи звание «Математик 6 класса» (+150 XP)</div>
|
||||
</div>
|
||||
<div class="cf-chev"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></div>
|
||||
</div>
|
||||
<div class="cf-body">
|
||||
<div class="cf-hp"><div class="cf-hp-fill" id="cf-hp" style="width:100%"></div></div>
|
||||
<div class="cf-meta"><span>Испытание <b id="cf-i">1</b> / 6</span><span>Пройдено: <b id="cf-s">0</b> / 6</span></div>
|
||||
<div class="cf-name" id="cf-name"></div>
|
||||
<div class="cf-q" id="cf-q"></div>
|
||||
<div class="cf-row"><input type="text" id="cf-a" class="cf-inp" placeholder="ответ"><button class="cf-btn" id="cf-go">Ответить</button></div>
|
||||
<div class="cf-fb" id="cf-fb"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ach-strip" id="ach-strip">
|
||||
<div class="ach-icon">
|
||||
<svg viewBox="0 0 24 24"><path d="M6 9H4l-1-3h18l-1 3h-2M6 9l1 6h10l1-6M6 9h12"/><path d="M9 21h6M12 15v6"/></svg>
|
||||
@@ -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?'<b>Победа!</b> Звание «Математик 6 класса» ваше!':'<b>Бой окончен.</b> Пройдено '+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();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user