@
feat(chemistry-8): Phase 0 — каркас учебника «Химия 8» (hub + 7 глав) Архитектура hub + главы (как физика 7–11, алгебра, геометрия), не монолит. - chemistry_8_hub.html: хаб-каталог 7 разделов, amber-палитра, прогресс из /api/textbooks/chemistry-8/children, achievement «Химик 8 класса» - 7 каркасов глав (вводный + гл.1–6, §1–52) с оглавлением и баннером «в разработке» - /js/chem8_svg.js: неймспейс Chem8 (formula/ionLabel/chemEq готовы, 13 хелперов-заглушек) - миграция 041: родитель chemistry-8 + 7 детей (parent_slug), para_count сумма = 52 - gen_chem8_skeletons.js: генератор каркасов глав - tests/chemistry8.test.js: 9 тестов (примитивы + целостность каркаса), все зелёные - PLAN_CHEMISTRY_8.md обновлён под hub-архитектуру Источник: Шиманович, Красицкий, Сечко, Хвалюк. Химия 8, Народная асвета, 2018. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/* chem8_svg.js — химические наглядные примитивы для учебника «Химия 8».
|
||||
*
|
||||
* Неймспейс: window.Chem8.*
|
||||
* Молекулярные модели (структурные / шаростержневые / 3D) — НЕ здесь, а через
|
||||
* biochem-core.js (window.BioChem). Здесь только то, чего там нет: рендер формул и
|
||||
* уравнений, ионы, степени окисления, интерактивные виджеты (растворимость, ряд
|
||||
* активности, индикаторы, классификаторы, калькуляторы расчётов и т. п.).
|
||||
*
|
||||
* Phase 0: реализованы чистые текстовые примитивы (ionLabel, chemEq, formula).
|
||||
* Остальные хелперы — каркасы-заглушки, наполняются по фазам (см. PLAN_CHEMISTRY_8.md, разд. B).
|
||||
*
|
||||
* Правила (CLAUDE.md / план):
|
||||
* - без эмоджи, только inline SVG .ic;
|
||||
* - в KaTeX-шаблонах двойной backslash (\\to, \\downarrow, \\rightleftharpoons);
|
||||
* - drag/слайдеры: window-listeners + state ВЫШЕ redraw(), без setPointerCapture.
|
||||
*/
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
var SUB = { '0':'₀','1':'₁','2':'₂','3':'₃','4':'₄',
|
||||
'5':'₅','6':'₆','7':'₇','8':'₈','9':'₉' };
|
||||
var SUP = { '0':'⁰','1':'¹','2':'²','3':'³','4':'⁴',
|
||||
'5':'⁵','6':'⁶','7':'⁷','8':'⁸','9':'⁹',
|
||||
'+':'⁺','-':'⁻' };
|
||||
|
||||
function toSub(digits) {
|
||||
return String(digits).replace(/[0-9]/g, function (d) { return SUB[d]; });
|
||||
}
|
||||
function toSup(s) {
|
||||
return String(s).replace(/[0-9+\-]/g, function (c) { return SUP[c] || c; });
|
||||
}
|
||||
|
||||
/* formula('CaCO3') -> 'CaCO₃' : числовые индексы атомов в подстрочные.
|
||||
Не трогает множители-коэффициенты в начале (их рендерит chemEq). */
|
||||
function formula(src) {
|
||||
if (src == null) return '';
|
||||
return String(src).replace(/([A-Za-z\)\]])(\d+)/g, function (_, a, n) {
|
||||
return a + toSub(n);
|
||||
});
|
||||
}
|
||||
|
||||
/* ionLabel('SO4', -2) -> 'SO₄²⁻' ; ionLabel('Ca', 2) -> 'Ca²⁺' ; ionLabel('Na', 1) -> 'Na⁺' */
|
||||
function ionLabel(form, charge) {
|
||||
var body = formula(form);
|
||||
var c = Number(charge) || 0;
|
||||
if (c === 0) return body;
|
||||
var mag = Math.abs(c);
|
||||
var sign = c > 0 ? '+' : '-';
|
||||
var num = mag === 1 ? '' : String(mag);
|
||||
return body + toSup(num + sign);
|
||||
}
|
||||
|
||||
/* chemEq('2Na + 2H2O -> 2NaOH + H2^', {arrow:'->'}) -> HTML-строка с индексами,
|
||||
стрелками (= → ⇌), значками газа (↑) и осадка (↓), условием над стрелкой.
|
||||
Токены: '->'/'=' необратимая, '<->'/'<=>' обратимая, '^' газ, 'v' осадок.
|
||||
opts.cond — подпись над стрелкой (например 't', 'кат.', 'эл. ток'). */
|
||||
function chemEq(src, opts) {
|
||||
opts = opts || {};
|
||||
var s = String(src == null ? '' : src).trim();
|
||||
var arrowHtml = ' <span class="ceq-arrow">' + arrowGlyph(s, opts) + condHtml(opts) + '</span> ';
|
||||
// выделяем стрелку
|
||||
var parts = s.split(/<->|<=>|->|⇌|=(?![^(]*\))|→/);
|
||||
var left = parts[0] || '';
|
||||
var right = parts.length > 1 ? parts.slice(1).join(' ') : '';
|
||||
var html = renderSide(left);
|
||||
if (right) html += arrowHtml + renderSide(right);
|
||||
return '<span class="ceq">' + html + '</span>';
|
||||
}
|
||||
|
||||
function arrowGlyph(s, opts) {
|
||||
if (opts.arrow === '<->' || opts.arrow === '<=>' || /<->|<=>|⇌/.test(s)) return '⇌';
|
||||
return '→'; // →
|
||||
}
|
||||
function condHtml(opts) {
|
||||
if (!opts.cond) return '';
|
||||
return '<sup class="ceq-cond">' + escapeHtml(opts.cond) + '</sup>';
|
||||
}
|
||||
|
||||
/* одна сторона уравнения: разбор на вещества по '+', значки ↑/↓ */
|
||||
function renderSide(side) {
|
||||
return side.split('+').map(function (term) {
|
||||
var t = term.trim();
|
||||
if (!t) return '';
|
||||
var gas = false, prec = false;
|
||||
t = t.replace(/\^|↑/g, function () { gas = true; return ''; })
|
||||
.replace(/(^|[A-Za-z0-9\)])v(\b|$)|↓/g, function (m) {
|
||||
prec = true; return m.replace(/v|↓/, '');
|
||||
});
|
||||
// коэффициент в начале
|
||||
var coef = '';
|
||||
t = t.replace(/^(\d+)/, function (_, n) { coef = n; return ''; });
|
||||
var out = (coef ? coef : '') + formula(t.trim());
|
||||
if (gas) out += '↑';
|
||||
if (prec) out += '↓';
|
||||
return out;
|
||||
}).filter(Boolean).join(' + ');
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, function (c) {
|
||||
return { '&':'&','<':'<','>':'>','"':'"',"'":''' }[c];
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Каркасы-заглушки интерактивных виджетов (реализуются по фазам) ---- */
|
||||
function notImplemented(name) {
|
||||
return function () {
|
||||
if (global.console && console.warn) {
|
||||
console.warn('[Chem8] ' + name + ' ещё не реализован (Phase 0 заглушка)');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
var Chem8 = {
|
||||
// готово (Phase 0)
|
||||
formula: formula,
|
||||
ionLabel: ionLabel,
|
||||
chemEq: chemEq,
|
||||
toSub: toSub,
|
||||
toSup: toSup,
|
||||
// заглушки (см. план, разд. B) — наполняются в Phase 1–6
|
||||
testTube: notImplemented('testTube'), // §18,25 — пробирка: осадок/газ/окраска
|
||||
moleTriangle: notImplemented('moleTriangle'), // §6 — треугольник n–m–M
|
||||
equationBalancer: notImplemented('equationBalancer'),// §8 — балансировщик уравнений
|
||||
oxStateCalc: notImplemented('oxStateCalc'), // §42 — калькулятор степени окисления
|
||||
redoxBalancer: notImplemented('redoxBalancer'), // §44 — e-баланс ОВР
|
||||
orbitalDiagram: notImplemented('orbitalDiagram'), // §33 — орбитальная диаграмма
|
||||
solubilityTable: notImplemented('solubilityTable'), // §19,20,48 — таблица растворимости
|
||||
activitySeries: notImplemented('activitySeries'), // §14,20 — ряд активности металлов
|
||||
miniPeriodic: notImplemented('miniPeriodic'), // §1,26,34 — мини-ПСХЭ с подсветкой
|
||||
indicatorScale: notImplemented('indicatorScale'), // §13,14,16,17 — индикатор + шкала pH
|
||||
dissociationAnim: notImplemented('dissociationAnim'),// §47 — анимация растворения
|
||||
classifier: notImplemented('classifier'), // §10,13,16,19,46 — DnD-классификатор
|
||||
geneticMap: notImplemented('geneticMap') // §22 — генетическая карта-граф классов
|
||||
};
|
||||
|
||||
global.Chem8 = Chem8;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
Reference in New Issue
Block a user