diff --git a/frontend/js/phys9_ch1_widgets.js b/frontend/js/phys9_ch1_widgets.js index 3c2dfa2..1855115 100644 --- a/frontend/js/phys9_ch1_widgets.js +++ b/frontend/js/phys9_ch1_widgets.js @@ -89,7 +89,7 @@ function appendTo(secId, html){ div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; box.appendChild(div); - try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(box, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} return true; } @@ -202,7 +202,7 @@ function add_p4(){ s += '$a_x$='+ax.toFixed(1)+''; s += '$a_y$='+(-ay).toFixed(1)+''; document.getElementById('p4w-svg').innerHTML = s; - try { if(window.renderMathInElement) window.renderMathInElement(document.getElementById('p4-extra').parentNode); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(document.getElementById('p4-extra').parentNode, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} }; document.getElementById('p4w-a-r').addEventListener('input', upd); document.getElementById('p4w-an-r').addEventListener('input', upd); @@ -278,7 +278,7 @@ function add_p7(){ +'' +'
' +'$\\langle v\\rangle = (v_1 t_1 + v_2 t_2)/(t_1+t_2)$ = 13.3 м/с' - +'Ловушка: ($v_1+v_2)/2$ = 15.0 м/с — НЕВЕРНО' + +'Ловушка: $(v_1+v_2)/2$ = 15.0 м/с — НЕВЕРНО' +'
'; if(appendTo('p7', wgWrapper('p7-extra', 'CALC', 'Средняя скорость', 'Меняй $v$ и $t$ на двух участках. Сравни средневзвешенное и арифметическое.', body))){ const upd = ()=>{ diff --git a/frontend/js/phys9_ch2_widgets.js b/frontend/js/phys9_ch2_widgets.js index 20ba52b..d49deff 100644 --- a/frontend/js/phys9_ch2_widgets.js +++ b/frontend/js/phys9_ch2_widgets.js @@ -75,7 +75,7 @@ function appendTo(secId, html){ div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; box.appendChild(div); - try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(box, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} return true; } diff --git a/frontend/js/phys9_ch3_widgets.js b/frontend/js/phys9_ch3_widgets.js index 8c5bd31..7aa57f8 100644 --- a/frontend/js/phys9_ch3_widgets.js +++ b/frontend/js/phys9_ch3_widgets.js @@ -49,7 +49,7 @@ function appendTo(secId, html){ if(box.querySelector('.wg-phys9-extra-'+secId)) return false; const div = document.createElement('div'); div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; box.appendChild(div); - try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(box, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} return true; } diff --git a/frontend/js/phys9_ch4_widgets.js b/frontend/js/phys9_ch4_widgets.js index c791c4c..0217bc9 100644 --- a/frontend/js/phys9_ch4_widgets.js +++ b/frontend/js/phys9_ch4_widgets.js @@ -49,7 +49,7 @@ function appendTo(secId, html){ if(box.querySelector('.wg-phys9-extra-'+secId)) return false; const div = document.createElement('div'); div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; box.appendChild(div); - try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(box, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} return true; } diff --git a/frontend/js/phys9_ch5_widgets.js b/frontend/js/phys9_ch5_widgets.js index 88460be..073b271 100644 --- a/frontend/js/phys9_ch5_widgets.js +++ b/frontend/js/phys9_ch5_widgets.js @@ -11,7 +11,7 @@ function appendTo(secId, html){ if(box.querySelector('.wg-phys9-extra-'+secId)) return false; const div = document.createElement('div'); div.className = 'wg-phys9-extra-'+secId; div.innerHTML = html; box.appendChild(div); - try { if(window.renderMathInElement) window.renderMathInElement(box); } catch(e){} + try { if(window.renderMathInElement) window.renderMathInElement(box, { delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} return true; } function wireSubmit(id){ diff --git a/frontend/js/phys9_finals.js b/frontend/js/phys9_finals.js new file mode 100644 index 0000000..fc13612 --- /dev/null +++ b/frontend/js/phys9_finals.js @@ -0,0 +1,125 @@ +// phys9_finals.js — улучшение финалов 1-5 Физики 9: +// 1. Расширяет window.checkNum чтобы поддерживать сигнатуру (id, answer, unit, tol) +// (раньше legacy checkNum принимал только sec из POOLS — финалы не работали). +// 2. Считает решённые задачи каждого финала, рисует прогресс-бар. +// 3. При 100% — выдаёт XP + ачивку «Мастер главы N». +(function(){ +'use strict'; + +const FINAL_TASKS = {}; /* finalN → { total, ok: Set } */ +const ACHIEVED = new Set(); + +/* === Расширение checkNum === */ +const _origCheckNum = typeof window.checkNum === 'function' ? window.checkNum : null; +function patchedCheckNum(arg1, arg2, arg3, arg4){ + /* Финальная задача: arg1 = id (например 'fin1-q1'), arg2 = answer, arg3 = unit, arg4 = tol */ + if (typeof arg2 === 'number' && /^fin\d+-q\d+/.test(arg1)) { + const id = arg1; + const answer = arg2; + const tol = arg4 || Math.max(0.005, Math.abs(answer) * 0.03); + const inp = document.getElementById(id); + const fb = document.getElementById('fb-' + id); + if (!inp || !fb) return; + const val = (inp.value || '').trim().replace(',', '.'); + const num = parseFloat(val); + if (val === '' || isNaN(num)) { + fb.className = 'feedback fail show'; + fb.style.display = 'block'; + fb.innerHTML = 'Введи число.'; + return; + } + const ok = Math.abs(num - answer) <= tol; + if (ok) { + fb.className = 'feedback ok show'; + fb.style.display = 'block'; + fb.innerHTML = '✓ Верно! ' + (arg3 ? '(' + arg3 + ')' : ''); + inp.disabled = true; + const finalKey = id.match(/^fin(\d+)/)[1]; + const finalId = 'final' + finalKey; + if (!FINAL_TASKS[finalId]) FINAL_TASKS[finalId] = { total: 0, ok: new Set() }; + FINAL_TASKS[finalId].ok.add(id); + _updateFinalProgress(finalId); + try { if (window.addXp) window.addXp(8, 'phys9-fin-' + id); } catch(e){} + } else { + fb.className = 'feedback fail show'; + fb.style.display = 'block'; + fb.innerHTML = '✗ Не то. Перепроверь решение.'; + } + return; + } + /* Legacy путь — для POOLS секций */ + if (_origCheckNum) return _origCheckNum.apply(this, arguments); +} +window.checkNum = patchedCheckNum; + +/* === Прогресс-бар + ачивка === */ +function _ensureProgressBar(finalId){ + const box = document.getElementById(finalId + '-body'); + if (!box) return null; + let bar = box.querySelector('.phys9-fin-bar'); + if (bar) return bar; + /* Подсчёт общего количества задач в финале */ + const tasks = box.querySelectorAll('input[id^="' + finalId.replace('final','fin') + '-q"]'); + const total = tasks.length; + FINAL_TASKS[finalId] = FINAL_TASKS[finalId] || { total: total, ok: new Set() }; + FINAL_TASKS[finalId].total = total; + /* Вставляем бар как первый дочерний элемент в body */ + const wrap = document.createElement('div'); + wrap.className = 'phys9-fin-bar'; + wrap.style.cssText = 'margin:14px 0 18px;padding:14px 16px;background:linear-gradient(135deg,var(--sec-acc-soft,#dbeafe),var(--card,#fff));border:1.5px solid var(--sec-acc,#2563eb);border-radius:12px'; + wrap.innerHTML = '
' + + '
Финал главы — задач решено: 0 / ' + total + '
' + + '' + + '
' + + '
'; + /* Вставка перед первой task-card или в начало */ + const firstTask = box.querySelector('.task-card'); + if (firstTask) box.insertBefore(wrap, firstTask); + else box.appendChild(wrap); + return wrap; +} + +function _updateFinalProgress(finalId){ + const bar = _ensureProgressBar(finalId); + if (!bar) return; + const data = FINAL_TASKS[finalId]; + if (!data) return; + const pct = data.total > 0 ? Math.round(data.ok.size / data.total * 100) : 0; + const cnt = document.getElementById(finalId + '-cnt'); + const fill = document.getElementById(finalId + '-fill'); + const badge = document.getElementById(finalId + '-badge'); + if (cnt) cnt.textContent = data.ok.size; + if (fill) fill.style.width = pct + '%'; + if (data.total > 0 && data.ok.size === data.total && !ACHIEVED.has(finalId)) { + ACHIEVED.add(finalId); + if (badge) badge.style.display = 'inline-block'; + try { if (window.addXp) window.addXp(50, 'phys9-master-' + finalId); } catch(e){} + try { + localStorage.setItem('physics9_' + finalId + '_master', '1'); + const allDone = ['final1','final2','final3','final4','final5'].every(f => + localStorage.getItem('physics9_' + f + '_master') === '1'); + if (allDone && !localStorage.getItem('physics9_grandmaster')) { + localStorage.setItem('physics9_grandmaster', '1'); + if (window.addXp) window.addXp(150, 'phys9-grandmaster'); + alert('Поздравляем! Все 5 финалов глав сданы.\nАчивка: МАГИСТР ФИЗИКИ 9 (+150 XP)'); + } + } catch(e){} + } +} + +/* === Инициализация при открытии финала === */ +window.PHYS9_FINALS_INIT = function(finalId){ + _ensureProgressBar(finalId); + /* Восстановить состояние из disabled полей (если перезагрузка/возврат) */ + const box = document.getElementById(finalId + '-body'); + if (!box) return; + box.querySelectorAll('input[id^="' + finalId.replace('final','fin') + '-q"]').forEach(inp=>{ + if (inp.disabled) { + if (!FINAL_TASKS[finalId]) FINAL_TASKS[finalId] = { total: 0, ok: new Set() }; + FINAL_TASKS[finalId].ok.add(inp.id); + } + }); + _updateFinalProgress(finalId); +}; + +})(); diff --git a/frontend/textbooks/physics_9_ch1.html b/frontend/textbooks/physics_9_ch1.html index d69bf77..0322e61 100644 --- a/frontend/textbooks/physics_9_ch1.html +++ b/frontend/textbooks/physics_9_ch1.html @@ -17,6 +17,7 @@ +