diff --git a/frontend/js/labs/chemsandbox.js b/frontend/js/labs/chemsandbox.js index bf1eeaa..98a373f 100644 --- a/frontend/js/labs/chemsandbox.js +++ b/frontend/js/labs/chemsandbox.js @@ -1585,7 +1585,7 @@ class ChemSandboxSim { if (rx.fx.gas) { questions.push(`Получи газ ${rx.fx.gas}`); } - questions.push(`Проведи реакцию: ${prods}`); + questions.push(`Проведи реакцию: ${_csClean(prods)}`); if (rx.type === 'Нейтрализация') { questions.push('Проведи реакцию нейтрализации'); } @@ -1622,7 +1622,7 @@ class ChemSandboxSim { score: this._quizScore, total: this._quizTotal, result: this._quizResult, - answer: this._quizTask ? this._quizTask.rx.eq : null, + answer: this._quizTask ? _csClean(this._quizTask.rx.eq) : null, }); } } @@ -1821,7 +1821,9 @@ class ChemSandboxSim { const eqEl = document.getElementById('csbar-v4'); eqEl.innerHTML = info.equation || '—'; eqEl.title = (info.equation || '').replace(/<[^>]*>/g, ''); - document.getElementById('csbar-v5').textContent = info.products || '—'; + const prodEl = document.getElementById('csbar-v5'); + prodEl.innerHTML = info.products || '—'; + prodEl.title = (info.products || '').replace(/<[^>]*>/g, ''); const ionEl = document.getElementById('csbar-v6'); ionEl.innerHTML = info.ionNet || '—'; ionEl.title = (info.ionNet || '').replace(/<[^>]*>/g, ''); diff --git a/frontend/js/labs/collision.js b/frontend/js/labs/collision.js index df07e31..05c04c6 100644 --- a/frontend/js/labs/collision.js +++ b/frontend/js/labs/collision.js @@ -709,7 +709,7 @@ class CollisionSim { ctx.fillText(label, ix, iy); } else if (lossPct === 0 && keBefore > 0.1) { const ix = this._impactPt.x, iy = this._impactPt.y - 42; - const label = 'KE сохранена '; + const label = 'KE сохранена ✓'; ctx.font = 'bold 10px Manrope'; const tw = ctx.measureText(label).width; ctx.fillStyle = 'rgba(123,245,164,.15)'; diff --git a/frontend/js/labs/ionexchange.js b/frontend/js/labs/ionexchange.js index 2a16140..5774328 100644 --- a/frontend/js/labs/ionexchange.js +++ b/frontend/js/labs/ionexchange.js @@ -15,11 +15,11 @@ class IonExSim { reacts: ['Ba²⁺', 'SO₄²⁻'], spectators: ['Cl⁻', 'Na⁺'], product: { f: 'BaSO₄', color: '#E0E0E0' }, - mol: 'BaCl₂ + Na₂SO₄ BaSO₄ + 2NaCl', - full_ion: 'Ba²⁺ + 2Cl⁻ + 2Na⁺ + SO₄²⁻ BaSO₄ + 2Na⁺ + 2Cl⁻', - net_ion: 'Ba²⁺ + SO₄²⁻ BaSO₄', + mol: 'BaCl₂ + Na₂SO₄ → BaSO₄↓ + 2NaCl', + full_ion: 'Ba²⁺ + 2Cl⁻ + 2Na⁺ + SO₄²⁻ → BaSO₄↓ + 2Na⁺ + 2Cl⁻', + net_ion: 'Ba²⁺ + SO₄²⁻ → BaSO₄↓', type: 'precip', pcolor: '#E0E0E0', pname: 'BaSO₄ — белый осадок', - sign: '', signColor: '#E0E0E0', + sign: '↓', signColor: '#E0E0E0', }, ag_cl: { name: 'AgNO₃ + NaCl', @@ -28,11 +28,11 @@ class IonExSim { reacts: ['Ag⁺', 'Cl⁻'], spectators: ['NO₃⁻', 'Na⁺'], product: { f: 'AgCl', color: '#F5F5F5' }, - mol: 'AgNO₃ + NaCl AgCl + NaNO₃', - full_ion: 'Ag⁺ + NO₃⁻ + Na⁺ + Cl⁻ AgCl + Na⁺ + NO₃⁻', - net_ion: 'Ag⁺ + Cl⁻ AgCl', + mol: 'AgNO₃ + NaCl → AgCl↓ + NaNO₃', + full_ion: 'Ag⁺ + NO₃⁻ + Na⁺ + Cl⁻ → AgCl↓ + Na⁺ + NO₃⁻', + net_ion: 'Ag⁺ + Cl⁻ → AgCl↓', type: 'precip', pcolor: '#F5F5F5', pname: 'AgCl — белый творожистый осадок', - sign: '', signColor: '#F5F5F5', + sign: '↓', signColor: '#F5F5F5', }, co3_hcl: { name: 'Na₂CO₃ + HCl', @@ -40,12 +40,12 @@ class IonExSim { right: [{ f: 'H⁺', color: '#EF5350', count: 10 }, { f: 'Cl⁻', color: '#AED581', count: 10 }], reacts: ['CO₃²⁻', 'H⁺'], spectators: ['Na⁺', 'Cl⁻'], - product: { f: 'CO₂', color: '#B0BEC5' }, - mol: 'Na₂CO₃ + 2HCl 2NaCl + CO₂ + H₂O', - full_ion: '2Na⁺ + CO₃²⁻ + 2H⁺ + 2Cl⁻ 2Na⁺ + 2Cl⁻ + CO₂ + H₂O', - net_ion: 'CO₃²⁻ + 2H⁺ CO₂ + H₂O', + product: { f: 'CO₂↑', color: '#B0BEC5' }, + mol: 'Na₂CO₃ + 2HCl → 2NaCl + CO₂↑ + H₂O', + full_ion: '2Na⁺ + CO₃²⁻ + 2H⁺ + 2Cl⁻ → 2Na⁺ + 2Cl⁻ + CO₂↑ + H₂O', + net_ion: 'CO₃²⁻ + 2H⁺ → CO₂↑ + H₂O', type: 'gas', gcolor: '#B0BEC5', gname: 'CO₂ — углекислый газ', - sign: '', signColor: '#B0BEC5', + sign: '↑', signColor: '#B0BEC5', }, pb_i: { name: 'Pb(NO₃)₂ + KI', @@ -54,11 +54,11 @@ class IonExSim { reacts: ['Pb²⁺', 'I⁻'], spectators: ['NO₃⁻', 'K⁺'], product: { f: 'PbI₂', color: '#F9A825' }, - mol: 'Pb(NO₃)₂ + 2KI PbI₂ + 2KNO₃', - full_ion: 'Pb²⁺ + 2NO₃⁻ + 2K⁺ + 2I⁻ PbI₂ + 2K⁺ + 2NO₃⁻', - net_ion: 'Pb²⁺ + 2I⁻ PbI₂', + mol: 'Pb(NO₃)₂ + 2KI → PbI₂↓ + 2KNO₃', + full_ion: 'Pb²⁺ + 2NO₃⁻ + 2K⁺ + 2I⁻ → PbI₂↓ + 2K⁺ + 2NO₃⁻', + net_ion: 'Pb²⁺ + 2I⁻ → PbI₂↓', type: 'precip', pcolor: '#F9A825', pname: 'PbI₂ — ярко-жёлтый осадок', - sign: '', signColor: '#F9A825', + sign: '↓', signColor: '#F9A825', }, ca_co3: { name: 'CaCl₂ + Na₂CO₃', @@ -67,11 +67,11 @@ class IonExSim { reacts: ['Ca²⁺', 'CO₃²⁻'], spectators: ['Cl⁻', 'Na⁺'], product: { f: 'CaCO₃', color: '#F5F5F5' }, - mol: 'CaCl₂ + Na₂CO₃ CaCO₃ + 2NaCl', - full_ion: 'Ca²⁺ + 2Cl⁻ + 2Na⁺ + CO₃²⁻ CaCO₃ + 2Na⁺ + 2Cl⁻', - net_ion: 'Ca²⁺ + CO₃²⁻ CaCO₃', + mol: 'CaCl₂ + Na₂CO₃ → CaCO₃↓ + 2NaCl', + full_ion: 'Ca²⁺ + 2Cl⁻ + 2Na⁺ + CO₃²⁻ → CaCO₃↓ + 2Na⁺ + 2Cl⁻', + net_ion: 'Ca²⁺ + CO₃²⁻ → CaCO₃↓', type: 'precip', pcolor: '#F5F5F5', pname: 'CaCO₃ — белый осадок (мел)', - sign: '', signColor: '#F5F5F5', + sign: '↓', signColor: '#F5F5F5', }, }; @@ -466,7 +466,7 @@ class IonExSim { ctx.fillStyle = rxn.signColor; ctx.font = 'bold 10px monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'top'; ctx.shadowColor = rxn.signColor; ctx.shadowBlur = 8; - const label = rxn.type === 'precip' ? ` ${rxn.sign} осадок` : ` ${rxn.sign} газ`; + const label = rxn.type === 'precip' ? `✓ ${rxn.sign} осадок` : `✓ ${rxn.sign} газ`; ctx.fillText(label, W - 14, py + 3); ctx.restore(); } diff --git a/frontend/js/labs/newton.js b/frontend/js/labs/newton.js index eb4d453..308e70e 100644 --- a/frontend/js/labs/newton.js +++ b/frontend/js/labs/newton.js @@ -277,7 +277,7 @@ class NewtonSim { } } - /* ── Физика I-B : орбита прямолинейное движение ────────── */ + /* ── Физика I-B : орбита → прямолинейное движение ────────── */ _step1B(dt) { const s = this._1B; @@ -804,10 +804,10 @@ class NewtonSim { const alpha = Math.min(1, s.forceFlash * 2.5); const fScale = 72 * alpha; const ny = g.gY - CH - 32; - /* Сила на ядро вправо */ - this._arrow(ctx, s.cx + CW / 2 + 20, ny, s.cx + CW / 2 + 20 + fScale, ny, '#EF476F', 'Fядро', 2.5); - /* Реакция на пушку влево */ - this._arrow(ctx, s.cx - CW / 2 - 20, ny, s.cx - CW / 2 - 20 - fScale, ny, '#4CC9F0', 'Fпушка', 2.5); + /* Сила на ядро → вправо */ + this._arrow(ctx, s.cx + CW / 2 + 20, ny, s.cx + CW / 2 + 20 + fScale, ny, '#EF476F', 'F→ядро', 2.5); + /* Реакция на пушку → влево */ + this._arrow(ctx, s.cx - CW / 2 - 20, ny, s.cx - CW / 2 - 20 - fScale, ny, '#4CC9F0', 'F→пушка', 2.5); ctx.save(); ctx.globalAlpha = alpha; ctx.font = 'bold 12px sans-serif'; ctx.fillStyle = '#FFD166'; @@ -990,7 +990,7 @@ class NewtonSim { } /* Falling after fuel out — show gravity arrow */ if (s.fuel <= 0 && !s.stopped) { - this._arrow(ctx, rx, ry + 25, rx, ry + 65, '#EF476F', 'mg', 2.5); + this._arrow(ctx, rx, ry + 25, rx, ry + 65, '#EF476F', 'mg↓', 2.5); ctx.font = 'bold 13px sans-serif'; ctx.fillStyle = '#EF476F'; ctx.textAlign = 'center'; ctx.fillText('Топливо кончилось — ракета падает!', W / 2, H * 0.15); ctx.textAlign = 'left'; } @@ -1009,7 +1009,7 @@ class NewtonSim { ctx.textAlign = 'center'; ctx.fillText('Нажмите «Запуск» для включения двигателя', W / 2, H * 0.50); ctx.textAlign = 'left'; } - this._caption(ctx, 'Газ вниз ракета вверх\n(3-й закон Ньютона)', W, H); + this._caption(ctx, 'Газ вниз → ракета вверх\n(3-й закон Ньютона)', W, H); } /* ── Вспомогательные рисовалки ──────────────────────────── */ @@ -1350,8 +1350,8 @@ function _nwt_lighten(hex, d) { // action button label const lbl = sceneData.action || (law === 1 ? ' Нить' : ' Действие'); - document.getElementById('newton-action-label').textContent = lbl; - document.getElementById('newton-action-top').textContent = lbl; + document.getElementById('newton-action-label').innerHTML = lbl; + document.getElementById('newton-action-top').innerHTML = lbl; // show/hide sliders document.getElementById('newton-mu-block').style.display = law === 1 && scene === 'A' ? '' : 'none'; diff --git a/frontend/js/labs/projectile.js b/frontend/js/labs/projectile.js index aa1be0c..da29f42 100644 --- a/frontend/js/labs/projectile.js +++ b/frontend/js/labs/projectile.js @@ -147,8 +147,8 @@ class ProjectileSim { } } const st = this.stats(); - const windStr = this.wind !== 0 ? ` ${this.wind > 0 ? '+' : ''}${this.wind}` : ''; - const label = `${this.angle}° ${this.v0}м/с${windStr}${this.drag ? ' +drag' : ''}${this.bounce ? ' ' : ''}`; + const windStr = this.wind !== 0 ? ` ветер ${this.wind > 0 ? '+' : ''}${this.wind}` : ''; + const label = `${this.angle}° ${this.v0}м/с${windStr}${this.drag ? ' +drag' : ''}${this.bounce ? ' ↩' : ''}`; const color = this._GHOST_COLORS[this._ghostIdx % this._GHOST_COLORS.length]; this._ghostIdx++; this._ghosts.push({ points, color, label, range: st.range, hMax: st.hMax }); @@ -811,12 +811,12 @@ class ProjectileSim { bRight -= 130; } if (this.wind !== 0) { - const dir = this.wind > 0 ? '' : ''; + const dir = this.wind > 0 ? '→' : '←'; this._drawBadge(ctx, bRight, PT + 6, dir + ' ветер ' + Math.abs(this.wind) + 'м/с', 'rgba(6,214,224,.12)', 'rgba(6,214,224,.8)'); bRight -= 130; } if (this.bounce) { - this._drawBadge(ctx, bRight, PT + 6, ' e=' + this.restitution.toFixed(2), 'rgba(123,245,164,.1)', 'rgba(123,245,164,.75)'); + this._drawBadge(ctx, bRight, PT + 6, '↩ e=' + this.restitution.toFixed(2), 'rgba(123,245,164,.1)', 'rgba(123,245,164,.75)'); } /* speed badge bottom-right */ @@ -1077,7 +1077,7 @@ function _projArrow(ctx, x1, y1, x2, y2, color, lw) { pSim.onPlayPause = projPlayPause; } pSim.fit(); - projParam(); // sync sliders sim + projParam(); // sync sliders → sim pSim.draw(); _projUpdateUI(pSim.stats()); })); @@ -1187,7 +1187,7 @@ function _projArrow(ctx, x1, y1, x2, y2, color, lw) { function projWindChange() { const wind = +document.getElementById('sl-wind').value; - const label = wind === 0 ? '0 м/с' : (wind > 0 ? ' +' : ' ') + Math.abs(wind) + ' м/с'; + const label = wind === 0 ? '0 м/с' : (wind > 0 ? '→ +' : '← ') + Math.abs(wind) + ' м/с'; document.getElementById('p-wind').textContent = label; document.getElementById('ps-loss-wrap').style.display = wind !== 0 ? '' : (pSim && pSim.drag ? '' : 'none'); if (pSim) { pSim.setParams({ wind }); _projSyncPlayBtn(); } diff --git a/frontend/js/labs/reactions.js b/frontend/js/labs/reactions.js index 7f89eba..9d0225c 100644 --- a/frontend/js/labs/reactions.js +++ b/frontend/js/labs/reactions.js @@ -564,7 +564,7 @@ class ReactionSim { ctx.fillText('C', ex + ew, toY(pE) - 4); // Mode label at bottom - const modeTxt = { forward: ' A + B C', reversible: '⇌ A + B ⇌ C', chain: 'цепная реакция' }[this.mode] || ''; + const modeTxt = { forward: '→ A + B → C', reversible: '⇌ A + B ⇌ C', chain: 'цепная реакция' }[this.mode] || ''; ctx.fillStyle = 'rgba(255,255,255,0.22)'; ctx.font = '8px sans-serif'; ctx.textAlign = 'center';