feat(phys8 ch2): Phase 2.1 — визуальный hero + 20 IV-6 stubs
Hero: новый p8-hero с electric-pulse градиентом (5s), молнией SVG-watermark (flicker анимация 3.2s), live meter тока в углу (0.5 → 2.0 → 1.2 → 0.8 → 1.5 А, плавная tween). 20 section watermarks: тематические SVG символы по теме § — 2 заряда, проводник, индукция, атом, силовые линии, U-стрелка, батарея, цепь, омега Ω, зигзаг ρl/S, посл/парал соединения, P-мощность, магнит N/S, компас, электромагнит. 20 IV-6 stubs: 'Новый интерактив §N · coming soon' (заглушки для Phase 2. bulk content). Все 20 builders на месте, JS парсится.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
// Phase 2.1 — визуальный редизайн ch2 (Электромагнитные явления):
|
||||
// 1. Hero: новый p8-hero с electric theme, lightning SVG watermark.
|
||||
// 2. Section watermarks: тематические SVG в каждой <section sec-pN>.
|
||||
// 3. IV-6 stubs для §12-31 (20 параграфов).
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch2.html');
|
||||
let h = fs.readFileSync(DST, 'utf8');
|
||||
|
||||
// === 1. Replace .hdr block with p8-hero ===
|
||||
const LIGHTNING_WM = `<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||
<path d="M55 8 L25 56 L46 56 L40 92 L75 38 L52 38 L60 8 Z"/>
|
||||
</svg>`;
|
||||
|
||||
const NEW_HERO = `<header class="p8-hero">
|
||||
<div class="p8-hero-wm">${LIGHTNING_WM}</div>
|
||||
<div class="p8-hero-meter" id="p8-meter-ch2"><span id="p8-meter-val">0.5</span> А</div>
|
||||
<div class="p8-hero-inner">
|
||||
<div class="p8-hero-eyebrow">Глава 2 · 20 параграфов</div>
|
||||
<h1 class="p8-hero-title">Электромагнитные явления</h1>
|
||||
<div class="p8-hero-sub">Заряд, ток, цепь, магнитное поле. Конструируйте цепи из компонентов, перемещайте заряды, наблюдайте за искрами и полями.</div>
|
||||
<div class="hdr-side" style="margin-top:18px;display:flex;gap:8px;flex-wrap:wrap;position:relative;z-index:1">
|
||||
<a href="/textbook/physics-8" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 8</a>
|
||||
<button id="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
|
||||
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
|
||||
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</header>`;
|
||||
|
||||
const oldHdrRegex = /<header class="hdr">[\s\S]*?<\/header>/;
|
||||
if (h.match(oldHdrRegex)) {
|
||||
h = h.replace(oldHdrRegex, NEW_HERO);
|
||||
console.log('Hero replaced');
|
||||
}
|
||||
|
||||
// === 2. Live meter скрипт (ток 0.5 → 2 → 1.2 → 0.8 → 1.5 А) ===
|
||||
const METER_SCRIPT = `
|
||||
<script>
|
||||
/* P8 hero meter — анимированный ток (Phase 2 electric) */
|
||||
(function(){
|
||||
function init(){
|
||||
const el = document.getElementById('p8-meter-val');
|
||||
if (!el || !window.P8Anim) return;
|
||||
const targets = [0.5, 2.0, 1.2, 0.8, 1.5];
|
||||
let i = 0;
|
||||
function step(){
|
||||
const from = parseFloat(el.textContent) || 0;
|
||||
const to = targets[i % targets.length];
|
||||
P8Anim.tween({
|
||||
from, to, duration: 1200, easing: 'cubicInOut',
|
||||
onUpdate: v => { el.textContent = v.toFixed(1); },
|
||||
onComplete: () => { i++; setTimeout(step, 1500); }
|
||||
});
|
||||
}
|
||||
setTimeout(step, 1200);
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
else init();
|
||||
})();
|
||||
</script>
|
||||
`;
|
||||
if (!h.includes('P8 hero meter')) {
|
||||
h = h.replace('</body>', METER_SCRIPT + '\n</body>');
|
||||
console.log('Meter animation added');
|
||||
}
|
||||
|
||||
// === 3. Section watermarks ===
|
||||
const SEC_SYMBOLS = {
|
||||
p12: '<svg viewBox="0 0 100 100"><circle cx="35" cy="50" r="14" fill="currentColor"/><circle cx="65" cy="50" r="14" fill="currentColor"/><path d="M40 50 L60 50 M50 40 L50 60" stroke="currentColor" stroke-width="3" fill="none"/></svg>', // 2 заряда
|
||||
p13: '<svg viewBox="0 0 100 100"><rect x="20" y="40" width="60" height="20" fill="none" stroke="currentColor" stroke-width="4"/><circle cx="35" cy="50" r="3" fill="currentColor"/><circle cx="50" cy="50" r="3" fill="currentColor"/><circle cx="65" cy="50" r="3" fill="currentColor"/></svg>', // проводник
|
||||
p14: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="24" fill="none" stroke="currentColor" stroke-width="4"/><path d="M50 26 L50 14 M50 86 L50 74 M26 50 L14 50 M86 50 L74 50" stroke="currentColor" stroke-width="4"/></svg>', // индукция
|
||||
p15: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="16" fill="currentColor"/><path d="M50 30 L50 18 M50 82 L50 70 M30 50 L18 50 M82 50 L70 50" stroke="currentColor" stroke-width="3"/></svg>', // заряд центр
|
||||
p16: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="6" fill="currentColor"/><ellipse cx="50" cy="50" rx="32" ry="14" fill="none" stroke="currentColor" stroke-width="2.5"/><ellipse cx="50" cy="50" rx="14" ry="32" fill="none" stroke="currentColor" stroke-width="2.5"/></svg>', // атом
|
||||
p17: '<svg viewBox="0 0 100 100"><line x1="20" y1="50" x2="80" y2="50" stroke="currentColor" stroke-width="3"/><line x1="20" y1="35" x2="80" y2="65" stroke="currentColor" stroke-width="3"/><line x1="20" y1="65" x2="80" y2="35" stroke="currentColor" stroke-width="3"/></svg>', // силовые линии
|
||||
p18: '<svg viewBox="0 0 100 100"><path d="M30 50 L70 50" stroke="currentColor" stroke-width="5" fill="none"/><path d="M65 45 L70 50 L65 55" stroke="currentColor" stroke-width="3" fill="none"/><text x="30" y="40" font-family="Inter" font-size="14" font-weight="700" fill="currentColor">U</text></svg>', // напряжение
|
||||
p19: '<svg viewBox="0 0 100 100"><rect x="20" y="40" width="14" height="20" fill="currentColor"/><rect x="38" y="35" width="6" height="30" fill="currentColor"/><line x1="50" y1="50" x2="80" y2="50" stroke="currentColor" stroke-width="3"/></svg>', // батарея
|
||||
p20: '<svg viewBox="0 0 100 100"><path d="M20 70 L40 30 L60 70 L80 30" stroke="currentColor" stroke-width="4" fill="none"/></svg>', // I=q/t
|
||||
p21: '<svg viewBox="0 0 100 100"><rect x="20" y="40" width="60" height="20" fill="none" stroke="currentColor" stroke-width="3"/><path d="M30 50 L50 50 M55 45 L60 50 L55 55" stroke="currentColor" stroke-width="2" fill="none"/></svg>', // цепь
|
||||
p22: '<svg viewBox="0 0 100 100"><text x="50" y="60" font-family="Unbounded" font-size="36" font-weight="900" fill="currentColor" text-anchor="middle">Ω</text></svg>', // Ом
|
||||
p23: '<svg viewBox="0 0 100 100"><path d="M20 50 Q30 20, 40 50 T60 50 T80 50" stroke="currentColor" stroke-width="4" fill="none"/></svg>', // ρl/S зигзаг
|
||||
p24: '<svg viewBox="0 0 100 100"><path d="M15 50 L25 50 L30 40 L40 60 L50 40 L60 60 L70 40 L75 50 L85 50" stroke="currentColor" stroke-width="3" fill="none"/></svg>', // последовательно
|
||||
p25: '<svg viewBox="0 0 100 100"><path d="M20 30 L80 30 M20 70 L80 70 M50 30 L50 70" stroke="currentColor" stroke-width="3" fill="none"/></svg>', // параллельно
|
||||
p26: '<svg viewBox="0 0 100 100"><text x="50" y="60" font-family="Unbounded" font-size="32" font-weight="900" fill="currentColor" text-anchor="middle">P</text></svg>', // мощность
|
||||
p27: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="28" fill="none" stroke="currentColor" stroke-width="4"/><path d="M28 50 L72 50 M50 28 L50 72" stroke="currentColor" stroke-width="3"/></svg>', // энергия
|
||||
p28: '<svg viewBox="0 0 100 100"><rect x="20" y="42" width="60" height="16" fill="currentColor"/><text x="32" y="56" font-family="Unbounded" font-size="14" font-weight="900" fill="#fff" text-anchor="middle">N</text><text x="68" y="56" font-family="Unbounded" font-size="14" font-weight="900" fill="#fff" text-anchor="middle">S</text></svg>', // магнит
|
||||
p29: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="6" fill="currentColor"/><ellipse cx="50" cy="50" rx="36" ry="18" fill="none" stroke="currentColor" stroke-width="3"/><path d="M82 38 L86 48 L78 46" stroke="currentColor" stroke-width="3" fill="none"/></svg>', // силовые линии магн
|
||||
p30: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="6" fill="currentColor"/><line x1="50" y1="20" x2="50" y2="80" stroke="currentColor" stroke-width="3"/><line x1="20" y1="50" x2="80" y2="50" stroke="currentColor" stroke-width="3"/></svg>', // компас
|
||||
p31: '<svg viewBox="0 0 100 100"><path d="M30 30 Q40 20, 50 30 T70 30 M30 50 Q40 40, 50 50 T70 50 M30 70 Q40 60, 50 70 T70 70" stroke="currentColor" stroke-width="3" fill="none"/><rect x="48" y="20" width="4" height="60" fill="currentColor"/></svg>' // электромагнит
|
||||
};
|
||||
|
||||
let secWmInjected = 0;
|
||||
for (const pid of Object.keys(SEC_SYMBOLS)) {
|
||||
const symbol = SEC_SYMBOLS[pid];
|
||||
const secOpenRegex = new RegExp(`(<section[^>]+id="sec-${pid}"[^>]*>)`);
|
||||
if (h.match(secOpenRegex) && !h.includes(`p8-sec-wm-${pid}`)) {
|
||||
const wmDiv = `<div class="p8-sec-wm" id="p8-sec-wm-${pid}" aria-hidden="true">${symbol}</div>`;
|
||||
h = h.replace(secOpenRegex, '$1\n ' + wmDiv);
|
||||
secWmInjected++;
|
||||
}
|
||||
}
|
||||
console.log('Section watermarks injected:', secWmInjected);
|
||||
|
||||
// === 4. IV-6 stubs для §12-31 ===
|
||||
let stubsAdded = 0;
|
||||
for (let n = 12; n <= 31; n++) {
|
||||
const pid = 'p' + n;
|
||||
const stubHtml = `
|
||||
/* IV6 — flagship интерактив (заглушка Phase 2, наполнение в Phase 2.${n}) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-electric">IV-6</span><div class="wg-title">Новый интерактив §${n}</div></div>'
|
||||
+'<div class="wg-help">Готовится: интерактивная визуализация с drag-and-drop для углубления темы. Скоро будет доступна.</div>'
|
||||
+'<div style="padding:30px;text-align:center;color:var(--p8-muted);font-style:italic">'
|
||||
+'<svg viewBox="0 0 24 24" style="width:32px;height:32px;stroke:currentColor;fill:none;stroke-width:1.5;opacity:.4"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
|
||||
+'<div style="margin-top:8px;font-size:.86rem">Phase 2.${n} — coming soon</div>'
|
||||
+'</div>'
|
||||
+'</div>';
|
||||
`;
|
||||
const marker = `box.innerHTML = h + secNavFor('${pid}') + readButton('${pid}');`;
|
||||
if (!h.includes(`Новый интерактив §${n}`) && h.includes(marker)) {
|
||||
h = h.replace(marker, stubHtml.trim() + '\n\n ' + marker);
|
||||
stubsAdded++;
|
||||
}
|
||||
}
|
||||
console.log('IV-6 stubs added:', stubsAdded);
|
||||
|
||||
fs.writeFileSync(DST, h);
|
||||
console.log('ch2 final size:', h.length);
|
||||
|
||||
const scripts = [...h.matchAll(/<script>([\s\S]*?)<\/script>/g)];
|
||||
for (const m of scripts) {
|
||||
try { new Function(m[1]); }
|
||||
catch (e) { console.error('JS PARSE FAIL:', e.message.slice(0, 150)); process.exit(1); }
|
||||
}
|
||||
console.log('inline JS parses OK');
|
||||
|
||||
const fns = [...h.matchAll(/function build_p(\d+)\(\)/g)].map(m => parseInt(m[1]));
|
||||
console.log('Builders after:', fns.length, fns);
|
||||
Reference in New Issue
Block a user