chore: консолидация незакоммиченной работы (биохимия + System Health + lab/textbooks)

Зафиксирована накопленная незакоммиченная работа рабочего дерева, КРОМЕ файлов
учебника «Химия 7» (migration 046, chemistry_7_*.html, chem7_svg.js, тест —
оставлены незакоммиченными по запросу).

Включает: модуль биохимии (ядро BIO, 3D VSEPR, химдвижок, баланс, challenges,
пути из БД), System Health Level 1 (вердикт/мониторинг), а также frontend-
страницы и lab/textbooks-правки параллельной сессии.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 18:12:55 +03:00
parent 6c1e003340
commit 5381679c68
55 changed files with 10203 additions and 305 deletions
+1 -2
View File
@@ -294,8 +294,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0, 2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin', 'teacher'].includes(user?.role);
if (!isTeacher) { location.href = '/dashboard'; throw new Error(); }
+46 -242
View File
@@ -475,244 +475,7 @@
// ═══════════════════════════════════════════════════════
// PATHWAY DATA
// ═══════════════════════════════════════════════════════
const PATHWAYS = {
glycolysis: {
name: 'Гликолиз',
color: '#f59e0b',
colorRgb: '245,158,11',
desc: '10 реакций расщепления глюкозы до пирувата. Происходит в цитоплазме. Выход: 2 АТФ (нетто), 2 НАДН, 2 пируват.',
stats: [
{ label: '2 АТФ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> +4 АТФ', cls: 'atp' },
{ label: '2 НАДН', cls: 'nadh' },
],
legend: [
{ color: '#f59e0b', type: 'circle', label: 'Метаболит' },
{ color: '#f59e0b', type: 'line', label: 'Реакция' },
{ color: '#f59e0b88', type: 'circle-sm', label: 'Кофактор (АТФ/НАД)' },
],
// nodes: id, label, formula, x, y, role
nodes: [
{ id:'glc', label:'Глюкоза', formula:'C₆H₁₂O₆', x:400, y:60, role:'substrate', desc:'Исходный субстрат гликолиза. 6-углеродный сахар, главный источник энергии клетки.', props:[] },
{ id:'g6p', label:'Глюкозо-6-Ф', formula:'C₆H₁₃O₉P', x:400, y:145, role:'inter', desc:'Глюкозо-6-фосфат. Образуется при фосфорилировании глюкозы за счёт АТФ. Удерживает молекулу в клетке.', props:['1 АТФ'] },
{ id:'f6p', label:'Фруктозо-6-Ф',formula:'C₆H₁₃O₉P', x:400, y:225, role:'inter', desc:'Изомер глюкозо-6-фосфата. Образуется при изомеризации ферментом фосфоглюкоизомеразой.', props:[] },
{ id:'f16bp', label:'Фруктозо-1,6-бФ',formula:'C₆H₁₄O₁₂P₂', x:400, y:310, role:'key', desc:'Фруктозо-1,6-бисфосфат — ключевой регуляторный метаболит. Образование катализирует фосфофруктокиназа-1 (ФФК-1).', props:['1 АТФ', 'Контроль скорости'] },
{ id:'dhap', label:'ДГАФ', formula:'C₃H₇O₆P', x:260, y:395, role:'inter', desc:'Дигидроксиацетонфосфат — один из двух триозофосфатов при расщеплении фруктозо-1,6-бисфосфата. Быстро конвертируется в ГАФ.', props:[] },
{ id:'gap', label:'ГАФ', formula:'C₃H₇O₆P', x:540, y:395, role:'inter', desc:'Глицеральдегид-3-фосфат (ГАФ) — непосредственный субстрат следующих реакций. Оба триозофосфата канализируются через ГАФ.', props:[] },
{ id:'bpg', label:'1,3-бФГ', formula:'C₃H₈O₁₀P₂',x:540, y:480, role:'inter', desc:'1,3-бисфосфоглицерат. Образуется при окислении ГАФ, сопряжённом с восстановлением НАД⁺ в НАДН.', props:['2 НАДН'] },
{ id:'pg3', label:'3-ФГК', formula:'C₃H₇O₇P', x:540, y:560, role:'inter', desc:'3-фосфоглицерат. Образуется при субстратном фосфорилировании АДФ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> АТФ ферментом фосфоглицераткиназой.', props:['+2 АТФ'] },
{ id:'pg2', label:'2-ФГК', formula:'C₃H₇O₇P', x:540, y:635, role:'inter', desc:'2-фосфоглицерат. Образуется при перемещении фосфатной группы с 3 на 2 положение.', props:[] },
{ id:'pep', label:'ФЕП', formula:'C₃H₅O₆P', x:540, y:710, role:'inter', desc:'Фосфоенолпируват (ФЕП) — высокоэнергетический промежуточный продукт. Образуется при дегидратации 2-ФГК.', props:[] },
{ id:'pyr', label:'Пируват', formula:'C₃H₄O₃', x:400, y:795, role:'product', desc:'Конечный продукт гликолиза. В аэробных условиях переходит в ацетил-КоА (цикл Кребса). В анаэробных <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> лактат или этанол.', props:['+2 АТФ', '2 молекулы'] },
],
edges: [
{ from:'glc', to:'g6p', enzyme:'Гексокиназа', co:'-АТФ', curveX:0 },
{ from:'g6p', to:'f6p', enzyme:'ФГИ', curveX:0 },
{ from:'f6p', to:'f16bp', enzyme:'ФФК-1', co:'-АТФ', curveX:0 },
{ from:'f16bp',to:'dhap', enzyme:'Альдолаза', curveX:0 },
{ from:'f16bp',to:'gap', enzyme:'Альдолаза', curveX:0 },
{ from:'dhap', to:'gap', enzyme:'ТФИ', curveX:0 },
{ from:'gap', to:'bpg', enzyme:'ГАФДГ', co:'+НАДН', curveX:0 },
{ from:'bpg', to:'pg3', enzyme:'ФГК', co:'+АТФ', curveX:0 },
{ from:'pg3', to:'pg2', enzyme:'Фосфоглицератмутаза', curveX:0 },
{ from:'pg2', to:'pep', enzyme:'Енолаза', curveX:0 },
{ from:'pep', to:'pyr', enzyme:'Пируваткиназа', co:'+АТФ', curveX:0 },
],
steps: [
{
title:'Фосфорилирование глюкозы',
mol:'g6p',
desc:'Гексокиназа катализирует перенос фосфатной группы с АТФ на глюкозу, образуя глюкозо-6-фосфат (Г6Ф). Реакция необратима и «ловит» глюкозу в клетке.',
energy:[{label:'-1 АТФ', cls:'atp-used'}],
quiz:{ q:'Зачем глюкозу фосфорилируют в первой реакции?', opts:['Для выхода из клетки','Чтобы удержать глюкозу в клетке','Для образования НАДН','Для расщепления кольца'], ans:1 }
},
{
title:'Изомеризация',
mol:'f6p',
desc:'Фосфоглюкоизомераза превращает Г6Ф в фруктозо-6-фосфат (Ф6Ф). Реакция обратима и перестраивает альдозный сахар в кетозный.',
energy:[],
quiz:{ q:'Какой фермент катализирует изомеризацию Г6Ф <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> Ф6Ф?', opts:['Гексокиназа','Альдолаза','Фосфоглюкоизомераза','Пируваткиназа'], ans:2 }
},
{
title:'Ключевой контрольный шаг',
mol:'f16bp',
desc:'Фосфофруктокиназа-1 (ФФК-1) фосфорилирует Ф6Ф <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> фруктозо-1,6-бисфосфат. Это необратимая реакция — главный регуляторный пункт гликолиза. АТФ ингибирует, АМФ/АДФ активирует.',
energy:[{label:'-1 АТФ', cls:'atp-used'}],
quiz:{ q:'Что является главным аллостерическим активатором ФФК-1?', opts:['АТФ','АМФ','НАДН','Пируват'], ans:1 }
},
{
title:'Расщепление на триозы',
mol:'gap',
desc:'Альдолаза расщепляет фруктозо-1,6-бисфосфат на два триозофосфата: ДГАФ и ГАФ (глицеральдегид-3-фосфат). Триозофосфатизомераза быстро конвертирует ДГАФ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> ГАФ.',
energy:[],
quiz:{ q:'Сколько молекул ГАФ образуется из одной глюкозы?', opts:['1','2','3','4'], ans:1 }
},
{
title:'Окислительное фосфорилирование',
mol:'bpg',
desc:'ГАФДГ окисляет ГАФ и присоединяет неорганический фосфат <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> 1,3-бисфосфоглицерат. Сопряжено с восстановлением НАД⁺ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> НАДН. Реакция субстратного фосфорилирования.',
energy:[{label:'+2 НАДН', cls:'nadh'}],
quiz:{ q:'Чем восстанавливается НАД⁺ в этой реакции?', opts:['ГАФ','Пируват','ДГАФ','АТФ'], ans:0 }
},
{
title:'Первая выработка АТФ',
mol:'pg3',
desc:'Фосфоглицераткиназа переносит фосфат с 1,3-бФГ на АДФ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> АТФ. Это субстратное фосфорилирование — первый синтез АТФ в гликолизе. С каждой глюкозы получаем 2 АТФ.',
energy:[{label:'+2 АТФ', cls:'atp-prod'}],
quiz:{ q:'Как называется тип синтеза АТФ в этой реакции?', opts:['Окислительное фосфорилирование','Субстратное фосфорилирование','Фотофосфорилирование','Трансфосфорилирование'], ans:1 }
},
{
title:'Мутация фосфатной группы',
mol:'pg2',
desc:'Фосфоглицератмутаза перемещает фосфатную группу с 3-го на 2-е углеродное положение, подготавливая молекулу к дегидратации.',
energy:[],
quiz:{ q:'Какой продукт образуется из 3-ФГК под действием мутазы?', opts:['ФЕП','2-ФГК','Пируват','1,3-бФГ'], ans:1 }
},
{
title:'Образование ФЕП',
mol:'pep',
desc:'Енолаза катализирует дегидратацию 2-фосфоглицерата <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> фосфоенолпируват (ФЕП). ФЕП — высокоэнергетический соединение с большой отрицательной ΔG° гидролиза фосфата.',
energy:[],
quiz:{ q:'Почему ФЕП называют «высокоэнергетическим»?', opts:['Содержит много атомов С','Большая ΔG° гидролиза фосфатной связи','Растворяется в жирах','Содержит двойную связь'], ans:1 }
},
{
title:'Финальная реакция — пируват',
mol:'pyr',
desc:'Пируваткиназа переносит фосфат с ФЕП на АДФ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> АТФ + пируват. Необратимая реакция. Итог: из 1 глюкозы 2 пирувата, 2 НАДН, +2 АТФ нетто.',
energy:[{label:'+2 АТФ', cls:'atp-prod'},{label:'2 пируват', cls:'co2'}],
quiz:{ q:'Каков нетто-выход АТФ на 1 молекулу глюкозы в гликолизе?', opts:['1','2','4','36'], ans:1 }
},
]
},
krebs: {
name: 'Цикл Кребса',
color: '#06b6d4',
colorRgb: '6,182,212',
desc: '8 реакций окисления ацетил-КоА. Происходит в матриксе митохондрий. Выход на 1 оборот: 3 НАДН, 1 ФАДН₂, 1 ГТФ, 2 СО₂.',
stats: [
{ label: '3 НАДН / оборот', cls: 'nadh' },
{ label: '2 CO₂', cls: 'co2' },
{ label: '1 ГТФ', cls: 'atp' },
],
legend: [
{ color: '#06b6d4', type: 'circle', label: 'Промежуточный метаболит' },
{ color: '#06b6d4', type: 'line', label: 'Реакция цикла' },
],
nodes: [
{ id:'acetcoa', label:'Ацетил-КоА', formula:'CH₃CO-SCoA',x:440, y:80, role:'substrate', desc:'Активированный ацетат. Образуется из пирувата (гликолиз), жирных кислот (β-окисление) и аминокислот.', props:['Входит в цикл'] },
{ id:'oaa', label:'ОАА', formula:'C₄H₄O₅', x:260, y:140, role:'key', desc:'Оксалоацетат — акцептор ацетил-КоА. Регенерируется в каждом обороте цикла. Ключевой анаплеротический метаболит.', props:['Акцептор'] },
{ id:'cit', label:'Цитрат', formula:'C₆H₈O₇', x:160, y:260, role:'inter', desc:'Цитрат — первый продукт цикла. Синтезируется цитратсинтазой из ацетил-КоА и ОАА.', props:[] },
{ id:'isocit', label:'Изоцитрат', formula:'C₆H₈O₇', x:120, y:390, role:'inter', desc:'Изоцитрат — изомер цитрата. Субстрат изоцитратдегидрогеназы — ключевого регуляторного фермента.', props:[] },
{ id:'akg', label:'α-КГ', formula:'C₅H₆O₅', x:160, y:520, role:'inter', desc:'α-кетоглутарат (α-КГ). Образуется при окислительном декарбоксилировании изоцитрата. Выделяется CO₂.', props:['CO₂','+НАДН'] },
{ id:'succoa', label:'Сукцинил-КоА',formula:'C₅H₆O₃S', x:300, y:620, role:'inter', desc:'Сукцинил-КоА — высокоэнергетический тиоэфир. Образуется при окислительном декарбоксилировании α-КГ.', props:['+НАДН','+ГТФ','-CO₂'] },
{ id:'succ', label:'Сукцинат', formula:'C₄H₆O₄', x:480, y:620, role:'inter', desc:'Сукцинат. Окисляется сукцинатдегидрогеназой (СДГ) — единственным мембранным ферментом цикла.', props:['+ФАДН₂'] },
{ id:'fum', label:'Фумарат', formula:'C₄H₄O₄', x:620, y:520, role:'inter', desc:'Фумарат — транс-изомер. Образуется при окислении сукцината. Гидратируется фумаразой.', props:[] },
{ id:'mal', label:'Малат', formula:'C₄H₆O₅', x:660, y:390, role:'inter', desc:'Малат (яблочная кислота). Образуется при гидратации фумарата. Окисляется малатдегидрогеназой.', props:['+НАДН'] },
],
edges: [
{ from:'acetcoa',to:'cit', enzyme:'Цитратсинтаза', co:'+ОАА', curveX:0 },
{ from:'oaa', to:'cit', enzyme:'', curveX:0 },
{ from:'cit', to:'isocit', enzyme:'Аконитаза', curveX:0 },
{ from:'isocit', to:'akg', enzyme:'ИзоцитратДГ', co:'+НАДН,-CO₂', curveX:0 },
{ from:'akg', to:'succoa', enzyme:'α-КГДГ-комплекс', co:'+НАДН,-CO₂', curveX:0 },
{ from:'succoa', to:'succ', enzyme:'Сукцинил-КоА-синтетаза', co:'+ГТФ', curveX:0 },
{ from:'succ', to:'fum', enzyme:'Сукцинатдегидрогеназа', co:'+ФАДН₂', curveX:0 },
{ from:'fum', to:'mal', enzyme:'Фумараза', curveX:0 },
{ from:'mal', to:'oaa', enzyme:'МалатДГ', co:'+НАДН', curveX:0 },
],
steps: [
{ title:'Конденсация с ОАА', mol:'cit', desc:'Цитратсинтаза присоединяет ацетил-КоА (2C) к оксалоацетату (4C) <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> цитрат (6C). Это необратимая реакция, запускающая цикл.', energy:[], quiz:{q:'Сколько углеродов в цитрате?', opts:['2','4','6','8'], ans:2} },
{ title:'Изомеризация цитрата', mol:'isocit', desc:'Аконитаза через промежуточный цис-аконитат превращает цитрат в изоцитрат. Реакция обратима, равновесие сдвинуто в сторону цитрата.', energy:[], quiz:{q:'Какой фермент изомеризует цитрат?', opts:['Фумараза','Аконитаза','Малатдегидрогеназа','Цитратсинтаза'], ans:1} },
{ title:'Первое окислительное декарбоксилирование', mol:'akg', desc:'Изоцитратдегидрогеназа окисляет изоцитрат <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> α-кетоглутарат с выделением CO₂ и НАДН. Ключевой регуляторный шаг — активируется изоцитратом, ингибируется НАДН.', energy:[{label:'+НАДН',cls:'nadh'},{label:'-CO₂',cls:'co2'}], quiz:{q:'Сколько углеродов в α-кетоглутарате?', opts:['2','4','5','6'], ans:2} },
{ title:'Второе окислительное декарбоксилирование', mol:'succoa', desc:'α-кетоглутаратдегидрогеназный комплекс (аналог ПДК) окисляет α-КГ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> сукцинил-КоА. Выделяется ещё одна CO₂ и НАДН.', energy:[{label:'+НАДН',cls:'nadh'},{label:'-CO₂',cls:'co2'}], quiz:{q:'Чем структурно похож α-КГДК на пируватдегидрогеназный комплекс?', opts:['Использует ФАДН₂','Механизм окислительного декарбоксилирования с КоА','Находится в цитоплазме','Требует витамин К'], ans:1} },
{ title:'Субстратное фосфорилирование', mol:'succ', desc:'Сукцинил-КоА-синтетаза расщепляет тиоэфирную связь сукцинил-КоА, сопрягая это с синтезом ГТФ (или АТФ). Единственная реакция субстратного фосфорилирования в цикле.', energy:[{label:'+ГТФ',cls:'atp-prod'}], quiz:{q:'Что синтезируется при реакции сукцинил-КоА-синтетазы?', opts:['НАДН','ФАДН₂','ГТФ','CO₂'], ans:2} },
{ title:'Окисление сукцината', mol:'fum', desc:'Сукцинатдегидрогеназа (комплекс II дыхательной цепи) окисляет сукцинат <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> фумарат, восстанавливая ФАД <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> ФАДН₂.', energy:[{label:'+ФАДН₂',cls:'fadh2'}], quiz:{q:'К какому комплексу дыхательной цепи относится СДГ?', opts:['Комплекс I','Комплекс II','Комплекс III','АТФ-синтаза'], ans:1} },
{ title:'Гидратация фумарата', mol:'mal', desc:'Фумараза присоединяет воду к фумарату <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> L-малат. Реакция стереоспецифична — образуется только L-изомер.', energy:[], quiz:{q:'Что присоединяется к фумарату в этой реакции?', opts:['CO₂','АТФ','H₂O','НАДН'], ans:2} },
{ title:'Регенерация ОАА', mol:'oaa', desc:'Малатдегидрогеназа окисляет малат <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> оксалоацетат с образованием НАДН. Регенерируется акцептор для следующего оборота цикла.', energy:[{label:'+НАДН',cls:'nadh'}], quiz:{q:'Сколько оборотов цикла Кребса нужно на 1 молекулу глюкозы?', opts:['1','2','4','10'], ans:1} },
]
},
oxidation: {
name: 'β-Окисление',
color: '#fb923c',
colorRgb: '251,146,60',
desc: 'Повторяющиеся циклы окисления жирных кислот в митохондриях. Каждый цикл отщепляет 2C в виде ацетил-КоА и выделяет 1 НАДН + 1 ФАДН₂.',
stats: [
{ label: '+НАДН / цикл', cls: 'nadh' },
{ label: '+ФАДН₂', cls: 'nadh' },
{ label: '+Ацетил-КоА', cls: 'atp' },
],
legend: [
{ color: '#fb923c', type: 'circle', label: 'Промежуточный продукт' },
{ color: '#fb923c', type: 'line', label: 'Реакция β-окисления' },
],
nodes: [
{ id:'fac', label:'Жирная к-та',formula:'R-COOH', x:400, y:60, role:'substrate', desc:'Свободная жирная кислота (напр. пальмитиновая C₁₆). Активируется в ацил-КоА перед входом в митохондрии.', props:[] },
{ id:'acylcoa', label:'Ацил-КоА', formula:'R-CO-SCoA', x:400, y:150, role:'key', desc:'Активированная жирная кислота. Образуется при участии ацил-КоА-синтетазы за счёт АТФ (<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>АМФ+PPi). Не проходит через мембрану — транспортируется как карнитиновый эфир.', props:['-АТФ (<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>АМФ)'] },
{ id:'enoylcoa',label:'Транс-еноил-КоА',formula:'R-CH=CH-CO-SCoA',x:400,y:250,role:'inter',desc:'Транс-Δ²-еноил-КоА. Образуется при ФАД-зависимом окислении ацил-КоА ацил-КоА-дегидрогеназой.', props:['+ФАДН₂'] },
{ id:'hydroxy', label:'L-β-гидрокси-КоА',formula:'R-CHOH-CH₂-CO-SCoA',x:400,y:345,role:'inter',desc:'L-β-гидроксиацил-КоА. Образуется при гидратации двойной связи еноил-КоА гидратазой.', props:[] },
{ id:'ketoacoa',label:'β-кето-КоА', formula:'R-CO-CH₂-CO-SCoA',x:400,y:440,role:'inter',desc:'β-кетоацил-КоА. Образуется при НАД⁺-зависимом окислении L-β-гидроксиацил-КоА.', props:['+НАДН'] },
{ id:'newacyl', label:'Ацил-КоА (2C)',formula:'R\'—CO-SCoA', x:240, y:540, role:'inter', desc:'Укороченный на 2 углерода ацил-КоА. Возвращается на начало цикла β-окисления.', props:['Следующий цикл'] },
{ id:'acetcoa2',label:'Ацетил-КоА', formula:'CH₃CO-SCoA', x:560, y:540, role:'product', desc:'Ацетил-КоА — входит в цикл Кребса. Из пальмитиновой кислоты (C₁₆) образуется 8 ацетил-КоА за 7 циклов β-окисления.', props:['<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> Цикл Кребса'] },
],
edges: [
{ from:'fac', to:'acylcoa', enzyme:'Ацил-КоА-синтетаза', co:'-АТФ', curveX:0 },
{ from:'acylcoa', to:'enoylcoa', enzyme:'Ацил-КоА-ДГ', co:'+ФАДН₂', curveX:0 },
{ from:'enoylcoa',to:'hydroxy', enzyme:'Еноил-КоА-гидратаза', curveX:0 },
{ from:'hydroxy', to:'ketoacoa', enzyme:'L-3-гидроксиацил-КоА-ДГ', co:'+НАДН', curveX:0 },
{ from:'ketoacoa',to:'newacyl', enzyme:'Тиолаза', curveX:0 },
{ from:'ketoacoa',to:'acetcoa2', enzyme:'Тиолаза', curveX:0 },
{ from:'newacyl', to:'acylcoa', enzyme:'Повтор цикла', curveX:-60 },
],
steps: [
{ title:'Активация жирной кислоты', mol:'acylcoa', desc:'Ацил-КоА-синтетаза присоединяет КоА к жирной кислоте, образуя ацил-КоА. Расходуется АТФ (<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>АМФ+PPi, что эквивалентно 2 АТФ). Это происходит в цитоплазме.', energy:[{label:'-2 АТФ',cls:'atp-used'}], quiz:{q:'Где происходит активация жирной кислоты в ацил-КоА?', opts:['В митохондриях','В ядре','В цитоплазме','В рибосомах'], ans:2} },
{ title:'ФАД-зависимое окисление', mol:'enoylcoa', desc:'Ацил-КоА-дегидрогеназа окисляет ацил-КоА, вводя двойную связь между α и β углеродами <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> транс-Δ²-еноил-КоА. ФАД восстанавливается до ФАДН₂.', energy:[{label:'+ФАДН₂',cls:'fadh2'}], quiz:{q:'Какой кофактор восстанавливается в первой реакции β-окисления?', opts:['НАД⁺','ФАД','ГТФ','КоА'], ans:1} },
{ title:'Гидратация двойной связи', mol:'hydroxy', desc:'Еноил-КоА-гидратаза присоединяет воду по двойной связи <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> L-β-гидроксиацил-КоА. Реакция стереоспецифична.', energy:[], quiz:{q:'Что присоединяется в реакции гидратации еноил-КоА?', opts:['CO₂','O₂','H₂O','НАД⁺'], ans:2} },
{ title:'НАД⁺-зависимое окисление', mol:'ketoacoa', desc:'L-3-гидроксиацил-КоА-дегидрогеназа окисляет гидроксильную группу <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> кетогруппу, восстанавливая НАД⁺ <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> НАДН.', energy:[{label:'+НАДН',cls:'nadh'}], quiz:{q:'Какая группа окисляется в этой реакции?', opts:['Карбоксильная','Аминогруппа','Гидроксильная','Метильная'], ans:2} },
{ title:'Тиолитическое расщепление', mol:'acetcoa2', desc:'Тиолаза расщепляет β-кетоацил-КоА присоединением КоА <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> ацетил-КоА (2C) + укороченный ацил-КоА. Цикл повторяется.', energy:[{label:'+Ацетил-КоА',cls:'atp-prod'}], quiz:{q:'Сколько ацетил-КоА образуется из пальмитиновой кислоты (C16)?', opts:['4','6','7','8'], ans:3} },
]
},
synthesis: {
name: 'Синтез белка',
color: '#a78bfa',
colorRgb: '167,139,250',
desc: 'Трансляция — считывание мРНК рибосомой и полимеризация аминокислот в полипептидную цепь.',
stats: [
{ label: '~2 ГТФ / аминокислота', cls: 'atp' },
{ label: 'мРНК <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> белок', cls: 'nadh' },
],
legend: [
{ color: '#a78bfa', type: 'circle', label: 'Участник трансляции' },
{ color: '#a78bfa', type: 'line', label: 'Этап синтеза' },
],
nodes: [
{ id:'mrna', label:'мРНК', formula:'5-AUG…-3', x:400, y:60, role:'substrate', desc:'Матричная РНК — несёт генетическую информацию от ДНК к рибосоме в виде кодонов (триплетов нуклеотидов).', props:['Матрица'] },
{ id:'ribosome',label:'Рибосома', formula:'60S+40S', x:400, y:160, role:'key', desc:'Эукариотическая рибосома (80S). Состоит из малой (40S) и большой (60S) субъединиц. Имеет 3 сайта: A (аминоацильный), P (пептидильный), E (выход).', props:['A-P-E сайты'] },
{ id:'trna', label:'аминоацил-тРНК', formula:'aa-tRNA', x:230, y:260, role:'inter', desc:'тРНК с присоединённой аминокислотой. Распознаёт кодон мРНК через антикодон. Доставляется в A-сайт в комплексе с EF-Tu·ГТФ.', props:['-2 ГТФ'] },
{ id:'peptide', label:'Растущая цепь', formula:'...aa-aa-aa', x:560, y:260, role:'inter', desc:'Нарастающая полипептидная цепь в P-сайте. Пептидилтрансфераза (23S rRNA) катализирует образование пептидной связи.', props:['P-сайт'] },
{ id:'peptbond',label:'Пептидная связь',formula:'—CO—NH—', x:400, y:360, role:'inter', desc:'Образование пептидной связи катализируется рибозимом (23S rRNA) — пептидилтрансферазой. Выделяется тРНК из P-сайта.', props:[] },
{ id:'translo', label:'Транслокация', formula:'EF-G·ГТФ', x:400, y:460, role:'inter', desc:'Фактор EF-G (с ГТФ) сдвигает рибосому на 1 кодон (3 нт) в направлении 5<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>3. Освобождается Е-сайт. Расходуется ГТФ.', props:['-1 ГТФ'] },
{ id:'protein', label:'Белок', formula:'[полипептид]', x:400, y:560, role:'product', desc:'Готовый полипептид. Освобождается при встрече со стоп-кодоном (UAA, UAG, UGA) при участии факторов высвобождения RF1/RF2.', props:['Готовый продукт'] },
],
edges: [
{ from:'mrna', to:'ribosome', enzyme:'Инициация (eIF)', curveX:0 },
{ from:'ribosome',to:'trna', enzyme:'Декодирование', curveX:0 },
{ from:'trna', to:'peptbond', enzyme:'Пептидилтрансфераза', curveX:0 },
{ from:'peptide', to:'peptbond', enzyme:'', curveX:0 },
{ from:'peptbond',to:'translo', enzyme:'EF-G·ГТФ', curveX:0 },
{ from:'translo', to:'protein', enzyme:'Терминация (RF)', curveX:0 },
{ from:'translo', to:'ribosome', enzyme:'Следующий кодон', curveX:-70 },
],
steps: [
{ title:'Инициация', mol:'ribosome', desc:'Малая субъединица рибосомы распознаёт 5′-кэп мРНК при помощи факторов инициации (eIF4E/4G). Инициаторная Met-тРНК занимает P-сайт. Присоединяется большая субъединица.', energy:[{label:'-3 ГТФ',cls:'atp-used'}], quiz:{q:'Какой сайт занимает инициаторная Met-тРНК?', opts:['A-сайт','P-сайт','E-сайт','Все три'], ans:1} },
{ title:'Элонгация — доставка аа-тРНК', mol:'trna', desc:'EF-Tu·ГТФ доставляет аминоацил-тРНК в A-сайт. При правильном спаривании кодон–антикодон ГТФ гидролизуется, EF-Tu·ГДФ уходит.', energy:[{label:'-1 ГТФ',cls:'atp-used'}], quiz:{q:'Какой фактор доставляет аа-тРНК в А-сайт?', opts:['EF-G','EF-Tu','eIF2','RF1'], ans:1} },
{ title:'Пептидная связь', mol:'peptbond', desc:'Пептидилтрансфераза переносит пептидильную группу с P-сайта на аминогруппу в A-сайте, образуя пептидную связь. Энергия — из гидролиза аминоацильной связи тРНК.', energy:[], quiz:{q:'Что катализирует образование пептидной связи?', opts:['Белковый фермент','23S rRNA (рибозим)','ДНК-полимераза','АТФ-синтаза'], ans:1} },
{ title:'Транслокация', mol:'translo', desc:'EF-G·ГТФ сдвигает рибосому на 3 нуклеотида по мРНК. Цепь с тРНК перемещается из A <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> P, пустая тРНК из P <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> E и уходит. Расходуется ГТФ.', energy:[{label:'-1 ГТФ',cls:'atp-used'}], quiz:{q:'На сколько нуклеотидов сдвигается рибосома при транслокации?', opts:['1','2','3','4'], ans:2} },
{ title:'Терминация и высвобождение', mol:'protein', desc:'Стоп-кодон (UAA/UAG/UGA) распознаётся факторами высвобождения RF1/RF2. Пептидилтрансфераза гидролизует связь пептид-тРНК <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> белок освобождается. Рибосома диссоциирует.', energy:[], quiz:{q:'Сколько стоп-кодонов существует?', opts:['1','2','3','4'], ans:2} },
]
}
};
let PATHWAYS = {}; // данные путей грузятся из БД через API в init() (loadPathways)
// ═══════════════════════════════════════════════════════
// STATE
@@ -1060,6 +823,46 @@ function clickNode(id) {
// ═══════════════════════════════════════════════════════
// LEARN MODE
// ═══════════════════════════════════════════════════════
// ── Загрузка данных путей из БД (API) ──
async function loadPathways() {
try {
const data = await LS.biochemGetPathways();
if (data && Object.keys(data).length) PATHWAYS = data;
} catch (e) {
LS.toast?.('Не удалось загрузить пути', 'error');
}
if (!PATHWAYS[currentPath]) currentPath = Object.keys(PATHWAYS)[0] || currentPath;
}
// ── Прогресс прохождения путей (персистентность Learn-режима) ──
let _pathProgress = {};
async function loadPathProgress() {
try { _pathProgress = (await LS.biochemGetPathwayProgress()) || {}; }
catch { _pathProgress = {}; }
markCompletedChips();
}
function markCompletedChips() {
document.querySelectorAll('.path-chip').forEach(chip => {
const key = chip.dataset.path;
const done = _pathProgress[key] && _pathProgress[key].completed;
let badge = chip.querySelector('.path-done');
if (done && !badge) {
badge = document.createElement('span');
badge.className = 'path-done';
badge.style.cssText = 'display:inline-flex;margin-left:5px;color:#4ade80';
badge.innerHTML = '<svg class="ic" viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="20 6 9 17 4 12"/></svg>';
chip.appendChild(badge);
} else if (!done && badge) { badge.remove(); }
});
}
function savePathCompletion() {
LS.biochemSavePathwayProgress(currentPath, learnStep, true).then(r => {
_pathProgress[currentPath] = { step: learnStep, completed: true };
markCompletedChips();
if (r && r.xp) LS.toast(`Путь пройден! +${r.xp} XP`, 'success');
}).catch(() => {});
}
function startLearn() {
learnMode = true;
learnStep = 0;
@@ -1149,6 +952,7 @@ function stepNav(dir) {
function renderLearnComplete() {
activeNode = null;
savePathCompletion(); // сохранить прохождение + начислить XP (один раз)
renderNodes(PATHWAYS[currentPath]);
document.getElementById('learn-active').innerHTML = `
<div class="learn-complete">
@@ -1210,10 +1014,8 @@ svgArea.addEventListener('wheel', e => {
async function init() {
try {
const user = await LS.getMe();
const initials = (user?.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
document.getElementById('nav-avatar').textContent = initials;
const nav2 = document.getElementById('nav-avatar2');
if (nav2) nav2.textContent = initials;
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
LS.renderNavAvatar(document.getElementById('nav-avatar2'), user);
if (user?.role === 'admin') document.getElementById('btn-admin').style.display = '';
LS.applyRoleSidebar(user);
LS.showBoardIfAllowed();
@@ -1225,8 +1027,10 @@ async function init() {
// wait for layout
await new Promise(r => setTimeout(r, 60));
await loadPathways(); // данные путей из БД
renderPath();
renderPathInfo();
loadPathProgress(); // отметить пройденные пути галочкой
if (window.lucide) lucide.createIcons();
LS.notif?.init();
LS.hideDisabledFeatures?.();
+2
View File
@@ -1550,6 +1550,8 @@ async function submitChallenge() {
loadChallenges();
} catch(e) {
if (e.data?.error === 'wrong_formula') LS.toast(`Неверно. Ожидается ${e.data.expected}, получено ${e.data.submitted}`, 'error');
else if (e.data?.error === 'wrong_structure') LS.toast('Формула верна, но структура (связность) не та — проверь, как соединены атомы', 'error');
else if (e.data?.error === 'valency_error') LS.toast('Есть ошибки валентности', 'error');
else LS.toast('Ошибка: ' + e.message, 'error');
}
}
+1 -2
View File
@@ -3481,8 +3481,7 @@
}
// setup nav
const initials = (_me.name || 'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('') || 'LS';
document.getElementById('nav-avatar').textContent = initials;
LS.renderNavAvatar(document.getElementById('nav-avatar'), _me);
document.getElementById('nav-user').textContent = _me.name || _me.email;
if (_me.role === 'admin') document.getElementById('btn-admin').style.display = '';
if (['teacher', 'admin'].includes(_me.role)) {
+1 -2
View File
@@ -319,8 +319,7 @@ async function init() {
const user = LS.getUser?.() || null;
if (user) {
document.getElementById('nav-user').textContent = user.name?.split(' ')[0] || 'Профиль';
const initials = (user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
document.getElementById('nav-avatar').textContent = initials;
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
}
if (localStorage.getItem('ls_sb_collapsed') === '1') {
document.getElementById('app').classList.add('sb-collapsed');
+1 -2
View File
@@ -358,8 +358,7 @@
const user = LS.getUser();
LS.applyRoleSidebar(user);
if (user) {
document.getElementById('nav-avatar').textContent =
(user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
document.getElementById('nav-user').textContent = user.name || '—';
LS.showBoardIfAllowed();
}
+1 -2
View File
@@ -455,8 +455,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0,2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin','teacher'].includes(user?.role);
LS.showBoardIfAllowed();
+1 -2
View File
@@ -297,8 +297,7 @@ let _subjectSlug = '';
const user = LS.getUser();
LS.applyRoleSidebar(user);
if (user) {
document.getElementById('nav-avatar').textContent =
(user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
document.getElementById('nav-user').textContent = user.name || '—';
LS.showBoardIfAllowed();
}
+1 -2
View File
@@ -255,8 +255,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0,2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin','teacher'].includes(user?.role);
if (!isTeacher) { location.href = '/dashboard'; throw new Error(); }
+1 -2
View File
@@ -838,8 +838,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0,2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin','teacher'].includes(user?.role);
LS.showBoardIfAllowed();
+1 -2
View File
@@ -458,8 +458,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0, 2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin', 'teacher'].includes(user?.role);
if (!isTeacher) { location.href = '/dashboard'; throw new Error(); }
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -172,8 +172,8 @@
<script src="/js/mobile.js"></script>
<script>
(async function () {
const user = LS.initPage();
if (!user || (user.role !== 'teacher' && user.role !== 'admin')) {
const { user, isTeacher } = LS.initPage();
if (!user || !isTeacher) {
location.href = '/dashboard'; return;
}
LS.showBoardIfAllowed();
+1 -2
View File
@@ -605,8 +605,7 @@ let _petCooldownTimer = null;
const user = LS.getUser();
LS.applyRoleSidebar(user);
if (user) {
document.getElementById('nav-avatar').textContent =
(user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
document.getElementById('nav-user').textContent = user.name || '—';
LS.showBoardIfAllowed();
}
+1 -1
View File
@@ -1493,7 +1493,7 @@
document.getElementById('nav-user').textContent = name;
const ini = name.split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
document.getElementById('big-avatar').textContent = ini;
document.getElementById('nav-avatar').textContent = ini;
LS.renderNavAvatar(document.getElementById('nav-avatar'), { ...LS.getUser(), name });
} catch(e) { showMsg(msg, LS.esc(e.message||'Ошибка'),'err'); }
finally { btn.disabled = false; }
}
+1 -2
View File
@@ -346,8 +346,7 @@
const user = LS.getUser();
document.getElementById('nav-user').textContent = user?.name || user?.email || '';
document.getElementById('nav-avatar').textContent =
(user?.name || 'LS').split(' ').slice(0, 2).map(w => w[0]?.toUpperCase() || '').join('') || 'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
const isTeacher = ['admin', 'teacher'].includes(user?.role);
if (!isTeacher) { location.href = '/dashboard'; throw new Error(); }
+1 -1
View File
@@ -873,7 +873,7 @@ async function init() {
const user = LS.getUser?.();
if (user) {
document.getElementById('nav-user').textContent = user.name?.split(' ')[0] || '—';
document.getElementById('nav-avatar').textContent = (user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
}
habitats = await LS.get('/api/red-book/habitats').catch(() => []);
renderTabs();
+1 -1
View File
@@ -802,7 +802,7 @@ if (localStorage.getItem('ls_sb_collapsed') === '1') document.getElementById('ap
const user = LS.getUser?.();
if (user) {
document.getElementById('nav-user').textContent = user.name?.split(' ')[0] || '—';
document.getElementById('nav-avatar').textContent = (user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
}
resize(); // init canvas size so raycaster works correctly
loadGraph();
+1 -1
View File
@@ -301,7 +301,7 @@ async function init() {
const user = LS.getUser?.();
if (user) {
document.getElementById('nav-user').textContent = user.name?.split(' ')[0] || '—';
document.getElementById('nav-avatar').textContent = (user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
}
const data = await LS.get('/api/red-book/species?limit=100').catch(() => ({ species: [] }));
allSpecies = data.species || [];
+1 -2
View File
@@ -799,8 +799,7 @@ async function init() {
const user = LS.getUser?.() || null;
if (user) {
document.getElementById('nav-user').textContent = user.name?.split(' ')[0] || 'Профиль';
const initials = (user.name||'LS').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'LS';
document.getElementById('nav-avatar').textContent = initials;
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
}
// Sidebar collapse