diff --git a/frontend/css/alg7-fx.css b/frontend/css/alg7-fx.css new file mode 100644 index 0000000..ff8a588 --- /dev/null +++ b/frontend/css/alg7-fx.css @@ -0,0 +1,290 @@ +/* alg7-fx.css — UX-эффекты для Алгебры 7 (все главы) + shake + pulse + combo badge + visual constructors styles */ + +/* === ANIMATIONS === */ +@keyframes alg7-shake { + 0%,100% { transform: translateX(0); } + 10%,30%,50%,70%,90% { transform: translateX(-6px); } + 20%,40%,60%,80% { transform: translateX(6px); } +} +@keyframes alg7-pulse { + 0% { box-shadow: 0 0 0 0 rgba(16,185,129,.55); } + 50% { box-shadow: 0 0 0 12px rgba(16,185,129,0); } + 100% { box-shadow: 0 0 0 0 rgba(16,185,129,0); } +} +@keyframes alg7-glow { + 0% { filter: drop-shadow(0 0 0 transparent); } + 50% { filter: drop-shadow(0 0 12px var(--sec-acc, #10b981)); } + 100% { filter: drop-shadow(0 0 0 transparent); } +} +@keyframes alg7-combo-pop { + 0% { transform: scale(.4) translateY(20px); opacity: 0; } + 40% { transform: scale(1.18) translateY(0); opacity: 1; } + 60% { transform: scale(1) translateY(0); } + 90% { transform: scale(1) translateY(0); opacity: 1; } + 100% { transform: scale(.85) translateY(-30px); opacity: 0; } +} +@keyframes alg7-sparkle { + 0%,100% { opacity: 0; transform: scale(0); } + 50% { opacity: 1; transform: scale(1); } +} +@keyframes alg7-area-in { + from { opacity: 0; transform: scale(.5); transform-origin: 0 0; } + to { opacity: 1; transform: scale(1); } +} +@keyframes alg7-slide-out { + 0% { transform: translate(0,0); } + 100% { transform: translate(var(--alg7-dx, 100px), var(--alg7-dy, 0)); } +} + +/* === STATE CLASSES === */ +.alg7-shake { animation: alg7-shake .52s cubic-bezier(.36,.07,.19,.97) both; } +.alg7-pulse { animation: alg7-pulse .85s ease-out; } +.alg7-glow { animation: alg7-glow 1.1s ease-out; } + +/* === COMBO BADGE === */ +.alg7-combo-badge { + position: fixed; + z-index: 9998; + pointer-events: none; + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, #f59e0b, #ef4444, #f59e0b); + background-size: 200% 100%; + color: #fff; + border-radius: 999px; + font-family: 'Unbounded', 'Inter', sans-serif; + font-weight: 900; + font-size: 1.1rem; + letter-spacing: .02em; + box-shadow: 0 8px 28px rgba(239,68,68,.4), 0 0 0 3px rgba(255,255,255,.2) inset; + animation: alg7-combo-pop 2.2s ease-out forwards, alg7-combo-shimmer 1.4s linear infinite; +} +@keyframes alg7-combo-shimmer { + 0% { background-position: 0% 50%; } + 100% { background-position: 200% 50%; } +} +.alg7-combo-badge .ic-fire { + width: 22px; height: 22px; + filter: drop-shadow(0 0 4px rgba(255,255,255,.8)); +} +.alg7-combo-badge .badge-x { + font-size: 1.4rem; + font-weight: 900; + font-family: 'JetBrains Mono', monospace; +} + +/* === STREAK INDICATOR (постоянный, в углу) === */ +.alg7-streak { + position: fixed; + bottom: 18px; + right: 18px; + z-index: 9990; + display: none; + align-items: center; + gap: 6px; + padding: 7px 14px; + background: linear-gradient(135deg, #f59e0b, #ef4444); + color: #fff; + border-radius: 999px; + font-family: 'Unbounded', 'Inter', sans-serif; + font-weight: 800; + font-size: .82rem; + box-shadow: 0 6px 20px rgba(239,68,68,.35); + transition: transform .2s, opacity .3s; + animation: alg7-streak-pulse 1.6s ease-in-out infinite; +} +.alg7-streak.show { display: inline-flex; } +.alg7-streak .ic-fire { width: 16px; height: 16px; } +@keyframes alg7-streak-pulse { + 0%,100% { box-shadow: 0 6px 20px rgba(239,68,68,.35); } + 50% { box-shadow: 0 6px 28px rgba(239,68,68,.7); } +} + +/* === SPARKLES при правильном ответе === */ +.alg7-sparkle { + position: absolute; + width: 8px; height: 8px; + background: radial-gradient(circle, #fbbf24 0%, transparent 70%); + border-radius: 50%; + pointer-events: none; + animation: alg7-sparkle .9s ease-out forwards; +} + +/* === ВИЗУАЛИЗАТОР КВАДРАТА СУММЫ — Ch2 §12 === */ +.alg7-qsum-stage { + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + padding: 14px; + background: linear-gradient(135deg, rgba(220,38,38,.05), rgba(245,158,11,.05)); + border-radius: 12px; + margin-top: 12px; +} +.alg7-qsum-svg { + width: 100%; + max-width: 380px; + height: auto; +} +.alg7-qsum-svg .area-aa { fill: #dc2626; opacity: .82; transition: opacity .3s; } +.alg7-qsum-svg .area-ab { fill: #f59e0b; opacity: .82; transition: opacity .3s; } +.alg7-qsum-svg .area-bb { fill: #10b981; opacity: .82; transition: opacity .3s; } +.alg7-qsum-svg .area-aa.dim, +.alg7-qsum-svg .area-ab.dim, +.alg7-qsum-svg .area-bb.dim { opacity: .15; } +.alg7-qsum-svg .area-label { + font-family: 'Unbounded','JetBrains Mono', monospace; + font-weight: 800; + font-size: 13px; + fill: #fff; + text-anchor: middle; + text-shadow: 0 1px 2px rgba(0,0,0,.4); + pointer-events: none; +} +.alg7-qsum-formula { + font-family: 'JetBrains Mono', monospace; + font-size: 1.05rem; + text-align: center; + line-height: 2; + padding: 10px 14px; + background: var(--card); + border-radius: 10px; + border: 1px solid var(--border); + width: 100%; +} +.alg7-qsum-formula .term-aa { color: #dc2626; font-weight: 800; } +.alg7-qsum-formula .term-ab { color: #d97706; font-weight: 800; } +.alg7-qsum-formula .term-bb { color: #047857; font-weight: 800; } +.alg7-qsum-controls { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; + justify-content: center; +} +.alg7-qsum-controls label { + font-size: .88rem; + color: var(--muted); + display: flex; + align-items: center; + gap: 8px; +} +.alg7-qsum-controls input[type="range"] { + width: 140px; + accent-color: var(--sec-acc, #d97706); +} +.alg7-qsum-controls b { + display: inline-block; + min-width: 28px; + padding: 2px 8px; + background: var(--sec-acc-soft, #fef3c7); + color: var(--sec-acc-d, #b45309); + border-radius: 6px; + font-family: 'JetBrains Mono', monospace; + text-align: center; +} +.alg7-qsum-mode { + display: flex; + gap: 6px; + margin-top: 4px; +} +.alg7-qsum-mode button { + padding: 5px 12px; + border-radius: 8px; + background: var(--card); + border: 1.5px solid var(--border); + font-size: .82rem; + font-weight: 700; + cursor: pointer; + transition: all .15s; +} +.alg7-qsum-mode button.active { + background: var(--sec-acc, #d97706); + color: #fff; + border-color: var(--sec-acc, #d97706); +} + +/* === ВИЗУАЛИЗАТОР РАЗНОСТИ КВАДРАТОВ — Ch2 §13 === */ +.alg7-dsq-stage { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 14px; + background: linear-gradient(135deg, rgba(234,88,12,.05), rgba(245,158,11,.05)); + border-radius: 12px; + margin-top: 12px; +} +.alg7-dsq-svg { + width: 100%; + max-width: 420px; + height: auto; +} +.alg7-dsq-svg .big-square { fill: #ea580c; opacity: .75; transition: opacity .4s; } +.alg7-dsq-svg .small-square { fill: #06b6d4; opacity: .85; } +.alg7-dsq-svg .l-shape { + fill: #ea580c; + opacity: .75; + transition: transform .9s cubic-bezier(.4,1.4,.4,1), opacity .4s; + transform-origin: center; +} +.alg7-dsq-svg .rect-piece { + fill: #ea580c; + opacity: .85; + transition: transform .9s cubic-bezier(.4,1.4,.4,1); +} +.alg7-dsq-svg .label { + font-family: 'Unbounded', monospace; + font-weight: 800; + font-size: 12px; + fill: #fff; + text-anchor: middle; + text-shadow: 0 1px 2px rgba(0,0,0,.5); + pointer-events: none; +} +.alg7-dsq-svg .label.dark { fill: #1e293b; text-shadow: none; } +.alg7-dsq-svg .ghost-rect { + fill: none; + stroke: #94a3b8; + stroke-width: 1.5; + stroke-dasharray: 4 3; + opacity: 0; + transition: opacity .4s; +} +.alg7-dsq-svg.stage-2 .ghost-rect { opacity: 1; } +.alg7-dsq-formula { + font-family: 'JetBrains Mono', monospace; + font-size: 1.05rem; + text-align: center; + line-height: 1.8; + padding: 10px 14px; + background: var(--card); + border-radius: 10px; + border: 1px solid var(--border); + width: 100%; +} +.alg7-dsq-step-btn { + padding: 10px 22px; + background: linear-gradient(135deg, #ea580c, #f59e0b); + color: #fff; + border-radius: 12px; + font-weight: 700; + font-size: .92rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; + box-shadow: 0 4px 14px rgba(234,88,12,.3); + transition: transform .15s, box-shadow .15s; +} +.alg7-dsq-step-btn:hover { + transform: translateY(-1px); + box-shadow: 0 8px 22px rgba(234,88,12,.5); +} +.alg7-dsq-step-btn:disabled { + opacity: .55; + cursor: not-allowed; +} diff --git a/frontend/js/alg7-fx.js b/frontend/js/alg7-fx.js new file mode 100644 index 0000000..fd5f129 --- /dev/null +++ b/frontend/js/alg7-fx.js @@ -0,0 +1,416 @@ +/* alg7-fx.js — универсальные эффекты для всех глав Алгебры 7 + * Подключается всеми ch1..ch4. Автоматически: + * - наблюдает за элементами .feedback, при появлении .ok → pulse + комбо, + * при .fail → shake + сброс комбо + * - показывает значок комбо при сериях 3/5/10 правильных подряд + * - даёт бонусный XP через global addXp(), если он определён + * + * Публичный API на window.ALG7: + * ALG7.shake(el), ALG7.pulse(el) + * ALG7.combo, ALG7.maxCombo, ALG7.resetCombo() + * ALG7.buildQuadSumViz(container) — виз. квадрата суммы (Ch2 §12) + * ALG7.buildDiffSquaresViz(container)— виз. разности квадратов (Ch2 §13) + */ +(function(){ +'use strict'; + +if (window.ALG7 && window.ALG7.__installed) return; +const ALG7 = window.ALG7 = window.ALG7 || {}; +ALG7.__installed = true; + +ALG7.combo = 0; +ALG7.maxCombo = 0; + +/* === ANIMATIONS === */ +ALG7.shake = function(el){ + if(!el) return; + el.classList.remove('alg7-shake'); + void el.offsetWidth; + el.classList.add('alg7-shake'); + setTimeout(()=>el.classList.remove('alg7-shake'), 600); +}; +ALG7.pulse = function(el){ + if(!el) return; + el.classList.remove('alg7-pulse'); + void el.offsetWidth; + el.classList.add('alg7-pulse'); + setTimeout(()=>el.classList.remove('alg7-pulse'), 950); +}; + +ALG7.resetCombo = function(silent){ + if(ALG7.combo > 0 && !silent){ + /* fade streak indicator */ + const s = document.querySelector('.alg7-streak'); + if(s){ s.style.opacity = '0'; setTimeout(()=>{ s.classList.remove('show'); s.style.opacity = ''; }, 300); } + } + ALG7.combo = 0; +}; + +function _ensureStreak(){ + let s = document.querySelector('.alg7-streak'); + if(!s){ + s = document.createElement('div'); + s.className = 'alg7-streak'; + s.innerHTML = '3'; + document.body.appendChild(s); + } + return s; +} + +function _showStreak(n){ + const s = _ensureStreak(); + s.classList.add('show'); + s.querySelector('.streak-text').textContent = '×' + n; +} + +function _showComboBadge(n, bonusXp){ + const badge = document.createElement('div'); + badge.className = 'alg7-combo-badge'; + badge.innerHTML = ' КОМБО ×' + n + ' +' + bonusXp + ' XP'; + /* position center top-ish */ + const vw = window.innerWidth, vh = window.innerHeight; + badge.style.left = (vw / 2 - 130) + 'px'; + badge.style.top = (vh / 2 - 80) + 'px'; + document.body.appendChild(badge); + setTimeout(()=>badge.remove(), 2300); +} + +function _addSparkles(el){ + if(!el) return; + const r = el.getBoundingClientRect(); + for(let i = 0; i < 6; i++){ + const sp = document.createElement('div'); + sp.className = 'alg7-sparkle'; + sp.style.left = (r.left + r.width * Math.random()) + 'px'; + sp.style.top = (r.top + r.height * Math.random()) + 'px'; + sp.style.position = 'fixed'; + sp.style.zIndex = '9999'; + sp.style.animationDelay = (i * 60) + 'ms'; + document.body.appendChild(sp); + setTimeout(()=>sp.remove(), 1000); + } +} + +ALG7.onCorrect = function(elm){ + ALG7.combo++; + if(ALG7.combo > ALG7.maxCombo) ALG7.maxCombo = ALG7.combo; + if(elm) { ALG7.pulse(elm); _addSparkles(elm); } + if(ALG7.combo >= 3) _showStreak(ALG7.combo); + /* милестоны: 3, 5, 10 — бонусные XP */ + const milestones = { 3:5, 5:15, 10:50, 15:75, 20:100 }; + if(milestones[ALG7.combo]){ + const bonus = milestones[ALG7.combo]; + _showComboBadge(ALG7.combo, bonus); + if(typeof window.addXp === 'function') window.addXp(bonus, 'combo-' + ALG7.combo); + } +}; + +ALG7.onWrong = function(elm){ + if(elm) ALG7.shake(elm); + ALG7.resetCombo(); +}; + +/* === АВТО-ХУК НА FEEDBACK ЧЕРЕЗ MUTATIONOBSERVER === */ +const _seenFeedback = new WeakMap(); +function _processFeedback(el){ + const display = window.getComputedStyle(el).display; + if(display === 'none') return; + const isOk = el.classList.contains('ok'); + const isFail = el.classList.contains('fail'); + if(!isOk && !isFail) return; + /* Дебаунс — каждый элемент обрабатываем не чаще раза в 700мс */ + const now = Date.now(); + const last = _seenFeedback.get(el) || 0; + if(now - last < 700) return; + _seenFeedback.set(el, now); + + /* Найти ближайший .wg или родительскую карточку */ + const target = el.closest('.wg') || el.closest('.card') || el.parentElement; + if(isOk) ALG7.onCorrect(target); + else ALG7.onWrong(target); +} + +function _initObserver(){ + if(!document.body) { setTimeout(_initObserver, 50); return; } + const obs = new MutationObserver(muts => { + for(const m of muts){ + if(m.type === 'attributes'){ + const t = m.target; + if(t && t.classList && t.classList.contains('feedback')) _processFeedback(t); + } else if(m.type === 'childList'){ + m.addedNodes && m.addedNodes.forEach(n => { + if(n.nodeType === 1 && n.classList && n.classList.contains('feedback')) _processFeedback(n); + }); + } + } + }); + obs.observe(document.body, { subtree:true, attributes:true, childList:true, attributeFilter:['class','style'] }); +} + +if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _initObserver); +else _initObserver(); + + +/* ============================================================ + ВИЗУАЛИЗАТОР КВАДРАТА СУММЫ (Ch2 §12) + Геометрическое доказательство (a+b)² = a² + 2ab + b² + ============================================================ */ +ALG7.buildQuadSumViz = function(container){ + if(!container) return; + container.innerHTML = '' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '' + + '
' + + '
Кликни цветную область — она «подсветится» в формуле
' + + '
'; + + const aSl = container.querySelector('#alg7-qsum-a-sl'); + const bSl = container.querySelector('#alg7-qsum-b-sl'); + const aV = container.querySelector('#alg7-qsum-a'); + const bV = container.querySelector('#alg7-qsum-b'); + const svg = container.querySelector('#alg7-qsum-svg'); + const formula = container.querySelector('#alg7-qsum-formula'); + const modeButtons = container.querySelectorAll('.alg7-qsum-mode button'); + let mode = 'sum'; + + function render(){ + const a = +aSl.value; + const b = +bSl.value; + aV.textContent = a; + bV.textContent = b; + + const isSum = mode === 'sum'; + /* Масштаб: общая сторона = a + b (для sum) или a (для diff) */ + const side = isSum ? (a + b) : a; + const PX = 300; /* размер квадрата в px */ + const offX = 10, offY = 10; + const unit = PX / side; + const aPx = a * unit; + const bPx = b * unit; + + let html = ''; + if(isSum){ + /* Большой квадрат (a+b) × (a+b) разбит на 4 области: + a×a (top-left), a×b (top-right), b×a (bottom-left), b×b (bottom-right) */ + /* 1. a² (top-left, красный) */ + html += ''; + /* 2. a*b (top-right, оранжевый) */ + html += ''; + /* 3. b*a (bottom-left, оранжевый) */ + html += ''; + /* 4. b² (bottom-right, зелёный) */ + html += ''; + /* Подписи */ + if(aPx > 32) html += 'a²='+(a*a)+''; + if(bPx > 22 && aPx > 28) html += 'ab='+(a*b)+''; + if(bPx > 22 && aPx > 28) html += 'ab='+(a*b)+''; + if(bPx > 32) html += 'b²='+(b*b)+''; + /* Стороны (внешняя разметка) */ + html += 'a='+a+''; + html += 'b='+b+''; + html += 'сторона = a+b = '+(a+b)+''; + /* Скобка снизу */ + html += ''; + html += ''; + html += ''; + /* Формула */ + formula.innerHTML = + '$(' + a + ' + ' + b + ')^2 = ' + + '\\underbrace{' + a + '^2}_{\\color{#dc2626}{' + (a*a) + '}} + ' + + '\\underbrace{2 \\cdot ' + a + ' \\cdot ' + b + '}_{\\color{#d97706}{' + (2*a*b) + '}} + ' + + '\\underbrace{' + b + '^2}_{\\color{#047857}{' + (b*b) + '}} = ' + + ((a+b)*(a+b)) + '$'; + } else { + /* DIFF mode: показываем (a-b)² внутри a×a, выделяя верхний-левый угол */ + if(b >= a){ + html += '$b \\ge a$ — выбери $b < a$'; + formula.innerHTML = 'Условие: $b < a$ для $(a-b)^2$'; + } else { + const diff = a - b; + const dPx = diff * unit; + /* (a-b)² в углу (красный) */ + html += ''; + /* Полосы 2ab внутри */ + html += ''; + html += ''; + /* b² в углу */ + html += ''; + /* Подписи */ + if(dPx > 30) html += '(a-b)²='+(diff*diff)+''; + if(bPx > 22 && dPx > 25) html += ''+(diff*b)+''; + if(bPx > 22 && dPx > 25) html += ''+(diff*b)+''; + if(bPx > 30) html += 'b²='+(b*b)+''; + html += 'сторона a='+a+'; b='+b+'; a−b='+diff+''; + /* Формула */ + formula.innerHTML = + '$(' + a + ' - ' + b + ')^2 = a^2 - 2ab + b^2 = ' + + a + '^2 - 2 \\cdot ' + a + ' \\cdot ' + b + ' + ' + b + '^2 = ' + + (a*a) + ' - ' + (2*a*b) + ' + ' + (b*b) + ' = ' + (diff*diff) + '$'; + } + } + svg.innerHTML = html; + if(window.renderMathInElement) try{ window.renderMathInElement(formula); }catch(e){} + + /* Hover-эффект: при клике подсветить термин */ + svg.querySelectorAll('rect[data-part]').forEach(r=>{ + r.style.cursor = 'pointer'; + r.addEventListener('click', ()=>{ + const part = r.dataset.part; + svg.querySelectorAll('rect.area-aa, rect.area-ab, rect.area-bb').forEach(x=>x.classList.add('dim')); + const sameClass = part==='aa' ? 'area-aa' : (part==='bb' ? 'area-bb' : 'area-ab'); + svg.querySelectorAll('.' + sameClass).forEach(x=>x.classList.remove('dim')); + setTimeout(()=>{ svg.querySelectorAll('rect.area-aa, rect.area-ab, rect.area-bb').forEach(x=>x.classList.remove('dim')); }, 1500); + }); + }); + } + + aSl.addEventListener('input', render); + bSl.addEventListener('input', render); + modeButtons.forEach(btn => btn.addEventListener('click', ()=>{ + modeButtons.forEach(b=>b.classList.remove('active')); + btn.classList.add('active'); + mode = btn.dataset.mode; + render(); + })); + render(); +}; + + +/* ============================================================ + ВИЗУАЛИЗАТОР РАЗНОСТИ КВАДРАТОВ (Ch2 §13) + a² - b² = (a-b)(a+b) — анимация перестроения + ============================================================ */ +ALG7.buildDiffSquaresViz = function(container){ + if(!container) return; + container.innerHTML = '' + + '
' + + '
' + + '' + + '' + + '
' + + '' + + '
' + + '' + + '
'; + + const aSl = container.querySelector('#alg7-dsq-a-sl'); + const bSl = container.querySelector('#alg7-dsq-b-sl'); + const aV = container.querySelector('#alg7-dsq-a'); + const bV = container.querySelector('#alg7-dsq-b'); + const svg = container.querySelector('#alg7-dsq-svg'); + const formula = container.querySelector('#alg7-dsq-formula'); + const stepBtn = container.querySelector('#alg7-dsq-step'); + const stepText = container.querySelector('#alg7-dsq-step-text'); + + let stage = 0; /* 0 = большой a² с вырезанной b²; 1 = две части L-формы; 2 = собранный прямоугольник */ + + function render(){ + const a = +aSl.value; + const b = +bSl.value; + aV.textContent = a; + bV.textContent = b; + if(b >= a){ + svg.innerHTML = 'Выбери $b < a$'; + formula.innerHTML = 'Условие: $b < a$'; + stepBtn.disabled = true; + return; + } + stepBtn.disabled = false; + const diff = a - b; + const sum = a + b; + + /* Площади 200x200 для исходного, 200x200 для финального — рядом */ + const PX = 200; + const unit = PX / Math.max(a, sum); /* единичная клетка */ + const aPx = a * unit; + const bPx = b * unit; + const dPx = diff * unit; + const sumPx = sum * unit; + const offX = 20, offY = 30; + const off2X = 250, off2Y = 30; + + let html = ''; + + /* === ЭТАП 0 — исходное состояние === */ + /* Левая сторона: большой квадрат a² */ + html += 'a² (сторона a='+a+')'; + /* Большой квадрат (рыжий) */ + html += ''; + /* Голубой квадрат b² в правом верхнем углу */ + if(stage >= 0){ + html += ''; + html += 'b²='+(b*b)+''; + } + /* Подпись большой стороны */ + html += 'a²='+(a*a)+''; + + /* === ЭТАП 1+ — L-форма (большой квадрат минус b²) разделена на 2 прямоугольника === */ + /* L-форма состоит из двух частей: + - Нижний прямоугольник: a × (a-b) = a × diff + - Верхний-левый прямоугольник: (a-b) × b = diff × b + Они собираются в прямоугольник (a-b) × (a+b) + */ + /* Этап 1: показываем линию разреза в исходном квадрате */ + if(stage === 1){ + const cutY = offY + dPx; /* горизонтальная линия разреза на высоте a-b */ + html += ''; + html += 'разрез'; + } + /* Этап 2: показываем «улетающие» части и финальный прямоугольник справа */ + if(stage === 2){ + /* Призрак исходного квадрата */ + html += ''; + /* Финальный прямоугольник (a-b) × (a+b) справа */ + html += '(a-b)(a+b) = '+(diff*sum)+''; + /* Часть 1: нижний прямоугольник a × (a-b) — поворачиваем в финале на правую часть */ + html += ''; + html += 'a·(a-b)='+(a*diff)+''; + /* Часть 2: верхний-левый прямоугольник (a-b) × b — приклеиваем к правому концу */ + html += ''; + html += 'b(a-b)='+(b*diff)+''; + /* Подписи сторон финального прямоугольника */ + html += 'a+b='+sum+''; + html += 'a-b='+diff+''; + /* Линия разделения */ + html += ''; + } + + svg.innerHTML = html; + + /* Формула */ + let formulaText; + if(stage === 0){ + formulaText = '$a^2 - b^2 = ' + a + '^2 - ' + b + '^2 = ' + (a*a) + ' - ' + (b*b) + ' = ' + (a*a - b*b) + '$'; + stepText.textContent = 'Шаг 1: разрезать L-форму'; + } else if(stage === 1){ + formulaText = 'Разрезаем L-форму горизонтально: $a \\cdot (a-b)$ + $(a-b) \\cdot b$'; + stepText.textContent = 'Шаг 2: собрать в прямоугольник'; + } else { + formulaText = '$a^2 - b^2 = (a-b)(a+b) = ' + diff + ' \\cdot ' + sum + ' = ' + (diff*sum) + '$ ✓'; + stepText.textContent = 'Сбросить'; + } + formula.innerHTML = formulaText; + if(window.renderMathInElement) try{ window.renderMathInElement(formula); }catch(e){} + } + + stepBtn.addEventListener('click', ()=>{ + stage = (stage + 1) % 3; + render(); + }); + aSl.addEventListener('input', ()=>{ stage = 0; render(); }); + bSl.addEventListener('input', ()=>{ stage = 0; render(); }); + render(); +}; + +})(); diff --git a/frontend/textbooks/algebra_7_ch1.html b/frontend/textbooks/algebra_7_ch1.html index 83304b3..40bfd76 100644 --- a/frontend/textbooks/algebra_7_ch1.html +++ b/frontend/textbooks/algebra_7_ch1.html @@ -13,6 +13,8 @@ onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"> + + diff --git a/frontend/textbooks/algebra_7_ch2.html b/frontend/textbooks/algebra_7_ch2.html index a3ce418..d7caa7c 100644 --- a/frontend/textbooks/algebra_7_ch2.html +++ b/frontend/textbooks/algebra_7_ch2.html @@ -13,6 +13,8 @@ onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"> + + @@ -1470,6 +1472,13 @@ function buildP12(){ +trainerHTML('p12-iv3', 4, 'результат') +''; + /* INTERACTIVE 4: Геометрическая визуализация квадрата суммы */ + html += '
' + +'
ИНТЕРАКТИВ 4
Геометрическая визуализация $(a \\pm b)^2$
' + +'
Большой квадрат со стороной $(a+b)$ разделён на 4 цветные области. Подвигай $a, b$ — увидишь живое доказательство формулы! Кликни цвет → подсветка в формуле.
' + +'
' + +'
'; + html += secNav('p11', 'p13') + readButton('p12'); box.innerHTML = html; renderMath(box); @@ -1510,6 +1519,15 @@ function buildP12(){ onComplete:(s,n)=>{ if(s===n){ addXp(12,'p12-iv3'); bumpProgress('p12',20); } else if(s>=2){ addXp(5,'p12-iv3'); bumpProgress('p12',10); } } }); + /* Init quadrat suммы viz from alg7-fx.js */ + if(window.ALG7 && window.ALG7.buildQuadSumViz){ + window.ALG7.buildQuadSumViz(document.getElementById('p12-iv4-viz-host')); + } else { + /* defer if not loaded yet */ + const _try = ()=>{ if(window.ALG7 && window.ALG7.buildQuadSumViz){ window.ALG7.buildQuadSumViz(document.getElementById('p12-iv4-viz-host')); } else setTimeout(_try, 100); }; + _try(); + } + wireReadBtn('p12'); } function buildP13(){ @@ -1548,6 +1566,13 @@ function buildP13(){ +trainerHTML('p13-iv2', 5, 'результат') +''; + /* INTERACTIVE 3: Анимированная визуализация $a^2 - b^2 = (a-b)(a+b)$ */ + html += '
' + +'
ИНТЕРАКТИВ 3
Анимация: $a^2 - b^2 = (a-b)(a+b)$
' + +'
Из квадрата $a^2$ вырезали малый квадрат $b^2$. Жми кнопку «Шаг» — L-форма «расклеится» и соберётся в прямоугольник со сторонами $(a-b)$ и $(a+b)$. Это геометрическое доказательство формулы!
' + +'
' + +'
'; + html += secNav('p12', 'p14') + readButton('p13'); box.innerHTML = html; renderMath(box); @@ -1576,6 +1601,14 @@ function buildP13(){ onComplete:(s,n)=>{ if(s===n){ addXp(12,'p13-iv2'); bumpProgress('p13',22); } else if(s>=3){ addXp(6,'p13-iv2'); bumpProgress('p13',12); } } }); + /* Init diff-squares viz from alg7-fx.js */ + if(window.ALG7 && window.ALG7.buildDiffSquaresViz){ + window.ALG7.buildDiffSquaresViz(document.getElementById('p13-iv3-viz-host')); + } else { + const _try = ()=>{ if(window.ALG7 && window.ALG7.buildDiffSquaresViz){ window.ALG7.buildDiffSquaresViz(document.getElementById('p13-iv3-viz-host')); } else setTimeout(_try, 100); }; + _try(); + } + wireReadBtn('p13'); } function buildP14(){ diff --git a/frontend/textbooks/algebra_7_ch3.html b/frontend/textbooks/algebra_7_ch3.html index 874867f..e43a62e 100644 --- a/frontend/textbooks/algebra_7_ch3.html +++ b/frontend/textbooks/algebra_7_ch3.html @@ -13,6 +13,8 @@ onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"> + +