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:
@@ -0,0 +1,187 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const targetFile = path.join(__dirname, '../../frontend/lab.html');
|
||||
let src = fs.readFileSync(targetFile, 'utf-8');
|
||||
|
||||
// --- 1. Add «Интерференция» tab button after Призма ---
|
||||
const tabPrism = '<button id="ob-tab-prism" onclick="obSwitchMode(\'prism\')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Призма</button>';
|
||||
const tabInterf = '\n <button id="ob-tab-interf" onclick="obSwitchMode(\'interf\')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Интерференция</button>';
|
||||
|
||||
if (src.indexOf('ob-tab-interf') < 0) {
|
||||
if (src.indexOf(tabPrism) >= 0) {
|
||||
src = src.replace(tabPrism, tabPrism + tabInterf);
|
||||
console.log('Added Интерференция tab button');
|
||||
} else {
|
||||
console.log('WARN: Prizm tab not found');
|
||||
}
|
||||
} else {
|
||||
console.log('Интерференция tab already present');
|
||||
}
|
||||
|
||||
// --- 2. Add ob-ctrl-interf control panel after ob-ctrl-freebuild ---
|
||||
// Find end of ob-ctrl-freebuild div (find the closing tag)
|
||||
const ctrlFreeEnd = ' <div class="pp-hint">Тащи линзы или предмет по оси мышью</div>\n </div>';
|
||||
const ctrlFreeEndCR = ' <div class="pp-hint">Тащи линзы или предмет по оси мышью</div>\r\n </div>';
|
||||
|
||||
const ctrlInterfHTML = `
|
||||
<!-- ── Interference control panel (Agent C) ── -->
|
||||
<div id="ob-ctrl-interf" class="proj-panel" style="width:240px;gap:0;flex-shrink:0;display:none">
|
||||
<!-- Sub-mode buttons -->
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Эксперимент</div>
|
||||
<div style="display:flex;gap:3px;margin-bottom:10px;flex-wrap:wrap">
|
||||
<button id="if-sub-newton" class="preset-btn active" onclick="ifSwitchSub('newton')" style="font-size:.7rem;flex:1">Кольца Ньютона</button>
|
||||
<button id="if-sub-thinfilm" class="preset-btn" onclick="ifSwitchSub('thinfilm')" style="font-size:.7rem;flex:1">Тонкая плёнка</button>
|
||||
<button id="if-sub-polarization" class="preset-btn" onclick="ifSwitchSub('polarization')" style="font-size:.7rem;flex:1">Поляризация</button>
|
||||
</div>
|
||||
<!-- Newton rings controls -->
|
||||
<div id="if-ctrl-newton">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Кольца Ньютона</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:65px">R = <span id="if-newton-r-val" style="color:var(--cyan);font-weight:700">200</span> мм</label>
|
||||
<input type="range" id="sl-if-newton-r" min="50" max="500" step="10" value="200" oninput="ifNewtParam('R',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:65px">n = <span id="if-newton-n-val" style="color:#FFD166;font-weight:700">12</span></label>
|
||||
<input type="range" id="sl-if-newton-n" min="4" max="20" step="1" value="12" oninput="ifNewtParam('nmax',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="pp-hint">r_n(dark) = sqrt(n*lambda*R)</div>
|
||||
</div>
|
||||
<!-- Thin film controls -->
|
||||
<div id="if-ctrl-thinfilm" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Тонкая плёнка</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">t = <span id="if-tf-t-val" style="color:var(--cyan);font-weight:700">400</span></label>
|
||||
<input type="range" id="sl-if-tf-t" min="50" max="2000" step="10" value="400" oninput="ifThinFilmParam('t',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">n = <span id="if-tf-n-val" style="color:#FFD166;font-weight:700">1.33</span></label>
|
||||
<input type="range" id="sl-if-tf-n" min="1.0" max="2.5" step="0.01" value="1.33" oninput="ifThinFilmParam('n',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">θ = <span id="if-tf-th-val" style="color:#EF476F;font-weight:700">0</span>°</label>
|
||||
<input type="range" id="sl-if-tf-th" min="0" max="60" step="1" value="0" oninput="ifThinFilmParam('theta',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="gp-section-title" style="margin-bottom:4px">Пресет</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:6px">
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('soap')" style="font-size:.68rem">Мыльная n=1.33</button>
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('oil')" style="font-size:.68rem">Масло n=1.50</button>
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('coating')" style="font-size:.68rem">Покрытие n=1.38</button>
|
||||
</div>
|
||||
<div class="pp-hint">2nt·cosθr = (m+0.5)λ — максимум</div>
|
||||
</div>
|
||||
<!-- Polarization controls -->
|
||||
<div id="if-ctrl-polarization" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Поляризация (Малюс)</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">θ = <span id="if-pol-th-val" style="color:var(--cyan);font-weight:700">45</span>°</label>
|
||||
<input type="range" id="sl-if-pol-th" min="0" max="90" step="1" value="45" oninput="ifPolParam('theta',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div style="margin-bottom:8px">
|
||||
<label style="font-size:.72rem;color:#ccc;display:flex;align-items:center;gap:6px;cursor:pointer">
|
||||
<input type="radio" name="if-pol-src" value="unpolarized" checked onchange="ifPolSrc(this.value)" style="accent-color:var(--violet)">
|
||||
Неполяризованный
|
||||
</label>
|
||||
<label style="font-size:.72rem;color:#ccc;display:flex;align-items:center;gap:6px;cursor:pointer;margin-top:4px">
|
||||
<input type="radio" name="if-pol-src" value="polarized" onchange="ifPolSrc(this.value)" style="accent-color:var(--violet)">
|
||||
Поляризованный
|
||||
</label>
|
||||
</div>
|
||||
<div class="pp-hint">I = I₀·cos²(θ)</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (src.indexOf('ob-ctrl-interf') < 0) {
|
||||
let replaced = false;
|
||||
if (src.indexOf(ctrlFreeEnd) >= 0) {
|
||||
src = src.replace(ctrlFreeEnd, ctrlFreeEnd + ctrlInterfHTML);
|
||||
replaced = true;
|
||||
} else if (src.indexOf(ctrlFreeEndCR) >= 0) {
|
||||
src = src.replace(ctrlFreeEndCR, ctrlFreeEndCR + ctrlInterfHTML);
|
||||
replaced = true;
|
||||
}
|
||||
if (replaced) {
|
||||
console.log('Added ob-ctrl-interf panel');
|
||||
} else {
|
||||
console.log('WARN: freebuild ctrl end not found, trying alternate pattern');
|
||||
// Try finding the shared canvas area comment
|
||||
const canvasAreaMarker = '<!-- ── Shared canvas area';
|
||||
const idx = src.indexOf(canvasAreaMarker);
|
||||
if (idx >= 0) {
|
||||
src = src.slice(0, idx) + ctrlInterfHTML + '\n ' + src.slice(idx);
|
||||
console.log('Added ob-ctrl-interf before canvas area');
|
||||
} else {
|
||||
console.log('WARN: canvas area marker not found');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('ob-ctrl-interf already present');
|
||||
}
|
||||
|
||||
// --- 3. Add ob-interf-canvas after ob-waves-canvas ---
|
||||
const wavesCanvas = '<canvas id="ob-waves-canvas"';
|
||||
const interfCanvas = '\n <canvas id="ob-interf-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>';
|
||||
if (src.indexOf('ob-interf-canvas') < 0) {
|
||||
// find the waves canvas line
|
||||
const wIdx = src.indexOf(wavesCanvas);
|
||||
if (wIdx >= 0) {
|
||||
// find end of that line
|
||||
const eol = src.indexOf('>', wIdx) + 1;
|
||||
const afterLine = src.indexOf('\n', eol);
|
||||
src = src.slice(0, afterLine) + interfCanvas + src.slice(afterLine);
|
||||
console.log('Added ob-interf-canvas');
|
||||
} else {
|
||||
// fallback: add after ob-free-canvas
|
||||
const freeCanvas = '<canvas id="ob-free-canvas"';
|
||||
const fIdx = src.indexOf(freeCanvas);
|
||||
if (fIdx >= 0) {
|
||||
const eol2 = src.indexOf('\n', src.indexOf('>', fIdx));
|
||||
src = src.slice(0, eol2) + interfCanvas + src.slice(eol2);
|
||||
console.log('Added ob-interf-canvas (after free)');
|
||||
} else {
|
||||
console.log('WARN: canvas insertion point not found');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('ob-interf-canvas already present');
|
||||
}
|
||||
|
||||
// --- 4. Add ob-stats-interf in stats bar (after ob-stats-prism) ---
|
||||
const prismStats = '</div>\n </div>\n </div>\n\n <!-- ── ISOPROCESS';
|
||||
const prismStatsCR = '</div>\r\n </div>\r\n </div>\r\n\r\n <!-- ── ISOPROCESS';
|
||||
const interfStats = `
|
||||
<div id="ob-stats-interf" style="display:none;flex:1;gap:0">
|
||||
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="ifbar-sub" style="color:var(--cyan)">Кольца</div></div>
|
||||
<div class="pstat"><div class="pstat-label">λ</div><div class="pstat-val" id="ifbar-wl" style="color:#FFFFFF">550 нм</div></div>
|
||||
</div>`;
|
||||
|
||||
if (src.indexOf('ob-stats-interf') < 0) {
|
||||
// find the closing of ob-stats-prism section
|
||||
const prismStatsBlock = '<div id="ob-stats-prism"';
|
||||
const pIdx = src.indexOf(prismStatsBlock);
|
||||
if (pIdx >= 0) {
|
||||
// find the closing </div> of this section then the closing </div> of statsbar div
|
||||
let depth = 0, i = pIdx;
|
||||
while (i < src.length) {
|
||||
if (src[i] === '<' && src.slice(i, i+5) === '<div ') depth++;
|
||||
if (src[i] === '<' && src.slice(i, i+6) === '</div>') {
|
||||
if (depth <= 1) {
|
||||
// found end of ob-stats-prism
|
||||
const afterDiv = src.indexOf('>', i) + 1;
|
||||
src = src.slice(0, afterDiv) + interfStats + src.slice(afterDiv);
|
||||
console.log('Added ob-stats-interf');
|
||||
break;
|
||||
}
|
||||
depth--;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
console.log('WARN: ob-stats-prism not found');
|
||||
}
|
||||
} else {
|
||||
console.log('ob-stats-interf already present');
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetFile, src, 'utf-8');
|
||||
console.log('HTML patching done. New size:', src.length);
|
||||
Reference in New Issue
Block a user