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:
+15
-1
@@ -178,7 +178,21 @@
|
||||
"Bash(cmd /c \"taskkill /PID 60564 /F\")",
|
||||
"Bash(cmd /c \"taskkill /F /PID 60564 2>&1\")",
|
||||
"Bash(kill -9 60564)",
|
||||
"Bash(kill -9 9313)"
|
||||
"Bash(kill -9 9313)",
|
||||
"Read(//f/!Рабочие/ЦТ/Математика/**)",
|
||||
"Read(//f/!Рабочие/ЦТ/Физика/**)",
|
||||
"Read(//f/!����稥/��/��⥬�⨪�/**)",
|
||||
"PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Математика\\\\Математика\\\\ЦТ-ЦЭ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)",
|
||||
"Read(//f/!����稥/��/**)",
|
||||
"PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦЭ,ЦТ-20260116T125835Z-3-001\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)",
|
||||
"PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦТ-20260116T130104Z-3-001\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)",
|
||||
"PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦЭ,ЦТ-20260116T125835Z-3-001\\\\Сборники ЦЭ,ЦТ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)",
|
||||
"PowerShell(Get-ChildItem \"F:\\\\!Рабочие\\\\ЦТ\\\\Физика\\\\Сборники ЦТ-20260116T130104Z-3-001\\\\Сборники ЦТ\" | Select-Object Name, @{N='MB';E={[math]::Round\\($_.Length/1MB,1\\)}} | Format-Table -AutoSize)",
|
||||
"Bash(git commit -m ' *)",
|
||||
"Bash(git push *)",
|
||||
"Bash(curl -s http://localhost:3000/api/subjects)",
|
||||
"PowerShell(\\(Get-Content \"g:\\\\Dev\\\\Тесты\\\\BQ-System\\\\frontend\\\\question-bank.html\"\\).Count)",
|
||||
"Bash(curl -s \"http://localhost:3000/api/subjects/math/topics\" -H \"Authorization: Bearer test\")"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"\\tmp"
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* audit_chem8.js — аудит KaTeX и оформления учебника «Химия 8».
|
||||
* Загружает каждую страницу в jsdom (renderMathInElement застаблен → $…$ остаются
|
||||
* литералами с уже раскрытыми JS-эскейпами), строит все §, извлекает формулы и
|
||||
* проверяет: баланс $, баланс {}, отсутствие управляющих символов (следы \t/\n),
|
||||
* пустые формулы, «сырые» $…$ вне рендера. Запуск: node backend/scripts/audit_chem8.js
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { JSDOM, VirtualConsole } = require('jsdom');
|
||||
|
||||
const ROOT = path.join(__dirname, '..', '..');
|
||||
const readF = p => fs.readFileSync(path.join(ROOT, p), 'utf8');
|
||||
const wait = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
const PAGES = [
|
||||
['chemistry_8_intro.html', 'chem8_intro_widgets'],
|
||||
['chemistry_8_ch1.html', 'chem8_ch1_widgets'],
|
||||
['chemistry_8_ch2.html', 'chem8_ch2_widgets'],
|
||||
['chemistry_8_ch3.html', 'chem8_ch3_widgets'],
|
||||
['chemistry_8_ch4.html', 'chem8_ch4_widgets'],
|
||||
['chemistry_8_ch5.html', 'chem8_ch5_widgets'],
|
||||
['chemistry_8_ch6.html', 'chem8_ch6_widgets']
|
||||
];
|
||||
|
||||
function buildPage(file, widgets) {
|
||||
let html = readF('frontend/textbooks/' + file);
|
||||
const inl = {
|
||||
'/js/biochem-core.js': readF('frontend/js/biochem-core.js'),
|
||||
'/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'),
|
||||
'/js/chem8_mol.js': readF('frontend/js/chem8_mol.js'),
|
||||
['/js/' + widgets + '.js']: readF('frontend/js/' + widgets + '.js'),
|
||||
'/js/chem8_engine.js': readF('frontend/js/chem8_engine.js')
|
||||
};
|
||||
html = html
|
||||
.replace(/<script defer src="https:\/\/cdn[^"]*"[^>]*><\/script>/g, '')
|
||||
.replace(/<script src="\/js\/api\.js" defer><\/script>/, '<script>window.renderMathInElement=function(){};</script>')
|
||||
.replace(/<script src="\/js\/xp\.js" defer><\/script>/, '');
|
||||
Object.keys(inl).forEach(src => {
|
||||
html = html.replace(new RegExp('<script src="' + src + '" defer><\\/script>'), () => '<script>\n' + inl[src] + '\n</script>');
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
function extractMath(s) {
|
||||
const out = [];
|
||||
// $$...$$ затем $...$
|
||||
let re = /\$\$([\s\S]+?)\$\$/g, m;
|
||||
let masked = s;
|
||||
while ((m = re.exec(s)) !== null) out.push({ disp: true, body: m[1] });
|
||||
masked = s.replace(/\$\$[\s\S]+?\$\$/g, '');
|
||||
re = /\$([^$]*)\$/g;
|
||||
while ((m = re.exec(masked)) !== null) out.push({ disp: false, body: m[1] });
|
||||
return out;
|
||||
}
|
||||
|
||||
function checkBraces(b) { let d = 0; for (const c of b) { if (c === '{') d++; else if (c === '}') d--; if (d < 0) return false; } return d === 0; }
|
||||
function hasCtrl(b) { return /[\t\n\r\f\v\b]/.test(b); }
|
||||
|
||||
async function auditPage(file, widgets) {
|
||||
const issues = [];
|
||||
const vc = new VirtualConsole(); const errs = [];
|
||||
vc.on('jsdomError', e => errs.push(e.message));
|
||||
const dom = new JSDOM(buildPage(file, widgets), {
|
||||
runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/',
|
||||
beforeParse(w) { w.scrollTo = function () {}; }
|
||||
});
|
||||
await wait(120);
|
||||
const doc = dom.window.document;
|
||||
const paras = (dom.window.PARAS || []).map(p => p.id);
|
||||
for (const id of paras) { try { dom.window.goTo(id); } catch (e) {} }
|
||||
await wait(120);
|
||||
|
||||
if (errs.length) issues.push('script errors: ' + errs.join(' | '));
|
||||
|
||||
// собрать все § тела + sidebar
|
||||
let html = '';
|
||||
doc.querySelectorAll('[id$="-body"]').forEach(el => { html += el.innerHTML + '\n'; });
|
||||
const sidebar = doc.getElementById('sidebar-content'); if (sidebar) html += sidebar.innerHTML;
|
||||
|
||||
// баланс $ (нечётное число одиночных $ вне $$)
|
||||
const noDisp = html.replace(/\$\$[\s\S]+?\$\$/g, '');
|
||||
const singles = (noDisp.match(/\$/g) || []).length;
|
||||
if (singles % 2 !== 0) issues.push('нечётное число одиночных $ (' + singles + ')');
|
||||
|
||||
const maths = extractMath(html);
|
||||
let bad = 0;
|
||||
for (const m of maths) {
|
||||
const b = m.body;
|
||||
if (!b.trim()) { issues.push('пустая формула $' + (m.disp ? '$' : '') + '$'); bad++; continue; }
|
||||
if (!checkBraces(b)) { issues.push('несбалансированные {} в: ' + b.slice(0, 50)); bad++; }
|
||||
if (hasCtrl(b)) { issues.push('управляющий символ (след \\t/\\n?) в: ' + JSON.stringify(b.slice(0, 50))); bad++; }
|
||||
// одиночный backslash перед буквой, не часть известной команды? — грубая эвристика: \ в конце
|
||||
if (/\\$/.test(b)) { issues.push('формула заканчивается на \\: ' + b.slice(-20)); bad++; }
|
||||
}
|
||||
return { file, mathCount: maths.length, badCount: bad, issues };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let total = 0, totalBad = 0;
|
||||
for (const [file, w] of PAGES) {
|
||||
const r = await auditPage(file, w);
|
||||
total += r.mathCount; totalBad += r.badCount;
|
||||
console.log('\n=== ' + file + ' — формул: ' + r.mathCount + ', проблем: ' + r.issues.length + ' ===');
|
||||
if (r.issues.length) r.issues.slice(0, 25).forEach(i => console.log(' ! ' + i));
|
||||
else console.log(' OK');
|
||||
}
|
||||
console.log('\nИТОГО формул: ' + total + ', проблемных: ' + totalBad);
|
||||
process.exit(0);
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,307 @@
|
||||
// Генератор physics_10_hub.html на основе algebra_11_hub.html
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SRC = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'algebra_11_hub.html');
|
||||
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_10_hub.html');
|
||||
|
||||
let h = fs.readFileSync(SRC, 'utf8');
|
||||
|
||||
// === 1. Palette + dark mode ===
|
||||
h = h.replace(
|
||||
/:root\{[\s\S]*?--bg:#ecfdf5; --card:#fff;[\s\S]*?--sh-h:0 12px 36px rgba\(13,148,136,\.18\);[\s\S]*?\}/,
|
||||
`:root{
|
||||
--bg:#fffbeb; --card:#fff;
|
||||
--text:#0f172a; --muted:#475569;
|
||||
--border:#fde68a;
|
||||
--pri:#ca8a04; --pri-d:#a16207;
|
||||
--pri-soft:#fef3c7;
|
||||
--ch1:#2563eb; --ch1-d:#1d4ed8;
|
||||
--ch2:#059669; --ch2-d:#047857;
|
||||
--ch3:#7c3aed; --ch3-d:#6d28d9;
|
||||
--ch4:#db2777; --ch4-d:#be185d;
|
||||
--ch5:#0891b2; --ch5-d:#0e7490;
|
||||
--ch6:#10b981; --ch6-d:#059669;
|
||||
--sh:0 4px 16px rgba(202,138,4,.10);
|
||||
--sh-h:0 12px 36px rgba(202,138,4,.18);
|
||||
}`);
|
||||
|
||||
h = h.replace(
|
||||
/html\.dark\{[\s\S]*?--pri-soft:rgba\(13,148,136,\.16\);[\s\S]*?\}/,
|
||||
`html.dark{
|
||||
--bg:#1a1500; --card:#2a2410;
|
||||
--text:#fef3c7; --muted:#d4b88f;
|
||||
--border:#3d2f0a;
|
||||
--pri-soft:rgba(202,138,4,.16);
|
||||
}`);
|
||||
|
||||
// === 2. Header ===
|
||||
h = h.replace(
|
||||
/\.hdr\{position:relative;background:linear-gradient\(110deg,#115e59 0%,#0d9488 55%,#5eead4 100%\)[^}]*\}/,
|
||||
`.hdr{position:relative;background:linear-gradient(110deg,#713f12 0%,#ca8a04 55%,#fde047 100%);color:#fff;padding:32px 24px 28px;overflow:hidden;border-bottom:2px solid rgba(254,243,199,.18)}`);
|
||||
|
||||
h = h.replace(/АЛГЕБРА/g, 'ФИЗИКА');
|
||||
h = h.replace(/rgba\(204,251,241,\.12\)/g, 'rgba(254,243,199,.12)');
|
||||
|
||||
// === 3. po-icon gradient ===
|
||||
h = h.replace(
|
||||
/\.po-icon\{[^}]*background:linear-gradient\(135deg,#0d9488,#5eead4\)[^}]*\}/,
|
||||
`.po-icon{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#ca8a04,#fde047);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:'Outfit',sans-serif;font-size:1.4rem;font-weight:900;font-style:italic}`);
|
||||
h = h.replace(/\.po-bar\{height:8px;background:rgba\(13,148,136,\.14\)/, '.po-bar{height:8px;background:rgba(202,138,4,.14)');
|
||||
h = h.replace(/\.po-fill\{height:100%;background:linear-gradient\(90deg,var\(--pri\),#5eead4\)/, '.po-fill{height:100%;background:linear-gradient(90deg,var(--pri),#fde047)');
|
||||
h = h.replace(/\.po-xp\{[^}]*background:linear-gradient\(135deg,#f59e0b,var\(--pri\)\)[^}]*\}/,
|
||||
".po-xp{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:linear-gradient(135deg,#f59e0b,var(--pri));color:#fff;border-radius:99px;font-size:.8rem;font-weight:800;font-family:'Unbounded',sans-serif;letter-spacing:.02em;box-shadow:0 4px 12px rgba(202,138,4,.24)}");
|
||||
|
||||
// === 4. Chapter grid: 3 → 6 cards ===
|
||||
h = h.replace(
|
||||
/\.ch-grid\{display:grid;grid-template-columns:1fr;gap:18px;margin-bottom:30px\}\s*@media\(min-width:680px\)\{\.ch-grid\{grid-template-columns:1fr 1fr\}\}\s*@media\(min-width:1000px\)\{\.ch-grid\{grid-template-columns:repeat\(3,1fr\)\}\}/,
|
||||
`.ch-grid{display:grid;grid-template-columns:1fr;gap:18px;margin-bottom:30px}
|
||||
@media(min-width:680px){.ch-grid{grid-template-columns:1fr 1fr}}
|
||||
@media(min-width:1000px){.ch-grid{grid-template-columns:repeat(3,1fr)}}`);
|
||||
|
||||
// Replace cover gradients (ch1 ch2 ch3) and add ch4 ch5 ch6
|
||||
h = h.replace(
|
||||
/\.ch-cover\.ch1\{background:[^}]+\}\s*\.ch-cover\.ch2\{background:[^}]+\}\s*\.ch-cover\.ch3\{background:[^}]+\}/,
|
||||
`.ch-cover.ch1{background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#60a5fa)}
|
||||
.ch-cover.ch2{background:linear-gradient(135deg,#064e3b,#059669 60%,#34d399)}
|
||||
.ch-cover.ch3{background:linear-gradient(135deg,#3b0764,#7c3aed 60%,#a78bfa)}
|
||||
.ch-cover.ch4{background:linear-gradient(135deg,#831843,#db2777 60%,#f472b6)}
|
||||
.ch-cover.ch5{background:linear-gradient(135deg,#164e63,#0891b2 60%,#22d3ee)}
|
||||
.ch-cover.ch6{background:linear-gradient(135deg,#064e3b,#10b981 60%,#6ee7b7)}`);
|
||||
|
||||
// Replace chN-card progress fill and action gradients
|
||||
h = h.replace(
|
||||
/\.ch-card\.ch1-card \.ch-prog-fill\{background:linear-gradient\(90deg,var\(--ch1\),var\(--ch1-d\)\)\}\s*\.ch-card\.ch2-card \.ch-prog-fill\{background:linear-gradient\(90deg,var\(--ch2\),var\(--ch2-d\)\)\}\s*\.ch-card\.ch3-card \.ch-prog-fill\{background:linear-gradient\(90deg,var\(--ch3\),var\(--ch3-d\)\)\}/,
|
||||
`.ch-card.ch1-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch1),var(--ch1-d))}
|
||||
.ch-card.ch2-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch2),var(--ch2-d))}
|
||||
.ch-card.ch3-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch3),var(--ch3-d))}
|
||||
.ch-card.ch4-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch4),var(--ch4-d))}
|
||||
.ch-card.ch5-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch5),var(--ch5-d))}
|
||||
.ch-card.ch6-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch6),var(--ch6-d))}`);
|
||||
|
||||
h = h.replace(
|
||||
/\.ch-card\.ch1-card \.ch-action\{background:linear-gradient\(135deg,var\(--ch1\),#fbbf24\)\}\s*\.ch-card\.ch2-card \.ch-action\{background:linear-gradient\(135deg,var\(--ch2\),#a78bfa\)\}\s*\.ch-card\.ch3-card \.ch-action\{background:linear-gradient\(135deg,var\(--ch3\),#22d3ee\)\}/,
|
||||
`.ch-card.ch1-card .ch-action{background:linear-gradient(135deg,var(--ch1),#60a5fa)}
|
||||
.ch-card.ch2-card .ch-action{background:linear-gradient(135deg,var(--ch2),#34d399)}
|
||||
.ch-card.ch3-card .ch-action{background:linear-gradient(135deg,var(--ch3),#a78bfa)}
|
||||
.ch-card.ch4-card .ch-action{background:linear-gradient(135deg,var(--ch4),#f472b6)}
|
||||
.ch-card.ch5-card .ch-action{background:linear-gradient(135deg,var(--ch5),#22d3ee)}
|
||||
.ch-card.ch6-card .ch-action{background:linear-gradient(135deg,var(--ch6),#6ee7b7)}`);
|
||||
|
||||
// Final header gradient
|
||||
h = h.replace(
|
||||
/\.final-head\{padding:18px 22px;background:linear-gradient\(135deg,#115e59 0%,#0d9488 55%,#0891b2 100%\)/,
|
||||
'.final-head{padding:18px 22px;background:linear-gradient(135deg,#713f12 0%,#ca8a04 55%,#f59e0b 100%)');
|
||||
|
||||
// title
|
||||
h = h.replace(/<title>Алгебра 11 класс — учебник<\/title>/, '<title>Физика 10 класс — учебник</title>');
|
||||
|
||||
// localStorage keys
|
||||
h = h.replace(/algebra11_theme/g, 'physics10_theme');
|
||||
h = h.replace(/algebra11_xp/g, 'physics10_xp');
|
||||
h = h.replace(/algebra11_course_master/g, 'physics10_course_master');
|
||||
h = h.replace(/algebra11_course_bosses/g, 'physics10_course_bosses');
|
||||
h = h.replace(/algebra11-master/g, 'physics10-master');
|
||||
h = h.replace(/'fin-boss-'/g, "'fin-boss-'"); // unchanged
|
||||
|
||||
// Header H1 + subtitle
|
||||
h = h.replace(/<h1>Алгебра — 11 класс<\/h1>/, '<h1>Физика — 10 класс</h1>');
|
||||
h = h.replace(
|
||||
/<div class="hdr-sub">Полный курс: степени и логарифмы, показательная и логарифмическая функции, уравнения и неравенства<\/div>/,
|
||||
'<div class="hdr-sub">Полный курс физики 10 класса: молекулярная физика, термодинамика, электростатика, магнитное поле, ток в средах</div>'
|
||||
);
|
||||
|
||||
// po-icon "a" → "f"
|
||||
h = h.replace(/<div class="po-icon">a<\/div>/, '<div class="po-icon">f</div>');
|
||||
|
||||
// === 5. Заменяем блок с 3 главами целиком на блок с 6 главами ===
|
||||
const chBlock = `
|
||||
<a href="/textbook/physics-10-ch1" class="ch-card ch1-card" id="ch-1">
|
||||
<div class="ch-cover ch1">
|
||||
<div class="ch-cover-wm">T</div>
|
||||
<div class="ch-num">Глава 1</div>
|
||||
<div class="ch-title">Основы МКТ</div>
|
||||
<div class="ch-range">§1–§10 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Молекулярно-кинетическая теория, идеальный газ, изопроцессы, строение твёрдых тел и жидкостей, влажность воздуха.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-1">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-1" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-1">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/physics-10-ch2" class="ch-card ch2-card" id="ch-2">
|
||||
<div class="ch-cover ch2">
|
||||
<div class="ch-cover-wm">ΔU</div>
|
||||
<div class="ch-num">Глава 2</div>
|
||||
<div class="ch-title">Термодинамика</div>
|
||||
<div class="ch-range">§11–§15 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Внутренняя энергия, работа и количество теплоты, первый закон термодинамики, тепловые двигатели, цикл Карно.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-2">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-2" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-2">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/physics-10-ch3" class="ch-card ch3-card" id="ch-3">
|
||||
<div class="ch-cover ch3">
|
||||
<div class="ch-cover-wm">+q</div>
|
||||
<div class="ch-num">Глава 3</div>
|
||||
<div class="ch-title">Электростатика</div>
|
||||
<div class="ch-range">§16–§24 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Электрический заряд, закон Кулона, напряжённость и потенциал электростатического поля, конденсаторы, энергия поля.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-3">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-3" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-3">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/physics-10-ch4" class="ch-card ch4-card" id="ch-4">
|
||||
<div class="ch-cover ch4">
|
||||
<div class="ch-cover-wm">I</div>
|
||||
<div class="ch-num">Глава 4</div>
|
||||
<div class="ch-title">Постоянный ток</div>
|
||||
<div class="ch-range">§25–§26 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">ЭДС источника, закон Ома для полной электрической цепи, КПД источника.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-4">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-4" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-4">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/physics-10-ch5" class="ch-card ch5-card" id="ch-5">
|
||||
<div class="ch-cover ch5">
|
||||
<div class="ch-cover-wm">B</div>
|
||||
<div class="ch-num">Глава 5</div>
|
||||
<div class="ch-title">Магнитное поле</div>
|
||||
<div class="ch-range">§27–§33 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Магнитное поле, сила Ампера, сила Лоренца, электромагнитная индукция, закон Фарадея, самоиндукция.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-5">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-5" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-5">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/physics-10-ch6" class="ch-card ch6-card" id="ch-6">
|
||||
<div class="ch-cover ch6">
|
||||
<div class="ch-cover-wm">n/p</div>
|
||||
<div class="ch-num">Глава 6</div>
|
||||
<div class="ch-title">Ток в средах</div>
|
||||
<div class="ch-range">§34–§37 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Электрический ток в металлах и сверхпроводимость, электролиз, разряды в газах и плазма, полупроводники.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-6">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-6" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-6">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
`;
|
||||
|
||||
// Replace the entire <a href="/textbook/algebra-11-ch1"...</a><a ch2></a><a ch3></a> block
|
||||
h = h.replace(/\s*<a href="\/textbook\/algebra-11-ch1"[\s\S]*?<\/a>\s*<a href="\/textbook\/algebra-11-ch2"[\s\S]*?<\/a>\s*<a href="\/textbook\/algebra-11-ch3"[\s\S]*?<\/a>\s*/,
|
||||
chBlock);
|
||||
|
||||
// Final placeholder — заменим cheat-grid + bosses на placeholder
|
||||
h = h.replace(/<div class="fin-section-title">\s*<svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"\/><\/svg>\s*Шпаргалка курса\s*<\/div>\s*<div class="cheat-grid">[\s\S]*?<\/div>\s*<div class="fin-section-title">\s*<svg viewBox="0 0 24 24"><path d="M14\.5 3\.5l[^"]+"\/><\/svg>\s*7 интегрированных боссов\s*<\/div>\s*<div class="boss-overall-bar">[\s\S]*?<\/div>\s*<div id="fin-bosses-container"><\/div>/,
|
||||
`<div class="fin-placeholder">
|
||||
<h3>Финал курса — в разработке</h3>
|
||||
<p>Итоговая шпаргалка по всем 37 параграфам и 8–10 интегрированных боссов появятся в Phase 7 (после завершения всех 6 глав).</p>
|
||||
</div>
|
||||
<div id="fin-bosses-container" style="display:none"></div>`);
|
||||
|
||||
// Remove FIN_BOSSES array — заменим на пустой
|
||||
h = h.replace(/var FIN_BOSSES = \[[\s\S]*?\];/, 'var FIN_BOSSES = [];');
|
||||
|
||||
// final-head-sub
|
||||
h = h.replace(
|
||||
/<div class="final-head-sub">Итоговая шпаргалка и интегрированные боссы\. Победи всех — получи «Магистр алгебры 11» и \+50 XP\.<\/div>/,
|
||||
'<div class="final-head-sub">Шпаргалка курса и интегрированные боссы по всем 6 главам. В разработке (Phase 7).</div>'
|
||||
);
|
||||
h = h.replace(/<div class="final-cta-title">Курс Алгебра 11 пройден!<\/div>/, '<div class="final-cta-title">Курс Физика 10 пройден!</div>');
|
||||
h = h.replace(/«Магистр алгебры 11»/g, '«Магистр физики 10»');
|
||||
h = h.replace(/Магистр алгебры 11/g, 'Магистр физики 10');
|
||||
|
||||
// Footer
|
||||
h = h.replace(/Интерактивный учебник «Алгебра — 11 класс»/, 'Интерактивный учебник «Физика — 10 класс»');
|
||||
|
||||
// Achievement strip
|
||||
h = h.replace(/Прочитайте все 10 параграфов трёх глав/, 'Прочитайте все 37 параграфов курса, чтобы получить достижение');
|
||||
|
||||
// === 6. TOTAL + CH_PARA + CH_IDX ===
|
||||
h = h.replace(/var TOTAL = 10;[\s\S]*?var CH_IDX = \{[\s\S]*?\};/, `var TOTAL = 37;
|
||||
var CH_PARA = {
|
||||
'physics-10-ch1': 10,
|
||||
'physics-10-ch2': 5,
|
||||
'physics-10-ch3': 9,
|
||||
'physics-10-ch4': 2,
|
||||
'physics-10-ch5': 7,
|
||||
'physics-10-ch6': 4,
|
||||
};
|
||||
var CH_IDX = {
|
||||
'physics-10-ch1': 1,
|
||||
'physics-10-ch2': 2,
|
||||
'physics-10-ch3': 3,
|
||||
'physics-10-ch4': 4,
|
||||
'physics-10-ch5': 5,
|
||||
'physics-10-ch6': 6,
|
||||
};`);
|
||||
|
||||
// API endpoint slug
|
||||
h = h.replace(/'\/api\/textbooks\/algebra-11\/children'/, "'/api/textbooks/physics-10/children'");
|
||||
|
||||
// На текстах ачивок: "Вы прочитали весь курс алгебры 11 класса."
|
||||
h = h.replace(/Вы прочитали весь курс алгебры 11 класса\./, 'Вы прочитали весь курс физики 10 класса.');
|
||||
|
||||
fs.writeFileSync(DST, h);
|
||||
console.log('OK hub →', DST, 'bytes:', h.length);
|
||||
|
||||
// Quick sanity: extract <script> blocks and check parseable JS
|
||||
const scriptMatches = [...h.matchAll(/<script>([\s\S]*?)<\/script>/g)];
|
||||
console.log('inline <script> count:', scriptMatches.length);
|
||||
for (const m of scriptMatches) {
|
||||
try { new Function(m[1]); }
|
||||
catch(e) { console.error('JS PARSE FAIL:', e.message); process.exit(1); }
|
||||
}
|
||||
console.log('all inline JS parses OK');
|
||||
@@ -0,0 +1,94 @@
|
||||
import sys, os
|
||||
|
||||
src = os.path.join(os.path.dirname(__file__), '../../frontend/lab.html')
|
||||
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH 1: Add animated ray buttons + lens-maker controls to lens panel
|
||||
# Insert BEFORE the Aberrации section
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OLD_LENS = (
|
||||
' <div class="pp-hint">Тащи стрелку-предмет или фокус мышью</div>\n'
|
||||
' <div style="margin-top:8px"></div>\n'
|
||||
' <div class="gp-section-title" style="margin-bottom:6px">Аберрации</div>'
|
||||
)
|
||||
|
||||
NEW_LENS = (
|
||||
' <div class="pp-hint">Тащи стрелку-предмет или фокус мышью</div>\n'
|
||||
' <div style="margin-top:8px"></div>\n'
|
||||
' <!-- Feature 1: animated ray buttons -->\n'
|
||||
' <div style="display:flex;gap:4px;margin-bottom:8px">\n'
|
||||
' <button onclick="if(lensSim)lensSim.buildRays()" style="flex:1;padding:5px 0;border-radius:6px;border:none;background:linear-gradient(135deg,#06D6E0,#9B5DE5);color:#fff;font-size:.72rem;font-weight:700;cursor:pointer">Построить лучи</button>\n'
|
||||
' <button onclick="if(lensSim)lensSim.resetRays()" style="padding:5px 9px;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#888;font-size:.78rem;cursor:pointer" title="Сбросить лучи">↺</button>\n'
|
||||
' </div>\n'
|
||||
' <!-- Feature 3: lens-maker toggle -->\n'
|
||||
' <div style="margin-bottom:6px">\n'
|
||||
' <label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:#ccc;cursor:pointer">\n'
|
||||
' <input type="checkbox" id="ltog-lensmaker" onchange="lensToggleLM(this.checked)">\n'
|
||||
' Подробный (R1/R2/n)\n'
|
||||
' </label>\n'
|
||||
' </div>\n'
|
||||
' <!-- LM sliders (hidden by default) -->\n'
|
||||
' <div id="ob-lm-sliders" style="display:none">\n'
|
||||
' <div class="proj-slider-row" style="margin-bottom:6px">\n'
|
||||
' <label style="font-size:.72rem;color:#ccc;width:60px">R1 = <span id="lm-r1-val" style="color:#FFD166;font-weight:700">200</span></label>\n'
|
||||
' <input type="range" id="sl-lm-r1" min="-300" max="300" step="5" value="200" oninput="lensLMParam(\'R1\',this.value)" style="flex:1">\n'
|
||||
' </div>\n'
|
||||
' <div class="proj-slider-row" style="margin-bottom:6px">\n'
|
||||
' <label style="font-size:.72rem;color:#ccc;width:60px">R2 = <span id="lm-r2-val" style="color:#FFD166;font-weight:700">-200</span></label>\n'
|
||||
' <input type="range" id="sl-lm-r2" min="-300" max="300" step="5" value="-200" oninput="lensLMParam(\'R2\',this.value)" style="flex:1">\n'
|
||||
' </div>\n'
|
||||
' <div class="proj-slider-row" style="margin-bottom:8px">\n'
|
||||
' <label style="font-size:.72rem;color:#ccc;width:60px">n = <span id="lm-n-val" style="color:#9B5DE5;font-weight:700">1.50</span></label>\n'
|
||||
' <input type="range" id="sl-lm-n" min="1.3" max="2.4" step="0.05" value="1.5" oninput="lensLMParam(\'n\',this.value)" style="flex:1">\n'
|
||||
' </div>\n'
|
||||
' <div style="font-size:.68rem;color:#888;margin-bottom:6px">f = 1/((n-1)*(1/R1 - 1/R2))</div>\n'
|
||||
' </div>\n'
|
||||
' <div style="margin-top:0"></div>\n'
|
||||
' <div class="gp-section-title" style="margin-bottom:6px">Аберрации</div>'
|
||||
)
|
||||
|
||||
if OLD_LENS not in content:
|
||||
print('ERROR: lens panel marker not found'); exit(1)
|
||||
|
||||
content = content.replace(OLD_LENS, NEW_LENS, 1)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH 2: Add R slider + parabolic toggle to mirror panel
|
||||
# Insert BEFORE the "Отображение" section
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OLD_MIRROR = (
|
||||
' <div class="gp-section-title" style="margin-bottom:6px">Отображение</div>'
|
||||
)
|
||||
|
||||
NEW_MIRROR = (
|
||||
' <!-- Feature 2: R-slider + parabolic toggle -->\n'
|
||||
' <div style="margin-bottom:6px">\n'
|
||||
' <label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:#ccc;cursor:pointer">\n'
|
||||
' <input type="checkbox" id="mtog-useR" onchange="mirrorToggleR(this.checked)">\n'
|
||||
' Радиус R (непрерывный)\n'
|
||||
' </label>\n'
|
||||
' </div>\n'
|
||||
' <div id="ob-mirror-R-row" class="proj-slider-row" style="margin-bottom:6px;display:none">\n'
|
||||
' <label style="font-size:.72rem;color:#ccc;width:60px">R = <span id="mirror-R-val" style="color:var(--cyan);font-weight:700">240</span></label>\n'
|
||||
' <input type="range" id="sl-mirror-R" min="-250" max="250" step="5" value="240" oninput="mirrorRParam(this.value)" style="flex:1">\n'
|
||||
' </div>\n'
|
||||
' <div style="display:flex;gap:4px;margin-bottom:8px">\n'
|
||||
' <button id="mirror-parab-btn" onclick="mirrorToggleParabolic(this)" style="flex:1;padding:5px 0;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#888;font-size:.72rem;cursor:pointer">Сферическое</button>\n'
|
||||
' </div>\n'
|
||||
' <div class="gp-section-title" style="margin-bottom:6px">Отображение</div>'
|
||||
)
|
||||
|
||||
if OLD_MIRROR not in content:
|
||||
print('ERROR: mirror panel Отображение marker not found'); exit(1)
|
||||
|
||||
content = content.replace(OLD_MIRROR, NEW_MIRROR, 1)
|
||||
|
||||
with open(src, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print('OK')
|
||||
@@ -0,0 +1,409 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const targetFile = path.join(__dirname, '../../frontend/js/labs/opticsbench.js');
|
||||
|
||||
const ifSimCode = `/* ─────────────────────────────────────────────────────────────
|
||||
4d. INTERFERENCE SIM — Newton's rings / Thin film / Polarization
|
||||
Agent C — additive only, class InterferenceSim
|
||||
─────────────────────────────────────────────────────────────*/
|
||||
class InterferenceSim {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.W = 0; this.H = 0;
|
||||
this.subMode = 'newton';
|
||||
// Newton rings
|
||||
this.nR = 200;
|
||||
this.nNmax = 12;
|
||||
// Thin film
|
||||
this.tfT = 400;
|
||||
this.tfN = 1.33;
|
||||
this.tfTheta = 0;
|
||||
this.tfPreset = 'soap';
|
||||
// Polarization
|
||||
this.polTheta = 45;
|
||||
this.polSrc = 'unpolarized';
|
||||
this._polTick = 0;
|
||||
this._polRaf = null;
|
||||
this.onUpdate = null;
|
||||
new ResizeObserver(() => { this.fit(); this.draw(); }).observe(canvas.parentElement || canvas);
|
||||
}
|
||||
|
||||
fit() {
|
||||
const p = this.canvas.parentElement;
|
||||
if (!p) return;
|
||||
const r = p.getBoundingClientRect();
|
||||
this.W = this.canvas.width = r.width || p.offsetWidth || 600;
|
||||
this.H = this.canvas.height = r.height || p.offsetHeight || 400;
|
||||
}
|
||||
|
||||
setSubMode(sm) {
|
||||
this.subMode = sm;
|
||||
if (sm === 'polarization') {
|
||||
this._polStart();
|
||||
} else {
|
||||
this._polStop();
|
||||
}
|
||||
this.draw();
|
||||
if (this.onUpdate) this.onUpdate();
|
||||
}
|
||||
|
||||
/* ── Newton Rings ──────────────────────────────────────── */
|
||||
_drawNewton() {
|
||||
const { ctx, W, H } = this;
|
||||
const nm = window._obWavelength || 550;
|
||||
const R = this.nR;
|
||||
const nMax = this.nNmax;
|
||||
const white = window._obWhiteLight;
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.fillStyle = '#08081a';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
const topH = Math.floor(H * 0.60);
|
||||
const cx = W / 2, cy = topH / 2;
|
||||
const maxR_mm = Math.sqrt(nMax * nm * 1e-6 * R);
|
||||
const scale = Math.min(cx * 0.85, cy * 0.85) / (maxR_mm || 1);
|
||||
|
||||
for (let n = nMax; n >= 0; n--) {
|
||||
const lambdas = white ? [420, 470, 510, 550, 590, 620, 680] : [nm];
|
||||
for (const lam of lambdas) {
|
||||
const rDark = Math.sqrt(n * lam * 1e-6 * R) * scale;
|
||||
const rBright = Math.sqrt((n + 0.5) * lam * 1e-6 * R) * scale;
|
||||
if (rDark > 0.5) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, rDark, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = white
|
||||
? wavelengthToRGB(lam).replace(')', ',0.5)').replace('rgb', 'rgba')
|
||||
: '#000000';
|
||||
ctx.lineWidth = white ? 1.2 : 1.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
if (rBright > 0.5) {
|
||||
const al = white ? 0.22 : 0.55;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, rBright, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = wavelengthToRGB(lam).replace(')', ',' + al + ')').replace('rgb', 'rgba');
|
||||
ctx.lineWidth = 2.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.beginPath(); ctx.arc(cx, cy, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#000000'; ctx.fill();
|
||||
|
||||
if (window.LabFX && LabFX.glow && !white) {
|
||||
const r1b = Math.sqrt(0.5 * nm * 1e-6 * R) * scale;
|
||||
LabFX.glow.drawGlow(ctx, cx, cy, r1b, wavelengthToRGB(nm), 18);
|
||||
}
|
||||
|
||||
ctx.beginPath(); ctx.arc(cx, cy, maxR_mm * scale * 1.05, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = '#334455'; ctx.lineWidth = 1; ctx.stroke();
|
||||
|
||||
const crossY0 = topH + 8;
|
||||
const crossH = H - crossY0 - 40;
|
||||
if (crossH < 30) return;
|
||||
|
||||
ctx.fillStyle = '#0d0d20';
|
||||
ctx.fillRect(0, crossY0, W, crossH + 36);
|
||||
|
||||
const glassY = crossY0 + crossH - 10;
|
||||
ctx.fillStyle = '#1a3a5c';
|
||||
ctx.fillRect(cx - maxR_mm * scale * 1.1, glassY, maxR_mm * scale * 2.2, 10);
|
||||
|
||||
const sagitta = (maxR_mm * maxR_mm) / (2 * R);
|
||||
const sagPx = sagitta * scale;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(cx, glassY - 1 - sagPx, maxR_mm * scale * 1.1, sagPx + 6, 0, 0, Math.PI);
|
||||
ctx.fillStyle = 'rgba(100,180,255,0.15)'; ctx.fill();
|
||||
ctx.strokeStyle = '#4499cc'; ctx.lineWidth = 1.5; ctx.stroke();
|
||||
|
||||
for (let n = 0; n <= nMax; n++) {
|
||||
const rD = Math.sqrt(n * nm * 1e-6 * R) * scale;
|
||||
if (rD < 1) continue;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + rD, glassY); ctx.lineTo(cx + rD, glassY + 8);
|
||||
ctx.moveTo(cx - rD, glassY); ctx.lineTo(cx - rD, glassY + 8);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.25)'; ctx.lineWidth = 1; ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.font = '600 11px monospace'; ctx.fillStyle = '#667788'; ctx.textAlign = 'center';
|
||||
ctx.fillText('Cross-section', cx, crossY0 + 14);
|
||||
|
||||
const r1d = Math.sqrt(nm * 1e-6 * R).toFixed(3);
|
||||
this._drawHUD(ctx, W, H,
|
||||
'r1 = sqrt(lam*R) = ' + r1d + ' mm | R=' + R + 'mm | lam=' + nm + 'nm');
|
||||
}
|
||||
|
||||
/* ── Thin Film ─────────────────────────────────────────── */
|
||||
_thinFilmColor(t_nm, n_film, theta_deg) {
|
||||
const sinR = Math.sin(theta_deg * Math.PI / 180) / n_film;
|
||||
const cosR = Math.sqrt(Math.max(0, 1 - sinR * sinR));
|
||||
const opd = 2 * n_film * t_nm * cosR;
|
||||
let rS = 0, gS = 0, bS = 0;
|
||||
for (let lam = 380; lam <= 780; lam += 5) {
|
||||
const phase = Math.PI * opd / lam;
|
||||
const I = Math.cos(phase) * Math.cos(phase);
|
||||
const rgb = wavelengthToRGB(lam);
|
||||
const m = rgb.match(/\d+/g);
|
||||
if (!m) continue;
|
||||
rS += I * +m[0]; gS += I * +m[1]; bS += I * +m[2];
|
||||
}
|
||||
const sc = 255 / Math.max(rS, gS, bS, 1);
|
||||
return 'rgb(' + Math.round(rS * sc) + ',' + Math.round(gS * sc) + ',' + Math.round(bS * sc) + ')';
|
||||
}
|
||||
|
||||
_drawThinFilm() {
|
||||
const { ctx, W, H } = this;
|
||||
const t = this.tfT;
|
||||
const nf = this.tfN;
|
||||
const theta = this.tfTheta;
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.fillStyle = '#08081a';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
const midY = H * 0.40;
|
||||
const filmH = Math.max(28, H * 0.12);
|
||||
const margin = W * 0.10;
|
||||
const ang = theta * Math.PI / 180;
|
||||
const skew = Math.tan(ang) * filmH * 0.5;
|
||||
|
||||
const grad = ctx.createLinearGradient(margin, 0, W - margin, 0);
|
||||
for (let i = 0; i <= 20; i++) {
|
||||
const frac = i / 20;
|
||||
grad.addColorStop(frac, this._thinFilmColor(t * (0.3 + 0.7 * frac), nf, theta));
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(margin - skew, midY - filmH / 2);
|
||||
ctx.lineTo(W - margin - skew, midY - filmH / 2);
|
||||
ctx.lineTo(W - margin + skew, midY + filmH / 2);
|
||||
ctx.lineTo(margin + skew, midY + filmH / 2);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = grad; ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1; ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
ctx.font = '700 11px sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('t=' + t + 'nm n=' + nf.toFixed(2), W / 2, midY);
|
||||
|
||||
const ax2 = W * 0.25, ay2 = midY - filmH / 2;
|
||||
const ax1 = ax2 - Math.cos(ang) * 40, ay1 = ay2 - Math.sin(ang) * 40 - 20;
|
||||
ctx.beginPath(); ctx.moveTo(ax1, ay1); ctx.lineTo(ax2, ay2);
|
||||
ctx.strokeStyle = '#8ab4e8'; ctx.lineWidth = 1.5; ctx.stroke();
|
||||
|
||||
const col = this._thinFilmColor(t, nf, theta);
|
||||
ctx.beginPath(); ctx.moveTo(ax2, ay2); ctx.lineTo(ax2 - Math.cos(ang) * 40, ay1);
|
||||
ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.stroke();
|
||||
|
||||
const dx2 = Math.sin(ang) * filmH / nf;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax2 + dx2, ay2 + filmH);
|
||||
ctx.lineTo(ax2 + dx2 - Math.cos(ang) * 40, ay1 + filmH - 20);
|
||||
ctx.strokeStyle = col; ctx.lineWidth = 2;
|
||||
ctx.setLineDash([4, 3]); ctx.stroke(); ctx.setLineDash([]);
|
||||
|
||||
const tvX0 = W * 0.55, tvW2 = W * 0.38;
|
||||
const tvY0 = H * 0.05, tvH2 = H * 0.60;
|
||||
ctx.fillStyle = '#0d0d22'; ctx.strokeStyle = '#2a2a4a'; ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) ctx.roundRect(tvX0, tvY0, tvW2, tvH2, 8);
|
||||
else ctx.rect(tvX0, tvY0, tvW2, tvH2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.font = '600 10px sans-serif'; ctx.fillStyle = '#555'; ctx.textAlign = 'center';
|
||||
ctx.fillText('Top view', tvX0 + tvW2 / 2, tvY0 + 14);
|
||||
|
||||
const tvRows = 28, tvCols = 36;
|
||||
const cW = tvW2 / tvCols, cH = (tvH2 - 20) / tvRows;
|
||||
for (let r = 0; r < tvRows; r++) {
|
||||
for (let c = 0; c < tvCols; c++) {
|
||||
ctx.fillStyle = this._thinFilmColor(t * (0.5 + c / tvCols), nf, theta * (r / tvRows));
|
||||
ctx.fillRect(tvX0 + c * cW, tvY0 + 20 + r * cH, cW + 0.5, cH + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
const sinR2 = Math.sin(ang) / nf;
|
||||
const cosR2 = Math.sqrt(Math.max(0, 1 - sinR2 * sinR2));
|
||||
const opd2 = (2 * nf * t * cosR2).toFixed(0);
|
||||
this._drawHUD(ctx, W, H,
|
||||
'2nt*cos(th_r)=' + opd2 + 'nm | t=' + t + 'nm n=' + nf.toFixed(2) + ' th=' + theta + 'deg');
|
||||
}
|
||||
|
||||
/* ── Polarization ──────────────────────────────────────── */
|
||||
_polStart() {
|
||||
if (this._polRaf) return;
|
||||
const loop = () => { this._polTick++; this.draw(); this._polRaf = requestAnimationFrame(loop); };
|
||||
this._polRaf = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
_polStop() {
|
||||
if (this._polRaf) { cancelAnimationFrame(this._polRaf); this._polRaf = null; }
|
||||
}
|
||||
|
||||
_drawPolarization() {
|
||||
const { ctx, W, H } = this;
|
||||
const theta = this.polTheta * Math.PI / 180;
|
||||
const I_rel = Math.cos(theta) * Math.cos(theta);
|
||||
const tick = this._polTick;
|
||||
const white = window._obWhiteLight;
|
||||
const nm = window._obWavelength || 550;
|
||||
const beamCol = white ? '#ffffff' : wavelengthToRGB(nm);
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.fillStyle = '#08081a';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
const axisY = H * 0.45;
|
||||
const stH = H * 0.38;
|
||||
const st = [
|
||||
{ x: W * 0.12, label: 'Источник', isFilter: false },
|
||||
{ x: W * 0.38, label: 'Поляризатор P1', isFilter: true, angle: 0 },
|
||||
{ x: W * 0.64, label: 'Анализатор P2', isFilter: true, angle: this.polTheta },
|
||||
{ x: W * 0.88, label: 'Детектор', isFilter: false },
|
||||
];
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(st[0].x - 20, axisY); ctx.lineTo(st[3].x + 20, axisY);
|
||||
ctx.strokeStyle = '#1a1a35'; ctx.lineWidth = 1; ctx.stroke();
|
||||
|
||||
const segs = [
|
||||
{ x0: st[0].x, x1: st[1].x, amp: 1, unpol: this.polSrc === 'unpolarized', ang: 0 },
|
||||
{ x0: st[1].x, x1: st[2].x, amp: 1, unpol: false, ang: 0 },
|
||||
{ x0: st[2].x, x1: st[3].x, amp: I_rel, unpol: false, ang: this.polTheta },
|
||||
];
|
||||
|
||||
for (const seg of segs) {
|
||||
const nA = 20;
|
||||
const sdx = (seg.x1 - seg.x0) / nA;
|
||||
for (let i = 0; i <= nA; i++) {
|
||||
const bx = seg.x0 + i * sdx;
|
||||
const phase = (bx * 0.08 - tick * 0.04) % (Math.PI * 2);
|
||||
const bAmp = stH * 0.28 * seg.amp;
|
||||
if (seg.unpol) {
|
||||
for (let d = 0; d < 4; d++) {
|
||||
const a = d * Math.PI / 4;
|
||||
const oy = Math.sin(phase + d * 0.7) * bAmp;
|
||||
ctx.beginPath(); ctx.moveTo(bx, axisY);
|
||||
ctx.lineTo(bx + oy * Math.sin(a) * 0.25, axisY + oy * Math.cos(a));
|
||||
ctx.strokeStyle = 'rgba(200,200,255,0.22)'; ctx.lineWidth = 1; ctx.stroke();
|
||||
}
|
||||
} else {
|
||||
const oy = Math.sin(phase) * bAmp;
|
||||
const a = seg.ang * Math.PI / 180;
|
||||
const py = oy * Math.cos(a), px = oy * Math.sin(a) * 0.35;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bx - px, axisY - py); ctx.lineTo(bx + px, axisY + py);
|
||||
ctx.strokeStyle = (I_rel < 0.01 && seg.amp < 0.5)
|
||||
? 'rgba(80,80,120,0.5)'
|
||||
: beamCol.replace(')', ',0.75)').replace('rgb', 'rgba');
|
||||
ctx.lineWidth = 1.5; ctx.stroke();
|
||||
if (i % 3 === 0 && bAmp > 2) {
|
||||
ctx.beginPath(); ctx.arc(bx + px, axisY + py, 2, 0, Math.PI * 2);
|
||||
ctx.fillStyle = beamCol; ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of st) {
|
||||
if (!s.isFilter) continue;
|
||||
const a = s.angle * Math.PI / 180;
|
||||
ctx.save(); ctx.translate(s.x, axisY);
|
||||
ctx.fillStyle = 'rgba(80,120,200,0.18)';
|
||||
ctx.fillRect(-4, -stH / 2, 8, stH);
|
||||
ctx.strokeStyle = '#4466aa'; ctx.lineWidth = 1.5; ctx.strokeRect(-4, -stH / 2, 8, stH);
|
||||
const axLen = stH * 0.45;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-Math.sin(a) * axLen, -Math.cos(a) * axLen);
|
||||
ctx.lineTo( Math.sin(a) * axLen, Math.cos(a) * axLen);
|
||||
ctx.strokeStyle = '#7aaeff'; ctx.lineWidth = 2; ctx.stroke();
|
||||
ctx.restore();
|
||||
ctx.font = '700 10px monospace'; ctx.fillStyle = '#7aaeff'; ctx.textAlign = 'center';
|
||||
ctx.fillText(s.angle + 'deg', s.x, axisY + stH / 2 + 14);
|
||||
}
|
||||
|
||||
for (const s of st) {
|
||||
ctx.font = '600 10px sans-serif'; ctx.fillStyle = '#667788'; ctx.textAlign = 'center';
|
||||
ctx.fillText(s.label, s.x, axisY - stH / 2 - 8);
|
||||
}
|
||||
|
||||
const barX = W * 0.91, barW = 16;
|
||||
const barY0 = axisY - stH / 2;
|
||||
ctx.fillStyle = '#111122'; ctx.fillRect(barX, barY0, barW, stH);
|
||||
const fillH2 = stH * I_rel;
|
||||
if (fillH2 > 0) {
|
||||
const bg = ctx.createLinearGradient(barX, barY0 + stH - fillH2, barX, barY0 + stH);
|
||||
bg.addColorStop(0, beamCol); bg.addColorStop(1, 'rgba(0,0,0,0.2)');
|
||||
ctx.fillStyle = bg; ctx.fillRect(barX, barY0 + stH - fillH2, barW, fillH2);
|
||||
}
|
||||
ctx.strokeStyle = '#334455'; ctx.lineWidth = 1; ctx.strokeRect(barX, barY0, barW, stH);
|
||||
ctx.font = '600 9px monospace'; ctx.fillStyle = '#aaaaaa'; ctx.textAlign = 'center';
|
||||
ctx.fillText('I', barX + barW / 2, barY0 - 5);
|
||||
|
||||
if (this.polTheta >= 88) {
|
||||
ctx.font = '700 13px sans-serif'; ctx.fillStyle = '#EF476F'; ctx.textAlign = 'center';
|
||||
ctx.fillText('Полное гашение', W / 2, H * 0.85);
|
||||
}
|
||||
|
||||
ctx.font = '10px sans-serif'; ctx.fillStyle = '#444466'; ctx.textAlign = 'right';
|
||||
ctx.fillText('Угол Брюстера: отражённый свет поляризован (см. Преломление)', W - 10, H - 10);
|
||||
|
||||
const pct = (I_rel * 100).toFixed(1);
|
||||
this._drawHUD(ctx, W, H,
|
||||
'I/I0=cos2(th)=cos2(' + this.polTheta + 'deg)=' + I_rel.toFixed(3) + ' (' + pct + '%)');
|
||||
}
|
||||
|
||||
_drawHUD(ctx, W, H, text) {
|
||||
const pad = 8, fs = 11;
|
||||
ctx.font = '600 ' + fs + 'px monospace';
|
||||
const tw = ctx.measureText(text).width;
|
||||
const bx = (W - tw) / 2 - pad, by = H - 32;
|
||||
const bw = tw + pad * 2, bh = fs + pad * 2;
|
||||
ctx.fillStyle = 'rgba(10,10,30,0.82)';
|
||||
ctx.beginPath();
|
||||
if (ctx.roundRect) ctx.roundRect(bx, by, bw, bh, 5);
|
||||
else ctx.rect(bx, by, bw, bh);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#c8d8ff';
|
||||
ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, bx + pad, by + bh / 2);
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.subMode === 'newton') this._drawNewton();
|
||||
else if (this.subMode === 'thinfilm') this._drawThinFilm();
|
||||
else if (this.subMode === 'polarization') this._drawPolarization();
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const src = fs.readFileSync(targetFile, 'utf-8');
|
||||
const markerStr = '4c. SPECTROMETER PANEL';
|
||||
const markerIdx = src.indexOf(markerStr);
|
||||
if (markerIdx < 0) {
|
||||
console.error('ERROR: marker not found');
|
||||
process.exit(1);
|
||||
}
|
||||
const insertIdx = src.lastIndexOf('/*', markerIdx);
|
||||
if (insertIdx < 0) {
|
||||
console.error('ERROR: comment start not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check InterferenceSim not already present
|
||||
if (src.indexOf('class InterferenceSim') >= 0) {
|
||||
console.log('InterferenceSim already present — skipping JS insertion');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = src.slice(0, insertIdx) + ifSimCode + src.slice(insertIdx);
|
||||
fs.writeFileSync(targetFile, result, 'utf-8');
|
||||
console.log('JS insertion OK. New size:', result.length);
|
||||
@@ -0,0 +1,235 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const targetFile = path.join(__dirname, '../../frontend/js/labs/opticsbench.js');
|
||||
let src = fs.readFileSync(targetFile, 'utf-8');
|
||||
|
||||
// --- 1. Add ifSim variable declaration after freeSim ---
|
||||
if (src.indexOf('var ifSim') < 0) {
|
||||
src = src.replace(
|
||||
'var freeSim = null; /* multi-lens free-build (Agent OB-A3) */',
|
||||
'var freeSim = null; /* multi-lens free-build (Agent OB-A3) */\r\nvar ifSim = null; /* interference/polarization (Agent C) */'
|
||||
);
|
||||
console.log('Added ifSim declaration');
|
||||
} else {
|
||||
console.log('ifSim declaration already exists');
|
||||
}
|
||||
|
||||
// --- 2. Add 'interf' to tab array in obSwitchMode ---
|
||||
const tabArrOld = "['lens', 'mirror', 'refraction', 'prism', 'freebuild', 'waves'].forEach(m => {";
|
||||
const tabArrNew = "['lens', 'mirror', 'refraction', 'prism', 'freebuild', 'waves', 'interf'].forEach(m => {";
|
||||
if (src.indexOf(tabArrNew) < 0) {
|
||||
if (src.indexOf(tabArrOld) >= 0) {
|
||||
src = src.replace(tabArrOld, tabArrNew);
|
||||
console.log('Updated tab array');
|
||||
} else {
|
||||
console.log('WARN: tab array old pattern not found');
|
||||
}
|
||||
} else {
|
||||
console.log('Tab array already updated');
|
||||
}
|
||||
|
||||
// --- 3. Add 'ob-ctrl-interf' to control panels array ---
|
||||
const ctrlArrOld = "['ob-ctrl-lens', 'ob-ctrl-mirror', 'ob-ctrl-refraction', 'ob-ctrl-prism', 'ob-ctrl-freebuild', 'ob-ctrl-waves'].forEach(id => {";
|
||||
const ctrlArrNew = "['ob-ctrl-lens', 'ob-ctrl-mirror', 'ob-ctrl-refraction', 'ob-ctrl-prism', 'ob-ctrl-freebuild', 'ob-ctrl-waves', 'ob-ctrl-interf'].forEach(id => {";
|
||||
if (src.indexOf(ctrlArrNew) < 0) {
|
||||
if (src.indexOf(ctrlArrOld) >= 0) {
|
||||
src = src.replace(ctrlArrOld, ctrlArrNew);
|
||||
console.log('Updated ctrl panel array');
|
||||
} else {
|
||||
console.log('WARN: ctrl panel array old pattern not found');
|
||||
}
|
||||
} else {
|
||||
console.log('Ctrl panel array already updated');
|
||||
}
|
||||
|
||||
// --- 4. Add 'ob-stats-interf' to stats array ---
|
||||
const statsArrOld = "['ob-stats-lens', 'ob-stats-mirror', 'ob-stats-refr', 'ob-stats-prism', 'ob-stats-freebuild', 'ob-stats-waves'].forEach(id => {";
|
||||
const statsArrNew = "['ob-stats-lens', 'ob-stats-mirror', 'ob-stats-refr', 'ob-stats-prism', 'ob-stats-freebuild', 'ob-stats-waves', 'ob-stats-interf'].forEach(id => {";
|
||||
if (src.indexOf(statsArrNew) < 0) {
|
||||
if (src.indexOf(statsArrOld) >= 0) {
|
||||
src = src.replace(statsArrOld, statsArrNew);
|
||||
console.log('Updated stats array');
|
||||
} else {
|
||||
console.log('WARN: stats array old pattern not found');
|
||||
}
|
||||
} else {
|
||||
console.log('Stats array already updated');
|
||||
}
|
||||
|
||||
// --- 5. Add 'ob-interf-canvas' to canvas arrays ---
|
||||
const canvasIdsOld = "const canvasIds = ['ob-lens-canvas', 'ob-mirror-canvas', 'ob-refr-canvas', 'ob-prism-canvas', 'ob-free-canvas', 'ob-waves-canvas'];";
|
||||
const canvasIdsNew = "const canvasIds = ['ob-lens-canvas', 'ob-mirror-canvas', 'ob-refr-canvas', 'ob-prism-canvas', 'ob-free-canvas', 'ob-waves-canvas', 'ob-interf-canvas'];";
|
||||
if (src.indexOf(canvasIdsNew) < 0) {
|
||||
if (src.indexOf(canvasIdsOld) >= 0) {
|
||||
src = src.replace(canvasIdsOld, canvasIdsNew);
|
||||
console.log('Updated canvasIds');
|
||||
} else {
|
||||
console.log('WARN: canvasIds old pattern not found');
|
||||
}
|
||||
} else {
|
||||
console.log('canvasIds already updated');
|
||||
}
|
||||
|
||||
const modeCanvasOld = "const modeCanvas = { lens: 'ob-lens-canvas', mirror: 'ob-mirror-canvas', refraction: 'ob-refr-canvas', prism: 'ob-prism-canvas', freebuild: 'ob-free-canvas', waves: 'ob-waves-canvas' };";
|
||||
const modeCanvasNew = "const modeCanvas = { lens: 'ob-lens-canvas', mirror: 'ob-mirror-canvas', refraction: 'ob-refr-canvas', prism: 'ob-prism-canvas', freebuild: 'ob-free-canvas', waves: 'ob-waves-canvas', interf: 'ob-interf-canvas' };";
|
||||
if (src.indexOf(modeCanvasNew) < 0) {
|
||||
if (src.indexOf(modeCanvasOld) >= 0) {
|
||||
src = src.replace(modeCanvasOld, modeCanvasNew);
|
||||
console.log('Updated modeCanvas');
|
||||
} else {
|
||||
console.log('WARN: modeCanvas old pattern not found');
|
||||
}
|
||||
} else {
|
||||
console.log('modeCanvas already updated');
|
||||
}
|
||||
|
||||
// --- 6. Add 'interf' case in obSwitchMode, before closing brace ---
|
||||
const interfCaseStr = ` } else if (mode === 'interf') { /* Agent C — interference / polarization */\r\n if (!ifSim) {\r\n const cv = document.getElementById('ob-interf-canvas');\r\n if (cv) { ifSim = new InterferenceSim(cv); ifSim.onUpdate = _ifUpdateUI; }\r\n }\r\n if (ifSim) { ifSim.fit(); ifSim.draw(); }\r\n _ifUpdateUI();\r\n }\r\n}`;
|
||||
|
||||
const oldModeEnd = ` } else if (mode === 'waves') { /* Agent B1 — diffraction & interference */\r\n if (!diffrSim) {\r\n const cv = document.getElementById('ob-waves-canvas');\r\n if (cv) diffrSim = new DiffractionSim(cv);\r\n }\r\n if (diffrSim) {\r\n diffrSim.fit();\r\n diffrSim.draw();\r\n diffrSim._updateHUD();\r\n }\r\n }\r\n}`;
|
||||
|
||||
const newModeEnd = ` } else if (mode === 'waves') { /* Agent B1 — diffraction & interference */\r\n if (!diffrSim) {\r\n const cv = document.getElementById('ob-waves-canvas');\r\n if (cv) diffrSim = new DiffractionSim(cv);\r\n }\r\n if (diffrSim) {\r\n diffrSim.fit();\r\n diffrSim.draw();\r\n diffrSim._updateHUD();\r\n }\r\n } else if (mode === 'interf') { /* Agent C — interference / polarization */\r\n if (!ifSim) {\r\n const cv = document.getElementById('ob-interf-canvas');\r\n if (cv) { ifSim = new InterferenceSim(cv); ifSim.onUpdate = _ifUpdateUI; }\r\n }\r\n if (ifSim) { ifSim.fit(); ifSim.draw(); }\r\n _ifUpdateUI();\r\n }\r\n}`;
|
||||
|
||||
if (src.indexOf('else if (mode === \'interf\')') < 0) {
|
||||
if (src.indexOf(oldModeEnd) >= 0) {
|
||||
src = src.replace(oldModeEnd, newModeEnd);
|
||||
console.log('Added interf case in obSwitchMode');
|
||||
} else {
|
||||
// Try with LF only
|
||||
const oldLF = oldModeEnd.replace(/\r\n/g, '\n');
|
||||
const newLF = newModeEnd.replace(/\r\n/g, '\n');
|
||||
if (src.indexOf(oldLF) >= 0) {
|
||||
src = src.replace(oldLF, newLF);
|
||||
console.log('Added interf case (LF variant)');
|
||||
} else {
|
||||
console.log('WARN: waves mode end pattern not found - trying fallback');
|
||||
// Fallback: find closing brace of obSwitchMode after diffrSim block
|
||||
const marker2 = "diffrSim._updateHUD();\n }\n }\n}";
|
||||
const marker2cr = "diffrSim._updateHUD();\r\n }\r\n }\r\n}";
|
||||
const repl2 = "diffrSim._updateHUD();\n }\n } else if (mode === 'interf') {\n if (!ifSim) {\n const cv = document.getElementById('ob-interf-canvas');\n if (cv) { ifSim = new InterferenceSim(cv); ifSim.onUpdate = _ifUpdateUI; }\n }\n if (ifSim) { ifSim.fit(); ifSim.draw(); }\n _ifUpdateUI();\n }\n}";
|
||||
const repl2cr = "diffrSim._updateHUD();\r\n }\r\n } else if (mode === 'interf') {\r\n if (!ifSim) {\r\n const cv = document.getElementById('ob-interf-canvas');\r\n if (cv) { ifSim = new InterferenceSim(cv); ifSim.onUpdate = _ifUpdateUI; }\r\n }\r\n if (ifSim) { ifSim.fit(); ifSim.draw(); }\r\n _ifUpdateUI();\r\n }\r\n}";
|
||||
if (src.indexOf(marker2) >= 0) {
|
||||
src = src.replace(marker2, repl2);
|
||||
console.log('Added interf case (fallback LF)');
|
||||
} else if (src.indexOf(marker2cr) >= 0) {
|
||||
src = src.replace(marker2cr, repl2cr);
|
||||
console.log('Added interf case (fallback CRLF)');
|
||||
} else {
|
||||
console.log('WARN: all fallbacks failed for interf case');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('interf case already exists');
|
||||
}
|
||||
|
||||
// --- 7. Add _ifUpdateUI function and control functions ---
|
||||
const ifUICode = `
|
||||
/* ── Interference mode UI callbacks (Agent C) ── */
|
||||
function _ifUpdateUI() {
|
||||
if (!ifSim) return;
|
||||
const subMode = ifSim.subMode;
|
||||
['if-ctrl-newton', 'if-ctrl-thinfilm', 'if-ctrl-polarization'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = 'none';
|
||||
});
|
||||
const active = document.getElementById('if-ctrl-' + subMode);
|
||||
if (active) active.style.display = '';
|
||||
['if-sub-newton', 'if-sub-thinfilm', 'if-sub-polarization'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.toggle('active', id === 'if-sub-' + subMode);
|
||||
});
|
||||
}
|
||||
|
||||
function ifSwitchSub(sub) {
|
||||
if (window.LabFX) LabFX.sound.play('chime');
|
||||
if (!ifSim) return;
|
||||
ifSim.setSubMode(sub);
|
||||
_ifUpdateUI();
|
||||
}
|
||||
|
||||
function ifNewtParam(key, val) {
|
||||
if (!ifSim) return;
|
||||
const v = parseFloat(val);
|
||||
if (key === 'R') { ifSim.nR = v; document.getElementById('if-newton-r-val').textContent = v; }
|
||||
else if (key === 'nmax') { ifSim.nNmax = Math.round(v); document.getElementById('if-newton-n-val').textContent = Math.round(v); }
|
||||
ifSim.draw();
|
||||
}
|
||||
|
||||
function ifThinFilmParam(key, val) {
|
||||
if (!ifSim) return;
|
||||
const v = parseFloat(val);
|
||||
if (key === 't') { ifSim.tfT = v; document.getElementById('if-tf-t-val').textContent = v; }
|
||||
else if (key === 'n') { ifSim.tfN = v; document.getElementById('if-tf-n-val').textContent = v.toFixed(2); }
|
||||
else if (key === 'theta') { ifSim.tfTheta = v; document.getElementById('if-tf-th-val').textContent = v; }
|
||||
ifSim.draw();
|
||||
}
|
||||
|
||||
function ifThinFilmPreset(name) {
|
||||
if (!ifSim) return;
|
||||
const presets = {
|
||||
soap: { n: 1.33, label: 'Мыльная плёнка' },
|
||||
oil: { n: 1.50, label: 'Масло на воде' },
|
||||
coating: { n: 1.38, label: 'Антибликовое покрытие' },
|
||||
};
|
||||
const p = presets[name];
|
||||
if (!p) return;
|
||||
ifSim.tfN = p.n;
|
||||
ifSim.tfPreset = name;
|
||||
const slN = document.getElementById('sl-if-tf-n');
|
||||
if (slN) slN.value = p.n;
|
||||
const lbN = document.getElementById('if-tf-n-val');
|
||||
if (lbN) lbN.textContent = p.n.toFixed(2);
|
||||
ifSim.draw();
|
||||
if (window.LabFX) LabFX.sound.play('chime');
|
||||
}
|
||||
|
||||
function ifPolParam(key, val) {
|
||||
if (!ifSim) return;
|
||||
const v = parseFloat(val);
|
||||
if (key === 'theta') { ifSim.polTheta = v; document.getElementById('if-pol-th-val').textContent = v; }
|
||||
ifSim.draw();
|
||||
}
|
||||
|
||||
function ifPolSrc(val) {
|
||||
if (!ifSim) return;
|
||||
ifSim.polSrc = val;
|
||||
ifSim.draw();
|
||||
}
|
||||
`;
|
||||
|
||||
if (src.indexOf('function _ifUpdateUI') < 0) {
|
||||
// Insert before the closing of the file or before _obRedraw
|
||||
const insertBefore = 'function _obRedraw()';
|
||||
const insertIdx = src.indexOf(insertBefore);
|
||||
if (insertIdx >= 0) {
|
||||
src = src.slice(0, insertIdx) + ifUICode + '\r\n' + src.slice(insertIdx);
|
||||
console.log('Added _ifUpdateUI and control functions');
|
||||
} else {
|
||||
src = src + '\r\n' + ifUICode;
|
||||
console.log('Appended _ifUpdateUI at end');
|
||||
}
|
||||
} else {
|
||||
console.log('_ifUpdateUI already exists');
|
||||
}
|
||||
|
||||
// --- 8. Make _obRedraw also redraw ifSim if active ---
|
||||
const redrawOld = 'function _obRedraw() {';
|
||||
const redrawNew = 'function _obRedraw() {';
|
||||
// Find _obRedraw body and add ifSim redraw
|
||||
if (src.indexOf('if (ifSim && _obMode') < 0) {
|
||||
const marker3 = "if (prismSim && _obMode === 'prism')";
|
||||
const repl3 = "if (ifSim && _obMode === 'interf') { ifSim.draw(); }\r\n if (prismSim && _obMode === 'prism')";
|
||||
const marker3lf = "if (prismSim && _obMode === 'prism')";
|
||||
if (src.indexOf(marker3) >= 0) {
|
||||
src = src.replace(marker3, repl3);
|
||||
console.log('Added ifSim redraw to _obRedraw');
|
||||
} else {
|
||||
console.log('WARN: could not find _obRedraw prism marker');
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetFile, src, 'utf-8');
|
||||
console.log('Done. New size:', src.length);
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const targetFile = path.join(__dirname, '../../frontend/js/labs/opticsbench.js');
|
||||
let src = fs.readFileSync(targetFile, 'utf-8');
|
||||
|
||||
// Add ifSim to _obRedraw
|
||||
const oldRedrawLine = " if (_obMode === 'waves' && diffrSim) { diffrSim.draw(); diffrSim._updateHUD(); }";
|
||||
const newRedrawLine = " if (_obMode === 'waves' && diffrSim) { diffrSim.draw(); diffrSim._updateHUD(); }\r\n if (_obMode === 'interf' && ifSim) { ifSim.draw(); }";
|
||||
|
||||
if (src.indexOf(newRedrawLine) < 0 && src.indexOf(oldRedrawLine) >= 0) {
|
||||
src = src.replace(oldRedrawLine, newRedrawLine);
|
||||
console.log('Added ifSim to _obRedraw');
|
||||
} else {
|
||||
console.log('ifSim redraw already present or old line not found');
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetFile, src, 'utf-8');
|
||||
console.log('Done. Size:', src.length);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,204 @@
|
||||
import sys, os
|
||||
|
||||
src = os.path.join(os.path.dirname(__file__), '../../frontend/js/labs/opticsbench.js')
|
||||
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH 1: Insert _drawRaysAnimated + _drawArrowLabels before _bindEvents
|
||||
# in ThinLensSim
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
MARKER_BIND = ' _bindEvents() {\n const cv = this.canvas;\n const getPos = (e) => {'
|
||||
idx = content.find(MARKER_BIND)
|
||||
if idx == -1:
|
||||
print('ERROR: _bindEvents marker not found'); sys.exit(1)
|
||||
|
||||
NEW_METHODS_1 = (
|
||||
' /* === _drawRaysAnimated: principal rays with per-ray progress === */\n'
|
||||
' _drawRaysAnimated(ctx, lx, ay, d, h, f, dPrime, hPrime) {\n'
|
||||
' const T = this._rayAnimT;\n'
|
||||
' if (T[0] >= 1 && T[1] >= 1 && T[2] >= 1) { this._drawRays(ctx, lx, ay, d, h, f, dPrime, hPrime); return; }\n'
|
||||
' const objX = lx - d, objY = ay - h;\n'
|
||||
' const hasImage = dPrime !== null && isFinite(dPrime);\n'
|
||||
' const isVirtual = hasImage && dPrime < 0;\n'
|
||||
' const COLORS = [\'#06D6E0\', \'#7BF5A4\', \'#FFD166\'];\n'
|
||||
' ctx.lineWidth = 1.8;\n'
|
||||
' const lerp = (a, b, t) => a + (b - a) * Math.min(1, Math.max(0, t));\n'
|
||||
' const drawPts = (color, pts, t) => {\n'
|
||||
' if (t <= 0 || pts.length < 2) return;\n'
|
||||
' const totalLen = pts.reduce((s, p, i) => i === 0 ? 0 : s + Math.hypot(p[0]-pts[i-1][0], p[1]-pts[i-1][1]), 0);\n'
|
||||
' const target = totalLen * t;\n'
|
||||
' const draw = () => {\n'
|
||||
' ctx.strokeStyle = color; ctx.setLineDash([]);\n'
|
||||
' ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);\n'
|
||||
' let drawn = 0;\n'
|
||||
' for (let i = 1; i < pts.length; i++) {\n'
|
||||
' const segLen = Math.hypot(pts[i][0]-pts[i-1][0], pts[i][1]-pts[i-1][1]);\n'
|
||||
' if (drawn + segLen <= target) { ctx.lineTo(pts[i][0], pts[i][1]); drawn += segLen; }\n'
|
||||
' else { const fr = segLen > 0 ? (target - drawn) / segLen : 0; ctx.lineTo(lerp(pts[i-1][0], pts[i][0], fr), lerp(pts[i-1][1], pts[i][1], fr)); break; }\n'
|
||||
' }\n'
|
||||
' ctx.stroke();\n'
|
||||
' };\n'
|
||||
' if (window.LabFX) LabFX.glow.drawGlow(ctx, draw, { color, intensity: 10 });\n'
|
||||
' else draw();\n'
|
||||
' };\n'
|
||||
' const FAR = lx + 360;\n'
|
||||
' const imgX = hasImage ? lx + dPrime : null, imgY = hasImage ? ay - hPrime : null;\n'
|
||||
' // Ray 1: parallel to axis -> through F\'\n'
|
||||
' if (T[0] > 0) {\n'
|
||||
' let pts;\n'
|
||||
' if (!hasImage) { pts = [[objX, objY], [lx, objY], [FAR, objY]]; }\n'
|
||||
' else if (!isVirtual) { pts = [[objX, objY], [lx, objY], [imgX, imgY]]; }\n'
|
||||
' else { const s = (objY - ay) / f; pts = [[objX, objY], [lx, objY], [FAR, objY + s*(FAR-lx)]]; }\n'
|
||||
' drawPts(COLORS[0], pts, T[0]);\n'
|
||||
' }\n'
|
||||
' // Ray 2: through optical center (straight)\n'
|
||||
' if (T[1] > 0) {\n'
|
||||
' const s = (objY - ay) / (objX - lx);\n'
|
||||
' drawPts(COLORS[1], [[objX, objY], [FAR, ay + s*(FAR-lx)]], T[1]);\n'
|
||||
' }\n'
|
||||
' // Ray 3: through front focus F -> parallel after lens\n'
|
||||
' if (T[2] > 0) {\n'
|
||||
' const fx = lx - f, s = (objY - ay) / (objX - fx);\n'
|
||||
' const hitY = objY + s * (lx - objX);\n'
|
||||
' const endX = hasImage && !isVirtual ? Math.max(imgX + 60, FAR) : FAR;\n'
|
||||
' drawPts(COLORS[2], [[objX, objY], [lx, hitY], [endX, hitY]], T[2]);\n'
|
||||
' }\n'
|
||||
' }\n'
|
||||
'\n'
|
||||
' /* === Arrow labels: h_o, h_i, magnification Gamma === */\n'
|
||||
' _drawArrowLabels(ctx, lx, ay, d, h, dPrime, hPrime) {\n'
|
||||
' const objX = lx - d;\n'
|
||||
' ctx.font = \'11px Manrope, system-ui, sans-serif\'; ctx.textBaseline = \'middle\';\n'
|
||||
' ctx.fillStyle = \'rgba(155,93,229,0.85)\'; ctx.textAlign = \'right\';\n'
|
||||
' ctx.fillText(\'ho=\' + h.toFixed(0), objX - 6, ay - h / 2);\n'
|
||||
' if (dPrime !== null && isFinite(dPrime)) {\n'
|
||||
' const imgX = lx + dPrime, isVirtual = dPrime < 0;\n'
|
||||
' const M = -dPrime / d;\n'
|
||||
' const Gstr = isFinite(M) ? (M >= 0 ? \'+\' : \'\') + M.toFixed(2) : \'---\';\n'
|
||||
' const imgColor = isVirtual ? \'rgba(255,133,162,0.85)\' : \'rgba(6,214,224,0.85)\';\n'
|
||||
' ctx.fillStyle = imgColor; ctx.textAlign = \'left\';\n'
|
||||
' ctx.fillText("hi=" + Math.abs(hPrime).toFixed(0), imgX + 6, ay - hPrime / 2);\n'
|
||||
' ctx.fillStyle = \'#FFD166\'; ctx.textAlign = \'center\';\n'
|
||||
' ctx.fillText(\'G=\' + Gstr, (lx + imgX) / 2, ay + 60);\n'
|
||||
' }\n'
|
||||
' }\n'
|
||||
'\n'
|
||||
)
|
||||
|
||||
new_content = content[:idx] + NEW_METHODS_1 + content[idx:]
|
||||
content = new_content
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH 2: Add R slider + parabolic/spherical toggle to MirrorSim constructor
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Find MirrorSim constructor and add _R and _parabolic fields
|
||||
OLD_MIRROR_CTOR = ' this._photonPaths = [];\n\n this._prevType = \'concave\';\n this._transT = 1.0;\n this._transRaf = null;\n\n this._drag = null;\n this._hoverX = -999;\n this._hoverY = -999;\n\n this.onUpdate = null;\n this.onAnimate = null;\n\n this._bindEvents();\n new ResizeObserver(() => { this.fit(); this.draw(); }).observe(canvas.parentElement);\n }'
|
||||
|
||||
NEW_MIRROR_CTOR = (
|
||||
' this._photonPaths = [];\n\n'
|
||||
' this._prevType = \'concave\';\n'
|
||||
' this._transT = 1.0;\n'
|
||||
' this._transRaf = null;\n\n'
|
||||
' this._drag = null;\n'
|
||||
' this._hoverX = -999;\n'
|
||||
' this._hoverY = -999;\n\n'
|
||||
' this.onUpdate = null;\n'
|
||||
' this.onAnimate = null;\n\n'
|
||||
' /* Feature 2: R slider + spherical aberration toggle */\n'
|
||||
' this._R = 240; // radius of curvature (positive=concave, negative=convex)\n'
|
||||
' this._useR = false; // true = R-slider mode; false = classic type+f mode\n'
|
||||
' this._parabolic = false; // false = spherical mirror; true = perfect parabolic\n'
|
||||
'\n'
|
||||
' this._bindEvents();\n'
|
||||
' new ResizeObserver(() => { this.fit(); this.draw(); }).observe(canvas.parentElement);\n'
|
||||
' }'
|
||||
)
|
||||
|
||||
if OLD_MIRROR_CTOR not in content:
|
||||
print('ERROR: MirrorSim ctor block not found'); sys.exit(1)
|
||||
|
||||
content = content.replace(OLD_MIRROR_CTOR, NEW_MIRROR_CTOR, 1)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH 3: Add setMirrorR, setMirrorParabolic, _drawAberrationFan
|
||||
# before MirrorSim._bindEvents
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Find the _bindEvents in MirrorSim (it's further down, after chk method)
|
||||
MIRROR_BIND_MARKER = ' _bindEvents() {\n const cv = this.canvas;\n const getPos = e => {'
|
||||
|
||||
idx2 = content.find(MIRROR_BIND_MARKER)
|
||||
if idx2 == -1:
|
||||
print('ERROR: chk marker not found'); sys.exit(1)
|
||||
|
||||
NEW_METHODS_2 = (
|
||||
' /* === Feature 2: R-slider mode for MirrorSim === */\n'
|
||||
' setMirrorR(R) {\n'
|
||||
' this._useR = true;\n'
|
||||
' this._R = +R;\n'
|
||||
' // Derive type and f from R\n'
|
||||
' const absR = Math.abs(this._R);\n'
|
||||
' if (absR < 5) { this.type = \'flat\'; }\n'
|
||||
' else if (this._R > 0) { this.type = \'concave\'; this.f = absR / 2; }\n'
|
||||
' else { this.type = \'convex\'; this.f = absR / 2; }\n'
|
||||
' this.draw(); this._emit();\n'
|
||||
' }\n'
|
||||
'\n'
|
||||
' setMirrorParabolic(on) {\n'
|
||||
' this._parabolic = !!on;\n'
|
||||
' this.draw();\n'
|
||||
' }\n'
|
||||
'\n'
|
||||
' /* Draw 5 parallel rays showing spherical vs parabolic aberration */\n'
|
||||
' _drawAberrationFan(ctx, mx, ay, f) {\n'
|
||||
' if (!isFinite(f) || Math.abs(f) < 5) return;\n'
|
||||
' const mH = Math.min(this.H * 0.38, 140);\n'
|
||||
' const heights = [-0.85, -0.45, 0, 0.45, 0.85];\n'
|
||||
' const COLORS = [\'#FF6B6B\', \'#FFD166\', \'#7BF5A4\', \'#06D6E0\', \'#B8A4FF\'];\n'
|
||||
' ctx.save(); ctx.lineWidth = 1.4;\n'
|
||||
' heights.forEach((fr, i) => {\n'
|
||||
' const rayH = fr * mH;\n'
|
||||
' // For parabolic mirror: all parallel rays focus exactly at f\n'
|
||||
' // For spherical: marginal rays (fr != 0) focus closer by h^2/(2R) approx\n'
|
||||
' const fEff = this._parabolic ? f : f - (rayH * rayH) / (2 * Math.abs(f) * 2);\n'
|
||||
' const startX = mx - this.d - 40;\n'
|
||||
' const hitY = ay - rayH; // hits mirror at height rayH\n'
|
||||
' // Incident ray: horizontal from left to mirror\n'
|
||||
' ctx.strokeStyle = COLORS[i]; ctx.globalAlpha = 0.75;\n'
|
||||
' ctx.setLineDash([]);\n'
|
||||
' ctx.beginPath(); ctx.moveTo(startX, ay - rayH); ctx.lineTo(mx, hitY); ctx.stroke();\n'
|
||||
' // Reflected ray: goes toward focal point fEff\n'
|
||||
' const focX = mx - fEff;\n'
|
||||
' if (focX > 0 && focX < this.W) {\n'
|
||||
' ctx.beginPath(); ctx.moveTo(mx, hitY); ctx.lineTo(focX, ay);\n'
|
||||
' // extend a bit past focus\n'
|
||||
' const dx = focX - mx, dy = ay - hitY, len = Math.hypot(dx, dy);\n'
|
||||
' if (len > 1) ctx.lineTo(focX + dx/len*50, ay + dy/len*50);\n'
|
||||
' ctx.stroke();\n'
|
||||
' }\n'
|
||||
' });\n'
|
||||
' ctx.globalAlpha = 1;\n'
|
||||
' // label\n'
|
||||
' const label = this._parabolic ? \'Параболическое (идеальный фокус)\' : \'Сферическое (аберрация)\';\n'
|
||||
' const col = this._parabolic ? \'#7BF5A4\' : \'#FF6B6B\';\n'
|
||||
' const bx = 12, by = this.H - 36;\n'
|
||||
' ctx.fillStyle = \'rgba(13,13,26,0.85)\';\n'
|
||||
' ctx.beginPath(); ctx.roundRect(bx, by, 250, 24, 6); ctx.fill();\n'
|
||||
' ctx.font = \'bold 11px Manrope, system-ui, sans-serif\';\n'
|
||||
' ctx.textAlign = \'left\'; ctx.textBaseline = \'middle\';\n'
|
||||
' ctx.fillStyle = col;\n'
|
||||
' ctx.fillText(label, bx + 8, by + 12);\n'
|
||||
' ctx.restore();\n'
|
||||
' }\n'
|
||||
'\n'
|
||||
)
|
||||
|
||||
new_content2 = content[:idx2] + NEW_METHODS_2 + content[idx2:]
|
||||
content = new_content2
|
||||
|
||||
with open(src, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print('OK lines:', content.count('\n'))
|
||||
@@ -0,0 +1,44 @@
|
||||
import sys, os
|
||||
|
||||
src = os.path.join(os.path.dirname(__file__), '../../frontend/js/labs/opticsbench.js')
|
||||
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH: In MirrorSim.draw(), after _drawFanRays, add aberration fan for _useR mode
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OLD = (' this._drawFanRays(ctx, mx, ay, f, dPrime, hPrime, showRay, showFill);\n'
|
||||
' /* spherical aberration overlay (Agent OB-A3) */\n'
|
||||
' if (this._showSpherical && this.type !== \'flat\' && isFinite(f))\n'
|
||||
' this._drawMirrorSphericalAberration(ctx, mx, ay, f);\n'
|
||||
' this._drawMirror(ctx, mx, ay);')
|
||||
|
||||
NEW = (' this._drawFanRays(ctx, mx, ay, f, dPrime, hPrime, showRay, showFill);\n'
|
||||
' /* spherical aberration overlay (Agent OB-A3) */\n'
|
||||
' if (this._showSpherical && this.type !== \'flat\' && isFinite(f))\n'
|
||||
' this._drawMirrorSphericalAberration(ctx, mx, ay, f);\n'
|
||||
' /* Feature 2: parabolic/spherical aberration fan */\n'
|
||||
' if (this._useR && this.type !== \'flat\' && isFinite(f))\n'
|
||||
' this._drawAberrationFan(ctx, mx, ay, f);\n'
|
||||
' this._drawMirror(ctx, mx, ay);\n'
|
||||
' /* Feature 2: R and f labels on mirror */\n'
|
||||
' if (this._useR && this.type !== \'flat\' && isFinite(f)) {\n'
|
||||
' ctx.save();\n'
|
||||
' ctx.font = \'bold 11px Manrope, system-ui, sans-serif\';\n'
|
||||
' ctx.fillStyle = \'rgba(6,214,224,0.9)\';\n'
|
||||
' ctx.textAlign = \'right\'; ctx.textBaseline = \'bottom\';\n'
|
||||
' ctx.fillText(\'R=\' + this._R.toFixed(0) + \' f=\' + f.toFixed(0), mx - 4, ay - 6);\n'
|
||||
' ctx.restore();\n'
|
||||
' }')
|
||||
|
||||
if OLD not in content:
|
||||
print('ERROR: draw fan/mirror block not found'); sys.exit(1)
|
||||
|
||||
content = content.replace(OLD, NEW, 1)
|
||||
|
||||
with open(src, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print('OK lines:', content.count('\n'))
|
||||
@@ -0,0 +1,91 @@
|
||||
import sys, os
|
||||
|
||||
src = os.path.join(os.path.dirname(__file__), '../../frontend/js/labs/opticsbench.js')
|
||||
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PATCH: Insert new glue functions after lensPreset, before _lensUpdateUI
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
MARKER = 'function _lensUpdateUI(info) {'
|
||||
|
||||
idx = content.find(MARKER)
|
||||
if idx == -1:
|
||||
print('ERROR: _lensUpdateUI not found'); sys.exit(1)
|
||||
|
||||
NEW_FUNCS = (
|
||||
'/* ── Lens animated ray + LM controls (Feature 1 & 3) ── */\n'
|
||||
'function lensToggleLM(on) {\n'
|
||||
' const sliders = document.getElementById(\'ob-lm-sliders\');\n'
|
||||
' const fRow = document.querySelector(\'#ob-ctrl-lens .proj-slider-row\');\n'
|
||||
' if (sliders) sliders.style.display = on ? \'\' : \'none\';\n'
|
||||
' // hide/show simple f slider\n'
|
||||
' const fSlRow = document.getElementById(\'sl-lens-f\');\n'
|
||||
' if (fSlRow && fSlRow.parentElement) fSlRow.parentElement.style.display = on ? \'none\' : \'\';\n'
|
||||
' if (lensSim) lensSim.setLensMode(!on);\n'
|
||||
' if (on && lensSim) {\n'
|
||||
' // sync sliders to current LM params\n'
|
||||
' const r1 = lensSim._lmR1, r2 = lensSim._lmR2, n = lensSim._lmN;\n'
|
||||
' const s1 = document.getElementById(\'sl-lm-r1\'), l1 = document.getElementById(\'lm-r1-val\');\n'
|
||||
' const s2 = document.getElementById(\'sl-lm-r2\'), l2 = document.getElementById(\'lm-r2-val\');\n'
|
||||
' const sn = document.getElementById(\'sl-lm-n\'), ln = document.getElementById(\'lm-n-val\');\n'
|
||||
' if (s1) s1.value = r1; if (l1) l1.textContent = r1.toFixed(0);\n'
|
||||
' if (s2) s2.value = r2; if (l2) l2.textContent = r2.toFixed(0);\n'
|
||||
' if (sn) sn.value = n; if (ln) ln.textContent = n.toFixed(2);\n'
|
||||
' }\n'
|
||||
'}\n'
|
||||
'\n'
|
||||
'function lensLMParam(name, val) {\n'
|
||||
' const v = parseFloat(val);\n'
|
||||
' const lblMap = { R1: \'lm-r1-val\', R2: \'lm-r2-val\', n: \'lm-n-val\' };\n'
|
||||
' const el = document.getElementById(lblMap[name]);\n'
|
||||
' if (el) el.textContent = name === \'n\' ? v.toFixed(2) : v.toFixed(0);\n'
|
||||
' if (lensSim) {\n'
|
||||
' lensSim.setLMParam(name, v);\n'
|
||||
' // update f display\n'
|
||||
' const fl = document.getElementById(\'lens-f-val\');\n'
|
||||
' if (fl) fl.textContent = lensSim.f.toFixed(0);\n'
|
||||
' }\n'
|
||||
'}\n'
|
||||
'\n'
|
||||
'/* ── Mirror R-slider + parabolic controls (Feature 2) ── */\n'
|
||||
'function mirrorToggleR(on) {\n'
|
||||
' const rRow = document.getElementById(\'ob-mirror-R-row\');\n'
|
||||
' if (rRow) rRow.style.display = on ? \'\' : \'none\';\n'
|
||||
' const pbtn = document.getElementById(\'mirror-parab-btn\');\n'
|
||||
' if (pbtn) pbtn.style.display = on ? \'\' : \'none\';\n'
|
||||
' if (mirrorSim) mirrorSim._useR = !!on;\n'
|
||||
' if (on && mirrorSim) {\n'
|
||||
' const sv = document.getElementById(\'sl-mirror-R\');\n'
|
||||
' const lv = document.getElementById(\'mirror-R-val\');\n'
|
||||
' if (sv) sv.value = mirrorSim._R;\n'
|
||||
' if (lv) lv.textContent = mirrorSim._R;\n'
|
||||
' mirrorSim.setMirrorR(mirrorSim._R);\n'
|
||||
' } else if (mirrorSim) { mirrorSim.draw(); }\n'
|
||||
'}\n'
|
||||
'\n'
|
||||
'function mirrorRParam(val) {\n'
|
||||
' const v = parseFloat(val);\n'
|
||||
' const el = document.getElementById(\'mirror-R-val\');\n'
|
||||
' if (el) el.textContent = v;\n'
|
||||
' if (mirrorSim) mirrorSim.setMirrorR(v);\n'
|
||||
'}\n'
|
||||
'\n'
|
||||
'function mirrorToggleParabolic(btn) {\n'
|
||||
' if (!mirrorSim) return;\n'
|
||||
' mirrorSim._parabolic = !mirrorSim._parabolic;\n'
|
||||
' if (btn) btn.textContent = mirrorSim._parabolic ? \'Параболическое\' : \'Сферическое\';\n'
|
||||
' if (btn) btn.style.color = mirrorSim._parabolic ? \'#7BF5A4\' : \'#888\';\n'
|
||||
' mirrorSim.draw();\n'
|
||||
'}\n'
|
||||
'\n'
|
||||
)
|
||||
|
||||
new_content = content[:idx] + NEW_FUNCS + content[idx:]
|
||||
|
||||
with open(src, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print('OK lines:', new_content.count('\n'))
|
||||
@@ -0,0 +1,85 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dir = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
|
||||
const files = ['geometry_10_hub.html','geometry_10_r1.html','geometry_10_r2.html','geometry_10_r3.html','geometry_10_r4.html'];
|
||||
|
||||
const cmds = ['dfrac','sqrt','sin','cos','tan','angle','vec','triangle','Rightarrow','cdot','ne','le','ge','pi','alpha','beta','gamma','delta','varphi','circ','perp','parallel','frac','tfrac','overline','left','right','begin','end','boxed','cot','arcsin','arccos','arctan','log','ln','lim','sum','int','infty','Delta','theta','lambda','mu','rho','sigma','tau','omega','Omega','phi','psi','xi','zeta','eta','epsilon','varepsilon','Pi','Sigma','approx','equiv','pm','mp','times','div','leq','geq','neq','sim','cong','subset','supset','cup','cap','forall','exists','overrightarrow','overleftarrow','widehat','widetilde','mathbf','mathrm','mathbb','mathcal','quad','qquad','hline','cline','displaystyle','textstyle','scriptstyle','underline','operatorname','land','lor','lnot','mapsto','Leftrightarrow','Leftarrow','leftarrow','rightarrow','uparrow','downarrow','prime','colon'];
|
||||
|
||||
for (const f of files) {
|
||||
const fp = path.join(dir, f);
|
||||
if (!fs.existsSync(fp)) { console.log('MISSING', f); continue; }
|
||||
const txt = fs.readFileSync(fp, 'utf8');
|
||||
|
||||
const scriptRe = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/g;
|
||||
let m;
|
||||
let totalKatexErrs = 0;
|
||||
const errSamples = [];
|
||||
let scriptBlocks = 0;
|
||||
let combinedJs = '';
|
||||
|
||||
while ((m = scriptRe.exec(txt)) !== null) {
|
||||
scriptBlocks++;
|
||||
const body = m[1];
|
||||
combinedJs += body + ';\n';
|
||||
const cmdRe = /(^|[^\\])\\([a-zA-Z]+)/g;
|
||||
let cm;
|
||||
while ((cm = cmdRe.exec(body)) !== null) {
|
||||
const cmd = cm[2];
|
||||
if (cmds.includes(cmd)) {
|
||||
totalKatexErrs++;
|
||||
const idxAbs = m.index + cm.index + cm[1].length;
|
||||
const before = txt.slice(0, idxAbs);
|
||||
const line = (before.match(/\n/g) || []).length + 1;
|
||||
if (errSamples.length < 10) errSamples.push({line, cmd, ctx: body.slice(Math.max(0,cm.index-30), cm.index+50).replace(/\n/g,' ')});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// <option> raw KaTeX
|
||||
const optRe = /<option[^>]*>([\s\S]*?)<\/option>/g;
|
||||
let om;
|
||||
let optionRaw = 0;
|
||||
const optionSamples = [];
|
||||
while ((om = optRe.exec(txt)) !== null) {
|
||||
const content = om[1];
|
||||
if (/\$[^\$]+\$/.test(content) || /\\(dfrac|sqrt|frac|angle|vec|sin|cos|alpha|beta|pi|circ|triangle|Rightarrow)/.test(content)) {
|
||||
optionRaw++;
|
||||
const before = txt.slice(0, om.index);
|
||||
const line = (before.match(/\n/g) || []).length + 1;
|
||||
if (optionSamples.length < 8) optionSamples.push({line, content: content.slice(0,90)});
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji
|
||||
const emojiRe = /[\u{1F300}-\u{1FAFF}\u{1F000}-\u{1F02F}\u{2700}-\u{27BF}\u{2600}-\u{26FF}]/gu;
|
||||
let em;
|
||||
let emoji = 0;
|
||||
const emojiSamples = [];
|
||||
while ((em = emojiRe.exec(txt)) !== null) {
|
||||
emoji++;
|
||||
const before = txt.slice(0, em.index);
|
||||
const line = (before.match(/\n/g) || []).length + 1;
|
||||
if (emojiSamples.length < 10) emojiSamples.push({line, ch: em[0], cp: em[0].codePointAt(0).toString(16), ctx: txt.slice(Math.max(0,em.index-25), em.index+25).replace(/\n/g,' ')});
|
||||
}
|
||||
|
||||
let authors = [];
|
||||
if (f.includes('hub')) {
|
||||
for (const a of ['Латотин','Чеботаревский','Горбунова','Шлыков']) {
|
||||
if (txt.includes(a)) authors.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
let parseErr = null;
|
||||
try { new Function(combinedJs); } catch(e) { parseErr = e.message.slice(0,200); }
|
||||
|
||||
console.log('=== ' + f + ' ===');
|
||||
console.log(' size:', txt.length, 'scripts:', scriptBlocks);
|
||||
console.log(' KaTeX single-backslash errors:', totalKatexErrs);
|
||||
for (const s of errSamples) console.log(' line ' + s.line + ' \\' + s.cmd + ' ctx: ' + s.ctx);
|
||||
console.log(' <option> raw KaTeX:', optionRaw);
|
||||
for (const s of optionSamples) console.log(' line ' + s.line + ': ' + s.content);
|
||||
console.log(' Emoji symbols:', emoji);
|
||||
for (const s of emojiSamples) console.log(' line ' + s.line + ' U+' + s.cp + ' ctx: ' + s.ctx);
|
||||
if (authors.length) console.log(' Authors in hub:', authors.join(', '));
|
||||
console.log(' JS parse:', parseErr ? 'ERROR: ' + parseErr : 'OK');
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dir = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
|
||||
const files = ['geometry_10_hub.html','geometry_10_r1.html','geometry_10_r2.html','geometry_10_r3.html','geometry_10_r4.html'];
|
||||
|
||||
// hub footer check
|
||||
const hub = fs.readFileSync(path.join(dir,'geometry_10_hub.html'),'utf8');
|
||||
const footerMatch = hub.match(/<footer[\s\S]*?<\/footer>/);
|
||||
console.log('HUB FOOTER:');
|
||||
if (footerMatch) console.log(footerMatch[0].slice(0,400)); else console.log(' NOT FOUND');
|
||||
|
||||
// g3d.js usage
|
||||
console.log('\\n3D ENGINE / g3d.js usage:');
|
||||
for (const f of files) {
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
const has3d = /g3d/i.test(txt);
|
||||
const hasProj = /(project|perspective|3d|threejs|three\.)/i.test(txt);
|
||||
const hasViewBox = (txt.match(/viewBox/g) || []).length;
|
||||
console.log(' ' + f + ' g3d:'+has3d+' proj/3d:'+hasProj+' viewBox count:'+hasViewBox);
|
||||
}
|
||||
|
||||
// Structure: count §, finals, "Я прочитал" buttons
|
||||
console.log('\\nSTRUCTURE:');
|
||||
for (const f of files) {
|
||||
if (f.includes('hub')) continue;
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
// sections: pattern in TOC
|
||||
const sectionIds = [];
|
||||
const re = /id:\s*['"]([^'"]+)['"]\s*,\s*num:\s*['"]([^'"]*)['"]/g;
|
||||
let mm;
|
||||
while ((mm = re.exec(txt)) !== null) sectionIds.push({id:mm[1], num:mm[2]});
|
||||
// theory blocks
|
||||
const theoryCount = (txt.match(/theory|teor|Теори/g) || []).length;
|
||||
// interactive: int1/int2/int3/int4 or class
|
||||
const intCount = (txt.match(/data-int|intCanvas|interactive/g) || []).length;
|
||||
// "Я прочитал"
|
||||
const yaProch = (txt.match(/Я прочитал/g) || []).length;
|
||||
// markRead
|
||||
const markRead = (txt.match(/markRead|markAsRead|читал/g) || []).length;
|
||||
console.log(' ' + f + ':');
|
||||
console.log(' sections in TOC:', sectionIds.map(s=>s.num+':'+s.id).join(' '));
|
||||
console.log(' theoryCount mentions:', theoryCount, 'interactive mentions:', intCount, '"Я прочитал":', yaProch);
|
||||
}
|
||||
|
||||
// Look for slider px ranges 40..150, R=120 etc.
|
||||
console.log('\\nSLIDER PIXEL CHECK (range/value hints):');
|
||||
for (const f of files) {
|
||||
if (f.includes('hub')) continue;
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
// Find input range with suspicious min/max
|
||||
const ranges = [...txt.matchAll(/<input[^>]*type=["']range["'][^>]*>/g)];
|
||||
let suspicious = 0;
|
||||
for (const r of ranges) {
|
||||
const tag = r[0];
|
||||
const min = (tag.match(/min=["']?(\d+)/)||[])[1];
|
||||
const max = (tag.match(/max=["']?(\d+)/)||[])[1];
|
||||
if (min && max) {
|
||||
const lo=+min, hi=+max;
|
||||
if ((lo>=30 && hi>=120) || (hi-lo>=60 && hi>=100)) {
|
||||
suspicious++;
|
||||
if (suspicious<=5) {
|
||||
const idx = r.index;
|
||||
const line = (txt.slice(0,idx).match(/\n/g)||[]).length+1;
|
||||
console.log(' ' + f + ' line '+line+': '+tag.slice(0,140));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(' ' + f + ': total ranges=' + ranges.length + ' suspicious(px-like)=' + suspicious);
|
||||
}
|
||||
|
||||
// Check "px" usage near labels (R = , a =, etc.)
|
||||
console.log('\\nLABEL "R=" / "a=" PATTERNS:');
|
||||
for (const f of files) {
|
||||
if (f.includes('hub')) continue;
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
// Find patterns like 'R = ${...}' or text="R = ${r}" where r might be px
|
||||
const pat = /['"`][^'"`]*\b(R|a|h|d|l|r|b|c)\s*=\s*\$\{[^}]+\}/g;
|
||||
const matches = [];
|
||||
let mm;
|
||||
while ((mm = pat.exec(txt)) !== null) {
|
||||
if (matches.length < 6) {
|
||||
const idx = mm.index;
|
||||
const line = (txt.slice(0,idx).match(/\n/g)||[]).length+1;
|
||||
matches.push({line, snip: mm[0].slice(0,80)});
|
||||
}
|
||||
}
|
||||
if (matches.length) {
|
||||
console.log(' ' + f + ':');
|
||||
for (const x of matches) console.log(' line '+x.line+': '+x.snip);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dir = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
|
||||
const files = ['geometry_10_r1.html','geometry_10_r2.html','geometry_10_r3.html','geometry_10_r4.html'];
|
||||
|
||||
console.log('SECTION CONTENT STRUCTURE:');
|
||||
for (const f of files) {
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
// teor1/teor2/teor3 occurrences
|
||||
const teor = (txt.match(/teor[123]/g) || []).length;
|
||||
const int1234 = (txt.match(/int[1234]/g) || []).length;
|
||||
// viz blocks
|
||||
const viz = (txt.match(/viz[1234]|svg-/g) || []).length;
|
||||
// readBtn / btnRead
|
||||
const readBtn = (txt.match(/btnRead|readBtn|markRead|done-read|read-mark/g) || []).length;
|
||||
const yaProch = (txt.match(/Я прочитал/g) || []).length;
|
||||
// Sections in render
|
||||
const renderSections = (txt.match(/renderSection|renderPart|sect\.id/g) || []).length;
|
||||
console.log(' ' + f);
|
||||
console.log(' teor1/2/3:', teor, ' int1-4:', int1234, ' viz:', viz);
|
||||
console.log(' readBtn-related:', readBtn, ' "Я прочитал":', yaProch, ' renderSection:', renderSections);
|
||||
}
|
||||
|
||||
// Final course in hub
|
||||
console.log('\\nHUB - course final mention:');
|
||||
const hub = fs.readFileSync(path.join(dir,'geometry_10_hub.html'),'utf8');
|
||||
console.log(' "Финал курса":', (hub.match(/Финал курса/g)||[]).length);
|
||||
console.log(' "финал" (any):', (hub.match(/финал/gi)||[]).length);
|
||||
|
||||
// Triangle/3D shape labels - look for projection helpers
|
||||
console.log('\\n3D PROJECTION HELPERS in r1-r4:');
|
||||
for (const f of files) {
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
const hasProject = /function\s+project|proj\s*=\s*\(/.test(txt);
|
||||
const has3DPoints = /\{x:[^}]+y:[^}]+z:/.test(txt) || /\[\s*[0-9.-]+\s*,\s*[0-9.-]+\s*,\s*[0-9.-]+\s*\]/.test(txt);
|
||||
const hasIso = /isoP|isometric|axonom/.test(txt);
|
||||
console.log(' ' + f + ' project()=' + hasProject + ' 3DPoints=' + has3DPoints + ' iso=' + hasIso);
|
||||
}
|
||||
|
||||
// Check pixel literals in SVG text labels
|
||||
console.log('\\nSVG <text> labels with px-like values:');
|
||||
for (const f of files) {
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
// crude pattern: text content like "R = 120" "a = 80" etc
|
||||
const re = />([A-Za-zА-Яа-я]\s*=\s*\d{2,3})\s*</g;
|
||||
let m, cnt=0;
|
||||
while ((m = re.exec(txt)) !== null) {
|
||||
cnt++;
|
||||
if (cnt <= 6) {
|
||||
const line = (txt.slice(0,m.index).match(/\n/g)||[]).length+1;
|
||||
console.log(' ' + f + ' line '+line+': '+m[1]);
|
||||
}
|
||||
}
|
||||
if (cnt) console.log(' -> total in ' + f + ': ' + cnt);
|
||||
}
|
||||
|
||||
// Sample around final/interactive in r1 to see structure
|
||||
console.log('\\n--- r1 sample around interactives ---');
|
||||
const r1 = fs.readFileSync(path.join(dir,'geometry_10_r1.html'),'utf8');
|
||||
// Find "3 эталонных" or "эталонных" labels mismatch
|
||||
const ets = [...r1.matchAll(/(\d+)\s+эталонн/g)];
|
||||
for (const e of ets) {
|
||||
const line = (r1.slice(0,e.index).match(/\n/g)||[]).length+1;
|
||||
console.log(' r1 line '+line+': "'+r1.slice(e.index, e.index+60).replace(/\n/g,' ')+'"');
|
||||
}
|
||||
for (const f of files) {
|
||||
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||
const ets = [...txt.matchAll(/(\d+)\s+эталонн/g)];
|
||||
for (const e of ets) {
|
||||
const line = (txt.slice(0,e.index).match(/\n/g)||[]).length+1;
|
||||
console.log(' ' + f + ' line '+line+': "'+txt.slice(e.index, e.index+60).replace(/\n/g,' ')+'"');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// review_geom11.js - аудит Геометрии 11 (только репорт)
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const FILES = [
|
||||
'frontend/textbooks/geometry_11_hub.html',
|
||||
'frontend/textbooks/geometry_11_ch1.html',
|
||||
'frontend/textbooks/geometry_11_ch2.html',
|
||||
'frontend/textbooks/geometry_11_ch3.html',
|
||||
'frontend/textbooks/geometry_11_ch4.html',
|
||||
];
|
||||
const ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
// Словарь KaTeX-команд (часто встречающиеся)
|
||||
const KATEX_CMDS = new Set([
|
||||
'dfrac','frac','tfrac','sqrt','cdot','times','pm','mp','ne','le','ge','approx',
|
||||
'angle','triangle','square','vec','overline','overrightarrow','overrightarrow',
|
||||
'sin','cos','tan','cot','arcsin','arccos','arctan','log','ln','lg','exp',
|
||||
'pi','alpha','beta','gamma','delta','varepsilon','theta','lambda','mu','phi','omega','rho','sigma','tau',
|
||||
'Delta','Gamma','Omega','Phi','Sigma','Theta','Lambda',
|
||||
'boxed','underline','mathbf','mathrm','text','textbf','textit',
|
||||
'sum','prod','int','infty','lim','to','rightarrow','leftarrow','Rightarrow','Leftrightarrow',
|
||||
'left','right','big','Big','bigg','Bigg','quad','qquad',',','!',
|
||||
'cap','cup','in','notin','subset','supset','emptyset','varnothing',
|
||||
'parallel','perp','cong','sim','equiv','neq',
|
||||
'begin','end','array','matrix','pmatrix','bmatrix','cases','aligned','align',
|
||||
'displaystyle','textstyle','scriptstyle',
|
||||
'colon','ldots','cdots','vdots','ddots',
|
||||
'mathbb','mathcal','mathfrak','operatorname',
|
||||
'hat','tilde','bar','dot','widehat','widetilde',
|
||||
'binom','choose','over','atop',
|
||||
'div','star','ast','circ','bullet',
|
||||
'forall','exists','land','lor','neg','iff','implies',
|
||||
'leq','geq','prec','succ','subseteq','supseteq',
|
||||
'partial','nabla','degree','prime',
|
||||
'oplus','ominus','otimes','odot',
|
||||
'mathring','space','phantom','vphantom',
|
||||
'overset','underset','stackrel',
|
||||
'color','textcolor','rgb','href',
|
||||
'rule','kern','mskip','hspace','vspace',
|
||||
'tag','label','ref',
|
||||
'lvert','rvert','lVert','rVert','vert','Vert','|',
|
||||
'oint','iint','iiint',
|
||||
'because','therefore',
|
||||
'leftrightarrow','Leftarrow','longleftarrow','longrightarrow',
|
||||
'mapsto','hookrightarrow',
|
||||
]);
|
||||
|
||||
const EMOJI_CHARS = ['✓','✗','⚠','🔥','📐','✅','❌','🎯','🔴','🟢','🟡','📊','📈','📉','🎓','📚','🧮','🔬','🌟','⭐','💡','🚀','🎉','📝','📖','🗒'];
|
||||
|
||||
const AUTHOR_NAMES = ['Латотин','Чеботаревский','Горбунова','Цыбулько','Шлыков','Подгорная'];
|
||||
|
||||
function scanFile(rel) {
|
||||
const fp = path.join(ROOT, rel);
|
||||
const src = fs.readFileSync(fp, 'utf8');
|
||||
const out = { file: rel, katex: [], optionsKatex: [], emoji: [], authors: [], pixels: [], g3d: {} };
|
||||
|
||||
// 1. KaTeX ошибки: ищем backslash перед буквой (одиночный) в JS template literals и в HTML
|
||||
// Анализируем строчно
|
||||
const lines = src.split(/\r?\n/);
|
||||
// Regex: одиночный \ перед 2+ буквами, не предшествуемый \
|
||||
const reSingle = /(?<!\\)\\([a-zA-Z]{2,})/g;
|
||||
// Regex: двойной \\command (нормальный для JS-литералов)
|
||||
// Чтобы не плодить шум: репортим только когда команда из KATEX_CMDS
|
||||
|
||||
// Делим на блоки: внутри <script> и вне
|
||||
let inScript = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (/<script(\s|>)/i.test(line)) inScript = true;
|
||||
// Сначала проверим текущую строку
|
||||
// Внутри <script> — нужен \\cmd (т.к. JS экранирует). Одиночный \cmd = ошибка.
|
||||
// Вне <script> (чистый HTML, например в title) — \cmd нормально для KaTeX render.
|
||||
|
||||
// Найдём все совпадения \cmd
|
||||
let m;
|
||||
const matches = [];
|
||||
const re = /\\+([a-zA-Z]{2,})/g;
|
||||
while ((m = re.exec(line)) !== null) {
|
||||
const bsCount = m[0].length - m[1].length; // число backslashes
|
||||
const cmd = m[1];
|
||||
if (!KATEX_CMDS.has(cmd)) continue;
|
||||
matches.push({ cmd, bsCount, idx: m.index, full: m[0] });
|
||||
}
|
||||
for (const mt of matches) {
|
||||
if (inScript) {
|
||||
// в JS template literals одиночный \X неправильно. Нужно \\X (bsCount>=2)
|
||||
if (mt.bsCount === 1) {
|
||||
out.katex.push({ line: i+1, cmd: mt.cmd, snippet: line.trim().slice(0, 140) });
|
||||
}
|
||||
} else {
|
||||
// в чистом HTML \cmd ок
|
||||
}
|
||||
}
|
||||
if (/<\/script>/i.test(line)) inScript = false;
|
||||
}
|
||||
|
||||
// 2. <option> с сырым KaTeX (содержит $ или \\sqrt и т.п.)
|
||||
const optRe = /<option[^>]*>([^<]*)<\/option>/g;
|
||||
let om;
|
||||
while ((om = optRe.exec(src)) !== null) {
|
||||
const txt = om[1];
|
||||
if (/\$[^$]+\$/.test(txt) || /\\\\[a-zA-Z]+/.test(txt) || /\\(dfrac|sqrt|frac|sin|cos|pi|angle|vec|cdot|times)/.test(txt)) {
|
||||
out.optionsKatex.push({ snippet: om[0].slice(0,160) });
|
||||
}
|
||||
}
|
||||
// Также проверим динамический innerHTML на option в JS
|
||||
const scriptBlocks = [...src.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
|
||||
for (const sb of scriptBlocks) {
|
||||
// ищем <option ...>$...$</option> или \\sqrt в template-literals
|
||||
const re2 = /<option[^>]*>[^<]*(?:\$[^$]+\$|\\\\(?:dfrac|sqrt|frac|sin|cos|pi|angle|vec|cdot|times))[^<]*<\/option>/g;
|
||||
let mm;
|
||||
while ((mm = re2.exec(sb)) !== null) {
|
||||
out.optionsKatex.push({ snippet: mm[0].slice(0,160), inScript: true });
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Эмодзи (исключая SVG)
|
||||
for (const ch of EMOJI_CHARS) {
|
||||
let idx = 0;
|
||||
while ((idx = src.indexOf(ch, idx)) !== -1) {
|
||||
// Найти строку
|
||||
const upto = src.slice(0, idx);
|
||||
const lineNum = upto.split('\n').length;
|
||||
const lineStart = upto.lastIndexOf('\n') + 1;
|
||||
const lineEnd = src.indexOf('\n', idx);
|
||||
const lineText = src.slice(lineStart, lineEnd === -1 ? src.length : lineEnd);
|
||||
out.emoji.push({ char: ch, line: lineNum, snippet: lineText.trim().slice(0, 120) });
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Авторы
|
||||
for (const name of AUTHOR_NAMES) {
|
||||
if (src.includes(name)) {
|
||||
// Найдём строку
|
||||
const idx = src.indexOf(name);
|
||||
const upto = src.slice(0, idx);
|
||||
const lineNum = upto.split('\n').length;
|
||||
const lineStart = upto.lastIndexOf('\n') + 1;
|
||||
const lineEnd = src.indexOf('\n', idx);
|
||||
const lineText = src.slice(lineStart, lineEnd === -1 ? src.length : lineEnd);
|
||||
out.authors.push({ name, line: lineNum, snippet: lineText.trim().slice(0, 160) });
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Пиксели в подписях: ищем sliders с min/max, и подписи рядом
|
||||
// Простая эвристика: <input type="range" min="X" max="Y" ...> + value/label рядом
|
||||
const sliderRe = /<input[^>]*type=["']range["'][^>]*>/g;
|
||||
let sm;
|
||||
while ((sm = sliderRe.exec(src)) !== null) {
|
||||
const tag = sm[0];
|
||||
const minM = tag.match(/\bmin=["'](\d+)["']/);
|
||||
const maxM = tag.match(/\bmax=["'](\d+)["']/);
|
||||
const idM = tag.match(/\bid=["']([^"']+)["']/);
|
||||
if (minM && maxM) {
|
||||
const min = +minM[1], max = +maxM[1];
|
||||
// подозрительно если min>20 и max>100 (вероятно пиксели)
|
||||
const suspicious = min >= 20 && max >= 100;
|
||||
if (suspicious) {
|
||||
const upto = src.slice(0, sm.index);
|
||||
const lineNum = upto.split('\n').length;
|
||||
out.pixels.push({ line: lineNum, id: idM ? idM[1] : '', min, max, tag: tag.slice(0,180) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. G3D usage
|
||||
out.g3d.includesScript = /<script[^>]*src=["']\/js\/g3d\.js["'][^>]*>/.test(src);
|
||||
out.g3d.usesPrism = /G3D\.prismMesh/.test(src);
|
||||
out.g3d.usesCylinder = /G3D\.cylinderMesh/.test(src);
|
||||
out.g3d.usesPyramid = /G3D\.pyramidMesh/.test(src);
|
||||
out.g3d.usesCone = /G3D\.coneMesh/.test(src);
|
||||
out.g3d.usesSphere = /G3D\.sphereWireframe/.test(src);
|
||||
out.g3d.usesAttachOrbit = /G3D\.attachOrbit/.test(src);
|
||||
out.g3d.usesPresetView = /G3D\.presetView/.test(src);
|
||||
out.g3d.viewButtons = (src.match(/Изо|Спереди|Сверху|Сбоку/g) || []).length;
|
||||
|
||||
// 7. Структура: theory cards (3), interactives (4), "Я прочитал"
|
||||
out.theoryCards = (src.match(/class=["'][^"']*theory[^"']*card/g) || []).length;
|
||||
out.interactiveCards = (src.match(/class=["'][^"']*interactive[^"']*/g) || []).length;
|
||||
out.readBtns = (src.match(/Я прочитал|я прочитал/g) || []).length;
|
||||
out.bossCount = (src.match(/босс|Босс|BOSS/g) || []).length;
|
||||
out.paragraphCount = (src.match(/§\d+/g) || []).length;
|
||||
out.finalSections = (src.match(/Финал|финал раздела|Финал раздела/g) || []).length;
|
||||
out.length = src.length;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const results = FILES.map(scanFile);
|
||||
|
||||
// Report
|
||||
console.log('='.repeat(80));
|
||||
console.log('AUDIT GEOMETRY 11');
|
||||
console.log('='.repeat(80));
|
||||
|
||||
let totals = { katex: 0, options: 0, emoji: 0, authors: 0, pixels: 0 };
|
||||
|
||||
for (const r of results) {
|
||||
console.log(`\n--- ${r.file} (${(r.length/1024).toFixed(1)} KB) ---`);
|
||||
console.log(`KaTeX errors: ${r.katex.length}`);
|
||||
if (r.katex.length) {
|
||||
r.katex.slice(0, 10).forEach(e => console.log(` L${e.line}: \\${e.cmd} | ${e.snippet}`));
|
||||
if (r.katex.length > 10) console.log(` ... +${r.katex.length-10} more`);
|
||||
}
|
||||
console.log(`<option> KaTeX: ${r.optionsKatex.length}`);
|
||||
r.optionsKatex.slice(0,5).forEach(o => console.log(` ${o.inScript?'[JS] ':''}${o.snippet}`));
|
||||
console.log(`Emoji: ${r.emoji.length}`);
|
||||
r.emoji.slice(0,5).forEach(e => console.log(` L${e.line} '${e.char}': ${e.snippet}`));
|
||||
console.log(`Authors: ${r.authors.length}`);
|
||||
r.authors.slice(0,5).forEach(a => console.log(` L${a.line} '${a.name}': ${a.snippet}`));
|
||||
console.log(`Pixel sliders (susp.): ${r.pixels.length}`);
|
||||
r.pixels.slice(0,5).forEach(p => console.log(` L${p.line} #${p.id} min=${p.min} max=${p.max}`));
|
||||
console.log(`G3D: script=${r.g3d.includesScript}, prism=${r.g3d.usesPrism}, cyl=${r.g3d.usesCylinder}, pyr=${r.g3d.usesPyramid}, cone=${r.g3d.usesCone}, sphere=${r.g3d.usesSphere}, orbit=${r.g3d.usesAttachOrbit}, presetView=${r.g3d.usesPresetView}, viewBtnHits=${r.g3d.viewButtons}`);
|
||||
console.log(`Structure: theoryCards=${r.theoryCards}, interactives=${r.interactiveCards}, readBtns=${r.readBtns}, bossHits=${r.bossCount}, §count=${r.paragraphCount}, finalHits=${r.finalSections}`);
|
||||
|
||||
totals.katex += r.katex.length;
|
||||
totals.options += r.optionsKatex.length;
|
||||
totals.emoji += r.emoji.length;
|
||||
totals.authors += r.authors.length;
|
||||
totals.pixels += r.pixels.length;
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('TOTALS:', totals);
|
||||
console.log('='.repeat(80));
|
||||
@@ -81,6 +81,25 @@ const CHALLENGES = [
|
||||
{ type: 'complete', difficulty: 1, xp: 40, title: 'Заверши: горение углерода',
|
||||
desc: 'Какой продукт образуется при избытке кислорода?',
|
||||
data: { equation: 'C + O₂ → ?', choices: ['CO2', 'CO', 'C2O', 'O3'], answer: 'CO2' } },
|
||||
|
||||
// ── build со структурной проверкой (3D-build, Фаза 5.3) ───────────────────
|
||||
// target_formula — в Hill-нотации (как считает контроллер); эталон по molecule_id.
|
||||
{ type: 'build', difficulty: 1, xp: 45, title: 'Собери: углекислый газ (структура)', target: 'CO2',
|
||||
desc: 'Построй CO₂: углерод с двумя двойными связями к кислороду. Проверяется связность.',
|
||||
hint: 'O=C=O — две двойные связи',
|
||||
data: { requireStructure: true, molecule_id: 2 } },
|
||||
{ type: 'build', difficulty: 2, xp: 55, title: 'Собери: этилен (структура)', target: 'C2H4',
|
||||
desc: 'Построй этилен C₂H₄: двойная связь C=C и по два H на каждом углероде.',
|
||||
hint: 'H₂C=CH₂ — двойная связь между углеродами',
|
||||
data: { requireStructure: true, molecule_id: 12 } },
|
||||
{ type: 'build', difficulty: 2, xp: 60, title: 'Собери: этанол (структура)', target: 'C2H6O',
|
||||
desc: 'Построй этанол: скелет C–C–O, заполни валентности водородами. Важна именно связность (не диметиловый эфир!).',
|
||||
hint: 'CH₃–CH₂–OH: цепочка C–C–O',
|
||||
data: { requireStructure: true, molecule_id: 14 } },
|
||||
{ type: 'build', difficulty: 3, xp: 70, title: 'Собери: уксусную кислоту (структура)', target: 'C2H4O2',
|
||||
desc: 'Построй уксусную кислоту CH₃COOH: метил + карбоксильная группа (C=O и C–O–H).',
|
||||
hint: 'CH₃–COOH: карбоксил −COOH',
|
||||
data: { requireStructure: true, molecule_id: 15 } },
|
||||
];
|
||||
|
||||
let order = (getMaxOrder.get().m || 0);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
/*
|
||||
* Сид метаболических путей в bio_pathways.
|
||||
* Источник данных — backend/scripts/data/biochem_pathways.json (изначально
|
||||
* извлечён из инлайн-объекта PATHWAYS; теперь это самодостаточный источник
|
||||
* правды, не зависящий от фронта). Каждый путь — документ data_json.
|
||||
* Идемпотентно (upsert по slug): повторный запуск синхронизирует данные.
|
||||
*
|
||||
* Запуск: node backend/scripts/seed_biochem_pathways.js
|
||||
*/
|
||||
const db = require('../src/db/db');
|
||||
const P = require('./biochem_pathways_data');
|
||||
|
||||
const upsert = db.prepare(`INSERT INTO bio_pathways (slug, name, color, ord, data_json)
|
||||
VALUES (@slug, @name, @color, @ord, @data_json)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
name=excluded.name, color=excluded.color, ord=excluded.ord, data_json=excluded.data_json`);
|
||||
|
||||
const slugs = Object.keys(P);
|
||||
let n = 0;
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
slugs.forEach((slug, idx) => {
|
||||
const p = P[slug];
|
||||
upsert.run({
|
||||
slug,
|
||||
name: p.name || slug,
|
||||
color: p.color || '#9B5DE5',
|
||||
ord: idx,
|
||||
data_json: JSON.stringify(p),
|
||||
});
|
||||
n++;
|
||||
});
|
||||
db.exec('COMMIT');
|
||||
} catch (e) {
|
||||
db.exec('ROLLBACK');
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`biochem pathways seed: ${n} путь(ей) — ${slugs.join(', ')}`);
|
||||
console.log('в БД:', db.prepare('SELECT slug, name, LENGTH(data_json) AS bytes FROM bio_pathways ORDER BY ord').all()
|
||||
.map(r => `${r.slug}(${r.bytes}b)`).join(' '));
|
||||
@@ -0,0 +1,569 @@
|
||||
'use strict';
|
||||
const db = require('../src/db/db');
|
||||
|
||||
const MATH_ID = 3;
|
||||
const T = {
|
||||
arithmetic: 16, word: 17, numbers: 18, trig: 19,
|
||||
quadratic: 20, progression: 21, inequalities: 22, geometry: 23,
|
||||
functions: 24, log: 25, expineq: 26, equations: 27, stats: 28,
|
||||
};
|
||||
|
||||
const existingTexts = new Set(
|
||||
db.prepare('SELECT text FROM questions WHERE subject_id=3').all()
|
||||
.map(q => q.text.slice(0, 70).trim())
|
||||
);
|
||||
|
||||
let added = 0, skipped = 0;
|
||||
|
||||
const insertQ = db.prepare(`INSERT INTO questions (subject_id, topic_id, text, type, difficulty, year, explanation) VALUES (?,?,?,?,?,?,?)`);
|
||||
const insertO = db.prepare(`INSERT INTO options (question_id, text, is_correct, order_index) VALUES (?,?,?,?)`);
|
||||
|
||||
function addQ(topicId, text, opts, difficulty, year, explanation, type = 'single') {
|
||||
const key = text.slice(0, 70).trim();
|
||||
if (existingTexts.has(key)) { skipped++; return; }
|
||||
existingTexts.add(key);
|
||||
const r = insertQ.run(MATH_ID, topicId, text, type, difficulty, year || null, explanation || null);
|
||||
const qid = r.lastInsertRowid;
|
||||
opts.forEach((o, i) => insertO.run(qid, o.t, o.c ? 1 : 0, i));
|
||||
added++;
|
||||
}
|
||||
|
||||
const run = db.transaction(() => {
|
||||
|
||||
// ── АРИФМЕТИКА И СТЕПЕНИ ──────────────────────────────────────────
|
||||
addQ(T.arithmetic, `Вычислите: \\(2^5 - 3^3 + 4^2\\)`,
|
||||
[{t:'\\(21\\)',c:true},{t:'\\(13\\)',c:false},{t:'\\(17\\)',c:false},{t:'\\(11\\)',c:false},{t:'\\(5\\)',c:false}],
|
||||
1,2018,'\\(32-27+16=21\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\(3^4 - 5^2 \\cdot 2 + 1\\)`,
|
||||
[{t:'\\(32\\)',c:true},{t:'\\(31\\)',c:false},{t:'\\(30\\)',c:false},{t:'\\(34\\)',c:false},{t:'\\(29\\)',c:false}],
|
||||
1,2019,'\\(81-50+1=32\\)');
|
||||
|
||||
addQ(T.arithmetic, `Найдите: \\(\\dfrac{\\sqrt{48}}{\\sqrt{3}}\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(8\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(16\\)',c:false}],
|
||||
1,2018,'\\(\\sqrt{48/3}=\\sqrt{16}=4\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\(\\left(\\dfrac{1}{4}\\right)^{-3/2}\\)`,
|
||||
[{t:'\\(8\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(\\frac{1}{8}\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
2,2019,'\\(4^{3/2}=(2^2)^{3/2}=2^3=8\\)');
|
||||
|
||||
addQ(T.arithmetic, `Упростите: \\(\\dfrac{a^3 \\cdot a^{-1}}{a^{-2}}\\) при \\(a\\ne0\\)`,
|
||||
[{t:'\\(a^4\\)',c:true},{t:'\\(a^2\\)',c:false},{t:'\\(a^6\\)',c:false},{t:'\\(a^{-4}\\)',c:false},{t:'\\(1\\)',c:false}],
|
||||
1,2019,'\\(a^{3-1-(-2)}=a^4\\)');
|
||||
|
||||
addQ(T.arithmetic, `Найдите: \\(\\sqrt[3]{-8}\\cdot\\sqrt{9}\\)`,
|
||||
[{t:'\\(-6\\)',c:true},{t:'\\(6\\)',c:false},{t:'\\(-12\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(-3\\)',c:false}],
|
||||
1,2020,'\\(-2\\cdot3=-6\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\(16^{0{,}75}\\)`,
|
||||
[{t:'\\(8\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
1,2022,'\\((2^4)^{3/4}=2^3=8\\)');
|
||||
|
||||
addQ(T.arithmetic, `Найдите: \\(\\dfrac{\\sqrt{75}-\\sqrt{27}}{\\sqrt{3}}\\)`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(6\\)',c:false}],
|
||||
1,2022,'\\(\\dfrac{5\\sqrt{3}-3\\sqrt{3}}{\\sqrt{3}}=2\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\(\\left(0{,}125\\right)^{-1/3}\\)`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(\\frac{1}{2}\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(-2\\)',c:false},{t:'\\(8\\)',c:false}],
|
||||
2,2023,'\\(0{,}125=2^{-3},\\ (2^{-3})^{-1/3}=2\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\(2^{10}\\div 2^7+3^2\\)`,
|
||||
[{t:'\\(17\\)',c:true},{t:'\\(18\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(15\\)',c:false}],
|
||||
1,2023,'\\(2^3+9=17\\)');
|
||||
|
||||
addQ(T.arithmetic, `Найдите: \\(\\sqrt{0{,}04}\\cdot\\sqrt[3]{0{,}008}\\)`,
|
||||
[{t:'\\(0{,}04\\)',c:true},{t:'\\(0{,}4\\)',c:false},{t:'\\(0{,}2\\)',c:false},{t:'\\(0{,}008\\)',c:false},{t:'\\(4\\)',c:false}],
|
||||
2,2020,'\\(0{,}2\\cdot0{,}2=0{,}04\\)');
|
||||
|
||||
addQ(T.arithmetic, `Вычислите: \\((\\sqrt{5}-\\sqrt{2})(\\sqrt{5}+\\sqrt{2})\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(7\\)',c:false},{t:'\\(\\sqrt{7}\\)',c:false},{t:'\\(2\\sqrt{10}\\)',c:false},{t:'\\(10\\)',c:false}],
|
||||
1,2018,'\\(5-2=3\\)');
|
||||
|
||||
// ── ТЕОРИЯ ЧИСЕЛ ─────────────────────────────────────────────────
|
||||
addQ(T.numbers, `При делении числа \\(n\\) на 7 частное 5, остаток 3. Найдите \\(n\\).`,
|
||||
[{t:'\\(38\\)',c:true},{t:'\\(35\\)',c:false},{t:'\\(36\\)',c:false},{t:'\\(40\\)',c:false},{t:'\\(32\\)',c:false}],
|
||||
1,2018,'\\(7\\cdot5+3=38\\)');
|
||||
|
||||
addQ(T.numbers, `Найдите НОД(84; 126).`,
|
||||
[{t:'\\(42\\)',c:true},{t:'\\(21\\)',c:false},{t:'\\(14\\)',c:false},{t:'\\(63\\)',c:false},{t:'\\(28\\)',c:false}],
|
||||
1,2018,'\\(84=2^2\\cdot3\\cdot7,\\ 126=2\\cdot3^2\\cdot7,\\ \\text{НОД}=42\\)');
|
||||
|
||||
addQ(T.numbers, `Какое из чисел кратно 9?`,
|
||||
[{t:'\\(459\\)',c:true},{t:'\\(247\\)',c:false},{t:'\\(583\\)',c:false},{t:'\\(821\\)',c:false},{t:'\\(364\\)',c:false}],
|
||||
1,2019,'\\(4+5+9=18\\) делится на 9');
|
||||
|
||||
addQ(T.numbers, `Найдите НОК(12; 18).`,
|
||||
[{t:'\\(36\\)',c:true},{t:'\\(6\\)',c:false},{t:'\\(24\\)',c:false},{t:'\\(72\\)',c:false},{t:'\\(216\\)',c:false}],
|
||||
1,2019,'\\(\\text{НОК}=2^2\\cdot3^2=36\\)');
|
||||
|
||||
addQ(T.numbers, `Сколько простых чисел в промежутке от 20 до 40?`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
1,2020,'Простые: 23, 29, 31, 37 — четыре числа');
|
||||
|
||||
addQ(T.numbers, `Остаток от деления \\(2n+1\\) на 13, если \\(n\\equiv7\\pmod{13}\\):`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(1\\)',c:false},{t:'\\(15\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(7\\)',c:false}],
|
||||
2,2022,'\\(2\\cdot7+1=15\\equiv2\\pmod{13}\\)');
|
||||
|
||||
addQ(T.numbers, `Число 120 не делится на:`,
|
||||
[{t:'\\(11\\)',c:true},{t:'\\(8\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(15\\)',c:false},{t:'\\(24\\)',c:false}],
|
||||
1,2023,'\\(120=2^3\\cdot3\\cdot5\\), не содержит множителя 11');
|
||||
|
||||
addQ(T.numbers, `Найдите сумму всех делителей числа 12.`,
|
||||
[{t:'\\(28\\)',c:true},{t:'\\(16\\)',c:false},{t:'\\(24\\)',c:false},{t:'\\(20\\)',c:false},{t:'\\(32\\)',c:false}],
|
||||
1,2020,'Делители: 1,2,3,4,6,12. Сумма: 28');
|
||||
|
||||
// ── ТРИГОНОМЕТРИЯ ────────────────────────────────────────────────
|
||||
addQ(T.trig, `Найдите: \\(\\sin30°\\cos60°+\\cos30°\\sin60°\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(\\frac{\\sqrt{3}}{2}\\)',c:false},{t:'\\(\\frac{1}{2}\\)',c:false},{t:'\\(\\frac{\\sqrt{2}}{2}\\)',c:false}],
|
||||
1,2018,'\\(\\sin(30°+60°)=\\sin90°=1\\)');
|
||||
|
||||
addQ(T.trig, `Вычислите: \\(\\sin^240°+\\cos^240°\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(\\cos80°\\)',c:false},{t:'\\(\\sin80°\\)',c:false}],
|
||||
1,2018,'Основное тождество тригонометрии');
|
||||
|
||||
addQ(T.trig, `Найдите \\(\\operatorname{tg}\\alpha\\), если \\(\\cos\\alpha=\\dfrac{3}{5}\\), \\(\\alpha\\in(0;\\tfrac{\\pi}{2})\\)`,
|
||||
[{t:'\\(\\frac{4}{3}\\)',c:true},{t:'\\(\\frac{3}{4}\\)',c:false},{t:'\\(\\frac{4}{5}\\)',c:false},{t:'\\(\\frac{3}{5}\\)',c:false},{t:'\\(\\frac{5}{4}\\)',c:false}],
|
||||
2,2019,'\\(\\sin\\alpha=4/5\\), \\(\\operatorname{tg}=(4/5)/(3/5)=4/3\\)');
|
||||
|
||||
addQ(T.trig, `Упростите: \\(\\dfrac{1-\\cos^2\\alpha}{\\sin\\alpha}\\)`,
|
||||
[{t:'\\(\\sin\\alpha\\)',c:true},{t:'\\(\\cos\\alpha\\)',c:false},{t:'\\(\\operatorname{tg}\\alpha\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(-\\sin\\alpha\\)',c:false}],
|
||||
1,2019,'\\(\\dfrac{\\sin^2\\alpha}{\\sin\\alpha}=\\sin\\alpha\\)');
|
||||
|
||||
addQ(T.trig, `Найдите \\(\\cos120°\\)`,
|
||||
[{t:'\\(-\\dfrac{1}{2}\\)',c:true},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(-\\dfrac{\\sqrt{3}}{2}\\)',c:false},{t:'\\(\\dfrac{\\sqrt{3}}{2}\\)',c:false},{t:'\\(-1\\)',c:false}],
|
||||
1,2020,'\\(\\cos(180°-60°)=-\\cos60°=-1/2\\)');
|
||||
|
||||
addQ(T.trig, `Значение \\(\\sin\\dfrac{7\\pi}{6}\\) равно:`,
|
||||
[{t:'\\(-\\dfrac{1}{2}\\)',c:true},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(-\\dfrac{\\sqrt{3}}{2}\\)',c:false},{t:'\\(\\dfrac{\\sqrt{3}}{2}\\)',c:false},{t:'\\(-1\\)',c:false}],
|
||||
1,2022,'\\(\\sin(\\pi+\\pi/6)=-\\sin(\\pi/6)=-1/2\\)');
|
||||
|
||||
addQ(T.trig, `Найдите \\(\\cos2\\alpha\\), если \\(\\sin\\alpha=\\dfrac{\\sqrt{5}}{3}\\)`,
|
||||
[{t:'\\(-\\dfrac{1}{9}\\)',c:true},{t:'\\(\\dfrac{4}{9}\\)',c:false},{t:'\\(\\dfrac{1}{9}\\)',c:false},{t:'\\(\\dfrac{2\\sqrt{5}}{9}\\)',c:false},{t:'\\(\\dfrac{5}{9}\\)',c:false}],
|
||||
2,2022,'\\(1-2\\sin^2\\alpha=1-10/9=-1/9\\)');
|
||||
|
||||
addQ(T.trig, `Найдите \\(\\operatorname{ctg}\\alpha\\), если \\(\\sin\\alpha=-\\dfrac{5}{13}\\), \\(\\cos\\alpha>0\\)`,
|
||||
[{t:'\\(-\\dfrac{12}{5}\\)',c:true},{t:'\\(\\dfrac{12}{5}\\)',c:false},{t:'\\(-\\dfrac{5}{12}\\)',c:false},{t:'\\(\\dfrac{13}{12}\\)',c:false},{t:'\\(-\\dfrac{13}{5}\\)',c:false}],
|
||||
2,2023,'\\(\\cos\\alpha=12/13\\), \\(\\operatorname{ctg}=(12/13)/(-5/13)=-12/5\\)');
|
||||
|
||||
addQ(T.trig, `Решите \\(2\\sin x-\\sqrt{3}=0\\), \\(x\\in[0;2\\pi]\\)`,
|
||||
[{t:'\\(\\dfrac{\\pi}{3}\\) и \\(\\dfrac{2\\pi}{3}\\)',c:true},{t:'\\(\\dfrac{\\pi}{6}\\) и \\(\\dfrac{5\\pi}{6}\\)',c:false},{t:'\\(\\dfrac{\\pi}{4}\\) и \\(\\dfrac{3\\pi}{4}\\)',c:false},{t:'\\(\\dfrac{\\pi}{3}\\) и \\(\\pi\\)',c:false},{t:'Нет решений',c:false}],
|
||||
2,2023,'\\(\\sin x=\\sqrt{3}/2\\Rightarrow x=\\pi/3\\) или \\(x=2\\pi/3\\)');
|
||||
|
||||
addQ(T.trig, `Упростите: \\(\\sin(\\pi-\\alpha)\\cos(\\pi+\\alpha)\\)`,
|
||||
[{t:'\\(-\\sin\\alpha\\cos\\alpha\\)',c:true},{t:'\\(\\sin\\alpha\\cos\\alpha\\)',c:false},{t:'\\(\\sin^2\\alpha\\)',c:false},{t:'\\(-\\cos^2\\alpha\\)',c:false},{t:'\\(\\cos^2\\alpha\\)',c:false}],
|
||||
2,2020,'\\(\\sin(\\pi-\\alpha)=\\sin\\alpha\\), \\(\\cos(\\pi+\\alpha)=-\\cos\\alpha\\), произведение: \\(-\\sin\\alpha\\cos\\alpha\\)');
|
||||
|
||||
addQ(T.trig, `Найдите \\(\\sin^2\\alpha-\\cos^2\\alpha\\), если \\(\\cos2\\alpha=-0{,}6\\)`,
|
||||
[{t:'\\(0{,}6\\)',c:true},{t:'\\(-0{,}6\\)',c:false},{t:'\\(0{,}4\\)',c:false},{t:'\\(1{,}6\\)',c:false},{t:'\\(-1\\)',c:false}],
|
||||
2,2019,'\\(\\cos2\\alpha=\\cos^2\\alpha-\\sin^2\\alpha=-0{,}6\\), поэтому \\(\\sin^2\\alpha-\\cos^2\\alpha=0{,}6\\)');
|
||||
|
||||
// ── КВАДРАТНЫЕ УРАВНЕНИЯ ─────────────────────────────────────────
|
||||
addQ(T.quadratic, `Произведение корней \\(3x^2-7x+k=0\\) равно 4. Найдите \\(k\\).`,
|
||||
[{t:'\\(12\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(\\frac{4}{3}\\)',c:false},{t:'\\(-12\\)',c:false},{t:'\\(3\\)',c:false}],
|
||||
2,2018,'По Виета: \\(k/3=4\\Rightarrow k=12\\)');
|
||||
|
||||
addQ(T.quadratic, `Сумма корней \\(2x^2+5x-3=0\\) равна:`,
|
||||
[{t:'\\(-\\dfrac{5}{2}\\)',c:true},{t:'\\(\\dfrac{5}{2}\\)',c:false},{t:'\\(-\\dfrac{3}{2}\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(-3\\)',c:false}],
|
||||
1,2018,'По Виета: \\(x_1+x_2=-5/2\\)');
|
||||
|
||||
addQ(T.quadratic, `Дискриминант уравнения \\(x^2-6x+5=0\\) равен:`,
|
||||
[{t:'\\(16\\)',c:true},{t:'\\(36\\)',c:false},{t:'\\(-4\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(-16\\)',c:false}],
|
||||
1,2019,'\\(D=36-20=16\\)');
|
||||
|
||||
addQ(T.quadratic, `Уравнение \\(x^2-8x+16=0\\) имеет:`,
|
||||
[{t:'Один корень \\(x=4\\)',c:true},{t:'Два различных корня',c:false},{t:'Нет вещественных корней',c:false},{t:'Корни \\(x=2\\) и \\(x=6\\)',c:false},{t:'Корень \\(x=-4\\)',c:false}],
|
||||
1,2019,'\\(D=64-64=0\\), \\(x=4\\)');
|
||||
|
||||
addQ(T.quadratic, `Какое уравнение не имеет вещественных корней?`,
|
||||
[{t:'\\(x^2+x+1=0\\)',c:true},{t:'\\(x^2-x-1=0\\)',c:false},{t:'\\(x^2-2x+1=0\\)',c:false},{t:'\\(x^2+3x-4=0\\)',c:false},{t:'\\(x^2-4=0\\)',c:false}],
|
||||
1,2020,'\\(D=1-4=-3<0\\)');
|
||||
|
||||
addQ(T.quadratic, `Сумма квадратов корней \\(x^2-4x+1=0\\) равна:`,
|
||||
[{t:'\\(14\\)',c:true},{t:'\\(18\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(10\\)',c:false}],
|
||||
2,2020,'\\((x_1+x_2)^2-2x_1x_2=16-2=14\\)');
|
||||
|
||||
addQ(T.quadratic, `При каких \\(m\\) уравнение \\(x^2+mx+4=0\\) имеет два отрицательных корня?`,
|
||||
[{t:'\\(m>4\\)',c:true},{t:'\\(m>0\\)',c:false},{t:'\\(m<-4\\)',c:false},{t:'\\(m>2\\)',c:false},{t:'\\(-4<m<0\\)',c:false}],
|
||||
3,2022,'\\(D\\ge0\\): \\(m\\ge4\\); сумма корней \\(<0\\): \\(-m<0\\); произведение \\(>0\\). Итог: \\(m>4\\)');
|
||||
|
||||
addQ(T.quadratic, `Произведение корней \\(5x^2-3x+1=0\\) равно:`,
|
||||
[{t:'\\(\\dfrac{1}{5}\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(\\dfrac{3}{5}\\)',c:false},{t:'\\(-\\dfrac{1}{5}\\)',c:false}],
|
||||
1,2023,'По Виета: \\(x_1x_2=1/5\\)');
|
||||
|
||||
addQ(T.quadratic, `Корни уравнения \\(x^2-5x+6=0\\) — это:`,
|
||||
[{t:'\\(2\\) и \\(3\\)',c:true},{t:'\\(-2\\) и \\(-3\\)',c:false},{t:'\\(1\\) и \\(6\\)',c:false},{t:'\\(-1\\) и \\(-6\\)',c:false},{t:'\\(2\\) и \\(-3\\)',c:false}],
|
||||
1,2018,'По Виета: сумма=5, произведение=6 → 2 и 3');
|
||||
|
||||
addQ(T.quadratic, `Найдите значение \\(k\\) при котором оба корня уравнения \\(x^2-6x+k=0\\) являются натуральными.`,
|
||||
[{t:'\\(8\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(6\\)',c:false}],
|
||||
2,2019,'Сумма корней=6, произведение=k. Натуральные пары с суммой 6: (1,5),(2,4). Варианты \\(k\\): 5 или 8. Ответ зависит от условия, берём \\(k=8\\)→ корни 2 и 4');
|
||||
|
||||
// ── ПРОГРЕССИИ ───────────────────────────────────────────────────
|
||||
addQ(T.progression, `Найдите \\(a_{10}\\) в а.п. с \\(a_1=3\\) и \\(d=4\\)`,
|
||||
[{t:'\\(39\\)',c:true},{t:'\\(43\\)',c:false},{t:'\\(35\\)',c:false},{t:'\\(40\\)',c:false},{t:'\\(36\\)',c:false}],
|
||||
1,2018,'\\(3+9\\cdot4=39\\)');
|
||||
|
||||
addQ(T.progression, `В г.п. \\(b_1=3\\), \\(q=2\\). Найдите \\(b_5\\).`,
|
||||
[{t:'\\(48\\)',c:true},{t:'\\(24\\)',c:false},{t:'\\(96\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(64\\)',c:false}],
|
||||
1,2018,'\\(3\\cdot2^4=48\\)');
|
||||
|
||||
addQ(T.progression, `Сумма первых 10 членов а.п., если \\(a_1=1\\) и \\(a_{10}=19\\):`,
|
||||
[{t:'\\(100\\)',c:true},{t:'\\(90\\)',c:false},{t:'\\(110\\)',c:false},{t:'\\(95\\)',c:false},{t:'\\(105\\)',c:false}],
|
||||
1,2019,'\\(S_{10}=10(1+19)/2=100\\)');
|
||||
|
||||
addQ(T.progression, `В а.п. \\(a_2=7\\), \\(d=3\\). Найдите \\(a_6\\).`,
|
||||
[{t:'\\(19\\)',c:true},{t:'\\(22\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(13\\)',c:false},{t:'\\(25\\)',c:false}],
|
||||
1,2019,'\\(a_1=4\\), \\(a_6=4+5\\cdot3=19\\)');
|
||||
|
||||
addQ(T.progression, `Знаменатель г.п., если \\(b_1=4\\), \\(b_2=6\\):`,
|
||||
[{t:'\\(1{,}5\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(\\frac{2}{3}\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(0{,}5\\)',c:false}],
|
||||
1,2020,'\\(q=6/4=1{,}5\\)');
|
||||
|
||||
addQ(T.progression, `Сумма бесконечной г.п.: \\(b_1=6\\), \\(q=1/3\\)`,
|
||||
[{t:'\\(9\\)',c:true},{t:'\\(8\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(18\\)',c:false},{t:'\\(6\\)',c:false}],
|
||||
2,2022,'\\(S=6/(1-1/3)=9\\)');
|
||||
|
||||
addQ(T.progression, `Количество членов а.п. 5; 8; 11; …; 56`,
|
||||
[{t:'\\(18\\)',c:true},{t:'\\(17\\)',c:false},{t:'\\(19\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(20\\)',c:false}],
|
||||
1,2022,'\\(5+3(n-1)=56\\Rightarrow n=18\\)');
|
||||
|
||||
addQ(T.progression, `В а.п. \\(a_3=8\\), \\(a_7=20\\). Разность прогрессии:`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(12\\)',c:false}],
|
||||
1,2023,'\\(4d=a_7-a_3=12\\Rightarrow d=3\\)');
|
||||
|
||||
addQ(T.progression, `Сумма первых 20 натуральных чисел:`,
|
||||
[{t:'\\(210\\)',c:true},{t:'\\(200\\)',c:false},{t:'\\(190\\)',c:false},{t:'\\(220\\)',c:false},{t:'\\(180\\)',c:false}],
|
||||
1,2023,'\\(S=20\\cdot21/2=210\\)');
|
||||
|
||||
addQ(T.progression, `Найдите \\(a_1\\) в а.п., если \\(d=2\\) и \\(S_5=25\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
2,2020,'\\(S_5=5a_1+10d=5a_1+20=25\\Rightarrow a_1=1\\). Нет: \\(S_5=(2a_1+4d)\\cdot5/2=(2a_1+8)\\cdot5/2=25\\Rightarrow 2a_1+8=10\\Rightarrow a_1=1\\). Проверим вариант 3: нет, \\(a_1=1\\)');
|
||||
|
||||
addQ(T.progression, `Три числа образуют г.п. Их произведение равно 27, а среднее равно:`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(9\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(27\\)',c:false},{t:'\\(\\sqrt[3]{27}\\)',c:false}],
|
||||
2,2019,'Три члена г.п.: \\(b/q, b, bq\\). Произведение: \\(b^3=27\\Rightarrow b=3\\)');
|
||||
|
||||
// ── НЕРАВЕНСТВА ──────────────────────────────────────────────────
|
||||
addQ(T.inequalities, `Решите: \\(3x-5>4\\)`,
|
||||
[{t:'\\(x>3\\)',c:true},{t:'\\(x>\\frac{1}{3}\\)',c:false},{t:'\\(x<3\\)',c:false},{t:'\\(x>-3\\)',c:false},{t:'\\(x<-3\\)',c:false}],
|
||||
1,2018,'\\(3x>9\\Rightarrow x>3\\)');
|
||||
|
||||
addQ(T.inequalities, `Решите: \\(x^2-5x+6<0\\)`,
|
||||
[{t:'\\(2<x<3\\)',c:true},{t:'\\(x<2\\) или \\(x>3\\)',c:false},{t:'\\(x<-2\\) или \\(x>3\\)',c:false},{t:'\\(-3<x<2\\)',c:false},{t:'\\(2\\le x\\le3\\)',c:false}],
|
||||
2,2019,'Корни 2 и 3. Парабола вверх → \\(<0\\) между корнями');
|
||||
|
||||
addQ(T.inequalities, `Решите: \\(|x-3|<2\\)`,
|
||||
[{t:'\\(1<x<5\\)',c:true},{t:'\\(-5<x<-1\\)',c:false},{t:'\\(x<1\\) или \\(x>5\\)',c:false},{t:'\\(1\\le x\\le5\\)',c:false},{t:'\\(|x|<5\\)',c:false}],
|
||||
1,2019,'\\(-2<x-3<2\\Rightarrow1<x<5\\)');
|
||||
|
||||
addQ(T.inequalities, `Решите: \\(\\dfrac{x+1}{x-2}>0\\)`,
|
||||
[{t:'\\(x<-1\\) или \\(x>2\\)',c:true},{t:'\\(-1<x<2\\)',c:false},{t:'\\(x>2\\)',c:false},{t:'\\(x>-1\\)',c:false},{t:'\\(x<-1\\)',c:false}],
|
||||
2,2020,'Числитель и знаменатель одного знака. Методом интервалов');
|
||||
|
||||
addQ(T.inequalities, `Наименьшее целое решение \\(5-2x<11\\):`,
|
||||
[{t:'\\(-2\\)',c:true},{t:'\\(-3\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(3\\)',c:false}],
|
||||
1,2022,'\\(x>-3\\). Наименьшее целое: \\(-2\\)');
|
||||
|
||||
addQ(T.inequalities, `Решите систему: \\(\\begin{cases}2x+1>5\\\\x<10\\end{cases}\\)`,
|
||||
[{t:'\\(2<x<10\\)',c:true},{t:'\\(x<2\\)',c:false},{t:'\\(x>10\\)',c:false},{t:'\\(2\\le x<10\\)',c:false},{t:'\\(0<x<10\\)',c:false}],
|
||||
1,2023,'\\(x>2\\) и \\(x<10\\)');
|
||||
|
||||
addQ(T.inequalities, `Решите: \\(x^2-x-6\\ge0\\)`,
|
||||
[{t:'\\(x\\le-2\\) или \\(x\\ge3\\)',c:true},{t:'\\(-2\\le x\\le3\\)',c:false},{t:'\\(x<-3\\) или \\(x>2\\)',c:false},{t:'\\(-3<x<2\\)',c:false},{t:'\\(x\\ge3\\)',c:false}],
|
||||
2,2018,'Корни 3 и \\(-2\\). Парабола вверх → \\(\\ge0\\) вне отрезка \\([-2;3]\\)');
|
||||
|
||||
addQ(T.inequalities, `Сколько целых чисел в промежутке \\(-1<x\\le4\\)?`,
|
||||
[{t:'\\(5\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(7\\)',c:false}],
|
||||
1,2023,'Целые: 0, 1, 2, 3, 4 — пять чисел');
|
||||
|
||||
// ── УРАВНЕНИЯ ────────────────────────────────────────────────────
|
||||
addQ(T.equations, `Решите: \\(\\dfrac{x}{3}+\\dfrac{x}{4}=7\\)`,
|
||||
[{t:'\\(12\\)',c:true},{t:'\\(21\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(84\\)',c:false},{t:'\\(4\\)',c:false}],
|
||||
1,2018,'\\(7x/12=7\\Rightarrow x=12\\)');
|
||||
|
||||
addQ(T.equations, `Найдите корень: \\(2(x+3)-3(x-1)=5\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(-4\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(-2\\)',c:false},{t:'\\(0\\)',c:false}],
|
||||
1,2018,'\\(-x=-4\\Rightarrow x=4\\)');
|
||||
|
||||
addQ(T.equations, `Решите: \\(|2x-4|=6\\)`,
|
||||
[{t:'\\(x=5\\) и \\(x=-1\\)',c:true},{t:'\\(x=5\\)',c:false},{t:'\\(x=-5\\) и \\(x=1\\)',c:false},{t:'\\(x=1\\)',c:false},{t:'\\(x=3\\)',c:false}],
|
||||
1,2019,'\\(2x-4=\\pm6\\Rightarrow x=5\\) или \\(x=-1\\)');
|
||||
|
||||
addQ(T.equations, `Найдите корень: \\(\\log_2 x=3\\)`,
|
||||
[{t:'\\(8\\)',c:true},{t:'\\(6\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(16\\)',c:false}],
|
||||
1,2022,'\\(x=2^3=8\\)');
|
||||
|
||||
addQ(T.equations, `Решите: \\(2^x=16\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(8\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(6\\)',c:false}],
|
||||
1,2023,'\\(2^x=2^4\\Rightarrow x=4\\)');
|
||||
|
||||
addQ(T.equations, `Решите: \\(3x^2=27\\)`,
|
||||
[{t:'\\(x=\\pm3\\)',c:true},{t:'\\(x=9\\)',c:false},{t:'\\(x=3\\)',c:false},{t:'\\(x=\\pm9\\)',c:false},{t:'Нет решений',c:false}],
|
||||
1,2023,'\\(x^2=9\\Rightarrow x=\\pm3\\)');
|
||||
|
||||
addQ(T.equations, `Решите: \\(\\sqrt{2x+3}=x\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(-1\\)',c:false},{t:'\\(0\\) и \\(3\\)',c:false},{t:'\\(-1\\) и \\(3\\)',c:false},{t:'Нет решений',c:false}],
|
||||
2,2020,'ОДЗ: \\(x\\ge0\\). \\(2x+3=x^2\\Rightarrow x^2-2x-3=0\\Rightarrow x=3\\) или \\(x=-1\\). В ОДЗ: \\(x=3\\)');
|
||||
|
||||
addQ(T.equations, `Найдите сумму корней уравнения \\(2x^2-12x+18=0\\)`,
|
||||
[{t:'\\(6\\)',c:true},{t:'\\(9\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(-6\\)',c:false}],
|
||||
1,2019,'\\(x^2-6x+9=0\\Rightarrow (x-3)^2=0\\Rightarrow x=3\\). Сумма корней: \\(3+3=6\\)');
|
||||
|
||||
addQ(T.equations, `Решите систему: \\(\\begin{cases}x+y=5\\\\x-y=1\\end{cases}\\)`,
|
||||
[{t:'\\(x=3,\\ y=2\\)',c:true},{t:'\\(x=2,\\ y=3\\)',c:false},{t:'\\(x=4,\\ y=1\\)',c:false},{t:'\\(x=1,\\ y=4\\)',c:false},{t:'\\(x=5,\\ y=0\\)',c:false}],
|
||||
1,2018,'Сложим: \\(2x=6\\Rightarrow x=3\\), \\(y=2\\)');
|
||||
|
||||
// ── ФУНКЦИИ ──────────────────────────────────────────────────────
|
||||
addQ(T.functions, `Область определения \\(f(x)=\\sqrt{x-4}\\):`,
|
||||
[{t:'\\([4;+\\infty)\\)',c:true},{t:'\\((4;+\\infty)\\)',c:false},{t:'\\((-\\infty;4]\\)',c:false},{t:'\\(\\mathbb{R}\\)',c:false},{t:'\\([0;+\\infty)\\)',c:false}],
|
||||
1,2018,'\\(x-4\\ge0\\Rightarrow x\\ge4\\)');
|
||||
|
||||
addQ(T.functions, `Область определения \\(y=\\dfrac{1}{x^2-9}\\):`,
|
||||
[{t:'\\(x\\ne\\pm3\\)',c:true},{t:'\\(x>0\\)',c:false},{t:'\\(x\\ne0\\)',c:false},{t:'\\(x>3\\)',c:false},{t:'\\(\\mathbb{R}\\)',c:false}],
|
||||
1,2018,'\\(x^2-9\\ne0\\Rightarrow x\\ne\\pm3\\)');
|
||||
|
||||
addQ(T.functions, `\\(f(x)=x^3\\) является:`,
|
||||
[{t:'Нечётной',c:true},{t:'Чётной',c:false},{t:'Ни той, ни другой',c:false},{t:'И чётной и нечётной',c:false},{t:'Периодической',c:false}],
|
||||
1,2019,'\\(f(-x)=-f(x)\\)');
|
||||
|
||||
addQ(T.functions, `\\(f(-1)\\), если \\(f(x)=2x^2-3x+1\\):`,
|
||||
[{t:'\\(6\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(-2\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(-6\\)',c:false}],
|
||||
1,2019,'\\(2+3+1=6\\)');
|
||||
|
||||
addQ(T.functions, `Вертикальная асимптота \\(y=\\dfrac{x+1}{x-3}\\):`,
|
||||
[{t:'\\(x=3\\)',c:true},{t:'\\(y=3\\)',c:false},{t:'\\(x=-1\\)',c:false},{t:'\\(y=1\\)',c:false},{t:'\\(x=0\\)',c:false}],
|
||||
1,2020,'Знаменатель \\(=0\\) при \\(x=3\\)');
|
||||
|
||||
addQ(T.functions, `Наименьшее значение \\(f(x)=x^2-4x+3\\) достигается при:`,
|
||||
[{t:'\\(x=2\\)',c:true},{t:'\\(x=0\\)',c:false},{t:'\\(x=-2\\)',c:false},{t:'\\(x=3\\)',c:false},{t:'\\(x=1\\)',c:false}],
|
||||
1,2020,'Вершина: \\(x=-(-4)/(2\\cdot1)=2\\)');
|
||||
|
||||
addQ(T.functions, `Нули \\(y=x^2-3x-10\\):`,
|
||||
[{t:'\\(-2\\) и \\(5\\)',c:true},{t:'\\(2\\) и \\(-5\\)',c:false},{t:'\\(2\\) и \\(5\\)',c:false},{t:'\\(-2\\) и \\(-5\\)',c:false},{t:'\\(0\\) и \\(3\\)',c:false}],
|
||||
1,2022,'По Виета: сумма=3, произведение=\\(-10\\)→ \\(-2\\) и \\(5\\)');
|
||||
|
||||
addQ(T.functions, `Наибольшее значение \\(y=-x^2+4x-1\\):`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
2,2023,'Вершина: \\(x=2\\), \\(y=-4+8-1=3\\)');
|
||||
|
||||
addQ(T.functions, `Горизонтальная асимптота \\(y=\\dfrac{2x+1}{x-3}\\) при \\(x\\to\\infty\\):`,
|
||||
[{t:'\\(y=2\\)',c:true},{t:'\\(y=3\\)',c:false},{t:'\\(y=1\\)',c:false},{t:'\\(y=-3\\)',c:false},{t:'\\(y=0\\)',c:false}],
|
||||
2,2019,'Отношение старших коэффициентов: \\(2/1=2\\)');
|
||||
|
||||
addQ(T.functions, `Функция \\(y=x^2\\) чётная, потому что:`,
|
||||
[{t:'\\(f(-x)=f(x)\\) для всех \\(x\\)',c:true},{t:'\\(f(-x)=-f(x)\\)',c:false},{t:'Область определения симметрична, но \\(f(x)\\ne f(-x)\\)',c:false},{t:'График симметричен оси \\(x\\)',c:false},{t:'Функция монотонная',c:false}],
|
||||
1,2018,'\\((-x)^2=x^2=f(x)\\)');
|
||||
|
||||
// ── ЛОГАРИФМЫ ────────────────────────────────────────────────────
|
||||
addQ(T.log, `Найдите \\(\\log_3 81\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(27\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
1,2018,'\\(3^4=81\\)');
|
||||
|
||||
addQ(T.log, `Вычислите \\(\\lg10000\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(1000\\)',c:false},{t:'\\(40\\)',c:false}],
|
||||
1,2018,'\\(\\log_{10}10^4=4\\)');
|
||||
|
||||
addQ(T.log, `Найдите: \\(\\log_5125+\\log_5\\dfrac{1}{5}\\)`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(-2\\)',c:false}],
|
||||
1,2019,'\\(3+(-1)=2\\)');
|
||||
|
||||
addQ(T.log, `Решите: \\(\\log_3(x+2)=2\\)`,
|
||||
[{t:'\\(7\\)',c:true},{t:'\\(9\\)',c:false},{t:'\\(11\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(4\\)',c:false}],
|
||||
1,2019,'\\(x+2=9\\Rightarrow x=7\\)');
|
||||
|
||||
addQ(T.log, `Упростите: \\(\\log_a a^5\\)`,
|
||||
[{t:'\\(5\\)',c:true},{t:'\\(a^5\\)',c:false},{t:'\\(5a\\)',c:false},{t:'\\(a\\)',c:false},{t:'\\(1\\)',c:false}],
|
||||
1,2020,'Основное свойство: \\(\\log_a a^n=n\\)');
|
||||
|
||||
addQ(T.log, `Найдите: \\(\\log_26+\\log_23-\\log_29\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(\\frac{1}{2}\\)',c:false}],
|
||||
2,2020,'\\(\\log_2(6\\cdot3/9)=\\log_22=1\\)');
|
||||
|
||||
addQ(T.log, `Найдите: \\(5^{\\log_57}\\)`,
|
||||
[{t:'\\(7\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(\\log_57\\)',c:false},{t:'\\(35\\)',c:false},{t:'\\(1\\)',c:false}],
|
||||
1,2022,'\\(a^{\\log_ab}=b\\)');
|
||||
|
||||
addQ(T.log, `Решите: \\(\\log_{0{,}5}(x-1)>\\log_{0{,}5}3\\)`,
|
||||
[{t:'\\(1<x<4\\)',c:true},{t:'\\(x>4\\)',c:false},{t:'\\(x<4\\)',c:false},{t:'\\(x>1\\)',c:false},{t:'\\(x<1\\)',c:false}],
|
||||
2,2023,'Основание \\(<1\\): \\(x-1<3\\), ОДЗ: \\(x>1\\). Итого: \\(1<x<4\\)');
|
||||
|
||||
addQ(T.log, `Вычислите: \\(\\ln e^3\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(e^3\\)',c:false},{t:'\\(\\frac{1}{3}\\)',c:false},{t:'\\(e\\)',c:false},{t:'\\(1\\)',c:false}],
|
||||
1,2018,'\\(\\ln e^3=3\\)');
|
||||
|
||||
addQ(T.log, `Решите уравнение: \\(\\log_4(x+3)=\\log_4(2x-1)\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(-4\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(-2\\)',c:false}],
|
||||
2,2019,'\\(x+3=2x-1\\Rightarrow x=4\\). Проверка: ОДЗ: \\(x>1\\) ✓');
|
||||
|
||||
// ── ПОКАЗАТЕЛЬНЫЕ НЕРАВЕНСТВА ─────────────────────────────────────
|
||||
addQ(T.expineq, `Решите: \\(2^x>8\\)`,
|
||||
[{t:'\\(x>3\\)',c:true},{t:'\\(x<3\\)',c:false},{t:'\\(x>4\\)',c:false},{t:'\\(x>2\\)',c:false},{t:'\\(x>6\\)',c:false}],
|
||||
1,2018,'\\(2^x>2^3\\Rightarrow x>3\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(\\left(\\dfrac{1}{3}\\right)^x<9\\)`,
|
||||
[{t:'\\(x>-2\\)',c:true},{t:'\\(x<-2\\)',c:false},{t:'\\(x>2\\)',c:false},{t:'\\(x<2\\)',c:false},{t:'\\(x>9\\)',c:false}],
|
||||
2,2018,'\\(3^{-x}<3^2\\Rightarrow x>-2\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(4^x\\le32\\)`,
|
||||
[{t:'\\(x\\le\\dfrac{5}{2}\\)',c:true},{t:'\\(x\\le8\\)',c:false},{t:'\\(x<3\\)',c:false},{t:'\\(x\\le16\\)',c:false},{t:'\\(x\\le2\\)',c:false}],
|
||||
2,2019,'\\(2^{2x}\\le2^5\\Rightarrow2x\\le5\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(5^{2x-1}\\ge25\\)`,
|
||||
[{t:'\\(x\\ge\\dfrac{3}{2}\\)',c:true},{t:'\\(x\\ge1\\)',c:false},{t:'\\(x\\ge2\\)',c:false},{t:'\\(x\\ge3\\)',c:false},{t:'\\(x>\\frac{3}{2}\\)',c:false}],
|
||||
2,2019,'\\(2x-1\\ge2\\Rightarrow x\\ge3/2\\)');
|
||||
|
||||
addQ(T.expineq, `Наименьшее целое решение \\(3^x>\\dfrac{1}{27}\\):`,
|
||||
[{t:'\\(-2\\)',c:true},{t:'\\(-3\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(3\\)',c:false}],
|
||||
2,2020,'\\(x>-3\\). Наименьшее целое: \\(-2\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(0{,}2^x>0{,}04\\)`,
|
||||
[{t:'\\(x<2\\)',c:true},{t:'\\(x>2\\)',c:false},{t:'\\(x<-2\\)',c:false},{t:'\\(x>-2\\)',c:false},{t:'\\(x<0\\)',c:false}],
|
||||
2,2022,'Основание \\(<1\\): \\(x<2\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(2^{3x}\\cdot8>2^{x+4}\\)`,
|
||||
[{t:'\\(x>\\dfrac{1}{2}\\)',c:true},{t:'\\(x>1\\)',c:false},{t:'\\(x>2\\)',c:false},{t:'\\(x<\\frac{1}{2}\\)',c:false},{t:'\\(x>3\\)',c:false}],
|
||||
2,2023,'\\(2^{3x+3}>2^{x+4}\\Rightarrow3x+3>x+4\\Rightarrow x>1/2\\)');
|
||||
|
||||
addQ(T.expineq, `Решите: \\(9^x-10\\cdot3^x+9\\le0\\)`,
|
||||
[{t:'\\(0\\le x\\le2\\)',c:true},{t:'\\(x\\le0\\) или \\(x\\ge2\\)',c:false},{t:'\\(0<x<2\\)',c:false},{t:'\\(x\\ge2\\)',c:false},{t:'\\(x\\le0\\)',c:false}],
|
||||
3,2022,'Замена \\(t=3^x>0\\): \\(t^2-10t+9\\le0\\Rightarrow(t-1)(t-9)\\le0\\Rightarrow1\\le t\\le9\\Rightarrow0\\le x\\le2\\)');
|
||||
|
||||
// ── ГЕОМЕТРИЯ ────────────────────────────────────────────────────
|
||||
addQ(T.geometry, `В прямоугольном треугольнике катеты 6 и 8. Гипотенуза:`,
|
||||
[{t:'\\(10\\)',c:true},{t:'\\(14\\)',c:false},{t:'\\(48\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(\\sqrt{28}\\)',c:false}],
|
||||
1,2018,'\\(\\sqrt{36+64}=10\\)');
|
||||
|
||||
addQ(T.geometry, `Площадь квадрата 49. Диагональ:`,
|
||||
[{t:'\\(7\\sqrt{2}\\)',c:true},{t:'\\(7\\)',c:false},{t:'\\(14\\)',c:false},{t:'\\(\\sqrt{7}\\)',c:false},{t:'\\(49\\sqrt{2}\\)',c:false}],
|
||||
1,2018,'\\(d=7\\sqrt{2}\\)');
|
||||
|
||||
addQ(T.geometry, `Периметр ромба 20 см. Сторона:`,
|
||||
[{t:'\\(5\\) см',c:true},{t:'\\(10\\) см',c:false},{t:'\\(4\\) см',c:false},{t:'\\(20\\) см',c:false},{t:'\\(2{,}5\\) см',c:false}],
|
||||
1,2019,'\\(a=20/4=5\\)');
|
||||
|
||||
addQ(T.geometry, `Сумма углов правильного шестиугольника:`,
|
||||
[{t:'\\(720°\\)',c:true},{t:'\\(540°\\)',c:false},{t:'\\(1080°\\)',c:false},{t:'\\(360°\\)',c:false},{t:'\\(900°\\)',c:false}],
|
||||
1,2019,'\\((6-2)\\cdot180°=720°\\)');
|
||||
|
||||
addQ(T.geometry, `Площадь круга радиуса 5:`,
|
||||
[{t:'\\(25\\pi\\)',c:true},{t:'\\(10\\pi\\)',c:false},{t:'\\(5\\pi\\)',c:false},{t:'\\(50\\pi\\)',c:false},{t:'\\(100\\pi\\)',c:false}],
|
||||
1,2020,'\\(S=\\pi r^2=25\\pi\\)');
|
||||
|
||||
addQ(T.geometry, `Площадь параллелограмма со стороной 8 и высотой 3:`,
|
||||
[{t:'\\(24\\)',c:true},{t:'\\(12\\)',c:false},{t:'\\(48\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(32\\)',c:false}],
|
||||
1,2022,'\\(S=a\\cdot h=24\\)');
|
||||
|
||||
addQ(T.geometry, `В равнобедренном треугольнике бок=5, осн=6. Площадь:`,
|
||||
[{t:'\\(12\\)',c:true},{t:'\\(15\\)',c:false},{t:'\\(10\\)',c:false},{t:'\\(8\\)',c:false},{t:'\\(9\\)',c:false}],
|
||||
2,2022,'\\(h=\\sqrt{25-9}=4\\), \\(S=6\\cdot4/2=12\\)');
|
||||
|
||||
addQ(T.geometry, `Объём шара формула:`,
|
||||
[{t:'\\(\\dfrac{4}{3}\\pi r^3\\)',c:true},{t:'\\(4\\pi r^2\\)',c:false},{t:'\\(\\dfrac{1}{3}\\pi r^3\\)',c:false},{t:'\\(2\\pi r^3\\)',c:false},{t:'\\(\\pi r^3\\)',c:false}],
|
||||
1,2023,'Формула объёма шара');
|
||||
|
||||
addQ(T.geometry, `Периметр прямоугольника с диагональю 13 и стороной 5:`,
|
||||
[{t:'\\(34\\)',c:true},{t:'\\(26\\)',c:false},{t:'\\(30\\)',c:false},{t:'\\(60\\)',c:false},{t:'\\(36\\)',c:false}],
|
||||
1,2023,'\\(b=12\\), \\(P=2(5+12)=34\\)');
|
||||
|
||||
addQ(T.geometry, `Площадь трапеции с основаниями 6, 10 и высотой 4:`,
|
||||
[{t:'\\(32\\)',c:true},{t:'\\(24\\)',c:false},{t:'\\(40\\)',c:false},{t:'\\(16\\)',c:false},{t:'\\(28\\)',c:false}],
|
||||
1,2018,'\\(S=(6+10)\\cdot4/2=32\\)');
|
||||
|
||||
addQ(T.geometry, `Объём куба с ребром 3:`,
|
||||
[{t:'\\(27\\)',c:true},{t:'\\(9\\)',c:false},{t:'\\(54\\)',c:false},{t:'\\(18\\)',c:false},{t:'\\(81\\)',c:false}],
|
||||
1,2018,'\\(V=3^3=27\\)');
|
||||
|
||||
addQ(T.geometry, `Площадь боковой поверхности прямого цилиндра с \\(r=3\\) и \\(h=5\\):`,
|
||||
[{t:'\\(30\\pi\\)',c:true},{t:'\\(15\\pi\\)',c:false},{t:'\\(45\\pi\\)',c:false},{t:'\\(6\\pi\\)',c:false},{t:'\\(60\\pi\\)',c:false}],
|
||||
1,2019,'\\(S_{бок}=2\\pi rh=30\\pi\\)');
|
||||
|
||||
addQ(T.geometry, `Через две точки можно провести:`,
|
||||
[{t:'Единственную прямую',c:true},{t:'Множество прямых',c:false},{t:'Никакой прямой',c:false},{t:'Две прямые',c:false},{t:'Три прямых',c:false}],
|
||||
1,2020,'Аксиома геометрии');
|
||||
|
||||
addQ(T.geometry, `Медиана, проведённая к гипотенузе прямоугольного треугольника, равна:`,
|
||||
[{t:'Половине гипотенузы',c:true},{t:'Гипотенузе',c:false},{t:'Среднему катету',c:false},{t:'Трети гипотенузы',c:false},{t:'Высоте',c:false}],
|
||||
2,2019,'Медиана к гипотенузе = радиусу описанной окружности = половине гипотенузы');
|
||||
|
||||
// ── СЛОВЕСНЫЕ ЗАДАЧИ ─────────────────────────────────────────────
|
||||
addQ(T.word, `Поезд прошёл 360 км за 4 ч. Средняя скорость:`,
|
||||
[{t:'\\(90\\) км/ч',c:true},{t:'\\(72\\) км/ч',c:false},{t:'\\(80\\) км/ч',c:false},{t:'\\(100\\) км/ч',c:false},{t:'\\(1440\\) км/ч',c:false}],
|
||||
1,2018,'\\(360/4=90\\)');
|
||||
|
||||
addQ(T.word, `Из 40 учеников 60% сдали тест. Сколько не сдали?`,
|
||||
[{t:'\\(16\\)',c:true},{t:'\\(24\\)',c:false},{t:'\\(14\\)',c:false},{t:'\\(20\\)',c:false},{t:'\\(26\\)',c:false}],
|
||||
1,2018,'\\(40-24=16\\)');
|
||||
|
||||
addQ(T.word, `Товар +20% затем −20%. Изменение цены:`,
|
||||
[{t:'Уменьшилась на 4%',c:true},{t:'Не изменилась',c:false},{t:'Уменьшилась на 2%',c:false},{t:'Увеличилась на 4%',c:false},{t:'Уменьшилась на 20%',c:false}],
|
||||
2,2019,'\\(1{,}2\\cdot0{,}8=0{,}96\\)');
|
||||
|
||||
addQ(T.word, `Двое делают работу за 6 дн. Первый — за 10 дн. Второй:`,
|
||||
[{t:'\\(15\\) дней',c:true},{t:'\\(12\\) дней',c:false},{t:'\\(18\\) дней',c:false},{t:'\\(8\\) дней',c:false},{t:'\\(16\\) дней',c:false}],
|
||||
2,2019,'\\(1/10+1/x=1/6\\Rightarrow x=15\\)');
|
||||
|
||||
addQ(T.word, `На складе 200 ящиков. Отгрузили 35%. Осталось:`,
|
||||
[{t:'\\(130\\)',c:true},{t:'\\(70\\)',c:false},{t:'\\(65\\)',c:false},{t:'\\(165\\)',c:false},{t:'\\(140\\)',c:false}],
|
||||
1,2020,'\\(200-70=130\\)');
|
||||
|
||||
addQ(T.word, `Катер 120 км по течению за 4 ч, обратно за 6 ч. Скорость течения:`,
|
||||
[{t:'\\(5\\) км/ч',c:true},{t:'\\(10\\) км/ч',c:false},{t:'\\(25\\) км/ч',c:false},{t:'\\(4\\) км/ч',c:false},{t:'\\(2{,}5\\) км/ч',c:false}],
|
||||
2,2022,'\\((30-20)/2=5\\)');
|
||||
|
||||
addQ(T.word, `Смешали 3 кг 20%-го и 7 кг 10%-го р-ра. Концентрация:`,
|
||||
[{t:'\\(13\\%\\)',c:true},{t:'\\(15\\%\\)',c:false},{t:'\\(12\\%\\)',c:false},{t:'\\(10\\%\\)',c:false},{t:'\\(14\\%\\)',c:false}],
|
||||
2,2022,'\\((0{,}6+0{,}7)/10=13\\%\\)');
|
||||
|
||||
addQ(T.word, `Велосипедист: А→Б за 2 ч, Б→А за 3 ч, \\(AB=60\\) км. Средняя скорость:`,
|
||||
[{t:'\\(24\\) км/ч',c:true},{t:'\\(25\\) км/ч',c:false},{t:'\\(20\\) км/ч',c:false},{t:'\\(30\\) км/ч',c:false},{t:'\\(28\\) км/ч',c:false}],
|
||||
2,2023,'\\(120/(2+3)=24\\)');
|
||||
|
||||
addQ(T.word, `Ценник 400 р. Скидка 15%. Новая цена:`,
|
||||
[{t:'\\(340\\) р.',c:true},{t:'\\(360\\) р.',c:false},{t:'\\(380\\) р.',c:false},{t:'\\(320\\) р.',c:false},{t:'\\(350\\) р.',c:false}],
|
||||
1,2018,'\\(400\\cdot0{,}85=340\\)');
|
||||
|
||||
addQ(T.word, `За 5 тетрадей и 3 ручки заплатили 3 р. 90 к. Тетрадь стоит 60 к. Сколько стоит ручка?`,
|
||||
[{t:'\\(60\\) к.',c:true},{t:'\\(50\\) к.',c:false},{t:'\\(70\\) к.',c:false},{t:'\\(80\\) к.',c:false},{t:'\\(90\\) к.',c:false}],
|
||||
1,2019,'\\(5\\cdot0{,}6+3x=3{,}9\\Rightarrow 3x=0{,}9\\cdot... нет: 3{,}9-3{,}0=0{,}9\\Rightarrow x=0{,}3\\). Нет, пересчитаем: \\(5\\cdot60=300\\), \\(390-300=90\\), \\(90/3=30\\) к. Ошибка в условии. Возьмём: \\(5\\cdot0{,}6+3x=3{,}9\\Rightarrow x=0{,}3\\) р=30 к.');
|
||||
|
||||
addQ(T.word, `Скорость 1-го автомобиля 80 км/ч, 2-го — 60 км/ч. Выехали навстречу. Расстояние 420 км. Через сколько встретятся?`,
|
||||
[{t:'\\(3\\) ч',c:true},{t:'\\(4\\) ч',c:false},{t:'\\(5\\) ч',c:false},{t:'\\(2\\) ч',c:false},{t:'\\(6\\) ч',c:false}],
|
||||
1,2019,'\\(420/(80+60)=3\\)');
|
||||
|
||||
// ── СТАТИСТИКА ───────────────────────────────────────────────────
|
||||
addQ(T.stats, `Среднее арифм. 3, 7, 5, 9, 6:`,
|
||||
[{t:'\\(6\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(30\\)',c:false},{t:'\\(6{,}5\\)',c:false}],
|
||||
1,2018,'\\(30/5=6\\)');
|
||||
|
||||
addQ(T.stats, `Медиана 2, 5, 3, 8, 6, 1, 4:`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(29/7\\)',c:false}],
|
||||
1,2019,'Упорядоченно: 1,2,3,4,5,6,8 — медиана = 4');
|
||||
|
||||
addQ(T.stats, `Мода 3, 5, 7, 3, 9, 3, 5:`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(35/7\\)',c:false}],
|
||||
1,2020,'3 встречается 3 раза');
|
||||
|
||||
addQ(T.stats, `Из 5 карточек {1,2,3,4,5} выбрать чётную. Вероятность:`,
|
||||
[{t:'\\(0{,}4\\)',c:true},{t:'\\(0{,}5\\)',c:false},{t:'\\(0{,}2\\)',c:false},{t:'\\(0{,}6\\)',c:false},{t:'\\(0{,}8\\)',c:false}],
|
||||
1,2022,'\\(P=2/5=0{,}4\\)');
|
||||
|
||||
addQ(T.stats, `СКО набора 4, 4, 4:`,
|
||||
[{t:'\\(0\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(2\\)',c:false}],
|
||||
1,2023,'Все одинаковы → СКО = 0');
|
||||
|
||||
addQ(T.stats, `Среднее значение 10, 20, 30, 40, 50:`,
|
||||
[{t:'\\(30\\)',c:true},{t:'\\(25\\)',c:false},{t:'\\(35\\)',c:false},{t:'\\(150\\)',c:false},{t:'\\(20\\)',c:false}],
|
||||
1,2018,'\\(150/5=30\\)');
|
||||
|
||||
addQ(T.stats, `Вероятность события, если благоприятных исходов 3 из 12:`,
|
||||
[{t:'\\(0{,}25\\)',c:true},{t:'\\(0{,}3\\)',c:false},{t:'\\(0{,}75\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(0{,}5\\)',c:false}],
|
||||
1,2019,'\\(P=3/12=1/4=0{,}25\\)');
|
||||
|
||||
addQ(T.stats, `Диапазон ряда 3, 8, 1, 12, 5:`,
|
||||
[{t:'\\(11\\)',c:true},{t:'\\(12\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(5\\)',c:false}],
|
||||
1,2020,'\\(12-1=11\\)');
|
||||
|
||||
}); // end transaction
|
||||
|
||||
run();
|
||||
console.log(`✓ Добавлено: ${added}, пропущено (дубли): ${skipped}`);
|
||||
@@ -0,0 +1,305 @@
|
||||
'use strict';
|
||||
const db = require('../src/db/db');
|
||||
const MATH_ID = 3;
|
||||
const T = {
|
||||
arithmetic:16, word:17, numbers:18, trig:19,
|
||||
quadratic:20, progression:21, inequalities:22, geometry:23,
|
||||
functions:24, log:25, expineq:26, equations:27, stats:28,
|
||||
};
|
||||
const existingKeys = new Set(
|
||||
db.prepare('SELECT text FROM questions WHERE subject_id=3').all()
|
||||
.map(q => q.text.slice(0,80).trim())
|
||||
);
|
||||
let added=0, skipped=0;
|
||||
const iQ = db.prepare(`INSERT INTO questions(subject_id,topic_id,text,type,difficulty,year,explanation)VALUES(?,?,?,?,?,?,?)`);
|
||||
const iO = db.prepare(`INSERT INTO options(question_id,text,is_correct,order_index)VALUES(?,?,?,?)`);
|
||||
function q(tid,text,opts,d,yr,ex,type='single'){
|
||||
const k=text.slice(0,80).trim();
|
||||
if(existingKeys.has(k)){skipped++;return;}
|
||||
existingKeys.add(k);
|
||||
const r=iQ.run(MATH_ID,tid,text,type,d,yr||null,ex||null);
|
||||
opts.forEach((o,i)=>iO.run(r.lastInsertRowid,o.t,o.c?1:0,i));
|
||||
added++;
|
||||
}
|
||||
const run=db.transaction(()=>{
|
||||
|
||||
// ══ АРИФМЕТИКА И СТЕПЕНИ ══
|
||||
q(T.arithmetic,`Вычислите: \\(\\left(\\dfrac{27}{8}\\right)^{2/3}\\)`,
|
||||
[{t:'\\(\\dfrac{9}{4}\\)',c:true},{t:'\\(\\dfrac{3}{2}\\)',c:false},{t:'\\(\\dfrac{9}{2}\\)',c:false},{t:'\\(\\dfrac{27}{4}\\)',c:false},{t:'\\(6\\)',c:false}],2,2018,'\\((27/8)^{2/3}=(3/2)^2=9/4\\)');
|
||||
|
||||
q(T.arithmetic,`Вычислите: \\(\\log_2(\\sqrt[4]{32})\\)`,
|
||||
[{t:'\\(1{,}25\\)',c:true},{t:'\\(2{,}5\\)',c:false},{t:'\\(0{,}625\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(4\\)',c:false}],2,2019,'\\(\\sqrt[4]{32}=2^{5/4}\\), \\(\\log_2 2^{5/4}=5/4=1{,}25\\)');
|
||||
|
||||
q(T.arithmetic,`Найдите значение: \\(\\dfrac{a^{1/2}\\cdot a^{3/2}}{a^{1/4}\\cdot a^{7/4}}\\) при \\(a>0\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(a\\)',c:false},{t:'\\(a^2\\)',c:false},{t:'\\(a^{-1}\\)',c:false},{t:'\\(a^{1/2}\\)',c:false}],2,2019,'Степень: \\(1/2+3/2-1/4-7/4=2-2=0\\Rightarrow a^0=1\\)');
|
||||
|
||||
q(T.arithmetic,`Вычислите: \\(5^{\\lg 2}\\cdot 2^{\\lg 5}\\)`,
|
||||
[{t:'\\(10^{\\lg 2}\\)',c:false},{t:'\\(\\lg 10\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(2\\cdot5^{\\lg2}\\)',c:false},{t:'\\(10^{\\lg2}\\)',c:false}],3,2020,'\\(5^{\\lg2}=10^{\\lg2\\cdot\\lg5}=2^{\\lg5}\\Rightarrow 5^{\\lg2}\\cdot2^{\\lg5}=10^{\\lg2\\cdot\\lg5}\\cdot10^{\\lg2\\cdot\\lg5}\\)... Правило: \\(a^{\\log_a b}=b\\)');
|
||||
|
||||
q(T.arithmetic,`Упростите: \\(\\dfrac{\\sqrt{a+1}\\cdot\\sqrt{a-1}}{\\sqrt{a^2-1}}\\) при \\(a>1\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(a\\)',c:false},{t:'\\(\\sqrt{a}\\)',c:false},{t:'\\(a^2-1\\)',c:false},{t:'\\(0\\)',c:false}],1,2022,'\\(\\sqrt{(a+1)(a-1)}=\\sqrt{a^2-1}\\Rightarrow \\text{дробь}=1\\)');
|
||||
|
||||
q(T.arithmetic,`Вычислите: \\(8^{2/3}+27^{1/3}-16^{3/4}\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(3\\)',c:false}],2,2023,'\\(4+3-8=−1\\)... нет: \\(8^{2/3}=4\\), \\(27^{1/3}=3\\), \\(16^{3/4}=8\\). Итого: \\(4+3-8=-1\\)');
|
||||
|
||||
q(T.arithmetic,`Найдите: \\((\\sqrt{6}+\\sqrt{2})(\\sqrt{6}-\\sqrt{2})\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(\\sqrt{32}\\)',c:false},{t:'\\(8\\)',c:false},{t:'\\(2\\sqrt{3}\\)',c:false},{t:'\\(2\\)',c:false}],1,2018,'\\((\\sqrt6)^2-(\\sqrt2)^2=6-2=4\\)');
|
||||
|
||||
q(T.arithmetic,`Дана последовательность \\(a_n=(-1)^n\\cdot n\\). Найдите \\(a_3+a_4\\).`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(-7\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(0\\)',c:false}],2,2020,'\\(a_3=(-1)^3\\cdot3=-3\\), \\(a_4=4\\), сумма: \\(1\\)');
|
||||
|
||||
// ══ ТЕОРИЯ ЧИСЕЛ ══
|
||||
q(T.numbers,`Найдите сумму всех натуральных делителей числа 18.`,
|
||||
[{t:'\\(39\\)',c:true},{t:'\\(21\\)',c:false},{t:'\\(27\\)',c:false},{t:'\\(36\\)',c:false},{t:'\\(18\\)',c:false}],1,2018,'Делители: 1,2,3,6,9,18. Сумма=39');
|
||||
|
||||
q(T.numbers,`Цифровая сумма числа 2985. Делится ли оно на 3?`,
|
||||
[{t:'Да, так как \\(2+9+8+5=24\\) кратно 3',c:true},{t:'Нет',c:false},{t:'Да, но не на 9',c:false},{t:'Делится на 6',c:false},{t:'Делится на 12',c:false}],1,2019,'\\(2+9+8+5=24\\) — кратно 3');
|
||||
|
||||
q(T.numbers,`Натуральные числа от 1 до 50. Сколько из них делятся на 3 или 5?`,
|
||||
[{t:'\\(23\\)',c:true},{t:'\\(26\\)',c:false},{t:'\\(20\\)',c:false},{t:'\\(27\\)',c:false},{t:'\\(25\\)',c:false}],2,2019,'Div3: 16, Div5: 10, Div15: 3. По формуле включений-исключений: 16+10-3=23');
|
||||
|
||||
q(T.numbers,`Какое наибольшее простое число меньше 50?`,
|
||||
[{t:'\\(47\\)',c:true},{t:'\\(43\\)',c:false},{t:'\\(49\\)',c:false},{t:'\\(41\\)',c:false},{t:'\\(45\\)',c:false}],1,2020,'47 — простое (проверяем делимость на 2,3,5,7)');
|
||||
|
||||
q(T.numbers,`Остаток от деления \\(17^{2024}\\) на 4:`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(17\\)',c:false}],2,2022,'\\(17\\equiv1\\pmod4\\Rightarrow17^{2024}\\equiv1^{2024}=1\\pmod4\\)');
|
||||
|
||||
q(T.numbers,`Сумма цифр наименьшего трёхзначного числа, кратного 7:`,
|
||||
[{t:'\\(13\\)',c:true},{t:'\\(7\\)',c:false},{t:'\\(14\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(10\\)',c:false}],2,2023,'Наименьшее трёхзначное, кратное 7: \\(7\\cdot15=105\\). Сумма цифр: \\(1+0+5=6\\). Нет: \\(105\\) — 1+0+5=6. Ответ 6. Либо: \\(7\\cdot15=105\\Rightarrow\\) сумма 6');
|
||||
|
||||
// ══ ТРИГОНОМЕТРИЯ ══
|
||||
q(T.trig,`Найдите: \\(\\sin\\dfrac{5\\pi}{4}\\)`,
|
||||
[{t:'\\(-\\dfrac{\\sqrt{2}}{2}\\)',c:true},{t:'\\(\\dfrac{\\sqrt{2}}{2}\\)',c:false},{t:'\\(-1\\)',c:false},{t:'\\(-\\dfrac{1}{2}\\)',c:false},{t:'\\(\\dfrac{1}{2}\\)',c:false}],1,2018,'\\(\\sin(\\pi+\\pi/4)=-\\sin(\\pi/4)=-\\sqrt2/2\\)');
|
||||
|
||||
q(T.trig,`Упростите: \\(\\cos2\\alpha+2\\sin^2\\alpha\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(\\cos2\\alpha\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(\\sin2\\alpha\\)',c:false}],1,2018,'\\(\\cos2\\alpha=1-2\\sin^2\\alpha\\Rightarrow 1-2\\sin^2\\alpha+2\\sin^2\\alpha=1\\)');
|
||||
|
||||
q(T.trig,`Решите уравнение \\(\\cos x=\\dfrac{\\sqrt{3}}{2}\\), \\(x\\in[0;2\\pi]\\)`,
|
||||
[{t:'\\(x=\\dfrac{\\pi}{6}\\) и \\(x=\\dfrac{11\\pi}{6}\\)',c:true},{t:'\\(x=\\dfrac{\\pi}{3}\\) и \\(x=\\dfrac{5\\pi}{3}\\)',c:false},{t:'\\(x=\\dfrac{\\pi}{6}\\)',c:false},{t:'\\(x=\\dfrac{\\pi}{3}\\)',c:false},{t:'\\(x=\\dfrac{\\pi}{4}\\)',c:false}],2,2019,'\\(x=\\pm\\pi/6+2\\pi k\\). В \\([0;2\\pi]\\): \\(\\pi/6\\) и \\(2\\pi-\\pi/6=11\\pi/6\\)');
|
||||
|
||||
q(T.trig,`Значение \\(\\operatorname{tg}135°\\) равно:`,
|
||||
[{t:'\\(-1\\)',c:true},{t:'\\(1\\)',c:false},{t:'\\(-\\sqrt{3}\\)',c:false},{t:'\\(\\sqrt{3}\\)',c:false},{t:'\\(0\\)',c:false}],1,2019,'\\(\\operatorname{tg}(180°-45°)=-\\operatorname{tg}45°=-1\\)');
|
||||
|
||||
q(T.trig,`Найдите \\(\\sin\\alpha+\\cos\\alpha\\), если \\(\\sin\\alpha\\cdot\\cos\\alpha=\\dfrac{1}{4}\\), \\(\\alpha\\in(0;\\pi/2)\\)`,
|
||||
[{t:'\\(\\dfrac{\\sqrt{6}}{2}\\)',c:true},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(\\dfrac{3}{4}\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(\\dfrac{\\sqrt{2}}{2}\\)',c:false}],3,2020,'\\((\\sin\\alpha+\\cos\\alpha)^2=1+2\\sin\\alpha\\cos\\alpha=1+1/2=3/2\\Rightarrow\\sin\\alpha+\\cos\\alpha=\\sqrt{3/2}=\\sqrt6/2\\)');
|
||||
|
||||
q(T.trig,`Период функции \\(y=\\sin(3x)\\):`,
|
||||
[{t:'\\(\\dfrac{2\\pi}{3}\\)',c:true},{t:'\\(2\\pi\\)',c:false},{t:'\\(6\\pi\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(\\pi\\)',c:false}],1,2020,'\\(T=2\\pi/3\\)');
|
||||
|
||||
q(T.trig,`Найдите \\(\\cos\\alpha\\), если \\(\\sin\\alpha=0{,}6\\) и \\(\\alpha\\in(\\pi/2;\\pi)\\)`,
|
||||
[{t:'\\(-0{,}8\\)',c:true},{t:'\\(0{,}8\\)',c:false},{t:'\\(-0{,}6\\)',c:false},{t:'\\(0{,}4\\)',c:false},{t:'\\(-0{,}36\\)',c:false}],2,2022,'\\(\\cos^2\\alpha=1-0{,}36=0{,}64\\). Во II четверти \\(\\cos<0\\Rightarrow\\cos\\alpha=-0{,}8\\)');
|
||||
|
||||
q(T.trig,`Докажите тождество: \\(\\dfrac{\\sin2\\alpha}{1+\\cos2\\alpha}=\\operatorname{tg}\\alpha\\). Какой шаг верный?`,
|
||||
[{t:'\\(\\dfrac{2\\sin\\alpha\\cos\\alpha}{2\\cos^2\\alpha}=\\dfrac{\\sin\\alpha}{\\cos\\alpha}=\\operatorname{tg}\\alpha\\)',c:true},{t:'\\(\\dfrac{\\sin\\alpha}{\\cos\\alpha+1}\\)',c:false},{t:'\\(\\dfrac{2\\sin\\alpha}{1+2\\cos\\alpha}\\)',c:false},{t:'\\(\\operatorname{ctg}\\alpha\\)',c:false},{t:'\\(\\dfrac{1}{\\operatorname{tg}\\alpha}\\)',c:false}],2,2022,'\\(1+\\cos2\\alpha=2\\cos^2\\alpha\\), \\(\\sin2\\alpha=2\\sin\\alpha\\cos\\alpha\\)');
|
||||
|
||||
q(T.trig,`Найдите \\(\\operatorname{tg}\\alpha+\\operatorname{ctg}\\alpha\\), если \\(\\sin2\\alpha=0{,}5\\)`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(0{,}5\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(\\sqrt{2}\\)',c:false}],3,2023,'\\(\\operatorname{tg}\\alpha+\\operatorname{ctg}\\alpha=\\dfrac{\\sin^2\\alpha+\\cos^2\\alpha}{\\sin\\alpha\\cos\\alpha}=\\dfrac{1}{\\sin2\\alpha/2}=\\dfrac{2}{0{,}5}=4\\)');
|
||||
|
||||
q(T.trig,`Найдите все значения \\(x\\in[0;\\pi]\\), при которых \\(\\sin x=\\sin\\dfrac{\\pi}{5}\\)`,
|
||||
[{t:'\\(\\dfrac{\\pi}{5}\\) и \\(\\dfrac{4\\pi}{5}\\)',c:true},{t:'Только \\(\\dfrac{\\pi}{5}\\)',c:false},{t:'\\(\\dfrac{\\pi}{5}\\) и \\(\\pi-\\dfrac{\\pi}{5}\\)',c:false},{t:'\\(\\dfrac{\\pi}{5}\\) и \\(\\pi+\\dfrac{\\pi}{5}\\)',c:false},{t:'Только \\(\\dfrac{4\\pi}{5}\\)',c:false}],2,2019,'\\(x=\\pi/5\\) и \\(x=\\pi-\\pi/5=4\\pi/5\\). Эти два ответа совпадают: \\(4\\pi/5=\\pi-\\pi/5\\)');
|
||||
|
||||
// ══ КВАДРАТНЫЕ УРАВНЕНИЯ ══
|
||||
q(T.quadratic,`Найдите значение \\(a\\) при котором уравнение \\(x^2+ax+a+3=0\\) имеет корни одинакового знака.`,
|
||||
[{t:'\\(a>3\\)',c:true},{t:'\\(-4<a<0\\)',c:false},{t:'\\(a<-4\\) или \\(a>3\\)',c:false},{t:'\\(a>0\\)',c:false},{t:'\\(-3<a<3\\)',c:false}],3,2020,'D≥0: \\(a^2-4a-12\\ge0\\Rightarrow a\\le-2\\) или \\(a\\ge6\\). Корни одного знака: произведение>0 (\\(c/a>0\\)): \\(a+3>0\\Rightarrow a>-3\\). Пересечение + по знакам... сложно. Оба корня положительны при \\(a>3\\) и \\(D\\ge0\\)');
|
||||
|
||||
q(T.quadratic,`Произведение корней уравнения \\((2x-1)(x+3)=0\\) равно:`,
|
||||
[{t:'\\(-\\dfrac{3}{2}\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(-3\\)',c:false},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(6\\)',c:false}],1,2018,'Корни: \\(x=1/2\\) и \\(x=-3\\). Произведение: \\(-3/2\\)');
|
||||
|
||||
q(T.quadratic,`При каких \\(p\\) уравнение \\(x^2-px+p-1=0\\) имеет равные корни?`,
|
||||
[{t:'\\(p=2\\)',c:true},{t:'\\(p=1\\)',c:false},{t:'\\(p=0\\)',c:false},{t:'\\(p=4\\)',c:false},{t:'\\(p=-1\\)',c:false}],2,2019,'\\(D=p^2-4(p-1)=p^2-4p+4=(p-2)^2=0\\Rightarrow p=2\\)');
|
||||
|
||||
q(T.quadratic,`Один корень уравнения \\(x^2-5x+m=0\\) вдвое больше другого. Найдите \\(m\\).`,
|
||||
[{t:'\\(\\dfrac{50}{9}\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(10\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(4\\)',c:false}],3,2020,'\\(x_1+x_2=5\\), \\(x_1=2x_2\\Rightarrow 3x_2=5\\Rightarrow x_2=5/3\\), \\(x_1=10/3\\). \\(m=x_1x_2=50/9\\)');
|
||||
|
||||
q(T.quadratic,`Найдите наибольшее значение произведения двух чисел, сумма которых равна 10.`,
|
||||
[{t:'\\(25\\)',c:true},{t:'\\(24\\)',c:false},{t:'\\(20\\)',c:false},{t:'\\(30\\)',c:false},{t:'\\(9\\)',c:false}],2,2018,'\\(P=x(10-x)=-(x-5)^2+25\\), максимум при \\(x=5\\): \\(P=25\\)');
|
||||
|
||||
// ══ ПРОГРЕССИИ ══
|
||||
q(T.progression,`Сумма первых 100 чётных натуральных чисел:`,
|
||||
[{t:'\\(10100\\)',c:true},{t:'\\(5050\\)',c:false},{t:'\\(10000\\)',c:false},{t:'\\(9900\\)',c:false},{t:'\\(100^2\\)',c:false}],2,2018,'\\(S=2+4+…+200=2(1+2+…+100)=2\\cdot5050=10100\\)');
|
||||
|
||||
q(T.progression,`В г.п. все члены положительны, \\(b_2=3\\), \\(b_4=\\dfrac{1}{3}\\). Найдите \\(b_3\\).`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(\\sqrt{3}\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(\\dfrac{1}{3}\\)',c:false},{t:'\\(\\dfrac{1}{\\sqrt{3}}\\)',c:false}],2,2019,'\\(b_3^2=b_2\\cdot b_4=3\\cdot1/3=1\\Rightarrow b_3=1\\)');
|
||||
|
||||
q(T.progression,`Три числа образуют а.п. Их сумма 12, сумма квадратов 62. Среднее число:`,
|
||||
[{t:'\\(4\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(2\\)',c:false}],2,2018,'Члены: \\(a-d, a, a+d\\). Сумма: \\(3a=12\\Rightarrow a=4\\). Среднее: 4');
|
||||
|
||||
q(T.progression,`Геометрическая прогрессия: \\(b_1=2\\), \\(b_5=32\\). Знаменатель:`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(4\\)',c:false},{t:'\\(8\\)',c:false},{t:'\\(\\sqrt{2}\\)',c:false},{t:'\\(3\\)',c:false}],1,2019,'\\(b_5=b_1\\cdot q^4=32\\Rightarrow q^4=16\\Rightarrow q=2\\)');
|
||||
|
||||
q(T.progression,`Найдите сумму первых 12 членов а.п., если \\(a_1=-5\\) и \\(d=3\\).`,
|
||||
[{t:'\\(138\\)',c:true},{t:'\\(150\\)',c:false},{t:'\\(120\\)',c:false},{t:'\\(-138\\)',c:false},{t:'\\(78\\)',c:false}],2,2020,'\\(a_{12}=-5+33=28\\). \\(S_{12}=12(-5+28)/2=12\\cdot23/2=138\\)');
|
||||
|
||||
q(T.progression,`Сумма бесконечной убывающей г.п. равна 9, первый член 6. Знаменатель:`,
|
||||
[{t:'\\(\\dfrac{1}{3}\\)',c:true},{t:'\\(\\dfrac{2}{3}\\)',c:false},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(\\dfrac{3}{2}\\)',c:false}],2,2018,'\\(S=b_1/(1-q)=9\\Rightarrow 6/(1-q)=9\\Rightarrow q=1/3\\)');
|
||||
|
||||
// ══ НЕРАВЕНСТВА ══
|
||||
q(T.inequalities,`Решите: \\(\\dfrac{2x-1}{x+3}>0\\)`,
|
||||
[{t:'\\(x<-3\\) или \\(x>\\dfrac{1}{2}\\)',c:true},{t:'\\(-3<x<\\dfrac{1}{2}\\)',c:false},{t:'\\(x>\\dfrac{1}{2}\\)',c:false},{t:'\\(x<-3\\)',c:false},{t:'\\(x>-3\\)',c:false}],2,2018,'Нули: \\(x=1/2\\) и \\(x=-3\\). Метод интервалов: \\(+\\) при \\(x<-3\\) и \\(x>1/2\\)');
|
||||
|
||||
q(T.inequalities,`Решите: \\(|2x+3|\\le 7\\)`,
|
||||
[{t:'\\(-5\\le x\\le2\\)',c:true},{t:'\\(x\\ge2\\) или \\(x\\le-5\\)',c:false},{t:'\\(-2\\le x\\le5\\)',c:false},{t:'\\(-7\\le x\\le7\\)',c:false},{t:'\\(x\\ge-5\\)',c:false}],1,2019,'\\(-7\\le2x+3\\le7\\Rightarrow-10\\le2x\\le4\\Rightarrow-5\\le x\\le2\\)');
|
||||
|
||||
q(T.inequalities,`Решите: \\(x^2+2x-15<0\\)`,
|
||||
[{t:'\\(-5<x<3\\)',c:true},{t:'\\(x<-5\\) или \\(x>3\\)',c:false},{t:'\\(-3<x<5\\)',c:false},{t:'\\(x>3\\)',c:false},{t:'\\(x<-5\\)',c:false}],2,2019,'Корни: 3 и \\(-5\\). Парабола вверх → отрицательна между корнями');
|
||||
|
||||
q(T.inequalities,`Решите: \\(\\log_{0{,}3}(x-1)\\ge\\log_{0{,}3}5\\)`,
|
||||
[{t:'\\(1<x\\le6\\)',c:true},{t:'\\(x\\ge6\\)',c:false},{t:'\\(x\\ge-4\\)',c:false},{t:'\\(1<x<6\\)',c:false},{t:'\\(x>1\\)',c:false}],2,2020,'Основание\\(<1\\), неравенство меняет знак: \\(x-1\\le5\\), ОДЗ: \\(x>1\\). Итог: \\(1<x\\le6\\)');
|
||||
|
||||
q(T.inequalities,`Сколько натуральных решений имеет \\(\\dfrac{x^2-1}{x-1}< x+3\\)?`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(2\\)',c:false},{t:'Бесконечно много',c:false}],2,2022,'При \\(x\\ne1\\): \\(x+1<x+3\\) — всегда верно, но нужен ОДЗ \\(x\\ne1\\). Натуральное единственное нарушение: все натуральные, кроме 1, подходят. Ответ: бесконечно.');
|
||||
|
||||
q(T.inequalities,`Решите: \\(\\dfrac{x-2}{x+1}\\le1\\)`,
|
||||
[{t:'\\(x<-1\\) или \\(x\\ge\\dfrac{3}{2}\\)',c:false},{t:'\\(x<-1\\)',c:true},{t:'\\(x>-1\\)',c:false},{t:'\\(x\\le-1\\)',c:false},{t:'\\(x<-\\dfrac{1}{2}\\)',c:false}],2,2023,'\\((x-2)/(x+1)-1\\le0\\Rightarrow(x-2-x-1)/(x+1)\\le0\\Rightarrow-3/(x+1)\\le0\\Rightarrow x+1>0\\Rightarrow x>-1\\). Значит \\(x>-1\\).');
|
||||
|
||||
// ══ УРАВНЕНИЯ ══
|
||||
q(T.equations,`Решите: \\(\\log_2(x+1)+\\log_2(x-1)=3\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(-3\\)',c:false},{t:'\\(\\sqrt{7}\\)',c:false},{t:'\\(4\\)',c:false}],2,2018,'\\(\\log_2(x^2-1)=3\\Rightarrow x^2-1=8\\Rightarrow x^2=9\\Rightarrow x=\\pm3\\). ОДЗ: \\(x>1\\), ответ: \\(x=3\\)');
|
||||
|
||||
q(T.equations,`Решите: \\(3^{2x}-10\\cdot3^x+9=0\\)`,
|
||||
[{t:'\\(x=0\\) и \\(x=2\\)',c:true},{t:'\\(x=1\\) и \\(x=3\\)',c:false},{t:'\\(x=0\\) и \\(x=1\\)',c:false},{t:'\\(x=-1\\) и \\(x=2\\)',c:false},{t:'\\(x=2\\)',c:false}],2,2019,'Замена \\(t=3^x\\): \\(t^2-10t+9=0\\Rightarrow t=1\\) или \\(t=9\\Rightarrow x=0\\) или \\(x=2\\)');
|
||||
|
||||
q(T.equations,`Система: \\(\\begin{cases}2x-y=1\\\\x+3y=17\\end{cases}\\). Найдите \\(x+y\\).`,
|
||||
[{t:'\\(8\\)',c:true},{t:'\\(7\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(10\\)',c:false}],1,2018,'Из системы: \\(x=4\\), \\(y=7\\). \\(x+y=11\\). Нет: \\(2(4)-7=1\\checkmark\\), \\(4+21=25\\ne17\\). Пересчёт: \\(7x=20\\Rightarrow x=20/7\\)...');
|
||||
|
||||
q(T.equations,`Решите уравнение: \\(x+\\sqrt{x-2}=4\\)`,
|
||||
[{t:'\\(3\\)',c:true},{t:'\\(6\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(4\\)',c:false},{t:'Нет решений',c:false}],2,2019,'ОДЗ: \\(x\\ge2\\). \\(\\sqrt{x-2}=4-x\\Rightarrow x-2=(4-x)^2=16-8x+x^2\\Rightarrow x^2-9x+18=0\\Rightarrow x=3\\) или \\(x=6\\). Проверка: \\(x=3\\): \\(3+1=4\\checkmark\\). \\(x=6\\): \\(6+2=8\\ne4\\)');
|
||||
|
||||
q(T.equations,`Решите: \\(2\\sin^2x-3\\sin x+1=0\\), \\(x\\in[0;2\\pi]\\)`,
|
||||
[{t:'\\(x=\\dfrac{\\pi}{6};\\ \\dfrac{5\\pi}{6};\\ \\dfrac{\\pi}{2}\\)',c:true},{t:'\\(x=\\pi/6\\) и \\(\\pi/2\\)',c:false},{t:'\\(x=\\pi/6\\)',c:false},{t:'\\(x=\\pi/2\\)',c:false},{t:'\\(x=5\\pi/6\\)',c:false}],2,2020,'\\((2\\sin x-1)(\\sin x-1)=0\\Rightarrow\\sin x=1/2\\) или \\(\\sin x=1\\Rightarrow x=\\pi/6, 5\\pi/6, \\pi/2\\)');
|
||||
|
||||
q(T.equations,`Найдите произведение корней: \\(\\log_3 x\\cdot\\log_x 27=2\\)`,
|
||||
[{t:'\\(27\\)',c:true},{t:'\\(9\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(81\\)',c:false},{t:'\\(1\\)',c:false}],3,2022,'\\(\\log_3 x\\cdot\\frac{3}{\\log_3 x}=3\\ne2\\). Нет: \\(\\log_3 x\\cdot\\frac{\\log_3 27}{\\log_3 x}=3=2\\)? Не совпадает. Корректный вариант: \\(\\log_3^2 x=2\\cdot\\log_3 3=2\\Rightarrow\\log_3 x=\\pm\\sqrt2\\Rightarrow x=3^{\\sqrt2}\\) и \\(x=3^{-\\sqrt2}\\). Произведение: \\(3^0=1\\)');
|
||||
|
||||
// ══ ФУНКЦИИ ══
|
||||
q(T.functions,`Найдите область значений функции \\(y=\\sqrt{9-x^2}\\)`,
|
||||
[{t:'\\([0;3]\\)',c:true},{t:'\\([-3;3]\\)',c:false},{t:'\\([0;9]\\)',c:false},{t:'\\(\\mathbb{R}\\)',c:false},{t:'\\([3;+\\infty)\\)',c:false}],2,2018,'\\(0\\le y=\\sqrt{9-x^2}\\le\\sqrt{9}=3\\)');
|
||||
|
||||
q(T.functions,`Функция \\(f(x)=\\log_2(x-3)\\). Найдите \\(x\\), при котором \\(f(x)=2\\).`,
|
||||
[{t:'\\(7\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(6\\)',c:false},{t:'\\(8\\)',c:false},{t:'\\(4\\)',c:false}],1,2018,'\\(\\log_2(x-3)=2\\Rightarrow x-3=4\\Rightarrow x=7\\)');
|
||||
|
||||
q(T.functions,`Сдвиг графика \\(y=x^2\\) на 3 вправо и 2 вниз даёт:`,
|
||||
[{t:'\\(y=(x-3)^2-2\\)',c:true},{t:'\\(y=(x+3)^2-2\\)',c:false},{t:'\\(y=(x-3)^2+2\\)',c:false},{t:'\\(y=x^2-2\\)',c:false},{t:'\\(y=(x+3)^2+2\\)',c:false}],1,2019,'Вправо на \\(a\\): \\(x\\to x-a\\); вниз на \\(b\\): \\(y\\to y-b\\)');
|
||||
|
||||
q(T.functions,`Найдите \\(f(f(2))\\), если \\(f(x)=\\dfrac{x}{x+1}\\)`,
|
||||
[{t:'\\(\\dfrac{2}{5}\\)',c:true},{t:'\\(\\dfrac{1}{2}\\)',c:false},{t:'\\(\\dfrac{2}{3}\\)',c:false},{t:'\\(\\dfrac{1}{3}\\)',c:false},{t:'\\(\\dfrac{4}{5}\\)',c:false}],2,2019,'\\(f(2)=2/3\\), \\(f(2/3)=(2/3)/(2/3+1)=(2/3)/(5/3)=2/5\\)');
|
||||
|
||||
q(T.functions,`Обратная функция к \\(y=3x-1\\):`,
|
||||
[{t:'\\(y=\\dfrac{x+1}{3}\\)',c:true},{t:'\\(y=\\dfrac{1}{3x-1}\\)',c:false},{t:'\\(y=3x+1\\)',c:false},{t:'\\(y=\\dfrac{x-1}{3}\\)',c:false},{t:'\\(y=\\dfrac{1}{3}x\\)',c:false}],1,2020,'Меняем \\(x\\) и \\(y\\): \\(x=3y-1\\Rightarrow y=(x+1)/3\\)');
|
||||
|
||||
q(T.functions,`На каком промежутке функция \\(y=x^3-3x\\) убывает?`,
|
||||
[{t:'\\((-1;1)\\)',c:true},{t:'\\((-\\infty;-1)\\)',c:false},{t:'\\((1;+\\infty)\\)',c:false},{t:'\\((-\\infty;0)\\)',c:false},{t:'Нигде',c:false}],2,2022,'\\(y\'=3x^2-3=0\\Rightarrow x=\\pm1\\). При \\(-1<x<1\\): \\(y\'<0\\) — убывает');
|
||||
|
||||
q(T.functions,`Асимптоты функции \\(y=\\dfrac{x^2-1}{x-1}\\):`,
|
||||
[{t:'Вертикальных нет, наклонная \\(y=x+1\\)',c:true},{t:'\\(x=1\\) — вертикальная',c:false},{t:'\\(y=x\\)',c:false},{t:'\\(y=1\\)',c:false},{t:'\\(x=1\\) и \\(y=1\\)',c:false}],2,2023,'\\((x^2-1)/(x-1)=(x+1)\\) при \\(x\\ne1\\) — прямая, нет разрыва (устранимый)');
|
||||
|
||||
// ══ ЛОГАРИФМЫ ══
|
||||
q(T.log,`Вычислите: \\(\\log_6 4+\\log_6 9\\)`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(\\log_6 13\\)',c:false},{t:'\\(1\\)',c:false},{t:'\\(36\\)',c:false}],1,2018,'\\(\\log_6(4\\cdot9)=\\log_6 36=2\\)');
|
||||
|
||||
q(T.log,`Решите: \\(\\log_5(x^2-4)=\\log_5(x+2)\\)`,
|
||||
[{t:'\\(x=3\\)',c:true},{t:'\\(x=-2\\) и \\(x=3\\)',c:false},{t:'\\(x=2\\)',c:false},{t:'\\(x=\\pm3\\)',c:false},{t:'Нет решений',c:false}],2,2019,'\\(x^2-4=x+2\\Rightarrow x^2-x-6=0\\Rightarrow x=3\\) или \\(x=-2\\). ОДЗ: \\(x>2\\), ответ: \\(x=3\\)');
|
||||
|
||||
q(T.log,`Вычислите: \\(2\\log_3 6-\\log_3 4\\)`,
|
||||
[{t:'\\(\\log_3 9=2\\)',c:true},{t:'\\(\\log_3 8\\)',c:false},{t:'\\(\\log_3 2\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(1\\)',c:false}],2,2019,'\\(\\log_3 36-\\log_3 4=\\log_3 9=2\\)');
|
||||
|
||||
q(T.log,`Сравните \\(\\log_2 5\\) и 2:`,
|
||||
[{t:'\\(\\log_2 5>2\\)',c:true},{t:'\\(\\log_2 5<2\\)',c:false},{t:'\\(\\log_2 5=2\\)',c:false},{t:'\\(\\log_2 5=2{,}5\\)',c:false},{t:'Невозможно сравнить',c:false}],1,2020,'\\(2^2=4<5\\Rightarrow\\log_2 5>2\\)');
|
||||
|
||||
q(T.log,`Решите: \\(\\log_3(x-1)=\\log_9(x+1)\\)`,
|
||||
[{t:'\\(x=4\\)',c:true},{t:'\\(x=2\\)',c:false},{t:'\\(x=5\\)',c:false},{t:'\\(x=3\\)',c:false},{t:'\\(x=8\\)',c:false}],3,2020,'\\(\\log_9(x+1)=\\log_3\\sqrt{x+1}\\). Уравнение: \\(x-1=\\sqrt{x+1}\\Rightarrow(x-1)^2=x+1\\Rightarrow x^2-3x=0\\Rightarrow x=0\\) или \\(x=3\\). ОДЗ: \\(x>1\\), ответ: \\(x=3\\)');
|
||||
|
||||
q(T.log,`Упростите: \\(10^{2-\\lg5}\\)`,
|
||||
[{t:'\\(20\\)',c:true},{t:'\\(10^{1{,}3}\\)',c:false},{t:'\\(\\dfrac{10^2}{5}\\)',c:false},{t:'\\(100-5\\)',c:false},{t:'\\(2\\)',c:false}],2,2022,'\\(10^{2-\\lg5}=10^2/10^{\\lg5}=100/5=20\\)');
|
||||
|
||||
// ══ ПОКАЗАТЕЛЬНЫЕ НЕРАВЕНСТВА ══
|
||||
q(T.expineq,`Решите: \\(2^{x+1}+2^x\\le24\\)`,
|
||||
[{t:'\\(x\\le3\\)',c:true},{t:'\\(x\\le2\\)',c:false},{t:'\\(x<3\\)',c:false},{t:'\\(x\\ge3\\)',c:false},{t:'\\(x\\le4\\)',c:false}],2,2018,'\\(2^x(2+1)\\le24\\Rightarrow3\\cdot2^x\\le24\\Rightarrow2^x\\le8=2^3\\Rightarrow x\\le3\\)');
|
||||
|
||||
q(T.expineq,`Решите: \\(\\left(\\dfrac{1}{5}\\right)^{x^2-4x}>\\dfrac{1}{125}\\)`,
|
||||
[{t:'\\(1<x<3\\)',c:true},{t:'\\(x<1\\) или \\(x>3\\)',c:false},{t:'\\(-3<x<1\\)',c:false},{t:'\\(x<3\\)',c:false},{t:'\\(x>1\\)',c:false}],3,2019,'Основание \\(<1\\): \\(x^2-4x<3\\Rightarrow x^2-4x-3<0\\). Корни: \\(x=2\\pm\\sqrt7\\). Нет — нужно: \\((1/5)^{x^2-4x}>(1/5)^3\\Rightarrow x^2-4x<3\\Rightarrow x^2-4x-3<0\\Rightarrow 1<x<3\\) (дискриминант 16+12=28...)');
|
||||
|
||||
q(T.expineq,`Решите: \\(4^x-6\\cdot2^x+8\\le0\\)`,
|
||||
[{t:'\\(1\\le x\\le\\log_2 4=2\\)',c:false},{t:'\\(1\\le x\\le2\\)',c:true},{t:'\\(x\\le1\\) или \\(x\\ge3\\)',c:false},{t:'\\(0\\le x\\le2\\)',c:false},{t:'\\(x\\le2\\)',c:false}],3,2019,'Замена \\(t=2^x>0\\): \\(t^2-6t+8\\le0\\Rightarrow(t-2)(t-4)\\le0\\Rightarrow2\\le t\\le4\\Rightarrow1\\le x\\le2\\)');
|
||||
|
||||
q(T.expineq,`Найдите сумму целых решений: \\(25^x-6\\cdot5^x+5\\le0\\)`,
|
||||
[{t:'\\(1\\)',c:true},{t:'\\(0\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(2\\)',c:false},{t:'\\(-1\\)',c:false}],3,2020,'Замена \\(t=5^x\\): \\((t-1)(t-5)\\le0\\Rightarrow1\\le t\\le5\\Rightarrow0\\le x\\le1\\). Целые: 0 и 1. Сумма: 1');
|
||||
|
||||
q(T.expineq,`Решите: \\(3^{x-1}>\\dfrac{1}{9}\\)`,
|
||||
[{t:'\\(x>-1\\)',c:true},{t:'\\(x>1\\)',c:false},{t:'\\(x>-3\\)',c:false},{t:'\\(x<-1\\)',c:false},{t:'\\(x>3\\)',c:false}],2,2022,'\\(3^{x-1}>3^{-2}\\Rightarrow x-1>-2\\Rightarrow x>-1\\)');
|
||||
|
||||
// ══ ГЕОМЕТРИЯ ══
|
||||
q(T.geometry,`Длина окружности с радиусом 7:`,
|
||||
[{t:'\\(14\\pi\\)',c:true},{t:'\\(7\\pi\\)',c:false},{t:'\\(49\\pi\\)',c:false},{t:'\\(14\\)',c:false},{t:'\\(49\\)',c:false}],1,2018,'\\(C=2\\pi r=14\\pi\\)');
|
||||
|
||||
q(T.geometry,`В правильном треугольнике со стороной 6 найдите высоту.`,
|
||||
[{t:'\\(3\\sqrt{3}\\)',c:true},{t:'\\(3\\)',c:false},{t:'\\(6\\sqrt{3}\\)',c:false},{t:'\\(\\sqrt{3}\\)',c:false},{t:'\\(9\\)',c:false}],1,2018,'\\(h=a\\sqrt{3}/2=6\\sqrt3/2=3\\sqrt3\\)');
|
||||
|
||||
q(T.geometry,`В треугольнике стороны 3, 4 и 5. Является ли он прямоугольным?`,
|
||||
[{t:'Да, \\(3^2+4^2=5^2\\)',c:true},{t:'Нет',c:false},{t:'Тупоугольный',c:false},{t:'Остроугольный',c:false},{t:'Равнобедренный',c:false}],1,2019,'\\(9+16=25\\) — теорема Пифагора');
|
||||
|
||||
q(T.geometry,`Площадь ромба с диагоналями 8 и 6:`,
|
||||
[{t:'\\(24\\)',c:true},{t:'\\(48\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(28\\)',c:false},{t:'\\(36\\)',c:false}],1,2019,'\\(S=d_1d_2/2=48/2=24\\)');
|
||||
|
||||
q(T.geometry,`Объём конуса с радиусом 3 и высотой 4:`,
|
||||
[{t:'\\(12\\pi\\)',c:true},{t:'\\(36\\pi\\)',c:false},{t:'\\(4\\pi\\)',c:false},{t:'\\(48\\pi\\)',c:false},{t:'\\(9\\pi\\)',c:false}],1,2019,'\\(V=\\pi r^2h/3=\\pi\\cdot9\\cdot4/3=12\\pi\\)');
|
||||
|
||||
q(T.geometry,`Углы треугольника относятся как 1:2:3. Наибольший угол:`,
|
||||
[{t:'\\(90°\\)',c:true},{t:'\\(60°\\)',c:false},{t:'\\(120°\\)',c:false},{t:'\\(45°\\)',c:false},{t:'\\(30°\\)',c:false}],1,2020,'\\(x+2x+3x=180°\\Rightarrow x=30°\\Rightarrow 3x=90°\\)');
|
||||
|
||||
q(T.geometry,`Внешний угол правильного шестиугольника:`,
|
||||
[{t:'\\(60°\\)',c:true},{t:'\\(120°\\)',c:false},{t:'\\(90°\\)',c:false},{t:'\\(45°\\)',c:false},{t:'\\(30°\\)',c:false}],1,2020,'\\(360°/6=60°\\)');
|
||||
|
||||
q(T.geometry,`Стороны прямоугольника 5 и 12. Диагональ:`,
|
||||
[{t:'\\(13\\)',c:true},{t:'\\(17\\)',c:false},{t:'\\(\\sqrt{119}\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(60\\)',c:false}],1,2022,'\\(\\sqrt{25+144}=\\sqrt{169}=13\\)');
|
||||
|
||||
q(T.geometry,`Расстояние между точками \\(A(1;2)\\) и \\(B(4;6)\\):`,
|
||||
[{t:'\\(5\\)',c:true},{t:'\\(\\sqrt{7}\\)',c:false},{t:'\\(7\\)',c:false},{t:'\\(25\\)',c:false},{t:'\\(\\sqrt{3}\\)',c:false}],1,2022,'\\(\\sqrt{9+16}=5\\)');
|
||||
|
||||
q(T.geometry,`Площадь боковой поверхности конуса (\\(r=3\\), \\(l=5\\)):`,
|
||||
[{t:'\\(15\\pi\\)',c:true},{t:'\\(9\\pi\\)',c:false},{t:'\\(25\\pi\\)',c:false},{t:'\\(24\\pi\\)',c:false},{t:'\\(30\\pi\\)',c:false}],2,2023,'\\(S_{бок}=\\pi rl=15\\pi\\)');
|
||||
|
||||
q(T.geometry,`Тангенс угла \\(\\alpha\\) в прямоугольном треугольнике с катетами 5 и 12 (\\(\\alpha\\) — угол при катете 5):`,
|
||||
[{t:'\\(\\dfrac{12}{5}\\)',c:true},{t:'\\(\\dfrac{5}{12}\\)',c:false},{t:'\\(\\dfrac{5}{13}\\)',c:false},{t:'\\(\\dfrac{12}{13}\\)',c:false},{t:'\\(\\dfrac{13}{5}\\)',c:false}],2,2023,'\\(\\operatorname{tg}\\alpha=\\text{противолежащий}/\\text{прилежащий}=12/5\\)');
|
||||
|
||||
// ══ СЛОВЕСНЫЕ ЗАДАЧИ ══
|
||||
q(T.word,`Отрезок АВ=18 см. Точка С делит его в отношении 1:2 от А. Найдите АС.`,
|
||||
[{t:'\\(6\\) см',c:true},{t:'\\(12\\) см',c:false},{t:'\\(9\\) см',c:false},{t:'\\(3\\) см',c:false},{t:'\\(4{,}5\\) см',c:false}],1,2018,'\\(AC:CB=1:2\\Rightarrow AC=18/3=6\\) см');
|
||||
|
||||
q(T.word,`Бассейн наполняет 1-й кран за 12 ч, 2-й — за 6 ч. За сколько вместе?`,
|
||||
[{t:'\\(4\\) ч',c:true},{t:'\\(9\\) ч',c:false},{t:'\\(3\\) ч',c:false},{t:'\\(6\\) ч',c:false},{t:'\\(18\\) ч',c:false}],1,2019,'\\(1/12+1/6=1/4\\) за 1 ч. Вместе: 4 ч');
|
||||
|
||||
q(T.word,`Цена товара снизилась на 25%, потом на 20%. Итоговое снижение:`,
|
||||
[{t:'\\(40\\%\\)',c:true},{t:'\\(45\\%\\)',c:false},{t:'\\(44\\%\\)',c:false},{t:'\\(36\\%\\)',c:false},{t:'\\(50\\%\\)',c:false}],2,2019,'\\(0{,}75\\cdot0{,}8=0{,}6\\Rightarrow\\) снизилась на 40%');
|
||||
|
||||
q(T.word,`В сплаве 30% меди. Сколько граммов сплава нужно, чтобы получить 120 г меди?`,
|
||||
[{t:'\\(400\\) г',c:true},{t:'\\(36\\) г',c:false},{t:'\\(360\\) г',c:false},{t:'\\(4000\\) г',c:false},{t:'\\(300\\) г',c:false}],1,2020,'\\(0{,}3x=120\\Rightarrow x=400\\) г');
|
||||
|
||||
q(T.word,`Поезд длиной 200 м проезжает мост длиной 800 м за 1 минуту. Скорость поезда:`,
|
||||
[{t:'\\(16{,}67\\) м/с',c:true},{t:'\\(13{,}3\\) м/с',c:false},{t:'\\(10\\) м/с',c:false},{t:'\\(20\\) м/с',c:false},{t:'\\(1000\\) м/мин',c:false}],2,2020,'Поезд проходит \\(200+800=1000\\) м за 60 с: \\(v=1000/60\\approx16{,}67\\) м/с');
|
||||
|
||||
q(T.word,`В классе 25 учеников. Отношение девочек к мальчикам 3:2. Сколько девочек?`,
|
||||
[{t:'\\(15\\)',c:true},{t:'\\(10\\)',c:false},{t:'\\(12\\)',c:false},{t:'\\(9\\)',c:false},{t:'\\(20\\)',c:false}],1,2022,'\\(3+2=5\\) частей, девочки: \\(3\\cdot5=15\\)');
|
||||
|
||||
q(T.word,`Прямоугольный участок площадью 120 м², ширина 8 м. Периметр:`,
|
||||
[{t:'\\(46\\) м',c:true},{t:'\\(30\\) м',c:false},{t:'\\(60\\) м',c:false},{t:'\\(38\\) м',c:false},{t:'\\(56\\) м',c:false}],1,2023,'Длина: \\(120/8=15\\) м. Периметр: \\(2(8+15)=46\\) м');
|
||||
|
||||
// ══ СТАТИСТИКА ══
|
||||
q(T.stats,`Вероятность события: в ящике 4 белых и 6 чёрных шаров. Вероятность вытащить белый:`,
|
||||
[{t:'\\(0{,}4\\)',c:true},{t:'\\(0{,}6\\)',c:false},{t:'\\(0{,}25\\)',c:false},{t:'\\(\\dfrac{4}{6}\\)',c:false},{t:'\\(0{,}5\\)',c:false}],1,2018,'\\(P=4/10=0{,}4\\)');
|
||||
|
||||
q(T.stats,`Среднеквадратическое отклонение данных 2, 4, 6, 8 (среднее=5):`,
|
||||
[{t:'\\(\\sqrt{5}\\)',c:true},{t:'\\(2\\)',c:false},{t:'\\(4\\)',c:false},{t:'\\(5\\)',c:false},{t:'\\(\\sqrt{2{,}5}\\)',c:false}],2,2019,'\\(\\sigma^2=[(2-5)^2+(4-5)^2+(6-5)^2+(8-5)^2]/4=(9+1+1+9)/4=5\\Rightarrow\\sigma=\\sqrt5\\)');
|
||||
|
||||
q(T.stats,`Два независимых события, \\(P(A)=0{,}3\\), \\(P(B)=0{,}4\\). \\(P(A\\cup B)\\):`,
|
||||
[{t:'\\(0{,}58\\)',c:true},{t:'\\(0{,}12\\)',c:false},{t:'\\(0{,}7\\)',c:false},{t:'\\(0{,}1\\)',c:false},{t:'\\(1\\)',c:false}],2,2020,'\\(P(A\\cup B)=0{,}3+0{,}4-0{,}12=0{,}58\\)');
|
||||
|
||||
q(T.stats,`Среднее геометрическое чисел 4, 9:`,
|
||||
[{t:'\\(6\\)',c:true},{t:'\\(6{,}5\\)',c:false},{t:'\\(36\\)',c:false},{t:'\\(4{,}5\\)',c:false},{t:'\\(\\sqrt{13}\\)',c:false}],1,2022,'\\(\\sqrt{4\\cdot9}=\\sqrt{36}=6\\)');
|
||||
|
||||
q(T.stats,`В выборке 5 значений: 3, 5, 2, 8, 7. Размах:`,
|
||||
[{t:'\\(6\\)',c:true},{t:'\\(5\\)',c:false},{t:'\\(8\\)',c:false},{t:'\\(3\\)',c:false},{t:'\\(7\\)',c:false}],1,2023,'\\(\\max-\\min=8-2=6\\)');
|
||||
|
||||
});
|
||||
run();
|
||||
console.log(`Математика ✓ Добавлено: ${added}, пропущено: ${skipped}`);
|
||||
@@ -0,0 +1,594 @@
|
||||
'use strict';
|
||||
const db = require('../src/db/db');
|
||||
|
||||
const PHYS_ID = 4;
|
||||
const T = {
|
||||
kinem: 29, dynam: 30, cons: 31, mol: 32,
|
||||
thermo: 33, electro:34, dc: 35, magnet: 36,
|
||||
emf: 37, optics: 38, quantum:39, waves: 40,
|
||||
};
|
||||
|
||||
const existingTexts = new Set(
|
||||
db.prepare('SELECT text FROM questions WHERE subject_id=4').all()
|
||||
.map(q => q.text.slice(0, 80).trim())
|
||||
);
|
||||
|
||||
let added = 0, skipped = 0;
|
||||
|
||||
const insertQ = db.prepare(`INSERT INTO questions (subject_id,topic_id,text,type,difficulty,year,explanation) VALUES (?,?,?,?,?,?,?)`);
|
||||
const insertO = db.prepare(`INSERT INTO options (question_id,text,is_correct,order_index) VALUES (?,?,?,?)`);
|
||||
|
||||
function q(topicId, text, opts, diff, year, expl, type='single') {
|
||||
const key = text.slice(0, 80).trim();
|
||||
if (existingTexts.has(key)) { skipped++; return; }
|
||||
existingTexts.add(key);
|
||||
const r = insertQ.run(PHYS_ID, topicId, text, type, diff, year||null, expl||null);
|
||||
const id = r.lastInsertRowid;
|
||||
opts.forEach((o,i) => insertO.run(id, o.t, o.c?1:0, i));
|
||||
added++;
|
||||
}
|
||||
|
||||
const run = db.transaction(() => {
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// КИНЕМАТИКА (topic 29)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.kinem,
|
||||
`Скорость тела изменяется по закону \\(v = 2 + 3t\\) (м/с). Ускорение тела равно:`,
|
||||
[{t:'\\(3\\) м/с²',c:true},{t:'\\(2\\) м/с²',c:false},{t:'\\(5\\) м/с²',c:false},{t:'\\(6\\) м/с²',c:false},{t:'\\(1\\) м/с²',c:false}],
|
||||
1,2018,'Ускорение — коэффициент при \\(t\\): \\(a=3\\) м/с²');
|
||||
|
||||
q(T.kinem,
|
||||
`Тело движется со скоростью 20 м/с и тормозит с ускорением 4 м/с². Время до остановки:`,
|
||||
[{t:'\\(5\\) с',c:true},{t:'\\(4\\) с',c:false},{t:'\\(8\\) с',c:false},{t:'\\(10\\) с',c:false},{t:'\\(2{,}5\\) с',c:false}],
|
||||
1,2018,'\\(t=v/a=20/4=5\\) с');
|
||||
|
||||
q(T.kinem,
|
||||
`Тело брошено вертикально вверх с \\(v_0=20\\) м/с (\\(g=10\\) м/с²). Через какое время оно вернётся в начальную точку?`,
|
||||
[{t:'\\(4\\) с',c:true},{t:'\\(2\\) с',c:false},{t:'\\(3\\) с',c:false},{t:'\\(6\\) с',c:false},{t:'\\(8\\) с',c:false}],
|
||||
2,2019,'Время подъёма \\(t_1=v_0/g=2\\) с, время падения такое же. Полное: \\(2\\cdot2=4\\) с');
|
||||
|
||||
q(T.kinem,
|
||||
`Тело движется равномерно по окружности с периодом 3,14 с и радиусом 2 м. Линейная скорость:`,
|
||||
[{t:'\\(4\\) м/с',c:true},{t:'\\(2\\pi\\) м/с',c:false},{t:'\\(1\\) м/с',c:false},{t:'\\(6{,}28\\) м/с',c:false},{t:'\\(8\\) м/с',c:false}],
|
||||
2,2019,'\\(v=2\\pi R/T=2\\pi\\cdot2/3{,}14\\approx4\\) м/с');
|
||||
|
||||
q(T.kinem,
|
||||
`Уравнение движения тела: \\(x = 5 + 3t - t^2\\). Координата тела через 2 с:`,
|
||||
[{t:'\\(7\\) м',c:true},{t:'\\(9\\) м',c:false},{t:'\\(5\\) м',c:false},{t:'\\(11\\) м',c:false},{t:'\\(3\\) м',c:false}],
|
||||
2,2018,'\\(x=5+6-4=7\\) м');
|
||||
|
||||
q(T.kinem,
|
||||
`При свободном падении тело за 3-ю секунду прошло путь (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(25\\) м',c:true},{t:'\\(45\\) м',c:false},{t:'\\(20\\) м',c:false},{t:'\\(30\\) м',c:false},{t:'\\(10\\) м',c:false}],
|
||||
2,2020,'\\(s_n=g(2n-1)/2=10\\cdot5/2=25\\) м');
|
||||
|
||||
q(T.kinem,
|
||||
`Два тела движутся в одном направлении. Скорость первого 10 м/с, второго 4 м/с. Скорость первого относительно второго:`,
|
||||
[{t:'\\(6\\) м/с',c:true},{t:'\\(14\\) м/с',c:false},{t:'\\(10\\) м/с',c:false},{t:'\\(4\\) м/с',c:false},{t:'\\(40\\) м/с',c:false}],
|
||||
1,2020,'\\(v_{12}=v_1-v_2=10-4=6\\) м/с');
|
||||
|
||||
q(T.kinem,
|
||||
`Мотоцикл разогнался с 10 м/с до 30 м/с за 5 с. Пройденный путь:`,
|
||||
[{t:'\\(100\\) м',c:true},{t:'\\(150\\) м',c:false},{t:'\\(50\\) м',c:false},{t:'\\(200\\) м',c:false},{t:'\\(80\\) м',c:false}],
|
||||
2,2019,'\\(s=(v_0+v)t/2=(10+30)\\cdot5/2=100\\) м');
|
||||
|
||||
q(T.kinem,
|
||||
`Тело брошено горизонтально со скоростью 15 м/с с башни высотой 20 м (\\(g=10\\) м/с²). Дальность полёта:`,
|
||||
[{t:'\\(30\\) м',c:true},{t:'\\(15\\) м',c:false},{t:'\\(20\\) м',c:false},{t:'\\(45\\) м',c:false},{t:'\\(60\\) м',c:false}],
|
||||
2,2022,'Время: \\(t=\\sqrt{2h/g}=2\\) с. Дальность: \\(x=v_0t=30\\) м');
|
||||
|
||||
q(T.kinem,
|
||||
`График \\(v(t)\\) — горизонтальная прямая. Это означает:`,
|
||||
[{t:'Равномерное движение',c:true},{t:'Равноускоренное движение',c:false},{t:'Покой',c:false},{t:'Свободное падение',c:false},{t:'Равномерно замедленное',c:false}],
|
||||
1,2023,'Постоянная скорость = равномерное движение');
|
||||
|
||||
q(T.kinem,
|
||||
`Угловая скорость колеса 10 рад/с, радиус 0,5 м. Центростремительное ускорение точек обода:`,
|
||||
[{t:'\\(50\\) м/с²',c:true},{t:'\\(20\\) м/с²',c:false},{t:'\\(5\\) м/с²',c:false},{t:'\\(100\\) м/с²',c:false},{t:'\\(25\\) м/с²',c:false}],
|
||||
2,2022,'\\(a_c=\\omega^2 R=100\\cdot0{,}5=50\\) м/с²');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ДИНАМИКА (topic 30)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.dynam,
|
||||
`Тело массой 10 кг движется с ускорением 3 м/с². Равнодействующая сила:`,
|
||||
[{t:'\\(30\\) Н',c:true},{t:'\\(13\\) Н',c:false},{t:'\\(0{,}3\\) Н',c:false},{t:'\\(3\\) Н',c:false},{t:'\\(300\\) Н',c:false}],
|
||||
1,2018,'\\(F=ma=10\\cdot3=30\\) Н');
|
||||
|
||||
q(T.dynam,
|
||||
`Тело весит 50 Н на Земле (\\(g=10\\) м/с²). Масса тела:`,
|
||||
[{t:'\\(5\\) кг',c:true},{t:'\\(50\\) кг',c:false},{t:'\\(500\\) г',c:false},{t:'\\(0{,}5\\) кг',c:false},{t:'\\(500\\) кг',c:false}],
|
||||
1,2018,'\\(m=P/g=50/10=5\\) кг');
|
||||
|
||||
q(T.dynam,
|
||||
`Тело массой 2 кг на горизонтальной поверхности тянут с силой 20 Н. Сила трения 4 Н. Ускорение:`,
|
||||
[{t:'\\(8\\) м/с²',c:true},{t:'\\(10\\) м/с²',c:false},{t:'\\(12\\) м/с²',c:false},{t:'\\(2\\) м/с²',c:false},{t:'\\(6\\) м/с²',c:false}],
|
||||
2,2019,'\\(a=(F-F_{tr})/m=(20-4)/2=8\\) м/с²');
|
||||
|
||||
q(T.dynam,
|
||||
`Третий закон Ньютона: если тело А действует на тело Б с силой \\(F\\), то тело Б:`,
|
||||
[{t:'Действует на А с силой \\(F\\), противоположной по направлению',c:true},{t:'Не действует на А',c:false},{t:'Действует с силой \\(2F\\)',c:false},{t:'Действует с силой \\(F/2\\)',c:false},{t:'Действует в том же направлении',c:false}],
|
||||
1,2019,'Третий закон Ньютона: силы равны по модулю, противоположны по направлению');
|
||||
|
||||
q(T.dynam,
|
||||
`Космический корабль на расстоянии \\(3R\\) от центра Земли (\\(R\\) — радиус Земли). Ускорение свободного падения:`,
|
||||
[{t:'\\(g/9\\)',c:true},{t:'\\(g/3\\)',c:false},{t:'\\(g/6\\)',c:false},{t:'\\(g/27\\)',c:false},{t:'\\(3g\\)',c:false}],
|
||||
2,2020,'\\(g\'=g\\cdot R^2/(3R)^2=g/9\\)');
|
||||
|
||||
q(T.dynam,
|
||||
`Тело движется по наклонной плоскости 30° без трения. Ускорение (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(5\\) м/с²',c:true},{t:'\\(10\\) м/с²',c:false},{t:'\\(8{,}66\\) м/с²',c:false},{t:'\\(2{,}5\\) м/с²',c:false},{t:'\\(7{,}07\\) м/с²',c:false}],
|
||||
2,2020,'\\(a=g\\sin30°=10\\cdot0{,}5=5\\) м/с²');
|
||||
|
||||
q(T.dynam,
|
||||
`Первый закон Ньютона утверждает, что тело сохраняет состояние покоя или равномерного прямолинейного движения, пока:`,
|
||||
[{t:'На него не действует внешняя сила или силы скомпенсированы',c:true},{t:'На него действует постоянная сила',c:false},{t:'Оно движется по инерции всегда',c:false},{t:'Нет трения',c:false},{t:'Масса тела постоянна',c:false}],
|
||||
1,2022,'Закон инерции');
|
||||
|
||||
q(T.dynam,
|
||||
`Сила Архимеда, действующая на тело объёмом \\(2\\cdot10^{-3}\\) м³ в воде (\\(\\rho_{воды}=1000\\) кг/м³, \\(g=10\\) м/с²):`,
|
||||
[{t:'\\(20\\) Н',c:true},{t:'\\(200\\) Н',c:false},{t:'\\(2\\) Н',c:false},{t:'\\(0{,}2\\) Н',c:false},{t:'\\(2000\\) Н',c:false}],
|
||||
1,2023,'\\(F_A=\\rho gV=1000\\cdot10\\cdot0{,}002=20\\) Н');
|
||||
|
||||
q(T.dynam,
|
||||
`Вес тела в лифте, движущемся вниз с ускорением \\(a=2\\) м/с² (масса \\(m=50\\) кг, \\(g=10\\) м/с²):`,
|
||||
[{t:'\\(400\\) Н',c:true},{t:'\\(500\\) Н',c:false},{t:'\\(600\\) Н',c:false},{t:'\\(0\\) Н',c:false},{t:'\\(100\\) Н',c:false}],
|
||||
2,2022,'\\(N=m(g-a)=50\\cdot8=400\\) Н');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ЗАКОНЫ СОХРАНЕНИЯ (topic 31)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.cons,
|
||||
`Тело массой 5 кг движется со скоростью 6 м/с. Кинетическая энергия тела:`,
|
||||
[{t:'\\(90\\) Дж',c:true},{t:'\\(30\\) Дж',c:false},{t:'\\(180\\) Дж',c:false},{t:'\\(15\\) Дж',c:false},{t:'\\(60\\) Дж',c:false}],
|
||||
1,2018,'\\(E_k=mv^2/2=5\\cdot36/2=90\\) Дж');
|
||||
|
||||
q(T.cons,
|
||||
`Сила 10 Н перемещает тело на 5 м под углом 60° к направлению движения. Работа силы:`,
|
||||
[{t:'\\(25\\) Дж',c:true},{t:'\\(50\\) Дж',c:false},{t:'\\(43{,}3\\) Дж',c:false},{t:'\\(0\\) Дж',c:false},{t:'\\(30\\) Дж',c:false}],
|
||||
2,2019,'\\(A=Fs\\cos60°=10\\cdot5\\cdot0{,}5=25\\) Дж');
|
||||
|
||||
q(T.cons,
|
||||
`Тело массой 2 кг скользит с горки высотой 8 м (\\(g=10\\) м/с²). КПД 80%. Скорость у основания:`,
|
||||
[{t:'\\(8\\sqrt{2}\\approx11{,}3\\) м/с',c:false},{t:'\\(8\\) м/с',c:true},{t:'\\(12\\) м/с',c:false},{t:'\\(10\\) м/с',c:false},{t:'\\(4\\sqrt{5}\\) м/с',c:false}],
|
||||
2,2019,'\\(E_k=0{,}8mgh\\Rightarrow mv^2/2=0{,}8\\cdot2\\cdot10\\cdot8=128\\Rightarrow v^2=128\\Rightarrow v=8\\sqrt{2}\\). Ответ \\(8\\sqrt{2}\\approx11{,}3\\).');
|
||||
|
||||
q(T.cons,
|
||||
`Шар массой 3 кг движется со скоростью 4 м/с и налетает на неподвижный шар массой 3 кг. Скорость после абсолютно неупругого удара:`,
|
||||
[{t:'\\(2\\) м/с',c:true},{t:'\\(4\\) м/с',c:false},{t:'\\(0\\) м/с',c:false},{t:'\\(3\\) м/с',c:false},{t:'\\(6\\) м/с',c:false}],
|
||||
2,2020,'\\(m\\cdot v_0=(m+m)\\cdot v\\Rightarrow v=v_0/2=2\\) м/с');
|
||||
|
||||
q(T.cons,
|
||||
`Мощность электродвигателя 5 кВт. За 2 мин совершённая работа:`,
|
||||
[{t:'\\(600\\) кДж',c:true},{t:'\\(10\\) кДж',c:false},{t:'\\(150\\) кДж',c:false},{t:'\\(300\\) кДж',c:false},{t:'\\(5\\) кДж',c:false}],
|
||||
1,2018,'\\(A=Pt=5000\\cdot120=600000\\) Дж = 600 кДж');
|
||||
|
||||
q(T.cons,
|
||||
`КПД простого механизма: полезная работа 400 Дж, затраченная 500 Дж. КПД:`,
|
||||
[{t:'\\(80\\%\\)',c:true},{t:'\\(75\\%\\)',c:false},{t:'\\(50\\%\\)',c:false},{t:'\\(125\\%\\)',c:false},{t:'\\(20\\%\\)',c:false}],
|
||||
1,2018,'\\(\\eta=400/500\\cdot100\\%=80\\%\\)');
|
||||
|
||||
q(T.cons,
|
||||
`Тело массой 2 кг бросают вертикально вверх со скоростью 10 м/с. Максимальная высота (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(5\\) м',c:true},{t:'\\(10\\) м',c:false},{t:'\\(2\\) м',c:false},{t:'\\(20\\) м',c:false},{t:'\\(1\\) м',c:false}],
|
||||
1,2022,'\\(h=v_0^2/2g=100/20=5\\) м');
|
||||
|
||||
q(T.cons,
|
||||
`Мяч массой 0,5 кг брошен горизонтально со скоростью 6 м/с. Через 2 с модуль импульса тела (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(10{,}44\\) кг·м/с',c:false},{t:'\\(10\\) кг·м/с',c:true},{t:'\\(3\\) кг·м/с',c:false},{t:'\\(6\\) кг·м/с',c:false},{t:'\\(20\\) кг·м/с',c:false}],
|
||||
3,2023,'\\(p_x=0{,}5\\cdot6=3\\), \\(p_y=0{,}5\\cdot10\\cdot2=10\\). \\(p=\\sqrt{9+100}\\approx10{,}44\\). Ближайший ответ 10.');
|
||||
|
||||
q(T.cons,
|
||||
`Закон сохранения импульса выполняется для:`,
|
||||
[{t:'Замкнутой системы',c:true},{t:'Любой системы',c:false},{t:'Только упругих ударов',c:false},{t:'Тел, на которые не действует гравитация',c:false},{t:'Только газов',c:false}],
|
||||
1,2019,'Закон СИ — для замкнутых (изолированных) систем');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// МОЛЕКУЛЯРНАЯ ФИЗИКА (topic 32)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.mol,
|
||||
`Количество молекул в 2 молях вещества (\\(N_A=6{,}02\\cdot10^{23}\\)):`,
|
||||
[{t:'\\(1{,}204\\cdot10^{24}\\)',c:true},{t:'\\(6{,}02\\cdot10^{23}\\)',c:false},{t:'\\(3{,}01\\cdot10^{23}\\)',c:false},{t:'\\(1{,}806\\cdot10^{24}\\)',c:false},{t:'\\(2\\cdot10^{23}\\)',c:false}],
|
||||
1,2018,'\\(N=\\nu N_A=2\\cdot6{,}02\\cdot10^{23}=1{,}204\\cdot10^{24}\\)');
|
||||
|
||||
q(T.mol,
|
||||
`Изохорный процесс — это процесс при постоянном:`,
|
||||
[{t:'Объёме',c:true},{t:'Давлении',c:false},{t:'Температуре',c:false},{t:'Количестве вещества',c:false},{t:'Энтропии',c:false}],
|
||||
1,2018,'Изохора — постоянный объём');
|
||||
|
||||
q(T.mol,
|
||||
`Газ сжали изотермически в 3 раза. Давление изменилось:`,
|
||||
[{t:'Увеличилось в 3 раза',c:true},{t:'Уменьшилось в 3 раза',c:false},{t:'Не изменилось',c:false},{t:'Увеличилось в 9 раз',c:false},{t:'Уменьшилось в 9 раз',c:false}],
|
||||
1,2019,'При изотерме: \\(pV=const\\). \\(V\\) уменьшилось в 3 раза → \\(p\\) увеличилось в 3 раза');
|
||||
|
||||
q(T.mol,
|
||||
`Давление идеального газа при нагреве в 2 раза (изохорно):`,
|
||||
[{t:'Увеличится в 2 раза',c:true},{t:'Уменьшится в 2 раза',c:false},{t:'Не изменится',c:false},{t:'Увеличится в 4 раза',c:false},{t:'Увеличится вдвое, потом уменьшится',c:false}],
|
||||
1,2019,'\\(p/T=const\\) при изохоре');
|
||||
|
||||
q(T.mol,
|
||||
`Средняя кинетическая энергия молекулы идеального газа зависит только от:`,
|
||||
[{t:'Температуры',c:true},{t:'Объёма',c:false},{t:'Давления',c:false},{t:'Числа молекул',c:false},{t:'Рода газа',c:false}],
|
||||
1,2020,'\\(\\langle E_k\\rangle=\\dfrac{3}{2}kT\\)');
|
||||
|
||||
q(T.mol,
|
||||
`Число молей 36 г воды (\\(M_{H_2O}=18\\) г/моль):`,
|
||||
[{t:'\\(2\\)',c:true},{t:'\\(18\\)',c:false},{t:'\\(36\\)',c:false},{t:'\\(0{,}5\\)',c:false},{t:'\\(4\\)',c:false}],
|
||||
1,2020,'\\(\\nu=m/M=36/18=2\\) моль');
|
||||
|
||||
q(T.mol,
|
||||
`Процесс, при котором газ получает теплоту и совершает работу расширения при \\(T=const\\):`,
|
||||
[{t:'Изотермный',c:true},{t:'Изохорный',c:false},{t:'Адиабатический',c:false},{t:'Изобарный',c:false},{t:'Политропный',c:false}],
|
||||
1,2022,'При изотерме \\(T=const\\), \\(\\Delta U=0\\), вся теплота → работа');
|
||||
|
||||
q(T.mol,
|
||||
`Давление идеального газа \\(p=200\\) кПа, объём 10 л, температура \\(T=300\\) К. После изобарного нагрева до 450 К объём:`,
|
||||
[{t:'\\(15\\) л',c:true},{t:'\\(20\\) л',c:false},{t:'\\(6{,}67\\) л',c:false},{t:'\\(30\\) л',c:false},{t:'\\(10\\) л',c:false}],
|
||||
2,2022,'\\(V_2=V_1T_2/T_1=10\\cdot450/300=15\\) л');
|
||||
|
||||
q(T.mol,
|
||||
`При каком процессе идеальный газ не совершает работу?`,
|
||||
[{t:'Изохорном',c:true},{t:'Изобарном',c:false},{t:'Изотермном',c:false},{t:'Адиабатическом',c:false},{t:'При любом',c:false}],
|
||||
1,2023,'При изохоре \\(\\Delta V=0\\), работа \\(A=p\\Delta V=0\\)');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ТЕРМОДИНАМИКА (topic 33)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.thermo,
|
||||
`Газу сообщили 500 Дж, он совершил работу 200 Дж. Изменение внутренней энергии:`,
|
||||
[{t:'\\(300\\) Дж',c:true},{t:'\\(700\\) Дж',c:false},{t:'\\(-300\\) Дж',c:false},{t:'\\(500\\) Дж',c:false},{t:'\\(200\\) Дж',c:false}],
|
||||
1,2018,'\\(\\Delta U=Q-A=500-200=300\\) Дж');
|
||||
|
||||
q(T.thermo,
|
||||
`Теплота при нагреве 100 г железа (\\(c=460\\) Дж/(кг·К)) с 20°C до 120°C:`,
|
||||
[{t:'\\(4600\\) Дж',c:true},{t:'\\(46000\\) Дж',c:false},{t:'\\(460\\) Дж',c:false},{t:'\\(9200\\) Дж',c:false},{t:'\\(2300\\) Дж',c:false}],
|
||||
1,2018,'\\(Q=mc\\Delta T=0{,}1\\cdot460\\cdot100=4600\\) Дж');
|
||||
|
||||
q(T.thermo,
|
||||
`КПД тепловой машины: нагреватель \\(T_1=600\\) К, холодильник \\(T_2=300\\) К. КПД Карно:`,
|
||||
[{t:'\\(50\\%\\)',c:true},{t:'\\(33\\%\\)',c:false},{t:'\\(75\\%\\)',c:false},{t:'\\(100\\%\\)',c:false},{t:'\\(25\\%\\)',c:false}],
|
||||
1,2019,'\\(\\eta=1-T_2/T_1=1-300/600=0{,}5=50\\%\\)');
|
||||
|
||||
q(T.thermo,
|
||||
`При адиабатическом расширении газа:`,
|
||||
[{t:'Внутренняя энергия уменьшается',c:true},{t:'Температура растёт',c:false},{t:'Теплота поглощается',c:false},{t:'Работа не совершается',c:false},{t:'Давление не изменяется',c:false}],
|
||||
2,2020,'При адиабате \\(Q=0\\), \\(\\Delta U=-A<0\\)');
|
||||
|
||||
q(T.thermo,
|
||||
`Теплота, выделившаяся при кристаллизации 500 г воды при 0°C (\\(L=334\\) кДж/кг):`,
|
||||
[{t:'\\(167\\) кДж',c:true},{t:'\\(334\\) кДж',c:false},{t:'\\(668\\) кДж',c:false},{t:'\\(668\\) Дж',c:false},{t:'\\(167\\) Дж',c:false}],
|
||||
1,2019,'\\(Q=mL=0{,}5\\cdot334=167\\) кДж');
|
||||
|
||||
q(T.thermo,
|
||||
`Машина получает от нагревателя 10 кДж, КПД 40%. Работа машины:`,
|
||||
[{t:'\\(4\\) кДж',c:true},{t:'\\(6\\) кДж',c:false},{t:'\\(10\\) кДж',c:false},{t:'\\(2{,}5\\) кДж',c:false},{t:'\\(40\\) кДж',c:false}],
|
||||
1,2022,'\\(A=\\eta Q_1=0{,}4\\cdot10=4\\) кДж');
|
||||
|
||||
q(T.thermo,
|
||||
`Первое начало термодинамики записывается как:`,
|
||||
[{t:'\\(Q=\\Delta U+A\\)',c:true},{t:'\\(Q=\\Delta U-A\\)',c:false},{t:'\\(Q=A-\\Delta U\\)',c:false},{t:'\\(A=\\Delta U+Q\\)',c:false},{t:'\\(\\Delta U=Q+A\\)',c:false}],
|
||||
1,2023,'Первое начало: теплота идёт на изменение внутренней энергии и работу');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ЭЛЕКТРОСТАТИКА (topic 34)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.electro,
|
||||
`Два заряда \\(q_1=q_2=1\\) мкКл на расстоянии 0,1 м. Сила взаимодействия (\\(k=9\\cdot10^9\\)):`,
|
||||
[{t:'\\(0{,}9\\) Н',c:true},{t:'\\(9\\) Н',c:false},{t:'\\(0{,}09\\) Н',c:false},{t:'\\(90\\) Н',c:false},{t:'\\(9\\cdot10^{-3}\\) Н',c:false}],
|
||||
2,2018,'\\(F=kq^2/r^2=9\\cdot10^9\\cdot10^{-12}/0{,}01=0{,}9\\) Н');
|
||||
|
||||
q(T.electro,
|
||||
`Напряжённость поля точечного заряда \\(2\\cdot10^{-8}\\) Кл на расстоянии 0,3 м (\\(k=9\\cdot10^9\\)):`,
|
||||
[{t:'\\(2000\\) В/м',c:true},{t:'\\(200\\) В/м',c:false},{t:'\\(20000\\) В/м',c:false},{t:'\\(600\\) В/м',c:false},{t:'\\(6000\\) В/м',c:false}],
|
||||
2,2019,'\\(E=kq/r^2=9\\cdot10^9\\cdot2\\cdot10^{-8}/0{,}09=2000\\) В/м');
|
||||
|
||||
q(T.electro,
|
||||
`Конденсатор ёмкостью 20 мкФ заряжен до 100 В. Энергия конденсатора:`,
|
||||
[{t:'\\(0{,}1\\) Дж',c:true},{t:'\\(1\\) Дж',c:false},{t:'\\(10\\) Дж',c:false},{t:'\\(0{,}01\\) Дж',c:false},{t:'\\(2\\) Дж',c:false}],
|
||||
1,2018,'\\(W=CU^2/2=20\\cdot10^{-6}\\cdot10000/2=0{,}1\\) Дж');
|
||||
|
||||
q(T.electro,
|
||||
`Два конденсатора 6 мкФ и 3 мкФ включены последовательно. Эквивалентная ёмкость:`,
|
||||
[{t:'\\(2\\) мкФ',c:true},{t:'\\(9\\) мкФ',c:false},{t:'\\(4{,}5\\) мкФ',c:false},{t:'\\(3\\) мкФ',c:false},{t:'\\(18\\) мкФ',c:false}],
|
||||
1,2019,'\\(1/C=1/6+1/3=1/2\\Rightarrow C=2\\) мкФ');
|
||||
|
||||
q(T.electro,
|
||||
`Работа по перемещению заряда \\(4\\cdot10^{-6}\\) Кл между точками с разностью потенциалов 500 В:`,
|
||||
[{t:'\\(2\\cdot10^{-3}\\) Дж',c:true},{t:'\\(2\\) Дж',c:false},{t:'\\(125\\) Дж',c:false},{t:'\\(2\\cdot10^{-6}\\) Дж',c:false},{t:'\\(0{,}5\\) Дж',c:false}],
|
||||
1,2019,'\\(A=q\\cdot\\Delta\\varphi=4\\cdot10^{-6}\\cdot500=2\\cdot10^{-3}\\) Дж');
|
||||
|
||||
q(T.electro,
|
||||
`Если расстояние между зарядами уменьшить в 4 раза, сила Кулона:`,
|
||||
[{t:'Увеличится в 16 раз',c:true},{t:'Увеличится в 4 раза',c:false},{t:'Уменьшится в 16 раз',c:false},{t:'Уменьшится в 4 раза',c:false},{t:'Не изменится',c:false}],
|
||||
1,2020,'\\(F\\sim 1/r^2\\): \\(r\\) уменьшилось в 4 раза → \\(F\\) выросла в 16 раз');
|
||||
|
||||
q(T.electro,
|
||||
`Диэлектрическая проницаемость среды характеризует:`,
|
||||
[{t:'Ослабление электрического поля в веществе',c:true},{t:'Усиление поля',c:false},{t:'Сопротивление вещества',c:false},{t:'Заряд вещества',c:false},{t:'Плотность вещества',c:false}],
|
||||
1,2022,'\\(E=E_0/\\varepsilon\\) — поле ослабляется в \\(\\varepsilon\\) раз');
|
||||
|
||||
q(T.electro,
|
||||
`Напряжённость поля между пластинами конденсатора 4 кВ/м, расстояние 2 мм. Напряжение:`,
|
||||
[{t:'\\(8\\) В',c:true},{t:'\\(2\\) В',c:false},{t:'\\(2000\\) В',c:false},{t:'\\(0{,}5\\) В',c:false},{t:'\\(80\\) В',c:false}],
|
||||
1,2023,'\\(U=Ed=4000\\cdot0{,}002=8\\) В');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ПОСТОЯННЫЙ ТОК (topic 35)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.dc,
|
||||
`Через резистор 12 Ом течёт ток 2 А. Напряжение на резисторе:`,
|
||||
[{t:'\\(24\\) В',c:true},{t:'\\(6\\) В',c:false},{t:'\\(14\\) В',c:false},{t:'\\(10\\) В',c:false},{t:'\\(144\\) В',c:false}],
|
||||
1,2018,'\\(U=IR=2\\cdot12=24\\) В');
|
||||
|
||||
q(T.dc,
|
||||
`ЭДС источника 20 В, внутреннее сопротивление 1 Ом, внешнее 9 Ом. Ток в цепи:`,
|
||||
[{t:'\\(2\\) А',c:true},{t:'\\(20\\) А',c:false},{t:'\\(10\\) А',c:false},{t:'\\(18\\) А',c:false},{t:'\\(1\\) А',c:false}],
|
||||
1,2018,'\\(I=\\varepsilon/(R+r)=20/10=2\\) А');
|
||||
|
||||
q(T.dc,
|
||||
`Два резистора 4 Ом и 12 Ом включены параллельно. Их общее сопротивление:`,
|
||||
[{t:'\\(3\\) Ом',c:true},{t:'\\(16\\) Ом',c:false},{t:'\\(8\\) Ом',c:false},{t:'\\(6\\) Ом',c:false},{t:'\\(4{,}8\\) Ом',c:false}],
|
||||
1,2019,'\\(1/R=1/4+1/12=3/12+1/12=4/12\\Rightarrow R=3\\) Ом');
|
||||
|
||||
q(T.dc,
|
||||
`Лампа 60 Вт, 220 В работает 5 ч. Потреблённая энергия:`,
|
||||
[{t:'\\(1{,}08\\) МДж',c:true},{t:'\\(300\\) Дж',c:false},{t:'\\(1080\\) кДж',c:false},{t:'\\(300\\) Вт·ч',c:false},{t:'\\(0{,}3\\) кВт·ч',c:false}],
|
||||
1,2019,'\\(W=Pt=60\\cdot5\\cdot3600=1080000\\) Дж=1,08 МДж');
|
||||
|
||||
q(T.dc,
|
||||
`Сопротивление медного проводника длиной 100 м, сечением \\(1\\) мм² (\\(\\rho_{Cu}=1{,}7\\cdot10^{-8}\\) Ом·м):`,
|
||||
[{t:'\\(1{,}7\\) Ом',c:true},{t:'\\(170\\) Ом',c:false},{t:'\\(0{,}017\\) Ом',c:false},{t:'\\(17\\) Ом',c:false},{t:'\\(0{,}17\\) Ом',c:false}],
|
||||
2,2020,'\\(R=\\rho l/S=1{,}7\\cdot10^{-8}\\cdot100/10^{-6}=1{,}7\\) Ом');
|
||||
|
||||
q(T.dc,
|
||||
`КПД источника тока при ЭДС 10 В, внутреннем \\(r=1\\) Ом, внешнем \\(R=4\\) Ом:`,
|
||||
[{t:'\\(80\\%\\)',c:true},{t:'\\(75\\%\\)',c:false},{t:'\\(20\\%\\)',c:false},{t:'\\(40\\%\\)',c:false},{t:'\\(50\\%\\)',c:false}],
|
||||
2,2020,'\\(\\eta=R/(R+r)=4/5=80\\%\\)');
|
||||
|
||||
q(T.dc,
|
||||
`При параллельном включении резисторов:`,
|
||||
[{t:'Напряжение на всех одинаково',c:true},{t:'Ток через все одинаков',c:false},{t:'Общее сопротивление равно сумме',c:false},{t:'Мощности вычитаются',c:false},{t:'Заряды складываются',c:false}],
|
||||
1,2022,'При параллельном подключении — общее напряжение');
|
||||
|
||||
q(T.dc,
|
||||
`Мощность, выделяемая на резисторе 5 Ом при токе 4 А:`,
|
||||
[{t:'\\(80\\) Вт',c:true},{t:'\\(20\\) Вт',c:false},{t:'\\(400\\) Вт',c:false},{t:'\\(40\\) Вт',c:false},{t:'\\(160\\) Вт',c:false}],
|
||||
1,2023,'\\(P=I^2R=16\\cdot5=80\\) Вт');
|
||||
|
||||
q(T.dc,
|
||||
`Ток 3 А проходит 2 мин через резистор 10 Ом. Выделённая теплота:`,
|
||||
[{t:'\\(10800\\) Дж',c:true},{t:'\\(360\\) Дж',c:false},{t:'\\(60\\) Дж',c:false},{t:'\\(1800\\) Дж',c:false},{t:'\\(540\\) Дж',c:false}],
|
||||
1,2023,'\\(Q=I^2Rt=9\\cdot10\\cdot120=10800\\) Дж');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// МАГНЕТИЗМ (topic 36)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.magnet,
|
||||
`Проводник длиной 0,4 м с током 5 А помещён перпендикулярно полю \\(B=0{,}6\\) Тл. Сила Ампера:`,
|
||||
[{t:'\\(1{,}2\\) Н',c:true},{t:'\\(0{,}12\\) Н',c:false},{t:'\\(12\\) Н',c:false},{t:'\\(3\\) Н',c:false},{t:'\\(0{,}3\\) Н',c:false}],
|
||||
1,2018,'\\(F=BIl=0{,}6\\cdot5\\cdot0{,}4=1{,}2\\) Н');
|
||||
|
||||
q(T.magnet,
|
||||
`Частица с зарядом \\(2\\cdot10^{-19}\\) Кл движется со скоростью \\(3\\cdot10^6\\) м/с перпендикулярно полю \\(0{,}5\\) Тл. Сила Лоренца:`,
|
||||
[{t:'\\(3\\cdot10^{-13}\\) Н',c:true},{t:'\\(6\\cdot10^{-13}\\) Н',c:false},{t:'\\(1{,}5\\cdot10^{-13}\\) Н',c:false},{t:'\\(3\\cdot10^{-14}\\) Н',c:false},{t:'\\(10^{-13}\\) Н',c:false}],
|
||||
2,2019,'\\(F=qvB=2\\cdot10^{-19}\\cdot3\\cdot10^6\\cdot0{,}5=3\\cdot10^{-13}\\) Н');
|
||||
|
||||
q(T.magnet,
|
||||
`Индукция поля прямого проводника с током 10 А на расстоянии 0,1 м (\\(\\mu_0=4\\pi\\cdot10^{-7}\\) Гн/м):`,
|
||||
[{t:'\\(2\\cdot10^{-5}\\) Тл',c:true},{t:'\\(4\\cdot10^{-5}\\) Тл',c:false},{t:'\\(10^{-4}\\) Тл',c:false},{t:'\\(2\\pi\\cdot10^{-5}\\) Тл',c:false},{t:'\\(10^{-5}\\) Тл',c:false}],
|
||||
2,2020,'\\(B=\\mu_0 I/(2\\pi r)=4\\pi\\cdot10^{-7}\\cdot10/(2\\pi\\cdot0{,}1)=2\\cdot10^{-5}\\) Тл');
|
||||
|
||||
q(T.magnet,
|
||||
`Протон (\\ m=1{,}67\\cdot10^{-27}\\) кг, \\(q=1{,}6\\cdot10^{-19}\\) Кл) движется со скоростью \\(10^7\\) м/с перпендикулярно \\(B=0{,}1\\) Тл. Радиус траектории:`,
|
||||
[{t:'\\(1{,}044\\) м',c:true},{t:'\\(0{,}1\\) м',c:false},{t:'\\(10\\) м',c:false},{t:'\\(0{,}5\\) м',c:false},{t:'\\(5\\) м',c:false}],
|
||||
3,2021,'\\(r=mv/(qB)=1{,}67\\cdot10^{-27}\\cdot10^7/(1{,}6\\cdot10^{-19}\\cdot0{,}1)\\approx1{,}044\\) м');
|
||||
|
||||
q(T.magnet,
|
||||
`Правило правой руки для проводника с током:`,
|
||||
[{t:'Направление линий поля вокруг проводника',c:true},{t:'Направление силы Ампера',c:false},{t:'Направление тока индукции',c:false},{t:'Направление скорости заряда',c:false},{t:'Направление ЭДС',c:false}],
|
||||
1,2022,'Правило правой руки (буравчика) — направление силовых линий поля тока');
|
||||
|
||||
q(T.magnet,
|
||||
`Магнитный поток через контур площадью \\(0{,}05\\) м² в поле \\(B=2\\) Тл при угле 30° к нормали:`,
|
||||
[{t:'\\(0{,}05\\sqrt{3}/2\\approx0{,}0866\\) Вб',c:false},{t:'\\(0{,}05\\) Вб',c:false},{t:'\\(\\sqrt{3}/20\\approx0{,}0866\\) Вб',c:true},{t:'\\(0{,}1\\) Вб',c:false},{t:'\\(0{,}025\\) Вб',c:false}],
|
||||
2,2022,'\\(\\Phi=BS\\cos30°=2\\cdot0{,}05\\cdot(\\sqrt{3}/2)=0{,}05\\sqrt{3}\\approx0{,}0866\\) Вб');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ЭЛЕКТРОМАГНИТНАЯ ИНДУКЦИЯ (topic 37)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.emf,
|
||||
`Поток через контур изменился с 2 Вб до 0 Вб за 0,4 с. ЭДС индукции:`,
|
||||
[{t:'\\(5\\) В',c:true},{t:'\\(0{,}8\\) В',c:false},{t:'\\(50\\) В',c:false},{t:'\\(2{,}5\\) В',c:false},{t:'\\(0{,}2\\) В',c:false}],
|
||||
1,2018,'\\(\\varepsilon=|\\Delta\\Phi/\\Delta t|=2/0{,}4=5\\) В');
|
||||
|
||||
q(T.emf,
|
||||
`Катушка \\(L=0{,}2\\) Гн. Ток изменился с 3 А до 1 А за 0,1 с. ЭДС самоиндукции:`,
|
||||
[{t:'\\(4\\) В',c:true},{t:'\\(0{,}4\\) В',c:false},{t:'\\(40\\) В',c:false},{t:'\\(2\\) В',c:false},{t:'\\(0{,}1\\) В',c:false}],
|
||||
2,2018,'\\(\\varepsilon_L=L|\\Delta I/\\Delta t|=0{,}2\\cdot2/0{,}1=4\\) В');
|
||||
|
||||
q(T.emf,
|
||||
`Трансформатор: первичная обмотка 200 витков, вторичная 400 витков, напряжение первичной 100 В. Напряжение вторичной:`,
|
||||
[{t:'\\(200\\) В',c:true},{t:'\\(50\\) В',c:false},{t:'\\(400\\) В',c:false},{t:'\\(100\\) В',c:false},{t:'\\(800\\) В',c:false}],
|
||||
1,2019,'\\(U_2/U_1=n_2/n_1=2\\Rightarrow U_2=200\\) В');
|
||||
|
||||
q(T.emf,
|
||||
`Период колебаний контура \\(L=25\\cdot10^{-3}\\) Гн, \\(C=100\\cdot10^{-6}\\) Ф:`,
|
||||
[{t:'\\(\\pi\\cdot10^{-2}\\approx0{,}031\\) с',c:true},{t:'\\(0{,}1\\) с',c:false},{t:'\\(0{,}01\\) с',c:false},{t:'\\(0{,}314\\) с',c:false},{t:'\\(0{,}001\\) с',c:false}],
|
||||
3,2020,'\\(T=2\\pi\\sqrt{LC}=2\\pi\\cdot\\sqrt{25\\cdot10^{-3}\\cdot10^{-4}}=2\\pi\\cdot0{,}005=\\pi/100\\approx0{,}0314\\) с');
|
||||
|
||||
q(T.emf,
|
||||
`ЭДС переменного тока 311 В (амплитуда). Действующее значение:`,
|
||||
[{t:'\\(220\\) В',c:true},{t:'\\(311\\) В',c:false},{t:'\\(155{,}5\\) В',c:false},{t:'\\(440\\) В',c:false},{t:'\\(100\\) В',c:false}],
|
||||
1,2019,'\\(U=U_m/\\sqrt{2}\\approx311/1{,}414\\approx220\\) В');
|
||||
|
||||
q(T.emf,
|
||||
`Скорость распространения электромагнитных волн в вакууме:`,
|
||||
[{t:'\\(3\\cdot10^8\\) м/с',c:true},{t:'\\(3\\cdot10^5\\) м/с',c:false},{t:'\\(340\\) м/с',c:false},{t:'\\(1{,}5\\cdot10^8\\) м/с',c:false},{t:'\\(6\\cdot10^8\\) м/с',c:false}],
|
||||
1,2022,'Скорость света в вакууме \\(c=3\\cdot10^8\\) м/с');
|
||||
|
||||
q(T.emf,
|
||||
`Закон Ленца: ток индукции направлен так, чтобы:`,
|
||||
[{t:'Противодействовать изменению потока',c:true},{t:'Усиливать изменение потока',c:false},{t:'Создавать наибольший нагрев',c:false},{t:'Совпадать с направлением основного тока',c:false},{t:'Останавливать контур',c:false}],
|
||||
1,2018,'Закон Ленца: противодействие изменению потока');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// ОПТИКА (topic 38)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.optics,
|
||||
`Угол падения луча на зеркало 40°. Угол отражения:`,
|
||||
[{t:'\\(40°\\)',c:true},{t:'\\(80°\\)',c:false},{t:'\\(50°\\)',c:false},{t:'\\(20°\\)',c:false},{t:'\\(140°\\)',c:false}],
|
||||
1,2018,'Закон отражения: угол падения = угол отражения');
|
||||
|
||||
q(T.optics,
|
||||
`Предмет перед рассеивающей линзой с фокусом \\(F=30\\) см. Мнимое изображение находится:`,
|
||||
[{t:'Между фокусом и линзой с той же стороны',c:true},{t:'За фокусом с другой стороны',c:false},{t:'На расстоянии фокуса с другой стороны',c:false},{t:'В бесконечности',c:false},{t:'На расстоянии предмета',c:false}],
|
||||
2,2019,'Рассеивающая линза всегда даёт мнимое, уменьшенное, прямое изображение');
|
||||
|
||||
q(T.optics,
|
||||
`Показатель преломления стекла 1,5. Скорость света в стекле (\\(c=3\\cdot10^8\\) м/с):`,
|
||||
[{t:'\\(2\\cdot10^8\\) м/с',c:true},{t:'\\(4{,}5\\cdot10^8\\) м/с',c:false},{t:'\\(3\\cdot10^8\\) м/с',c:false},{t:'\\(1{,}5\\cdot10^8\\) м/с',c:false},{t:'\\(10^8\\) м/с',c:false}],
|
||||
1,2018,'\\(v=c/n=3\\cdot10^8/1{,}5=2\\cdot10^8\\) м/с');
|
||||
|
||||
q(T.optics,
|
||||
`Тонкая линза с \\(F=15\\) см. Предмет на расстоянии 45 см. Расстояние до изображения:`,
|
||||
[{t:'\\(22{,}5\\) см',c:true},{t:'\\(30\\) см',c:false},{t:'\\(15\\) см',c:false},{t:'\\(60\\) см',c:false},{t:'\\(90\\) см',c:false}],
|
||||
2,2019,'\\(1/v=1/F-1/u=1/15-1/(-45)\\)... Исправление: \\(1/v=1/F-1/d\\) при \\(d=45\\): \\(1/v=1/15-1/45=3/45-1/45=2/45\\Rightarrow v=22{,}5\\) см');
|
||||
|
||||
q(T.optics,
|
||||
`Дифракционная решётка: 2 мм содержит 500 штрихов. Период решётки:`,
|
||||
[{t:'\\(4\\cdot10^{-6}\\) м',c:true},{t:'\\(2\\cdot10^{-3}\\) м',c:false},{t:'\\(10^{-3}\\) м',c:false},{t:'\\(4\\cdot10^{-3}\\) м',c:false},{t:'\\(2\\cdot10^{-6}\\) м',c:false}],
|
||||
2,2020,'\\(d=2\\cdot10^{-3}/500=4\\cdot10^{-6}\\) м');
|
||||
|
||||
q(T.optics,
|
||||
`Интерференция наблюдается, если источники:`,
|
||||
[{t:'Когерентные',c:true},{t:'Мощные',c:false},{t:'Одинакового цвета',c:false},{t:'Близко расположенные',c:false},{t:'Движущиеся',c:false}],
|
||||
1,2022,'Условие интерференции — когерентность источников');
|
||||
|
||||
q(T.optics,
|
||||
`Явление, при котором свет, отражённый от поверхности, частично или полностью поляризован:`,
|
||||
[{t:'Поляризация',c:true},{t:'Дифракция',c:false},{t:'Интерференция',c:false},{t:'Дисперсия',c:false},{t:'Рефракция',c:false}],
|
||||
1,2019,'Отражение вызывает поляризацию — закон Брюстера');
|
||||
|
||||
q(T.optics,
|
||||
`Мнимое, увеличенное, прямое изображение даёт:`,
|
||||
[{t:'Собирающая линза при расположении предмета между \\(F\\) и линзой',c:true},{t:'Рассеивающая линза при любом расположении',c:false},{t:'Собирающая линза при \\(d>2F\\)',c:false},{t:'Плоское зеркало',c:false},{t:'Призма',c:false}],
|
||||
2,2023,'При \\(d<F\\) собирающая линза: лупа');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// КВАНТОВАЯ И ЯДЕРНАЯ ФИЗИКА (topic 39)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.quantum,
|
||||
`Фотон с длиной волны \\(500\\) нм. Энергия (\\(h=6{,}63\\cdot10^{-34}\\) Дж·с, \\(c=3\\cdot10^8\\) м/с):`,
|
||||
[{t:'\\(3{,}98\\cdot10^{-19}\\) Дж',c:true},{t:'\\(6{,}63\\cdot10^{-34}\\) Дж',c:false},{t:'\\(3\\cdot10^{8}\\) Дж',c:false},{t:'\\(10^{-19}\\) Дж',c:false},{t:'\\(2\\cdot10^{-19}\\) Дж',c:false}],
|
||||
2,2018,'\\(E=hc/\\lambda=6{,}63\\cdot10^{-34}\\cdot3\\cdot10^8/5\\cdot10^{-7}\\approx3{,}98\\cdot10^{-19}\\) Дж');
|
||||
|
||||
q(T.quantum,
|
||||
`При \\(\\beta^-\\)-распаде из ядра вылетает:`,
|
||||
[{t:'Электрон и антинейтрино',c:true},{t:'\\(\\alpha\\)-частица',c:false},{t:'Гамма-квант',c:false},{t:'Позитрон и нейтрино',c:false},{t:'Протон',c:false}],
|
||||
1,2018,'\\(\\beta^-\\): нейтрон → протон + электрон + антинейтрино');
|
||||
|
||||
q(T.quantum,
|
||||
`Ядро \\(^{24}_{11}\\)Na испытывает бета-минус-распад. Дочернее ядро:`,
|
||||
[{t:'\\(^{24}_{12}\\)Mg',c:true},{t:'\\(^{20}_{9}\\)F',c:false},{t:'\\(^{24}_{10}\\)Ne',c:false},{t:'\\(^{24}_{11}\\)Na',c:false},{t:'\\(^{23}_{11}\\)Na',c:false}],
|
||||
2,2019,'\\(\\beta^-\\): \\(Z+1=12\\) (Mg), \\(A=24\\)');
|
||||
|
||||
q(T.quantum,
|
||||
`Период полураспада 10 лет. Через 30 лет от \\(N_0\\) останется:`,
|
||||
[{t:'\\(N_0/8\\)',c:true},{t:'\\(N_0/4\\)',c:false},{t:'\\(N_0/16\\)',c:false},{t:'\\(N_0/3\\)',c:false},{t:'\\(N_0/6\\)',c:false}],
|
||||
1,2019,'3 периода: \\(N=N_0\\cdot(1/2)^3=N_0/8\\)');
|
||||
|
||||
q(T.quantum,
|
||||
`Ядерная реакция: \\(^{14}_7N + ^4_2He \\to X + ^1_1H\\). Ядро \\(X\\):`,
|
||||
[{t:'\\(^{17}_8O\\)',c:true},{t:'\\(^{18}_9F\\)',c:false},{t:'\\(^{15}_7N\\)',c:false},{t:'\\(^{13}_6C\\)',c:false},{t:'\\(^{17}_7N\\)',c:false}],
|
||||
2,2020,'\\(A=14+4-1=17\\), \\(Z=7+2-1=8\\) — кислород');
|
||||
|
||||
q(T.quantum,
|
||||
`Фотоэффект: красная граница — это:`,
|
||||
[{t:'Минимальная частота, при которой возможен фотоэффект',c:true},{t:'Максимальная длина волны, не вызывающая фотоэффект',c:false},{t:'Максимальная скорость фотоэлектронов',c:false},{t:'Цвет излучения металла',c:false},{t:'Порог энергии кванта',c:false}],
|
||||
1,2019,'Красная граница: \\(\\nu_0=A/h\\)');
|
||||
|
||||
q(T.quantum,
|
||||
`Энергия связи нуклонов в ядре — это:`,
|
||||
[{t:'Работа, необходимая для полного расщепления ядра на нуклоны',c:true},{t:'Энергия, выделяемая при слиянии ядер',c:false},{t:'Кинетическая энергия нуклонов',c:false},{t:'Энергия электронной оболочки',c:false},{t:'Энергия гамма-излучения',c:false}],
|
||||
1,2022,'Энергия связи = минимальная работа против ядерных сил');
|
||||
|
||||
q(T.quantum,
|
||||
`\\(\\alpha\\)-излучение отклоняется в магнитном поле. Это объясняется:`,
|
||||
[{t:'\\(\\alpha\\)-частица имеет заряд \\(+2e\\)',c:true},{t:'Большой массой',c:false},{t:'Высокой скоростью',c:false},{t:'Нейтральностью',c:false},{t:'Волновыми свойствами',c:false}],
|
||||
1,2020,'Сила Лоренца действует на движущийся заряд');
|
||||
|
||||
q(T.quantum,
|
||||
`В реакции деления тяжёлого ядра выделяется энергия вследствие:`,
|
||||
[{t:'Дефекта масс',c:true},{t:'Выброса фотонов',c:false},{t:'Нагрева нейтронов',c:false},{t:'Химических реакций',c:false},{t:'Давления радиации',c:false}],
|
||||
2,2023,'\\(E=\\Delta m c^2\\) — дефект масс');
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// КОЛЕБАНИЯ И ВОЛНЫ (topic 40)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
|
||||
q(T.waves,
|
||||
`Длина волны, если скорость 340 м/с, частота 85 Гц:`,
|
||||
[{t:'\\(4\\) м',c:true},{t:'\\(0{,}25\\) м',c:false},{t:'\\(28900\\) м',c:false},{t:'\\(2\\) м',c:false},{t:'\\(8\\) м',c:false}],
|
||||
1,2018,'\\(\\lambda=v/f=340/85=4\\) м');
|
||||
|
||||
q(T.waves,
|
||||
`Частота колебаний маятника: 5 колебаний за 10 с:`,
|
||||
[{t:'\\(0{,}5\\) Гц',c:true},{t:'\\(5\\) Гц',c:false},{t:'\\(2\\) Гц',c:false},{t:'\\(50\\) Гц',c:false},{t:'\\(0{,}2\\) Гц',c:false}],
|
||||
1,2018,'\\(f=N/t=5/10=0{,}5\\) Гц');
|
||||
|
||||
q(T.waves,
|
||||
`Период математического маятника зависит от:`,
|
||||
[{t:'Длины нити и \\(g\\)',c:true},{t:'Массы груза',c:false},{t:'Амплитуды колебаний',c:false},{t:'Материала нити',c:false},{t:'Плотности воздуха',c:false}],
|
||||
1,2019,'\\(T=2\\pi\\sqrt{L/g}\\)');
|
||||
|
||||
q(T.waves,
|
||||
`Скорость поперечных волн на струне зависит от:`,
|
||||
[{t:'Натяжения и линейной плотности струны',c:true},{t:'Только амплитуды',c:false},{t:'Частоты колебаний',c:false},{t:'Длины волны',c:false},{t:'Температуры',c:false}],
|
||||
2,2019,'\\(v=\\sqrt{T/\\mu}\\), где \\(T\\) — натяжение, \\(\\mu\\) — линейная масса');
|
||||
|
||||
q(T.waves,
|
||||
`При увеличении длины математического маятника в 4 раза период:`,
|
||||
[{t:'Увеличится в 2 раза',c:true},{t:'Уменьшится в 2 раза',c:false},{t:'Увеличится в 4 раза',c:false},{t:'Не изменится',c:false},{t:'Уменьшится в 4 раза',c:false}],
|
||||
1,2020,'\\(T\\sim\\sqrt{L}\\): \\(L\\) ×4 → \\(T\\) ×2');
|
||||
|
||||
q(T.waves,
|
||||
`Резонанс — это явление:`,
|
||||
[{t:'Резкое возрастание амплитуды вынужденных колебаний при совпадении частот',c:true},{t:'Затухание собственных колебаний',c:false},{t:'Отражение волн',c:false},{t:'Интерференция звука',c:false},{t:'Дифракция звука',c:false}],
|
||||
1,2020,'Резонанс: \\(\\omega_{вынужд}=\\omega_0\\)');
|
||||
|
||||
q(T.waves,
|
||||
`Звуковая волна — это волна:`,
|
||||
[{t:'Продольная',c:true},{t:'Поперечная',c:false},{t:'Электромагнитная',c:false},{t:'Световая',c:false},{t:'Стоячая',c:false}],
|
||||
1,2022,'В звуке колебания частиц вдоль направления распространения — продольная');
|
||||
|
||||
q(T.waves,
|
||||
`Эффект Доплера: источник звука удаляется от наблюдателя. Наблюдаемая частота:`,
|
||||
[{t:'Меньше, чем у источника',c:true},{t:'Больше',c:false},{t:'Равна частоте источника',c:false},{t:'Зависит от громкости',c:false},{t:'Равна нулю',c:false}],
|
||||
1,2022,'При удалении источника: \\(f<f_0\\)');
|
||||
|
||||
q(T.waves,
|
||||
`Стоячая волна образуется при:`,
|
||||
[{t:'Наложении двух одинаковых волн, распространяющихся навстречу',c:true},{t:'Отражении волны от свободного конца',c:false},{t:'Дифракции волны',c:false},{t:'Поляризации',c:false},{t:'Преломлении',c:false}],
|
||||
2,2023,'Суперпозиция двух бегущих волн, идущих навстречу');
|
||||
|
||||
q(T.waves,
|
||||
`Маятник с периодом 2 с. При какой длине нити (\\(g=10\\) м/с²)?`,
|
||||
[{t:'\\(1\\) м',c:true},{t:'\\(2\\) м',c:false},{t:'\\(0{,}5\\) м',c:false},{t:'\\(4\\) м',c:false},{t:'\\(0{,}25\\) м',c:false}],
|
||||
2,2019,'\\(L=g(T/2\\pi)^2=10\\cdot(2/2\\pi)^2=10/(\\pi^2)\\approx1\\) м');
|
||||
|
||||
}); // end transaction
|
||||
|
||||
run();
|
||||
console.log(`✓ Добавлено: ${added}, пропущено (дубли): ${skipped}`);
|
||||
@@ -0,0 +1,288 @@
|
||||
'use strict';
|
||||
const db = require('../src/db/db');
|
||||
const PHYS_ID = 4;
|
||||
const T = {
|
||||
kinem:29, dynam:30, cons:31, mol:32, thermo:33,
|
||||
electro:34, dc:35, magnet:36, emf:37, optics:38, quantum:39, waves:40,
|
||||
};
|
||||
const existingKeys = new Set(
|
||||
db.prepare('SELECT text FROM questions WHERE subject_id=4').all()
|
||||
.map(q=>q.text.slice(0,80).trim())
|
||||
);
|
||||
let added=0, skipped=0;
|
||||
const iQ=db.prepare(`INSERT INTO questions(subject_id,topic_id,text,type,difficulty,year,explanation)VALUES(?,?,?,?,?,?,?)`);
|
||||
const iO=db.prepare(`INSERT INTO options(question_id,text,is_correct,order_index)VALUES(?,?,?,?)`);
|
||||
function q(tid,text,opts,d,yr,ex,type='single'){
|
||||
const k=text.slice(0,80).trim();
|
||||
if(existingKeys.has(k)){skipped++;return;}
|
||||
existingKeys.add(k);
|
||||
const r=iQ.run(PHYS_ID,tid,text,type,d,yr||null,ex||null);
|
||||
opts.forEach((o,i)=>iO.run(r.lastInsertRowid,o.t,o.c?1:0,i));
|
||||
added++;
|
||||
}
|
||||
const run=db.transaction(()=>{
|
||||
|
||||
// ══ КИНЕМАТИКА ══
|
||||
q(T.kinem,`Тело брошено вертикально вверх с \\(v_0=25\\) м/с (\\(g=10\\) м/с²). Высота через 3 с:`,
|
||||
[{t:'\\(30\\) м',c:true},{t:'\\(25\\) м',c:false},{t:'\\(45\\) м',c:false},{t:'\\(15\\) м',c:false},{t:'\\(10\\) м',c:false}],2,2018,'\\(h=v_0t-gt^2/2=75-45=30\\) м');
|
||||
|
||||
q(T.kinem,`Скорость тела при равноускоренном движении: \\(a=5\\) м/с², \\(v_0=0\\), \\(t=6\\) с:`,
|
||||
[{t:'\\(30\\) м/с',c:true},{t:'\\(11\\) м/с',c:false},{t:'\\(5\\) м/с',c:false},{t:'\\(90\\) м/с',c:false},{t:'\\(1\\) м/с',c:false}],1,2018,'\\(v=at=5\\cdot6=30\\) м/с');
|
||||
|
||||
q(T.kinem,`Тело движется равномерно: за 2 мин прошло 600 м. Скорость в км/ч:`,
|
||||
[{t:'\\(18\\) км/ч',c:true},{t:'\\(300\\) км/ч',c:false},{t:'\\(5\\) км/ч',c:false},{t:'\\(5{,}4\\) км/ч',c:false},{t:'\\(10\\) км/ч',c:false}],1,2019,'\\(v=600/120=5\\) м/с \\(=18\\) км/ч');
|
||||
|
||||
q(T.kinem,`Камень бросают горизонтально со скоростью 20 м/с с высоты 20 м. Скорость при ударе (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(20\\sqrt{2}\\approx28{,}3\\) м/с',c:true},{t:'\\(20\\) м/с',c:false},{t:'\\(40\\) м/с',c:false},{t:'\\(20+20=40\\) м/с',c:false},{t:'\\(10\\) м/с',c:false}],3,2019,'\\(t=\\sqrt{2h/g}=2\\) с. \\(v_y=gt=20\\) м/с. \\(v=\\sqrt{20^2+20^2}=20\\sqrt2\\)');
|
||||
|
||||
q(T.kinem,`Ускорение при равномерном движении по окружности (\\(v=6\\) м/с, \\(R=9\\) м):`,
|
||||
[{t:'\\(4\\) м/с²',c:true},{t:'\\(54\\) м/с²',c:false},{t:'\\(1{,}5\\) м/с²',c:false},{t:'\\(3\\) м/с²',c:false},{t:'\\(2\\) м/с²',c:false}],1,2020,'\\(a_c=v^2/R=36/9=4\\) м/с²');
|
||||
|
||||
q(T.kinem,`Автомобиль тормозит с \\(a=5\\) м/с² от \\(v_0=30\\) м/с. Тормозной путь:`,
|
||||
[{t:'\\(90\\) м',c:true},{t:'\\(6\\) м',c:false},{t:'\\(150\\) м',c:false},{t:'\\(300\\) м',c:false},{t:'\\(45\\) м',c:false}],2,2020,'\\(s=v_0^2/(2a)=900/10=90\\) м');
|
||||
|
||||
q(T.kinem,`Тело падает вертикально. Через 4 с скорость (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(40\\) м/с',c:true},{t:'\\(80\\) м/с',c:false},{t:'\\(160\\) м/с',c:false},{t:'\\(20\\) м/с',c:false},{t:'\\(10\\) м/с',c:false}],1,2022,'\\(v=gt=40\\) м/с');
|
||||
|
||||
q(T.kinem,`Уравнение движения \\(x=10+4t-t^2\\). В какой момент тело остановится?`,
|
||||
[{t:'\\(t=2\\) с',c:true},{t:'\\(t=4\\) с',c:false},{t:'\\(t=10\\) с',c:false},{t:'\\(t=1\\) с',c:false},{t:'\\(t=5\\) с',c:false}],2,2019,'\\(v=4-2t=0\\Rightarrow t=2\\) с');
|
||||
|
||||
q(T.kinem,`Точка на краю колеса радиуса 0,3 м, вращающегося с \\(n=10\\) об/с. Линейная скорость:`,
|
||||
[{t:'\\(6\\pi\\) м/с',c:true},{t:'\\(3\\) м/с',c:false},{t:'\\(\\pi\\) м/с',c:false},{t:'\\(60\\pi\\) м/с',c:false},{t:'\\(20\\pi\\) м/с',c:false}],2,2023,'\\(v=2\\pi nR=2\\pi\\cdot10\\cdot0{,}3=6\\pi\\) м/с');
|
||||
|
||||
// ══ ДИНАМИКА ══
|
||||
q(T.dynam,`Если скорость тела равномерна, то:`,
|
||||
[{t:'Суммарная сила равна нулю',c:true},{t:'Масса тела нулевая',c:false},{t:'Силы трения нет',c:false},{t:'Тело не движется',c:false},{t:'Ускорение велико',c:false}],1,2018,'Первый закон Ньютона: равномерное движение ⟹ F⃗_рез=0');
|
||||
|
||||
q(T.dynam,`Тело на горизонтальной поверхности с \\(\\mu=0{,}4\\), \\(m=5\\) кг, \\(g=10\\) м/с². Сила трения покоя (максимальная):`,
|
||||
[{t:'\\(20\\) Н',c:true},{t:'\\(50\\) Н',c:false},{t:'\\(4\\) Н',c:false},{t:'\\(2\\) Н',c:false},{t:'\\(40\\) Н',c:false}],1,2018,'\\(f=\\mu mg=0{,}4\\cdot5\\cdot10=20\\) Н');
|
||||
|
||||
q(T.dynam,`Два тела (2 кг и 3 кг) связаны нитью через блок. Ускорение системы (\\(g=10\\) м/с²):`,
|
||||
[{t:'\\(2\\) м/с²',c:true},{t:'\\(5\\) м/с²',c:false},{t:'\\(1\\) м/с²',c:false},{t:'\\(10\\) м/с²',c:false},{t:'\\(3\\) м/с²',c:false}],2,2019,'\\(a=(m_2-m_1)g/(m_1+m_2)=(3-2)\\cdot10/5=2\\) м/с²');
|
||||
|
||||
q(T.dynam,`Сила гравитации Земли на спутник: Земля \\(M=6\\cdot10^{24}\\) кг, спутник \\(m=100\\) кг, \\(r=7\\cdot10^6\\) м, \\(G=6{,}67\\cdot10^{-11}\\):`,
|
||||
[{t:'\\(\\approx820\\) Н',c:true},{t:'\\(1000\\) Н',c:false},{t:'\\(500\\) Н',c:false},{t:'\\(1500\\) Н',c:false},{t:'\\(9800\\) Н',c:false}],2,2020,'\\(F=GMm/r^2=6{,}67\\cdot10^{-11}\\cdot6\\cdot10^{24}\\cdot100/(49\\cdot10^{12})\\approx820\\) Н');
|
||||
|
||||
q(T.dynam,`Какая сила удерживает автомобиль (\\(m=1500\\) кг) на повороте \\(R=50\\) м при \\(v=10\\) м/с?`,
|
||||
[{t:'\\(3000\\) Н',c:true},{t:'\\(300\\) Н',c:false},{t:'\\(30000\\) Н',c:false},{t:'\\(750\\) Н',c:false},{t:'\\(1500\\) Н',c:false}],2,2019,'\\(F=mv^2/R=1500\\cdot100/50=3000\\) Н');
|
||||
|
||||
q(T.dynam,`Вес человека (70 кг) в лифте, движущемся вверх с \\(a=2\\) м/с², \\(g=10\\) м/с²:`,
|
||||
[{t:'\\(840\\) Н',c:true},{t:'\\(700\\) Н',c:false},{t:'\\(560\\) Н',c:false},{t:'\\(140\\) Н',c:false},{t:'\\(980\\) Н',c:false}],2,2020,'\\(N=m(g+a)=70\\cdot12=840\\) Н');
|
||||
|
||||
q(T.dynam,`Тело на горизонтальной плоскости: сила 30 Н под углом 60° к горизонту. Горизонтальная составляющая:`,
|
||||
[{t:'\\(15\\) Н',c:true},{t:'\\(15\\sqrt3\\) Н',c:false},{t:'\\(26\\) Н',c:false},{t:'\\(30\\) Н',c:false},{t:'\\(10\\) Н',c:false}],2,2023,'\\(F_x=F\\cos60°=30\\cdot0{,}5=15\\) Н');
|
||||
|
||||
// ══ ЗАКОНЫ СОХРАНЕНИЯ ══
|
||||
q(T.cons,`Тело массой 3 кг движется со скоростью 4 м/с. Кинетическая энергия:`,
|
||||
[{t:'\\(24\\) Дж',c:true},{t:'\\(12\\) Дж',c:false},{t:'\\(48\\) Дж',c:false},{t:'\\(6\\) Дж',c:false},{t:'\\(36\\) Дж',c:false}],1,2018,'\\(E_k=mv^2/2=3\\cdot16/2=24\\) Дж');
|
||||
|
||||
q(T.cons,`При абсолютно упругом ударе сохраняются:`,
|
||||
[{t:'И импульс, и кинетическая энергия',c:true},{t:'Только импульс',c:false},{t:'Только энергия',c:false},{t:'Ни то, ни другое',c:false},{t:'Только скорости',c:false}],1,2018,'Определение упругого удара');
|
||||
|
||||
q(T.cons,`Тело массой 2 кг поднимают на высоту 5 м с постоянной скоростью (\\(g=10\\) м/с²). Работа силы тяги:`,
|
||||
[{t:'\\(100\\) Дж',c:true},{t:'\\(-100\\) Дж',c:false},{t:'\\(0\\) Дж',c:false},{t:'\\(50\\) Дж',c:false},{t:'\\(200\\) Дж',c:false}],1,2019,'\\(A=mgh=2\\cdot10\\cdot5=100\\) Дж');
|
||||
|
||||
q(T.cons,`Шар (1 кг, 3 м/с) и шар (2 кг, покой). Абсолютно неупругий удар. Скорость:`,
|
||||
[{t:'\\(1\\) м/с',c:true},{t:'\\(3\\) м/с',c:false},{t:'\\(1{,}5\\) м/с',c:false},{t:'\\(0{,}5\\) м/с',c:false},{t:'\\(2\\) м/с',c:false}],2,2019,'\\(mv_0=(m+M)v\\Rightarrow v=3/3=1\\) м/с');
|
||||
|
||||
q(T.cons,`Пружина с жёсткостью 200 Н/м сжата на 5 см. Потенциальная энергия:`,
|
||||
[{t:'\\(0{,}25\\) Дж',c:true},{t:'\\(10\\) Дж',c:false},{t:'\\(25\\) Дж',c:false},{t:'\\(5\\) Дж',c:false},{t:'\\(0{,}05\\) Дж',c:false}],1,2020,'\\(E=kx^2/2=200\\cdot(0{,}05)^2/2=0{,}25\\) Дж');
|
||||
|
||||
q(T.cons,`Машина мощностью 40 кВт движется равномерно. Сила тяги при скорости 20 м/с:`,
|
||||
[{t:'\\(2000\\) Н',c:true},{t:'\\(800000\\) Н',c:false},{t:'\\(200\\) Н',c:false},{t:'\\(20\\) Н',c:false},{t:'\\(8000\\) Н',c:false}],1,2020,'\\(F=P/v=40000/20=2000\\) Н');
|
||||
|
||||
q(T.cons,`Снаряд 10 кг летит горизонтально 300 м/с и разрывается. Первый осколок (6 кг) улетает со скоростью 500 м/с в том же направлении. Скорость второго (4 кг):`,
|
||||
[{t:'\\(0\\) м/с',c:true},{t:'\\(150\\) м/с',c:false},{t:'\\(-100\\) м/с',c:false},{t:'\\(-75\\) м/с',c:false},{t:'\\(100\\) м/с',c:false}],3,2022,'\\(10\\cdot300=6\\cdot500+4v_2\\Rightarrow3000=3000+4v_2\\Rightarrow v_2=0\\)');
|
||||
|
||||
// ══ МОЛЕКУЛЯРНАЯ ФИЗИКА ══
|
||||
q(T.mol,`Газ при 0°C и \\(2\\cdot10^5\\) Па занимает 3 л. Если его нагреть до 273°C при том же давлении, объём:`,
|
||||
[{t:'\\(6\\) л',c:true},{t:'\\(9\\) л',c:false},{t:'\\(4{,}5\\) л',c:false},{t:'\\(1{,}5\\) л',c:false},{t:'\\(3\\) л',c:false}],2,2018,'\\(V_2=V_1T_2/T_1=3\\cdot546/273=6\\) л');
|
||||
|
||||
q(T.mol,`Среднеквадратичная скорость молекул кислорода при 0°C (\\(M=0{,}032\\) кг/моль, \\(R=8{,}31\\)):`,
|
||||
[{t:'\\(461\\) м/с',c:true},{t:'\\(920\\) м/с',c:false},{t:'\\(230\\) м/с',c:false},{t:'\\(1500\\) м/с',c:false},{t:'\\(100\\) м/с',c:false}],3,2019,'\\(v_{кв}=\\sqrt{3RT/M}=\\sqrt{3\\cdot8{,}31\\cdot273/0{,}032}\\approx461\\) м/с');
|
||||
|
||||
q(T.mol,`Уравнение состояния идеального газа: \\(pV=\\nu RT\\). Что такое \\(R\\)?`,
|
||||
[{t:'Универсальная газовая постоянная (8,31 Дж/(моль·К))',c:true},{t:'Сопротивление',c:false},{t:'Радиус молекулы',c:false},{t:'Постоянная Авогадро',c:false},{t:'Постоянная Больцмана',c:false}],1,2018,'\\(R=8{,}31\\) Дж/(моль·К)');
|
||||
|
||||
q(T.mol,`При изотермическом сжатии газ объёма 4 л до 1 л при давлении 100 кПа. Новое давление:`,
|
||||
[{t:'\\(400\\) кПа',c:true},{t:'\\(25\\) кПа',c:false},{t:'\\(200\\) кПа',c:false},{t:'\\(100\\) кПа',c:false},{t:'\\(4000\\) кПа',c:false}],1,2019,'\\(p_1V_1=p_2V_2\\Rightarrow p_2=100\\cdot4/1=400\\) кПа');
|
||||
|
||||
q(T.mol,`Какой процесс изображён прямой линией на графике \\(p-T\\) при \\(V=const\\)?`,
|
||||
[{t:'Изохорный',c:true},{t:'Изобарный',c:false},{t:'Изотермный',c:false},{t:'Адиабатический',c:false},{t:'Политропный',c:false}],1,2020,'При \\(V=const\\): \\(p=\\nu R/V\\cdot T\\) — прямая через начало координат');
|
||||
|
||||
q(T.mol,`Концентрация молекул идеального газа связана с давлением и температурой:`,
|
||||
[{t:'\\(n=p/(kT)\\)',c:true},{t:'\\(n=kT/p\\)',c:false},{t:'\\(n=p\\cdot T/k\\)',c:false},{t:'\\(n=pk/T\\)',c:false},{t:'\\(n=RT/p\\)',c:false}],2,2022,'\\(p=nkT\\Rightarrow n=p/(kT)\\)');
|
||||
|
||||
q(T.mol,`Масса молекулы азота \\(N_2\\) (\\(M=28\\) г/моль):`,
|
||||
[{t:'\\(4{,}65\\cdot10^{-26}\\) кг',c:true},{t:'\\(28\\cdot10^{-3}\\) кг',c:false},{t:'\\(14\\cdot10^{-27}\\) кг',c:false},{t:'\\(4{,}65\\cdot10^{-23}\\) кг',c:false},{t:'\\(10^{-26}\\) кг',c:false}],2,2023,'\\(m=M/N_A=28\\cdot10^{-3}/(6{,}02\\cdot10^{23})\\approx4{,}65\\cdot10^{-26}\\) кг');
|
||||
|
||||
// ══ ТЕРМОДИНАМИКА ══
|
||||
q(T.thermo,`При изобарном расширении газ получает 500 Дж и совершает работу 300 Дж. Изменение внутренней энергии:`,
|
||||
[{t:'\\(200\\) Дж',c:true},{t:'\\(800\\) Дж',c:false},{t:'\\(-200\\) Дж',c:false},{t:'\\(300\\) Дж',c:false},{t:'\\(500\\) Дж',c:false}],1,2018,'\\(\\Delta U=Q-A=500-300=200\\) Дж');
|
||||
|
||||
q(T.thermo,`Теплота плавления льда: \\(m=200\\) г, \\(L=334\\) кДж/кг:`,
|
||||
[{t:'\\(66{,}8\\) кДж',c:true},{t:'\\(334\\) кДж',c:false},{t:'\\(167\\) кДж',c:false},{t:'\\(668\\) Дж',c:false},{t:'\\(6{,}68\\) кДж',c:false}],1,2019,'\\(Q=mL=0{,}2\\cdot334=66{,}8\\) кДж');
|
||||
|
||||
q(T.thermo,`КПД тепловой машины 0,25. При получении \\(Q_1=1000\\) Дж работа:`,
|
||||
[{t:'\\(250\\) Дж',c:true},{t:'\\(750\\) Дж',c:false},{t:'\\(4000\\) Дж',c:false},{t:'\\(25\\) Дж',c:false},{t:'\\(500\\) Дж',c:false}],1,2018,'\\(A=\\eta Q_1=0{,}25\\cdot1000=250\\) Дж');
|
||||
|
||||
q(T.thermo,`Нагреватель даёт 5000 Дж, холодильник получает 3500 Дж. КПД:`,
|
||||
[{t:'\\(30\\%\\)',c:true},{t:'\\(70\\%\\)',c:false},{t:'\\(50\\%\\)',c:false},{t:'\\(43\\%\\)',c:false},{t:'\\(0\\%\\)',c:false}],2,2019,'\\(A=5000-3500=1500\\). \\(\\eta=1500/5000=0{,}3=30\\%\\)');
|
||||
|
||||
q(T.thermo,`При адиабатическом сжатии газа температура:`,
|
||||
[{t:'Увеличивается',c:true},{t:'Уменьшается',c:false},{t:'Не изменяется',c:false},{t:'Сначала растёт, потом падает',c:false},{t:'Зависит от давления',c:false}],2,2022,'\\(Q=0\\), работа внешних сил → рост внутренней энергии → рост \\(T\\)');
|
||||
|
||||
q(T.thermo,`Второе начало термодинамики запрещает:`,
|
||||
[{t:'Самопроизвольный переход теплоты от холодного тела к горячему',c:true},{t:'Тепловой двигатель',c:false},{t:'Охлаждение газа',c:false},{t:'Нагрев тела',c:false},{t:'Работу при адиабате',c:false}],1,2023,'Второе начало: теплота сама по себе не переходит от холодного к горячему');
|
||||
|
||||
// ══ ЭЛЕКТРОСТАТИКА ══
|
||||
q(T.electro,`Напряжённость поля в точке между двумя равными и противоположными зарядами (направление):`,
|
||||
[{t:'От положительного к отрицательному',c:true},{t:'От отрицательного к положительному',c:false},{t:'Перпендикулярно оси',c:false},{t:'Равна нулю',c:false},{t:'Хаотична',c:false}],1,2018,'Линии поля выходят из \\(+\\) и входят в \\(-\\)');
|
||||
|
||||
q(T.electro,`Заряд \\(q=5\\) мкКл в однородном поле \\(E=4000\\) В/м. Сила на заряд:`,
|
||||
[{t:'\\(0{,}02\\) Н',c:true},{t:'\\(0{,}2\\) Н',c:false},{t:'\\(20\\) Н',c:false},{t:'\\(4005\\) Н',c:false},{t:'\\(0{,}8\\) Н',c:false}],1,2019,'\\(F=qE=5\\cdot10^{-6}\\cdot4000=0{,}02\\) Н');
|
||||
|
||||
q(T.electro,`Ёмкость плоского конденсатора увеличится, если:`,
|
||||
[{t:'Уменьшить расстояние между пластинами',c:true},{t:'Увеличить расстояние',c:false},{t:'Уменьшить площадь пластин',c:false},{t:'Удалить диэлектрик (воздух)',c:false},{t:'Уменьшить напряжение',c:false}],1,2019,'\\(C=\\varepsilon\\varepsilon_0 S/d\\): при уменьшении \\(d\\) ёмкость растёт');
|
||||
|
||||
q(T.electro,`Электрическое поле не существует:`,
|
||||
[{t:'Внутри проводника в статике',c:true},{t:'Между пластинами конденсатора',c:false},{t:'Вокруг точечного заряда',c:false},{t:'В диэлектрике',c:false},{t:'В вакууме',c:false}],1,2020,'В равновесии внутри проводника \\(E=0\\)');
|
||||
|
||||
q(T.electro,`Потенциал точки, если заряд \\(q=4\\cdot10^{-9}\\) Кл, \\(r=0{,}2\\) м (\\(k=9\\cdot10^9\\)):`,
|
||||
[{t:'\\(180\\) В',c:true},{t:'\\(1800\\) В',c:false},{t:'\\(18\\) В',c:false},{t:'\\(9\\) В',c:false},{t:'\\(90\\) В',c:false}],2,2022,'\\(\\varphi=kq/r=9\\cdot10^9\\cdot4\\cdot10^{-9}/0{,}2=180\\) В');
|
||||
|
||||
q(T.electro,`Энергия, запасённая в конденсаторе \\(C=8\\) мкФ при \\(U=500\\) В:`,
|
||||
[{t:'\\(1\\) Дж',c:true},{t:'\\(2\\) Дж',c:false},{t:'\\(0{,}5\\) Дж',c:false},{t:'\\(4000\\) Дж',c:false},{t:'\\(0{,}002\\) Дж',c:false}],2,2023,'\\(W=CU^2/2=8\\cdot10^{-6}\\cdot250000/2=1\\) Дж');
|
||||
|
||||
q(T.electro,`Заряд конденсатора 30 мкКл при напряжении 150 В. Ёмкость:`,
|
||||
[{t:'\\(0{,}2\\) мкФ',c:true},{t:'\\(4500\\) мкФ',c:false},{t:'\\(180\\) мкФ',c:false},{t:'\\(5\\) мкФ',c:false},{t:'\\(2\\) мкФ',c:false}],1,2018,'\\(C=q/U=30\\cdot10^{-6}/150=0{,}2\\cdot10^{-6}\\) Ф = 0,2 мкФ');
|
||||
|
||||
// ══ ПОСТОЯННЫЙ ТОК ══
|
||||
q(T.dc,`Напряжение 6 В, сопротивление 3 Ом. Ток:`,
|
||||
[{t:'\\(2\\) А',c:true},{t:'\\(18\\) А',c:false},{t:'\\(0{,}5\\) А',c:false},{t:'\\(3\\) А',c:false},{t:'\\(9\\) А',c:false}],1,2018,'\\(I=U/R=6/3=2\\) А');
|
||||
|
||||
q(T.dc,`Три резистора 2 Ом, 4 Ом, 6 Ом включены последовательно. Общее сопротивление:`,
|
||||
[{t:'\\(12\\) Ом',c:true},{t:'\\(4\\) Ом',c:false},{t:'\\(\\frac{4}{3}\\) Ом',c:false},{t:'\\(24\\) Ом',c:false},{t:'\\(1\\) Ом',c:false}],1,2018,'\\(R=2+4+6=12\\) Ом');
|
||||
|
||||
q(T.dc,`Три резистора 6 Ом параллельно. Общее сопротивление:`,
|
||||
[{t:'\\(2\\) Ом',c:true},{t:'\\(18\\) Ом',c:false},{t:'\\(6\\) Ом',c:false},{t:'\\(3\\) Ом',c:false},{t:'\\(0{,}5\\) Ом',c:false}],1,2019,'\\(R=6/3=2\\) Ом');
|
||||
|
||||
q(T.dc,`Источник: ЭДС=9 В, \\(r=0{,}5\\) Ом, \\(R=4{,}5\\) Ом. Напряжение на внешнем резисторе:`,
|
||||
[{t:'\\(8{,}1\\) В',c:true},{t:'\\(9\\) В',c:false},{t:'\\(7{,}2\\) В',c:false},{t:'\\(4{,}5\\) В',c:false},{t:'\\(0{,}9\\) В',c:false}],2,2019,'\\(I=9/5=1{,}8\\) А, \\(U=IR=1{,}8\\cdot4{,}5=8{,}1\\) В');
|
||||
|
||||
q(T.dc,`Проводник медный, длина 50 м, сечение 2 мм² (\\(\\rho=1{,}7\\cdot10^{-8}\\)):`,
|
||||
[{t:'\\(0{,}425\\) Ом',c:true},{t:'\\(4{,}25\\) Ом',c:false},{t:'\\(42{,}5\\) Ом',c:false},{t:'\\(0{,}0425\\) Ом',c:false},{t:'\\(17\\) Ом',c:false}],2,2020,'\\(R=\\rho l/S=1{,}7\\cdot10^{-8}\\cdot50/(2\\cdot10^{-6})=0{,}425\\) Ом');
|
||||
|
||||
q(T.dc,`Ток через нить лампы 0,5 А, сопротивление 800 Ом. Мощность лампы:`,
|
||||
[{t:'\\(200\\) Вт',c:true},{t:'\\(400\\) Вт',c:false},{t:'\\(100\\) Вт',c:false},{t:'\\(1600\\) Вт',c:false},{t:'\\(1{,}6\\) кВт',c:false}],1,2020,'\\(P=I^2R=0{,}25\\cdot800=200\\) Вт');
|
||||
|
||||
q(T.dc,`ЭДС источника 12 В, внутреннее сопротивление 2 Ом. При коротком замыкании ток:`,
|
||||
[{t:'\\(6\\) А',c:true},{t:'\\(12\\) А',c:false},{t:'\\(24\\) А',c:false},{t:'\\(0\\) А',c:false},{t:'\\(14\\) А',c:false}],2,2022,'\\(I_{кз}=\\varepsilon/r=12/2=6\\) А');
|
||||
|
||||
q(T.dc,`Мощность источника, отдаваемая во внешнюю цепь, максимальна при:`,
|
||||
[{t:'\\(R=r\\)',c:true},{t:'\\(R=0\\)',c:false},{t:'\\(R\\to\\infty\\)',c:false},{t:'\\(R=2r\\)',c:false},{t:'\\(R=r/2\\)',c:false}],3,2023,'Теорема о максимальной мощности: \\(R_{внеш}=r\\)');
|
||||
|
||||
// ══ МАГНЕТИЗМ ══
|
||||
q(T.magnet,`Электрон (\\(q=1{,}6\\cdot10^{-19}\\) Кл) влетает со скоростью \\(2\\cdot10^6\\) м/с в поле \\(B=0{,}1\\) Тл. Сила Лоренца:`,
|
||||
[{t:'\\(3{,}2\\cdot10^{-14}\\) Н',c:true},{t:'\\(3{,}2\\cdot10^{-13}\\) Н',c:false},{t:'\\(1{,}6\\cdot10^{-14}\\) Н',c:false},{t:'\\(0{,}32\\) Н',c:false},{t:'\\(10^{-14}\\) Н',c:false}],2,2018,'\\(F=qvB=1{,}6\\cdot10^{-19}\\cdot2\\cdot10^6\\cdot0{,}1=3{,}2\\cdot10^{-14}\\) Н');
|
||||
|
||||
q(T.magnet,`Если сила Ампера максимальна, угол между током и полем:`,
|
||||
[{t:'\\(90°\\)',c:true},{t:'\\(0°\\)',c:false},{t:'\\(45°\\)',c:false},{t:'\\(180°\\)',c:false},{t:'\\(60°\\)',c:false}],1,2018,'\\(F=BIl\\sin\\alpha\\), максимум при \\(\\alpha=90°\\)');
|
||||
|
||||
q(T.magnet,`Катушка с 100 витками, площадь \\(0{,}01\\) м², в поле \\(B=2\\) Тл. Магнитный поток через катушку:`,
|
||||
[{t:'\\(2\\) Вб',c:true},{t:'\\(0{,}02\\) Вб',c:false},{t:'\\(200\\) Вб',c:false},{t:'\\(0{,}2\\) Вб',c:false},{t:'\\(100\\) Вб',c:false}],2,2019,'\\(\\Phi=NBS=100\\cdot2\\cdot0{,}01=2\\) Вб');
|
||||
|
||||
q(T.magnet,`Проводник 2 м с током 5 А в поле 0,4 Тл, угол 30°. Сила Ампера:`,
|
||||
[{t:'\\(2\\) Н',c:true},{t:'\\(4\\) Н',c:false},{t:'\\(1\\) Н',c:false},{t:'\\(3{,}46\\) Н',c:false},{t:'\\(8\\) Н',c:false}],2,2019,'\\(F=BIl\\sin30°=0{,}4\\cdot5\\cdot2\\cdot0{,}5=2\\) Н');
|
||||
|
||||
q(T.magnet,`В каком случае сила Лоренца не действует на заряженную частицу?`,
|
||||
[{t:'Частица движется вдоль поля',c:true},{t:'Частица движется перпендикулярно полю',c:false},{t:'Частица движется под углом 45°',c:false},{t:'Частица в покое в поле',c:false},{t:'При высокой скорости',c:false}],1,2020,'\\(F=qvB\\sin\\alpha=0\\) при \\(\\alpha=0°\\) (вдоль поля)');
|
||||
|
||||
q(T.magnet,`Катушка с \\(L=0{,}1\\) Гн несёт ток 3 А. Энергия магнитного поля катушки:`,
|
||||
[{t:'\\(0{,}45\\) Дж',c:true},{t:'\\(0{,}3\\) Дж',c:false},{t:'\\(0{,}9\\) Дж',c:false},{t:'\\(3\\) Дж',c:false},{t:'\\(0{,}15\\) Дж',c:false}],2,2022,'\\(W=LI^2/2=0{,}1\\cdot9/2=0{,}45\\) Дж');
|
||||
|
||||
// ══ ЭЛЕКТРОМАГНИТНАЯ ИНДУКЦИЯ ══
|
||||
q(T.emf,`Правило Ленца: при усилении внешнего магнитного потока через контур, ток индукции:`,
|
||||
[{t:'Противодействует усилению потока (создаёт поле против внешнего)',c:true},{t:'Усиливает внешний поток',c:false},{t:'Не возникает',c:false},{t:'Зависит от материала контура',c:false},{t:'Направлен по полю',c:false}],1,2018,'Закон Ленца — электромагнитная инерция');
|
||||
|
||||
q(T.emf,`Прямолинейный проводник длиной 0,5 м движется со скоростью 4 м/с перпендикулярно полю 2 Тл. ЭДС:`,
|
||||
[{t:'\\(4\\) В',c:true},{t:'\\(0{,}25\\) В',c:false},{t:'\\(16\\) В',c:false},{t:'\\(1\\) В',c:false},{t:'\\(8\\) В',c:false}],2,2019,'\\(\\varepsilon=Blv=2\\cdot0{,}5\\cdot4=4\\) В');
|
||||
|
||||
q(T.emf,`Трансформатор 500/50 витков подключён к 220 В. Вторичное напряжение:`,
|
||||
[{t:'\\(22\\) В',c:true},{t:'\\(2200\\) В',c:false},{t:'\\(44\\) В',c:false},{t:'\\(11\\) В',c:false},{t:'\\(110\\) В',c:false}],1,2019,'\\(U_2=U_1\\cdot n_2/n_1=220\\cdot50/500=22\\) В');
|
||||
|
||||
q(T.emf,`Частота переменного тока 50 Гц. Период:`,
|
||||
[{t:'\\(0{,}02\\) с',c:true},{t:'\\(50\\) с',c:false},{t:'\\(2\\) с',c:false},{t:'\\(0{,}05\\) с',c:false},{t:'\\(100\\) с',c:false}],1,2020,'\\(T=1/f=1/50=0{,}02\\) с');
|
||||
|
||||
q(T.emf,`Длина радиоволны при частоте 90 МГц:`,
|
||||
[{t:'\\(3{,}33\\) м',c:true},{t:'\\(0{,}3\\) м',c:false},{t:'\\(33{,}3\\) м',c:false},{t:'\\(300\\) м',c:false},{t:'\\(3000\\) м',c:false}],2,2022,'\\(\\lambda=c/f=3\\cdot10^8/(90\\cdot10^6)=10/3\\approx3{,}33\\) м');
|
||||
|
||||
q(T.emf,`Действующее значение тока \\(i=10\\sin(314t)\\) А:`,
|
||||
[{t:'\\(\\frac{10}{\\sqrt{2}}\\approx7{,}07\\) А',c:true},{t:'\\(10\\) А',c:false},{t:'\\(5\\) А',c:false},{t:'\\(20\\) А',c:false},{t:'\\(314\\) А',c:false}],2,2023,'\\(I=I_m/\\sqrt2=10/\\sqrt2\\approx7{,}07\\) А');
|
||||
|
||||
// ══ ОПТИКА ══
|
||||
q(T.optics,`Угол падения луча на границу стекло-воздух равен критическому (\\(n=1{,}5\\)). Угол преломления:`,
|
||||
[{t:'\\(90°\\)',c:true},{t:'\\(45°\\)',c:false},{t:'\\(0°\\)',c:false},{t:'\\(60°\\)',c:false},{t:'\\(30°\\)',c:false}],2,2018,'При полном внутреннем отражении преломлённый луч идёт вдоль границы: \\(\\theta_2=90°\\)');
|
||||
|
||||
q(T.optics,`Рассеивающая линза с \\(f=-20\\) см. Оптическая сила:`,
|
||||
[{t:'\\(-5\\) дптр',c:true},{t:'\\(5\\) дптр',c:false},{t:'\\(-0{,}2\\) дптр',c:false},{t:'\\(20\\) дптр',c:false},{t:'\\(0{,}5\\) дптр',c:false}],1,2018,'\\(D=1/f=1/(-0{,}2)=-5\\) дптр (фокус в метрах)');
|
||||
|
||||
q(T.optics,`Предмет на расстоянии \\(2F\\) от собирающей линзы. Изображение:`,
|
||||
[{t:'Действительное, равное, перевёрнутое на \\(2F\\)',c:true},{t:'Мнимое, увеличенное',c:false},{t:'В фокусе',c:false},{t:'В бесконечности',c:false},{t:'Перед линзой',c:false}],2,2019,'При \\(d=2F\\): \\(v=2F\\), увеличение 1, перевёрнутое');
|
||||
|
||||
q(T.optics,`Фотон видимого света имеет наибольшую энергию при:`,
|
||||
[{t:'Фиолетовом цвете',c:true},{t:'Красном',c:false},{t:'Жёлтом',c:false},{t:'Зелёном',c:false},{t:'Синем',c:false}],1,2019,'\\(E=h\\nu\\): наибольшая \\(\\nu\\) у фиолетового (наименьшая длина волны)');
|
||||
|
||||
q(T.optics,`Луч из воды (\\(n=1{,}33\\)) выходит в воздух под углом 30° к нормали. Угол преломления:`,
|
||||
[{t:'\\(\\approx41{,}7°\\)',c:true},{t:'\\(30°\\)',c:false},{t:'\\(60°\\)',c:false},{t:'\\(90°\\)',c:false},{t:'\\(20°\\)',c:false}],2,2020,'\\(\\sin\\theta_2=n\\sin30°=1{,}33\\cdot0{,}5=0{,}665\\Rightarrow\\theta_2\\approx41{,}7°\\)');
|
||||
|
||||
q(T.optics,`Дифракционная решётка: период 2 мкм, длина волны 500 нм, 1-й максимум:`,
|
||||
[{t:'\\(\\sin\\theta=0{,}25\\)',c:true},{t:'\\(\\theta=30°\\)',c:false},{t:'\\(\\sin\\theta=0{,}5\\)',c:false},{t:'\\(\\theta=15°\\)',c:false},{t:'\\(\\theta=45°\\)',c:false}],2,2023,'\\(d\\sin\\theta=m\\lambda\\Rightarrow\\sin\\theta=500\\cdot10^{-9}/(2\\cdot10^{-6})=0{,}25\\)');
|
||||
|
||||
// ══ КВАНТОВАЯ И ЯДЕРНАЯ ФИЗИКА ══
|
||||
q(T.quantum,`Постулат Бора: атом излучает при переходе электрона:`,
|
||||
[{t:'С более высокого уровня на более низкий',c:true},{t:'С нижнего на верхний',c:false},{t:'При любом движении',c:false},{t:'При ускорении',c:false},{t:'В магнитном поле',c:false}],1,2018,'Второй постулат Бора: излучение при переходе вниз');
|
||||
|
||||
q(T.quantum,`Закон радиоактивного распада: \\(N=N_0\\cdot2^{-t/T_{1/2}}\\). Через \\(T_{1/2}\\) останется:`,
|
||||
[{t:'\\(N_0/2\\)',c:true},{t:'\\(N_0\\)',c:false},{t:'\\(N_0/4\\)',c:false},{t:'\\(0\\)',c:false},{t:'\\(2N_0\\)',c:false}],1,2018,'По определению периода полураспада');
|
||||
|
||||
q(T.quantum,`\\(\\gamma\\)-излучение — это:`,
|
||||
[{t:'Коротковолновое электромагнитное излучение',c:true},{t:'Поток электронов',c:false},{t:'Поток протонов',c:false},{t:'Поток нейтронов',c:false},{t:'Поток \\(\\alpha\\)-частиц',c:false}],1,2019,'\\(\\gamma\\)-кванты — фотоны высокой энергии');
|
||||
|
||||
q(T.quantum,`Реакция синтеза: \\(^2_1H+^3_1H\\to X+^1_0n\\). Ядро \\(X\\):`,
|
||||
[{t:'\\(^4_2He\\)',c:true},{t:'\\(^3_1H\\)',c:false},{t:'\\(^4_1H\\)',c:false},{t:'\\(^5_2He\\)',c:false},{t:'\\(^3_2He\\)',c:false}],2,2019,'\\(A=2+3-1=4\\), \\(Z=1+1-0=2\\) — гелий');
|
||||
|
||||
q(T.quantum,`Работа выхода электрона 3,36 эВ (\\(h=6{,}63\\cdot10^{-34}\\)). Красная граница:`,
|
||||
[{t:'\\(\\approx370\\) нм',c:true},{t:'\\(600\\) нм',c:false},{t:'\\(700\\) нм',c:false},{t:'\\(250\\) нм',c:false},{t:'\\(500\\) нм',c:false}],3,2020,'\\(\\nu_0=A/h=3{,}36\\cdot1{,}6\\cdot10^{-19}/(6{,}63\\cdot10^{-34})\\approx8{,}1\\cdot10^{14}\\) Гц. \\(\\lambda=c/\\nu\\approx370\\) нм');
|
||||
|
||||
q(T.quantum,`Число нейтронов в ядре \\(^{235}_{92}U\\):`,
|
||||
[{t:'\\(143\\)',c:true},{t:'\\(92\\)',c:false},{t:'\\(235\\)',c:false},{t:'\\(327\\)',c:false},{t:'\\(147\\)',c:false}],1,2022,'\\(N=A-Z=235-92=143\\)');
|
||||
|
||||
q(T.quantum,`Линейчатый спектр испускания атома водорода объясняет:`,
|
||||
[{t:'Теория Бора: квантованные переходы',c:true},{t:'Тепловое излучение',c:false},{t:'Рассеяние фотонов',c:false},{t:'Эффект Комптона',c:false},{t:'Радиоактивность',c:false}],1,2023,'Постулаты Бора объясняют дискретный спектр');
|
||||
|
||||
// ══ КОЛЕБАНИЯ И ВОЛНЫ ══
|
||||
q(T.waves,`Уравнение колебания: \\(x=5\\cos(2\\pi t)\\) см. Период:`,
|
||||
[{t:'\\(1\\) с',c:true},{t:'\\(2\\pi\\) с',c:false},{t:'\\(0{,}5\\) с',c:false},{t:'\\(5\\) с',c:false},{t:'\\(10\\pi\\) с',c:false}],1,2018,'\\(\\omega=2\\pi/T\\Rightarrow T=1\\) с');
|
||||
|
||||
q(T.waves,`Звуковая волна 340 м/с, длина 0,85 м. Частота:`,
|
||||
[{t:'\\(400\\) Гц',c:true},{t:'\\(289\\) Гц',c:false},{t:'\\(4000\\) Гц',c:false},{t:'\\(85\\) Гц',c:false},{t:'\\(850\\) Гц',c:false}],1,2018,'\\(f=v/\\lambda=340/0{,}85=400\\) Гц');
|
||||
|
||||
q(T.waves,`Колебательный контур: \\(L=4\\) мГн, \\(C=4\\) нФ. Резонансная частота:`,
|
||||
[{t:'\\(\\frac{1}{4\\pi}\\cdot10^6\\approx79{,}6\\) кГц',c:true},{t:'\\(1\\) МГц',c:false},{t:'\\(250\\) кГц',c:false},{t:'\\(4\\) МГц',c:false},{t:'\\(0{,}25\\) МГц',c:false}],3,2019,'\\(f=1/(2\\pi\\sqrt{LC})=1/(2\\pi\\sqrt{16\\cdot10^{-12}})=1/(2\\pi\\cdot4\\cdot10^{-6})\\approx39{,}8\\) кГц');
|
||||
|
||||
q(T.waves,`Маятник длиной 0,25 м (\\(g=10\\) м/с²). Частота колебаний:`,
|
||||
[{t:'\\(1\\) Гц',c:true},{t:'\\(2\\) Гц',c:false},{t:'\\(\\pi\\) Гц',c:false},{t:'\\(0{,}5\\) Гц',c:false},{t:'\\(4\\) Гц',c:false}],2,2019,'\\(T=2\\pi\\sqrt{0{,}25/10}=\\pi/\\sqrt{10}\\approx1\\) с, \\(f\\approx1\\) Гц');
|
||||
|
||||
q(T.waves,`Вынужденные колебания: амплитуда максимальна при:`,
|
||||
[{t:'Резонансе (частота внешней силы = собственной)',c:true},{t:'Максимальном затухании',c:false},{t:'Нулевой скорости',c:false},{t:'Увеличении массы',c:false},{t:'Любой частоте',c:false}],1,2020,'Определение резонанса');
|
||||
|
||||
q(T.waves,`Скорость распространения волн не зависит от:`,
|
||||
[{t:'Амплитуды',c:true},{t:'Среды',c:false},{t:'Температуры среды',c:false},{t:'Давления (для газов)',c:false},{t:'Типа волны',c:false}],1,2022,'Скорость волны — свойство среды, не зависит от амплитуды');
|
||||
|
||||
q(T.waves,`Интенсивность звука пропорциональна квадрату:`,
|
||||
[{t:'Амплитуды',c:true},{t:'Длины волны',c:false},{t:'Частоты',c:false},{t:'Скорости звука',c:false},{t:'Плотности среды',c:false}],2,2023,'\\(I\\sim A^2\\)');
|
||||
|
||||
});
|
||||
run();
|
||||
console.log(`Физика ✓ Добавлено: ${added}, пропущено: ${skipped}`);
|
||||
@@ -610,47 +610,105 @@ function clearErrorLog(req, res) {
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { DB_PATH, UPLOADS_DIR } = require('../config');
|
||||
const { execSync } = require('child_process');
|
||||
const { monitorEventLoopDelay } = require('perf_hooks');
|
||||
const sse = require('../sse');
|
||||
const { DB_PATH, UPLOADS_DIR, NODE_ENV } = require('../config');
|
||||
|
||||
// Монитор лага event-loop (включается один раз при загрузке модуля).
|
||||
const _eluMonitor = monitorEventLoopDelay({ resolution: 20 });
|
||||
_eluMonitor.enable();
|
||||
|
||||
// Версия приложения + git-commit (кэшируются один раз).
|
||||
const _appVersion = (() => { try { return require('../../package.json').version; } catch { return '?'; } })();
|
||||
const _gitCommit = (() => {
|
||||
try { return execSync('git rev-parse --short HEAD', { cwd: __dirname, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); }
|
||||
catch { return null; }
|
||||
})();
|
||||
|
||||
function _dirSize(dir) {
|
||||
let total = 0;
|
||||
try { for (const f of fs.readdirSync(dir)) { try { total += fs.statSync(path.join(dir, f)).size; } catch {} } } catch {}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Топ таблиц БД по числу строк (исключая служебные sqlite_* и _migrations).
|
||||
function _dbTables() {
|
||||
try {
|
||||
const tables = db.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '\\_%' ESCAPE '\\'"
|
||||
).all();
|
||||
const rows = tables.map(t => {
|
||||
let n = 0;
|
||||
try { n = db.prepare(`SELECT COUNT(*) AS n FROM "${t.name}"`).get().n; } catch {}
|
||||
return { name: t.name, rows: n };
|
||||
});
|
||||
rows.sort((a, b) => b.rows - a.rows);
|
||||
return rows.slice(0, 12);
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function getHealth(_req, res) {
|
||||
const uptimeSec = process.uptime();
|
||||
let dbSizeBytes = 0;
|
||||
try { dbSizeBytes = fs.statSync(DB_PATH).size; } catch {}
|
||||
let uploadsSizeBytes = 0;
|
||||
try {
|
||||
const files = fs.readdirSync(UPLOADS_DIR);
|
||||
for (const f of files) {
|
||||
try { uploadsSizeBytes += fs.statSync(path.join(UPLOADS_DIR, f)).size; } catch {}
|
||||
}
|
||||
} catch {}
|
||||
const mem = process.memoryUsage();
|
||||
const dbSizeBytes = (() => { try { return fs.statSync(DB_PATH).size; } catch { return 0; } })();
|
||||
const walSizeBytes = (() => { try { return fs.statSync(DB_PATH + '-wal').size; } catch { return 0; } })();
|
||||
const uploadsSizeBytes = _dirSize(UPLOADS_DIR);
|
||||
|
||||
const totalUsers = db.prepare('SELECT COUNT(*) AS n FROM users').get().n;
|
||||
const todaySessions = db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= date('now')").get().n;
|
||||
const totalSessions = db.prepare('SELECT COUNT(*) AS n FROM test_sessions').get().n;
|
||||
// Свободное место на разделе, где лежит БД.
|
||||
let disk = null;
|
||||
try { const s = fs.statfsSync(path.dirname(path.resolve(DB_PATH))); disk = { freeBytes: s.bavail * s.bsize, totalBytes: s.blocks * s.bsize }; } catch {}
|
||||
|
||||
const totalMem = os.totalmem(), freeMem = os.freemem();
|
||||
const memPercent = totalMem ? (totalMem - freeMem) / totalMem : 0;
|
||||
const eventLoopLagMs = _eluMonitor.mean / 1e6;
|
||||
|
||||
const totalUsers = db.prepare('SELECT COUNT(*) AS n FROM users').get().n;
|
||||
const todaySessions = db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= date('now')").get().n;
|
||||
const totalSessions = db.prepare('SELECT COUNT(*) AS n FROM test_sessions').get().n;
|
||||
const totalQuestions = db.prepare('SELECT COUNT(*) AS n FROM questions').get().n;
|
||||
const recentErrors = db.prepare("SELECT COUNT(*) AS n FROM error_log WHERE created_at >= datetime('now', '-24 hours')").get().n;
|
||||
const recentErrors = db.prepare("SELECT COUNT(*) AS n FROM error_log WHERE created_at >= datetime('now', '-24 hours')").get().n;
|
||||
|
||||
let sseStats = { users: 0, guests: 0, connections: 0 };
|
||||
try { sseStats = sse.stats(); } catch {}
|
||||
|
||||
// Вердикт здоровья по порогам.
|
||||
const reasons = [];
|
||||
let status = 'ok';
|
||||
const warn = (m) => { reasons.push(m); if (status === 'ok') status = 'warning'; };
|
||||
const crit = (m) => { reasons.push(m); status = 'critical'; };
|
||||
if (memPercent > 0.92) crit(`Память ${Math.round(memPercent * 100)}%`);
|
||||
else if (memPercent > 0.80) warn(`Память ${Math.round(memPercent * 100)}%`);
|
||||
if (disk) {
|
||||
if (disk.freeBytes < 500e6) crit('Мало места на диске (<500 МБ)');
|
||||
else if (disk.freeBytes < 2e9) warn('Места на диске <2 ГБ');
|
||||
}
|
||||
if (recentErrors > 50) crit(`${recentErrors} ошибок за 24ч`);
|
||||
else if (recentErrors > 5) warn(`${recentErrors} ошибок за 24ч`);
|
||||
if (eventLoopLagMs > 200) crit(`Лаг event-loop ${eventLoopLagMs.toFixed(0)} мс`);
|
||||
else if (eventLoopLagMs > 70) warn(`Лаг event-loop ${eventLoopLagMs.toFixed(0)} мс`);
|
||||
if (dbSizeBytes > 1.5e9) warn('БД >1.5 ГБ');
|
||||
|
||||
res.json({
|
||||
status, reasons,
|
||||
uptime: uptimeSec,
|
||||
memory: {
|
||||
rss: process.memoryUsage().rss,
|
||||
heapUsed: process.memoryUsage().heapUsed,
|
||||
},
|
||||
db: {
|
||||
sizeBytes: dbSizeBytes,
|
||||
totalUsers,
|
||||
totalSessions,
|
||||
todaySessions,
|
||||
totalQuestions,
|
||||
},
|
||||
uploads: {
|
||||
sizeBytes: uploadsSizeBytes,
|
||||
},
|
||||
startedAt: new Date(Date.now() - uptimeSec * 1000).toISOString(),
|
||||
memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal },
|
||||
memPercent, eventLoopLagMs,
|
||||
loadavg: os.loadavg(),
|
||||
disk,
|
||||
db: { sizeBytes: dbSizeBytes, walBytes: walSizeBytes, totalUsers, totalSessions, todaySessions, totalQuestions, tables: _dbTables() },
|
||||
uploads: { sizeBytes: uploadsSizeBytes },
|
||||
sse: sseStats,
|
||||
node: process.version,
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
cpus: os.cpus().length,
|
||||
freeMem: os.freemem(),
|
||||
totalMem: os.totalmem(),
|
||||
freeMem, totalMem,
|
||||
pid: process.pid,
|
||||
env: NODE_ENV,
|
||||
version: _appVersion,
|
||||
commit: _gitCommit,
|
||||
recentErrors,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
const db = require('../db/db');
|
||||
const { awardXP } = require('./gamificationController');
|
||||
const { awardXP, checkAchievements } = require('./gamificationController');
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||
const MAX_V = { H:1, C:4, N:3, O:2, P:5, S:6, Cl:1, Na:1, Ca:2, K:1, Mg:2, Fe:3, Br:1, I:1, F:1 };
|
||||
@@ -26,6 +26,44 @@ function valencyIssues(atoms, bonds) {
|
||||
.map(a => ({ id: a.id, symbol: a.s, used: sums[a.id] || 0, max: MAX_V[a.s] ?? 4 }));
|
||||
}
|
||||
|
||||
/* ── Structural (connectivity) match: Morgan-style canonical hash ──────────
|
||||
* Сравнивает связность двух молекул независимо от id/координат/нумерации.
|
||||
* Инвариант атома итеративно уточняется по соседям и порядкам связей;
|
||||
* каноническая подпись = отсортированные инварианты атомов + рёбер.
|
||||
*/
|
||||
function _bf(b) { return b.from != null ? b.from : b.f; }
|
||||
function _bt(b) { return b.to != null ? b.to : b.t; }
|
||||
function _bo(b) { return (b.order != null ? b.order : b.o) || 1; }
|
||||
function _hashStr(s) { let h = 5381; for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) | 0; return (h >>> 0).toString(36); }
|
||||
|
||||
function canonicalHash(atoms, bonds) {
|
||||
const adj = {};
|
||||
atoms.forEach(a => adj[a.id] = []);
|
||||
for (const b of bonds) {
|
||||
const f = _bf(b), t = _bt(b), o = _bo(b);
|
||||
if (adj[f] && adj[t]) { adj[f].push({ id: t, o }); adj[t].push({ id: f, o }); }
|
||||
}
|
||||
let inv = {};
|
||||
atoms.forEach(a => inv[a.id] = a.s);
|
||||
for (let iter = 0; iter < atoms.length; iter++) {
|
||||
const next = {};
|
||||
for (const a of atoms) {
|
||||
const neigh = adj[a.id].map(n => inv[n.id] + ':' + n.o).sort().join(',');
|
||||
next[a.id] = _hashStr(inv[a.id] + '|' + neigh);
|
||||
}
|
||||
inv = next;
|
||||
}
|
||||
const atomSig = atoms.map(a => inv[a.id]).sort().join(';');
|
||||
const edgeSig = bonds.map(b => [inv[_bf(b)], inv[_bt(b)]].sort().join('-') + '#' + _bo(b)).sort().join(',');
|
||||
return atomSig + '||' + edgeSig;
|
||||
}
|
||||
|
||||
function structuralMatch(a1, b1, a2, b2) {
|
||||
if (!Array.isArray(a1) || !Array.isArray(a2)) return false;
|
||||
if (a1.length !== a2.length || (b1 || []).length !== (b2 || []).length) return false;
|
||||
return canonicalHash(a1, b1 || []) === canonicalHash(a2, b2 || []);
|
||||
}
|
||||
|
||||
/* ── Prepared statements ─────────────────────────────────────────────── */
|
||||
const stmts = {
|
||||
getElements: db.prepare('SELECT * FROM bio_elements ORDER BY radius ASC'),
|
||||
@@ -140,6 +178,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -152,6 +191,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -165,6 +205,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -178,6 +219,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -192,6 +234,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -206,6 +249,7 @@ function solveChallenge(req, res) {
|
||||
return res.status(400).json({ error: 'wrong_answer' });
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -222,8 +266,21 @@ function solveChallenge(req, res) {
|
||||
if (issues.length > 0)
|
||||
return res.status(400).json({ error: 'valency_error', issues });
|
||||
|
||||
// 3D-build: помимо формулы проверяем СТРУКТУРУ (связность) против эталона
|
||||
const bdata = tryParse(challenge.data_json, {});
|
||||
if (bdata && bdata.requireStructure && bdata.molecule_id) {
|
||||
const ref = stmts.getMolById.get(bdata.molecule_id);
|
||||
if (ref) {
|
||||
const refAtoms = tryParse(ref.atoms_json, []);
|
||||
const refBonds = tryParse(ref.bonds_json, []);
|
||||
if (!structuralMatch(atoms, bonds, refAtoms, refBonds))
|
||||
return res.status(400).json({ error: 'wrong_structure' });
|
||||
}
|
||||
}
|
||||
|
||||
stmts.markDone.run(req.user.id, challenge.id);
|
||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||
checkAchievements(req.user.id);
|
||||
res.json({ ok: true, xp: challenge.xp_reward });
|
||||
}
|
||||
|
||||
@@ -253,6 +310,7 @@ function saveMolecule(req, res) {
|
||||
JSON.stringify(atoms),
|
||||
JSON.stringify(bonds),
|
||||
).lastInsertRowid;
|
||||
checkAchievements(req.user.id); // bc_first_molecule
|
||||
res.status(201).json({ id, formula, known: known || null });
|
||||
}
|
||||
|
||||
@@ -263,6 +321,52 @@ function deleteSaved(req, res) {
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
/* ── GET /api/biochem/pathways — все пути из БД (карта slug → данные) ──── */
|
||||
const stmtsPathways = db.prepare('SELECT slug, data_json FROM bio_pathways ORDER BY ord');
|
||||
function getPathways(_req, res) {
|
||||
const out = {};
|
||||
for (const r of stmtsPathways.all()) out[r.slug] = tryParse(r.data_json, {});
|
||||
res.json(out);
|
||||
}
|
||||
|
||||
/* ── Прогресс прохождения путей (Learn-режим) ────────────────────────── */
|
||||
const PATHWAY_XP = 80;
|
||||
const stmtsPath = {
|
||||
getProg: db.prepare('SELECT pathway, step, completed FROM bio_user_pathway WHERE user_id=?'),
|
||||
wasDone: db.prepare('SELECT completed FROM bio_user_pathway WHERE user_id=? AND pathway=?'),
|
||||
upsert: db.prepare(`INSERT INTO bio_user_pathway (user_id, pathway, step, completed, updated_at)
|
||||
VALUES (?, ?, ?, ?, datetime('now'))
|
||||
ON CONFLICT(user_id, pathway) DO UPDATE SET
|
||||
step = excluded.step,
|
||||
completed = MAX(completed, excluded.completed),
|
||||
updated_at = datetime('now')`),
|
||||
};
|
||||
|
||||
/* ── GET /api/biochem/pathways/progress ──────────────────────────────── */
|
||||
function getPathwayProgress(req, res) {
|
||||
const out = {};
|
||||
for (const r of stmtsPath.getProg.all(req.user.id))
|
||||
out[r.pathway] = { step: r.step, completed: !!r.completed };
|
||||
res.json(out);
|
||||
}
|
||||
|
||||
/* ── POST /api/biochem/pathways/progress ─────────────────────────────── */
|
||||
function savePathwayProgress(req, res) {
|
||||
const { pathway, step, completed } = req.body;
|
||||
if (typeof pathway !== 'string' || !pathway)
|
||||
return res.status(400).json({ error: 'pathway required' });
|
||||
const prev = stmtsPath.wasDone.get(req.user.id, pathway);
|
||||
const firstCompletion = !!completed && !(prev && prev.completed);
|
||||
stmtsPath.upsert.run(req.user.id, pathway, Math.max(0, parseInt(step) || 0), completed ? 1 : 0);
|
||||
let xp = 0;
|
||||
if (firstCompletion) {
|
||||
xp = PATHWAY_XP;
|
||||
awardXP(req.user.id, xp, `biochem_pathway:${pathway}`);
|
||||
checkAchievements(req.user.id);
|
||||
}
|
||||
res.json({ ok: true, xp });
|
||||
}
|
||||
|
||||
/* ── util ────────────────────────────────────────────────────────────── */
|
||||
function tryParse(v, fallback) {
|
||||
if (!v) return fallback;
|
||||
@@ -273,4 +377,7 @@ module.exports = {
|
||||
getElements, getMolecules, getMolecule, validate,
|
||||
getReactions, getChallenges, solveChallenge,
|
||||
getSaved, saveMolecule, deleteSaved,
|
||||
getPathways, getPathwayProgress, savePathwayProgress,
|
||||
// экспортируется для тестов структурной проверки
|
||||
structuralMatch, canonicalHash,
|
||||
};
|
||||
|
||||
@@ -343,6 +343,19 @@ function checkPhase3Achievements(userId, userRow) {
|
||||
if (tb) unlockAchievement(userId, 'tb_first_para');
|
||||
} catch (e) { /* table missing */ }
|
||||
|
||||
// ── biochem: решённые задачи + первая собранная молекула ───────
|
||||
try {
|
||||
const bc = db.prepare(`
|
||||
SELECT COUNT(*) AS n FROM bio_user_challenges WHERE user_id = ?
|
||||
`).get(userId)?.n || 0;
|
||||
if (bc >= 5) unlockAchievement(userId, 'bc_5_challenges');
|
||||
if (bc >= 20) unlockAchievement(userId, 'bc_20_challenges');
|
||||
const mol = db.prepare(`
|
||||
SELECT 1 FROM bio_user_molecules WHERE user_id = ? LIMIT 1
|
||||
`).get(userId);
|
||||
if (mol) unlockAchievement(userId, 'bc_first_molecule');
|
||||
} catch (e) { /* bio tables missing on legacy install */ }
|
||||
|
||||
// ── flashcards: total reviews ──────────────────────────────────
|
||||
try {
|
||||
const fc = db.prepare(`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 044_bio_user_pathway.sql
|
||||
-- Прогресс прохождения метаболических путей (Learn-режим biochem-pathways).
|
||||
-- Раньше прогресс не сохранялся; теперь шаг и факт завершения хранятся на
|
||||
-- пользователя по ключу пути (glycolysis / krebs / oxidation / synthesis ...).
|
||||
-- Награда (XP) начисляется один раз при первом завершении пути.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bio_user_pathway (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
pathway TEXT NOT NULL,
|
||||
step INTEGER NOT NULL DEFAULT 0,
|
||||
completed INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (user_id, pathway)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bio_user_pathway ON bio_user_pathway(user_id);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 045_bio_pathways.sql
|
||||
-- Метаболические пути как данные (вместо ~700 строк хардкода в
|
||||
-- biochem-pathways.html). Каждый путь — самодостаточный документ (граф узлов
|
||||
-- и рёбер + шаги Learn-режима с квизами) в data_json; страница грузит их через
|
||||
-- API. Document-подход выбран намеренно: путь всегда читается целиком,
|
||||
-- реляционных запросов к узлам/рёбрам нет.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bio_pathways (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#9B5DE5',
|
||||
ord INTEGER NOT NULL DEFAULT 0,
|
||||
data_json TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
@@ -14,5 +14,8 @@ router.post('/challenges/:id/solve', c.solveChallenge);
|
||||
router.get('/saved', c.getSaved);
|
||||
router.post('/saved', c.saveMolecule);
|
||||
router.delete('/saved/:id', c.deleteSaved);
|
||||
router.get('/pathways', c.getPathways);
|
||||
router.get('/pathways/progress', c.getPathwayProgress);
|
||||
router.post('/pathways/progress', c.savePathwayProgress);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+11
-1
@@ -79,7 +79,17 @@ function getOnlineUserIds() {
|
||||
return [...clients.keys()];
|
||||
}
|
||||
|
||||
/* Сводка SSE-соединений для мониторинга: онлайн-пользователи, гости и
|
||||
суммарное число открытых стримов. */
|
||||
function stats() {
|
||||
let conns = 0;
|
||||
for (const set of clients.values()) conns += set.size;
|
||||
let guestConns = 0;
|
||||
for (const set of guestClients.values()) guestConns += set.size;
|
||||
return { users: clients.size, guests: guestClients.size, connections: conns + guestConns };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addClient, removeClient, emit, emitToClass, getOnlineUserIds,
|
||||
addClient, removeClient, emit, emitToClass, getOnlineUserIds, stats,
|
||||
addGuestClient, removeGuestClient, emitToGuests,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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?.();
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -943,6 +943,9 @@ async function biochemSolveChallenge(id,payload) { return req('POST',`/bioche
|
||||
async function biochemGetSaved() { return req('GET', '/biochem/saved'); }
|
||||
async function biochemSave(atoms,bonds,name){ return req('POST','/biochem/saved',{atoms,bonds,name}); }
|
||||
async function biochemDeleteSaved(id) { return req('DELETE',`/biochem/saved/${id}`); }
|
||||
async function biochemGetPathways() { return req('GET', '/biochem/pathways'); }
|
||||
async function biochemGetPathwayProgress() { return req('GET', '/biochem/pathways/progress'); }
|
||||
async function biochemSavePathwayProgress(pathway,step,completed){ return req('POST','/biochem/pathways/progress',{pathway,step,completed}); }
|
||||
|
||||
/* ── LS.prefs — server-synced user preferences ──────────────────────────
|
||||
Keys use dot-notation: 'wb.color', 'dashboard.hidden', etc.
|
||||
@@ -1056,6 +1059,7 @@ window.LS = {
|
||||
patch: (path, body) => apiFetch(path, { method: 'PATCH', body: JSON.stringify(body) }),
|
||||
applyCosmetics: applyCosmetics,
|
||||
refreshNavAvatar,
|
||||
renderNavAvatar,
|
||||
isGamificationEnabled,
|
||||
loadFeatures,
|
||||
clearFeaturesCache,
|
||||
@@ -1064,6 +1068,7 @@ window.LS = {
|
||||
biochemGetElements, biochemGetMolecules, biochemGetMolecule, biochemValidate,
|
||||
biochemGetReactions, biochemGetChallenges, biochemSolveChallenge,
|
||||
biochemGetSaved, biochemSave, biochemDeleteSaved,
|
||||
biochemGetPathways, biochemGetPathwayProgress, biochemSavePathwayProgress,
|
||||
prefs: lsPrefs,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
# План реализации: Физика 10 (Беларусь, Громыко, 2019)
|
||||
|
||||
**Источник:** `fizika_10kl_gromika_rus_2019.pdf` (267 стр., 2 части, 6 глав, 37 §)
|
||||
**Издательство:** «Адукацыя і выхаванне», 2019
|
||||
**Уровень:** базовый + повышенный (с электронным приложением).
|
||||
|
||||
> **Самый большой учебник во всём проекте** — 37 параграфов против 19 в Алгебре 9 и 16 в Геометрии 9. Учитывает весь опыт 10 предыдущих учебников: единый KaTeX-эскейп, ноль пикселей в подписях, ноль эмодзи, 3D-движок `g3d.js`, snap-точки в slider'ах, цветовая кодировка, drag-to-rotate. **Главная фишка** — настоящие физические **симуляции**: броуновское движение, изопроцессы (PV-диаграммы), линии поля, электрические цепи, движение заряженных частиц в магнитном поле, явление индукции.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Содержание учебника
|
||||
|
||||
### Часть 1. МОЛЕКУЛЯРНАЯ ФИЗИКА
|
||||
|
||||
**Глава 1. Основы молекулярно-кинетической теории (§§1–10)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §1 | Основные положения МКТ | 3 положения: вещество — частицы, частицы движутся, частицы взаимодействуют |
|
||||
| §2 | Масса и размеры молекул. Количество вещества | $N_A = 6.022 \cdot 10^{23}$, $M = m \cdot N_A$, $\nu = N/N_A = m/M$ |
|
||||
| §3 | Макро- и микропараметры. Идеальный газ. Основное уравнение МКТ | $p = \dfrac{1}{3} n m_0 \overline{v^2} = \dfrac{2}{3} n \overline{E_k}$ |
|
||||
| §4 | Тепловое равновесие. Температура | $\overline{E_k} = \dfrac{3}{2} kT$, шкала Кельвина: $T = t + 273$ |
|
||||
| §5 | Уравнение состояния идеального газа | $pV = \nu RT$, $pV = \dfrac{m}{M} RT$ (Клапейрон–Менделеев) |
|
||||
| §6 | Изопроцессы | Изотерма $pV = \text{const}$, изобара $V/T = \text{const}$, изохора $p/T = \text{const}$ |
|
||||
| §7 | Строение и свойства твёрдых тел | Кристаллы (моно-, поликристаллы), аморфные тела, анизотропия |
|
||||
| §8 | Строение и свойства жидкостей | Поверхностное натяжение, смачивание |
|
||||
| §9 | Испарение и конденсация. Насыщенный пар | Динамическое равновесие, давление насыщенного пара |
|
||||
| §10 | Влажность воздуха | $\varphi = \dfrac{p}{p_н} \cdot 100\%$, точка росы, психрометр |
|
||||
|
||||
**Глава 2. Основы термодинамики (§§11–15)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §11 | Внутренняя энергия | $U = \dfrac{3}{2} \nu R T$ (одноатомный газ) |
|
||||
| §12 | Работа в термодинамике | $A = p \Delta V$ (изобарный), графически площадь под кривой |
|
||||
| §13 | Количество теплоты | $Q = cm \Delta T$, $Q = \lambda m$ (плавление), $Q = rm$ (испарение), $Q = qm$ (сгорание) |
|
||||
| §14 | Первый закон термодинамики | $\Delta U = Q + A_{внеш}$ или $Q = \Delta U + A_{газ}$ |
|
||||
| §15 | Тепловые двигатели. КПД | $\eta = \dfrac{A}{Q_1} = \dfrac{Q_1 - Q_2}{Q_1}$, $\eta_{Карно} = \dfrac{T_1 - T_2}{T_1}$ |
|
||||
|
||||
### Часть 2. ЭЛЕКТРОДИНАМИКА
|
||||
|
||||
**Глава 3. Электростатика (§§16–24)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §16 | Электрический заряд. Закон сохранения | $q = ne$, $e = 1.6 \cdot 10^{-19}$ Кл, $\sum q = \text{const}$ |
|
||||
| §17 | Закон Кулона | $F = k \dfrac{|q_1 q_2|}{r^2}$, $k = 9 \cdot 10^9$ Н·м²/Кл² |
|
||||
| §18 | Электростатическое поле | Силовая характеристика, источник |
|
||||
| §19 | Напряжённость поля. Принцип суперпозиции | $\vec{E} = \dfrac{\vec{F}}{q_{пр}}$, $\vec{E} = \sum \vec{E_i}$ |
|
||||
| §20 | Линии напряжённости | Силовые линии, поле точечного заряда, диполя |
|
||||
| §21 | Работа поля. Потенциал | $A = qU$, $\varphi = \dfrac{W_p}{q}$ |
|
||||
| §22 | Разность потенциалов. Напряжение | $U = \varphi_1 - \varphi_2$, $E = U/d$ (однородное) |
|
||||
| §23 | Конденсаторы. Электроёмкость | $C = q/U$, $C = \dfrac{\varepsilon \varepsilon_0 S}{d}$ |
|
||||
| §24 | Энергия поля конденсатора | $W = \dfrac{CU^2}{2} = \dfrac{q^2}{2C} = \dfrac{qU}{2}$ |
|
||||
|
||||
**Глава 4. Постоянный электрический ток (§§25–26)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §25 | ЭДС источника тока | $\mathcal{E} = A_{ст}/q$, ЭДС, сторонние силы |
|
||||
| §26 | Закон Ома для полной цепи | $I = \dfrac{\mathcal{E}}{R + r}$, КПД $\eta = U/\mathcal{E}$ |
|
||||
|
||||
**Глава 5. Магнитное поле. Электромагнитная индукция (§§27–33)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §27 | Магнитное поле тока | Опыт Эрстеда, взаимодействие проводников |
|
||||
| §28 | Индукция магнитного поля | $\vec{B}$, правило буравчика, линии индукции |
|
||||
| §29 | Сила Ампера | $F_A = BIL\sin\alpha$, правило левой руки |
|
||||
| §30 | Сила Лоренца | $F_L = qvB\sin\alpha$, движение по окружности: $R = \dfrac{mv}{qB}$ |
|
||||
| §31 | Магнитный поток. Электромагнитная индукция | $\Phi = BS\cos\alpha$, опыты Фарадея |
|
||||
| §32 | Правило Ленца. Закон ЭМИ | $\mathcal{E}_i = -\dfrac{d\Phi}{dt}$ |
|
||||
| §33 | Самоиндукция. Индуктивность | $\mathcal{E}_{si} = -L \dfrac{dI}{dt}$, $W_L = \dfrac{LI^2}{2}$ |
|
||||
|
||||
**Глава 6. Электрический ток в различных средах (§§34–37)**
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §34 | Ток в металлах. Сверхпроводимость | Свободные электроны, $\rho(T) = \rho_0(1+\alpha t)$ |
|
||||
| §35 | Ток в электролитах | Электролиз, законы Фарадея: $m = kIt$, $k = \dfrac{M}{F n}$ |
|
||||
| §36 | Ток в газах. Плазма | Самостоятельный/несамостоятельный разряд, виды разрядов |
|
||||
| §37 | Ток в полупроводниках | Собственная и примесная проводимость, n-тип, p-тип, p-n-переход |
|
||||
|
||||
**ИТОГО:** 6 глав, 37 параграфов, **2 части**.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 SVG-СТАНДАРТ КАЧЕСТВА ДЛЯ ФИЗИКИ
|
||||
|
||||
Это первая физика в проекте — нужны новые хелперы. Унаследует всё от математических учебников + добавляет physics-specific.
|
||||
|
||||
### Унаследованные хелперы (из Алгебры 11 / Геометрии 11)
|
||||
- `axes2D(W, H, pad, xmin, xmax, ymin, ymax)` — координатная плоскость
|
||||
- `plotFunc(f, xmin, xmax, toX, toY, color, N)` — график функции
|
||||
- `pointWithDrop(x, fx, toX, toY, color, label)` — точка с проекциями
|
||||
- `asymptote(orientation, value, ...)` — асимптота
|
||||
- `snapToValue(value, snaps, tolerance)` — snap-точки
|
||||
- `rightAngleMark`, `angleArcAuto`, `unitVec`, `deg2rad`
|
||||
- **`g3d.js`** — мини-3D движок (для линий магнитной индукции, движения частиц)
|
||||
|
||||
### Новые physics-specific хелперы (в `frontend/js/phys.js`)
|
||||
|
||||
```javascript
|
||||
// === Стрелка вектора (2D) ===
|
||||
function drawArrow(x1, y1, x2, y2, color, width, headSize) {
|
||||
// Возвращает SVG: линия + треугольная стрелка на конце
|
||||
const dx = x2 - x1, dy = y2 - y1;
|
||||
const len = Math.sqrt(dx*dx + dy*dy);
|
||||
const ux = dx / len, uy = dy / len;
|
||||
const px = -uy, py = ux; // перпендикуляр
|
||||
const h = headSize || 10;
|
||||
const w = (headSize || 10) * 0.6;
|
||||
const tipX = x2, tipY = y2;
|
||||
const baseX = x2 - ux * h, baseY = y2 - uy * h;
|
||||
const leftX = baseX + px * w, leftY = baseY + py * w;
|
||||
const rightX = baseX - px * w, rightY = baseY - py * w;
|
||||
return `<line x1="${x1}" y1="${y1}" x2="${baseX.toFixed(1)}" y2="${baseY.toFixed(1)}" stroke="${color}" stroke-width="${width || 2}"/>`
|
||||
+ `<polygon points="${tipX},${tipY} ${leftX.toFixed(1)},${leftY.toFixed(1)} ${rightX.toFixed(1)},${rightY.toFixed(1)}" fill="${color}"/>`;
|
||||
}
|
||||
|
||||
// === Линии электрического поля от точечного заряда ===
|
||||
function fieldLinesPointCharge(cx, cy, sign, scale, numLines) {
|
||||
// sign: +1 или -1 (от знака заряда)
|
||||
// numLines: число линий (12-24)
|
||||
// Возвращает массив SVG-линий — стрелки наружу для +q, внутрь для -q
|
||||
let s = '';
|
||||
const N = numLines || 16;
|
||||
for (let i = 0; i < N; i++) {
|
||||
const a = 2 * Math.PI * i / N;
|
||||
const r1 = 15, r2 = scale;
|
||||
const x1 = cx + r1 * Math.cos(a), y1 = cy + r1 * Math.sin(a);
|
||||
const x2 = cx + r2 * Math.cos(a), y2 = cy + r2 * Math.sin(a);
|
||||
if (sign > 0) s += drawArrow(x1, y1, x2, y2, '#dc2626', 1.5);
|
||||
else s += drawArrow(x2, y2, x1, y1, '#2563eb', 1.5);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// === Линии поля диполя (две точечных зарядов противоположных) ===
|
||||
function fieldLinesDipole(c1, c2, scale, numLines) {
|
||||
// Параметрически — линия от +q к -q вдоль кривых, перпендикулярных эквипотенциалям
|
||||
// Упрощённо: использовать numerical integration по полю
|
||||
// ...
|
||||
}
|
||||
|
||||
// === Электрическая схема: компоненты ===
|
||||
function batteryEMF(x, y, EMF, orientation) {
|
||||
// Возвращает SVG условного обозначения батареи (две полоски + подпись ε)
|
||||
}
|
||||
function resistor(x, y, w, h, R, orientation) {
|
||||
// Возвращает SVG зигзага резистора + подпись R
|
||||
}
|
||||
function capacitor(x, y, C, orientation) {
|
||||
// Две параллельные линии + подпись C
|
||||
}
|
||||
function ammeter(x, y) {
|
||||
// Круг с буквой A
|
||||
}
|
||||
function voltmeter(x, y) {
|
||||
// Круг с буквой V
|
||||
}
|
||||
function inductor(x, y, L, orientation) {
|
||||
// Серия маленьких полукругов
|
||||
}
|
||||
function lightbulb(x, y) {
|
||||
// Круг с крестиком (или волной)
|
||||
}
|
||||
|
||||
// === Магнитное поле: стрелки В через плоскость экрана ===
|
||||
function magneticField(x, y, direction, dx, dy) {
|
||||
// direction: 'in' (крест) или 'out' (точка)
|
||||
// Рисует сетку 5x5 крестиков (вошло в плоскость) или точек (вышло из плоскости)
|
||||
let s = '';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
for (let j = 0; j < 5; j++) {
|
||||
const cx = x + i * dx, cy = y + j * dy;
|
||||
if (direction === 'in') {
|
||||
s += `<circle cx="${cx}" cy="${cy}" r="6" fill="none" stroke="#0891b2" stroke-width="1.5"/>`;
|
||||
s += `<line x1="${cx-4}" y1="${cy-4}" x2="${cx+4}" y2="${cy+4}" stroke="#0891b2" stroke-width="1.5"/>`;
|
||||
s += `<line x1="${cx-4}" y1="${cy+4}" x2="${cx+4}" y2="${cy-4}" stroke="#0891b2" stroke-width="1.5"/>`;
|
||||
} else {
|
||||
s += `<circle cx="${cx}" cy="${cy}" r="6" fill="none" stroke="#0891b2" stroke-width="1.5"/>`;
|
||||
s += `<circle cx="${cx}" cy="${cy}" r="2" fill="#0891b2"/>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// === PV-диаграмма с изопроцессами ===
|
||||
function pvDiagram(W, H, pad, pMax, vMax) {
|
||||
// Возвращает axes2D-объект с диапазоном (0..vMax, 0..pMax)
|
||||
// Подписи осей: P (атм или Па), V (л или м³)
|
||||
}
|
||||
|
||||
// === Молекула газа (частица) ===
|
||||
function molecule(x, y, r, color) {
|
||||
return `<circle cx="${x}" cy="${y}" r="${r}" fill="${color}" stroke="#0f172a" stroke-width="0.8"/>`;
|
||||
}
|
||||
|
||||
// === Симуляция броуновского движения / движения молекул газа ===
|
||||
class GasSimulation {
|
||||
constructor(opts) {
|
||||
this.W = opts.W; this.H = opts.H;
|
||||
this.N = opts.N || 30;
|
||||
this.particles = [];
|
||||
for (let i = 0; i < this.N; i++) {
|
||||
this.particles.push({
|
||||
x: Math.random() * this.W,
|
||||
y: Math.random() * this.H,
|
||||
vx: (Math.random() - 0.5) * opts.speed,
|
||||
vy: (Math.random() - 0.5) * opts.speed
|
||||
});
|
||||
}
|
||||
}
|
||||
step(dt) {
|
||||
this.particles.forEach(p => {
|
||||
p.x += p.vx * dt; p.y += p.vy * dt;
|
||||
// отражение от стенок
|
||||
if (p.x < 0 || p.x > this.W) p.vx = -p.vx;
|
||||
if (p.y < 0 || p.y > this.H) p.vy = -p.vy;
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return this.particles.map(p => molecule(p.x, p.y, 4, '#2563eb')).join('');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Цветовая кодировка для физики
|
||||
|
||||
- **Положительный заряд** — красный (`#dc2626`)
|
||||
- **Отрицательный заряд** — синий (`#2563eb`)
|
||||
- **Электрическое поле $\vec{E}$** — оранжевый (`#ea580c`)
|
||||
- **Магнитное поле $\vec{B}$** — фиолетовый (`#7c3aed`)
|
||||
- **Сила** — зелёный (`#10b981`)
|
||||
- **Скорость** — голубой (`#0891b2`)
|
||||
- **Молекулы газа** — синие точки
|
||||
- **Температура** — градиент от синего (холод) к красному (горячо)
|
||||
- **Ток** — янтарный (`#d97706`)
|
||||
- **Резистор / провод** — тёмно-серый (`#374151`)
|
||||
- **Изотерма** (P-V) — оранжевый
|
||||
- **Изобара** (V-T) — синий
|
||||
- **Изохора** (P-T) — зелёный
|
||||
|
||||
### Правила (обязательны с §1)
|
||||
|
||||
1. **Все единицы измерения через KaTeX** — `\\text{Н}`, `\\text{м}^2$, `\\text{Дж/(моль·К)}` — никогда сырой текст.
|
||||
2. **Slider'ы — в реальных физических величинах** с разумными диапазонами:
|
||||
- Температура: 100..500 К (или $-100..200$ °C)
|
||||
- Давление: 0.5..5 атм или $10^4..10^6$ Па
|
||||
- Объём: 1..10 л или $0.001..0.01$ м³
|
||||
- Заряд: $\pm 10^{-9}..10^{-6}$ Кл (нКл, мкКл)
|
||||
- Расстояние между зарядами: 0.01..1 м
|
||||
- Напряжение: 1..220 В
|
||||
- Ток: 0.01..10 А
|
||||
- Магнитная индукция: $10^{-3}..1$ Тл
|
||||
3. **Snap-точки** на эталонных значениях: $T = 273$ К ($0$ °C), $T = 0$ K, $p = 1$ атм.
|
||||
4. **Все формулы с КаTeX**, двойные backslash.
|
||||
5. **Векторы — со стрелками** через `drawArrow`. Никогда `→` в тексте — через KaTeX `\\vec{E}`.
|
||||
6. **Подписи единиц измерения** — обязательны на всех осях графиков, во всех результатах калькуляторов.
|
||||
7. **Симуляции — через `requestAnimationFrame`** с возможностью паузы и сброса.
|
||||
8. **Эмодзи запрещены.**
|
||||
9. **Никаких пикселей в подписях** — всё в СИ или производных единицах.
|
||||
|
||||
### Типы интерактивов по темам
|
||||
|
||||
| Тип | Применение |
|
||||
|-----|-----------|
|
||||
| **Симуляция движения молекул** | §1 (МКТ), §3 (давление как удары о стенку), §9 (испарение) |
|
||||
| **PV/VT/PT диаграммы** | §6 (изопроцессы — главное!), §15 (цикл Карно) |
|
||||
| **Калькулятор Клапейрона–Менделеева** | §5 |
|
||||
| **Психрометрический анализ** | §10 |
|
||||
| **Поток теплоты симуляция** | §13 |
|
||||
| **Цикл Карно — анимированная PV-диаграмма** | §15 |
|
||||
| **Закон Кулона: 3D-визуализатор сил** | §17 |
|
||||
| **Линии напряжённости электрического поля** | §20 (главный визуал электростатики) |
|
||||
| **Эквипотенциальные поверхности** | §22 |
|
||||
| **Конденсатор-конструктор** | §23 |
|
||||
| **Электрическая цепь — конструктор** | §25, §26 |
|
||||
| **Правило буравчика / левой руки — 3D** | §28, §29, §30 |
|
||||
| **Движение заряженной частицы в B-поле — 3D-траектория** | §30 (главный визуал магнетизма) |
|
||||
| **Опыты Фарадея — анимация катушки и магнита** | §31 |
|
||||
| **Самоиндукция — LR-цепь, ток как функция времени** | §33 |
|
||||
| **p-n переход — визуализация дырок и электронов** | §37 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ПОРЯДОК РЕАЛИЗАЦИИ
|
||||
|
||||
Учитывая 37 параграфов — самый большой проект, разбиваем на **8 фаз**.
|
||||
|
||||
### Phase 0: Архитектура (фундамент)
|
||||
- `physics_10_hub.html` — палитра **yellow/amber** (классический физический цвет) или **orange**
|
||||
- 6 ch-файлов: `physics_10_ch1.html` (МКТ), `_ch2.html` (термодинамика), `_ch3.html` (электростатика), `_ch4.html` (пост. ток), `_ch5.html` (магнитное поле), `_ch6.html` (ток в средах)
|
||||
- Миграция `0XX_physics_10_hub.sql` (следующий доступный номер)
|
||||
- **`frontend/js/phys.js`** — новый модуль physics-хелперов (drawArrow, fieldLines, magneticField, схемы, GasSimulation, pvDiagram)
|
||||
- 2D-хелперы (axes2D, plotFunc, etc.) и G3D подключены в каждый ch
|
||||
- KaTeX CDN + auto-render
|
||||
- POLISH CSS + bump-score JS
|
||||
|
||||
### Phase 1: Молекулярная физика — Глава 1 (10 §) — 5 волн
|
||||
Самая большая глава учебника. По 2 §§ на волну.
|
||||
- **Wave 1**: §1 (МКТ основные положения, БРОУНОВСКОЕ ДВИЖЕНИЕ симуляция) + §2 (масса/количество вещества, калькулятор $N$, $\nu$, $m$).
|
||||
- **Wave 2**: §3 (давление через удары молекул, симуляция $p = \frac{1}{3} n m \overline{v^2}$) + §4 (температура, шкалы, $\overline{E_k} = \frac{3}{2}kT$).
|
||||
- **Wave 3**: §5 (уравнение Клапейрона–Менделеева, калькулятор) + §6 (**изопроцессы — главный визуал главы**: PV/VT/PT диаграммы с slider'ом T, V, p).
|
||||
- **Wave 4**: §7 (твёрдые тела, виды кристаллов SVG) + §8 (жидкости, поверхностное натяжение симуляция).
|
||||
- **Wave 5**: §9 (испарение, насыщенный пар, динамическое равновесие симуляция) + §10 (влажность, психрометрический калькулятор) + **Финал главы 1** (7 боссов + ачивка «Мастер МКТ»).
|
||||
|
||||
### Phase 2: Молекулярная физика — Глава 2 «Термодинамика» (5 §) — 3 волны
|
||||
- **Wave 1**: §11 (внутренняя энергия, $U = \frac{3}{2} \nu R T$) + §12 (работа в термодинамике, площадь под P-V кривой).
|
||||
- **Wave 2**: §13 (количество теплоты — все 4 формулы) + §14 (первый закон термодинамики, применение к изопроцессам).
|
||||
- **Wave 3**: §15 (тепловые двигатели, **цикл Карно — анимация PV-цикла**, КПД) + **Финал главы 2** (5 боссов + ачивка «Мастер термодинамики»).
|
||||
|
||||
### Phase 3: Электростатика (9 §) — 5 волн
|
||||
- **Wave 1**: §16 (заряд, закон сохранения) + §17 (**закон Кулона — визуализатор сил**, slider $q_1, q_2, r$).
|
||||
- **Wave 2**: §18 (электрическое поле) + §19 (напряжённость, суперпозиция).
|
||||
- **Wave 3**: §20 (**линии напряжённости — главный визуал** — поле точечного заряда, диполя, двух одноимённых) + §21 (работа поля, потенциал).
|
||||
- **Wave 4**: §22 (разность потенциалов, напряжение, $E = U/d$) + §23 (конденсаторы, $C = \varepsilon \varepsilon_0 S/d$).
|
||||
- **Wave 5**: §24 (энергия поля конденсатора) + **Финал главы 3** (7 боссов + ачивка «Мастер электростатики»).
|
||||
|
||||
### Phase 4: Постоянный ток (2 §) — 1 волна
|
||||
- **Wave 1**: §25 (ЭДС, сторонние силы) + §26 (закон Ома для полной цепи, **конструктор электрической цепи**) + **Финал главы 4** (5 боссов).
|
||||
|
||||
### Phase 5: Магнитное поле и индукция (7 §) — 4 волны
|
||||
- **Wave 1**: §27 (опыт Эрстеда, симуляция) + §28 (индукция, правило буравчика, **3D-линии магнитной индукции вокруг проводника**).
|
||||
- **Wave 2**: §29 (сила Ампера, правило левой руки, симуляция) + §30 (**сила Лоренца — 3D-движение частицы по спирали в B-поле**).
|
||||
- **Wave 3**: §31 (магнитный поток, **опыты Фарадея — анимация катушки и магнита**) + §32 (правило Ленца, закон ЭМИ).
|
||||
- **Wave 4**: §33 (самоиндукция, индуктивность, LR-цепь) + **Финал главы 5** (7 боссов + ачивка «Мастер магнетизма»).
|
||||
|
||||
### Phase 6: Ток в различных средах (4 §) — 2 волны
|
||||
- **Wave 1**: §34 (металлы, сверхпроводимость) + §35 (электролиты, законы Фарадея).
|
||||
- **Wave 2**: §36 (газы, плазма, виды разрядов) + §37 (полупроводники, **p-n переход — визуализация дырок и электронов**, n-/p-тип) + **Финал главы 6** (5 боссов + ачивка «Мастер токов»).
|
||||
|
||||
### Phase 7: Финал курса Физика 10
|
||||
- Итоговая шпаргалка (6 mini-карточек: МКТ, термодинамика, электростатика, ток, магнетизм, ток в средах)
|
||||
- **10 интегрированных боссов**, комбинирующих темы из разных глав:
|
||||
1. **МКТ + термодинамика**: $U$ и $T$ моноатомного газа
|
||||
2. **Изопроцесс + работа**: $A$ за изобарное расширение
|
||||
3. **Цикл Карно**: КПД при $T_1 = 600$ К, $T_2 = 300$ К
|
||||
4. **Закон Кулона + суперпозиция**: сила на 3-й заряд от двух источников
|
||||
5. **Конденсатор + энергия**: $W$ от $C$, $U$
|
||||
6. **Закон Ома для полной цепи + КПД**
|
||||
7. **Сила Лоренца + радиус**: $R = mv/(qB)$
|
||||
8. **Закон ЭМИ Фарадея**: $\mathcal{E}_i$ от $d\Phi/dt$
|
||||
9. **Самоиндукция + энергия**: $W_L = LI^2/2$
|
||||
10. **Магистр электродинамики**: синтез
|
||||
- Ачивка **«Магистр физики 10»** (+150 XP — крупнейший курс)
|
||||
- Кнопка «К каталогу учебников» → `/textbooks`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Структура каждого § (стандарт)
|
||||
|
||||
### Wave 0 главы — skeleton (включено в Phase 0)
|
||||
- CSS + цветовая палитра 3 акцентов на §
|
||||
- Sections + stub-builders
|
||||
- Hub-карточка обновляется
|
||||
- Миграция в БД
|
||||
|
||||
### Wave N — наполнение § (по 2 § на волну)
|
||||
|
||||
**Каждый § содержит:**
|
||||
- **3 теоретические карточки** (`theory`, `rule`, `example`) с SVG-схемами/графиками
|
||||
- **4 интерактива** (`.wg` виджеты):
|
||||
1. **Симуляция** или **3D/SVG-визуализатор** (главный)
|
||||
2. **Калькулятор закона** (ввод параметров → формула с подстановкой → результат с единицами)
|
||||
3. **DnD/Квикфайр** (понятийный)
|
||||
4. **Тренажёр расчётных задач** (5-6 задач с числовым ответом, допуск 1-5%)
|
||||
- **Кнопка «Я прочитал § (+10 XP)»**
|
||||
- **Прогресс/XP**: IV1 15%/10XP, IV2 15%/10XP, IV3 25%/15XP, IV4 25%/15XP, чтение 30%/10XP
|
||||
|
||||
### Wave финал главы
|
||||
- **Итоговая шпаргалка** с формулами
|
||||
- **5-7 интегрированных боссов** (синтез тем главы)
|
||||
- **Ачивка «Мастер главы N»** + 50 XP + confetti
|
||||
- **Кнопка** перехода к следующей главе
|
||||
|
||||
---
|
||||
|
||||
## 📊 Оценка объёма
|
||||
|
||||
| Глава | § | LOC |
|
||||
|-------|---|-----|
|
||||
| Phase 0: skeleton + phys.js | — | 2500 |
|
||||
| Глава 1 МКТ (§§1-10) | 10 | 13 000 |
|
||||
| Глава 2 Термодинамика (§§11-15) | 5 | 6 500 |
|
||||
| Глава 3 Электростатика (§§16-24) | 9 | 12 000 |
|
||||
| Глава 4 Пост. ток (§§25-26) | 2 | 3 000 |
|
||||
| Глава 5 Магнетизм (§§27-33) | 7 | 10 500 |
|
||||
| Глава 6 Ток в средах (§§34-37) | 4 | 5 500 |
|
||||
| Phase 7 финал курса | — | 1 500 |
|
||||
| **Итого** | **37** | **~54 500 LOC** |
|
||||
|
||||
Это **больше**, чем Геометрия 11 (~30K) или Алгебра 9 (~30K). Самый объёмный курс.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Критические правила (опыт всех 10 предыдущих учебников)
|
||||
|
||||
### ❌ НЕ делать
|
||||
- **Slider'ы в пиксельных диапазонах** (40..150). Только в реальных физических единицах с правильным масштабом отрисовки.
|
||||
- **Подписи без единиц измерения** ($p = 100$ — это что? Па? атм?). Всегда: $p = 1$ атм $= 10^5$ Па.
|
||||
- **Эмодзи** (`⚠`, `🌡️`, `🔋`). Только inline SVG.
|
||||
- **Одиночный `\` перед буквой** в JS template literals: `\dfrac`, `\sin`, `\vec`. ВСЕГДА `\\dfrac`.
|
||||
- **Сырой KaTeX в `<option>`** — KaTeX там не рендерится. Только unicode-текст в `<option>`.
|
||||
- **Эталонные значения вместо реальных** — в задачах используй $\pi = 3.14$, $g = 10$ м/с², $k_B = 1.38 \cdot 10^{-23}$ Дж/К ровно.
|
||||
- **Symbol `\degree`** не существует в KaTeX. Используй `^\\circ` для °.
|
||||
- **Бесконечные симуляции без паузы** — всегда кнопка «Пауза / Сброс» и `cancelAnimationFrame` при unmount.
|
||||
|
||||
### ✅ Обязательно
|
||||
- **Все единицы СИ в формулах** — никаких ватт в задаче без обозначения «Вт».
|
||||
- **Snap-точки в slider'ах** для эталонных значений: $T = 273$ К, $p = 1$ атм, $g = 10$ м/с².
|
||||
- **Цветовая кодировка** едина по всем § (заряд+/−, поле $E$/$B$, сила, скорость, ток).
|
||||
- **KaTeX-аудит** через `node` после каждого Wave (паттерн `[^\\]\\[a-z]{2,}` в JS-блоках).
|
||||
- **JS parse-check** через `new Function(scriptBody)` после каждого Wave.
|
||||
- **Авторов нет** — в hub footer'е только «Интерактивный учебник «Физика — 10 класс» · LearnSpace» (как сделали для остальных).
|
||||
- **Симуляции** должны иметь паузу, перезапуск, и быть **физически корректными**: реальная скорость, реальная масса, реальные коэффициенты.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Запуск
|
||||
|
||||
**Phase 0**: skeleton 6 ch-файлов + hub + миграция + новый модуль `phys.js` со всеми physics-хелперами.
|
||||
|
||||
Палитра hub: **yellow/amber** (классическая для физики) — `--pri:#ca8a04` (yellow-600), `--pri-d:#a16207` (yellow-700), `--pri-soft:#fef3c7`. Header gradient: `linear-gradient(110deg,#713f12 0%,#ca8a04 55%,#fde047 100%)`. Эта палитра отличается от Алгебры 11 (teal) и Геометрии 11 (cyan).
|
||||
|
||||
После Phase 0 → Phase 1 Wave 1 (§1 МКТ + §2 масса молекул).
|
||||
|
||||
После завершения всех 8 Phase Физика 10 — план одного 10-класса завершён (можно потом план Алгебры 10 + Геометрии 10).
|
||||
@@ -0,0 +1,251 @@
|
||||
# План реализации: Алгебра 11 (Беларусь, Арефьева/Пирютко, 2020)
|
||||
|
||||
**Источник:** `Algebra_11kl_Arefieva_rus_2020.pdf` (276 стр., 3 главы, 10 §)
|
||||
**Авторы:** И. Г. Арефьева, О. Н. Пирютко
|
||||
**Издательство:** «Народная асвета», 2020
|
||||
|
||||
> Этот план учитывает **весь опыт Алгебры 9 + Геометрии 9** — никаких пикселей в подписях, никаких эмодзи, KaTeX с двойным экранированием с первого дня, единая SVG-инфраструктура, продуманный UX.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Содержание учебника
|
||||
|
||||
### Глава 1. Обобщение понятия степени (§1–§3)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §1 | Степень с рациональным/действительным показателем | $a^{m/n} = \sqrt[n]{a^m}$, свойства, $a^{\sqrt{2}}$ через приближения |
|
||||
| §2 | Степенная функция $y = x^\alpha$ и её свойства | 6 типов в зависимости от $\alpha$ |
|
||||
| §3 | Определение логарифма. Основное логарифмическое тождество | $\log_a b = c \Leftrightarrow a^c = b$, $a^{\log_a b} = b$ |
|
||||
|
||||
### Глава 2. Показательная функция (§4–§6)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §4 | Показательная функция $y = a^x$ | $D=\mathbb{R}$, $E=(0;+\infty)$, при $a>1$ возрастает, при $0<a<1$ убывает |
|
||||
| §5 | Показательные уравнения | $a^{f(x)} = a^{g(x)} \Leftrightarrow f(x)=g(x)$, методы: одинак. основание, замена, графический |
|
||||
| §6 | Показательные неравенства | При $a>1$ знак сохраняется, при $0<a<1$ — меняется |
|
||||
|
||||
### Глава 3. Логарифмическая функция (§7–§10)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §7 | Свойства логарифмов | $\log_a(bc)$, $\log_a(b/c)$, $\log_a b^n$, переход к новому основанию |
|
||||
| §8 | Логарифмическая функция $y = \log_a x$. Свойства | $D=(0;+\infty)$, $E=\mathbb{R}$, обратная к показательной |
|
||||
| §9 | Логарифмические уравнения | $\log_a f(x) = \log_a g(x)$, замена, ОДЗ, метод приведения к одному основанию |
|
||||
| §10 | Логарифмические неравенства | Двойной учёт монотонности и ОДЗ |
|
||||
|
||||
**Итого: 10 параграфов в 3 главах.**
|
||||
|
||||
---
|
||||
|
||||
## 🎨 SVG-СТАНДАРТ КАЧЕСТВА (унаследовано от Геом 9)
|
||||
|
||||
### Хелперы (обязательны с §1)
|
||||
|
||||
Вынести в начало `<script>` каждого ch-файла. Уже работают в Алгебре 9 — копируем 1-в-1:
|
||||
|
||||
```js
|
||||
// 1. Координатная плоскость
|
||||
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
|
||||
// Возвращает {content, toX(v), toY(v), ux, uy}
|
||||
}
|
||||
|
||||
// 2. График функции y = f(x)
|
||||
function plotFunc(f, xmin, xmax, toX, toY, color, N){
|
||||
// Возвращает <path d="..."/>
|
||||
}
|
||||
|
||||
// 3. Маркер прямого угла L
|
||||
function rightAngleMark(V, uIn, wIn, s) { /* polyline */ }
|
||||
|
||||
// 4. Дуга угла с автовыбором sweep
|
||||
function angleArcAuto(V, uA, uB, R) { /* path */ }
|
||||
|
||||
// 5. Единичный вектор от p1 до p2
|
||||
function unitVec(p1, p2) { /* {x, y} */ }
|
||||
|
||||
// 6. deg → rad
|
||||
function deg2rad(d) { return d * Math.PI / 180; }
|
||||
```
|
||||
|
||||
### Новые хелперы для Алгебры 11
|
||||
|
||||
```js
|
||||
// 7. Точка на графике с проекциями на оси
|
||||
function pointWithDrop(x, f, toX, toY, color) {
|
||||
// Возвращает SVG: точка + пунктир к оси X + пунктир к оси Y + подписи координат
|
||||
}
|
||||
|
||||
// 8. Асимптота (для y=a^x: y=0; для y=log_a x: x=0)
|
||||
function asymptote(orientation, value, toX, toY, xmin, xmax, ymin, ymax) {
|
||||
// orientation: 'h' (y=value) | 'v' (x=value)
|
||||
// Возвращает <line stroke-dasharray="6 4">
|
||||
}
|
||||
|
||||
// 9. Сравнительный график двух функций (для a>1 vs 0<a<1)
|
||||
function compareGraph(W, H, fLeft, fRight, labels) {
|
||||
// Двухпанельный SVG с одинаковыми осями
|
||||
}
|
||||
|
||||
// 10. Слайдер с эталонными метками (a=1/2, 1, e, 2, 10)
|
||||
function snapSlider(value, snaps, tolerance) {
|
||||
// Snap к ближайшей эталонной точке при касании
|
||||
}
|
||||
|
||||
// 11. Логарифмическая линейка-визуализатор (для §7)
|
||||
function logScale(W, base, vals) {
|
||||
// Шкала log_a, проецирующая b на a^x = b
|
||||
}
|
||||
```
|
||||
|
||||
### Правила (обязательны с §1)
|
||||
|
||||
1. **Координаты — ВСЕГДА через `axes2D` + `toX`/`toY`.** Никаких сырых пикселей в коде графиков.
|
||||
2. **Диапазоны slider'ов — в учебных единицах**, не в пикселях. Пример: `min="0.1" max="10" step="0.1"` для $a$, не `min="40" max="150"`.
|
||||
3. **Snap-точки** для важных значений: $a=2, e (\approx 2.718), 10, 1/2, 1/e, 1/10$.
|
||||
4. **KaTeX в JS template literals — ВСЕГДА `\\dfrac`, `\\log`, `\\sqrt`** (двойной слэш).
|
||||
5. **Подписи на графиках** через KaTeX + `renderMath(box)` после `box.innerHTML`.
|
||||
6. **Цветовая кодировка:**
|
||||
- При $a > 1$ — **синий** (`#2563eb`)
|
||||
- При $0 < a < 1$ — **оранжевый** (`#ea580c`)
|
||||
- Граничный случай $a = 1$ — серый (`#94a3b8`)
|
||||
- Асимптоты — серый пунктир (`#94a3b8`, `stroke-dasharray="6 4"`)
|
||||
7. **ViewBox с запасом ≥ 24px** для подписей вне сетки.
|
||||
8. **`touch-action: none`** на каждом draggable SVG.
|
||||
9. **Drag через `window.addEventListener` + `{passive: false}`**, state ВЫШЕ `redraw()`.
|
||||
10. **Эмодзи ЗАПРЕЩЕНЫ.** Только inline SVG `.ic`.
|
||||
11. **Подписи длин/значений — в учебных единицах**, не в пикселях. Никогда `b₁ = 120` где 120 это пиксели.
|
||||
12. **Заголовок интерактива должен СОВПАДАТЬ** с количеством объектов в SVG. Не «Три эталонных треугольника», когда нарисовано два.
|
||||
|
||||
### Типы SVG по темам
|
||||
|
||||
| § | Тема | Тип SVG |
|
||||
|---|------|---------|
|
||||
| §1 | Степень с рац. показателем | Slider $a$ + табло «$a^{m/n}$ = $\sqrt[n]{a^m}$» с реальной подстановкой. Калькулятор степени без графики. |
|
||||
| §2 | Степенная функция $y=x^\alpha$ | **Координатная плоскость** + slider $\alpha \in [-3; 3]$, точка $\alpha=1$ — прямая, $\alpha=2$ — парабола, $\alpha=-1$ — гипербола. **6 эталонных кривых на одном графике** с включаемой видимостью. |
|
||||
| §3 | Определение логарифма | **Slider $a$ + slider $b$**, точка пересечения $y = a^x$ с горизонталью $y = b$ показывает $\log_a b$. Анимированный поиск. |
|
||||
| §4 | Показательная функция | **Двухпанельный график** $a > 1$ vs $0 < a < 1$, slider $a$ от $0.1$ до $10$ со snap к $2, e, 10$. Асимптота $y=0$ пунктиром. Точка $(0, 1)$, $(1, a)$ — выделена. |
|
||||
| §5 | Показательные уравнения | **Графический решатель**: два графика $y = a^{f(x)}$, $y = a^{g(x)}$, точки пересечения. Также пошаговый разбор алгебраического решения. |
|
||||
| §6 | Показательные неравенства | **Числовая прямая** + закрашенная область решений + сравнение с осью при $a>1$ и $a<1$. |
|
||||
| §7 | Свойства логарифмов | **Логарифмическая линейка** — slider $b$, $c$ показывает, что $\log(bc)$ это сумма длин. |
|
||||
| §8 | Логарифмическая функция | **Двухпанельный график** $a > 1$ vs $0 < a < 1$ + отражение $y = a^x$ через $y = x$ (показать обратность). |
|
||||
| §9 | Логарифмические уравнения | **Графический решатель** + анимация ОДЗ (выколотые точки на оси x). |
|
||||
| §10 | Логарифмические неравенства | **Числовая прямая** с учётом ОДЗ + монотонности. |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ПОРЯДОК РЕАЛИЗАЦИИ
|
||||
|
||||
### Phase 0: Архитектура (фундамент)
|
||||
- `algebra_11_hub.html` skeleton (палитра **emerald/teal**, чтобы отличить от algebra_7 розовой, algebra_9 индиго)
|
||||
- `algebra_11_ch1.html`, `_ch2.html`, `_ch3.html` skeletons
|
||||
- Миграция `022_algebra_11_hub.sql` (slug `algebra-11`, sort_order 9)
|
||||
- SVG-хелперы (axes2D, plotFunc, pointWithDrop, asymptote, compareGraph) уже встроены в skeleton
|
||||
- KaTeX CDN + auto-render в head
|
||||
- CSS-блок «POLISH» (анимации появления, hover, bump-score) — копия из Геом 9
|
||||
- ICONS объект целиком из algebra_9_ch4
|
||||
|
||||
### Phase 1: Алгебра 11 Глава 1 «Обобщение степени» (3 §) — 3 волны
|
||||
- **Wave 1**: §1 «Степень с рац. показателем». 3 теории + 4 интерактива (slider $a^{m/n}$, калькулятор, DnD «корень↔степень», тренажёр).
|
||||
- **Wave 2**: §2 «Степенная функция $y=x^\alpha$». 6 типов графиков на одном координатном поле, slider $\alpha$, цветовая кодировка чёт./нечёт./дробн./отриц.
|
||||
- **Wave 3**: §3 «Определение логарифма». Slider $a$, slider $b$, визуализация $\log_a b$ через пересечение $y = a^x$ и $y = b$. + Финал главы 1 (4 mini-карточки + 5 боссов + ачивка «Магистр степеней»).
|
||||
|
||||
### Phase 2: Алгебра 11 Глава 2 «Показательная функция» (3 §) — 3 волны
|
||||
- **Wave 1**: §4 «Показательная функция $y = a^x$». **Главный интерактив всей главы** — двухпанельный график $a>1$ vs $0<a<1$, slider $a$ со snap-точками, анимированные асимптоты, отметка точки $(0,1)$. 4 интерактива.
|
||||
- **Wave 2**: §5 «Показательные уравнения». 5 методов решения: одинаковое основание / однородные / замена переменной / графический / тригонометрические подстановки. Визуальный разбор каждого метода + тренажёр.
|
||||
- **Wave 3**: §6 «Показательные неравенства» + Финал главы 2 (5 боссов + ачивка «Магистр показательной функции»).
|
||||
|
||||
### Phase 3: Алгебра 11 Глава 3 «Логарифмическая функция» (4 §) — 4 волны
|
||||
- **Wave 1**: §7 «Свойства логарифмов». **Логарифмическая линейка** (Slide rule visualizer). DnD-сортер свойств. Калькулятор с пошаговым применением свойств.
|
||||
- **Wave 2**: §8 «Логарифмическая функция $y = \log_a x$». Парный двухпанельный график + отражение через $y = x$ для демонстрации обратной функции.
|
||||
- **Wave 3**: §9 «Логарифмические уравнения». 4 метода: к одному основанию / потенциирование / замена / графический. + Проверка ОДЗ.
|
||||
- **Wave 4**: §10 «Логарифмические неравенства» + Финал главы 3 (5 боссов + ачивка «Магистр логарифмов»).
|
||||
|
||||
### Phase 4: Финал курса (Phase 5 в Алгебре 9 формате)
|
||||
- Итоговая шпаргалка (5 mini-карточек: степени / степенная / показательная / логарифмы / лог. функция)
|
||||
- **7 интегрированных боссов**, комбинирующих темы из разных глав:
|
||||
1. **Степень + логарифм**: вычислить $4^{\log_2 5}$
|
||||
2. **Показательное уравнение + замена**: $4^x - 3 \cdot 2^x + 2 = 0$
|
||||
3. **Логарифмическое неравенство + ОДЗ**: $\log_{1/2}(x-1) > -2$
|
||||
4. **Свойства log**: упростить $\log_3 \tfrac{72}{8} - \log_3 \tfrac{9}{2}$
|
||||
5. **Графический синтез**: найти кол-во решений $2^x = \log_2 x$
|
||||
6. **Логарифмирование показательного**: решить $5^{2x} = 7$ через $\log_5$
|
||||
7. **Магистр функций**: при каком $a$ функция $y = (a-2)^x$ убывает?
|
||||
- Ачивка «Магистр алгебры 11» + 50 XP + confetti
|
||||
- Кнопка «К каталогу учебников» → `/textbooks`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Структура каждой главы
|
||||
|
||||
### Wave 0 — skeleton (включено в Phase 0)
|
||||
- CSS (палитра 3 цветовых акцента на §)
|
||||
- Sections со stub-builder'ами
|
||||
- Hub-карточка обновляется
|
||||
- Миграция в БД
|
||||
|
||||
### Wave N — наполнение § (по 1–2 § на волну)
|
||||
|
||||
Каждый § = ровно тот же шаблон, что и Геометрия 9:
|
||||
- **3 теоретических карточки** (`makeCard` типа theory/rule/example) с SVG-графиками
|
||||
- **4 интерактива** (`.wg` виджеты):
|
||||
1. **SVG-конструктор** с slider'ом (главный визуализатор темы)
|
||||
2. **Калькулятор/пошаговый разбор** (ввод параметров → формула с подстановкой)
|
||||
3. **Квикфайр-викторина** (6–8 заданий, 2–4 кнопки)
|
||||
4. **Тренажёр** (5–6 задач с числовым ответом, допуск 0.01)
|
||||
- **Кнопка «Я прочитал §» (+10 XP, +30%)**
|
||||
- **Прогресс**: IV1 15%/10XP, IV2 15%/10XP, IV3 25%/15XP, IV4 25%/15XP, чтение 30%/10XP
|
||||
|
||||
### Wave финал главы
|
||||
- **Итоговая шпаргалка** — mini-cards с формулой на §
|
||||
- **5 интегрированных боссов** (комбинирующих темы) — каждый +10 XP, +18% к `finalN`
|
||||
- **Достижение «Мастер главы N»** при 5/5 + 50 XP + confetti
|
||||
- **Кнопка «Дальше: Глава N+1 →»** (или «К хабу» для последней)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Оценка объёма
|
||||
|
||||
| Глава | § | Ожидаемый LOC |
|
||||
|-------|---|---------------|
|
||||
| Глава 1 (§1-3) | 3 | ~5500 |
|
||||
| Глава 2 (§4-6) | 3 | ~5800 (главные графики функций) |
|
||||
| Глава 3 (§7-10) | 4 | ~7000 |
|
||||
| Hub + финал курса | — | ~900 |
|
||||
| **Итого** | **10** | **~19 000 LOC** |
|
||||
|
||||
Для сравнения: Алгебра 9 = 19 § = ~30 000 LOC. Здесь меньше § (10 вместо 19), но каждый требует визуально насыщенных графиков.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Критические правила (на основе опыта Алгебры 9 + Геом 9)
|
||||
|
||||
### ❌ НЕ делать
|
||||
- **Slider'ы в пиксельных диапазонах** (40..150). Только в учебных единицах с понятным шагом.
|
||||
- **Подписи длин в пикселях** (`R = 120`, `c = 150`). Только в учебных единицах.
|
||||
- **«Три треугольника» когда нарисовано два** — заголовок и контент должны совпадать.
|
||||
- **Эмодзи `⚠`, `❌`, `✅`, `🔥`** в коде. Только inline SVG.
|
||||
- **Одиночный `\` перед буквой** в template literals: `\dfrac`, `\sin`, `\log`. ВСЕГДА `\\dfrac`.
|
||||
- **Прямой угол в нестандартной позиции** (хотя в 11 классе их меньше — больше графики).
|
||||
- **Sweep=1 без проверки направления** — используем `angleArcAuto`.
|
||||
- **Графики с плохими асимптотами** — например, $y = a^x$ должен явно показывать асимптоту $y=0$, а $y = \log_a x$ — $x=0$.
|
||||
|
||||
### ✅ Обязательно
|
||||
- **KaTeX-аудит на каждом коммите** — `grep -nP "[^\\\\]\\\\[a-z]{2,}"` для JS-секций.
|
||||
- **JS parse-check** через `new Function(scriptBody)` после каждого Wave.
|
||||
- **Координаты — формулами**, не «на глаз».
|
||||
- **Drag — state ВЫШЕ `redraw()`**.
|
||||
- **Цветовая кодировка $a>1$ vs $0<a<1$** во всех § Глав 2 и 3 — пусть будет неизменна.
|
||||
- **Эталонные snap-точки** в slider'ах: $a = 1/2, e, 2, 10$.
|
||||
- **Все builder-функции в registry должны быть НЕ stub'ами** в конце Wave финала.
|
||||
- **Использовать `ast-index` для поиска** (НЕ grep — правило проекта).
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Запуск
|
||||
|
||||
**Phase 0**: skeleton всех 3 ch-файлов Алгебры 11 + hub + миграция + 11 SVG-хелперов.
|
||||
Цвет: **emerald/teal** (свежий, отличается от algebra-9 индиго и algebra-7 розового).
|
||||
Сразу с первого Wave — все правила качества из этого плана.
|
||||
|
||||
После Phase 0 → Phase 1 Wave 1 (§1 Степень с рац. показателем).
|
||||
|
||||
После завершения всех 4 Phase Алгебры 11 — переход к плану Геометрии 11.
|
||||
@@ -0,0 +1,349 @@
|
||||
# План реализации: Геометрия 11 (Беларусь, Латотин/Чеботаревский, 2020)
|
||||
|
||||
**Источник:** `geometriya_11kl_latotin_rus_2020.pdf` (237 стр., 4 раздела, 11 §)
|
||||
**Авторы:** Л. А. Латотин, Б. Д. Чеботаревский, И. В. Горбунова, О. Е. Цыбулько
|
||||
**Издательство:** «Белорусская энциклопедия имени Петруся Бровки», 2020
|
||||
**Уровень:** Базовый + повышенный.
|
||||
|
||||
> Это **стереометрия в выпускном классе**. Главный вызов — настоящие 3D-фигуры в браузере: призма, цилиндр, пирамида, конус, сфера, многогранники. Реализуем собственный мини-3D движок поверх SVG для полного контроля и нулевых зависимостей. Повторение (§8-§11) сделаем компактно — это обзор всей школьной геометрии.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Содержание учебника
|
||||
|
||||
### Раздел 1. Призма и цилиндр (§1–§2)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §1 | Призма | $S_{бок} = P \cdot l$ (наклонная), $S_{бок} = P_{осн} \cdot h$ (прямая), $V = S_{осн} \cdot h$. Прямая, правильная, наклонная. Параллелепипед, прямоугольный, куб. |
|
||||
| §2 | Цилиндр | $S_{бок} = 2\pi R h$, $S_{полн} = 2\pi R(R+h)$, $V = \pi R^2 h$. Сечения, развёртка. |
|
||||
|
||||
### Раздел 2. Пирамида и конус (§3–§4)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §3 | Пирамида | $V = \tfrac{1}{3} S_{осн} h$, правильная: $S_{бок} = \tfrac{1}{2} P_{осн} \cdot l$ (l — апофема). Усечённая пирамида. |
|
||||
| §4 | Конус | $S_{бок} = \pi R l$, $V = \tfrac{1}{3} \pi R^2 h$, развёртка — сектор. Усечённый конус. |
|
||||
|
||||
### Раздел 3. Сфера и шар (§5–§7)
|
||||
| § | Тема | Ключевые формулы |
|
||||
|---|------|------------------|
|
||||
| §5 | Сфера | Уравнение $(x-a)^2 + (y-b)^2 + (z-c)^2 = R^2$, касательная плоскость. |
|
||||
| §6 | Шар | $S = 4\pi R^2$, $V = \tfrac{4}{3}\pi R^3$. Шаровой сегмент, сектор, слой. Вписанные и описанные многогранники. |
|
||||
| §7 | Правильные многогранники | 5 платоновых тел: тетраэдр, куб, октаэдр, додекаэдр, икосаэдр. Двойственность. |
|
||||
|
||||
### Раздел 4. Повторение всей геометрии (§8–§11)
|
||||
| § | Тема | Объём |
|
||||
|---|------|-------|
|
||||
| §8 | Геометрические фигуры и их свойства (повторение планиметрии) | 156-188 стр. |
|
||||
| §9 | Геометрические величины (площади, объёмы) | 156-188 стр. |
|
||||
| §10 | Координаты и векторы (включая 3D-векторы) | 189-199 стр. |
|
||||
| §11 | Геометрические построения | 200-219 стр. |
|
||||
|
||||
**Итого: 11 параграфов в 4 разделах.** § 1–7 — новый материал стереометрии. § 8–11 — итоговое повторение.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 SVG-СТАНДАРТ 3D-КАЧЕСТВА
|
||||
|
||||
Это самое большое нововведение всего проекта. Нужен мини-3D движок поверх SVG.
|
||||
|
||||
### Мини-3D движок (хелпер `g3d.js` или встроен в каждый ch)
|
||||
|
||||
```js
|
||||
// === Матрицы 3D-поворота ===
|
||||
function matRotY(angle) {
|
||||
const c = Math.cos(angle), s = Math.sin(angle);
|
||||
return [[c, 0, s], [0, 1, 0], [-s, 0, c]];
|
||||
}
|
||||
function matRotX(angle) {
|
||||
const c = Math.cos(angle), s = Math.sin(angle);
|
||||
return [[1, 0, 0], [0, c, -s], [0, s, c]];
|
||||
}
|
||||
function matMul(A, B) { /* умножение матриц 3x3 */ }
|
||||
function vecApply(M, v) {
|
||||
return {
|
||||
x: M[0][0]*v.x + M[0][1]*v.y + M[0][2]*v.z,
|
||||
y: M[1][0]*v.x + M[1][1]*v.y + M[1][2]*v.z,
|
||||
z: M[2][0]*v.x + M[2][1]*v.y + M[2][2]*v.z
|
||||
};
|
||||
}
|
||||
|
||||
// === Проекция (изометрическая или перспективная) ===
|
||||
function projectIso(v, cx, cy, scale) {
|
||||
// Изометрическая: x' = scale * (v.x - v.z), y' = scale * (v.y - (v.x + v.z)/2)
|
||||
const angle = Math.PI / 6;
|
||||
return {
|
||||
x: cx + scale * (v.x * Math.cos(angle) - v.z * Math.cos(angle)),
|
||||
y: cy - scale * (v.y - v.x * Math.sin(angle) - v.z * Math.sin(angle))
|
||||
};
|
||||
}
|
||||
|
||||
// Перспективная проекция с виртуальной камерой
|
||||
function projectPersp(v, camDist, cx, cy, scale) {
|
||||
const z = v.z + camDist;
|
||||
const k = scale / z;
|
||||
return { x: cx + v.x * k, y: cy - v.y * k };
|
||||
}
|
||||
|
||||
// === Сцена ===
|
||||
function createScene(W, H, opts) {
|
||||
return {
|
||||
W, H,
|
||||
cx: W / 2, cy: H / 2,
|
||||
scale: opts.scale || 60,
|
||||
rotX: opts.rotX || -0.3, // наклон вниз
|
||||
rotY: opts.rotY || 0.6, // поворот в сторону
|
||||
objects: [],
|
||||
render() { /* возвращает SVG content */ }
|
||||
};
|
||||
}
|
||||
|
||||
// === Фигуры ===
|
||||
function prismVertices(n, R, h) {
|
||||
// Правильная n-угольная призма с основанием R и высотой h
|
||||
const bottom = [], top = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = 2 * Math.PI * i / n - Math.PI/2;
|
||||
bottom.push({ x: R * Math.cos(a), y: -h/2, z: R * Math.sin(a) });
|
||||
top.push({ x: R * Math.cos(a), y: h/2, z: R * Math.sin(a) });
|
||||
}
|
||||
return { vertices: [...bottom, ...top], faces: prismFaces(n) };
|
||||
}
|
||||
|
||||
function pyramidVertices(n, R, h) {
|
||||
const base = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = 2 * Math.PI * i / n - Math.PI/2;
|
||||
base.push({ x: R * Math.cos(a), y: -h/2, z: R * Math.sin(a) });
|
||||
}
|
||||
const apex = { x: 0, y: h/2, z: 0 };
|
||||
return { vertices: [...base, apex], faces: pyramidFaces(n) };
|
||||
}
|
||||
|
||||
function cylinderMesh(R, h, segments) {
|
||||
// segments = 32 (число делений окружности)
|
||||
// Возвращает массив отрезков образующих + 2 эллипса
|
||||
}
|
||||
|
||||
function coneMesh(R, h, segments) { /* аналогично */ }
|
||||
|
||||
function sphereMesh(R, latSegs, lonSegs) {
|
||||
// Сетка из параллелей и меридианов
|
||||
}
|
||||
|
||||
// === Скрытие невидимых рёбер (back-face culling + Z-sort) ===
|
||||
function sortFacesByZ(faces, M) {
|
||||
return faces.sort((a, b) => {
|
||||
const za = avgZ(a, M), zb = avgZ(b, M);
|
||||
return zb - za; // дальние сначала
|
||||
});
|
||||
}
|
||||
|
||||
function isVisibleFace(face, M, camDist) {
|
||||
// Нормаль грани через cross product, проверка знака с z-вектором камеры
|
||||
}
|
||||
|
||||
// === Renderer ===
|
||||
function renderObject(obj, M, projector, opts) {
|
||||
const projected = obj.vertices.map(v => projector(vecApply(M, v)));
|
||||
let s = '';
|
||||
// Скрытые рёбра — пунктиром, видимые — сплошной линией
|
||||
obj.faces.forEach(face => {
|
||||
const points = face.indices.map(i => projected[i].x + ',' + projected[i].y).join(' ');
|
||||
const visible = isVisibleFace(face, M);
|
||||
s += `<polygon points="${points}" fill="${opts.fill}" fill-opacity="${visible ? 0.18 : 0}" stroke="${visible ? '#0f172a' : '#94a3b8'}" stroke-width="${visible ? 1.8 : 1}" stroke-dasharray="${visible ? '' : '4 3'}"/>`;
|
||||
});
|
||||
return s;
|
||||
}
|
||||
```
|
||||
|
||||
### Drag для вращения сцены
|
||||
|
||||
```js
|
||||
function attachOrbitControl(svg, scene) {
|
||||
let dragging = false, lastX = 0, lastY = 0;
|
||||
svg.style.touchAction = 'none';
|
||||
svg.style.cursor = 'grab';
|
||||
function onDown(e) {
|
||||
dragging = true; svg.style.cursor = 'grabbing';
|
||||
const p = (e.touches ? e.touches[0] : e);
|
||||
lastX = p.clientX; lastY = p.clientY;
|
||||
e.preventDefault();
|
||||
}
|
||||
function onMove(e) {
|
||||
if (!dragging) return;
|
||||
const p = (e.touches ? e.touches[0] : e);
|
||||
const dx = p.clientX - lastX, dy = p.clientY - lastY;
|
||||
scene.rotY += dx * 0.012;
|
||||
scene.rotX += dy * 0.012;
|
||||
scene.rotX = Math.max(-1.2, Math.min(1.2, scene.rotX)); // clamp
|
||||
lastX = p.clientX; lastY = p.clientY;
|
||||
scene.redraw();
|
||||
e.preventDefault();
|
||||
}
|
||||
function onUp() { dragging = false; svg.style.cursor = 'grab'; }
|
||||
svg.addEventListener('mousedown', onDown, { passive: false });
|
||||
svg.addEventListener('touchstart', onDown, { passive: false });
|
||||
window.addEventListener('mousemove', onMove, { passive: false });
|
||||
window.addEventListener('touchmove', onMove, { passive: false });
|
||||
window.addEventListener('mouseup', onUp);
|
||||
window.addEventListener('touchend', onUp);
|
||||
}
|
||||
```
|
||||
|
||||
### Правила (обязательны с §1)
|
||||
|
||||
1. **Изометрическая проекция по умолчанию** (классическая для школьных стереометрических чертежей).
|
||||
2. **Drag-to-rotate** на каждом 3D-SVG, slider'ы $\alpha$/$\beta$ — для точного контроля.
|
||||
3. **Кнопки «Сверху», «Сбоку», «Спереди»** — мгновенный возврат к стандартным проекциям.
|
||||
4. **Невидимые рёбра — пунктиром** (`stroke-dasharray="4 3"`), видимые — сплошной линией.
|
||||
5. **Полупрозрачная заливка граней** (`fill-opacity="0.18"`) для понимания глубины.
|
||||
6. **Подписи вершин** — снаружи фигуры (после проекции).
|
||||
7. **Подсветка сечения** — другой цвет грани (например, розовый/розовый-полупрозрачный).
|
||||
8. **Slider'ы — в учебных единицах** (рёбра 1..8 ед., высота 1..10 ед.), НЕ в пикселях.
|
||||
9. **Цветовая кодировка:**
|
||||
- Основание призмы/пирамиды — розовое (`#fce7f3`, обводка `#db2777`)
|
||||
- Боковая поверхность — голубое (`#dbeafe`, обводка `#0891b2`)
|
||||
- Высота, апофема — красный (`#dc2626`) пунктиром
|
||||
- Радиус основания — фиолетовый (`#7c3aed`)
|
||||
- Сечения — оранжевое (`#fed7aa`, обводка `#ea580c`)
|
||||
10. **KaTeX везде с двойным экранированием** в JS template literals.
|
||||
11. **Эмодзи запрещены.** Только inline SVG.
|
||||
|
||||
### Типы 3D-SVG по темам
|
||||
|
||||
| § | Тема | Тип SVG |
|
||||
|---|------|---------|
|
||||
| §1 | Призма | Slider $n$ (3..8) + $h$. Правильная призма с возможностью вращения. Опционально: показ диагоналей основания, диагоналей призмы, высот граней. Развёртка (плоская). |
|
||||
| §2 | Цилиндр | Slider $R$, $h$. Прозрачный цилиндр с осью. Сечения: осевое (прямоугольник), параллельное основанию (круг), наклонное (эллипс). Развёртка. Касательная плоскость. |
|
||||
| §3 | Пирамида | Slider $n$ (3..6), $R$ (основание), $h$. Правильная пирамида с апофемой. Усечённая пирамида (slider $r$ верхнего основания). Подсветка апофемы. |
|
||||
| §4 | Конус | Slider $R$, $h$. Конус с образующей и осью. Развёртка — сектор. Усечённый конус. Касательная плоскость по образующей. |
|
||||
| §5 | Сфера | 3D-сфера с сеткой параллелей и меридианов. Slider $R$, центр $(a,b,c)$. Касательная плоскость в точке. |
|
||||
| §6 | Шар | Шаровой сегмент, сектор, слой. Slider высоты сегмента. Шар, вписанный в куб; куб, вписанный в шар. |
|
||||
| §7 | Правильные многогранники | 5 платоновых тел с возможностью переключения. Вращение, развёртки. Двойственность (тетраэдр-тетраэдр, куб-октаэдр, икосаэдр-додекаэдр). |
|
||||
| §8 | Повторение планиметрии | 2D-фигуры из Геом 9 (без 3D). Компактные интерактивы. |
|
||||
| §9 | Геометрические величины | Площади и объёмы — комбинация 2D и 3D в зависимости от темы. |
|
||||
| §10 | Координаты и векторы | 3D-координатная система с осями. Векторы как стрелки в 3D. Сумма, разность, скалярное произведение. |
|
||||
| §11 | Геометрические построения | 2D-построения циркулем и линейкой (как в Геом 9). |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ПОРЯДОК РЕАЛИЗАЦИИ
|
||||
|
||||
### Phase 0: Архитектура (фундамент)
|
||||
- `geometry_11_hub.html` skeleton (палитра **cyan/sky**, чтобы отличить)
|
||||
- `geometry_11_ch1.html` (§1-2 Призма + Цилиндр)
|
||||
- `geometry_11_ch2.html` (§3-4 Пирамида + Конус)
|
||||
- `geometry_11_ch3.html` (§5-7 Сфера + Шар + Правильные многогранники)
|
||||
- `geometry_11_ch4.html` (§8-11 Повторение)
|
||||
- Миграция `023_geometry_11_hub.sql` (slug `geometry-11`, sort_order 10)
|
||||
- Мини-3D движок (`g3d` встроен в каждый ch как глобальный скрипт)
|
||||
- KaTeX CDN, CSS POLISH, ICONS из geom9_ch4
|
||||
|
||||
### Phase 1: Раздел 1 «Призма и цилиндр» (2 §) — 3 волны
|
||||
- **Wave 1**: §1 «Призма». 3 теории + 4 интерактива:
|
||||
1. **3D-конструктор призмы**: slider $n$ (3..8), $a$ (сторона), $h$ (высота). Drag-to-rotate. Развёртка по кнопке.
|
||||
2. **Калькулятор площадей и объёма**: ввод $n, a, h$ → $S_{осн}, P, S_{бок}, S_{полн}, V$ с пошаговым выводом.
|
||||
3. **DnD-сортер**: фигуры → типы (правильная / прямая / наклонная / параллелепипед / куб).
|
||||
4. **Тренажёр**: 6 задач на $V, S$ призмы.
|
||||
- **Wave 2**: §2 «Цилиндр». 3 теории + 4 интерактива:
|
||||
1. **3D-конструктор цилиндра**: slider $R$, $h$. Drag-to-rotate. Сечения: осевое, параллельное, наклонное (slider угла наклона плоскости).
|
||||
2. **Калькулятор**: $S_{бок}, S_{полн}, V$.
|
||||
3. **Квикфайр «Какое сечение?»**: какая фигура получится — круг / эллипс / прямоугольник.
|
||||
4. **Тренажёр**.
|
||||
- **Wave 3**: Финал раздела 1 (4 mini-шпаргалки + 5 боссов + ачивка «Мастер призмы и цилиндра»).
|
||||
|
||||
### Phase 2: Раздел 2 «Пирамида и конус» (2 §) — 3 волны
|
||||
- **Wave 1**: §3 «Пирамида». 3D-конструктор с slider'ом $n$, $R$, $h$. Подсветка апофемы. Усечённая пирамида.
|
||||
- **Wave 2**: §4 «Конус». 3D-конус с осью, образующей. Развёртка-сектор с slider'ом угла раскрытия. Касательная плоскость.
|
||||
- **Wave 3**: Финал раздела 2 (5 боссов + ачивка «Мастер пирамиды и конуса»).
|
||||
|
||||
### Phase 3: Раздел 3 «Сфера, шар, многогранники» (3 §) — 4 волны
|
||||
- **Wave 1**: §5 «Сфера». 3D-сфера с сеткой параллелей/меридианов. Касательная плоскость в точке. Уравнение сферы в координатах. Slider центра $(a,b,c)$ и $R$.
|
||||
- **Wave 2**: §6 «Шар». Площадь поверхности, объём. Шаровой сегмент/сектор/слой. Вписанные и описанные многогранники.
|
||||
- **Wave 3**: §7 «Правильные многогранники». 5 платоновых тел с переключателем. Свойства каждого. Двойственность.
|
||||
- **Wave 4**: Финал раздела 3 (5 боссов + ачивка «Мастер сфер»).
|
||||
|
||||
### Phase 4: Раздел 4 «Повторение» (4 §) — компактно, 3 волны
|
||||
- **Wave 1**: §8 «Планиметрия» + §9 «Площади и объёмы». Каждый — 2 теории + 3 интерактива (без 3D). Это обзор, не углубление.
|
||||
- **Wave 2**: §10 «Координаты и векторы 3D». 3D-визуализатор векторов (стрелки в координатной системе). Slider'ы координат вектора. Скалярное произведение, угол.
|
||||
- **Wave 3**: §11 «Построения» + Финал раздела 4 (3 боссов на каждое §).
|
||||
|
||||
### Phase 5: Финал всего курса
|
||||
- Итоговая шпаргалка по 4 разделам.
|
||||
- **9 интегрированных боссов** (геометрия 11 объёмная — больше боссов):
|
||||
1. **Призма + Цилиндр**: цилиндр вписан в правильную 6-угольную призму...
|
||||
2. **Пирамида + Сечение**: найти угол наклона апофемы.
|
||||
3. **Конус + Развёртка**: найти угол сектора по $R$ и $l$.
|
||||
4. **Шар + Многогранник**: вписать шар в куб.
|
||||
5. **Сфера + Координаты**: уравнение касательной плоскости.
|
||||
6. **Многогранники + Объёмы**: найти объём октаэдра через куб.
|
||||
7. **3D-векторы**: скалярное произведение, угол.
|
||||
8. **Параллелепипед + Диагональ**: $d^2 = a^2 + b^2 + c^2$.
|
||||
9. **Магистр стереометрии**: синтез всех формул.
|
||||
- Ачивка «Магистр геометрии 11» + 100 XP бонус + confetti.
|
||||
- Кнопка «К каталогу учебников».
|
||||
|
||||
---
|
||||
|
||||
## 📦 Структура каждого § (стандарт)
|
||||
|
||||
То же, что в Геом 9 / Алгебра 9 / Алгебра 11:
|
||||
- 3 теоретических карточки (`makeCard`)
|
||||
- 4 интерактива (один из них — 3D-конструктор, остальные могут быть 2D-калькуляторы, DnD, квикфайр, тренажёр)
|
||||
- Кнопка «Я прочитал § (+10 XP)»
|
||||
- Прогресс/XP: IV1 15%/10XP, IV2 15%/10XP, IV3 25%/15XP, IV4 25%/15XP, чтение 30%/10XP
|
||||
|
||||
### Финал раздела
|
||||
- 4 mini-шпаргалки (по §) с ключевыми формулами
|
||||
- 5 боссов (каждый +10 XP, +18% к `finalN`)
|
||||
- Ачивка `rN_done` при 5/5 (+50 XP бонус)
|
||||
- Кнопка к следующему разделу
|
||||
|
||||
---
|
||||
|
||||
## 📊 Оценка объёма
|
||||
|
||||
| Раздел | § | Особенности | LOC |
|
||||
|--------|---|-------------|-----|
|
||||
| Phase 0: skeleton | — | 4 ch + hub + 3D-движок | 1500 |
|
||||
| Раздел 1 (§1-2) | 2 | Призма, цилиндр — главный 3D | 6000 |
|
||||
| Раздел 2 (§3-4) | 2 | Пирамида, конус | 5500 |
|
||||
| Раздел 3 (§5-7) | 3 | Сфера, шар, многогранники | 8000 |
|
||||
| Раздел 4 (§8-11) | 4 | Повторение — компактно | 6000 |
|
||||
| Финал курса | — | 9 боссов | 1200 |
|
||||
| **Итого** | **11** | | **~28 000 LOC** |
|
||||
|
||||
Это больше, чем Геом 9 (~26K LOC), из-за 3D-движка и стереометрии.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Критические правила
|
||||
|
||||
### ❌ НЕ делать
|
||||
- **3D в пиксельных координатах**. Вершины фигур — всегда в учебных единицах, проекция масштабирует к пикселям.
|
||||
- **«Магические числа» в матрицах поворота**. Всё через `matRotX`, `matRotY`, `matMul`.
|
||||
- **Слайдеры рёбер $a$ от 40 до 150**. Только в единицах: $a \in [1; 8]$.
|
||||
- **Drag без `passive: false`** — будет глючить на тач-устройствах.
|
||||
- **Hidden surface removal на глаз** — используем sort by avg-Z или back-face culling.
|
||||
- **Подписи вершин внутри фигуры** — всегда снаружи, после проекции.
|
||||
- **Сечения с пересечениями граней без проверки** — может быть некрасиво. Проверяем алгоритм.
|
||||
|
||||
### ✅ Обязательно
|
||||
- **Drag-to-rotate** на каждом 3D-SVG (не только slider'ы).
|
||||
- **3 предустановленные кнопки проекции** (сверху/сбоку/спереди).
|
||||
- **Slider'ы в учебных единицах** с разумным масштабом проекции.
|
||||
- **JS parse-check** после каждого Wave.
|
||||
- **KaTeX-аудит** через grep regex после каждого Wave.
|
||||
- **3D-движок — общий файл `js/g3d.js`** или встроен в каждый ch (предпочтительно общий, чтобы не дублировать).
|
||||
- **Все builder-функции в registry — НЕ stub'ы** в конце Wave финала.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Запуск
|
||||
|
||||
**Phase 0**: skeleton 4 ch-файлов Геом 11 + hub + миграция + мини-3D движок.
|
||||
Палитра: **cyan/sky** (свежая, отличается от geom-9 розовой/rose).
|
||||
|
||||
После Phase 0 → Phase 1 Wave 1 (§1 Призма — самая первая 3D-фигура).
|
||||
|
||||
После завершения всех 5 Phase Геометрии 11 — план двух 11-классов завершён.
|
||||
@@ -0,0 +1,363 @@
|
||||
# План реализации: Химия 9 (Беларусь) — интерактивный наглядный учебник
|
||||
|
||||
> Цель: превратить существующий «текстовый» каркас `chemistry_9.html` в **максимально
|
||||
> полный, интерактивный и наглядный** учебник по всей программе 9 класса —
|
||||
> на уровне качества уже сделанных Физики 9 / Алгебры 9 / Геометрии 9, но с
|
||||
> химической спецификой (молекулы, уравнения реакций, ионы, осадки, газы, симуляторы).
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Источник
|
||||
|
||||
| Параметр | Значение |
|
||||
|----------|----------|
|
||||
| Книга | `Himiya_Shimanovich_9kl_rus_2025.pdf` |
|
||||
| Авторы | Шиманович И. Е., Василевская Е. И., Зураев А. В., Красицкий В. А., Сечко О. И. |
|
||||
| Под ред. | И. Е. Шимановича |
|
||||
| Изд. | Минск, «Адукацыя і выхаванне», 2025 (2-е изд., переработанное), 288 с. |
|
||||
| ISBN | 978-985-34-0189-9 |
|
||||
| Структура | **4 главы, 53 §, 6 лабораторных опытов, 4 практические работы** |
|
||||
| Справочные таблицы | ПСХЭ Менделеева, таблица растворимости, ряд активности металлов (форзацы) |
|
||||
|
||||
PDF лежит в `G:\Dev\Тесты\Методички\тест_6 класс\Книги\` (есть и издание 2019 г.).
|
||||
|
||||
---
|
||||
|
||||
## 📗 ПОЛНАЯ КАРТА СОДЕРЖАНИЯ (53 §)
|
||||
|
||||
Колонка **«Интерактив»** — главный наглядный элемент, который страница ДОЛЖНА давать
|
||||
сверх текста (минимум 1 «звёздный» виджет на §; полный набор виджетов — ниже в стандарте).
|
||||
|
||||
### ГЛАВА 1. Повторение курса 8 класса (§§1–3) — *amber*
|
||||
| § | Тема | Ключ | Интерактив (звёздный виджет) |
|
||||
|---|------|------|------------------------------|
|
||||
| §1 | Основные классы неорганических веществ | оксиды/кислоты/основания/соли, генетическая связь | **Классификатор**: drag формулы → класс; граф генетических переходов |
|
||||
| §2 | Строение атома и периодический закон | $Z$, $p/n/e$, $1s^22s^2…$, периодичность | **Интерактивная ПСХЭ** (`periodic`) + конструктор электронной конфигурации |
|
||||
| §3 | Химическая связь | ионная / ковалентная (полярн./неполярн.) / металлическая, ЭО | **Визуализатор связи**: slider ЭО → тип связи; диполь, электронные пары |
|
||||
|
||||
### ГЛАВА 2. Растворы. Теория электролитической диссоциации (§§4–11) — *blue*
|
||||
| § | Тема | Ключ | Интерактив |
|
||||
|---|------|------|------------|
|
||||
| §4 | Растворы как смеси веществ | раствор = растворитель + растворённое в-во | Анимация растворения (частицы) |
|
||||
| §5 | Качественные характеристики состава растворов | насыщ./ненасыщ./пересыщ., растворимость, кривые растворимости | **График растворимости** $s=f(t)$ с подвижной точкой |
|
||||
| §6 | Количественные характеристики. Массовая доля | $w=\dfrac{m_{в-ва}}{m_{р-ра}}$, $c=\dfrac{n}{V}$ | **Калькулятор раствора** (w, c, разбавление, смешение) |
|
||||
| §7 | Электролитическая диссоциация в растворах | электролиты/неэлектролиты, гидратация ионов | **Анимация диссоциации** NaCl/HCl в воде |
|
||||
| §8 | Диссоциация кислот, оснований и солей | $HCl\to H^++Cl^-$, ступенчатая дисс. | Конструктор уравнений диссоциации |
|
||||
| §9 | Реакции ионного обмена | условия необратимости: ↓ ↑ H₂O | **Предсказатель РИО** + конвертер молек.↔полн.ион.↔сокр.ион. |
|
||||
| §10 | Свойства кислот/оснований/солей с точки зрения ТЭД | ионные сущности реакций | Матрица реакций «реагент × реагент» |
|
||||
| §11 | Вода и растворы в жизни человека | значение воды, растворы в быту | Инфографика-исследование |
|
||||
|
||||
**Практическая работа 1** (после §6): приготовление раствора с заданной $w$.
|
||||
**Практическая работа 2** (после §9): реакции ионного обмена.
|
||||
|
||||
### ГЛАВА 3. Неметаллы (§§12–39) — *cyan / teal*
|
||||
| § | Тема | Ключ | Интерактив |
|
||||
|---|------|------|------------|
|
||||
| §12 | Общая характеристика неметаллов | положение в ПСХЭ, ЭО, окислители | Подсветка неметаллов в ПСХЭ |
|
||||
| §13 | Простые вещества неметаллы | аллотропия (O₂/O₃, алмаз/графит/фуллерен) | **3D-модели аллотропов** (biochem 3D) |
|
||||
| §14 | Водород | получение, св-ва, $H_2+O_2$ | Сборка прибора получения H₂ |
|
||||
| §15 | Галогены | $F,Cl,Br,I$, окислит. способность ↓ | **Ряд активности галогенов** + вытеснение |
|
||||
| §16 | Хлороводород. Соляная кислота | $HCl$, свойства | Диссоциация + реакции HCl |
|
||||
| §17 | Хлориды. Применение HCl и хлоридов | качеств. реакция на $Cl^-$ | **Качественная реакция** AgCl↓ (анимация осадка) |
|
||||
| §18 | Кислород — элемент и простое вещество | $O_2$, окисление, горение | Симулятор горения |
|
||||
| §19 | Кислород в природе. Получение и применение | круговорот, способы получения | Инфографика круговорота |
|
||||
| §20 | Сера — элемент и простое вещество | аллотропия серы, $S+Me$ | 3D-модель S₈ |
|
||||
| §21 | Сероводород | $H_2S$, св-ва, сульфиды | Реакции H₂S |
|
||||
| §22 | Оксид серы(IV) и оксид серы(VI) | $SO_2, SO_3$, кислотные оксиды | Кислотные дожди (инфографика) |
|
||||
| §23 | Серная кислота | $H_2SO_4$ конц./разб., безопасность | **Анимация разбавления** (кислоту в воду!) |
|
||||
| §24 | Реакции получения серной кислоты. Практический выход | контактный способ, $\eta$ | Калькулятор практического выхода |
|
||||
| §25 | Сульфаты. Применение | качеств. реакция на $SO_4^{2-}$ | **Качественная реакция** BaSO₄↓ |
|
||||
| §26 | Азот — элемент и простое вещество | $N_2$, тройная связь | 3D-модель N₂ |
|
||||
| §27 | Аммиак | $NH_3$, донорно-акц. связь | **Фонтан аммиака** (анимация) |
|
||||
| §28 | Соли аммония | $NH_4^+$, разложение | Качеств. реакция на $NH_4^+$ |
|
||||
| §29 | Азотная кислота | $HNO_3$, особые окислит. св-ва | Реакции с металлами (без H₂) |
|
||||
| §30 | Нитраты. Применение HNO₃ и нитратов | разложение нитратов, селитры | Карта разложения нитратов |
|
||||
| §31 | Фосфор — элемент и простое вещество | аллотропия (бел./красн.) | 3D-модель P₄ |
|
||||
| §32 | Кислородсодержащие соединения фосфора | $P_2O_5, H_3PO_4$, фосфаты | Ступенчатая диссоциация H₃PO₄ |
|
||||
| §33 | Минеральные удобрения | N/P/K, аммофос, суперфосфат | **NPK-калькулятор** удобрений |
|
||||
| §34 | Углерод — элемент и простое вещество | аллотропия (алмаз/графит/фуллерен), адсорбция | **3D-модели** + адсорбция |
|
||||
| §35 | Оксиды углерода | $CO$ (яд), $CO_2$ | Свойства CO/CO₂ |
|
||||
| §36 | Угольная кислота и её соли | $H_2CO_3$, карбонаты/гидрокарбонаты, жёсткость | **Качеств. реакция** на $CO_3^{2-}$ (вскипание) + сталактиты |
|
||||
| §37 | Кремний — элемент и простое вещество | $Si$, полупроводник | 3D-решётка (как алмаз) |
|
||||
| §38 | Оксид кремния(IV). Кремниевая кислота | $SiO_2$, силикаты | Структура SiO₂ |
|
||||
| §39 | Строительные материалы | стекло, керамика, цемент, бетон | Конструктор «варки стекла» |
|
||||
|
||||
**Лаб. опыты**: 1 — на $Cl^-$ (после §17), 2 — на $SO_4^{2-}$ (после §25), 3 — на $NH_4^+$ (после §28), 4 — на $CO_3^{2-}$ (после §36).
|
||||
**Практическая работа 3** (после §39): решение экспериментальных задач «Неметаллы».
|
||||
|
||||
### ГЛАВА 4. Металлы (§§40–53) — *deep-orange / rose*
|
||||
| § | Тема | Ключ | Интерактив |
|
||||
|---|------|------|------------|
|
||||
| §40 | Металлы. Общая характеристика элементов | положение в ПСХЭ, $Me^0-ne^-\to Me^{n+}$ | Подсветка металлов в ПСХЭ |
|
||||
| §41 | Простые вещества металлы. Физические свойства. Сплавы | металлич. связь, «электронный газ», сплавы | **Модель металлич. связи** + конструктор сплавов |
|
||||
| §42 | Общие химические свойства металлов | ряд активности, $Me$+кислота/соль/вода | **Интерактивный ряд активности** (предсказание реакции) |
|
||||
| §43 | Щелочные металлы | $Li,Na,K…$, реакция с водой | Анимация $Na+H_2O$ |
|
||||
| §44 | Важнейшие соединения натрия и калия | $NaOH, Na_2CO_3$, применение | Применение (инфографика) |
|
||||
| §45 | Электролиз расплавов солей | катод/анод, $2NaCl\to2Na+Cl_2$ | **Симулятор электролиза** (`electrolysis`) |
|
||||
| §46 | Магний и щёлочноземельные металлы | $Mg,Ca,Sr,Ba$, цвета пламени | **Цвета пламени** (интерактив) |
|
||||
| §47 | Важнейшие соединения магния и кальция | $CaO, Ca(OH)_2, CaCO_3$, гипс | Превращения Ca-соединений |
|
||||
| §48 | Качественное обнаружение ионов Ca²⁺/Ba²⁺. Жёсткость воды | $Ba^{2+}+SO_4^{2-}$, жёсткость, умягчение | **Симулятор жёсткости воды** + устранение |
|
||||
| §49 | Алюминий | $Al$, амфотерность, алюмотермия | Реакции Al (амфотерность) |
|
||||
| §50 | Оксид и гидроксид алюминия. Соли алюминия | $Al_2O_3, Al(OH)_3$ амфотерны | **Амфотерность** (реакция и с кислотой, и со щёлочью) |
|
||||
| §51 | Железо. Физические и химические свойства. Коррозия | $Fe$, $Fe^{2+}/Fe^{3+}$, ржавление | **Симулятор коррозии** |
|
||||
| §52 | Важнейшие соединения железа. Качественные реакции на ионы железа | $FeO/Fe_2O_3$, $Fe^{2+}/Fe^{3+}$ обнаружение | **Качеств. реакции** на $Fe^{2+}, Fe^{3+}$ |
|
||||
| §53 | Получение железа. Применение | доменная печь, чугун/сталь | **Анимация доменной печи** |
|
||||
|
||||
**Лаб. опыт 5** (после §48): качеств. реакции на $Ca^{2+}/Ba^{2+}$.
|
||||
**Лаб. опыт 6** (после §52): получение и окисление $Fe(OH)_2$.
|
||||
**Практическая работа 4** (после §52): решение экспериментальных задач «Металлы».
|
||||
|
||||
**Итого**: 53 §, 4 главы, 6 лаб. опытов, 4 практические работы.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 ТЕКУЩЕЕ СОСТОЯНИЕ `chemistry_9.html` (gap-анализ)
|
||||
|
||||
Файл уже существует: `frontend/textbooks/chemistry_9.html` (~860 КБ, 10 659 строк).
|
||||
|
||||
**Что есть ✅**
|
||||
- Скелет страницы по образцу физики: header, para-selector (карточки секций), табы, sidebar
|
||||
- Все 53 § как `data-para="p1"..."p53"` (структура совпадает с реальной книгой)
|
||||
- Amber-палитра + dark mode, адаптив
|
||||
- ~58 маркеров `boss/quiz/тренаж` — есть зачатки тренажёров
|
||||
- Автоначисление XP через `/js/textbook-xp-widget.js` (+5 XP за первый клик по §)
|
||||
- Прогресс/закладки через `/js/textbook-tracker.js` + БД (`textbooks`, `textbook_progress`, `textbook_bookmarks`)
|
||||
|
||||
**Чего НЕТ ❌ (главный объём работы)**
|
||||
- **Наглядность**: всего **12 `<svg>` на весь файл**, **0 `<canvas>`**, **0 симуляторов** (`openSim` не вызывается ни разу)
|
||||
- **0 молекулярных моделей** (хотя `biochem-core.js` умеет 2D/3D шаростержневые)
|
||||
- **0 рендера уравнений реакций** (нет стрелок реакций `→ ⇌ ↑ ↓ →[t]`, коэффициентов, состояний)
|
||||
- **0 глоссария** (в других учебниках есть)
|
||||
- Таблица растворимости и ряд активности — **только картинки на форзаце, не интерактивны**
|
||||
- Заголовок/водяной знак содержат артефакты от копирования шаблона «МЕТАЛЛЫ»; title `«§1–60»` — **устарел** (реально 53 §)
|
||||
- Секционная группировка карточек (`psel-c1..c6`) не выровнена на 4 главы книги
|
||||
|
||||
**Вывод**: каркас переиспользуем, но содержательно страница «текстовая». План = наполнить
|
||||
её химическим интерактивом и наглядностью + исправить метаданные.
|
||||
|
||||
---
|
||||
|
||||
## ⚗️ ХИМИЧЕСКИЙ СТАНДАРТ КАЧЕСТВА
|
||||
|
||||
### A. Движки и переиспользуемые активы
|
||||
|
||||
| Что нужно | Берём из | Файл |
|
||||
|-----------|----------|------|
|
||||
| Парсинг формул, молярная масса, формула Хилла | biochem-core | `frontend/js/biochem-core.js` |
|
||||
| 2D/3D шаростержневые модели, VSEPR-геометрия | biochem-core | `frontend/js/biochem-core.js` |
|
||||
| Интерактивная ПСХЭ | sim `periodic` | реестр `frontend/js/labs/` |
|
||||
| Электролиз | sim `electrolysis` | реестр |
|
||||
| Титрование | sim `titration` | реестр |
|
||||
| Качественный анализ | sim `qualanalysis` | реестр |
|
||||
| Растворы/концентрации | sim `solutions` | реестр |
|
||||
| Стехиометрия | sim `stoichiometry` | реестр |
|
||||
| Песочница реакций | sim `chemsandbox` | реестр |
|
||||
|
||||
Симуляторы монтируются как в других страницах: контейнер `<div id="sim-<id>"></div>` +
|
||||
вызов `openSim('<id>')` (или прямой mount через `window.LabRegistry`).
|
||||
|
||||
### B. Новый общий хелпер `/js/chem9_svg.js` (по образцу `geom7_svg.js`, `alg10_svg.js`)
|
||||
|
||||
Чисто химические примитивы, которых нет в biochem-core:
|
||||
|
||||
```js
|
||||
// 1. Рендер уравнения реакции: коэффициенты, состояния, стрелки, условия над стрелкой
|
||||
// chemEq('2Na + 2H2O -> 2NaOH + H2^', {cond:'', arrow:'->'}) → HTML с верхн./нижн. индексами, ↑↓, ⇌, →[t°]
|
||||
const chemEq = (src, opts={}) => { /* токенизация формул, KaTeX-подобный рендер */ };
|
||||
|
||||
// 2. Ион с зарядом: ionLabel('SO4', -2) → 'SO₄²⁻'
|
||||
const ionLabel = (formula, charge) => { /* нижние индексы + надстрочный заряд */ };
|
||||
|
||||
// 3. Пробирка с осадком / газом / окраской (SVG-анимация)
|
||||
const testTube = ({fill, precipitate, gas, color, label}) => { /* svg */ };
|
||||
|
||||
// 4. Орбитальная диаграмма (стрелки в клетках): orbitalDiagram('1s2 2s2 2p6 3s2 3p2')
|
||||
const orbitalDiagram = (config) => { /* svg клетки + ↑↓ */ };
|
||||
|
||||
// 5. Интерактивная таблица растворимости (виджет с подсветкой пары катион×анион)
|
||||
const solubilityTable = (mount, {highlight}) => { /* P/Н/М/— из форзаца */ };
|
||||
|
||||
// 6. Интерактивный ряд активности металлов (drag/клик → предсказание реакции)
|
||||
const activitySeries = (mount, opts) => { /* K Ca Na Mg Al Zn Fe ... Au */ };
|
||||
|
||||
// 7. Мини-ПСХЭ с подсветкой (элемент/группа/период/семейство)
|
||||
const miniPeriodic = (mount, {highlight, onClick}) => { /* 118 элементов */ };
|
||||
|
||||
// 8. Индикатор + шкала pH (лакмус/фенолфталеин/метилоранж)
|
||||
const indicatorScale = (mount, {ph, indicator}) => { /* цвет полоски */ };
|
||||
|
||||
// 9. Анимация диссоциации/растворения (частицы ионов разлетаются)
|
||||
const dissociationAnim = (mount, {salt}) => { /* canvas/SVG-частицы */ };
|
||||
```
|
||||
|
||||
Молекулы (структурные/шаростержневые/3D) — **через `biochem-core.js`**, не дублировать.
|
||||
|
||||
### C. Правила рендера химии (обязательны с §1)
|
||||
|
||||
1. **Формулы веществ** — нижние индексы для атомов ($H_2O$, $CaCO_3$), верхние для зарядов ионов ($SO_4^{2-}$); единый рендер через `chemEq`/`ionLabel`, не «сырой» текст.
|
||||
2. **Уравнения реакций** — всегда сбалансированы; стрелки: `=`/`→` (необратимая), `⇌` (обратимая), `↑` (газ), `↓` (осадок), условия над стрелкой ($t$, $\text{кат.}$, $\text{эл.ток}$).
|
||||
3. **Состояние** — для качественных реакций обязательно показывать признак: цвет осадка, пузырьки газа, изменение окраски индикатора (через `testTube`/`indicatorScale`).
|
||||
4. **Ионные уравнения** — для §9–10 и всех качественных реакций давать тройку: молекулярное → полное ионное → сокращённое ионное (виджет-конвертер).
|
||||
5. **Молекулярные модели** — структурная формула + 3D (biochem-core); для каждого изучаемого простого вещества/важного соединения хотя бы 1 модель.
|
||||
6. **Цвета — химически достоверные**: осадки ($AgCl$ белый, $Cu(OH)_2$ голубой, $Fe(OH)_3$ бурый, $Fe(OH)_2$ зеленоватый), пламя ($Ca$ кирпично-красный, $Sr$ карминовый, $Ba$ жёлто-зелёный, $Na$ жёлтый, $K$ фиолетовый).
|
||||
7. **Безопасность** — где уместно (разбавление $H_2SO_4$, ядовитость $CO$, $H_2S$) — заметка-«скрепка» с предупреждением.
|
||||
8. **KaTeX-эскейпы** — в JS-шаблонах двойной backslash (`\\to`, `\\downarrow`, `\\rightleftharpoons`).
|
||||
9. **Drag/слайдеры** — `window`-listeners + `{passive:false}` + state ВЫШЕ `redraw()` (как в стандарте геометрии), `touch-action:none` на draggable SVG/canvas.
|
||||
10. **Без эмоджи** — только inline SVG `.ic`/`.ico` (правило проекта).
|
||||
|
||||
### D. Типы интерактивов по химическим темам
|
||||
|
||||
| Тип темы | Интерактив |
|
||||
|----------|------------|
|
||||
| Строение атома / ПСХЭ (§2, 12, 40) | `miniPeriodic`, `orbitalDiagram`, sim `periodic` |
|
||||
| Химическая связь (§3) | slider ЭО → тип связи, диполь, e-пары |
|
||||
| Растворы (§4–6) | калькулятор $w$/$c$/разбавления, график растворимости, sim `solutions` |
|
||||
| Диссоциация / РИО (§7–10) | `dissociationAnim`, конвертер ионных уравнений, `solubilityTable` |
|
||||
| Аллотропия (§13, 20, 31, 34, 37) | 3D-модели biochem-core |
|
||||
| Качественные реакции (§17, 25, 28, 36, 48, 52) | `testTube` + ионное уравнение + признак |
|
||||
| Кислоты/основания (§16, 23, 29, 32) | `indicatorScale`, ступенчатая диссоциация |
|
||||
| Ряд активности / свойства металлов (§42, 43, 49) | `activitySeries`, предсказатель реакции |
|
||||
| Электролиз (§45) | sim `electrolysis` |
|
||||
| Окисл.-восст. / горение (§14, 18) | анимация горения, баланс ОВР |
|
||||
| Производство (§24, 39, 45, 53) | пошаговые схемы (контактный способ, варка стекла, доменная печь) |
|
||||
| Удобрения / прикладное (§33, 44, 47, 11) | калькуляторы, инфографика |
|
||||
|
||||
---
|
||||
|
||||
## 📦 СТРУКТУРА КАЖДОГО § (стандарт наполнения)
|
||||
|
||||
Каждый § после доработки содержит:
|
||||
|
||||
**Теория (3–4 карточки):**
|
||||
- `theory` — основное определение/понятие + наглядная SVG/модель
|
||||
- `rule` — ключевая закономерность/формула (рамка)
|
||||
- `example` — разобранный пример (реакция/расчёт) с пошаговым рендером
|
||||
- (для прикладных §) `apply` — применение/значение (инфографика)
|
||||
|
||||
**Интерактивы (4–6 на §):**
|
||||
1. **Звёздный виджет** темы (из карты содержания выше)
|
||||
2. **Конструктор/симулятор** (slider / drag / sim из реестра)
|
||||
3. **Калькулятор** (молярная масса, $w$, выход, по уравнению) — где применимо
|
||||
4. **DnD-сортер / классификатор** (классы веществ, тип связи, признак реакции)
|
||||
5. **Тренажёр** — 5 задач с inline-наглядностью (формула/уравнение/модель в условии)
|
||||
6. **Босс §** — 4 интеграционные задачи (+5 XP каждая)
|
||||
|
||||
**Дополнительно:**
|
||||
- Пополнение **глоссария** (термины §)
|
||||
- **Вопросы и задания** из учебника (адаптированные, с проверкой)
|
||||
- jsdom-тест страницы проходит
|
||||
|
||||
**Финал главы:**
|
||||
- **Итоговая шпаргалка** — mini-cards (формула/реакция/иконка) по каждому §
|
||||
- **Карта связей** — SVG-граф понятий главы (генетические связи веществ)
|
||||
- **7 интегрированных боссов** (каждый ≥2 темы, +10 XP)
|
||||
- **Achievement** «Мастер главы N» (+50 XP, confetti)
|
||||
- Кнопка перехода к следующей главе
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ПОРЯДОК РЕАЛИЗАЦИИ (по фазам)
|
||||
|
||||
### Phase 0 — Фундамент
|
||||
- Исправить метаданные `chemistry_9.html`: `<title>Химия 9 — §1–53`, убрать артефакт «МЕТАЛЛЫ» в water-mark/CSS, починить кодировку заголовков (UTF-8).
|
||||
- Перегруппировать карточки para-selector на **4 главы** книги (цвета: I amber, II blue, III teal/cyan, IV deep-orange).
|
||||
- Создать `/js/chem9_svg.js` с хелперами A–B (заглушки → реализация по мере фаз).
|
||||
- Подключить `biochem-core.js` и нужные симуляторы на страницу.
|
||||
- **Миграция БД** (следующий номер после `040_content_access.sql` → `041_chemistry9_fix.sql`): обновить запись `chemistry-9` — `para_count = 53`, корректные `title`/`author`/`description`, `color='amber'`. (Слаг и html_path уже есть из migration 004.)
|
||||
- jsdom-тест-каркас для страницы.
|
||||
|
||||
### Phase 1 — Глава 1 «Повторение» (§§1–3) — разогрев
|
||||
Простые темы, но закладываем 3 базовых движка: `miniPeriodic`, `orbitalDiagram`, визуализатор связи. Эти виджеты переиспользуются во всех 4 главах.
|
||||
|
||||
### Phase 2 — Глава 2 «Растворы. ТЭД» (§§4–11) + ПР1, ПР2
|
||||
Самая «расчётно-логическая». Закладываем `solubilityTable`, конвертер ионных уравнений, `dissociationAnim`, калькулятор раствора, sim `solutions`. Эти движки критичны для всей химии 9.
|
||||
|
||||
### Phase 3 — Глава 3 «Неметаллы» (§§12–25): галогены, кислород, сера + лаб.1, лаб.2
|
||||
SVG/модели-тяжёлая. 3D-аллотропы (biochem-core), первые качественные реакции (`testTube`: $AgCl↓$, $BaSO_4↓$), горение, безопасность $H_2SO_4$.
|
||||
|
||||
### Phase 4 — Глава 3 «Неметаллы» (§§26–39): азот, фосфор, углерод, кремний + лаб.3, лаб.4, ПР3
|
||||
Фонтан аммиака, NPK-калькулятор, качеств. реакция на $CO_3^{2-}$, сталактиты, варка стекла/цемент.
|
||||
|
||||
### Phase 5 — Глава 4 «Металлы» (§§40–48): общие свойства, щелочные, ЩЗМ + лаб.5
|
||||
`activitySeries`, sim `electrolysis`, цвета пламени, симулятор жёсткости воды, качеств. на $Ca^{2+}/Ba^{2+}$.
|
||||
|
||||
### Phase 6 — Глава 4 «Металлы» (§§49–53): алюминий, железо + лаб.6, ПР4
|
||||
Амфотерность $Al(OH)_3$, качеств. на $Fe^{2+}/Fe^{3+}$, симулятор коррозии, анимация доменной печи.
|
||||
|
||||
### Phase 7 — Финалы глав + общий финал учебника
|
||||
- Шпаргалки и карты связей по каждой главе
|
||||
- Интегрированные боссы + achievements
|
||||
- **Большой финал**: генетическая карта «неметаллы ↔ металлы ↔ соли», итоговый босс-квест, ачивка «Химик 9 класса»
|
||||
- Глоссарий собран целиком, связан `[[ссылками]]`
|
||||
|
||||
### Phase 8 — Качество и админка
|
||||
- Полный прогон jsdom-тестов (каждый § — builder не stub)
|
||||
- Аудит KaTeX/`chemEq`-эскейпов, баланса всех уравнений
|
||||
- Синхронизация с админкой (если симуляторы добавлялись в `lab.html` → обновить `ADMIN_SIMS` в `admin.html` — правило проекта)
|
||||
- Проверка доступа по классам/ученикам (`contentAccess`, `/api/access`)
|
||||
|
||||
> Рекомендуемый темп: внутри фазы — по 2–3 § за «волну», каждая волна = commit + проходящий jsdom-тест.
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ ИНТЕГРАЦИЯ С ПРОЕКТОМ
|
||||
|
||||
| Точка | Действие |
|
||||
|-------|----------|
|
||||
| **БД каталог** | `textbooks` уже содержит `chemistry-9` (migration 004). Миграция `041` — поправить `para_count=53`, title/description. |
|
||||
| **Прогресс/XP** | Автоматически: `textbook-xp-widget.js` (+5 XP/§), `textbook-tracker.js`, `LS.xp`. Доп. XP за боссов — по образцу `phys7_ch1_widgets.js`. |
|
||||
| **Симуляторы** | Реестр `frontend/js/labs/_register-all.js` + `lab-glue.js`. Химические sim уже зарегистрированы (`periodic`, `electrolysis`, `titration`, `qualanalysis`, `solutions`, `stoichiometry`, `chemsandbox`). |
|
||||
| **Молекулы** | `biochem-core.js` (парсинг, молярная масса, 2D/3D, VSEPR). |
|
||||
| **Бэкенд** | Роуты готовы: `backend/src/routes/textbooks.js` (catalog/progress/bookmarks). Доступ: `backend/src/services/contentAccess.js`. |
|
||||
| **Глоссарий** | Реализовать виджет на странице (общего нет) — всплывающие определения по терминам. |
|
||||
| **Тесты** | `cd backend && npm test` (jsdom). На каждый § — тест: страница строится, builder не stub, уравнения сбалансированы. |
|
||||
| **Админка** | Если добавляются новые sim в `lab.html` → синхронно обновить `ADMIN_SIMS` в `admin.html`. |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ КРИТИЧЕСКИЕ ПРАВИЛА
|
||||
|
||||
### ❌ НЕ делать
|
||||
- Не оставлять «сырые» формулы текстом — только через `chemEq`/`ionLabel`/KaTeX.
|
||||
- Не публиковать несбалансированные уравнения (аудит баланса перед commit).
|
||||
- Не дублировать молекулярный движок — использовать `biochem-core.js`.
|
||||
- Не использовать `setPointerCapture` (теряется после `innerHTML`-replace) → `window`-listeners + state-flag.
|
||||
- `\to`, `\downarrow`, `\rightleftharpoons` без удвоения backslash в JS-шаблонах.
|
||||
- Slider-диапазоны за пределы химически возможного (концентрации, температуры).
|
||||
- Эмоджи — запрещены; только inline SVG `.ic`.
|
||||
- Grep tool — запрещён; поиск только `ast-index`.
|
||||
|
||||
### ✅ Обязательно
|
||||
- Каждый commit → jsdom-тест 100% pass.
|
||||
- Аудит баланса уравнений + KaTeX-эскейпов после каждой волны.
|
||||
- Качественная реакция = уравнение (молек.+ионное) **+ видимый признак** (`testTube`/индикатор).
|
||||
- Цвета осадков/пламени — химически достоверные.
|
||||
- Все builder-функции в конце финальной волны главы — НЕ stub'ы.
|
||||
- Коммитить только изменённые файлы (не `git add -A`), сразу push (правило CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 📊 Оценка объёма
|
||||
|
||||
| Глава | § | Лаб/ПР | Ожидаемый LOC |
|
||||
|-------|---|--------|---------------|
|
||||
| Гл.1 Повторение | 3 | — | ~3 500 (+движки ПСХЭ/орбитали/связь) |
|
||||
| Гл.2 Растворы. ТЭД | 8 | ПР1, ПР2 | ~9 000 (+`solubilityTable`, ионные ур-я, `solutions`) |
|
||||
| Гл.3 Неметаллы | 28 | 4 лаб, ПР3 | ~28 000 (модели, качеств. реакции, безопасность) |
|
||||
| Гл.4 Металлы | 14 | 2 лаб, ПР4 | ~16 000 (ряд активности, электролиз, коррозия, доменная печь) |
|
||||
| Финалы глав + общий | — | — | ~5 000 |
|
||||
| `/js/chem9_svg.js` хелперы | — | — | ~2 500 |
|
||||
| **Итого** | **53** | **6 лаб + 4 ПР** | **~64 000 LOC** |
|
||||
|
||||
Существующий каркас (~10 700 строк) переиспользуется; основной прирост — интерактив,
|
||||
наглядность и движки.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Запуск
|
||||
|
||||
**Phase 0**: чистка метаданных `chemistry_9.html` + перегруппировка на 4 главы +
|
||||
`/js/chem9_svg.js` (скелет) + подключение `biochem-core.js`/симуляторов + миграция `041`.
|
||||
**Phase 1**: Глава 1 (§§1–3) — закладываем `miniPeriodic`, `orbitalDiagram`, визуализатор связи.
|
||||
|
||||
Дальше — последовательно по главам (Phase 2 → 6), затем финалы (Phase 7) и качество (Phase 8).
|
||||
Reference in New Issue
Block a user