Files
Learn_System/backend/scripts/patch_interfsim_html.js
Maxim Dolgolyov 5381679c68 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>
2026-05-30 18:12:55 +03:00

188 lines
11 KiB
JavaScript

'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">&#952; = <span id="if-tf-th-val" style="color:#EF476F;font-weight:700">0</span>&#176;</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&#183;cos&#952;r = (m+0.5)&#955; — максимум</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">&#952; = <span id="if-pol-th-val" style="color:var(--cyan);font-weight:700">45</span>&#176;</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&#8320;&#183;cos&#178;(&#952;)</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">&#955;</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);