+ ${widget('Геометрическое доказательство √(a·b) = √a·√b', 'VISUAL', 'Прямоугольник a × b разбивается на a·b единичных клеток. Те же клетки могут собраться в квадрат со стороной √(a·b). Нажмите «Анимировать» — увидите как клетки перетекают.', `
+
+
+ a =
+
+ 4
-
-
Квадрат со стороной √(ab)
-
-
- S = (√ab)²
-
-
сторона = 6.00
+
+ b =
+
+ 9
-
Площади всегда равны → $\\sqrt{ab}$ — сторона эквивалентного квадрата
+
+
+
+
-
▶ Воспроизвести доказательство
+
+
+ Анимировать
+
+
Сбросить
-
`)}
${makeCard('rule','Свойство 2: корень из частного',null,`
@@ -2744,67 +2742,162 @@ function buildP3(){
}
/* ──── Geometric proof √(ab) ──── */
+/* ──── Geometric proof √(ab)=√a·√b — анимация клеток ──── */
+const GEO_STATE = { animating: false };
+
function initGeoProof(){
const a = document.getElementById('geo-a');
if(!a) return;
const b = document.getElementById('geo-b');
function upd(){
- const av = +a.value, bv = +b.value;
- document.getElementById('geo-a-v').textContent = av;
- document.getElementById('geo-b-v').textContent = bv;
- const r = document.getElementById('geo-rect');
- // rect: width ~ a, height ~ b (scaled)
- const maxDim = 160, base = Math.max(av, bv);
- r.setAttribute('width', av/base*maxDim);
- r.setAttribute('height', bv/base*maxDim*0.6);
- const sq = document.getElementById('geo-sq');
- const s = Math.sqrt(av * bv);
- sq.setAttribute('width', s/base*maxDim);
- sq.setAttribute('height', s/base*maxDim);
- document.getElementById('geo-rect-s').textContent = (av*bv).toFixed(0);
- document.getElementById('geo-side').textContent = s.toFixed(2);
+ if(GEO_STATE.animating) return;
+ geoRenderRect();
}
a.addEventListener('input', upd);
b.addEventListener('input', upd);
- upd();
+ geoRenderRect();
}
-/* ──── Geo Proof Animation ──── */
-function geoProofAnimate(){
- const box = document.getElementById('geo-proof-anim');
- if(!box) return;
- const aVal = +(document.getElementById('geo-a') ? document.getElementById('geo-a').value : 4);
- const bVal = +(document.getElementById('geo-b') ? document.getElementById('geo-b').value : 9);
- const ab = aVal * bVal;
- const side = Math.sqrt(ab).toFixed(2);
- const steps = [
- { delay:0, html: `
Шаг 1. Прямоугольник
${aVal} × ${bVal} подсвечивается рамкой — его площадь
S = a·b = ${ab} ` },
- { delay:900, html: `
Шаг 2. Прямоугольник можно представить как ${ab} единичных клеток (
${aVal} строк × ${bVal} столбцов )` },
- { delay:2000, html: `
Шаг 3. Все ${ab} клеток «перетекают» в квадрат — ищем сторону, при которой сторона² = ${ab}` },
- { delay:3200, html: `
Шаг 4. Квадрат со стороной
√${ab} ≈ ${side} имеет ту же площадь: S = (√${ab})² = ${ab}` },
- { delay:4400, html: `
Доказано! √(${aVal}·${bVal}) = √${aVal}·√${bVal} = ${Math.sqrt(aVal).toFixed(2)}·${Math.sqrt(bVal).toFixed(2)} = ${side}` },
- ];
- const rectEl = document.getElementById('geo-rect');
- const sqEl = document.getElementById('geo-sq');
- // highlight steps visually
- if(rectEl){
- rectEl.style.transition = 'stroke-width .3s';
- rectEl.setAttribute('stroke-width','4');
- setTimeout(()=>rectEl.setAttribute('stroke-width','2'), 900);
- }
- if(sqEl){
- setTimeout(()=>{
- sqEl.style.transition = 'stroke-width .3s';
- sqEl.setAttribute('stroke-width','4');
- setTimeout(()=>sqEl.setAttribute('stroke-width','2'), 800);
- }, 3200);
- }
- steps.forEach(s=>{
- setTimeout(()=>{ box.innerHTML = s.html; renderMath(box); }, s.delay);
- });
- setTimeout(()=>bumpProgress('p3', 5), 4500);
+function geoCurrentAB(){
+ const a = +document.getElementById('geo-a').value;
+ const b = +document.getElementById('geo-b').value;
+ return [a, b];
}
+function geoRenderRect(){
+ const [aVal, bVal] = geoCurrentAB();
+ document.getElementById('geo-a-v').textContent = aVal;
+ document.getElementById('geo-b-v').textContent = bVal;
+ const svg = document.getElementById('geo-svg');
+ if(!svg) return;
+ svg.innerHTML = '';
+ const ab = aVal * bVal;
+ const sqSide = Math.sqrt(ab);
+ // sizing: cell size based on max dimensions
+ const W = 600, H = 280;
+ const halfW = 290, gap = 20;
+ const maxCol = Math.max(aVal, Math.ceil(sqSide));
+ const maxRow = Math.max(bVal, Math.ceil(sqSide));
+ const cell = Math.floor(Math.min(halfW/maxCol, (H-50)/maxRow, 30));
+ const rectW = aVal * cell, rectH = bVal * cell;
+ const rectX = (halfW - rectW) / 2;
+ const rectY = (H - 30 - rectH) / 2;
+ const sqW = sqSide * cell;
+ const sqX = halfW + gap + (halfW - sqW) / 2;
+ const sqY = (H - 30 - sqW) / 2;
+ // Labels
+ svg.appendChild(svgEl('text', {x:halfW/2, y:H-8, 'text-anchor':'middle', fill:'#03a9f4', 'font-size':14, 'font-weight':700, 'font-family':'Inter,sans-serif'}, `${aVal} × ${bVal} = ${ab} клеток`));
+ svg.appendChild(svgEl('text', {x:halfW + gap + halfW/2, y:H-8, 'text-anchor':'middle', fill:'#e91e63', 'font-size':14, 'font-weight':700, 'font-family':'Inter,sans-serif'}, `сторона ≈ ${sqSide.toFixed(2)} → S = ${ab}`));
+ // Arrow between
+ svg.appendChild(svgEl('path', {d:`M ${halfW + 2} ${H/2 - 18} L ${halfW + gap - 2} ${H/2 - 18}`, stroke:'#7c3aed', 'stroke-width':2.5, fill:'none'}));
+ svg.appendChild(svgEl('polyline', {points:`${halfW + gap - 10},${H/2 - 24} ${halfW + gap - 2},${H/2 - 18} ${halfW + gap - 10},${H/2 - 12}`, stroke:'#7c3aed', 'stroke-width':2.5, fill:'none'}));
+ // Hint outline rectangle on the right (where square would be)
+ svg.appendChild(svgEl('rect', {x:sqX, y:sqY, width:sqW, height:sqW, fill:'none', stroke:'#e91e63', 'stroke-width':2, 'stroke-dasharray':'4,4', opacity:0.5}));
+ // Cells in rectangle layout
+ for(let i = 0; i < ab; i++){
+ const col = i % aVal;
+ const row = Math.floor(i / aVal);
+ const cellEl = svgEl('rect', {
+ class:'geo-cell',
+ 'data-i':i,
+ x: rectX + col*cell + 1,
+ y: rectY + row*cell + 1,
+ width: cell - 2,
+ height: cell - 2,
+ fill: i % 2 === 0 ? '#03a9f4' : '#0288d1',
+ opacity: 0.85,
+ rx: 2,
+ });
+ cellEl.style.transition = 'x .55s cubic-bezier(.4,1.3,.5,1), y .55s cubic-bezier(.4,1.3,.5,1), fill .3s';
+ svg.appendChild(cellEl);
+ }
+ // Stash layout info
+ GEO_STATE.cell = cell;
+ GEO_STATE.rectX = rectX; GEO_STATE.rectY = rectY;
+ GEO_STATE.sqX = sqX; GEO_STATE.sqY = sqY;
+ GEO_STATE.sqSide = sqSide;
+ GEO_STATE.aVal = aVal; GEO_STATE.bVal = bVal; GEO_STATE.ab = ab;
+ // Formula
+ geoUpdateFormula(false);
+}
+
+function svgEl(tag, attrs, text){
+ const e = document.createElementNS('http://www.w3.org/2000/svg', tag);
+ if(attrs) for(const k in attrs) e.setAttribute(k, attrs[k]);
+ if(text != null) e.textContent = text;
+ return e;
+}
+
+function geoUpdateFormula(done){
+ const fb = document.getElementById('geo-formula');
+ if(!fb) return;
+ const a = GEO_STATE.aVal, b = GEO_STATE.bVal, ab = GEO_STATE.ab;
+ const ra = Math.sqrt(a), rb = Math.sqrt(b);
+ const rab = Math.sqrt(ab);
+ const aRound = Number.isInteger(ra) ? ra : ra.toFixed(2);
+ const bRound = Number.isInteger(rb) ? rb : rb.toFixed(2);
+ const abRound = Number.isInteger(rab) ? rab : rab.toFixed(2);
+ fb.innerHTML = `$$\\sqrt{${a} \\cdot ${b}} = \\sqrt{${ab}} = ${abRound}$$
+ $$\\sqrt{${a}} \\cdot \\sqrt{${b}} = ${aRound} \\cdot ${bRound} = ${abRound}$$`
+ + (done ? `
Доказано: $\\sqrt{${a} \\cdot ${b}} = \\sqrt{${a}} \\cdot \\sqrt{${b}}$
` : '');
+ renderMath(fb);
+}
+
+async function geoProofAnimate(){
+ if(GEO_STATE.animating) return;
+ GEO_STATE.animating = true;
+ const btn = document.getElementById('geo-play-btn');
+ if(btn) btn.disabled = true;
+ const svg = document.getElementById('geo-svg');
+ if(!svg){ GEO_STATE.animating = false; if(btn) btn.disabled = false; return; }
+ const cells = [...svg.querySelectorAll('.geo-cell')];
+ const cell = GEO_STATE.cell;
+ const sqX = GEO_STATE.sqX, sqY = GEO_STATE.sqY;
+ const sqSide = GEO_STATE.sqSide;
+ // Шаг 1: волна подсветки прямоугольника
+ for(let i = 0; i < cells.length; i++){
+ cells[i].setAttribute('fill', '#fbbf24');
+ cells[i].setAttribute('opacity', '1');
+ await sleep(20);
+ }
+ await sleep(280);
+ // Шаг 2: клетки летят в квадрат
+ // если ab - точный квадрат, плотная упаковка
+ const isSq = Number.isInteger(sqSide);
+ const cols = isSq ? sqSide : Math.ceil(sqSide);
+ const targetCellSize = isSq ? cell : (sqSide * cell) / cols;
+ for(let i = 0; i < cells.length; i++){
+ const col = i % cols;
+ const row = Math.floor(i / cols);
+ cells[i].setAttribute('x', sqX + col*targetCellSize + 1);
+ cells[i].setAttribute('y', sqY + row*targetCellSize + 1);
+ cells[i].setAttribute('width', targetCellSize - 2);
+ cells[i].setAttribute('height', targetCellSize - 2);
+ cells[i].setAttribute('fill', i % 2 === 0 ? '#e91e63' : '#c2185b');
+ await sleep(35);
+ }
+ await sleep(500);
+ // Шаг 3: пульс на финальном квадрате
+ cells.forEach(c => { c.setAttribute('opacity', '1'); });
+ await sleep(100);
+ geoUpdateFormula(true);
+ bumpProgress('p3', 6);
+ addXp(10, 'geo-proof');
+ confetti();
+ await sleep(2400);
+ GEO_STATE.animating = false;
+ if(btn) btn.disabled = false;
+}
+
+function geoProofReset(){
+ GEO_STATE.animating = false;
+ const btn = document.getElementById('geo-play-btn');
+ if(btn) btn.disabled = false;
+ geoRenderRect();
+}
+
+function sleep(ms){ return new Promise(r => setTimeout(r, ms)); }
+
/* ──── Property check slider ──── */
function initPropCheck(){
const a = document.getElementById('prop-a');