diff --git a/frontend/textbooks/algebra_9_ch4.html b/frontend/textbooks/algebra_9_ch4.html
index bde041b..76df4ec 100644
--- a/frontend/textbooks/algebra_9_ch4.html
+++ b/frontend/textbooks/algebra_9_ch4.html
@@ -111,6 +111,40 @@ a{color:inherit;text-decoration:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
+.wg{background:linear-gradient(135deg,var(--card),var(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,var(--pri));border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
+.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
+.wg-badge{padding:4px 9px;background:var(--sec-acc,var(--pri));color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
+.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--sec-acc-d,var(--pri2));flex:1}
+.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);padding:9px 14px;border-radius:9px}
+.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace}
+.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
+.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
+.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
+.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
+.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
+.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
+.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
+.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
+.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
+.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
+.dnd-pool.col{flex-direction:column;align-items:stretch}
+.dnd-pool.col .dnd-chip{width:auto}
+.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
+.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
+.dnd-chip:active{cursor:grabbing}
+.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
+.dnd-chip.dragging{opacity:.28}
+.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
+.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
+.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
+.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
+.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
+.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
+.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
+.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
+.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
+.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
+
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
@@ -442,41 +476,646 @@ function wireReadBtn(paraId){
});
}
+function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
+function makeCard(kind, title, num, body){
+ const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
+ return '
';
+}
+function setupSorter(cfg){
+ const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
+ if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
+ pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
+ let armed = null;
+ function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML=''+it.html+' \xd7 '; attach(e,it.id); return e; }
+ function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
+ ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
+ function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
+ function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
+ attachBoxTaps(); render();
+ return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
+}
+
+/* Координатная плоскость в SVG */
+function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
+ const ux = (W - 2*pad) / (xmax - xmin);
+ const uy = (H - 2*pad) / (ymax - ymin);
+ const toX = v => pad + (v - xmin) * ux;
+ const toY = v => H - pad - (v - ymin) * uy;
+ let g = '';
+ g += '';
+ for (let x = Math.ceil(xmin); x <= xmax; x++){
+ g += ' ';
+ }
+ for (let y = Math.ceil(ymin); y <= ymax; y++){
+ g += ' ';
+ }
+ g += ' ';
+ const y0 = toY(0), x0 = toX(0);
+ g += ' ';
+ g += ' ';
+ g += 'x ';
+ g += 'y ';
+ g += '';
+ for (let x = Math.ceil(xmin); x <= xmax; x++){
+ if (x !== 0) g += ''+x+' ';
+ }
+ for (let y = Math.ceil(ymin); y <= ymax; y++){
+ if (y !== 0) g += ''+y+' ';
+ }
+ g += '0 ';
+ g += ' ';
+ return { content: g, toX, toY, ux, uy };
+}
+
+/* График функции y=f(x) — возвращает строку */
+function plotFunc(f, xmin, xmax, toX, toY, color, N){
+ N = N || 200;
+ let d = '';
+ let prevValid = false;
+ for (let i = 0; i <= N; i++){
+ const x = xmin + (xmax - xmin) * i / N;
+ let y;
+ try { y = f(x); } catch(e){ y = NaN; }
+ if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
+ d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
+ prevValid = true;
+ }
+ return ' ';
+}
+
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP14(){
- const root = document.getElementById('p14-body');
- root.innerHTML = `
-
-
-
-
Содержание параграфа «Числовая последовательность» будет добавлено в следующих обновлениях.
-
Раздел Phase 1.
-
-
` + secNav(null, 'p15') + readButton('p14');
- renderMath(root);
+ const box = document.getElementById('p14-body');
+ let html = '';
+
+ html += makeCard('theory', 'Определение', '14.1', `
+ Числовая последовательность — это функция, заданная на множестве натуральных чисел $\\mathbb{N}$.
+ Записывают: $a_1,\\ a_2,\\ a_3,\\ \\ldots,\\ a_n,\\ \\ldots$ — это члены последовательности .
+
+ $a_n$ — общий член (член с номером $n$);
+ $n$ — номер члена ($n \\in \\mathbb{N}$);
+ $a_1$ — первый член, $a_2$ — второй, и т. д.
+
+ Кратко последовательность обозначают $(a_n)$. Это значит: $a \\colon \\mathbb{N} \\to \\mathbb{R}$.
`);
+
+ html += makeCard('rule', 'Три способа задания', '14.2', `
+ Последовательность можно задать одним из трёх способов:
+
+ Формулой $n$-го члена: $a_n = 2n + 1$ → последовательность $3, 5, 7, 9, \\ldots$ Чтобы найти любой член, подставь его номер $n$.
+ Рекуррентно: задают $a_1$ и формулу, выражающую следующий член через предыдущий. Например: $a_1 = 1,\\ a_{n+1} = a_n + 3$ → $1, 4, 7, 10, \\ldots$
+ Перечислением (словесно): $2, 4, 8, 16, \\ldots$ — если из записи понятен алгоритм (удвоение).
+ `);
+
+ html += makeCard('example', 'Конечные/бесконечные · возрастающие/убывающие', '14.3', `
+ Конечная последовательность: $1, 2, 3, \\ldots, 100$ — заканчивается на $a_{100}$.
+ Бесконечная : $1, 2, 4, 8, \\ldots$ — продолжается без конца.
+ Последовательность $(a_n)$ — возрастающая , если $a_{n+1} > a_n$ для всех $n$.
+ Убывающая — если $a_{n+1} < a_n$ для всех $n$.
+ Примеры:
+
+ $a_n = -n$ → $-1, -2, -3, \\ldots$ — убывающая;
+ $a_n = \\dfrac{1}{n}$ → $1, \\dfrac{1}{2}, \\dfrac{1}{3}, \\ldots$ — убывающая, но положительная;
+ $a_n = n^2$ → $1, 4, 9, 16, \\ldots$ — возрастающая;
+ $a_n = (-1)^n$ → $-1, 1, -1, 1, \\ldots$ — ни возрастает, ни убывает.
+ `);
+
+ /* INTERACTIVE 1 — конструктор последовательности */
+ html += `
+
+
Выбери одну из пяти классических последовательностей и посмотри её точечную диаграмму $(n, a_n)$ на координатной плоскости.
+
+ Тип №1 / 5
+
+
+
+
+
+
+
+
`;
+
+ /* INTERACTIVE 2 — вычисли a_n */
+ html += `
+
+
Подставь номер $n$ в формулу и вычисли значение $a_n$. Ответ — целое число.
+
Задача: 1 / 6 · Очки: 0
+
+
+ $a_n$ =
+
+ Проверить
+
+
+
`;
+
+ /* INTERACTIVE 3 — возрастает/убывает/нет */
+ html += `
+
+
Дана формула $a_n$. Определи: возрастает, убывает или ни то ни другое.
+
Задача: 1 / 6 · Очки: 0
+
+
+ Возрастает
+ Убывает
+ Ни то ни другое
+
+
+
`;
+
+ /* INTERACTIVE 4 — сортер: рекуррентно или формулой */
+ html += `
+
+
Перетащи каждый способ задания в нужный ящик.
+
+
+
Проверить Сбросить
+
+
`;
+
+ box.innerHTML = html + secNav(null, 'p15') + readButton('p14');
+ renderMath(box);
+
+ /* ===== IV1 wiring ===== */
+ (function(){
+ const types = [
+ { tex:'a_n = 2n - 1', f:n=>2*n-1, seq:'1, 3, 5, 7, 9, \\ldots', mono:'возрастает' },
+ { tex:'a_n = n^2', f:n=>n*n, seq:'1, 4, 9, 16, 25, \\ldots', mono:'возрастает' },
+ { tex:'a_n = (-1)^n', f:n=>Math.pow(-1,n), seq:'-1, 1, -1, 1, \\ldots', mono:'ни возрастает, ни убывает' },
+ { tex:'a_n = \\dfrac{1}{n}', f:n=>1/n, seq:'1, \\dfrac{1}{2}, \\dfrac{1}{3}, \\dfrac{1}{4}, \\ldots', mono:'убывает' },
+ { tex:'a_n = n(n+1)', f:n=>n*(n+1), seq:'2, 6, 12, 20, 30, \\ldots', mono:'возрастает' }
+ ];
+ const svg = document.getElementById('p14-iv1-svg');
+ const fnSl = document.getElementById('p14-iv1-fn');
+ const ti = document.getElementById('p14-iv1-ti');
+ const formula = document.getElementById('p14-iv1-formula');
+ const out = document.getElementById('p14-iv1-out');
+ const mono = document.getElementById('p14-iv1-mono');
+ let bumped = false;
+
+ function redraw(){
+ const idx = (+fnSl.value)-1;
+ const t = types[idx];
+ ti.textContent = (idx+1);
+
+ // вычисляем 10 точек
+ const pts = [];
+ for (let n=1;n<=10;n++){ const y=t.f(n); if (isFinite(y)) pts.push([n,y]); }
+ let ymin = Math.min(...pts.map(p=>p[1]), 0);
+ let ymax = Math.max(...pts.map(p=>p[1]), 0);
+ // ограничим, чтобы график не «улетал»
+ if (ymax > 30) ymax = 30;
+ if (ymin < -5) ymin = -5;
+ const pad = Math.max(1, (ymax-ymin)*0.1);
+ ymin = Math.floor(ymin - pad);
+ ymax = Math.ceil(ymax + pad);
+
+ const ax = axes2D(420, 280, 32, 0, 11, ymin, ymax);
+ let g = ax.content;
+ // точки + соединяющие отрезки (пунктир)
+ let path = '';
+ pts.forEach((p,i)=>{
+ const x = ax.toX(p[0]); const y = ax.toY(Math.min(ymax,Math.max(ymin,p[1])));
+ path += (i===0?'M':' L')+x.toFixed(1)+','+y.toFixed(1);
+ });
+ g += ' ';
+ pts.forEach(p=>{
+ const x = ax.toX(p[0]); const y = ax.toY(Math.min(ymax,Math.max(ymin,p[1])));
+ g += ' ';
+ });
+ svg.innerHTML = g;
+
+ formula.innerHTML = '$' + t.tex + '$';
+ out.innerHTML = 'Первые 10 членов: $' + t.seq + '$';
+ mono.innerHTML = 'Эта последовательность ' + t.mono + ' ';
+ renderMath(formula); renderMath(out); renderMath(mono);
+ if (!bumped){ bumped = true; bumpProgress('p14', 15); addXp(10,'p14-iv1'); }
+ }
+ fnSl.addEventListener('input', redraw);
+ redraw();
+ })();
+
+ /* ===== IV2 wiring ===== */
+ (function(){
+ const items = [
+ { q:'$a_n = 3n + 1$, найти $a_5$', ans: 16 },
+ { q:'$a_n = n^2 - 2$, найти $a_4$', ans: 14 },
+ { q:'$a_n = 2^n$, найти $a_6$', ans: 64 },
+ { q:'$a_n = \\dfrac{6}{n}$, найти $a_3$', ans: 2 },
+ { q:'$a_n = (-1)^n \\cdot n$, найти $a_7$', ans: -7 },
+ { q:'$a_n = n^3 - n$, найти $a_3$', ans: 24 }
+ ];
+ let i = 0, sc = 0;
+ const idxEl = document.getElementById('p14-iv2-idx');
+ const scEl = document.getElementById('p14-iv2-sc');
+ const qEl = document.getElementById('p14-iv2-q');
+ const inp = document.getElementById('p14-iv2-ans');
+ const btn = document.getElementById('p14-iv2-go');
+ const fb = document.getElementById('p14-iv2-fb');
+ let bumped = false;
+ function render(){
+ idxEl.textContent = Math.min(i+1, items.length);
+ scEl.textContent = sc;
+ if (i >= items.length){
+ qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length;
+ inp.disabled = true; btn.disabled = true;
+ inp.style.opacity = .5; btn.style.opacity = .5;
+ if (!bumped){ bumped = true; bumpProgress('p14', 15); addXp(10,'p14-iv2'); }
+ return;
+ }
+ qEl.innerHTML = items[i].q;
+ inp.value = ''; fb.style.display = 'none';
+ renderMath(qEl);
+ }
+ btn.addEventListener('click', ()=>{
+ if (i >= items.length) return;
+ const v = +inp.value;
+ const it = items[i];
+ const ok = (v === it.ans);
+ if (ok) sc++;
+ feedback(fb, ok, ok ? '✓ Верно. $a_n = ' + it.ans + '$' : '✗ Неверно. Правильный ответ: $a_n = ' + it.ans + '$');
+ i++;
+ setTimeout(render, 1000);
+ });
+ inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); btn.click(); } });
+ render();
+ })();
+
+ /* ===== IV3 wiring ===== */
+ (function(){
+ const items = [
+ { q:'$a_n = 2n$', ans:'up', hint:'$a_{n+1} - a_n = 2 > 0$.' },
+ { q:'$a_n = 10 - n$', ans:'dn', hint:'$a_{n+1} - a_n = -1 < 0$.' },
+ { q:'$a_n = (-1)^n$', ans:'no', hint:'Чередуется $-1, 1, -1, 1, \\ldots$' },
+ { q:'$a_n = n^2$', ans:'up', hint:'$1, 4, 9, 16, \\ldots$ — растёт.' },
+ { q:'$a_n = \\dfrac{1}{n}$', ans:'dn', hint:'$1, \\tfrac{1}{2}, \\tfrac{1}{3}, \\ldots$ — убывает.' },
+ { q:'$a_n = n - n^2$', ans:'dn', hint:'$0, -2, -6, -12, \\ldots$ — убывает с $n=1$.' }
+ ];
+ let i = 0, sc = 0;
+ const idxEl = document.getElementById('p14-iv3-idx');
+ const scEl = document.getElementById('p14-iv3-sc');
+ const qEl = document.getElementById('p14-iv3-q');
+ const fb = document.getElementById('p14-iv3-fb');
+ const upB = document.getElementById('p14-iv3-up');
+ const dnB = document.getElementById('p14-iv3-dn');
+ const noB = document.getElementById('p14-iv3-no');
+ let bumped = false;
+ function render(){
+ idxEl.textContent = Math.min(i+1, items.length);
+ scEl.textContent = sc;
+ if (i >= items.length){
+ qEl.innerHTML = 'Готово! Результат: ' + sc + ' / ' + items.length;
+ [upB,dnB,noB].forEach(b=>{ b.disabled=true; b.style.opacity=.5; });
+ if (!bumped){ bumped = true; bumpProgress('p14', 25); addXp(15,'p14-iv3'); }
+ return;
+ }
+ qEl.innerHTML = items[i].q;
+ fb.style.display = 'none';
+ renderMath(qEl);
+ }
+ function answer(v){
+ if (i >= items.length) return;
+ const it = items[i];
+ const ok = (v === it.ans);
+ if (ok) sc++;
+ feedback(fb, ok, (ok?'✓ Верно. ':'✗ Неверно. ') + it.hint);
+ i++;
+ setTimeout(render, 1000);
+ }
+ upB.addEventListener('click', ()=>answer('up'));
+ dnB.addEventListener('click', ()=>answer('dn'));
+ noB.addEventListener('click', ()=>answer('no'));
+ render();
+ })();
+
+ /* ===== IV4 wiring — DnD ===== */
+ (function(){
+ const items = [
+ { id:'a', html:'$a_n = 5n - 3$', cat:'fml' },
+ { id:'b', html:'$a_1 = 2,\\ a_{n+1} = a_n + 5$', cat:'rec' },
+ { id:'c', html:'$a_n = n^2$', cat:'fml' },
+ { id:'d', html:'$a_1 = 1,\\ a_{n+1} = 2 a_n$', cat:'rec' },
+ { id:'e', html:'$a_n = \\dfrac{n}{n+1}$', cat:'fml' },
+ { id:'f', html:'$a_1 = 3,\\ a_{n+1} = a_n - 2$', cat:'rec' }
+ ];
+ const sorter = setupSorter({
+ poolId: 'p14-iv4-pool',
+ scopeSelector: '#p14-iv4',
+ cats: ['rec','fml'],
+ items: items
+ });
+ let bumped = false;
+ document.getElementById('p14-iv4-check').addEventListener('click', ()=>{
+ const fb = document.getElementById('p14-iv4-fb');
+ const total = items.length;
+ let correct = 0, placed = 0;
+ items.forEach(it=>{ if(sorter.placed[it.id]){ placed++; if(sorter.placed[it.id]===it.cat) correct++; } });
+ if (placed < total){
+ feedback(fb, false, 'Размещены не все: ' + placed + ' / ' + total + '.');
+ return;
+ }
+ const ok = (correct === total);
+ feedback(fb, ok, ok ? '✓ Все верно! ' + correct + ' / ' + total : '✗ Правильно: ' + correct + ' / ' + total);
+ if (ok && !bumped){ bumped = true; bumpProgress('p14', 25); addXp(15,'p14-iv4'); }
+ });
+ document.getElementById('p14-iv4-reset').addEventListener('click', ()=>{
+ sorter.reset();
+ const fb = document.getElementById('p14-iv4-fb'); fb.style.display='none';
+ });
+ })();
+
wireReadBtn('p14');
}
function buildP15(){
- const root = document.getElementById('p15-body');
- root.innerHTML = `
-
-