feat(phys8 ch1): Phase 1 visual hero + IV-6 §1 drag-thermometer

Визуальный редизайн ch1 Тепловые явления:
- Hero: заменён старый .hdr на новый .p8-hero с анимированным
  градиентом (thermal-shift 14s), огненным SVG-watermark
  справа (дышащая анимация 6s), live-meter в углу с пульсацией
  и плавной анимацией значения 37 → 100 → 0 → -10 → 25 → 80 °C.
- Eyebrow 'Глава 1 · 11 параграфов', крупный title, sub-описание.
- Section watermarks: в каждой <section sec-pN> добавлены
  тематические SVG (атом, конвекция, солнце, сосуд, фазовый
  переход, пузыри и т.д.) с opacity .07 на правой стороне.

IV-6 §1 flagship interactive — Drag thermometer:
- SVG-sandbox 560×320 с 4 телами (лёд, вода, чай, пар) разной
  T и относительной U.
- Draggable термометр (P8Drag.attach + P8Helpers.svg).
- При наведении на тело — изменяется цвет термометра по
  P8Helpers.thermal.tempColor(), readout табло показывают
  T (°C) и U (отн.).
- +5 XP за 12 сек исследования.

IV-6 stubs для §2-§11: 'Coming soon' плашки с тематическим
SVG-иконкой clock. Расширим в Phase 1.2.
This commit is contained in:
Maxim Dolgolyov
2026-05-30 09:58:11 +03:00
parent 5b075cde86
commit a6a9fb858c
2 changed files with 514 additions and 18 deletions
+257
View File
@@ -0,0 +1,257 @@
// Phase 1 — визуальный редизайн ch1 (Тепловые явления):
// 1. Hero: заменяет старый <header class="hdr"> на p8-hero с
// eyebrow, title, sub, live meter, watermark (огонь SVG).
// 2. Section watermarks: в каждом <section id="sec-pN"> добавляет
// тематический SVG-watermark (пламя/термометр/снежинка).
// 3. Inject IV-6 (drag-thermometer) в §1 — flagship интерактив.
// Остальные §2-11 получают IV-6 stub-placeholder с заголовком.
'use strict';
const fs = require('fs');
const path = require('path');
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch1.html');
let h = fs.readFileSync(DST, 'utf8');
// === 1. Replace .hdr block with p8-hero ===
const FIRE_WM = `<svg viewBox="0 0 100 100" aria-hidden="true">
<path d="M50 8 C 52 22 65 30 64 46 C 63 56 56 60 55 48 C 53 56 48 60 42 58 C 36 56 32 50 34 42 C 30 52 22 60 24 72 C 26 84 36 92 50 92 C 64 92 76 84 76 70 C 76 50 60 40 56 22 C 54 14 52 10 50 8 Z"/>
</svg>`;
const NEW_HERO = `<header class="p8-hero">
<div class="p8-hero-wm">${FIRE_WM}</div>
<div class="p8-hero-meter" id="p8-meter-ch1"><span id="p8-meter-val">37</span>°C</div>
<div class="p8-hero-inner">
<div class="p8-hero-eyebrow">Глава 1 · 11 параграфов</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. Update meter live: добавим скрипт, который анимирует значение в углу ===
const METER_SCRIPT = `
<script>
/* P8 hero meter — анимированный счётчик в углу (Phase 1 thermal) */
(function(){
function init(){
const el = document.getElementById('p8-meter-val');
if (!el || !window.P8Anim) return;
const targets = [37, 100, 0, -10, 25, 80];
let i = 0;
function step(){
const from = parseInt(el.textContent) || 0;
const to = targets[i % targets.length];
P8Anim.tween({
from, to, duration: 1400, easing: 'cubicInOut',
onUpdate: v => { el.textContent = Math.round(v); },
onComplete: () => { i++; setTimeout(step, 1800); }
});
}
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 script added');
}
// === 3. Section watermarks — добавить data-attribute, CSS подхватит ===
// Используем pseudo-element через inline стиль не получится; вместо этого
// инжектим <div class="p8-sec-wm"> в каждую секцию.
// Watermark SVG-символы по §
const SEC_SYMBOLS = {
p1: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="28" stroke="currentColor" stroke-width="6" fill="none"/><path d="M50 22 v56 M22 50 h56" stroke="currentColor" stroke-width="3"/></svg>', // атом
p2: '<svg viewBox="0 0 100 100"><path d="M50 12 v76 M50 12 l-14 16 M50 12 l14 16 M50 88 l-14-16 M50 88 l14-16" stroke="currentColor" stroke-width="4" fill="none"/></svg>', // вверх/вниз
p3: '<svg viewBox="0 0 100 100"><path d="M14 50 h72 M86 50 l-14-14 M86 50 l-14 14" stroke="currentColor" stroke-width="5" fill="none"/></svg>', // правая стрелка
p4: '<svg viewBox="0 0 100 100"><path d="M30 80 C 30 50, 70 50, 70 30 M30 30 C 30 60, 70 60, 70 80" stroke="currentColor" stroke-width="4" fill="none"/></svg>', // спирали (конвекция)
p5: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" fill="none"><line x1="50" y1="6" x2="50" y2="22"/><line x1="50" y1="78" x2="50" y2="94"/><line x1="6" y1="50" x2="22" y2="50"/><line x1="78" y1="50" x2="94" y2="50"/><line x1="18" y1="18" x2="30" y2="30"/><line x1="70" y1="70" x2="82" y2="82"/><line x1="82" y1="18" x2="70" y2="30"/><line x1="30" y1="70" x2="18" y2="82"/></g></svg>', // солнце
p6: '<svg viewBox="0 0 100 100"><rect x="20" y="35" width="60" height="35" rx="4" stroke="currentColor" stroke-width="4" fill="none"/><path d="M28 35 v-8 M50 35 v-8 M72 35 v-8" stroke="currentColor" stroke-width="3"/></svg>', // сосуд
p7: '<svg viewBox="0 0 100 100"><path d="M28 78 L50 22 L72 78 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M40 60 L60 60" stroke="currentColor" stroke-width="3"/></svg>', // пламя/огонь
p8: '<svg viewBox="0 0 100 100"><path d="M30 30 L70 30 L70 70 L30 70 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M30 50 L70 50" stroke="currentColor" stroke-width="3" stroke-dasharray="4 3"/></svg>', // фазовый переход
p9: '<svg viewBox="0 0 100 100"><path d="M50 14 L70 50 L50 86 L30 50 Z" stroke="currentColor" stroke-width="4" fill="none"/></svg>', // ромб
p10: '<svg viewBox="0 0 100 100"><path d="M20 70 Q 35 50, 50 65 T 80 60" stroke="currentColor" stroke-width="4" fill="none"/><circle cx="78" cy="32" r="6" fill="currentColor"/></svg>', // капля + пар
p11: '<svg viewBox="0 0 100 100"><circle cx="35" cy="55" r="6" fill="currentColor"/><circle cx="55" cy="45" r="8" fill="currentColor"/><circle cx="65" cy="65" r="5" fill="currentColor"/><circle cx="50" cy="75" r="4" 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);
// Обеспечиваем что section имеет position:relative — добавляем inline-стиль
// (или полагаемся на существующий CSS .sec, который позиционирован)
// Проверим: ищем `.sec{` в style
// (Это уже есть в существующем CSS, sec — позиционирован относительно)
// === 4. IV-6 flagship: для §1 добавляем drag-thermometer интерактив ===
// Patch build_p1 — добавим IV-6 widget HTML + _initP1_iv6 функцию.
const IV6_P1_WIDGET = `
/* IV6 — Drag thermometer (Phase 1 flagship interactive) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Перетащи термометр</div></div>'
+'<div class="wg-help">Перетащи термометр на одно из четырёх тел и наблюдай, как меняется его температурный отсчёт. Тела разной массы и при разных условиях — оцени, в каком из них больше внутренней энергии.</div>'
+'<div class="p8-sandbox" id="p1-iv6-sandbox" style="height:320px"></div>'
+'<div class="actions" style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
+'<div class="p8-readout"><span class="p8-readout-label">T</span><span class="p8-readout-value" id="p1-iv6-T">—</span><span class="p8-readout-unit">°C</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">U отн.</span><span class="p8-readout-value" id="p1-iv6-U">—</span></div>'
+'</div>'
+'</div>';
`;
const IV6_P1_INIT = `
function _initP1_iv6(){
const sandbox = document.getElementById('p1-iv6-sandbox');
if (!sandbox || !window.P8Helpers || !window.P8Drag) return;
const svg = P8Helpers.svg.create(560, 320);
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.style.display = 'block';
sandbox.appendChild(svg);
/* 4 тела: имя, T (°C), относительная U */
const bodies = [
{ name:'Лёд 1 кг', cx: 95, cy: 200, T: -10, U: 14, color:'#bfdbfe' },
{ name:'Вода 1 кг', cx: 230, cy: 200, T: 20, U: 100, color:'#7dd3fc' },
{ name:'Чай 0,3 кг',cx: 365, cy: 200, T: 80, U: 80, color:'#fb923c' },
{ name:'Пар 0,5 кг',cx: 500, cy: 200, T: 110, U: 200, color:'#ef4444' }
];
bodies.forEach(b => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+b.cx+','+b.cy+')' });
g.appendChild(P8Helpers.svg.el('rect', {
x: -50, y: -55, width: 100, height: 110, rx: 12,
fill: b.color, stroke: '#0f172a', 'stroke-width': 1.5, opacity: 0.88
}));
g.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -68,
'font-family': "'JetBrains Mono', monospace",
'font-size': 11, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle',
text: b.name
}));
g.dataset = b;
svg.appendChild(g);
});
/* Термометр (draggable group) */
let thermoX = 50, thermoY = 70;
const thermoG = P8Helpers.svg.el('g', { transform: 'translate('+thermoX+','+thermoY+')', 'class': 'p8-draggable' });
/* Drop shadow rect */
thermoG.appendChild(P8Helpers.svg.el('rect', { x: -22, y: -10, width: 44, height: 130, fill: 'transparent' }));
/* Tube */
thermoG.appendChild(P8Helpers.svg.el('rect', {
x: -5, y: 0, width: 10, height: 100, rx: 5,
fill: '#f3f4f6', stroke: '#475569', 'stroke-width': 1.5
}));
const fill = P8Helpers.svg.el('rect', {
x: -3, y: 70, width: 6, height: 30, rx: 2, fill: '#f97316'
});
thermoG.appendChild(fill);
/* Bulb */
const bulb = P8Helpers.svg.el('circle', { cx: 0, cy: 110, r: 12, fill: '#f97316', stroke: '#475569', 'stroke-width': 1.5 });
thermoG.appendChild(bulb);
thermoG.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -2,
'font-family': "'Inter', sans-serif", 'font-size': 10, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle', text: 'Drag'
}));
svg.appendChild(thermoG);
/* Show current readout */
const tEl = document.getElementById('p1-iv6-T');
const uEl = document.getElementById('p1-iv6-U');
function checkHit(cx, cy){
for (const b of bodies){
if (Math.abs(cx - b.cx) < 50 && Math.abs(cy - b.cy) < 55){
const tColor = P8Helpers.thermal.tempColor((b.T + 20) / 130);
fill.setAttribute('fill', tColor);
bulb.setAttribute('fill', tColor);
if (tEl) tEl.textContent = b.T;
if (uEl) uEl.textContent = b.U;
return b;
}
}
fill.setAttribute('fill', '#94a3b8');
bulb.setAttribute('fill', '#94a3b8');
if (tEl) tEl.textContent = '—';
if (uEl) uEl.textContent = '—';
return null;
}
P8Drag.attach(thermoG, {
container: svg,
onMove: (ev, pos) => {
thermoX = Math.max(20, Math.min(540, pos.x));
thermoY = Math.max(10, Math.min(200, pos.y));
thermoG.setAttribute('transform', 'translate('+thermoX+','+thermoY+')');
checkHit(thermoX, thermoY + 110);
}
});
if (window.addXp) {
setTimeout(() => addXp(5, 'p1-iv6-explore'), 12000);
}
}
`;
const insertMarker = `box.innerHTML = h + secNavFor('p1') + readButton('p1');`;
if (!h.includes('p1-iv6-sandbox') && h.includes(insertMarker)) {
h = h.replace(insertMarker, IV6_P1_WIDGET.trim() + '\n\n ' + insertMarker);
// Add init call after wireReadBtn
h = h.replace(`wireReadBtn('p1');`, `wireReadBtn('p1');\n _initP1_iv6();`);
// Append init function after build_p1
const p1Start = h.indexOf('function build_p1()');
const p1End = h.indexOf('\n}\n', p1Start);
h = h.slice(0, p1End + 3) + '\n' + IV6_P1_INIT.trim() + '\n' + h.slice(p1End + 3);
console.log('IV-6 §1 drag-thermometer injected');
}
// === 5. Stub IV-6 placeholders для §2-11 ===
for (let n = 2; n <= 11; n++) {
const pid = 'p' + n;
const stubHtml = `
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.${n}) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">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 1.${n} — coming soon</div>'
+'</div>'
+'</div>';
`;
const marker = `box.innerHTML = h + secNavFor('${pid}') + readButton('${pid}');`;
if (!h.includes(`p8-iv6-${pid}`) && !h.includes(`Новый интерактив §${n}`) && h.includes(marker)) {
h = h.replace(marker, stubHtml.trim() + '\n\n ' + marker);
console.log(` ${pid}: IV-6 stub added`);
}
}
fs.writeFileSync(DST, h);
console.log('ch1 final size:', h.length);
// Sanity parse
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, 100)); process.exit(1); }
}
console.log('inline JS parses OK');
+257 -18
View File
@@ -170,13 +170,16 @@ a{color:inherit;text-decoration:none}
</head>
<body class="p8-theme-thermal">
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 8 · Глава 1</h1>
<div class="hdr-sub">Внутренняя энергия · теплопередача · фазовые переходы</div>
</div>
<div class="hdr-side">
<header class="p8-hero">
<div class="p8-hero-wm"><svg viewBox="0 0 100 100" aria-hidden="true">
<path d="M50 8 C 52 22 65 30 64 46 C 63 56 56 60 55 48 C 53 56 48 60 42 58 C 36 56 32 50 34 42 C 30 52 22 60 24 72 C 26 84 36 92 50 92 C 64 92 76 84 76 70 C 76 50 60 40 56 22 C 54 14 52 10 50 8 Z"/>
</svg></div>
<div class="p8-hero-meter" id="p8-meter-ch1"><span id="p8-meter-val">37</span>°C</div>
<div class="p8-hero-inner">
<div class="p8-hero-eyebrow">Глава 1 · 11 параграфов</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>
@@ -207,17 +210,28 @@ a{color:inherit;text-decoration:none}
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec"><div class="sec-header"><span class="sec-num">&sect; 1</span><h2 class="sec-h">Внутренняя энергия</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec"><div class="sec-header"><span class="sec-num">&sect; 2</span><h2 class="sec-h">Способы изменения внутренней энергии</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec"><div class="sec-header"><span class="sec-num">&sect; 3</span><h2 class="sec-h">Теплопроводность</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec"><div class="sec-header"><span class="sec-num">&sect; 4</span><h2 class="sec-h">Конвекция</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec"><div class="sec-header"><span class="sec-num">&sect; 5</span><h2 class="sec-h">Излучение</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec"><div class="sec-header"><span class="sec-num">&sect; 6</span><h2 class="sec-h">Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec"><div class="sec-header"><span class="sec-num">&sect; 7</span><h2 class="sec-h">Горение. Удельная теплота сгорания топлива</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec"><div class="sec-header"><span class="sec-num">&sect; 8</span><h2 class="sec-h">Плавление и кристаллизация</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec"><div class="sec-header"><span class="sec-num">&sect; 9</span><h2 class="sec-h">Удельная теплота плавления и кристаллизации</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec"><div class="sec-header"><span class="sec-num">&sect; 10</span><h2 class="sec-h">Испарение жидкостей. Факторы, влияющие на скорость испарения</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec"><div class="sec-header"><span class="sec-num">&sect; 11</span><h2 class="sec-h">Кипение жидкостей. Удельная теплота парообразования</h2></div><div id="p11-body"></div></section>
<section id="sec-p1" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p1" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="28" stroke="currentColor" stroke-width="6" fill="none"/><path d="M50 22 v56 M22 50 h56" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 1</span><h2 class="sec-h">Внутренняя энергия</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p2" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M50 12 v76 M50 12 l-14 16 M50 12 l14 16 M50 88 l-14-16 M50 88 l14-16" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 2</span><h2 class="sec-h">Способы изменения внутренней энергии</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p3" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M14 50 h72 M86 50 l-14-14 M86 50 l-14 14" stroke="currentColor" stroke-width="5" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 3</span><h2 class="sec-h">Теплопроводность</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p4" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M30 80 C 30 50, 70 50, 70 30 M30 30 C 30 60, 70 60, 70 80" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 4</span><h2 class="sec-h">Конвекция</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p5" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" fill="none"><line x1="50" y1="6" x2="50" y2="22"/><line x1="50" y1="78" x2="50" y2="94"/><line x1="6" y1="50" x2="22" y2="50"/><line x1="78" y1="50" x2="94" y2="50"/><line x1="18" y1="18" x2="30" y2="30"/><line x1="70" y1="70" x2="82" y2="82"/><line x1="82" y1="18" x2="70" y2="30"/><line x1="30" y1="70" x2="18" y2="82"/></g></svg></div><div class="sec-header"><span class="sec-num">&sect; 5</span><h2 class="sec-h">Излучение</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p6" aria-hidden="true"><svg viewBox="0 0 100 100"><rect x="20" y="35" width="60" height="35" rx="4" stroke="currentColor" stroke-width="4" fill="none"/><path d="M28 35 v-8 M50 35 v-8 M72 35 v-8" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 6</span><h2 class="sec-h">Расчёт количества теплоты при нагревании и охлаждении. Удельная теплоёмкость</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p7" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M28 78 L50 22 L72 78 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M40 60 L60 60" stroke="currentColor" stroke-width="3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 7</span><h2 class="sec-h">Горение. Удельная теплота сгорания топлива</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p8" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M30 30 L70 30 L70 70 L30 70 Z" stroke="currentColor" stroke-width="4" fill="none"/><path d="M30 50 L70 50" stroke="currentColor" stroke-width="3" stroke-dasharray="4 3"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 8</span><h2 class="sec-h">Плавление и кристаллизация</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p9" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M50 14 L70 50 L50 86 L30 50 Z" stroke="currentColor" stroke-width="4" fill="none"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 9</span><h2 class="sec-h">Удельная теплота плавления и кристаллизации</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p10" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M20 70 Q 35 50, 50 65 T 80 60" stroke="currentColor" stroke-width="4" fill="none"/><circle cx="78" cy="32" r="6" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 10</span><h2 class="sec-h">Испарение жидкостей. Факторы, влияющие на скорость испарения</h2></div><div id="p10-body"></div></section>
<section id="sec-p11" class="sec">
<div class="p8-sec-wm" id="p8-sec-wm-p11" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="35" cy="55" r="6" fill="currentColor"/><circle cx="55" cy="45" r="8" fill="currentColor"/><circle cx="65" cy="65" r="5" fill="currentColor"/><circle cx="50" cy="75" r="4" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">&sect; 11</span><h2 class="sec-h">Кипение жидкостей. Удельная теплота парообразования</h2></div><div id="p11-body"></div></section>
<section id="sec-final1" class="sec"><div class="sec-header"><span class="sec-num">&#9733;</span><h2 class="sec-h">Финал главы</h2></div><div id="final1-body"></div></section>
</div>
@@ -826,9 +840,21 @@ function build_p1(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p1-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p1-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — Drag thermometer (Phase 1 flagship interactive) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Перетащи термометр</div></div>'
+'<div class="wg-help">Перетащи термометр на одно из четырёх тел и наблюдай, как меняется его температурный отсчёт. Тела разной массы и при разных условиях — оцени, в каком из них больше внутренней энергии.</div>'
+'<div class="p8-sandbox" id="p1-iv6-sandbox" style="height:320px"></div>'
+'<div class="actions" style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">'
+'<div class="p8-readout"><span class="p8-readout-label">T</span><span class="p8-readout-value" id="p1-iv6-T"></span><span class="p8-readout-unit">°C</span></div>'
+'<div class="p8-readout"><span class="p8-readout-label">U отн.</span><span class="p8-readout-value" id="p1-iv6-U"></span></div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p1') + readButton('p1');
renderMath(box);
wireReadBtn('p1');
_initP1_iv6();
_initp1_iv5();
_initP1_sim();
@@ -837,6 +863,94 @@ function build_p1(){
_initP1_mcq();
}
function _initP1_iv6(){
const sandbox = document.getElementById('p1-iv6-sandbox');
if (!sandbox || !window.P8Helpers || !window.P8Drag) return;
const svg = P8Helpers.svg.create(560, 320);
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.style.display = 'block';
sandbox.appendChild(svg);
/* 4 тела: имя, T (°C), относительная U */
const bodies = [
{ name:'Лёд 1 кг', cx: 95, cy: 200, T: -10, U: 14, color:'#bfdbfe' },
{ name:'Вода 1 кг', cx: 230, cy: 200, T: 20, U: 100, color:'#7dd3fc' },
{ name:'Чай 0,3 кг',cx: 365, cy: 200, T: 80, U: 80, color:'#fb923c' },
{ name:'Пар 0,5 кг',cx: 500, cy: 200, T: 110, U: 200, color:'#ef4444' }
];
bodies.forEach(b => {
const g = P8Helpers.svg.el('g', { transform: 'translate('+b.cx+','+b.cy+')' });
g.appendChild(P8Helpers.svg.el('rect', {
x: -50, y: -55, width: 100, height: 110, rx: 12,
fill: b.color, stroke: '#0f172a', 'stroke-width': 1.5, opacity: 0.88
}));
g.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -68,
'font-family': "'JetBrains Mono', monospace",
'font-size': 11, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle',
text: b.name
}));
g.dataset = b;
svg.appendChild(g);
});
/* Термометр (draggable group) */
let thermoX = 50, thermoY = 70;
const thermoG = P8Helpers.svg.el('g', { transform: 'translate('+thermoX+','+thermoY+')', 'class': 'p8-draggable' });
/* Drop shadow rect */
thermoG.appendChild(P8Helpers.svg.el('rect', { x: -22, y: -10, width: 44, height: 130, fill: 'transparent' }));
/* Tube */
thermoG.appendChild(P8Helpers.svg.el('rect', {
x: -5, y: 0, width: 10, height: 100, rx: 5,
fill: '#f3f4f6', stroke: '#475569', 'stroke-width': 1.5
}));
const fill = P8Helpers.svg.el('rect', {
x: -3, y: 70, width: 6, height: 30, rx: 2, fill: '#f97316'
});
thermoG.appendChild(fill);
/* Bulb */
const bulb = P8Helpers.svg.el('circle', { cx: 0, cy: 110, r: 12, fill: '#f97316', stroke: '#475569', 'stroke-width': 1.5 });
thermoG.appendChild(bulb);
thermoG.appendChild(P8Helpers.svg.el('text', {
x: 0, y: -2,
'font-family': "'Inter', sans-serif", 'font-size': 10, 'font-weight': 700,
fill: '#0f172a', 'text-anchor': 'middle', text: 'Drag'
}));
svg.appendChild(thermoG);
/* Show current readout */
const tEl = document.getElementById('p1-iv6-T');
const uEl = document.getElementById('p1-iv6-U');
function checkHit(cx, cy){
for (const b of bodies){
if (Math.abs(cx - b.cx) < 50 && Math.abs(cy - b.cy) < 55){
const tColor = P8Helpers.thermal.tempColor((b.T + 20) / 130);
fill.setAttribute('fill', tColor);
bulb.setAttribute('fill', tColor);
if (tEl) tEl.textContent = b.T;
if (uEl) uEl.textContent = b.U;
return b;
}
}
fill.setAttribute('fill', '#94a3b8');
bulb.setAttribute('fill', '#94a3b8');
if (tEl) tEl.textContent = '—';
if (uEl) uEl.textContent = '—';
return null;
}
P8Drag.attach(thermoG, {
container: svg,
onMove: (ev, pos) => {
thermoX = Math.max(20, Math.min(540, pos.x));
thermoY = Math.max(10, Math.min(200, pos.y));
thermoG.setAttribute('transform', 'translate('+thermoX+','+thermoY+')');
checkHit(thermoX, thermoY + 110);
}
});
if (window.addXp) {
setTimeout(() => addXp(5, 'p1-iv6-explore'), 12000);
}
}
function _initp1_iv5(){
const TASKS = [{"q":"Переведите температуру $t = 27\\,^\\circ$C в кельвины. ($T = t + 273$)","ans":300,"tol":1,"why":"$T = 27 + 273 = 300$ К."},{"q":"Температура воды $T = 373$ К. Чему равно $t$ в градусах Цельсия?","ans":100,"tol":1,"why":"$t = T - 273 = 373 - 273 = 100\\,^\\circ$C — кипение воды."},{"q":"У стакана воды массой $m_1 = 0{,}5$ кг и у бочки воды массой $m_2 = 50$ кг одинаковая температура. У кого внутренняя энергия больше во сколько раз?","ans":100,"tol":1,"why":"$U \\propto m$ при одинаковой $T$. $U_2/U_1 = m_2/m_1 = 50/0{,}5 = 100$."},{"q":"Тело нагрели на $\\Delta T = 30$ К. На сколько градусов Цельсия изменилась его температура?","ans":30,"tol":0.5,"why":"Шкалы Кельвина и Цельсия отличаются только сдвигом — разность температур одинакова."},{"q":"При какой температуре по шкале Цельсия средняя кинетическая энергия молекул равна нулю (абсолютный ноль)?","ans":-273,"tol":1,"why":"Абсолютный ноль $T = 0$ К соответствует $t = 0 - 273 = -273\\,^\\circ$C."}];
let i = 0, ok = 0, awarded = false;
@@ -1180,6 +1294,16 @@ function build_p2(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p2-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p2-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.2) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §2</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 1.2 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p2') + readButton('p2');
renderMath(box);
wireReadBtn('p2');
@@ -1547,6 +1671,16 @@ function build_p3(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p3-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p3-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.3) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §3</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 1.3 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p3') + readButton('p3');
renderMath(box);
wireReadBtn('p3');
@@ -1811,6 +1945,16 @@ function build_p4(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p4-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p4-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.4) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §4</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 1.4 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p4') + readButton('p4');
renderMath(box);
wireReadBtn('p4');
@@ -2118,6 +2262,16 @@ function build_p5(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p5-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p5-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.5) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §5</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 1.5 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p5') + readButton('p5');
renderMath(box);
wireReadBtn('p5');
@@ -2456,6 +2610,16 @@ function build_p6(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p6-task-i">1</b> / 6</span><span>Правильно: <b id="p6-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.6) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §6</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 1.6 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p6') + readButton('p6');
renderMath(box);
wireReadBtn('p6');
@@ -2657,6 +2821,16 @@ function build_p7(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p7-task-i">1</b> / 5</span><span>Правильно: <b id="p7-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.7) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §7</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 1.7 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p7') + readButton('p7');
renderMath(box);
wireReadBtn('p7');
@@ -2899,6 +3073,16 @@ function build_p8(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p8-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p8-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.8) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §8</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 1.8 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p8') + readButton('p8');
renderMath(box);
wireReadBtn('p8');
@@ -3161,6 +3345,16 @@ function build_p9(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p9-task-i">1</b> / 6</span><span>Правильно: <b id="p9-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.9) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §9</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 1.9 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p9') + readButton('p9');
renderMath(box);
wireReadBtn('p9');
@@ -3374,6 +3568,16 @@ function build_p10(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p10-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p10-tasks5-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.10) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §10</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 1.10 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p10') + readButton('p10');
renderMath(box);
wireReadBtn('p10');
@@ -3685,6 +3889,16 @@ function build_p11(){
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p11-task-i">1</b> / 6</span><span>Правильно: <b id="p11-task-ok">0</b></span></div>'
+'</div>';
/* IV6 — flagship интерактив (заглушка Phase 1, наполнение в Phase 1.11) */
h += '<div class="wg p8-iv6">'
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-thermal">IV-6</span><div class="wg-title">Новый интерактив §11</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 1.11 — coming soon</div>'
+'</div>'
+'</div>';
box.innerHTML = h + secNavFor('p11') + readButton('p11');
renderMath(box);
wireReadBtn('p11');
@@ -3939,5 +4153,30 @@ document.addEventListener('DOMContentLoaded', init);
</script>
<script>
/* P8 hero meter — анимированный счётчик в углу (Phase 1 thermal) */
(function(){
function init(){
const el = document.getElementById('p8-meter-val');
if (!el || !window.P8Anim) return;
const targets = [37, 100, 0, -10, 25, 80];
let i = 0;
function step(){
const from = parseInt(el.textContent) || 0;
const to = targets[i % targets.length];
P8Anim.tween({
from, to, duration: 1400, easing: 'cubicInOut',
onUpdate: v => { el.textContent = Math.round(v); },
onComplete: () => { i++; setTimeout(step, 1800); }
});
}
setTimeout(step, 1200);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script>
</body>
</html>