feat(phys8 ch3): Phase 3 — Световые явления (визуал + 9 IV-6)
Hero: spectrum-drift градиент (18s), солнце SVG-watermark (rotate-анимация 40s), live-meter длины волны (400/470/550/600/700 нм с цветами цикла). 9 section watermarks: лампа, тень, угол, зеркало, парабола, рефракция, линза, призма, глаз. 9 IV-6 интерактивов: §32 Источники — кнопки 'Точечный/Протяжённый' с динамической тенью (точечный — чёткая, протяжённый — с полутенью). §33 Тени — drag-источника по X, размер тени пересчитывается проективно. §34 Закон отражения — scrubber угла, лучи + нормаль. §35 Плоское зеркало — drag-d объекта, мнимое изображение за зеркалом на том же расстоянии (штриховая стрелка). §36 Сферическое зеркало — drag-d, формула 1/v+1/d=1/F, изображение с правильным знаком/размером. §37 Преломление — scrubber угла, закон Снеллиуса (n₁=1, n₂=1.33). §38 Линза — 3 главных луча от объекта, формула v=dF/(d-F), изображение по принципу геометрической оптики. §39 Дисперсия — призма с разложением белого света на 7 цветов видимого спектра. §40 Глаз — кнопки 'Норма/Близорукость/Дальнозоркость' с fokus-точкой и корректирующей линзой (рассеив/собир).
This commit is contained in:
@@ -0,0 +1,544 @@
|
||||
// Phase 3 — Ch3 Световые явления: hero + 9 section watermarks + 9 IV-6.
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch3.html');
|
||||
let h = fs.readFileSync(DST, 'utf8');
|
||||
|
||||
// === 1. Hero replacement ===
|
||||
const SUN_WM = `<svg viewBox="0 0 100 100" aria-hidden="true">
|
||||
<circle cx="50" cy="50" r="22" />
|
||||
<g><line x1="50" y1="8" x2="50" y2="22" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="50" y1="78" x2="50" y2="92" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="8" y1="50" x2="22" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="78" y1="50" x2="92" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="20" y1="20" x2="30" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="70" y1="70" x2="80" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="80" y1="20" x2="70" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="30" y1="70" x2="20" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/></g>
|
||||
</svg>`;
|
||||
|
||||
const NEW_HERO = `<header class="p8-hero">
|
||||
<div class="p8-hero-wm">${SUN_WM}</div>
|
||||
<div class="p8-hero-meter" id="p8-meter-ch3"><span id="p8-meter-val">λ=550</span> нм</div>
|
||||
<div class="p8-hero-inner">
|
||||
<div class="p8-hero-eyebrow">Глава 3 · 9 параграфов</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 (wavelength cycles through visible spectrum) ===
|
||||
const METER_SCRIPT = `
|
||||
<script>
|
||||
/* P8 hero meter — анимация длины волны (Phase 3 spectrum) */
|
||||
(function(){
|
||||
function init(){
|
||||
const el = document.getElementById('p8-meter-val');
|
||||
if (!el || !window.P8Anim) return;
|
||||
const targets = [{ l: 400, c:'#7c3aed' }, { l: 470, c:'#2563eb' }, { l: 550, c:'#16a34a' }, { l: 600, c:'#f59e0b' }, { l: 700, c:'#dc2626' }];
|
||||
let i = 0;
|
||||
function step(){
|
||||
const from = parseFloat((el.textContent || '550').replace(/\\D/g,'')) || 550;
|
||||
const target = targets[i % targets.length];
|
||||
P8Anim.tween({
|
||||
from, to: target.l, duration: 1100, easing: 'cubicInOut',
|
||||
onUpdate: v => { el.textContent = 'λ=' + Math.round(v); el.style.color = target.c; },
|
||||
onComplete: () => { i++; setTimeout(step, 1400); }
|
||||
});
|
||||
}
|
||||
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 added');
|
||||
}
|
||||
|
||||
// === 3. Section watermarks ===
|
||||
const SEC_SYMBOLS = {
|
||||
p32: '<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" stroke-linecap="round"><line x1="50" y1="14" x2="50" y2="26"/><line x1="50" y1="74" x2="50" y2="86"/><line x1="14" y1="50" x2="26" y2="50"/><line x1="74" y1="50" x2="86" y2="50"/></g></svg>',
|
||||
p33: '<svg viewBox="0 0 100 100"><circle cx="32" cy="40" r="10" fill="currentColor"/><rect x="50" y="34" width="14" height="40" fill="currentColor"/><polygon points="68,40 92,30 92,80 68,70" fill="currentColor" opacity="0.5"/></svg>',
|
||||
p34: '<svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="20" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="50" y2="90" stroke="currentColor" stroke-width="2" stroke-dasharray="4 4"/></svg>',
|
||||
p35: '<svg viewBox="0 0 100 100"><line x1="30" y1="20" x2="30" y2="80" stroke="currentColor" stroke-width="4"/><g stroke="currentColor" stroke-width="1.5"><line x1="30" y1="30" x2="22" y2="34"/><line x1="30" y1="45" x2="22" y2="49"/><line x1="30" y1="60" x2="22" y2="64"/><line x1="30" y1="75" x2="22" y2="79"/></g><circle cx="60" cy="50" r="6" fill="currentColor"/></svg>',
|
||||
p36: '<svg viewBox="0 0 100 100"><path d="M30 20 Q 20 50, 30 80" stroke="currentColor" stroke-width="5" fill="none"/><line x1="48" y1="50" x2="70" y2="50" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3"/><circle cx="70" cy="50" r="3" fill="currentColor"/></svg>',
|
||||
p37: '<svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="80" stroke="currentColor" stroke-width="5" stroke-dasharray="0"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/></svg>',
|
||||
p38: '<svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="10" ry="36" fill="currentColor" opacity="0.4"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/><circle cx="25" cy="50" r="2" fill="currentColor"/><circle cx="75" cy="50" r="2" fill="currentColor"/></svg>',
|
||||
p39: '<svg viewBox="0 0 100 100"><polygon points="40,20 80,50 40,80" stroke="currentColor" stroke-width="4" fill="none"/><line x1="20" y1="50" x2="40" y2="50" stroke="currentColor" stroke-width="3"/><g stroke-width="2.5" fill="none"><line x1="60" y1="40" x2="90" y2="30" stroke="#dc2626"/><line x1="60" y1="50" x2="90" y2="50" stroke="#16a34a"/><line x1="60" y1="60" x2="90" y2="70" stroke="#2563eb"/></g></svg>',
|
||||
p40: '<svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="36" ry="22" fill="none" stroke="currentColor" stroke-width="3"/><circle cx="50" cy="50" r="12" fill="currentColor"/><circle cx="50" cy="50" r="5" fill="#fff"/></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:', secWmInjected);
|
||||
|
||||
// === 4. Stub function ===
|
||||
function makeStubText(n) {
|
||||
return `/* IV6 — flagship интерактив (заглушка Phase 3, наполнение в Phase 3.${n}) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">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 3.${n} — coming soon</div>'
|
||||
+'</div>'
|
||||
+'</div>';`;
|
||||
}
|
||||
|
||||
function replaceWithReal(pid, n, widgetHtml, initFn) {
|
||||
// Two paths: stub already present (need to replace) OR no stub (just inject before box.innerHTML).
|
||||
const stubLF = makeStubText(n);
|
||||
const stubCRLF = stubLF.replace(/\n/g, '\r\n');
|
||||
let stubText = null;
|
||||
if (h.includes(stubLF)) stubText = stubLF;
|
||||
else if (h.includes(stubCRLF)) stubText = stubCRLF;
|
||||
const eol = (h.indexOf('\r\n') >= 0) ? '\r\n' : '\n';
|
||||
const widget = widgetHtml.trim().replace(/\n/g, eol);
|
||||
if (stubText) {
|
||||
h = h.replace(stubText, widget);
|
||||
} else {
|
||||
// Inject before box.innerHTML
|
||||
const marker = `box.innerHTML = h + secNavFor('${pid}') + readButton('${pid}');`;
|
||||
if (!h.includes(marker)) { console.warn(`${pid}: no marker`); return false; }
|
||||
h = h.replace(marker, widget + eol + eol + ' ' + marker);
|
||||
}
|
||||
// Add init call
|
||||
h = h.replace(`wireReadBtn('${pid}');`, `wireReadBtn('${pid}');${eol} _init${pid.toUpperCase()}_iv6();`);
|
||||
// Append init function after build_pN
|
||||
const fnStart = h.indexOf(`function build_${pid}()`);
|
||||
const fnEnd = h.indexOf('\n}\n', fnStart);
|
||||
h = h.slice(0, fnEnd + 3) + '\n' + initFn.trim() + '\n' + h.slice(fnEnd + 3);
|
||||
console.log(`${pid}: injected real IV-6`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// === Compact widget builder ===
|
||||
function widget(pid, n, title, help, height, body, init) {
|
||||
const html = `/* IV6 — ${title} (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">${title}</div></div>'
|
||||
+'<div class="wg-help">${help}</div>'
|
||||
+'<div class="p8-sandbox" id="${pid}-iv6-sandbox" style="height:${height}px"></div>'
|
||||
${body}
|
||||
+'</div>';`;
|
||||
const initFn = `
|
||||
function _init${pid.toUpperCase()}_iv6(){
|
||||
const sb = document.getElementById('${pid}-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
${init}
|
||||
}
|
||||
`;
|
||||
replaceWithReal(pid, n, html, initFn);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// §32 — Источники света (типы)
|
||||
// ============================================================
|
||||
widget('p32', 32, 'Точечные и протяжённые источники',
|
||||
'Точечный источник (свеча издалека) даёт чёткие тени. Протяжённый (Солнце) — размытые с полутенью.',
|
||||
240,
|
||||
'+\'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p32-iv6-point">Точечный</button><button class="btn" id="p32-iv6-ext">Протяжённый</button></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let mode = 'point';
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Object */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: 230, y: 90, width: 30, height: 60, fill: '#475569' }));
|
||||
if (mode === 'point') {
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 110, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 130, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
|
||||
/* Sharp shadow */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 460,210 260,150', fill: '#0f172a', opacity: 0.7 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 380, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#fff', 'text-anchor':'middle', text: 'Чёткая тень' }));
|
||||
} else {
|
||||
/* Sun */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 30, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 90, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 150, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
/* Sharp inner shadow (umbra) */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,100 380,80 380,160 260,140', fill: '#0f172a', opacity: 0.7 }));
|
||||
/* Penumbra */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 380,80 260,100', fill: '#0f172a', opacity: 0.3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,150 380,160 460,210 260,150', fill: '#0f172a', opacity: 0.3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 320, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#0f172a', 'text-anchor':'middle', text: 'Тень + полутень' }));
|
||||
}
|
||||
}
|
||||
document.getElementById('p32-iv6-point').onclick = () => { mode = 'point'; render(); };
|
||||
document.getElementById('p32-iv6-ext').onclick = () => { mode = 'ext'; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §33 — Тени (расстояние источник-объект)
|
||||
// ============================================================
|
||||
widget('p33', 33, 'Тень и её размер',
|
||||
'Двигай источник света — наблюдай, как меняется размер тени. Чем ближе источник — тем больше тень.',
|
||||
240,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Источник X</span><input type="range" id="p33-iv6-x" min="20" max="200" step="2" value="80"><span class="p8-scrubber-value"><span id="p33-iv6-x-val">80</span><span class="p8-unit">px</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let lampX = 80;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Light */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lampX, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Object */
|
||||
const objX = 300;
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: objX - 12, y: 90, width: 24, height: 60, fill: '#475569' }));
|
||||
/* Rays + shadow */
|
||||
const wallX = 510;
|
||||
const t = (wallX - lampX) / (objX - lampX);
|
||||
const yTop = 120 + (90 - 120) * t;
|
||||
const yBot = 120 + (150 - 120) * t;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 110, x2: wallX, y2: yTop, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 130, x2: wallX, y2: yBot, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
/* Wall */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: wallX, y1: 20, x2: wallX, y2: 220, stroke: '#0f172a', 'stroke-width': 3 }));
|
||||
/* Shadow on wall */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: wallX, y: yTop, width: 28, height: yBot - yTop, fill: '#0f172a', opacity: 0.7 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: wallX + 14, y: yTop - 5, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'h='+(yBot-yTop).toFixed(0) }));
|
||||
}
|
||||
document.getElementById('p33-iv6-x').oninput = ev => { lampX = +ev.target.value; document.getElementById('p33-iv6-x-val').textContent = lampX; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §34 — Отражение (угол падения = угол отражения)
|
||||
// ============================================================
|
||||
widget('p34', 34, 'Закон отражения',
|
||||
'Двигай угол падения — угол отражения равен ему.',
|
||||
240,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p34-iv6-a" min="0" max="80" step="1" value="35"><span class="p8-scrubber-value"><span id="p34-iv6-a-val">35</span><span class="p8-unit">°</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let alpha = 35;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const cx = 280, cy = 200;
|
||||
/* Mirror */
|
||||
svg.appendChild(P8Helpers.optics.mirrorPlane(80, 200, 480, 200));
|
||||
/* Normal */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 200, x2: cx, y2: 30, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '5 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 40, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'нормаль' }));
|
||||
/* Incident ray */
|
||||
const rad = alpha * Math.PI / 180;
|
||||
const len = 150;
|
||||
const inX = cx - len * Math.sin(rad);
|
||||
const inY = cy - len * Math.cos(rad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Reflected ray */
|
||||
const rX = cx + len * Math.sin(rad);
|
||||
const rY = cy - len * Math.cos(rad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Angle labels */
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+alpha+'°' }));
|
||||
}
|
||||
document.getElementById('p34-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p34-iv6-a-val').textContent = alpha; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §35 — Плоское зеркало (объект → мнимое изображение)
|
||||
// ============================================================
|
||||
widget('p35', 35, 'Плоское зеркало',
|
||||
'Двигай объект — мнимое изображение появляется за зеркалом на том же расстоянии.',
|
||||
240,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p35-iv6-d" min="50" max="200" step="2" value="100"><span class="p8-scrubber-value"><span id="p35-iv6-d-val">100</span><span class="p8-unit">px</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let d = 100;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const mirX = 280;
|
||||
/* Mirror */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 40, x2: mirX, y2: 200, stroke: '#0f172a', 'stroke-width': 4 }));
|
||||
/* Hatch */
|
||||
for (let i = 0; i < 12; i++) {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 45 + i * 14, x2: mirX + 8, y2: 49 + i * 14, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
}
|
||||
/* Object (arrow) */
|
||||
const objX = mirX - d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 180, x2: objX, y2: 90, stroke: '#dc2626', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',85 '+(objX-6)+',95 '+(objX+6)+',95', fill: '#dc2626' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: objX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'объект' }));
|
||||
/* Virtual image */
|
||||
const imgX = mirX + d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 180, x2: imgX, y2: 90, stroke: '#94a3b8', 'stroke-width': 3, 'stroke-dasharray': '4 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+',85 '+(imgX-6)+',95 '+(imgX+6)+',95', fill: '#94a3b8' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: imgX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#94a3b8', 'text-anchor':'middle', text: 'мнимое изображение' }));
|
||||
/* Distance arrows */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 60, x2: mirX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 60, x2: imgX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: (objX + mirX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: (mirX + imgX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
|
||||
}
|
||||
document.getElementById('p35-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p35-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §36 — Сферическое зеркало (фокус)
|
||||
// ============================================================
|
||||
widget('p36', 36, 'Сферическое зеркало',
|
||||
'Двигай расстояние объекта до фокуса — изображение меняет тип (увеличенное/уменьшенное, прямое/перевёрнутое).',
|
||||
240,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Объект → зеркало</span><input type="range" id="p36-iv6-d" min="50" max="250" step="2" value="180"><span class="p8-scrubber-value"><span id="p36-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
const F = 100, mirX = 480;
|
||||
let d = 180;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Mirror curve */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M '+mirX+' 60 Q '+(mirX-30)+' 120, '+mirX+' 180', stroke: '#0f172a', 'stroke-width': 4, fill: 'none' }));
|
||||
/* Axis */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 120, x2: mirX, y2: 120, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
|
||||
/* Focus */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: mirX - F, cy: 120, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: mirX - F, y: 138, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', 'text-anchor':'middle', text: 'F' }));
|
||||
/* Object */
|
||||
const objX = mirX - d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 120, x2: objX, y2: 80, stroke: '#dc2626', 'stroke-width': 2.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',75 '+(objX-4)+',82 '+(objX+4)+',82', fill: '#dc2626' }));
|
||||
/* Lens formula: 1/v - 1/d = 1/F, here mirror equation: 1/v + 1/d = 1/F (using d positive in front) */
|
||||
const v = 1 / (1 / F - 1 / d);
|
||||
const imgX = mirX - v;
|
||||
const h_img = 40 * v / d * -1;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 120, x2: imgX, y2: 120 + h_img, stroke: '#2563eb', 'stroke-width': 2.5, 'stroke-dasharray': v < 0 ? '4 3' : '0' }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(120 + h_img - Math.sign(h_img) * 4)+' '+(imgX-4)+','+(120 + h_img + 1)+' '+(imgX+4)+','+(120 + h_img + 1), fill: '#2563eb' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
|
||||
}
|
||||
document.getElementById('p36-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p36-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §37 — Преломление (углы)
|
||||
// ============================================================
|
||||
widget('p37', 37, 'Преломление света',
|
||||
'Двигай угол падения. На границе двух сред (воздух/вода n=1.33) угол преломления меньше.',
|
||||
240,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p37-iv6-a" min="0" max="80" step="1" value="40"><span class="p8-scrubber-value"><span id="p37-iv6-a-val">40</span><span class="p8-unit">°</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let alpha = 40;
|
||||
const n1 = 1, n2 = 1.33;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const cx = 280, cy = 120;
|
||||
/* Water region */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: 0, y: 120, width: 560, height: 120, fill: '#7dd3fc', opacity: 0.35 }));
|
||||
/* Interface */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: cy, x2: 530, y2: cy, stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Normal */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 20, x2: cx, y2: 220, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '4 3' }));
|
||||
/* Incident */
|
||||
const aRad = alpha * Math.PI / 180;
|
||||
const len = 120;
|
||||
const inX = cx - len * Math.sin(aRad);
|
||||
const inY = cy - len * Math.cos(aRad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Snell: n1 sin α = n2 sin β */
|
||||
const beta = Math.asin(Math.min(1, n1 / n2 * Math.sin(aRad)));
|
||||
const rX = cx + len * Math.sin(beta);
|
||||
const rY = cy + len * Math.cos(beta);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Labels */
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 70, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 175, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+(beta * 180 / Math.PI).toFixed(1)+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'n₁=1 (воздух)' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 215, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'n₂=1.33 (вода)' }));
|
||||
}
|
||||
document.getElementById('p37-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p37-iv6-a-val').textContent = alpha; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §38 — Линзы (3 главных луча)
|
||||
// ============================================================
|
||||
widget('p38', 38, 'Собирающая линза — построение изображения',
|
||||
'Двигай объект. Три главных луча: через центр (прямо), параллельно главной оси (через F), через F (параллельно оси).',
|
||||
280,
|
||||
'+\'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p38-iv6-d" min="50" max="280" step="2" value="180"><span class="p8-scrubber-value"><span id="p38-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 280);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let d = 180;
|
||||
const F = 100, lensX = 320, axisY = 150;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Axis */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: axisY, x2: 530, y2: axisY, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
|
||||
/* Lens */
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(lensX, axisY, 140, 'converging'));
|
||||
/* F marks */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX - F, cy: axisY, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX + F, cy: axisY, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: lensX - F - 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: lensX + F + 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
|
||||
/* Object */
|
||||
const objX = lensX - d;
|
||||
const objH = 50;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: axisY, x2: objX, y2: axisY - objH, stroke: '#dc2626', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+','+(axisY-objH-6)+' '+(objX-5)+','+(axisY-objH+2)+' '+(objX+5)+','+(axisY-objH+2), fill: '#dc2626' }));
|
||||
/* Thin lens equation: 1/v - 1/(-d) = 1/F → v = dF/(d-F) (object on left, d>0) */
|
||||
const v = (d * F) / (d - F);
|
||||
const imgX = lensX + v;
|
||||
const imgH = objH * v / d;
|
||||
/* Three principal rays */
|
||||
/* Ray 1: parallel to axis, refracts through far F */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY - objH, { color: '#facc15', width: 1.5, arrow: false }));
|
||||
svg.appendChild(P8Helpers.optics.rayLine(lensX, axisY - objH, imgX, axisY + imgH, { color: '#facc15', width: 1.5, arrow: false }));
|
||||
/* Ray 2: through optic center */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, imgX, axisY + imgH, { color: '#16a34a', width: 1.5, arrow: false }));
|
||||
/* Ray 3: through near F, refracts parallel */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY + ((lensX - objX) / (lensX - F - objX)) * (-objH) - (-objH) * ((lensX - F - objX) / (lensX - F - objX) - 1), { color: '#2563eb', width: 1.5, arrow: false }));
|
||||
/* Image */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: axisY, x2: imgX, y2: axisY + imgH, stroke: '#2563eb', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(axisY+imgH+6)+' '+(imgX-5)+','+(axisY+imgH-2)+' '+(imgX+5)+','+(axisY+imgH-2), fill: '#2563eb' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 260, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
|
||||
}
|
||||
document.getElementById('p38-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p38-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §39 — Дисперсия (призма + спектр)
|
||||
// ============================================================
|
||||
widget('p39', 39, 'Дисперсия — разложение белого света',
|
||||
'Через призму белый свет разлагается на спектр. Угол отклонения зависит от длины волны: красный отклоняется меньше, фиолетовый больше.',
|
||||
240,
|
||||
'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Incident white */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#fff', 'stroke-width': 5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 30, y: 105, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'Белый свет' }));
|
||||
/* Prism */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '200,180 280,40 360,180', fill: 'rgba(125,211,252,.35)', stroke: '#0284c7', 'stroke-width': 2 }));
|
||||
/* Spectrum out */
|
||||
const colors = [
|
||||
{ c: '#dc2626', off: 0, label: 'красный' },
|
||||
{ c: '#f97316', off: 8, label: 'оранжевый' },
|
||||
{ c: '#facc15', off: 16, label: 'жёлтый' },
|
||||
{ c: '#16a34a', off: 24, label: 'зелёный' },
|
||||
{ c: '#0ea5e9', off: 32, label: 'голубой' },
|
||||
{ c: '#2563eb', off: 40, label: 'синий' },
|
||||
{ c: '#7c3aed', off: 48, label: 'фиолетовый' }
|
||||
];
|
||||
colors.forEach((cl, i) => {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 290, y1: 120, x2: 530, y2: 100 + cl.off, stroke: cl.c, 'stroke-width': 2.5, 'stroke-linecap': 'round' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 535, y: 104 + cl.off, 'font-family':"'Inter',sans-serif", 'font-size':9, 'font-weight':700, fill: cl.c, text: cl.label }));
|
||||
});
|
||||
}
|
||||
render();
|
||||
`);
|
||||
|
||||
// ============================================================
|
||||
// §40 — Глаз / коррекция (близорукость / дальнозоркость)
|
||||
// ============================================================
|
||||
widget('p40', 40, 'Глаз: аккомодация и очки',
|
||||
'Нормальный глаз: лучи фокусируются на сетчатке. Близорукий — перед сетчаткой (нужна рассеивающая). Дальнозоркий — за сетчаткой (нужна собирающая).',
|
||||
240,
|
||||
'+\'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p40-iv6-normal">Норма</button><button class="btn" id="p40-iv6-myop">Близорукость</button><button class="btn" id="p40-iv6-hyper">Дальнозоркость</button></div>\'',
|
||||
`
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let mode = 'normal';
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Eye outline */
|
||||
svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 380, cy: 120, rx: 90, ry: 70, fill: '#fff', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Cornea */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M 290 105 Q 270 120, 290 135', fill: '#bae6fd', stroke: '#0284c7', 'stroke-width': 2 }));
|
||||
/* Lens */
|
||||
svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 310, cy: 120, rx: 8, ry: 26, fill: 'rgba(125,211,252,.55)', stroke: '#0284c7', 'stroke-width': 1.5 }));
|
||||
/* Retina */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M 440 80 Q 470 120, 440 160', stroke: '#dc2626', 'stroke-width': 3, fill: 'none' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 465, y: 85, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', text: 'сетчатка' }));
|
||||
/* Rays */
|
||||
const focusX = mode === 'normal' ? 440 : (mode === 'myop' ? 420 : 480);
|
||||
const colorFocus = mode === 'normal' ? '#16a34a' : '#dc2626';
|
||||
/* 3 incoming rays */
|
||||
[80, 120, 160].forEach(y => {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: y, x2: 305, y2: y, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 315, y1: y, x2: focusX, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
});
|
||||
/* Focus point */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: focusX, cy: 120, r: 5, fill: colorFocus }));
|
||||
/* Correction lens if needed */
|
||||
if (mode === 'myop') {
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'diverging'));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#2563eb', 'text-anchor':'middle', text: '−дптр (рассеивающая)' }));
|
||||
} else if (mode === 'hyper') {
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'converging'));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: '+дптр (собирающая)' }));
|
||||
}
|
||||
/* Label */
|
||||
const labels = { normal: 'Норма: фокус на сетчатке', myop: 'Близорукость: фокус перед сетчаткой', hyper: 'Дальнозоркость: фокус за сетчаткой' };
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 222, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: labels[mode] }));
|
||||
}
|
||||
document.getElementById('p40-iv6-normal').onclick = () => { mode = 'normal'; render(); };
|
||||
document.getElementById('p40-iv6-myop').onclick = () => { mode = 'myop'; render(); };
|
||||
document.getElementById('p40-iv6-hyper').onclick = () => { mode = 'hyper'; render(); };
|
||||
render();
|
||||
`);
|
||||
|
||||
fs.writeFileSync(DST, h);
|
||||
console.log('ch3 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, 200)); 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:', fns.length, fns);
|
||||
@@ -1,4 +1,352 @@
|
||||
<!
|
||||
function _initP40_iv6(){
|
||||
const sb = document.getElementById('p40-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let mode = 'normal';
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Eye outline */
|
||||
svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 380, cy: 120, rx: 90, ry: 70, fill: '#fff', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Cornea */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M 290 105 Q 270 120, 290 135', fill: '#bae6fd', stroke: '#0284c7', 'stroke-width': 2 }));
|
||||
/* Lens */
|
||||
svg.appendChild(P8Helpers.svg.el('ellipse', { cx: 310, cy: 120, rx: 8, ry: 26, fill: 'rgba(125,211,252,.55)', stroke: '#0284c7', 'stroke-width': 1.5 }));
|
||||
/* Retina */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M 440 80 Q 470 120, 440 160', stroke: '#dc2626', 'stroke-width': 3, fill: 'none' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 465, y: 85, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', text: 'сетчатка' }));
|
||||
/* Rays */
|
||||
const focusX = mode === 'normal' ? 440 : (mode === 'myop' ? 420 : 480);
|
||||
const colorFocus = mode === 'normal' ? '#16a34a' : '#dc2626';
|
||||
/* 3 incoming rays */
|
||||
[80, 120, 160].forEach(y => {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: y, x2: 305, y2: y, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 315, y1: y, x2: focusX, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
});
|
||||
/* Focus point */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: focusX, cy: 120, r: 5, fill: colorFocus }));
|
||||
/* Correction lens if needed */
|
||||
if (mode === 'myop') {
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'diverging'));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#2563eb', 'text-anchor':'middle', text: '−дптр (рассеивающая)' }));
|
||||
} else if (mode === 'hyper') {
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(180, 120, 70, 'converging'));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 180, y: 200, 'font-family':"'Inter',sans-serif", 'font-size':10, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: '+дптр (собирающая)' }));
|
||||
}
|
||||
/* Label */
|
||||
const labels = { normal: 'Норма: фокус на сетчатке', myop: 'Близорукость: фокус перед сетчаткой', hyper: 'Дальнозоркость: фокус за сетчаткой' };
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 222, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: labels[mode] }));
|
||||
}
|
||||
document.getElementById('p40-iv6-normal').onclick = () => { mode = 'normal'; render(); };
|
||||
document.getElementById('p40-iv6-myop').onclick = () => { mode = 'myop'; render(); };
|
||||
document.getElementById('p40-iv6-hyper').onclick = () => { mode = 'hyper'; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP39_iv6(){
|
||||
const sb = document.getElementById('p39-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Incident white */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#fff', 'stroke-width': 5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: 120, x2: 200, y2: 120, stroke: '#facc15', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 30, y: 105, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'Белый свет' }));
|
||||
/* Prism */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '200,180 280,40 360,180', fill: 'rgba(125,211,252,.35)', stroke: '#0284c7', 'stroke-width': 2 }));
|
||||
/* Spectrum out */
|
||||
const colors = [
|
||||
{ c: '#dc2626', off: 0, label: 'красный' },
|
||||
{ c: '#f97316', off: 8, label: 'оранжевый' },
|
||||
{ c: '#facc15', off: 16, label: 'жёлтый' },
|
||||
{ c: '#16a34a', off: 24, label: 'зелёный' },
|
||||
{ c: '#0ea5e9', off: 32, label: 'голубой' },
|
||||
{ c: '#2563eb', off: 40, label: 'синий' },
|
||||
{ c: '#7c3aed', off: 48, label: 'фиолетовый' }
|
||||
];
|
||||
colors.forEach((cl, i) => {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 290, y1: 120, x2: 530, y2: 100 + cl.off, stroke: cl.c, 'stroke-width': 2.5, 'stroke-linecap': 'round' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 535, y: 104 + cl.off, 'font-family':"'Inter',sans-serif", 'font-size':9, 'font-weight':700, fill: cl.c, text: cl.label }));
|
||||
});
|
||||
}
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP38_iv6(){
|
||||
const sb = document.getElementById('p38-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 280);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let d = 180;
|
||||
const F = 100, lensX = 320, axisY = 150;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Axis */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: axisY, x2: 530, y2: axisY, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
|
||||
/* Lens */
|
||||
svg.appendChild(P8Helpers.optics.lensSVG(lensX, axisY, 140, 'converging'));
|
||||
/* F marks */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX - F, cy: axisY, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lensX + F, cy: axisY, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: lensX - F - 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: lensX + F + 8, y: axisY + 18, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', text: 'F' }));
|
||||
/* Object */
|
||||
const objX = lensX - d;
|
||||
const objH = 50;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: axisY, x2: objX, y2: axisY - objH, stroke: '#dc2626', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+','+(axisY-objH-6)+' '+(objX-5)+','+(axisY-objH+2)+' '+(objX+5)+','+(axisY-objH+2), fill: '#dc2626' }));
|
||||
/* Thin lens equation: 1/v - 1/(-d) = 1/F → v = dF/(d-F) (object on left, d>0) */
|
||||
const v = (d * F) / (d - F);
|
||||
const imgX = lensX + v;
|
||||
const imgH = objH * v / d;
|
||||
/* Three principal rays */
|
||||
/* Ray 1: parallel to axis, refracts through far F */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY - objH, { color: '#facc15', width: 1.5, arrow: false }));
|
||||
svg.appendChild(P8Helpers.optics.rayLine(lensX, axisY - objH, imgX, axisY + imgH, { color: '#facc15', width: 1.5, arrow: false }));
|
||||
/* Ray 2: through optic center */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, imgX, axisY + imgH, { color: '#16a34a', width: 1.5, arrow: false }));
|
||||
/* Ray 3: through near F, refracts parallel */
|
||||
svg.appendChild(P8Helpers.optics.rayLine(objX, axisY - objH, lensX, axisY + ((lensX - objX) / (lensX - F - objX)) * (-objH) - (-objH) * ((lensX - F - objX) / (lensX - F - objX) - 1), { color: '#2563eb', width: 1.5, arrow: false }));
|
||||
/* Image */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: axisY, x2: imgX, y2: axisY + imgH, stroke: '#2563eb', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(axisY+imgH+6)+' '+(imgX-5)+','+(axisY+imgH-2)+' '+(imgX+5)+','+(axisY+imgH-2), fill: '#2563eb' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 260, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
|
||||
}
|
||||
document.getElementById('p38-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p38-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP37_iv6(){
|
||||
const sb = document.getElementById('p37-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let alpha = 40;
|
||||
const n1 = 1, n2 = 1.33;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const cx = 280, cy = 120;
|
||||
/* Water region */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: 0, y: 120, width: 560, height: 120, fill: '#7dd3fc', opacity: 0.35 }));
|
||||
/* Interface */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 30, y1: cy, x2: 530, y2: cy, stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Normal */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 20, x2: cx, y2: 220, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '4 3' }));
|
||||
/* Incident */
|
||||
const aRad = alpha * Math.PI / 180;
|
||||
const len = 120;
|
||||
const inX = cx - len * Math.sin(aRad);
|
||||
const inY = cy - len * Math.cos(aRad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Snell: n1 sin α = n2 sin β */
|
||||
const beta = Math.asin(Math.min(1, n1 / n2 * Math.sin(aRad)));
|
||||
const rX = cx + len * Math.sin(beta);
|
||||
const rY = cy + len * Math.cos(beta);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Labels */
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 70, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 175, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+(beta * 180 / Math.PI).toFixed(1)+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 50, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'n₁=1 (воздух)' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 50, y: 215, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#0f172a', text: 'n₂=1.33 (вода)' }));
|
||||
}
|
||||
document.getElementById('p37-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p37-iv6-a-val').textContent = alpha; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP36_iv6(){
|
||||
const sb = document.getElementById('p36-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
const F = 100, mirX = 480;
|
||||
let d = 180;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Mirror curve */
|
||||
svg.appendChild(P8Helpers.svg.el('path', { d: 'M '+mirX+' 60 Q '+(mirX-30)+' 120, '+mirX+' 180', stroke: '#0f172a', 'stroke-width': 4, fill: 'none' }));
|
||||
/* Axis */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 50, y1: 120, x2: mirX, y2: 120, stroke: '#94a3b8', 'stroke-width': 1, 'stroke-dasharray': '4 3' }));
|
||||
/* Focus */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: mirX - F, cy: 120, r: 3, fill: '#16a34a' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: mirX - F, y: 138, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#16a34a', 'text-anchor':'middle', text: 'F' }));
|
||||
/* Object */
|
||||
const objX = mirX - d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 120, x2: objX, y2: 80, stroke: '#dc2626', 'stroke-width': 2.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',75 '+(objX-4)+',82 '+(objX+4)+',82', fill: '#dc2626' }));
|
||||
/* Lens formula: 1/v - 1/d = 1/F, here mirror equation: 1/v + 1/d = 1/F (using d positive in front) */
|
||||
const v = 1 / (1 / F - 1 / d);
|
||||
const imgX = mirX - v;
|
||||
const h_img = 40 * v / d * -1;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 120, x2: imgX, y2: 120 + h_img, stroke: '#2563eb', 'stroke-width': 2.5, 'stroke-dasharray': v < 0 ? '4 3' : '0' }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+','+(120 + h_img - Math.sign(h_img) * 4)+' '+(imgX-4)+','+(120 + h_img + 1)+' '+(imgX+4)+','+(120 + h_img + 1), fill: '#2563eb' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 280, y: 220, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'd='+d+' мм, F='+F+', v='+v.toFixed(0)+' мм' }));
|
||||
}
|
||||
document.getElementById('p36-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p36-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP35_iv6(){
|
||||
const sb = document.getElementById('p35-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let d = 100;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const mirX = 280;
|
||||
/* Mirror */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 40, x2: mirX, y2: 200, stroke: '#0f172a', 'stroke-width': 4 }));
|
||||
/* Hatch */
|
||||
for (let i = 0; i < 12; i++) {
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 45 + i * 14, x2: mirX + 8, y2: 49 + i * 14, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
}
|
||||
/* Object (arrow) */
|
||||
const objX = mirX - d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 180, x2: objX, y2: 90, stroke: '#dc2626', 'stroke-width': 3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: objX+',85 '+(objX-6)+',95 '+(objX+6)+',95', fill: '#dc2626' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: objX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#dc2626', 'text-anchor':'middle', text: 'объект' }));
|
||||
/* Virtual image */
|
||||
const imgX = mirX + d;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: imgX, y1: 180, x2: imgX, y2: 90, stroke: '#94a3b8', 'stroke-width': 3, 'stroke-dasharray': '4 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: imgX+',85 '+(imgX-6)+',95 '+(imgX+6)+',95', fill: '#94a3b8' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: imgX, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#94a3b8', 'text-anchor':'middle', text: 'мнимое изображение' }));
|
||||
/* Distance arrows */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: objX, y1: 60, x2: mirX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: mirX, y1: 60, x2: imgX, y2: 60, stroke: '#475569', 'stroke-width': 1.5 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: (objX + mirX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: (mirX + imgX) / 2, y: 52, 'font-family':"'JetBrains Mono',monospace", 'font-size':11, 'font-weight':700, fill:'#475569', 'text-anchor':'middle', text: 'd='+d }));
|
||||
}
|
||||
document.getElementById('p35-iv6-d').oninput = ev => { d = +ev.target.value; document.getElementById('p35-iv6-d-val').textContent = d; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP34_iv6(){
|
||||
const sb = document.getElementById('p34-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let alpha = 35;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
const cx = 280, cy = 200;
|
||||
/* Mirror */
|
||||
svg.appendChild(P8Helpers.optics.mirrorPlane(80, 200, 480, 200));
|
||||
/* Normal */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: cx, y1: 200, x2: cx, y2: 30, stroke: '#475569', 'stroke-width': 1.5, 'stroke-dasharray': '5 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 40, 'font-family':"'Inter',sans-serif", 'font-size':11, 'font-weight':700, fill:'#475569', text: 'нормаль' }));
|
||||
/* Incident ray */
|
||||
const rad = alpha * Math.PI / 180;
|
||||
const len = 150;
|
||||
const inX = cx - len * Math.sin(rad);
|
||||
const inY = cy - len * Math.cos(rad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(inX, inY, cx, cy, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Reflected ray */
|
||||
const rX = cx + len * Math.sin(rad);
|
||||
const rY = cy - len * Math.cos(rad);
|
||||
svg.appendChild(P8Helpers.optics.rayLine(cx, cy, rX, rY, { color: '#facc15', width: 3, glow: true }));
|
||||
/* Angle labels */
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx - 25, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#dc2626', text: 'α='+alpha+'°' }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: cx + 8, y: 130, 'font-family':"'JetBrains Mono',monospace", 'font-size':13, 'font-weight':800, fill:'#16a34a', text: 'β='+alpha+'°' }));
|
||||
}
|
||||
document.getElementById('p34-iv6-a').oninput = ev => { alpha = +ev.target.value; document.getElementById('p34-iv6-a-val').textContent = alpha; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP33_iv6(){
|
||||
const sb = document.getElementById('p33-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let lampX = 80;
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Light */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: lampX, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
/* Object */
|
||||
const objX = 300;
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: objX - 12, y: 90, width: 24, height: 60, fill: '#475569' }));
|
||||
/* Rays + shadow */
|
||||
const wallX = 510;
|
||||
const t = (wallX - lampX) / (objX - lampX);
|
||||
const yTop = 120 + (90 - 120) * t;
|
||||
const yBot = 120 + (150 - 120) * t;
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 110, x2: wallX, y2: yTop, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: lampX + 12, y1: 130, x2: wallX, y2: yBot, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
/* Wall */
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: wallX, y1: 20, x2: wallX, y2: 220, stroke: '#0f172a', 'stroke-width': 3 }));
|
||||
/* Shadow on wall */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: wallX, y: yTop, width: 28, height: yBot - yTop, fill: '#0f172a', opacity: 0.7 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: wallX + 14, y: yTop - 5, 'font-family':"'JetBrains Mono',monospace", 'font-size':10, 'font-weight':700, fill:'#0f172a', 'text-anchor':'middle', text: 'h='+(yBot-yTop).toFixed(0) }));
|
||||
}
|
||||
document.getElementById('p33-iv6-x').oninput = ev => { lampX = +ev.target.value; document.getElementById('p33-iv6-x-val').textContent = lampX; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initP32_iv6(){
|
||||
const sb = document.getElementById('p32-iv6-sandbox');
|
||||
if (!sb || !window.P8Helpers) return;
|
||||
|
||||
const svg = P8Helpers.svg.create(560, 240);
|
||||
svg.setAttribute('width','100%'); svg.setAttribute('height','100%'); svg.style.display='block';
|
||||
sb.appendChild(svg);
|
||||
let mode = 'point';
|
||||
function render(){
|
||||
svg.innerHTML = '';
|
||||
/* Object */
|
||||
svg.appendChild(P8Helpers.svg.el('rect', { x: 230, y: 90, width: 30, height: 60, fill: '#475569' }));
|
||||
if (mode === 'point') {
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 12, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 110, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 92, y1: 130, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 2, 'stroke-dasharray': '3 3' }));
|
||||
/* Sharp shadow */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 460,210 260,150', fill: '#0f172a', opacity: 0.7 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 380, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#fff', 'text-anchor':'middle', text: 'Чёткая тень' }));
|
||||
} else {
|
||||
/* Sun */
|
||||
svg.appendChild(P8Helpers.svg.el('circle', { cx: 80, cy: 120, r: 30, fill: '#facc15', stroke: '#0f172a', 'stroke-width': 2 }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 90, x2: 230, y2: 90, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
svg.appendChild(P8Helpers.svg.el('line', { x1: 110, y1: 150, x2: 230, y2: 150, stroke: '#facc15', 'stroke-width': 1.5, 'stroke-dasharray': '3 3' }));
|
||||
/* Sharp inner shadow (umbra) */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,100 380,80 380,160 260,140', fill: '#0f172a', opacity: 0.7 }));
|
||||
/* Penumbra */
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,90 460,30 380,80 260,100', fill: '#0f172a', opacity: 0.3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('polygon', { points: '260,150 380,160 460,210 260,150', fill: '#0f172a', opacity: 0.3 }));
|
||||
svg.appendChild(P8Helpers.svg.el('text', { x: 320, y: 220, 'font-family':"'Inter',sans-serif", 'font-size':12, 'font-weight':700, fill: '#0f172a', 'text-anchor':'middle', text: 'Тень + полутень' }));
|
||||
}
|
||||
}
|
||||
document.getElementById('p32-iv6-point').onclick = () => { mode = 'point'; render(); };
|
||||
document.getElementById('p32-iv6-ext').onclick = () => { mode = 'ext'; render(); };
|
||||
render();
|
||||
|
||||
}
|
||||
|
||||
function _initp39_iv5(){
|
||||
const TASKS = [{"q":"Из скольких основных цветов состоит спектр белого света (радуга)?","ans":7,"tol":0.1,"why":"$7$ цветов: красный, оранжевый, жёлтый, зелёный, голубой, синий, фиолетовый."},{"q":"У какого цвета света наибольшая длина волны: ($1$ — красный, $2$ — синий, $3$ — фиолетовый, $4$ — зелёный)?","ans":1,"tol":0.1,"why":"Красный свет имеет наибольшую длину волны ($\\sim 700$ нм) среди видимого спектра."},{"q":"У какого цвета света наименьшая длина волны: ($1$ — красный, $2$ — жёлтый, $3$ — зелёный, $4$ — фиолетовый)?","ans":4,"tol":0.1,"why":"Фиолетовый свет имеет наименьшую длину волны ($\\sim 400$ нм)."},{"q":"Линза с фокусом $F = 25$ см. Оптическая сила $D = 1/F$ (дптр). Найдите $D$ при $F$ в метрах.","ans":4,"tol":0.1,"why":"$F = 0{,}25$ м, $D = 1/F = 1/0{,}25 = 4$ дптр."},{"q":"У близорукого человека очки $-2$ дптр. Найдите фокусное расстояние линзы $F$ в м.","ans":-0.5,"tol":0.02,"why":"$F = 1/D = 1/(-2) = -0{,}5$ м (рассеивающая линза)."}];
|
||||
let i = 0, ok = 0, awarded = false;
|
||||
@@ -250,13 +598,24 @@ a{color:inherit;text-decoration:none}
|
||||
</head>
|
||||
<body class="p8-theme-spectrum">
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-row">
|
||||
<div>
|
||||
<h1>Физика 8 · Глава 3</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">
|
||||
<circle cx="50" cy="50" r="22" />
|
||||
<g><line x1="50" y1="8" x2="50" y2="22" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="50" y1="78" x2="50" y2="92" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="8" y1="50" x2="22" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="78" y1="50" x2="92" y2="50" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="20" y1="20" x2="30" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="70" y1="70" x2="80" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="80" y1="20" x2="70" y2="30" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="30" y1="70" x2="20" y2="80" stroke="currentColor" stroke-width="4" stroke-linecap="round"/></g>
|
||||
</svg></div>
|
||||
<div class="p8-hero-meter" id="p8-meter-ch3"><span id="p8-meter-val">λ=550</span> нм</div>
|
||||
<div class="p8-hero-inner">
|
||||
<div class="p8-hero-eyebrow">Глава 3 · 9 параграфов</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>
|
||||
@@ -287,15 +646,24 @@ a{color:inherit;text-decoration:none}
|
||||
<div id="psel-grid" class="psel-grid"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-p32" class="sec"><div class="sec-header"><span class="sec-num">§ 32</span><h2 class="sec-h">Источники света</h2></div><div id="p32-body"></div></section>
|
||||
<section id="sec-p33" class="sec"><div class="sec-header"><span class="sec-num">§ 33</span><h2 class="sec-h">Скорость света. Прямолинейное распространение света</h2></div><div id="p33-body"></div></section>
|
||||
<section id="sec-p34" class="sec"><div class="sec-header"><span class="sec-num">§ 34</span><h2 class="sec-h">Отражение света</h2></div><div id="p34-body"></div></section>
|
||||
<section id="sec-p35" class="sec"><div class="sec-header"><span class="sec-num">§ 35</span><h2 class="sec-h">Зеркала. Изображение в плоском зеркале</h2></div><div id="p35-body"></div></section>
|
||||
<section id="sec-p36" class="sec"><div class="sec-header"><span class="sec-num">§ 36</span><h2 class="sec-h">Преломление света</h2></div><div id="p36-body"></div></section>
|
||||
<section id="sec-p37" class="sec"><div class="sec-header"><span class="sec-num">§ 37</span><h2 class="sec-h">Линзы. Оптическая сила линзы</h2></div><div id="p37-body"></div></section>
|
||||
<section id="sec-p38" class="sec"><div class="sec-header"><span class="sec-num">§ 38</span><h2 class="sec-h">Построение изображений в тонких линзах</h2></div><div id="p38-body"></div></section>
|
||||
<section id="sec-p39" class="sec"><div class="sec-header"><span class="sec-num">§ 39</span><h2 class="sec-h">Глаз как оптическая система</h2></div><div id="p39-body"></div></section>
|
||||
<section id="sec-p40" class="sec"><div class="sec-header"><span class="sec-num">§ 40</span><h2 class="sec-h">Дефекты зрения. Очки</h2></div><div id="p40-body"></div></section>
|
||||
<section id="sec-p32" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p32" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="14" fill="currentColor"/><g stroke="currentColor" stroke-width="4" stroke-linecap="round"><line x1="50" y1="14" x2="50" y2="26"/><line x1="50" y1="74" x2="50" y2="86"/><line x1="14" y1="50" x2="26" y2="50"/><line x1="74" y1="50" x2="86" y2="50"/></g></svg></div><div class="sec-header"><span class="sec-num">§ 32</span><h2 class="sec-h">Источники света</h2></div><div id="p32-body"></div></section>
|
||||
<section id="sec-p33" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p33" aria-hidden="true"><svg viewBox="0 0 100 100"><circle cx="32" cy="40" r="10" fill="currentColor"/><rect x="50" y="34" width="14" height="40" fill="currentColor"/><polygon points="68,40 92,30 92,80 68,70" fill="currentColor" opacity="0.5"/></svg></div><div class="sec-header"><span class="sec-num">§ 33</span><h2 class="sec-h">Скорость света. Прямолинейное распространение света</h2></div><div id="p33-body"></div></section>
|
||||
<section id="sec-p34" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p34" aria-hidden="true"><svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="20" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="50" y2="90" stroke="currentColor" stroke-width="2" stroke-dasharray="4 4"/></svg></div><div class="sec-header"><span class="sec-num">§ 34</span><h2 class="sec-h">Отражение света</h2></div><div id="p34-body"></div></section>
|
||||
<section id="sec-p35" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p35" aria-hidden="true"><svg viewBox="0 0 100 100"><line x1="30" y1="20" x2="30" y2="80" stroke="currentColor" stroke-width="4"/><g stroke="currentColor" stroke-width="1.5"><line x1="30" y1="30" x2="22" y2="34"/><line x1="30" y1="45" x2="22" y2="49"/><line x1="30" y1="60" x2="22" y2="64"/><line x1="30" y1="75" x2="22" y2="79"/></g><circle cx="60" cy="50" r="6" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">§ 35</span><h2 class="sec-h">Зеркала. Изображение в плоском зеркале</h2></div><div id="p35-body"></div></section>
|
||||
<section id="sec-p36" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p36" aria-hidden="true"><svg viewBox="0 0 100 100"><path d="M30 20 Q 20 50, 30 80" stroke="currentColor" stroke-width="5" fill="none"/><line x1="48" y1="50" x2="70" y2="50" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3"/><circle cx="70" cy="50" r="3" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">§ 36</span><h2 class="sec-h">Преломление света</h2></div><div id="p36-body"></div></section>
|
||||
<section id="sec-p37" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p37" aria-hidden="true"><svg viewBox="0 0 100 100"><line x1="20" y1="20" x2="50" y2="50" stroke="currentColor" stroke-width="5"/><line x1="50" y1="50" x2="80" y2="80" stroke="currentColor" stroke-width="5" stroke-dasharray="0"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/></svg></div><div class="sec-header"><span class="sec-num">§ 37</span><h2 class="sec-h">Линзы. Оптическая сила линзы</h2></div><div id="p37-body"></div></section>
|
||||
<section id="sec-p38" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p38" aria-hidden="true"><svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="10" ry="36" fill="currentColor" opacity="0.4"/><line x1="0" y1="50" x2="100" y2="50" stroke="currentColor" stroke-width="2"/><circle cx="25" cy="50" r="2" fill="currentColor"/><circle cx="75" cy="50" r="2" fill="currentColor"/></svg></div><div class="sec-header"><span class="sec-num">§ 38</span><h2 class="sec-h">Построение изображений в тонких линзах</h2></div><div id="p38-body"></div></section>
|
||||
<section id="sec-p39" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p39" aria-hidden="true"><svg viewBox="0 0 100 100"><polygon points="40,20 80,50 40,80" stroke="currentColor" stroke-width="4" fill="none"/><line x1="20" y1="50" x2="40" y2="50" stroke="currentColor" stroke-width="3"/><g stroke-width="2.5" fill="none"><line x1="60" y1="40" x2="90" y2="30" stroke="#dc2626"/><line x1="60" y1="50" x2="90" y2="50" stroke="#16a34a"/><line x1="60" y1="60" x2="90" y2="70" stroke="#2563eb"/></g></svg></div><div class="sec-header"><span class="sec-num">§ 39</span><h2 class="sec-h">Глаз как оптическая система</h2></div><div id="p39-body"></div></section>
|
||||
<section id="sec-p40" class="sec">
|
||||
<div class="p8-sec-wm" id="p8-sec-wm-p40" aria-hidden="true"><svg viewBox="0 0 100 100"><ellipse cx="50" cy="50" rx="36" ry="22" fill="none" stroke="currentColor" stroke-width="3"/><circle cx="50" cy="50" r="12" fill="currentColor"/><circle cx="50" cy="50" r="5" fill="#fff"/></svg></div><div class="sec-header"><span class="sec-num">§ 40</span><h2 class="sec-h">Дефекты зрения. Очки</h2></div><div id="p40-body"></div></section>
|
||||
<section id="sec-final3" class="sec"><div class="sec-header"><span class="sec-num">★</span><h2 class="sec-h">Финал главы</h2></div><div id="final3-body"></div></section>
|
||||
|
||||
</div>
|
||||
@@ -785,8 +1153,17 @@ function build_p32(){
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p32-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p32-tasks5-ok">0</b></span></div>'
|
||||
+'</div>';
|
||||
|
||||
/* IV6 — Точечные и протяжённые источники (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Точечные и протяжённые источники</div></div>'
|
||||
+'<div class="wg-help">Точечный источник (свеча издалека) даёт чёткие тени. Протяжённый (Солнце) — размытые с полутенью.</div>'
|
||||
+'<div class="p8-sandbox" id="p32-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p32-iv6-point">Точечный</button><button class="btn" id="p32-iv6-ext">Протяжённый</button></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p32') + readButton('p32');
|
||||
renderMath(box); wireReadBtn('p32');
|
||||
_initP32_iv6();
|
||||
_initp32_iv5();
|
||||
_p32_quiz1(); _p32_quiz2(); _p32_dnd(); _p32_mcq();
|
||||
}
|
||||
@@ -943,8 +1320,17 @@ function build_p33(){
|
||||
+'<div class="wg-help">$c = 3 \\cdot 10^8$ м/с. 4+ — +15 XP.</div>'
|
||||
+'<div id="p33-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p33-task-i">1</b>/5</span><span>Правильно: <b id="p33-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Тень и её размер (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Тень и её размер</div></div>'
|
||||
+'<div class="wg-help">Двигай источник света — наблюдай, как меняется размер тени. Чем ближе источник — тем больше тень.</div>'
|
||||
+'<div class="p8-sandbox" id="p33-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Источник X</span><input type="range" id="p33-iv6-x" min="20" max="200" step="2" value="80"><span class="p8-scrubber-value"><span id="p33-iv6-x-val">80</span><span class="p8-unit">px</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p33') + readButton('p33');
|
||||
renderMath(box); wireReadBtn('p33');
|
||||
_initP33_iv6();
|
||||
_p33_shadow(); _p33_calc(); _p33_dnd(); _p33_tasks();
|
||||
}
|
||||
function _p33_shadow(){
|
||||
@@ -1119,8 +1505,17 @@ function build_p34(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p34-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p34-task-i">1</b>/5</span><span>Правильно: <b id="p34-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Закон отражения (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Закон отражения</div></div>'
|
||||
+'<div class="wg-help">Двигай угол падения — угол отражения равен ему.</div>'
|
||||
+'<div class="p8-sandbox" id="p34-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p34-iv6-a" min="0" max="80" step="1" value="35"><span class="p8-scrubber-value"><span id="p34-iv6-a-val">35</span><span class="p8-unit">°</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p34') + readButton('p34');
|
||||
renderMath(box); wireReadBtn('p34');
|
||||
_initP34_iv6();
|
||||
_p34_ref(); _p34_quiz(); _p34_dnd(); _p34_tasks();
|
||||
}
|
||||
function _p34_ref(){
|
||||
@@ -1250,8 +1645,17 @@ function build_p35(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p35-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p35-task-i">1</b>/5</span><span>Правильно: <b id="p35-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Плоское зеркало (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Плоское зеркало</div></div>'
|
||||
+'<div class="wg-help">Двигай объект — мнимое изображение появляется за зеркалом на том же расстоянии.</div>'
|
||||
+'<div class="p8-sandbox" id="p35-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p35-iv6-d" min="50" max="200" step="2" value="100"><span class="p8-scrubber-value"><span id="p35-iv6-d-val">100</span><span class="p8-unit">px</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p35') + readButton('p35');
|
||||
renderMath(box); wireReadBtn('p35');
|
||||
_initP35_iv6();
|
||||
_p35_mir(); _p35_quiz(); _p35_dnd(); _p35_tasks();
|
||||
}
|
||||
function _p35_mir(){
|
||||
@@ -1409,8 +1813,17 @@ function build_p36(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p36-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p36-task-i">1</b>/5</span><span>Правильно: <b id="p36-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Сферическое зеркало (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Сферическое зеркало</div></div>'
|
||||
+'<div class="wg-help">Двигай расстояние объекта до фокуса — изображение меняет тип (увеличенное/уменьшенное, прямое/перевёрнутое).</div>'
|
||||
+'<div class="p8-sandbox" id="p36-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Объект → зеркало</span><input type="range" id="p36-iv6-d" min="50" max="250" step="2" value="180"><span class="p8-scrubber-value"><span id="p36-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p36') + readButton('p36');
|
||||
renderMath(box); wireReadBtn('p36');
|
||||
_initP36_iv6();
|
||||
_p36_snell(); _p36_quiz(); _p36_dnd(); _p36_tasks();
|
||||
}
|
||||
function _p36_snell(){
|
||||
@@ -1574,8 +1987,17 @@ function build_p37(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p37-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p37-task-i">1</b>/5</span><span>Правильно: <b id="p37-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Преломление света (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Преломление света</div></div>'
|
||||
+'<div class="wg-help">Двигай угол падения. На границе двух сред (воздух/вода n=1.33) угол преломления меньше.</div>'
|
||||
+'<div class="p8-sandbox" id="p37-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Угол α</span><input type="range" id="p37-iv6-a" min="0" max="80" step="1" value="40"><span class="p8-scrubber-value"><span id="p37-iv6-a-val">40</span><span class="p8-unit">°</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p37') + readButton('p37');
|
||||
renderMath(box); wireReadBtn('p37');
|
||||
_initP37_iv6();
|
||||
_p37_lens(); _p37_calc(); _p37_dnd(); _p37_tasks();
|
||||
}
|
||||
function _p37_lens(){
|
||||
@@ -1710,8 +2132,17 @@ function build_p38(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p38-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p38-task-i">1</b>/5</span><span>Правильно: <b id="p38-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Собирающая линза — построение изображения (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Собирающая линза — построение изображения</div></div>'
|
||||
+'<div class="wg-help">Двигай объект. Три главных луча: через центр (прямо), параллельно главной оси (через F), через F (параллельно оси).</div>'
|
||||
+'<div class="p8-sandbox" id="p38-iv6-sandbox" style="height:280px"></div>'
|
||||
+'<div class="p8-scrubber" style="margin-top:10px"><span class="p8-scrubber-label">Дистанция d</span><input type="range" id="p38-iv6-d" min="50" max="280" step="2" value="180"><span class="p8-scrubber-value"><span id="p38-iv6-d-val">180</span><span class="p8-unit">мм</span></span></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p38') + readButton('p38');
|
||||
renderMath(box); wireReadBtn('p38');
|
||||
_initP38_iv6();
|
||||
_p38_constr(); _p38_quiz(); _p38_dnd(); _p38_tasks();
|
||||
}
|
||||
function _p38_constr(){
|
||||
@@ -1897,8 +2328,17 @@ function build_p39(){
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p39-tasks5-i">1</b> / 5</span><span>Правильно: <b id="p39-tasks5-ok">0</b></span></div>'
|
||||
+'</div>';
|
||||
|
||||
/* IV6 — Дисперсия — разложение белого света (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Дисперсия — разложение белого света</div></div>'
|
||||
+'<div class="wg-help">Через призму белый свет разлагается на спектр. Угол отклонения зависит от длины волны: красный отклоняется меньше, фиолетовый больше.</div>'
|
||||
+'<div class="p8-sandbox" id="p39-iv6-sandbox" style="height:240px"></div>'
|
||||
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p39') + readButton('p39');
|
||||
renderMath(box); wireReadBtn('p39');
|
||||
_initP39_iv6();
|
||||
_initp39_iv5();
|
||||
_p39_eye(); _p39_quiz(); _p39_dnd(); _p39_mcq();
|
||||
}
|
||||
@@ -2042,8 +2482,17 @@ function build_p40(){
|
||||
+'<div class="wg-help">4+ — +15 XP.</div>'
|
||||
+'<div id="p40-task"></div>'
|
||||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="p40-task-i">1</b>/5</span><span>Правильно: <b id="p40-task-ok">0</b></span></div></div>';
|
||||
/* IV6 — Глаз: аккомодация и очки (Phase 3) */
|
||||
h += '<div class="wg p8-iv6">'
|
||||
+'<div class="wg-header"><span class="wg-badge p8-badge p8-badge-spectrum">IV-6</span><div class="wg-title">Глаз: аккомодация и очки</div></div>'
|
||||
+'<div class="wg-help">Нормальный глаз: лучи фокусируются на сетчатке. Близорукий — перед сетчаткой (нужна рассеивающая). Дальнозоркий — за сетчаткой (нужна собирающая).</div>'
|
||||
+'<div class="p8-sandbox" id="p40-iv6-sandbox" style="height:240px"></div>'
|
||||
+'<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap"><button class="btn primary" id="p40-iv6-normal">Норма</button><button class="btn" id="p40-iv6-myop">Близорукость</button><button class="btn" id="p40-iv6-hyper">Дальнозоркость</button></div>'
|
||||
+'</div>';
|
||||
|
||||
box.innerHTML = h + secNavFor('p40') + readButton('p40');
|
||||
renderMath(box); wireReadBtn('p40');
|
||||
_initP40_iv6();
|
||||
_p40_glass(); _p40_quiz(); _p40_dnd(); _p40_tasks();
|
||||
}
|
||||
function _p40_glass(){
|
||||
@@ -2286,5 +2735,30 @@ document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
/* P8 hero meter — анимация длины волны (Phase 3 spectrum) */
|
||||
(function(){
|
||||
function init(){
|
||||
const el = document.getElementById('p8-meter-val');
|
||||
if (!el || !window.P8Anim) return;
|
||||
const targets = [{ l: 400, c:'#7c3aed' }, { l: 470, c:'#2563eb' }, { l: 550, c:'#16a34a' }, { l: 600, c:'#f59e0b' }, { l: 700, c:'#dc2626' }];
|
||||
let i = 0;
|
||||
function step(){
|
||||
const from = parseFloat((el.textContent || '550').replace(/\D/g,'')) || 550;
|
||||
const target = targets[i % targets.length];
|
||||
P8Anim.tween({
|
||||
from, to: target.l, duration: 1100, easing: 'cubicInOut',
|
||||
onUpdate: v => { el.textContent = 'λ=' + Math.round(v); el.style.color = target.c; },
|
||||
onComplete: () => { i++; setTimeout(step, 1400); }
|
||||
});
|
||||
}
|
||||
setTimeout(step, 1200);
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
else init();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user