Files
Learn_System/backend/scripts/redesign_p8_phase0.cjs
Maxim Dolgolyov 77e4dffb43 feat(phys8): Phase 0 redesign foundation — CSS + JS infrastructure
Закладывает уникальный визуальный язык и 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)
2026-05-30 09:55:00 +03:00

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);