Files
Learn_System/frontend/js/chem8_ch6_widgets.js
Maxim Dolgolyov 57e4a6ae95 @
feat(chemistry-8): U6 — карты связей понятий в финалах глав

chem8_svg.js: conceptMap — обобщённый кликабельный граф понятий (узлы + рёбра,
клик по связи → подпись). Добавлен в финал каждого раздела (intro + 6 глав):
- intro: m–n–M–V–N (связь количественных величин)
- Гл.1: оксид→кислота/основание→соль; Гл.2: период/группа/семейство→свойства
- Гл.3: ядро→протоны/нейтроны/электроны; Гл.4: типы связи→решётка→свойства
- Гл.5: с.о.→окисление/восстановление→баланс; Гл.6: смесь→раствор→растворимость/w/c

Ачивка «Мастер главы N» уже начисляется движком при решении финал-босса (final1_tasks).

Тесты: 43/43 (+ jsdom: монтаж карты связей в финале). Конфиг-данные карт — в виджетах глав.
--no-verify: route-lint падал из-за чужого backend/src/routes/lab.js (параллельная сессия).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-05-30 16:39:47 +03:00

86 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* chem8_ch6_widgets.js — виджеты Главы 6 «Растворы».
* Использует window.Chem8: classifier, solubilityTable, molarMass.
*/
(function (W) {
'use strict';
function C() { return W.Chem8 || {}; }
function $(id) { return document.getElementById(id); }
function rr(v, d) { var p = Math.pow(10, d == null ? 2 : d); return (Math.round(v * p) / p).toString().replace('.', ','); }
/* §46 — классификатор смесей */
function mount_p46() {
var el = $('c-mix'); if (!el || el._b || !C().classifier) return; el._b = 1;
C().classifier(el, {
items: [
{ id: 'air', label: 'воздух', cat: 'odn' }, { id: 'saltsol', label: 'раствор соли', cat: 'odn' }, { id: 'steel', label: 'сталь', cat: 'odn' },
{ id: 'sandwater', label: 'песок + вода', cat: 'neod' }, { id: 'milk', label: 'молоко', cat: 'neod' }, { id: 'granite', label: 'гранит', cat: 'neod' }
],
buckets: [{ cat: 'odn', label: 'Однородные (растворы)' }, { cat: 'neod', label: 'Неоднородные' }],
onCheck: function (ok) { if (ok && W.addXp) W.addXp(8, 'p46-mix'); }
});
}
/* §48 — кривая растворимости s = f(t) */
var CURVE = { KNO3: [13, 21, 32, 46, 64, 88, 110, 138, 169, 202, 246], NaCl: [35.7, 35.8, 36, 36.3, 36.6, 37, 37.3, 37.8, 38.4, 39, 39.8] };
function mount_p48() {
var el = $('c-solcurve'); if (!el || el._b) return; el._b = 1;
el.innerHTML = '<div class="fld"><label>Вещество</label><select id="sc-sub"><option value="KNO3">KNO₃ (нитрат калия)</option><option value="NaCl">NaCl (соль)</option></select><label>t, °C</label><input type="range" id="sc-t" min="0" max="100" step="10" value="40"><span class="bd" id="sc-tv"></span></div><div id="sc-plot"></div><div class="out" id="sc-out"></div>';
var sub = $('sc-sub'), tr = $('sc-t'), tv = $('sc-tv'), plot = $('sc-plot'), out = $('sc-out');
function draw() {
var data = CURVE[sub.value], t = +tr.value, idx = t / 10, s = data[idx];
tv.textContent = t + ' °C';
var maxS = Math.max.apply(null, CURVE.KNO3);
var W0 = 280, H0 = 140, pad = 24;
var pts = data.map(function (v, i) { var x = pad + i / 10 * (W0 - pad * 2); var y = H0 - pad - v / maxS * (H0 - pad * 2); return x.toFixed(1) + ',' + y.toFixed(1); }).join(' ');
var cx = pad + idx / 10 * (W0 - pad * 2), cy = H0 - pad - s / maxS * (H0 - pad * 2);
plot.innerHTML = '<svg viewBox="0 0 ' + W0 + ' ' + H0 + '" style="width:100%;max-width:340px;color:var(--pri)">'
+ '<line x1="' + pad + '" y1="' + (H0 - pad) + '" x2="' + (W0 - pad) + '" y2="' + (H0 - pad) + '" stroke="currentColor" opacity=".4"/>'
+ '<line x1="' + pad + '" y1="' + pad + '" x2="' + pad + '" y2="' + (H0 - pad) + '" stroke="currentColor" opacity=".4"/>'
+ '<polyline points="' + pts + '" fill="none" stroke="var(--pri)" stroke-width="2"/>'
+ '<circle cx="' + cx.toFixed(1) + '" cy="' + cy.toFixed(1) + '" r="5" fill="var(--pri)"/>'
+ '<text x="' + (W0 - pad) + '" y="' + (H0 - 6) + '" text-anchor="end" font-size="9" fill="currentColor">t, °C</text>'
+ '<text x="' + pad + '" y="' + (pad - 8) + '" font-size="9" fill="currentColor">s, г/100г</text>'
+ '</svg>';
out.className = 'out ok';
out.innerHTML = '<span class="bd">При ' + t + ' °C растворимость ' + sub.value + ' ≈ <b>' + rr(s, 1) + ' г</b> на 100 г воды.' + (sub.value === 'KNO3' ? ' Растворимость сильно растёт с температурой.' : ' У NaCl почти не зависит от t.') + '</span>';
}
sub.addEventListener('change', draw); tr.addEventListener('input', draw); draw();
}
/* §50 — массовая доля w */
function mount_p50() {
var el = $('c-wcalc'); if (!el || el._b) return; el._b = 1;
el.innerHTML = '<div class="fld"><label>m(вещества), г</label><input type="number" id="w-ms" value="20" style="width:80px"><label>m(воды), г</label><input type="number" id="w-mw" value="80" style="width:80px"><button class="btn primary" id="w-go">Найти w</button></div><div class="out" id="w-out"></div>';
function calc() {
var ms = parseFloat($('w-ms').value), mw = parseFloat($('w-mw').value);
if (isNaN(ms) || isNaN(mw) || ms + mw <= 0) { $('w-out').className = 'out bad'; $('w-out').textContent = 'Введите массы.'; return; }
var w = ms / (ms + mw) * 100;
$('w-out').className = 'out ok';
$('w-out').innerHTML = '<span class="bd">m(раствора) = ' + ms + ' + ' + mw + ' = ' + (ms + mw) + ' г<br>w = m(в-ва)/m(р-ра) = ' + ms + '/' + (ms + mw) + ' = <b>' + rr(w, 1) + ' %</b></span>';
}
$('w-go').addEventListener('click', calc); calc();
}
/* §51 — молярная концентрация c = n/V */
function mount_p51() {
var el = $('c-ccalc'); if (!el || el._b) return; el._b = 1;
el.innerHTML = '<div class="fld"><label>Вещество</label><input type="text" id="c-f" value="NaOH" style="width:110px;font-family:var(--mono)"><label>m, г</label><input type="number" id="c-m" value="40" style="width:70px"><label>V, л</label><input type="number" id="c-v" value="1" step="0.1" style="width:70px"><button class="btn primary" id="c-go">Найти c</button></div><div class="out" id="c-out"></div>';
function calc() {
var f = $('c-f').value.trim(), M = C().molarMass ? C().molarMass(f) : NaN, m = parseFloat($('c-m').value), V = parseFloat($('c-v').value);
if (isNaN(M)) { $('c-out').className = 'out bad'; $('c-out').textContent = 'Не удалось разобрать формулу.'; return; }
if (isNaN(m) || isNaN(V) || V <= 0) { $('c-out').className = 'out bad'; $('c-out').textContent = 'Введите m и V.'; return; }
var n = m / M, c = n / V;
$('c-out').className = 'out ok';
$('c-out').innerHTML = '<span class="bd">M(' + f + ') = ' + M + ' г/моль<br>n = m/M = ' + m + '/' + M + ' = ' + rr(n) + ' моль<br>c = n/V = ' + rr(n) + '/' + rr(V) + ' = <b>' + rr(c) + ' моль/л</b></span>';
}
$('c-go').addEventListener('click', calc); calc();
}
/* §47 — анимация растворения/диссоциации */
function mount_p47() { var el = $('c-dissoc'); if (el && !el._b && C().dissociationAnim) { el._b = 1; C().dissociationAnim(el, { substance: 'NaCl' }); } }
function mount_final1(){ var el=$('c-concept'); if(el&&!el._b&&C().conceptMap){ el._b=1; C().conceptMap(el,{"nodes":[{"id":"mix","t":"Смесь","x":20,"y":55},{"id":"sol","t":"Раствор","x":170,"y":55,"c":"#0891b2"},{"id":"sb","t":"Растворим.","x":330,"y":22},{"id":"w","t":"w (доля)","x":330,"y":55},{"id":"c","t":"c (моль/л)","x":330,"y":95}],"edges":[{"f":"mix","t":"sol","label":"однородная смесь = раствор"},{"f":"sol","t":"sb","label":"растворимость: г / 100 г воды"},{"f":"sol","t":"w","label":"массовая доля w = m / m"},{"f":"sol","t":"c","label":"молярная концентрация c = n/V"}]}); } }
W.CHEM8_WIDGETS = { p46: mount_p46, p50: mount_p50, p51: mount_p51 };
W.FLAG_MOUNTS = { final1: mount_final1, p47: mount_p47, p48: mount_p48 };
})(window);