/* 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 = ' ' + arrowGlyph(s, opts) + condHtml(opts) + ' '; // выделяем стрелку 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 '' + html + ''; } function arrowGlyph(s, opts) { if (opts.arrow === '<->' || opts.arrow === '<=>' || /<->|<=>|⇌/.test(s)) return '⇌'; return '→'; // → } function condHtml(opts) { if (!opts.cond) return ''; return '' + escapeHtml(opts.cond) + ''; } /* одна сторона уравнения: разбор на вещества по '+', значки ↑/↓ */ 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);