Files
Learn_System/backend/scripts/gen_phys9_hub.js
Maxim Dolgolyov 0c0eea7a6b feat(textbooks): скелет Физики 9 — hub + 5 глав + миграция БД
- gen_phys9_hub.js: генератор hub из physics_10_hub.html (blue palette, 5 cards)
- gen_phys9_ch.js: генератор 5 файлов глав со STUB-builder'ами по канве physics_10_ch
- 038_physics_9_hub.sql: переразмечает physics-9 как hub + 5 дочерних (ch1-ch5)
- Глава 5 — Лабораторный практикум, 12 ЛР с поддержкой lr-id вместо §

Источник: Исаченкова, Сокольский, Захаревич "Физика 9" (Народная асвета, 2019).
Контент в Phase 5 — авторский (наш материал).
2026-05-29 23:01:59 +03:00

241 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Генератор physics_9_hub.html на основе physics_10_hub.html
// Палитра: blue (вместо amber у Phys 10), 5 глав, заголовки/описания Физики 9.
'use strict';
const fs = require('fs');
const path = require('path');
const SRC = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_10_hub.html');
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_9_hub.html');
let h = fs.readFileSync(SRC, 'utf8');
// === 1. Primary palette: amber (#ca8a04 / #fde047) → blue (#2563eb / #60a5fa) ===
h = h.replace(
/:root\{[\s\S]*?--sh-h:0 12px 36px rgba\(202,138,4,\.18\);[\s\S]*?\}/,
`:root{
--bg:#eff6ff; --card:#fff;
--text:#0f172a; --muted:#475569;
--border:#bfdbfe;
--pri:#2563eb; --pri-d:#1d4ed8;
--pri-soft:#dbeafe;
--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;
--sh:0 4px 16px rgba(37,99,235,.10);
--sh-h:0 12px 36px rgba(37,99,235,.18);
}`);
h = h.replace(
/html\.dark\{[\s\S]*?--pri-soft:rgba\(202,138,4,\.16\);[\s\S]*?\}/,
`html.dark{
--bg:#0a1428; --card:#102137;
--text:#dbeafe; --muted:#93c5fd;
--border:#1e3a5f;
--pri-soft:rgba(37,99,235,.16);
}`);
// === 2. Header gradient: amber → blue ===
h = h.replace(
/\.hdr\{position:relative;background:linear-gradient\(110deg,#713f12 0%,#ca8a04 55%,#fde047 100%\)[^}]*\}/,
`.hdr{position:relative;background:linear-gradient(110deg,#1e3a8a 0%,#2563eb 55%,#60a5fa 100%);color:#fff;padding:32px 24px 28px;overflow:hidden;border-bottom:2px solid rgba(219,234,254,.18)}`);
h = h.replace(/rgba\(254,243,199,\.12\)/g, 'rgba(219,234,254,.12)');
h = h.replace(/rgba\(254,243,199,\.18\)/g, 'rgba(219,234,254,.18)');
// === 3. po-icon gradient + po-bar/po-fill ===
h = h.replace(
/\.po-icon\{[^}]*background:linear-gradient\(135deg,#ca8a04,#fde047\)[^}]*\}/,
`.po-icon{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#2563eb,#60a5fa);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\(202,138,4,\.14\)/, '.po-bar{height:8px;background:rgba(37,99,235,.14)');
h = h.replace(/\.po-fill\{height:100%;background:linear-gradient\(90deg,var\(--pri\),#fde047\)/, '.po-fill{height:100%;background:linear-gradient(90deg,var(--pri),#60a5fa)');
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,#3b82f6,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(37,99,235,.24)}");
// === 4. Final-head gradient ===
h = h.replace(
/\.final-head\{padding:18px 22px;background:linear-gradient\(135deg,#713f12 0%,#ca8a04 55%,#f59e0b 100%\)/,
'.final-head{padding:18px 22px;background:linear-gradient(135deg,#1e3a8a 0%,#2563eb 55%,#3b82f6 100%)');
// === 5. Title + H1 + subtitle ===
h = h.replace(/<title>Физика 10 класс — учебник<\/title>/, '<title>Физика 9 класс — учебник</title>');
h = h.replace(/<h1>Физика — 10 класс<\/h1>/, '<h1>Физика — 9 класс</h1>');
h = h.replace(
/<div class="hdr-sub">Полный курс физики 10 класса:[^<]+<\/div>/,
'<div class="hdr-sub">Полный курс механики: кинематика, динамика, статика, законы сохранения, 12 лабораторных работ</div>'
);
// === 6. localStorage keys + API endpoint ===
h = h.replace(/physics10_theme/g, 'physics9_theme');
h = h.replace(/physics10_xp/g, 'physics9_xp');
h = h.replace(/physics10_course_master/g, 'physics9_course_master');
h = h.replace(/physics10_course_bosses/g, 'physics9_course_bosses');
h = h.replace(/physics10-master/g, 'physics9-master');
h = h.replace(/'\/api\/textbooks\/physics-10\/children'/, "'/api/textbooks/physics-9/children'");
// === 7. Заменяем блок с 6 главами целиком на блок с 5 главами ===
const chBlock = `
<a href="/textbook/physics-9-ch1" class="ch-card ch1-card" id="ch-1">
<div class="ch-cover ch1">
<div class="ch-cover-wm">v</div>
<div class="ch-num">Глава 1</div>
<div class="ch-title">Основы кинематики</div>
<div class="ch-range">&sect;1&ndash;&sect;14</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-9-ch2" class="ch-card ch2-card" id="ch-2">
<div class="ch-cover ch2">
<div class="ch-cover-wm">F</div>
<div class="ch-num">Глава 2</div>
<div class="ch-title">Основы динамики</div>
<div class="ch-range">&sect;15&ndash;&sect;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-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-9-ch3" class="ch-card ch3-card" id="ch-3">
<div class="ch-cover ch3">
<div class="ch-cover-wm">M</div>
<div class="ch-num">Глава 3</div>
<div class="ch-title">Основы статики</div>
<div class="ch-range">&sect;25&ndash;&sect;30</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-9-ch4" class="ch-card ch4-card" id="ch-4">
<div class="ch-cover ch4">
<div class="ch-cover-wm">p&middot;E</div>
<div class="ch-num">Глава 4</div>
<div class="ch-title">Законы сохранения</div>
<div class="ch-range">&sect;31&ndash;&sect;36</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-9-ch5" class="ch-card ch5-card" id="ch-5">
<div class="ch-cover ch5">
<div class="ch-cover-wm">&Delta;t</div>
<div class="ch-num">Глава 5</div>
<div class="ch-title">Лабораторный практикум</div>
<div class="ch-range">ЛР 1&ndash;12</div>
</div>
<div class="ch-body">
<div class="ch-desc">12 лабораторных работ: погрешности, ускорение, окружность, закон Гука, трение, брошенное тело, рычаг, блоки, наклонная плоскость, Архимед, импульс, энергия.</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>
`;
// Replace the entire <a href="/textbook/physics-10-ch1"...</a> ... <a ch6></a> block (6 cards → 5 cards)
h = h.replace(/\s*<a href="\/textbook\/physics-10-ch1"[\s\S]*?<a href="\/textbook\/physics-10-ch6"[\s\S]*?<\/a>\s*/,
chBlock);
// === 8. final cta + master text ===
h = h.replace(/<div class="final-cta-title">Курс Физика 10 пройден!<\/div>/, '<div class="final-cta-title">Курс Физика 9 пройден!</div>');
h = h.replace(/«Магистр физики 10»/g, '«Магистр физики 9»');
h = h.replace(/Магистр физики 10/g, 'Магистр физики 9');
// final-head-sub
h = h.replace(
/<div class="final-head-sub">Шпаргалка курса и интегрированные боссы по всем 6 главам\. В разработке \(Phase 7\)\.<\/div>/,
'<div class="final-head-sub">Шпаргалка курса и интегрированные боссы по всем 5 главам. В разработке (Phase 7).</div>'
);
// fin-placeholder: 37 → 36, 6 → 5
h = h.replace(
/Итоговая шпаргалка по всем 37 параграфам и 8&ndash;10 интегрированных боссов появятся в Phase 7 \(после завершения всех 6 глав\)\./,
'Итоговая шпаргалка по всем 36 параграфам и 8&ndash;10 интегрированных боссов появятся в Phase 7 (после завершения всех 5 глав).'
);
// Footer
h = h.replace(/Интерактивный учебник «Физика — 10 класс»/, 'Интерактивный учебник «Физика — 9 класс»');
// Achievement strip
h = h.replace(/Прочитайте все 37 параграфов курса, чтобы получить достижение/, 'Прочитайте все 36 параграфов курса, чтобы получить достижение');
h = h.replace(/Вы прочитали весь курс физики 10 класса\./, 'Вы прочитали весь курс физики 9 класса.');
// === 9. TOTAL + CH_PARA + CH_IDX ===
h = h.replace(/var TOTAL = 37;[\s\S]*?var CH_IDX = \{[\s\S]*?\};/, `var TOTAL = 36;
var CH_PARA = {
'physics-9-ch1': 14,
'physics-9-ch2': 10,
'physics-9-ch3': 6,
'physics-9-ch4': 6,
'physics-9-ch5': 12,
};
var CH_IDX = {
'physics-9-ch1': 1,
'physics-9-ch2': 2,
'physics-9-ch3': 3,
'physics-9-ch4': 4,
'physics-9-ch5': 5,
};`);
// === 10. Chapter grid: 6 cards → 5 cards (2-1-2 на широких экранах смотрится лучше при 5) ===
// Оставим CSS как есть — repeat(3,1fr) на >=1000px и repeat(2,1fr) на >=680px.
// 5 карточек выстроятся как 3+2 (или 2+2+1). Это нормально.
fs.writeFileSync(DST, h);
console.log('OK hub →', DST, 'bytes:', h.length);
// Sanity: parse inline scripts
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');