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';