feat(textbooks): Wave Depth — 4 прокачанных интерактива (+474 строки)
1. §1 «Извлечение в столбик» — пошаговая анимация - Поле ввода числа + пресеты 1296/2916/7744 - Async-функция clStart() рендерит классическое 'деление в столбик' - JetBrains Mono шрифт, подсветка текущей грани цветом секции - Поясняющий текст для каждого шага рядом - При остатке 0: confetti + 15 XP + ачивка 'col-root' - Для нецелых корней — корректно показывает остаток 2. §4 «Сравнение через квадраты» — визуальное доказательство - 5 пар: 3√2 vs 2√3, 4√3 vs 3√5, √17 vs 4, ... - SVG с двумя анимированно растущими квадратами (transform scale 0→1, spring) - Победитель — бейдж в верхней части - Под квадратами: (3√2)² = 18 > 12 = (2√3)² 3. §5 «Эйлеровы диаграммы» — альтернатива линейной визуализации - 4 слайдера для границ A и B - Два эллипса (pink/blue) с пересечением - Режимы: 'Показать ∪' (золотой контур), '∩' (зелёная штриховка), 'Оба' - Дополняет существующую линейную визуализацию 4. §6 «Решатель систем 3+ неравенств» — расширен с 2 до 5 - Динамический контейнер #sys-list с массивом _sysRows - Кнопка '+ Добавить неравенство' (до 5) - Кнопка '×' удаляет (кроме первой) - SVG-прямая динамически масштабируется под N строк - Совместимость с sysMode/solveLin сохранена
This commit is contained in:
@@ -660,6 +660,26 @@ input,select,textarea{font-family:inherit}
|
||||
|
||||
/* Sound indicator */
|
||||
#sound-muted-hint{display:none}
|
||||
|
||||
/* ═══════════════════════════════════════════════
|
||||
WAVE DEPTH — 4 new widgets
|
||||
═══════════════════════════════════════════════ */
|
||||
|
||||
/* Widget 1: Column root extraction */
|
||||
.cl-workspace{font-family:'JetBrains Mono',monospace;font-size:1.05rem;background:var(--card);border:1px solid var(--border);border-radius:11px;padding:18px 22px;margin-top:14px;line-height:1.7;min-height:120px;white-space:pre}
|
||||
.cl-workspace .cl-active{background:var(--sec-acc-soft,#fce7f3);padding:0 4px;border-radius:3px;font-weight:700;color:var(--sec-acc,var(--pri))}
|
||||
.cl-workspace .cl-result{color:var(--ok);font-weight:800}
|
||||
.cl-explain{margin-top:10px;padding:10px 14px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:9px;font-size:.92rem;line-height:1.5;min-height:60px}
|
||||
.cl-final-badge{display:inline-block;padding:8px 16px;margin:14px auto;background:linear-gradient(135deg,var(--ok),#34d399);color:#fff;border-radius:99px;font-weight:800;font-size:1.1rem;box-shadow:0 6px 18px rgba(16,185,129,.3);animation:simpPop .5s ease}
|
||||
|
||||
/* Widget 2: Square comparison SVG */
|
||||
.sq-comp-conclusion{margin-top:10px;text-align:center;font-size:1rem;min-height:36px;padding:8px 12px;border-radius:8px;background:var(--card);border:1px solid var(--border)}
|
||||
|
||||
/* Widget 3: Euler-Venn diagrams */
|
||||
.ev-result{margin-top:10px;text-align:center;font-size:.95rem;padding:8px 12px;background:var(--card);border-radius:9px;border:1px solid var(--border);min-height:36px}
|
||||
|
||||
/* Widget 4: Multi-row system solver */
|
||||
.sys-row-card{padding:10px;background:var(--card);border-radius:9px;border:1.5px solid var(--border);margin-bottom:8px}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
@@ -1676,6 +1696,19 @@ function buildP1(){
|
||||
</details>
|
||||
`)}
|
||||
|
||||
${widget('Извлечение в столбик — пошаговая анимация', 'STEPS', 'Введите положительное число (точный квадрат до 10000). Нажмите «Извлечь по шагам» — увидите классический алгоритм.', `
|
||||
<div class="row" style="justify-content:center;gap:10px;flex-wrap:wrap">
|
||||
<span class="lab">Число под корнем:</span>
|
||||
<input id="cl-n" class="inp num" type="number" value="5184" min="1" max="999999" step="1" style="width:100px;font-size:1.1rem">
|
||||
<button class="btn primary" onclick="clStart()">Извлечь по шагам</button>
|
||||
<button class="btn" onclick="clPreset(1296)">1296</button>
|
||||
<button class="btn" onclick="clPreset(2916)">2916</button>
|
||||
<button class="btn" onclick="clPreset(7744)">7744</button>
|
||||
</div>
|
||||
<div id="cl-workspace" class="cl-workspace"></div>
|
||||
<div id="cl-explain" class="cl-explain"></div>
|
||||
`)}
|
||||
|
||||
${widget('Игра «Таблица квадратов 10–99»', 'GAME', 'Показано число — выберите его квадратный корень. Точность важнее скорости, но скорость даёт бонус. Лучший результат сохраняется!', `
|
||||
<div class="score-display">
|
||||
<div>Раунд: <b id="sq-round">1</b>/10</div>
|
||||
@@ -3235,6 +3268,20 @@ function buildP4(){
|
||||
<div id="comp-fb" class="feedback"></div>
|
||||
`)}
|
||||
|
||||
${widget('Сравнение через возведение в квадрат', 'VISUAL', 'Чтобы сравнить два выражения с корнями — возведи их в квадрат. У большего выражения квадрат больше.', `
|
||||
<div class="row" style="justify-content:center;gap:18px;font-family:'JetBrains Mono',monospace;font-size:1.3rem;margin-bottom:14px">
|
||||
<span id="sq-a-expr" style="color:var(--acc2);font-weight:700">3√2</span>
|
||||
<span style="color:var(--muted)">vs</span>
|
||||
<span id="sq-b-expr" style="color:var(--pri2);font-weight:700">2√3</span>
|
||||
</div>
|
||||
<svg id="sq-svg" viewBox="0 0 600 220" style="width:100%;max-width:560px;display:block;margin:0 auto"></svg>
|
||||
<div class="row-c" style="margin-top:12px">
|
||||
<button class="btn primary" onclick="sqAnimate()">Возвести в квадрат и сравнить</button>
|
||||
<button class="btn" onclick="sqNext()">Другая пара</button>
|
||||
</div>
|
||||
<div id="sq-conclusion" class="sq-comp-conclusion"></div>
|
||||
`)}
|
||||
|
||||
${widget('Тренажёр «Упрости»', 'GAME', 'Введите упрощённое выражение в виде a√b. Например, для √72 ответ: 6√2.', `
|
||||
<div id="simp4-card" style="padding:14px;background:var(--card);border-radius:9px;text-align:center">
|
||||
<div class="lab">Упростите:</div>
|
||||
@@ -3610,6 +3657,28 @@ function buildP5(){
|
||||
</div>
|
||||
`)}
|
||||
|
||||
${widget('Эйлеровы диаграммы для ∪ и ∩', 'VISUAL', 'Альтернативная визуализация: каждый промежуток — капсула (по точкам). Пересечение — общая часть. Объединение — оба вместе.', `
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px">
|
||||
<div>
|
||||
<div class="lab">A = [<span id="ev-a-lo">2</span>; <span id="ev-a-hi">6</span>]</div>
|
||||
<input id="ev-a-lo-s" type="range" class="slider" min="-5" max="10" step="0.5" value="2">
|
||||
<input id="ev-a-hi-s" type="range" class="slider" min="-5" max="10" step="0.5" value="6" style="margin-top:4px">
|
||||
</div>
|
||||
<div>
|
||||
<div class="lab">B = [<span id="ev-b-lo">4</span>; <span id="ev-b-hi">9</span>]</div>
|
||||
<input id="ev-b-lo-s" type="range" class="slider" min="-5" max="10" step="0.5" value="4">
|
||||
<input id="ev-b-hi-s" type="range" class="slider" min="-5" max="10" step="0.5" value="9" style="margin-top:4px">
|
||||
</div>
|
||||
</div>
|
||||
<svg id="ev-svg" viewBox="0 0 520 280" style="width:100%;max-width:560px;display:block;margin:0 auto"></svg>
|
||||
<div class="row-c" style="margin-top:12px">
|
||||
<button class="btn" id="ev-show-union" onclick="evMode('union')">Показать A ∪ B</button>
|
||||
<button class="btn" id="ev-show-inter" onclick="evMode('inter')">Показать A ∩ B</button>
|
||||
<button class="btn" onclick="evMode('both')">Оба</button>
|
||||
</div>
|
||||
<div id="ev-result" class="ev-result"></div>
|
||||
`)}
|
||||
|
||||
${makeCard('example','Примеры',null,`
|
||||
<ul>
|
||||
<li>$(-\\infty; 3) \\cup [3; 7] = (-\\infty; 7]$</li>
|
||||
@@ -3983,34 +4052,10 @@ function buildP6(){
|
||||
<p><b>Пример 2.</b> Двойное неравенство $-3 < 2x + 1 \\leq 7$. Решим как систему: $-3 < 2x + 1$ и $2x + 1 \\leq 7$. Получаем $x > -2$ и $x \\leq 3$, то есть $x \\in (-2; 3]$.</p>
|
||||
`)}
|
||||
|
||||
${widget('Решатель системы линейных неравенств', 'CALC', 'Введите две формы $ax+b$ ≷ $c$ для двух неравенств. Получите пересечение.', `
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px">
|
||||
<div style="padding:12px;background:var(--card);border:1.5px solid var(--pri);border-radius:9px">
|
||||
<div class="lab">Неравенство 1</div>
|
||||
<div class="row" style="margin-top:6px">
|
||||
<input id="s1-a" class="inp num" type="number" value="3" style="width:50px">
|
||||
<span class="lab">x +</span>
|
||||
<input id="s1-b" class="inp num" type="number" value="6" style="width:50px">
|
||||
<select id="s1-r" class="inp" style="width:auto">
|
||||
<option>≥</option><option>></option><option>≤</option><option><</option>
|
||||
</select>
|
||||
<input id="s1-c" class="inp num" type="number" value="0" style="width:50px">
|
||||
</div>
|
||||
<div id="s1-out" class="chip" style="margin-top:10px">x ≥ -2</div>
|
||||
</div>
|
||||
<div style="padding:12px;background:var(--card);border:1.5px solid var(--acc);border-radius:9px">
|
||||
<div class="lab">Неравенство 2</div>
|
||||
<div class="row" style="margin-top:6px">
|
||||
<input id="s2-a" class="inp num" type="number" value="-2" style="width:50px">
|
||||
<span class="lab">x +</span>
|
||||
<input id="s2-b" class="inp num" type="number" value="5" style="width:50px">
|
||||
<select id="s2-r" class="inp" style="width:auto">
|
||||
<option>></option><option>≥</option><option>≤</option><option><</option>
|
||||
</select>
|
||||
<input id="s2-c" class="inp num" type="number" value="1" style="width:50px">
|
||||
</div>
|
||||
<div id="s2-out" class="chip acc" style="margin-top:10px">x < 2</div>
|
||||
</div>
|
||||
${widget('Решатель системы линейных неравенств', 'CALC', 'Введите до 5 неравенств $ax+b$ ≷ $c$. Используйте кнопки «+ Добавить» / «×» для управления строками.', `
|
||||
<div id="sys-list"></div>
|
||||
<div class="row-c" style="margin-bottom:14px">
|
||||
<button class="btn ok" onclick="sysAddRow(1,0,'≥',0)">+ Добавить неравенство</button>
|
||||
</div>
|
||||
<div id="sys-line" style="position:relative;height:170px;background:var(--card);border:1px solid var(--border);border-radius:9px"></div>
|
||||
<div class="row-c" style="margin-top:12px">
|
||||
@@ -4095,14 +4140,69 @@ function buildP6(){
|
||||
setTimeout(()=>{ initSysSolver(); initDoubleIneq(); initFindInt(); }, 50);
|
||||
}
|
||||
|
||||
/* ──── Sys Solver ──── */
|
||||
/* ──── Sys Solver (multi-row) ──── */
|
||||
let sysCurMode = 'sys';
|
||||
let _sysRows = [];
|
||||
let _sysDefaults = [{a:3,b:6,op:'≥',c:0},{a:-2,b:5,op:'>',c:1}];
|
||||
|
||||
function initSysSolver(){
|
||||
['s1-a','s1-b','s1-c','s1-r','s2-a','s2-b','s2-c','s2-r'].forEach(id=>{
|
||||
const e = document.getElementById(id);
|
||||
if(e) e.addEventListener('input', sysUpdate);
|
||||
if(e && e.tagName === 'SELECT') e.addEventListener('change', sysUpdate);
|
||||
_sysRows = [];
|
||||
_sysDefaults.forEach((d,i) => { _sysRows.push({id:'sr'+(i+1), a:d.a, b:d.b, op:d.op, c:d.c}); });
|
||||
_sysRebuildHTML();
|
||||
sysUpdate();
|
||||
}
|
||||
|
||||
function sysAddRow(a, b, op, c){
|
||||
if(_sysRows.length >= 5) return;
|
||||
const id = 'sr' + (_sysRows.length + 1);
|
||||
_sysRows.push({id, a:a||1, b:b||0, op:op||'≥', c:c||0});
|
||||
_sysRebuildHTML();
|
||||
sysUpdate();
|
||||
}
|
||||
|
||||
function _sysRebuildHTML(){
|
||||
const list = document.getElementById('sys-list');
|
||||
if(!list) return;
|
||||
const COLORS = ['var(--pri)','var(--acc)','var(--ok)','var(--warn)','var(--sec-acc,#9333ea)'];
|
||||
list.innerHTML = _sysRows.map((r,i)=>`
|
||||
<div class="sys-row-card" style="border-color:${COLORS[i%COLORS.length]}">
|
||||
<div class="lab" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
||||
<span>Неравенство ${i+1}</span>
|
||||
${i > 0 ? `<button class="btn small" onclick="sysRemoveRow(${i})">×</button>` : ''}
|
||||
</div>
|
||||
<div class="row" style="margin-top:2px">
|
||||
<input id="${r.id}-a" class="inp num" type="number" value="${r.a}" style="width:50px">
|
||||
<span class="lab">x +</span>
|
||||
<input id="${r.id}-b" class="inp num" type="number" value="${r.b}" style="width:50px">
|
||||
<select id="${r.id}-r" class="inp" style="width:auto">
|
||||
<option${r.op==='>' ? ' selected' : ''}>></option>
|
||||
<option${r.op==='≥' ? ' selected' : ''}>≥</option>
|
||||
<option${r.op==='≤' ? ' selected' : ''}>≤</option>
|
||||
<option${r.op==='<' ? ' selected' : ''}><</option>
|
||||
</select>
|
||||
<input id="${r.id}-c" class="inp num" type="number" value="${r.c}" style="width:50px">
|
||||
</div>
|
||||
<div id="${r.id}-out" class="chip" style="margin-top:8px;border-color:${COLORS[i%COLORS.length]};color:${COLORS[i%COLORS.length]}">x ?</div>
|
||||
</div>
|
||||
`).join('');
|
||||
_sysRebindHandlers();
|
||||
}
|
||||
|
||||
function _sysRebindHandlers(){
|
||||
_sysRows.forEach(r => {
|
||||
[r.id+'-a', r.id+'-b', r.id+'-c', r.id+'-r'].forEach(id => {
|
||||
const e = document.getElementById(id);
|
||||
if(e){ e.addEventListener('input', sysUpdate); e.addEventListener('change', sysUpdate); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sysRemoveRow(i){
|
||||
if(_sysRows.length <= 1) return;
|
||||
_sysRows.splice(i, 1);
|
||||
// re-assign ids
|
||||
_sysRows = _sysRows.map((r,idx) => ({...r, id:'sr'+(idx+1)}));
|
||||
_sysRebuildHTML();
|
||||
sysUpdate();
|
||||
}
|
||||
function solveLin(a, b, op, c){
|
||||
@@ -4121,81 +4221,97 @@ function solveLin(a, b, op, c){
|
||||
return { bound, strict, dir };
|
||||
}
|
||||
function sysUpdate(){
|
||||
const s1 = solveLin(+document.getElementById('s1-a').value, +document.getElementById('s1-b').value, document.getElementById('s1-r').value, +document.getElementById('s1-c').value);
|
||||
const s2 = solveLin(+document.getElementById('s2-a').value, +document.getElementById('s2-b').value, document.getElementById('s2-r').value, +document.getElementById('s2-c').value);
|
||||
function describe(s){
|
||||
const rel = s.dir + (s.strict ? '' : '=');
|
||||
return 'x ' + (s.dir === '>' ? (s.strict ? '>' : '≥') : (s.strict ? '<' : '≤')) + ' ' + s.bound;
|
||||
if(!_sysRows.length) return;
|
||||
function getRowVal(r){
|
||||
const aEl = document.getElementById(r.id+'-a');
|
||||
const bEl = document.getElementById(r.id+'-b');
|
||||
const cEl = document.getElementById(r.id+'-c');
|
||||
const rEl = document.getElementById(r.id+'-r');
|
||||
if(!aEl) return null;
|
||||
const op = rEl ? rEl.value : '≥';
|
||||
// handle < being stored as <
|
||||
const opClean = op === '<' ? '<' : op;
|
||||
return solveLin(+aEl.value||0, +bEl.value||0, opClean, +cEl.value||0);
|
||||
}
|
||||
document.getElementById('s1-out').innerHTML = describe(s1);
|
||||
document.getElementById('s2-out').innerHTML = describe(s2);
|
||||
// intersect or union
|
||||
function toInterval(s){
|
||||
function describe(s){
|
||||
return 'x ' + (s.dir === '>' ? (s.strict ? '>' : '≥') : (s.strict ? '<' : '≤')) + ' ' + (Number.isFinite(s.bound) ? +s.bound.toFixed(4) : s.bound);
|
||||
}
|
||||
function toIntervalSys(s){
|
||||
if(s.dir === '>') return { l:s.bound, r:Infinity, lOp:s.strict, rOp:true };
|
||||
else return { l:-Infinity, r:s.bound, lOp:true, rOp:s.strict };
|
||||
}
|
||||
const A = toInterval(s1), B = toInterval(s2);
|
||||
// intersection of two intervals
|
||||
let inter = null;
|
||||
const lo = Math.max(A.l, B.l);
|
||||
const hi = Math.min(A.r, B.r);
|
||||
const loOp = (A.l > B.l) ? A.lOp : (A.l < B.l) ? B.lOp : (A.lOp || B.lOp);
|
||||
const hiOp = (A.r < B.r) ? A.rOp : (A.r > B.r) ? B.rOp : (A.rOp || B.rOp);
|
||||
if(lo < hi || (lo === hi && !loOp && !hiOp)) inter = {l:lo, r:hi, lOp:loOp, rOp:hiOp};
|
||||
function intersectTwo(A, B){
|
||||
const lo = Math.max(A.l, B.l);
|
||||
const hi = Math.min(A.r, B.r);
|
||||
const loOp = (A.l > B.l) ? A.lOp : (A.l < B.l) ? B.lOp : (A.lOp || B.lOp);
|
||||
const hiOp = (A.r < B.r) ? A.rOp : (A.r > B.r) ? B.rOp : (A.rOp || B.rOp);
|
||||
if(lo < hi || (lo === hi && !loOp && !hiOp)) return {l:lo, r:hi, lOp:loOp, rOp:hiOp};
|
||||
return null;
|
||||
}
|
||||
const COLORS = ['#e91e63','#03a9f4','#10b981','#f59e0b','#9333ea'];
|
||||
const intervals = [];
|
||||
_sysRows.forEach((r) => {
|
||||
const s = getRowVal(r);
|
||||
if(!s || !Number.isFinite(s.bound)) return;
|
||||
const outEl = document.getElementById(r.id+'-out');
|
||||
if(outEl) outEl.textContent = describe(s);
|
||||
intervals.push(toIntervalSys(s));
|
||||
});
|
||||
let inter = intervals.length ? intervals[0] : null;
|
||||
for(let i = 1; i < intervals.length; i++) inter = inter ? intersectTwo(inter, intervals[i]) : null;
|
||||
// visualize
|
||||
const vis = document.getElementById('sys-line');
|
||||
if(!vis) return;
|
||||
vis.innerHTML='';
|
||||
const VLO = -8, VHI = 12;
|
||||
vis.appendChild(el('div',{style:'position:absolute;top:140px;left:3%;right:3%;height:2px;background:var(--text)'}));
|
||||
const axisY = 20 + _sysRows.length * 25 + 20;
|
||||
vis.style.height = (axisY + 36) + 'px';
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:${axisY}px;left:3%;right:3%;height:2px;background:var(--text)`}));
|
||||
for(let i = VLO; i <= VHI; i++){
|
||||
const x = 3 + (i-VLO)/(VHI-VLO)*94;
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:152px;left:${x}%;transform:translateX(-50%);font-size:.72rem;color:var(--muted);font-family:'JetBrains Mono',monospace`}, ''+i));
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:${axisY+12}px;left:${x}%;transform:translateX(-50%);font-size:.72rem;color:var(--muted);font-family:'JetBrains Mono',monospace`}, ''+i));
|
||||
}
|
||||
function drawInt(y, iv, col, lbl){
|
||||
if(!iv) return;
|
||||
const l = iv.l === -Infinity ? VLO : iv.l;
|
||||
const r = iv.r === Infinity ? VHI : iv.r;
|
||||
if(l > VHI || r < VLO) return;
|
||||
const x1 = 3 + Math.max(VLO, l - VLO) / (VHI-VLO) * 94 - (VLO < 0 ? VLO/(VHI-VLO)*94 : 0);
|
||||
const xL = 3 + (Math.max(VLO,l) - VLO)/(VHI-VLO)*94;
|
||||
const xR = 3 + (Math.min(VHI,r) - VLO)/(VHI-VLO)*94;
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:${y}px;left:${xL}%;width:${xR-xL}%;height:10px;background:${col};border-radius:5px`}));
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:${y}px;left:${xL}%;width:${Math.max(0,xR-xL)}%;height:10px;background:${col};border-radius:5px`}));
|
||||
if(iv.l !== -Infinity) vis.appendChild(el('div',{style:`position:absolute;top:${y-2}px;left:${xL}%;width:14px;height:14px;border-radius:50%;background:${iv.lOp?'var(--card)':col};border:2.5px solid ${col};transform:translateX(-50%)`}));
|
||||
if(iv.r !== Infinity) vis.appendChild(el('div',{style:`position:absolute;top:${y-2}px;left:${xR}%;width:14px;height:14px;border-radius:50%;background:${iv.rOp?'var(--card)':col};border:2.5px solid ${col};transform:translateX(-50%)`}));
|
||||
vis.appendChild(el('div',{style:`position:absolute;top:${y-2}px;left:3px;font-size:.74rem;font-weight:700;color:${col}`}, lbl));
|
||||
}
|
||||
drawInt(20, A, '#e91e63', '1)');
|
||||
drawInt(50, B, '#03a9f4', '2)');
|
||||
let ans, lbl, col;
|
||||
if(sysCurMode === 'sys'){
|
||||
ans = inter; lbl = '∩ Система:'; col = '#10b981';
|
||||
} else {
|
||||
// union
|
||||
if(A.r < B.l || B.r < A.l){ ans = null; lbl = '∪ Совокупность (2 части):'; col = '#10b981'; }
|
||||
else { ans = {l:Math.min(A.l,B.l), r:Math.max(A.r,B.r), lOp:Math.min(A.l,B.l)===A.l?A.lOp:B.lOp, rOp:Math.max(A.r,B.r)===A.r?A.rOp:B.rOp}; lbl = '∪ Совокупность:'; col = '#10b981'; }
|
||||
}
|
||||
if(sysCurMode === 'un' && !ans){
|
||||
drawInt(80, A, col, '∪');
|
||||
drawInt(110, B, col, '');
|
||||
} else {
|
||||
drawInt(95, ans, col, lbl);
|
||||
}
|
||||
// answer
|
||||
intervals.forEach((iv, i) => drawInt(10 + i*25, iv, COLORS[i%COLORS.length], (i+1)+')'));
|
||||
function fmt(iv){
|
||||
if(!iv) return '∅';
|
||||
const lA = iv.lOp ? '(' : '[';
|
||||
const rA = iv.rOp ? ')' : ']';
|
||||
const l = iv.l === -Infinity ? '-∞' : iv.l;
|
||||
const r = iv.r === Infinity ? '+∞' : iv.r;
|
||||
const l = iv.l === -Infinity ? '-∞' : +iv.l.toFixed(4);
|
||||
const r = iv.r === Infinity ? '+∞' : +iv.r.toFixed(4);
|
||||
return lA + l + '; ' + r + (iv.r === Infinity ? ')' : rA);
|
||||
}
|
||||
let ansText;
|
||||
if(sysCurMode === 'sys'){
|
||||
ansText = inter ? fmt(inter) : '∅';
|
||||
} else if(ans){
|
||||
ansText = fmt(ans);
|
||||
if(inter) drawInt(axisY - 18, inter, '#10b981', '∩');
|
||||
} else {
|
||||
ansText = fmt(A) + ' ∪ ' + fmt(B);
|
||||
// union: just show all individually in green
|
||||
intervals.forEach(iv => drawInt(axisY - 18, iv, '#10b981', '∪'));
|
||||
if(intervals.length === 2){
|
||||
const A2 = intervals[0], B2 = intervals[1];
|
||||
if(A2.r >= B2.l && B2.r >= A2.l){
|
||||
const merged = {l:Math.min(A2.l,B2.l), r:Math.max(A2.r,B2.r),
|
||||
lOp:Math.min(A2.l,B2.l)===A2.l?A2.lOp:B2.lOp,
|
||||
rOp:Math.max(A2.r,B2.r)===A2.r?A2.rOp:B2.rOp};
|
||||
ansText = fmt(merged);
|
||||
} else {
|
||||
ansText = fmt(A2) + ' ∪ ' + fmt(B2);
|
||||
}
|
||||
} else {
|
||||
ansText = intervals.map(fmt).join(' ∪ ');
|
||||
}
|
||||
}
|
||||
document.getElementById('sys-answer').textContent = ansText;
|
||||
}
|
||||
@@ -5756,5 +5872,432 @@ function initWave4(){
|
||||
document.addEventListener('DOMContentLoaded', ()=>setTimeout(initWave4, 200));
|
||||
</script>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
/* ════════════════════════════════════════════════════════
|
||||
WAVE DEPTH — 4 new interactive widgets
|
||||
════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
WIDGET 1 — Column root extraction (§1)
|
||||
══════════════════════════════════════════════ */
|
||||
let _clRunning = false;
|
||||
|
||||
function clPreset(n){
|
||||
const inp = document.getElementById('cl-n');
|
||||
if(inp){ inp.value = n; }
|
||||
}
|
||||
|
||||
async function clStart(){
|
||||
if(_clRunning) return;
|
||||
const inp = document.getElementById('cl-n');
|
||||
const ws = document.getElementById('cl-workspace');
|
||||
const ex = document.getElementById('cl-explain');
|
||||
if(!inp || !ws || !ex) return;
|
||||
const N = Math.abs(Math.round(+inp.value)) || 5184;
|
||||
if(N < 1 || N > 999999){ ex.textContent = 'Введите число от 1 до 999999.'; return; }
|
||||
|
||||
_clRunning = true;
|
||||
ws.innerHTML = '';
|
||||
ex.textContent = 'Начинаем...';
|
||||
|
||||
// Step 1: Split into pairs from right
|
||||
const digits = '' + N;
|
||||
const pairs = [];
|
||||
for(let i = digits.length; i > 0; i -= 2){
|
||||
pairs.unshift(digits.slice(Math.max(0, i-2), i));
|
||||
}
|
||||
const pairsStr = pairs.join(' | ');
|
||||
|
||||
// Step 2: compute actual integer sqrt for display
|
||||
const sqrtN = Math.sqrt(N);
|
||||
const isExact = Math.abs(sqrtN - Math.round(sqrtN)) < 1e-9;
|
||||
const answerInt = isExact ? Math.round(sqrtN) : Math.floor(sqrtN);
|
||||
|
||||
// Build steps
|
||||
let remainder = 0;
|
||||
let result = '';
|
||||
let lines = [];
|
||||
|
||||
lines.push(` Число: ${N}`);
|
||||
lines.push(` Грани: ${pairsStr}`);
|
||||
lines.push(` ${'─'.repeat(30)}`);
|
||||
ws.innerHTML = '<span>' + lines.join('\n') + '</span>';
|
||||
ex.innerHTML = '<b>Шаг 1.</b> Разбиваем число на грани по 2 цифры справа налево: <b>' + pairsStr + '</b>';
|
||||
await sleep(1000);
|
||||
|
||||
for(let pi = 0; pi < pairs.length; pi++){
|
||||
const pairVal = parseInt(pairs[pi], 10);
|
||||
// Bring down the pair
|
||||
const current = remainder * 100 + pairVal;
|
||||
// Double current answer for divisor base
|
||||
const doubleResult = (result === '') ? 0 : parseInt(result, 10) * 2;
|
||||
|
||||
// Find next digit d: (doubleResult*10 + d) * d <= current
|
||||
let d = 0;
|
||||
for(let t = 9; t >= 0; t--){
|
||||
if((doubleResult * 10 + t) * t <= current){ d = t; break; }
|
||||
}
|
||||
const subtract = (doubleResult * 10 + d) * d;
|
||||
const newRemainder = current - subtract;
|
||||
|
||||
result += '' + d;
|
||||
|
||||
// Add to display
|
||||
if(pi === 0){
|
||||
lines.push(` <span class="cl-active">${pairVal}</span> | √${N}`);
|
||||
lines.push(` <span class="cl-active">-${subtract}</span> | <span class="cl-result">${d}</span>`);
|
||||
lines.push(` ${'─'.repeat(18)}`);
|
||||
lines.push(` ${newRemainder}`);
|
||||
} else {
|
||||
lines.push(` <span class="cl-active">${current}</span>`);
|
||||
lines.push(` <span class="cl-active">-${subtract}</span> | <span class="cl-result">${result}</span> ← (${doubleResult}·${d} = ${doubleResult*d}; добавили ${d}: ${doubleResult*10+d}×${d}=${subtract})`);
|
||||
lines.push(` ${'─'.repeat(18)}`);
|
||||
lines.push(` ${newRemainder}`);
|
||||
}
|
||||
ws.innerHTML = '<span>' + lines.join('\n') + '</span>';
|
||||
|
||||
if(pi === 0){
|
||||
ex.innerHTML = `<b>Шаг ${pi*3+2}.</b> Берём первую грань <b>${pairVal}</b>. Ищем наибольшее <b>d</b> такое что <b>d² ≤ ${pairVal}</b>: d = <b>${d}</b> (${d}² = ${d*d}). Записываем в ответ. Вычитаем: ${pairVal} − ${subtract} = <b>${newRemainder}</b>.`;
|
||||
} else {
|
||||
ex.innerHTML = `<b>Шаг ${pi*3+2}.</b> Сносим грань <b>${pairs[pi]}</b> → получаем <b>${current}</b>. Удваиваем ответ: ${parseInt(result.slice(0,-1)||'0',10)*2} → заготовка делителя <b>${doubleResult}</b>. Подбираем d: (${doubleResult*10}+d)×d ≤ ${current}. Подходит d = <b>${d}</b>. Вычитаем: ${current} − ${subtract} = <b>${newRemainder}</b>.`;
|
||||
}
|
||||
remainder = newRemainder;
|
||||
await sleep(1100);
|
||||
}
|
||||
|
||||
// Final
|
||||
if(remainder === 0){
|
||||
lines.push('');
|
||||
lines.push(` <span class="cl-result">√${N} = ${result}</span> (остаток 0 — точный квадрат)`);
|
||||
ws.innerHTML = '<span>' + lines.join('\n') + '</span>';
|
||||
ex.innerHTML = `<b>Готово!</b> Остаток равен 0 — корень извлечён точно. <b>√${N} = ${result}</b>.`;
|
||||
await sleep(600);
|
||||
// Badge
|
||||
ex.innerHTML += ` <span class="cl-final-badge">√${N} = ${result}</span>`;
|
||||
confetti();
|
||||
addXp(15, 'col-root');
|
||||
bumpProgress('p1', 8);
|
||||
achievement('col-root', 'Извлёк корень в столбик');
|
||||
} else {
|
||||
lines.push('');
|
||||
lines.push(` Остаток: <span class="cl-active">${remainder}</span> (не 0 — ${N} не точный квадрат)`);
|
||||
lines.push(` <span class="cl-result">⌊√${N}⌋ ≈ ${answerInt}</span>`);
|
||||
ws.innerHTML = '<span>' + lines.join('\n') + '</span>';
|
||||
ex.innerHTML = `<b>Внимание:</b> остаток ${remainder} ≠ 0. Число <b>${N}</b> — не точный квадрат. Целая часть корня: <b>⌊√${N}⌋ = ${answerInt}</b>. Точный ответ иррационален.`;
|
||||
addXp(8, 'col-root-approx');
|
||||
bumpProgress('p1', 4);
|
||||
}
|
||||
|
||||
_clRunning = false;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
WIDGET 2 — Square comparison SVG (§4)
|
||||
══════════════════════════════════════════════ */
|
||||
const SQ_PAIRS = [
|
||||
{ aExpr:'3√2', aSq:18, bExpr:'2√3', bSq:12 },
|
||||
{ aExpr:'4√3', aSq:48, bExpr:'3√5', bSq:45 },
|
||||
{ aExpr:'5√2', aSq:50, bExpr:'7', bSq:49 },
|
||||
{ aExpr:'2√7', aSq:28, bExpr:'3√3', bSq:27 },
|
||||
{ aExpr:'√17', aSq:17, bExpr:'4', bSq:16 },
|
||||
];
|
||||
let sqPairIdx = 0;
|
||||
let _sqAnimating = false;
|
||||
|
||||
function sqRender(){
|
||||
const p = SQ_PAIRS[sqPairIdx];
|
||||
const ae = document.getElementById('sq-a-expr');
|
||||
const be = document.getElementById('sq-b-expr');
|
||||
if(ae) ae.textContent = p.aExpr;
|
||||
if(be) be.textContent = p.bExpr;
|
||||
const svg = document.getElementById('sq-svg');
|
||||
if(svg){
|
||||
svg.innerHTML = '<text x="300" y="110" text-anchor="middle" font-size="18" fill="currentColor" opacity="0.5">Нажмите «Возвести в квадрат и сравнить»</text>';
|
||||
}
|
||||
const con = document.getElementById('sq-conclusion');
|
||||
if(con) con.innerHTML = '';
|
||||
}
|
||||
|
||||
async function sqAnimate(){
|
||||
if(_sqAnimating) return;
|
||||
_sqAnimating = true;
|
||||
const p = SQ_PAIRS[sqPairIdx];
|
||||
const svg = document.getElementById('sq-svg');
|
||||
const con = document.getElementById('sq-conclusion');
|
||||
if(!svg) { _sqAnimating = false; return; }
|
||||
|
||||
const W = 600, H = 220;
|
||||
const maxSq = Math.max(p.aSq, p.bSq);
|
||||
const maxSide = 100;
|
||||
const sideA = maxSide * Math.sqrt(p.aSq / maxSq);
|
||||
const sideB = maxSide * Math.sqrt(p.bSq / maxSq);
|
||||
const cy = H / 2;
|
||||
const cxA = 140, cxB = 460;
|
||||
|
||||
svg.innerHTML = '';
|
||||
|
||||
// Draw grid helper function as tiny squares
|
||||
function makeRect(cx, cy, side, col, label, sqVal, expr){
|
||||
const x = cx - side/2, y = cy - side/2;
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg','g');
|
||||
g.style.transformOrigin = `${cx}px ${cy}px`;
|
||||
g.style.transform = 'scale(0)';
|
||||
g.style.transition = 'transform 0.7s cubic-bezier(0.34,1.56,0.64,1)';
|
||||
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
|
||||
rect.setAttribute('x', x); rect.setAttribute('y', y);
|
||||
rect.setAttribute('width', side); rect.setAttribute('height', side);
|
||||
rect.setAttribute('fill', col + '33');
|
||||
rect.setAttribute('stroke', col);
|
||||
rect.setAttribute('stroke-width', '2.5');
|
||||
rect.setAttribute('rx', '4');
|
||||
g.appendChild(rect);
|
||||
|
||||
// Side label — top
|
||||
const tSide = document.createElementNS('http://www.w3.org/2000/svg','text');
|
||||
tSide.setAttribute('x', cx); tSide.setAttribute('y', y - 8);
|
||||
tSide.setAttribute('text-anchor','middle');
|
||||
tSide.setAttribute('font-size','14');
|
||||
tSide.setAttribute('font-weight','700');
|
||||
tSide.setAttribute('fill', col);
|
||||
tSide.textContent = expr;
|
||||
g.appendChild(tSide);
|
||||
|
||||
// Area label — center
|
||||
const tArea = document.createElementNS('http://www.w3.org/2000/svg','text');
|
||||
tArea.setAttribute('x', cx); tArea.setAttribute('y', cy + 5);
|
||||
tArea.setAttribute('text-anchor','middle');
|
||||
tArea.setAttribute('font-size','20');
|
||||
tArea.setAttribute('font-weight','900');
|
||||
tArea.setAttribute('fill', col);
|
||||
tArea.textContent = '(' + expr + ')² = ' + sqVal;
|
||||
g.appendChild(tArea);
|
||||
|
||||
// Expression bottom
|
||||
const tExpr = document.createElementNS('http://www.w3.org/2000/svg','text');
|
||||
tExpr.setAttribute('x', cx); tExpr.setAttribute('y', y + side + 20);
|
||||
tExpr.setAttribute('text-anchor','middle');
|
||||
tExpr.setAttribute('font-size','14');
|
||||
tExpr.setAttribute('fill','currentColor');
|
||||
tExpr.setAttribute('opacity','0.7');
|
||||
tExpr.textContent = label;
|
||||
g.appendChild(tExpr);
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
const colA = '#0288d1', colB = '#c2185b';
|
||||
const gA = makeRect(cxA, cy, sideA, colA, 'A = ' + p.aExpr, p.aSq, p.aExpr);
|
||||
const gB = makeRect(cxB, cy, sideB, colB, 'B = ' + p.bExpr, p.bSq, p.bExpr);
|
||||
|
||||
// VS text
|
||||
const vsText = document.createElementNS('http://www.w3.org/2000/svg','text');
|
||||
vsText.setAttribute('x', W/2); vsText.setAttribute('y', cy + 6);
|
||||
vsText.setAttribute('text-anchor','middle');
|
||||
vsText.setAttribute('font-size','22');
|
||||
vsText.setAttribute('font-weight','700');
|
||||
vsText.setAttribute('fill','#888');
|
||||
vsText.textContent = 'vs';
|
||||
svg.appendChild(vsText);
|
||||
svg.appendChild(gA);
|
||||
svg.appendChild(gB);
|
||||
|
||||
await sleep(50);
|
||||
gA.style.transform = 'scale(1)';
|
||||
await sleep(200);
|
||||
gB.style.transform = 'scale(1)';
|
||||
await sleep(800);
|
||||
|
||||
// Winner arrow/badge
|
||||
const bigger = p.aSq > p.bSq ? 'A' : (p.bSq > p.aSq ? 'B' : 'equal');
|
||||
const winX = bigger === 'A' ? cxA : bigger === 'B' ? cxB : W/2;
|
||||
const winCol = bigger === 'A' ? colA : bigger === 'B' ? colB : '#10b981';
|
||||
const winText = p.aSq > p.bSq
|
||||
? p.aExpr + ' > ' + p.bExpr
|
||||
: (p.bSq > p.aSq ? p.bExpr + ' > ' + p.aExpr : p.aExpr + ' = ' + p.bExpr);
|
||||
|
||||
const badge = document.createElementNS('http://www.w3.org/2000/svg','g');
|
||||
const br = document.createElementNS('http://www.w3.org/2000/svg','rect');
|
||||
br.setAttribute('x', winX - 80); br.setAttribute('y', 10);
|
||||
br.setAttribute('width', 160); br.setAttribute('height', 34);
|
||||
br.setAttribute('rx', 17); br.setAttribute('fill', winCol);
|
||||
badge.appendChild(br);
|
||||
const bt = document.createElementNS('http://www.w3.org/2000/svg','text');
|
||||
bt.setAttribute('x', winX); bt.setAttribute('y', 33);
|
||||
bt.setAttribute('text-anchor','middle');
|
||||
bt.setAttribute('font-size','15');
|
||||
bt.setAttribute('font-weight','800');
|
||||
bt.setAttribute('fill','#fff');
|
||||
bt.textContent = winText;
|
||||
badge.appendChild(bt);
|
||||
badge.style.opacity = '0';
|
||||
badge.style.transition = 'opacity 0.4s ease';
|
||||
svg.appendChild(badge);
|
||||
await sleep(50);
|
||||
badge.style.opacity = '1';
|
||||
|
||||
if(con){
|
||||
const sign = p.aSq > p.bSq ? '>' : (p.bSq > p.aSq ? '<' : '=');
|
||||
const conclusion = `${p.aExpr} ${sign} ${p.bExpr}, потому что (${p.aExpr})² = ${p.aSq} ${sign} ${p.bSq} = (${p.bExpr})²`;
|
||||
con.innerHTML = '<b style="color:var(--ok)">' + conclusion + '</b>';
|
||||
bumpProgress('p4', 4);
|
||||
addXp(8, 'sq-compare');
|
||||
achievement('sq-compare', 'Сравнил через квадрат');
|
||||
confetti();
|
||||
}
|
||||
_sqAnimating = false;
|
||||
}
|
||||
|
||||
function sqNext(){
|
||||
sqPairIdx = (sqPairIdx + 1) % SQ_PAIRS.length;
|
||||
_sqAnimating = false;
|
||||
sqRender();
|
||||
}
|
||||
|
||||
function initSqCompare(){
|
||||
sqPairIdx = 0;
|
||||
sqRender();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
WIDGET 3 — Euler–Venn diagrams (§5)
|
||||
══════════════════════════════════════════════ */
|
||||
let _evMode = 'both';
|
||||
|
||||
function _evGetVals(){
|
||||
const aLo = +document.getElementById('ev-a-lo-s').value;
|
||||
const aHi = +document.getElementById('ev-a-hi-s').value;
|
||||
const bLo = +document.getElementById('ev-b-lo-s').value;
|
||||
const bHi = +document.getElementById('ev-b-hi-s').value;
|
||||
return {
|
||||
aLo: Math.min(aLo, aHi),
|
||||
aHi: Math.max(aLo, aHi),
|
||||
bLo: Math.min(bLo, bHi),
|
||||
bHi: Math.max(bLo, bHi),
|
||||
};
|
||||
}
|
||||
|
||||
function evMode(m){
|
||||
_evMode = m;
|
||||
_evDraw();
|
||||
}
|
||||
|
||||
function _evDraw(){
|
||||
const svg = document.getElementById('ev-svg');
|
||||
const res = document.getElementById('ev-result');
|
||||
if(!svg) return;
|
||||
|
||||
const {aLo, aHi, bLo, bHi} = _evGetVals();
|
||||
|
||||
// Update labels
|
||||
const aLoLbl = document.getElementById('ev-a-lo'); if(aLoLbl) aLoLbl.textContent = aLo;
|
||||
const aHiLbl = document.getElementById('ev-a-hi'); if(aHiLbl) aHiLbl.textContent = aHi;
|
||||
const bLoLbl = document.getElementById('ev-b-lo'); if(bLoLbl) bLoLbl.textContent = bLo;
|
||||
const bHiLbl = document.getElementById('ev-b-hi'); if(bHiLbl) bHiLbl.textContent = bHi;
|
||||
|
||||
const W = 520, H = 280;
|
||||
// Map values to x coordinates in SVG
|
||||
const VMIN = -5, VMAX = 10;
|
||||
function toX(v){ return 50 + (v - VMIN) / (VMAX - VMIN) * (W - 100); }
|
||||
|
||||
const xA1 = toX(aLo), xA2 = toX(aHi);
|
||||
const xB1 = toX(bLo), xB2 = toX(bHi);
|
||||
const ry = 40; // ellipse y-radius
|
||||
const cyA = 100, cyB = 180;
|
||||
const rxA = Math.max(10, (xA2 - xA1) / 2);
|
||||
const rxB = Math.max(10, (xB2 - xB1) / 2);
|
||||
const cxA = (xA1 + xA2) / 2;
|
||||
const cxB = (xB1 + xB2) / 2;
|
||||
|
||||
// Intersection
|
||||
const interLo = Math.max(aLo, bLo);
|
||||
const interHi = Math.min(aHi, bHi);
|
||||
const hasInter = interLo <= interHi;
|
||||
const interX1 = toX(interLo), interX2 = toX(interHi);
|
||||
const interRx = Math.max(0, (interX2 - interX1) / 2);
|
||||
const interCx = (interX1 + interX2) / 2;
|
||||
|
||||
// Union
|
||||
const unionLo = Math.min(aLo, bLo);
|
||||
const unionHi = Math.max(aHi, bHi);
|
||||
|
||||
let html = `<defs>
|
||||
<clipPath id="clip-a"><ellipse cx="${cxA}" cy="${cyA}" rx="${rxA}" ry="${ry}"/></clipPath>
|
||||
<clipPath id="clip-b"><ellipse cx="${cxB}" cy="${cyB}" rx="${rxB}" ry="${ry}"/></clipPath>
|
||||
</defs>`;
|
||||
|
||||
// Base ellipses
|
||||
const opA = _evMode === 'inter' ? '0.35' : '0.7';
|
||||
const opB = _evMode === 'inter' ? '0.35' : '0.7';
|
||||
html += `<ellipse cx="${cxA}" cy="${cyA}" rx="${rxA}" ry="${ry}" fill="rgba(3,169,244,0.18)" stroke="#0288d1" stroke-width="2.5" opacity="${opA}"/>`;
|
||||
html += `<ellipse cx="${cxB}" cy="${cyB}" rx="${rxB}" ry="${ry}" fill="rgba(233,30,99,0.18)" stroke="#c2185b" stroke-width="2.5" opacity="${opB}"/>`;
|
||||
|
||||
// Labels
|
||||
html += `<text x="${cxA}" y="${cyA+5}" text-anchor="middle" font-size="16" font-weight="800" fill="#0288d1">A</text>`;
|
||||
html += `<text x="${cxB}" y="${cyB+5}" text-anchor="middle" font-size="16" font-weight="800" fill="#c2185b">B</text>`;
|
||||
|
||||
// Range labels below each ellipse
|
||||
html += `<text x="${cxA}" y="${cyA + ry + 14}" text-anchor="middle" font-size="11" fill="currentColor" opacity="0.7">[${aLo}; ${aHi}]</text>`;
|
||||
html += `<text x="${cxB}" y="${cyB + ry + 14}" text-anchor="middle" font-size="11" fill="currentColor" opacity="0.7">[${bLo}; ${bHi}]</text>`;
|
||||
|
||||
// Mode-specific highlights
|
||||
if(_evMode === 'inter' || _evMode === 'both'){
|
||||
if(hasInter && interRx > 0){
|
||||
// Check if ellipses actually overlap in y (they're on different y levels but show as overlapping if x-ranges cross)
|
||||
// We just highlight intersection region as a vertical band between both ellipses
|
||||
html += `<rect x="${interX1}" y="${cyA - ry}" width="${Math.max(0,interX2-interX1)}" height="${cyB + ry - (cyA - ry)}" fill="rgba(16,185,129,0.28)" stroke="#10b981" stroke-width="2" stroke-dasharray="5,3" rx="4"/>`;
|
||||
html += `<text x="${interCx}" y="${(cyA+cyB)/2+5}" text-anchor="middle" font-size="13" font-weight="700" fill="#10b981">∩</text>`;
|
||||
}
|
||||
}
|
||||
|
||||
if(_evMode === 'union' || _evMode === 'both'){
|
||||
// Draw union outline — thick gold border around combined span
|
||||
const ux1 = toX(unionLo), ux2 = toX(unionHi);
|
||||
html += `<rect x="${ux1 - 4}" y="${cyA - ry - 4}" width="${ux2 - ux1 + 8}" height="${cyB + ry + 8 - (cyA - ry - 4)}" fill="none" stroke="#f59e0b" stroke-width="3" rx="8" opacity="0.85"/>`;
|
||||
html += `<text x="${(ux1+ux2)/2}" y="${cyA - ry - 10}" text-anchor="middle" font-size="12" font-weight="700" fill="#f59e0b">∪</text>`;
|
||||
}
|
||||
|
||||
svg.innerHTML = html;
|
||||
|
||||
// Result text
|
||||
if(res){
|
||||
const interStr = hasInter ? `[${interLo}; ${interHi}]` : '∅';
|
||||
const unionStr = `[${unionLo}; ${unionHi}]`;
|
||||
if(_evMode === 'inter') res.innerHTML = `$A \\cap B = ${interStr}$`;
|
||||
else if(_evMode === 'union') res.innerHTML = `$A \\cup B = ${unionStr}$`;
|
||||
else res.innerHTML = `$A \\cup B = ${unionStr}$, $A \\cap B = ${interStr}$`;
|
||||
if(typeof renderMath === 'function') renderMath(res);
|
||||
bumpProgress('p5', 2);
|
||||
}
|
||||
}
|
||||
|
||||
function initEulerVenn(){
|
||||
['ev-a-lo-s','ev-a-hi-s','ev-b-lo-s','ev-b-hi-s'].forEach(id=>{
|
||||
const e = document.getElementById(id);
|
||||
if(e) e.addEventListener('input', _evDraw);
|
||||
});
|
||||
_evMode = 'both';
|
||||
_evDraw();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
PATCH buildP4 / buildP5 to init new widgets
|
||||
══════════════════════════════════════════════ */
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
// Patch _goToFinish after all other DOMContentLoaded hooks have registered
|
||||
// We use a late timeout so Wave3 patch (at +100ms) has already run
|
||||
setTimeout(function(){
|
||||
const _origFinish = window._goToFinish;
|
||||
window._goToFinish = function(id){
|
||||
_origFinish(id);
|
||||
if(id === 'p4') setTimeout(initSqCompare, 80);
|
||||
if(id === 'p5') setTimeout(initEulerVenn, 80);
|
||||
};
|
||||
}, 300);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user