77e4dffb43
Закладывает уникальный визуальный язык и engine'ы для редизайна Физики 8. CSS: - phys8-design-system.css (12 КБ): 3 темы (thermal/electric/spectrum), тематические hero-палитры, watermarks, animations (thermal-shift, electric-pulse, spectrum-drift, wm-breathe/flicker/rotate, noise overlay), staggered fade-in для виджетов, soft elevation на карточках, monospace для физ. величин, topic-aware progress bars, mobile responsive (≤768px), prefers-reduced-motion. - phys8-interactives.css (10 КБ): .p8-draggable + .p8-droptarget с hover-effects, .p8-palette (для circuit-builder), .p8-scrubber, .p8-readout табло, .p8-tooltip, .p8-sandbox canvas wrapper, .p8-thermometer + .p8-compass-needle SVG-композиции, glow-utility. JS: - phys8-anim.js (6 КБ): easing-функции (quad/cubic/expo/back/elastic/ bounce/spring), tween-engine с onUpdate/onComplete, raf-wrapper, oscillate, stagger, onVisible (IntersectionObserver). Экспорт P8Anim. - phys8-drag.js (12 КБ): универсальный drag-engine. P8Drag.attach() для DOM/SVG, P8Drag.attachCanvas() для логических объектов с hit-test, P8Drag.attachPalette() для drag-from-palette-to-drop, constraints (lockX/Y, bounds, snap-to-grid), touch + mouse + pointer. - phys8-helpers.js (18 КБ): тематические хелперы. P8Helpers.thermal (tempColor 0-1, heatFlowArrow, molecule, thermometerSVG, convectionCellParticles), .em (chargeSVG, circuitComponent для battery/resistor/lamp/ammeter/voltmeter/switch, fieldLineFrom), .optics (rayLine, lensSVG converging/diverging, mirrorPlane), .svg utils (el, create, linearGradient, radialGradient, gradientArrow, labeledText). Линковка (redesign_p8_phase0.cjs): - 2 CSS-link после katex CDN - 3 JS-link после phys.js/xp.js - body class p8-theme-thermal/electric/spectrum на ch1/ch2/ch3 - hub и lab — без темы (нейтральный пурпурный brand)
110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
// Phase 0 — подключает design system + interactives CSS + новые JS-модули
|
|
// (phys8-anim.js, phys8-drag.js, phys8-helpers.js) во все 5 файлов учебника Физики 8:
|
|
// physics_8_hub.html, physics_8_ch1.html, physics_8_ch2.html, physics_8_ch3.html, physics_8_lab.html.
|
|
// Также добавляет body class (p8-theme-thermal/electric/spectrum) на каждой странице.
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const TBOOKS = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
|
|
|
|
const FILES = [
|
|
{ name: 'physics_8_hub.html', theme: null },
|
|
{ name: 'physics_8_ch1.html', theme: 'thermal' },
|
|
{ name: 'physics_8_ch2.html', theme: 'electric' },
|
|
{ name: 'physics_8_ch3.html', theme: 'spectrum' },
|
|
{ name: 'physics_8_lab.html', theme: null },
|
|
];
|
|
|
|
const CSS_LINKS = [
|
|
'<link rel="stylesheet" href="/css/phys8-design-system.css">',
|
|
'<link rel="stylesheet" href="/css/phys8-interactives.css">',
|
|
];
|
|
|
|
const JS_LINKS = [
|
|
'<script src="/js/phys8-anim.js" defer></script>',
|
|
'<script src="/js/phys8-drag.js" defer></script>',
|
|
'<script src="/js/phys8-helpers.js" defer></script>',
|
|
];
|
|
|
|
// Anchor: после katex link мы добавляем design-system css
|
|
const CSS_ANCHOR = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">';
|
|
|
|
// Anchor: после phys.js / xp.js мы добавляем новые JS
|
|
const JS_ANCHOR_OPTIONS = [
|
|
'<script src="/js/phys.js" defer></script>',
|
|
'<script src="/js/xp.js" defer></script>',
|
|
];
|
|
|
|
let totalPatched = 0;
|
|
|
|
for (const { name, theme } of FILES) {
|
|
const fp = path.join(TBOOKS, name);
|
|
if (!fs.existsSync(fp)) { console.warn('miss:', name); continue; }
|
|
let h = fs.readFileSync(fp, 'utf8');
|
|
const before = h.length;
|
|
let changes = [];
|
|
|
|
// 1. CSS links
|
|
for (const link of CSS_LINKS) {
|
|
if (h.includes(link)) continue;
|
|
if (h.includes(CSS_ANCHOR)) {
|
|
h = h.replace(CSS_ANCHOR, CSS_ANCHOR + '\n' + link);
|
|
changes.push('+css: ' + link.match(/href="([^"]+)"/)[1]);
|
|
}
|
|
}
|
|
|
|
// 2. JS links — место подключения: после любого из якорей
|
|
for (const link of JS_LINKS) {
|
|
if (h.includes(link)) continue;
|
|
let placed = false;
|
|
for (const anchor of JS_ANCHOR_OPTIONS) {
|
|
if (h.includes(anchor)) {
|
|
h = h.replace(anchor, anchor + '\n' + link);
|
|
placed = true;
|
|
changes.push('+js: ' + link.match(/src="([^"]+)"/)[1]);
|
|
break;
|
|
}
|
|
}
|
|
if (!placed) {
|
|
// Fallback: перед </head>
|
|
h = h.replace('</head>', link + '\n</head>');
|
|
changes.push('+js (head): ' + link.match(/src="([^"]+)"/)[1]);
|
|
}
|
|
}
|
|
|
|
// 3. Theme class на body
|
|
if (theme) {
|
|
const themeClass = 'p8-theme-' + theme;
|
|
if (!h.includes(themeClass)) {
|
|
// Найти <body ...> и добавить класс
|
|
h = h.replace(/<body([^>]*)>/, (match, attrs) => {
|
|
if (/class="([^"]*)"/.test(attrs)) {
|
|
return '<body' + attrs.replace(/class="([^"]*)"/, (m, cls) =>
|
|
`class="${cls} ${themeClass}"`) + '>';
|
|
}
|
|
return `<body${attrs} class="${themeClass}">`;
|
|
});
|
|
changes.push('+body class: ' + themeClass);
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(fp, h);
|
|
if (changes.length) {
|
|
console.log(`${name}: ${before} → ${h.length} bytes`);
|
|
changes.forEach(c => console.log(' ' + c));
|
|
totalPatched++;
|
|
} else {
|
|
console.log(`${name}: no changes (already patched)`);
|
|
}
|
|
|
|
// Sanity parse inline scripts
|
|
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 in ${name}:`, e.message.slice(0, 100)); process.exit(1); }
|
|
}
|
|
}
|
|
|
|
console.log('Total patched:', totalPatched, '/', FILES.length);
|