0888a707cc
- подключён _registry.js в lab.html (был отсутствует -> LabRegistry был undefined) - регистрация 3 пилотов в _pilots.js (graph/quadratic/pendulum), подключён последним - loadTheory (lab-glue.js) адаптирован: реестр в приоритете, иначе THEORY Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1182 lines
83 KiB
JavaScript
1182 lines
83 KiB
JavaScript
'use strict';
|
||
const { user, isTeacher, isAdmin } = LS.initPage();
|
||
window._simQuizAllowed = true; // default; overridden after permission fetch for students
|
||
LS.showBoardIfAllowed();
|
||
|
||
|
||
/* ════════════════════════════════
|
||
SIM CATALOGUE (defined after P_* consts below)
|
||
════════════════════════════════ */
|
||
|
||
let _catFilter = 'all';
|
||
var _disabledSimIds = new Set();
|
||
let _simModuleDisabled = false;
|
||
|
||
function filterSims(cat, btn) {
|
||
_catFilter = cat;
|
||
document.querySelectorAll('.lab-filter').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
renderSims();
|
||
}
|
||
|
||
function renderSims() {
|
||
// Контент-движок: мёрж код-реестра поверх legacy SIMS.
|
||
// Порядок берём из SIMS; для мигрированных id используем манифест реестра;
|
||
// registry-only записи добавляем в конец.
|
||
const _reg = (window.LabRegistry ? window.LabRegistry.all() : []);
|
||
const _regById = {};
|
||
_reg.forEach(m => { _regById[m.id] = m; });
|
||
const _seen = {};
|
||
const _merged = [];
|
||
SIMS.forEach(s => {
|
||
_merged.push(s.id && _regById[s.id] ? _regById[s.id] : s);
|
||
if (s.id) _seen[s.id] = 1;
|
||
});
|
||
_reg.forEach(m => { if (!_seen[m.id]) _merged.push(m); });
|
||
|
||
const base = _catFilter === 'all' ? _merged : _merged.filter(s => s.cat === _catFilter);
|
||
const list = base.filter(s => !s.id || !_disabledSimIds.has(s.id));
|
||
document.getElementById('sim-grid').innerHTML = list.map(s => `
|
||
<div class="sim-card ${s.id ? '' : 'soon'}" ${s.id ? `onclick="openSim('${s.id}')"` : ''}>
|
||
${window.LabRegistry ? window.LabRegistry.resolvePreview(s) : s.preview}
|
||
<div class="sim-body">
|
||
<div class="sim-cat ${s.cat}">${s.cat === 'math' ? '∑ Математика' : s.cat === 'chem' ? '<svg class="ic" viewBox="0 0 24 24"><path d="M9 3h6m-4.5 0v5.5l-4 7.5a1 1 0 0 0 .9 1.5h8.2a1 1 0 0 0 .9-1.5l-4-7.5V3"/></svg> Химия' : s.cat === 'bio' ? '<svg class="ic" viewBox="0 0 24 24"><path d="M2 15c6.667-6 13.333 0 20-6"/><path d="M9 22c1.798-2 2.518-4 2.807-6"/><path d="M15 2c-1.798 2-2.518 4-2.807 6"/><path d="m17 6-2.5-2.5M14 8 13 7M7 18l2.5 2.5M3.5 14.5l.5.5M20 9l.5.5M6.5 12.5l1 1M16.5 10.5l1 1M10 16l1.5 1.5"/></svg> Биология' : s.cat === 'game' ? '<svg class="ic" viewBox="0 0 24 24"><line x1="6" y1="12" x2="10" y2="12"/><line x1="8" y1="10" x2="8" y2="14"/><line x1="15" y1="13" x2="15.01" y2="13"/><line x1="18" y1="11" x2="18.01" y2="11"/><rect x="2" y="6" width="20" height="12" rx="2"/></svg> Игры' : LS.icon('zap',14) + ' Физика'}</div>
|
||
<div class="sim-title">${s.title}</div>
|
||
<div class="sim-desc">${s.desc}</div>
|
||
</div>
|
||
${!s.id ? '<div class="sim-soon-badge">Скоро</div>' : ''}
|
||
</div>`).join('');
|
||
if (window.lucide) lucide.createIcons();
|
||
}
|
||
|
||
/* ════════════════════════════════
|
||
CARD PREVIEW SVGs
|
||
════════════════════════════════ */
|
||
function _grid(fg='rgba(255,255,255,0.06)') {
|
||
return `<g stroke="${fg}" stroke-width="1">
|
||
<line x1="45" y1="0" x2="45" y2="140"/><line x1="90" y1="0" x2="90" y2="140"/>
|
||
<line x1="135" y1="0" x2="135" y2="140"/><line x1="180" y1="0" x2="180" y2="140"/>
|
||
<line x1="225" y1="0" x2="225" y2="140"/>
|
||
<line x1="0" y1="35" x2="270" y2="35"/><line x1="0" y1="70" x2="270" y2="70"/>
|
||
<line x1="0" y1="105" x2="270" y2="105"/>
|
||
</g>`;
|
||
}
|
||
function _axes() {
|
||
return `<line x1="0" y1="70" x2="262" y2="70" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
|
||
<line x1="135" y1="140" x2="135" y2="6" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
|
||
<polygon points="265,70 258,67 258,73" fill="rgba(255,255,255,0.32)"/>
|
||
<polygon points="135,4 132,11 138,11" fill="rgba(255,255,255,0.32)"/>`;
|
||
}
|
||
function _svg(body) {
|
||
return `<svg class="sim-preview" viewBox="0 0 270 140" xmlns="http://www.w3.org/2000/svg">
|
||
<rect width="270" height="140" fill="#0D0D1A"/>${body}</svg>`;
|
||
}
|
||
|
||
/* 1 — Graph */
|
||
const P_GRAPH = _svg(`${_grid()}${_axes()}
|
||
<path d="M 15,132 Q 135,20 255,132" stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
|
||
<path d="M 0,70 C 34,30 56,30 90,70 C 124,110 146,110 180,70 C 214,30 236,30 270,70"
|
||
stroke="#06D6E0" stroke-width="2" fill="none" opacity="0.75"/>`);
|
||
|
||
/* 2 — Transform: three shifted/scaled sines */
|
||
const P_TRANSFORM = _svg(`${_grid()}${_axes()}
|
||
<path d="M 0,70 C 34,30 56,30 90,70 C 124,110 146,110 180,70 C 214,30 236,30 270,70"
|
||
stroke="#9B5DE5" stroke-width="2" fill="none" opacity="0.9"/>
|
||
<path d="M 0,53 C 22,24 42,24 67,53 C 92,82 112,82 135,53 C 158,24 178,24 202,53 C 227,82 248,82 270,53"
|
||
stroke="#06D6E0" stroke-width="2" fill="none" opacity="0.65"/>
|
||
<path d="M 0,85 C 45,36 80,36 135,85 C 190,134 225,134 270,85"
|
||
stroke="#F15BB5" stroke-width="2" fill="none" opacity="0.55"/>`);
|
||
|
||
/* 3 — Triangle geometry */
|
||
const P_TRIANGLE = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<polygon points="60,115 210,115 135,25" fill="rgba(155,93,229,0.1)" stroke="#9B5DE5" stroke-width="2"/>
|
||
<line x1="60" y1="115" x2="173" y2="70" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
|
||
<line x1="210" y1="115" x2="98" y2="70" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
|
||
<line x1="135" y1="25" x2="135" y2="115" stroke="rgba(6,214,224,0.5)" stroke-width="1.3" stroke-dasharray="4,3"/>
|
||
<circle cx="135" cy="78" r="3" fill="#06D6E0"/>
|
||
<rect x="131" y="111" width="8" height="8" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="1.2"/>`);
|
||
|
||
/* 4 — Inscribed/circumscribed circles */
|
||
const P_CIRCLES = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<polygon points="80,118 190,118 135,22" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<circle cx="135" cy="85" r="33" fill="none" stroke="#06D6E0" stroke-width="1.8" stroke-dasharray="5,3" opacity="0.7"/>
|
||
<circle cx="135" cy="55" r="52" fill="none" stroke="#F15BB5" stroke-width="1.5" stroke-dasharray="5,3" opacity="0.5"/>
|
||
<circle cx="135" cy="85" r="3" fill="#06D6E0" opacity="0.8"/>
|
||
<circle cx="135" cy="55" r="3" fill="#F15BB5" opacity="0.8"/>`);
|
||
|
||
/* 5 — Quadratic roots: parabola crossing x-axis */
|
||
const P_QUADRATIC = _svg(`${_grid()}${_axes()}
|
||
<path d="M 55,125 Q 135,8 215,125" stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
|
||
<circle cx="85" cy="70" r="5" fill="#F15BB5" stroke="#fff" stroke-width="1.5"/>
|
||
<circle cx="185" cy="70" r="5" fill="#F15BB5" stroke="#fff" stroke-width="1.5"/>
|
||
<line x1="85" y1="68" x2="85" y2="125" stroke="rgba(241,91,181,0.35)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<line x1="185" y1="68" x2="185" y2="125" stroke="rgba(241,91,181,0.35)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">D = b²− 4ac</text>`);
|
||
|
||
/* 6 — 3D geometry: isometric cube */
|
||
const P_3D = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<polygon points="135,20 210,58 210,115 135,77" fill="rgba(155,93,229,0.15)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<polygon points="135,20 60,58 60,115 135,77" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<polygon points="60,58 135,20 210,58 135,96" fill="rgba(155,93,229,0.22)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<line x1="135" y1="77" x2="135" y2="96" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">V = a³</text>`);
|
||
|
||
/* 7 — Probability: histogram bars */
|
||
const P_PROB = _svg(`${_grid()}
|
||
<line x1="30" y1="15" x2="30" y2="118" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
|
||
<line x1="28" y1="118" x2="255" y2="118" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
|
||
<rect x="38" y="90" width="24" height="28" fill="rgba(155,93,229,0.6)" rx="2"/>
|
||
<rect x="68" y="72" width="24" height="46" fill="rgba(155,93,229,0.7)" rx="2"/>
|
||
<rect x="98" y="44" width="24" height="74" fill="rgba(155,93,229,0.85)" rx="2"/>
|
||
<rect x="128" y="32" width="24" height="86" fill="#9B5DE5" rx="2"/>
|
||
<rect x="158" y="50" width="24" height="68" fill="rgba(155,93,229,0.8)" rx="2"/>
|
||
<rect x="188" y="76" width="24" height="42" fill="rgba(155,93,229,0.65)" rx="2"/>
|
||
<rect x="218" y="96" width="24" height="22" fill="rgba(155,93,229,0.5)" rx="2"/>`);
|
||
|
||
/* 8 — Normal distribution: bell curve */
|
||
const P_NORMAL = _svg(`${_grid()}
|
||
<line x1="10" y1="118" x2="260" y2="118" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
|
||
<path d="M 10,116 C 50,115 80,110 100,90 C 115,72 125,35 135,22 C 145,35 155,72 170,90 C 190,110 220,115 260,116"
|
||
stroke="#9B5DE5" stroke-width="2.5" fill="none"/>
|
||
<path d="M 100,90 C 115,72 125,35 135,22 C 145,35 155,72 170,90 L 170,118 L 100,118 Z"
|
||
fill="rgba(155,93,229,0.15)"/>
|
||
<line x1="135" y1="22" x2="135" y2="118" stroke="rgba(255,255,255,0.2)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">μ = 0, σ = 1</text>`);
|
||
|
||
/* 8b — Trig circle */
|
||
const P_TRIGCIRCLE = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="30" y1="70" x2="240" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2"/>
|
||
<line x1="135" y1="8" x2="135" y2="132" stroke="rgba(255,255,255,0.25)" stroke-width="1.2"/>
|
||
<circle cx="135" cy="70" r="52" fill="none" stroke="rgba(255,255,255,0.18)" stroke-width="1.8"/>
|
||
<line x1="135" y1="70" x2="172" y2="33" stroke="rgba(255,255,255,0.45)" stroke-width="1.3"/>
|
||
<line x1="135" y1="70" x2="172" y2="70" stroke="#06D6E0" stroke-width="2.5"/>
|
||
<line x1="172" y1="70" x2="172" y2="33" stroke="#EF476F" stroke-width="2.5"/>
|
||
<circle cx="172" cy="33" r="5" fill="#9B5DE5"/>
|
||
<path d="M 148,70 A 13,13 0 0,0 144,60" stroke="rgba(155,93,229,0.6)" stroke-width="1.5" fill="none"/>
|
||
<text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">sin · cos · tg · ctg</text>`);
|
||
|
||
/* 9 — Projectile motion */
|
||
const P_PROJECTILE = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="15" y1="118" x2="255" y2="118" stroke="rgba(255,255,255,0.32)" stroke-width="1.5"/>
|
||
<path d="M 20,118 Q 135,18 250,118" stroke="#06D6E0" stroke-width="2.5" fill="none"/>
|
||
<circle cx="20" cy="118" r="5" fill="#06D6E0"/>
|
||
<line x1="20" y1="118" x2="52" y2="80" stroke="rgba(6,214,224,0.8)" stroke-width="1.5"
|
||
marker-end="url(#arr)"/>
|
||
<defs><marker id="arr" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
|
||
<path d="M0,0 L6,3 L0,6 Z" fill="#06D6E0"/>
|
||
</marker></defs>
|
||
<line x1="135" y1="18" x2="135" y2="118" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">x = v₀cos(α)·t</text>`);
|
||
|
||
/* 10 — Pendulum */
|
||
const P_PENDULUM = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="135" y1="15" x2="165" y2="95" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||
<circle cx="165" cy="100" r="12" fill="rgba(6,214,224,0.25)" stroke="#06D6E0" stroke-width="2"/>
|
||
<line x1="135" y1="15" x2="95" y2="95" stroke="rgba(255,255,255,0.2)" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||
<circle cx="95" cy="100" r="12" fill="none" stroke="rgba(6,214,224,0.3)" stroke-width="1.5" stroke-dasharray="3,3"/>
|
||
<path d="M 110,60 A 55,55 0 0 1 160,60" fill="none" stroke="rgba(6,214,224,0.4)" stroke-width="1.2" stroke-dasharray="3,3"/>
|
||
<circle cx="135" cy="15" r="4" fill="rgba(255,255,255,0.5)"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">T = 2π√(l/g)</text>`);
|
||
|
||
/* 11 — Collision */
|
||
const P_COLLISION = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="15" y1="70" x2="255" y2="70" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
|
||
<circle cx="70" cy="70" r="28" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="2"/>
|
||
<text x="70" y="75" font-size="11" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₁</text>
|
||
<line x1="100" y1="70" x2="120" y2="70" stroke="#06D6E0" stroke-width="2" marker-end="url(#a2)"/>
|
||
<circle cx="195" cy="70" r="20" fill="rgba(241,91,181,0.15)" stroke="#F15BB5" stroke-width="2"/>
|
||
<text x="195" y="75" font-size="11" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₂</text>
|
||
<line x1="175" y1="70" x2="155" y2="70" stroke="#F15BB5" stroke-width="2" marker-end="url(#a3)"/>
|
||
<defs>
|
||
<marker id="a2" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#06D6E0"/></marker>
|
||
<marker id="a3" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#F15BB5"/></marker>
|
||
</defs>`);
|
||
|
||
|
||
/* 13 — Electric circuit */
|
||
const P_CIRCUIT = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<rect x="30" y="25" width="210" height="90" fill="none" stroke="rgba(255,255,255,0.25)" stroke-width="1.5" rx="4"/>
|
||
<line x1="30" y1="70" x2="70" y2="70" stroke="#06D6E0" stroke-width="2"/>
|
||
<rect x="70" y="58" width="36" height="24" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="1.8" rx="3"/>
|
||
<text x="88" y="74" font-size="10" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">R₁</text>
|
||
<line x1="106" y1="70" x2="130" y2="70" stroke="#06D6E0" stroke-width="2"/>
|
||
<rect x="130" y="58" width="36" height="24" fill="rgba(6,214,224,0.15)" stroke="#06D6E0" stroke-width="1.8" rx="3"/>
|
||
<text x="148" y="74" font-size="10" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">R₂</text>
|
||
<line x1="166" y1="70" x2="190" y2="70" stroke="#06D6E0" stroke-width="2"/>
|
||
<rect x="190" y="56" width="18" height="28" fill="rgba(241,91,181,0.15)" stroke="#F15BB5" stroke-width="1.8" rx="3"/>
|
||
<line x1="208" y1="70" x2="240" y2="70" stroke="#06D6E0" stroke-width="2"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">I = U/R</text>`);
|
||
|
||
/* 14 — Magnetic field */
|
||
const P_MAGNETIC = _svg(`
|
||
<rect width="270" height="140" fill="#05050F"/>
|
||
${_grid('rgba(155,93,229,0.06)')}
|
||
<defs>
|
||
<radialGradient id="mg1" cx="38%" cy="50%"><stop offset="0%" stop-color="#06D6E0" stop-opacity=".55"/><stop offset="100%" stop-color="#06D6E0" stop-opacity="0"/></radialGradient>
|
||
<radialGradient id="mg2" cx="62%" cy="50%"><stop offset="0%" stop-color="#F15BB5" stop-opacity=".55"/><stop offset="100%" stop-color="#F15BB5" stop-opacity="0"/></radialGradient>
|
||
</defs>
|
||
<rect width="270" height="140" fill="url(#mg1)" opacity=".7"/>
|
||
<rect width="270" height="140" fill="url(#mg2)" opacity=".7"/>
|
||
<ellipse cx="95" cy="70" rx="45" ry="45" fill="none" stroke="#06D6E0" stroke-width="1.4" stroke-dasharray="5,3" opacity=".6"/>
|
||
<ellipse cx="95" cy="70" rx="70" ry="60" fill="none" stroke="#06D6E0" stroke-width="1" stroke-dasharray="4,4" opacity=".3"/>
|
||
<ellipse cx="175" cy="70" rx="45" ry="45" fill="none" stroke="#F15BB5" stroke-width="1.4" stroke-dasharray="5,3" opacity=".6"/>
|
||
<ellipse cx="175" cy="70" rx="70" ry="60" fill="none" stroke="#F15BB5" stroke-width="1" stroke-dasharray="4,4" opacity=".3"/>
|
||
<path d="M95,30 C160,30 110,70 175,70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2" fill="none"/>
|
||
<path d="M95,110 C160,110 110,70 175,70" stroke="rgba(255,255,255,0.25)" stroke-width="1.2" fill="none"/>
|
||
<circle cx="95" cy="70" r="11" fill="rgba(5,5,20,0.9)" stroke="#06D6E0" stroke-width="2.2"/>
|
||
<circle cx="95" cy="70" r="4" fill="#06D6E0"/>
|
||
<circle cx="175" cy="70" r="11" fill="rgba(5,5,20,0.9)" stroke="#F15BB5" stroke-width="2.2"/>
|
||
<line x1="170" y1="65" x2="180" y2="75" stroke="#F15BB5" stroke-width="2"/>
|
||
<line x1="180" y1="65" x2="170" y2="75" stroke="#F15BB5" stroke-width="2"/>
|
||
<text x="135" y="132" font-size="9" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">B = μ₀I / 2πr</text>`);
|
||
|
||
/* 14 — Electric field lines */
|
||
const P_FIELD = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<circle cx="135" cy="70" r="10" fill="rgba(155,93,229,0.3)" stroke="#9B5DE5" stroke-width="2"/>
|
||
<text x="135" y="74" font-size="10" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="800">+</text>
|
||
<g stroke="#9B5DE5" stroke-width="1.3" fill="none" opacity="0.6">
|
||
<path d="M135,60 L135,20"/><path d="M135,80 L135,120"/>
|
||
<path d="M125,63 L95,38"/><path d="M145,63 L175,38"/>
|
||
<path d="M125,77 L95,102"/><path d="M145,77 L175,102"/>
|
||
<path d="M122,70 L80,70"/><path d="M148,70 L190,70"/>
|
||
<path d="M125,64 L102,42"/><path d="M145,64 L168,42"/>
|
||
<path d="M125,76 L102,98"/><path d="M145,76 L168,98"/>
|
||
</g>
|
||
<circle cx="135" cy="20" r="2" fill="#9B5DE5" opacity="0.5"/>
|
||
<circle cx="135" cy="120" r="2" fill="#9B5DE5" opacity="0.5"/>
|
||
<circle cx="80" cy="70" r="2" fill="#9B5DE5" opacity="0.5"/>
|
||
<circle cx="190" cy="70" r="2" fill="#9B5DE5" opacity="0.5"/>`);
|
||
|
||
/* 15 — Thin lens */
|
||
const P_LENS = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="10" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||
<path d="M 135,20 Q 155,70 135,120 Q 115,70 135,20" fill="rgba(6,214,224,0.12)" stroke="#06D6E0" stroke-width="2"/>
|
||
<line x1="30" y1="45" x2="135" y2="45" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<line x1="135" y1="45" x2="230" y2="90" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<line x1="30" y1="70" x2="230" y2="70" stroke="#06D6E0" stroke-width="1.5" stroke-dasharray="3,3" opacity="0.5"/>
|
||
<line x1="30" y1="95" x2="135" y2="95" stroke="#F15BB5" stroke-width="1.8"/>
|
||
<line x1="135" y1="95" x2="230" y2="55" stroke="#F15BB5" stroke-width="1.8"/>
|
||
<circle cx="30" cy="70" r="5" fill="#9B5DE5" opacity="0.7"/>
|
||
<line x1="30" y1="40" x2="30" y2="100" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>`);
|
||
|
||
/* 16 — Refraction */
|
||
const P_REFRACTION = _svg(`
|
||
<rect width="270" height="70" fill="#0D0D1A"/>
|
||
<rect y="70" width="270" height="70" fill="rgba(6,214,224,0.07)"/>
|
||
<line x1="0" y1="70" x2="270" y2="70" stroke="rgba(6,214,224,0.35)" stroke-width="1.5" stroke-dasharray="6,4"/>
|
||
<line x1="135" y1="10" x2="135" y2="130" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<line x1="60" y1="15" x2="135" y2="70" stroke="#9B5DE5" stroke-width="2.5"/>
|
||
<polygon points="135,70 127,50 143,50" fill="#9B5DE5" opacity="0.7"/>
|
||
<line x1="135" y1="70" x2="195" y2="125" stroke="#06D6E0" stroke-width="2.5"/>
|
||
<polygon points="195,125 183,112 196,107" fill="#06D6E0" opacity="0.7"/>
|
||
<path d="M 135,50 A 22,22 0 0 0 118,70" fill="none" stroke="rgba(155,93,229,0.5)" stroke-width="1.2"/>
|
||
<path d="M 135,90 A 28,28 0 0 1 157,70" fill="none" stroke="rgba(6,214,224,0.5)" stroke-width="1.2"/>
|
||
<text x="118" y="63" font-size="9" fill="rgba(155,93,229,0.8)" font-family="Manrope,sans-serif">α</text>
|
||
<text x="152" y="87" font-size="9" fill="rgba(6,214,224,0.8)" font-family="Manrope,sans-serif">β</text>
|
||
<text x="135" y="136" font-size="9" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">n₁sinα = n₂sinβ</text>`);
|
||
|
||
/* 17 — Mirrors */
|
||
const P_MIRROR = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="10" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||
<path d="M 200,15 Q 184,70 200,125" fill="none" stroke="#06D6E0" stroke-width="2.5"/>
|
||
<line x1="200" y1="20" x2="210" y2="30" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
|
||
<line x1="200" y1="45" x2="210" y2="55" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
|
||
<line x1="200" y1="70" x2="210" y2="80" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
|
||
<line x1="200" y1="95" x2="210" y2="105" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
|
||
<line x1="200" y1="118" x2="210" y2="128" stroke="rgba(6,214,224,0.25)" stroke-width="1.5"/>
|
||
<circle cx="130" cy="70" r="4" fill="#06D6E0" opacity="0.8"/>
|
||
<text x="130" y="84" text-anchor="middle" font-size="9" fill="#06D6E0" font-family="Manrope,sans-serif">F</text>
|
||
<line x1="50" y1="70" x2="50" y2="30" stroke="#9B5DE5" stroke-width="2"/>
|
||
<polygon points="50,30 44,42 56,42" fill="#9B5DE5"/>
|
||
<line x1="50" y1="30" x2="200" y2="30" stroke="#06D6E0" stroke-width="1.5"/>
|
||
<line x1="200" y1="30" x2="70" y2="105" stroke="#06D6E0" stroke-width="1.5"/>
|
||
<line x1="50" y1="30" x2="200" y2="70" stroke="#7BF5A4" stroke-width="1.5"/>
|
||
<line x1="200" y1="70" x2="70" y2="105" stroke="#7BF5A4" stroke-width="1.5"/>
|
||
<line x1="70" y1="70" x2="70" y2="105" stroke="#EF476F" stroke-width="2"/>
|
||
<polygon points="70,105 64,93 76,93" fill="#EF476F"/>`);
|
||
|
||
/* 18 — Isoprocesses */
|
||
const P_ISOPROCESS = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="30" y1="10" x2="30" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<line x1="30" y1="125" x2="265" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<path d="M 50,20 Q 140,60 240,110" fill="none" stroke="#EF476F" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
|
||
<path d="M 50,20 Q 130,80 230,118" fill="none" stroke="#FFD166" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
|
||
<line x1="50" y1="20" x2="50" y2="118" stroke="#06D6E0" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
|
||
<line x1="50" y1="20" x2="230" y2="20" stroke="#7BF5A4" stroke-width="2" opacity="0.5" stroke-dasharray="4,3"/>
|
||
<path d="M 50,20 Q 120,55 220,108" fill="none" stroke="#EF476F" stroke-width="2.5"/>
|
||
<circle cx="50" cy="20" r="5" fill="#9B5DE5"/>
|
||
<circle cx="220" cy="108" r="5" fill="#EF476F"/>
|
||
<text x="240" y="113" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif">2</text>
|
||
<text x="40" y="16" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif">1</text>
|
||
<text x="255" y="128" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">V</text>
|
||
<text x="18" y="12" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">P</text>`);
|
||
|
||
/* ── Chemistry / Molecular Physics previews ── */
|
||
const P_GAS = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<rect x="6" y="6" width="258" height="128" rx="4" fill="none" stroke="rgba(155,93,229,0.4)" stroke-width="2"/>
|
||
${[
|
||
[40,30,'#4CC9F0'],[70,80,'#7BF5A4'],[110,25,'#EF476F'],[150,60,'#FFD166'],[190,30,'#4CC9F0'],
|
||
[220,90,'#EF476F'],[55,110,'#7BF5A4'],[95,65,'#4CC9F0'],[130,110,'#EF476F'],[170,40,'#FFD166'],
|
||
[210,115,'#4CC9F0'],[240,55,'#7BF5A4'],[30,70,'#FFD166'],[80,120,'#EF476F'],[165,95,'#4CC9F0']
|
||
].map(([x,y,c])=>`<circle cx="${x}" cy="${y}" r="5" fill="${c}" opacity="0.85"/>`).join('')}
|
||
<rect x="6" y="105" width="258" height="29" rx="3" fill="rgba(0,0,0,0.55)"/>
|
||
<rect x="18" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
|
||
<rect x="18" y="112" width="14" height="12" rx="2" fill="rgba(155,93,229,0.6)"/>
|
||
<rect x="70" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
|
||
<rect x="70" y="112" width="22" height="12" rx="2" fill="#7BF5A4" opacity="0.7"/>
|
||
<rect x="122" y="112" width="40" height="12" rx="2" fill="rgba(155,93,229,0.25)"/>
|
||
<rect x="122" y="112" width="30" height="12" rx="2" fill="#EF476F" opacity="0.7"/>
|
||
<text x="202" y="121" font-size="8" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">PV=nRT</text>`);
|
||
|
||
|
||
/* ── Законы Ньютона ── */
|
||
const P_NEWTON = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<line x1="0" y1="105" x2="270" y2="105" stroke="rgba(255,255,255,0.22)" stroke-width="2"/>
|
||
<rect x="80" y="75" width="50" height="30" rx="5" fill="rgba(6,214,224,0.18)" stroke="#06D6E0" stroke-width="2"/>
|
||
<line x1="130" y1="90" x2="175" y2="90" stroke="#EF476F" stroke-width="2.5" marker-end="url(#na)"/>
|
||
<defs><marker id="na" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#EF476F"/></marker></defs>
|
||
<text x="153" y="84" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif" font-weight="700">F</text>
|
||
<line x1="105" y1="75" x2="105" y2="55" stroke="rgba(255,255,255,0.3)" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||
<line x1="105" y1="55" x2="175" y2="55" stroke="rgba(255,255,255,0.18)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<circle cx="65" cy="90" r="12" fill="rgba(155,93,229,0.2)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<text x="65" y="94" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">m₂</text>
|
||
<line x1="195" y1="90" x2="220" y2="90" stroke="#9B5DE5" stroke-width="2" stroke-dasharray="4,3" marker-end="url(#nb)"/>
|
||
<defs><marker id="nb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#9B5DE5"/></marker></defs>
|
||
<text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">a = F/m · III законы Ньютона</text>`);
|
||
|
||
/* ── Песочница сил ── */
|
||
const P_SANDBOX = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid('rgba(255,255,255,0.03)')}
|
||
<line x1="0" y1="115" x2="270" y2="115" stroke="rgba(155,93,229,0.35)" stroke-width="2"/>
|
||
<rect x="55" y="82" width="44" height="33" rx="6" fill="rgba(239,71,111,0.22)" stroke="#EF476F" stroke-width="1.8"/>
|
||
<text x="77" y="103" font-size="8" fill="#fff" text-anchor="middle" font-family="monospace" font-weight="700">5кг</text>
|
||
<circle cx="180" cy="88" r="18" fill="rgba(76,201,240,0.18)" stroke="#4CC9F0" stroke-width="1.8"/>
|
||
<text x="180" y="92" font-size="8" fill="#fff" text-anchor="middle" font-family="monospace" font-weight="700">8кг</text>
|
||
<line x1="99" y1="95" x2="140" y2="95" stroke="#FFD166" stroke-width="2.2" marker-end="url(#sa)"/>
|
||
<line x1="198" y1="88" x2="238" y2="68" stroke="#7BF5A4" stroke-width="2.2" marker-end="url(#sb)"/>
|
||
<defs>
|
||
<marker id="sa" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#FFD166"/></marker>
|
||
<marker id="sb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7BF5A4"/></marker>
|
||
</defs>
|
||
<text x="120" y="87" font-size="8" fill="#FFD166" font-family="monospace">F₁</text>
|
||
<text x="225" y="63" font-size="8" fill="#7BF5A4" font-family="monospace">F₂</text>
|
||
<text x="135" y="133" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Песочница сил · F = ma</text>`);
|
||
|
||
const P_HYDRO = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid('rgba(255,255,255,0.03)')}
|
||
<!-- left tall beaker with water + submerged body -->
|
||
<path d="M 38,30 L 38,118 Q 38,124 44,124 L 96,124 Q 102,124 102,118 L 102,30" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.6"/>
|
||
<rect x="40" y="56" width="60" height="66" rx="2" fill="rgba(41,121,255,0.32)"/>
|
||
<path d="M 40,56 Q 55,52 70,56 T 100,56 L 100,60 L 40,60 Z" fill="rgba(76,201,240,0.55)"/>
|
||
<text x="70" y="48" font-size="7.5" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="monospace">P = ρgh</text>
|
||
<!-- submerged cube + buoyancy arrow up -->
|
||
<rect x="55" y="80" width="28" height="22" rx="3" fill="rgba(255,209,102,0.55)" stroke="#FFD166" stroke-width="1.4"/>
|
||
<line x1="69" y1="80" x2="69" y2="62" stroke="#7BF5A4" stroke-width="2.2" marker-end="url(#hb)"/>
|
||
<text x="79" y="72" font-size="7.5" fill="#7BF5A4" font-family="monospace">F_A</text>
|
||
<!-- U-tube manometer right -->
|
||
<path d="M 150,40 L 150,108 Q 150,116 158,116 L 198,116 Q 206,116 206,108 L 206,40" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.6"/>
|
||
<path d="M 152,80 L 168,80 L 168,114 L 188,114 L 188,68 L 204,68 L 204,80" fill="none" stroke="none"/>
|
||
<rect x="152" y="80" width="16" height="34" fill="rgba(76,201,240,0.55)"/>
|
||
<rect x="188" y="68" width="16" height="46" fill="rgba(76,201,240,0.55)"/>
|
||
<line x1="168" y1="80" x2="188" y2="68" stroke="rgba(76,201,240,0.55)" stroke-width="6" stroke-linecap="round"/>
|
||
<line x1="172" y1="80" x2="184" y2="80" stroke="rgba(255,209,102,0.7)" stroke-width="1" stroke-dasharray="2 2"/>
|
||
<line x1="192" y1="68" x2="204" y2="68" stroke="rgba(255,209,102,0.7)" stroke-width="1" stroke-dasharray="2 2"/>
|
||
<text x="178" y="36" font-size="7.5" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="monospace">Δh</text>
|
||
<line x1="178" y1="38" x2="178" y2="66" stroke="rgba(255,209,102,0.85)" stroke-width="1.2" marker-end="url(#hb)"/>
|
||
<!-- communicating vessels (small inline icon) -->
|
||
<path d="M 224,80 L 224,116 L 258,116 L 258,52" fill="none" stroke="rgba(255,255,255,0.45)" stroke-width="1.4"/>
|
||
<rect x="226" y="100" width="6" height="14" fill="rgba(76,201,240,0.55)"/>
|
||
<rect x="250" y="100" width="6" height="14" fill="rgba(76,201,240,0.55)"/>
|
||
<line x1="232" y1="114" x2="250" y2="114" stroke="rgba(76,201,240,0.55)" stroke-width="3" stroke-linecap="round"/>
|
||
<defs>
|
||
<marker id="hb" markerWidth="7" markerHeight="7" refX="5" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7 Z" fill="#7BF5A4"/></marker>
|
||
</defs>
|
||
<text x="135" y="135" font-size="7.5" fill="rgba(255,255,255,0.4)" text-anchor="middle" font-family="Manrope,sans-serif">Архимед · Паскаль · капиллярность</text>`);
|
||
|
||
/* ── coming soon chem previews (simple) ── */
|
||
const P_KINETICS = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid()}
|
||
<path d="M 20,120 C 60,90 100,50 140,35 S 220,28 260,26" fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round"/>
|
||
<path d="M 20,30 C 60,55 100,100 140,112 S 220,118 260,120" fill="none" stroke="#EF476F" stroke-width="2" stroke-linecap="round"/>
|
||
<circle cx="140" cy="35" r="4" fill="#34d399"/>
|
||
<circle cx="140" cy="112" r="4" fill="#EF476F"/>
|
||
<text x="20" y="18" font-size="9" fill="rgba(52,211,153,0.8)" font-family="Manrope,sans-serif">[C] продукт</text>
|
||
<text x="180" y="130" font-size="9" fill="rgba(239,71,111,0.8)" font-family="Manrope,sans-serif">[A] реагент</text>`);
|
||
|
||
const P_EQUILIBRIUM = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<text x="135" y="30" font-size="11" fill="rgba(255,255,255,0.7)" text-anchor="middle" font-family="Manrope,sans-serif">A + B ⇌ C + D</text>
|
||
<rect x="30" y="50" width="60" height="70" rx="4" fill="rgba(155,93,229,0.15)" stroke="rgba(155,93,229,0.4)" stroke-width="1.5"/>
|
||
<rect x="100" y="75" width="70" height="45" rx="4" fill="rgba(6,214,224,0.12)" stroke="rgba(6,214,224,0.35)" stroke-width="1.5"/>
|
||
<rect x="180" y="55" width="60" height="65" rx="4" fill="rgba(241,91,181,0.12)" stroke="rgba(241,91,181,0.35)" stroke-width="1.5"/>
|
||
<text x="60" y="90" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif">A,B</text>
|
||
<text x="135" y="101" font-size="8" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif">K</text>
|
||
<text x="210" y="91" font-size="9" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif">C,D</text>`);
|
||
|
||
const P_ELECTROLYSIS = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<rect x="20" y="30" width="230" height="90" rx="6" fill="rgba(6,214,224,0.07)" stroke="rgba(6,214,224,0.2)" stroke-width="1.5"/>
|
||
<rect x="50" y="20" width="12" height="80" rx="3" fill="#9B5DE5" opacity="0.8"/>
|
||
<rect x="208" y="20" width="12" height="80" rx="3" fill="#EF476F" opacity="0.8"/>
|
||
${[55,58,61,64,67,70].map(x=>`<circle cx="${x}" cy="${110-Math.random()*20|0}" r="2.5" fill="rgba(155,93,229,0.6)"/>`).join('')}
|
||
${[210,214,218,222,226].map(x=>`<circle cx="${x}" cy="${100-Math.random()*15|0}" r="2.5" fill="rgba(239,71,111,0.6)"/>`).join('')}
|
||
<text x="56" y="15" font-size="8" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif">−</text>
|
||
<text x="214" y="15" font-size="8" fill="#EF476F" text-anchor="middle" font-family="Manrope,sans-serif">+</text>`);
|
||
|
||
const P_BOHR = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<circle cx="135" cy="70" r="8" fill="#FFD166" opacity="0.9"/>
|
||
<ellipse cx="135" cy="70" rx="30" ry="10" fill="none" stroke="rgba(155,93,229,0.4)" stroke-width="1.5"/>
|
||
<ellipse cx="135" cy="70" rx="55" ry="18" fill="none" stroke="rgba(6,214,224,0.3)" stroke-width="1.5"/>
|
||
<ellipse cx="135" cy="70" rx="80" ry="27" fill="none" stroke="rgba(241,91,181,0.25)" stroke-width="1.5"/>
|
||
<circle cx="165" cy="70" r="4" fill="#9B5DE5"/>
|
||
<circle cx="90" cy="70" r="4" fill="#06D6E0"/>
|
||
<circle cx="215" cy="70" r="4" fill="#F15BB5"/>
|
||
<line x1="165" y1="60" x2="190" y2="35" stroke="rgba(255,214,0,0.6)" stroke-width="1.5" stroke-dasharray="3,2"/>
|
||
<circle cx="190" cy="35" r="3" fill="#FFD166" opacity="0.8"/>`);
|
||
|
||
const P_ORBITALS = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<ellipse cx="135" cy="70" rx="60" ry="25" fill="rgba(155,93,229,0.15)" stroke="rgba(155,93,229,0.5)" stroke-width="1.5"/>
|
||
<ellipse cx="135" cy="70" rx="25" ry="60" fill="rgba(6,214,224,0.1)" stroke="rgba(6,214,224,0.4)" stroke-width="1.5"/>
|
||
<circle cx="135" cy="70" r="6" fill="#FFD166" opacity="0.9"/>
|
||
<circle cx="95" cy="70" r="5" fill="#9B5DE5" opacity="0.8"/>
|
||
<circle cx="175" cy="70" r="5" fill="#9B5DE5" opacity="0.8"/>`);
|
||
|
||
const P_PH = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid()}
|
||
<path d="M 20,110 L 60,108 L 100,105 L 120,90 L 130,30 L 140,75 L 180,40 L 220,35 L 260,32"
|
||
fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round"/>
|
||
<line x1="20" y1="70" x2="260" y2="70" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="4,3"/>
|
||
<text x="20" y="18" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">pH</text>
|
||
<text x="240" y="130" font-size="9" fill="rgba(255,255,255,0.4)" font-family="Manrope,sans-serif">V</text>`);
|
||
|
||
const P_CHEMSANDBOX = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid()}
|
||
<rect x="85" y="20" width="100" height="70" rx="8" fill="none" stroke="rgba(75,205,155,0.4)" stroke-width="1.5"/>
|
||
<rect x="88" y="55" width="94" height="32" rx="4" fill="rgba(75,205,155,0.15)"/>
|
||
<circle cx="110" cy="71" r="4" fill="rgba(255,200,60,0.5)"/>
|
||
<circle cx="130" cy="68" r="3" fill="rgba(255,255,255,0.3)"/>
|
||
<circle cx="150" cy="73" r="3.5" fill="rgba(90,200,235,0.4)"/>
|
||
<text x="135" y="105" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" text-anchor="middle">A + B <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> C + D</text>
|
||
<rect x="40" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||
<rect x="75" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||
<rect x="110" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||
<rect x="145" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||
<rect x="180" y="115" width="28" height="18" rx="4" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||
<circle cx="54" cy="121" r="3" fill="#78D278"/><circle cx="89" cy="121" r="3" fill="#7BF5A4"/>
|
||
<circle cx="124" cy="121" r="3" fill="#4CC9F0"/><circle cx="159" cy="121" r="3" fill="#9BB8CC"/>
|
||
<circle cx="194" cy="121" r="3" fill="#FFD166"/>`);
|
||
|
||
const P_STOICHIOMETRY = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid()}
|
||
<rect x="18" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(155,184,204,0.5)" stroke-width="1.5"/>
|
||
<rect x="20" y="58" width="48" height="36" rx="3" fill="rgba(155,184,204,0.12)"/>
|
||
<circle cx="32" cy="74" r="4" fill="rgba(155,184,204,0.7)"/>
|
||
<circle cx="45" cy="70" r="4" fill="rgba(155,184,204,0.7)"/>
|
||
<circle cx="58" cy="76" r="4" fill="rgba(155,184,204,0.7)"/>
|
||
<text x="44" y="46" font-size="9" fill="rgba(155,184,204,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">Zn</text>
|
||
<rect x="78" y="28" width="60" height="68" rx="5" fill="none" stroke="rgba(120,210,120,0.5)" stroke-width="1.5"/>
|
||
<rect x="80" y="58" width="56" height="36" rx="3" fill="rgba(120,210,120,0.12)"/>
|
||
<circle cx="92" cy="72" r="3.5" fill="rgba(120,210,120,0.7)"/>
|
||
<circle cx="103" cy="76" r="3.5" fill="rgba(120,210,120,0.7)"/>
|
||
<circle cx="114" cy="70" r="3.5" fill="rgba(120,210,120,0.7)"/>
|
||
<circle cx="125" cy="74" r="3.5" fill="rgba(120,210,120,0.7)"/>
|
||
<text x="108" y="46" font-size="9" fill="rgba(120,210,120,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">2HCl</text>
|
||
<text x="150" y="68" font-size="14" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" text-anchor="middle">→</text>
|
||
<rect x="162" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(76,201,240,0.5)" stroke-width="1.5"/>
|
||
<rect x="164" y="58" width="48" height="36" rx="3" fill="rgba(76,201,240,0.12)"/>
|
||
<circle cx="176" cy="74" r="4" fill="rgba(76,201,240,0.7)"/>
|
||
<circle cx="189" cy="70" r="4" fill="rgba(76,201,240,0.7)"/>
|
||
<circle cx="202" cy="76" r="4" fill="rgba(76,201,240,0.7)"/>
|
||
<text x="184" y="46" font-size="9" fill="rgba(76,201,240,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">ZnCl₂</text>
|
||
<rect x="222" y="28" width="36" height="68" rx="5" fill="none" stroke="rgba(255,209,102,0.5)" stroke-width="1.5"/>
|
||
<circle cx="235" cy="68" r="3" fill="rgba(255,209,102,0.7)"/>
|
||
<circle cx="248" cy="74" r="3" fill="rgba(255,209,102,0.7)"/>
|
||
<text x="240" y="46" font-size="9" fill="rgba(255,209,102,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">H₂</text>
|
||
<text x="135" y="118" font-size="8" fill="rgba(239,71,111,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">● лимит</text>`);
|
||
|
||
/* Periodic Table — 6×4 coloured cell grid */
|
||
const P_PERIODIC = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${(function(){
|
||
const cols=18,rows=4,pad=6,w=(270-pad*2)/cols,h=(140-pad*2)/rows;
|
||
const colors=['#EF476F','#FF6B35','#FFD166','#7BF5A4','#C77DFF','#A8DADC',
|
||
'#7B8EF7','#06D6E0','#9B5DE5','#F15BB5','#EF476F','#FF6B35',
|
||
'#06D6E0','#7B8EF7','#FFD166','#C77DFF','#A8DADC','#7BF5A4'];
|
||
let s='';
|
||
for(let r=0;r<rows;r++) for(let c=0;c<cols;c++){
|
||
const x=pad+c*w, y=pad+r*h;
|
||
const skip=(r===0&&c>=2&&c<=15)||(r===1&&c>=2&&c<=11);
|
||
if(!skip) s+=`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${(w-1.5).toFixed(1)}" height="${(h-1.5).toFixed(1)}" rx="2" fill="${colors[c]}" opacity="0.7"/>`;
|
||
}
|
||
return s;
|
||
})()}
|
||
<text x="135" y="134" font-size="8" fill="rgba(255,255,255,0.35)" font-family="Manrope,sans-serif" text-anchor="middle">118 элементов</text>`);
|
||
|
||
const P_CRYSTAL = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${[
|
||
[80,40],[135,40],[190,40],
|
||
[55,75],[110,75],[165,75],[220,75],
|
||
[80,110],[135,110],[190,110]
|
||
].map(([x,y],i)=>`<circle cx="${x}" cy="${y}" r="${i%2===0?7:5}" fill="${i%2===0?'#9B5DE5':'#06D6E0'}" opacity="0.8"/>`).join('')}
|
||
<line x1="80" y1="40" x2="135" y2="40" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="135" y1="40" x2="190" y2="40" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="80" y1="40" x2="55" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="135" y1="40" x2="110" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="190" y1="40" x2="165" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="190" y1="40" x2="220" y2="75" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="55" y1="75" x2="80" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="110" y1="75" x2="135" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="165" y1="75" x2="190" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="80" y1="110" x2="135" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||
<line x1="135" y1="110" x2="190" y2="110" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>`);
|
||
|
||
const P_CELLDIVISION = _svg(`
|
||
<rect width="270" height="140" fill="#0e0e18"/>
|
||
<ellipse cx="135" cy="70" rx="78" ry="54" fill="rgba(34,211,153,0.07)" stroke="rgba(34,211,153,0.5)" stroke-width="2"/>
|
||
<ellipse cx="135" cy="70" rx="44" ry="28" fill="rgba(155,93,229,0.08)" stroke="rgba(155,93,229,0.3)" stroke-width="1" stroke-dasharray="4,3"/>
|
||
<line x1="55" y1="70" x2="215" y2="70" stroke="rgba(255,214,0,0.35)" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||
<rect x="98" y="57" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
|
||
<rect x="112" y="57" width="9" height="15" rx="2" fill="#FF6B35" opacity="0.9"/>
|
||
<rect x="126" y="57" width="9" height="15" rx="2" fill="#9B5DE5" opacity="0.9"/>
|
||
<rect x="140" y="57" width="9" height="15" rx="2" fill="#FFD166" opacity="0.9"/>
|
||
<rect x="154" y="57" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
|
||
<rect x="98" y="75" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
|
||
<rect x="112" y="75" width="9" height="15" rx="2" fill="#FF6B35" opacity="0.9"/>
|
||
<rect x="126" y="75" width="9" height="15" rx="2" fill="#9B5DE5" opacity="0.9"/>
|
||
<rect x="140" y="75" width="9" height="15" rx="2" fill="#FFD166" opacity="0.9"/>
|
||
<rect x="154" y="75" width="9" height="15" rx="2" fill="#EF476F" opacity="0.9"/>
|
||
<line x1="135" y1="16" x2="114" y2="57" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
|
||
<line x1="135" y1="16" x2="135" y2="57" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
|
||
<line x1="135" y1="124" x2="114" y2="90" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
|
||
<line x1="135" y1="124" x2="135" y2="90" stroke="rgba(255,214,0,0.4)" stroke-width="1"/>
|
||
<circle cx="135" cy="15" r="5" fill="rgba(255,214,0,0.7)"/>
|
||
<circle cx="135" cy="125" r="5" fill="rgba(255,214,0,0.7)"/>
|
||
<text x="135" y="137" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Метафаза · митоз</text>`);
|
||
|
||
const P_PHOTOSYNTHESIS = _svg(`
|
||
<rect width="270" height="140" fill="#0a0e14"/>
|
||
<ellipse cx="135" cy="72" rx="100" ry="48" fill="rgba(34,211,153,0.07)" stroke="rgba(34,211,153,0.45)" stroke-width="2"/>
|
||
<rect x="52" y="60" width="166" height="22" rx="7" fill="rgba(34,211,153,0.18)" stroke="rgba(34,211,153,0.5)" stroke-width="1.5"/>
|
||
<line x1="70" y1="12" x2="79" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
|
||
<line x1="100" y1="8" x2="107" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
|
||
<line x1="135" y1="6" x2="135" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
|
||
<line x1="170" y1="8" x2="163" y2="59" stroke="#FFD166" stroke-width="1.8" stroke-dasharray="3,2" opacity="0.8"/>
|
||
<circle cx="70" cy="11" r="5" fill="#FFD166" opacity="0.9"/>
|
||
<circle cx="100" cy="7" r="5" fill="#FFD166" opacity="0.9"/>
|
||
<circle cx="135" cy="5" r="5" fill="#FFD166" opacity="0.9"/>
|
||
<circle cx="170" cy="7" r="5" fill="#FFD166" opacity="0.9"/>
|
||
<text x="52" y="98" font-size="8" fill="rgba(6,214,224,0.8)" font-family="Manrope,sans-serif">H₂O</text>
|
||
<text x="90" y="106" font-size="8" fill="rgba(255,255,255,0.45)" font-family="Manrope,sans-serif">CO₂</text>
|
||
<text x="168" y="52" font-size="8" fill="#9B5DE5" font-family="Manrope,sans-serif">ATP</text>
|
||
<text x="185" y="98" font-size="8" fill="#22d399" font-family="Manrope,sans-serif">G3P</text>
|
||
<text x="135" y="135" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Световые реакции · цикл Кальвина</text>`);
|
||
|
||
const P_ANGRYBIRDS = _svg(`
|
||
<rect width="270" height="140" fill="#0f1923"/>
|
||
<rect x="0" y="108" width="270" height="32" fill="#3d6b47"/>
|
||
<line x1="0" y1="108" x2="270" y2="108" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
|
||
<rect x="175" y="68" width="22" height="40" fill="#b5651d" stroke="#7a3f0a" stroke-width="1.5"/>
|
||
<rect x="158" y="56" width="56" height="14" fill="#b5651d" stroke="#7a3f0a" stroke-width="1.5"/>
|
||
<rect x="168" y="40" width="18" height="18" fill="#a8d8ea" stroke="#5badd4" stroke-width="1.5"/>
|
||
<rect x="202" y="78" width="16" height="30" fill="#7a7a7a" stroke="#444" stroke-width="1.5"/>
|
||
<circle cx="232" cy="100" r="10" fill="#22c55e" stroke="#166534" stroke-width="1.5"/>
|
||
<circle cx="215" cy="99" r="10" fill="#22c55e" stroke="#166534" stroke-width="1.5"/>
|
||
<path d="M 32,102 Q 90,38 148,98" stroke="#ef476f" stroke-width="2.5" fill="none" stroke-dasharray="4,3"/>
|
||
<circle cx="32" cy="102" r="9" fill="#e63946"/>
|
||
<circle cx="36" cy="98" r="2.5" fill="#fff"/>
|
||
<circle cx="37.5" cy="97.5" r="1.1" fill="#111"/>
|
||
<line x1="28" y1="94" x2="38" y2="98" stroke="#333" stroke-width="1.5" stroke-linecap="round"/>
|
||
<circle cx="21" cy="106" r="6.5" fill="#888" opacity="0.7"/>
|
||
<circle cx="10" cy="106" r="5.5" fill="#ffd166" opacity="0.5"/>
|
||
<line x1="18" y1="93" x2="22" y2="80" stroke="rgba(255,255,255,0.18)" stroke-width="5" stroke-linecap="round"/>
|
||
<line x1="22" y1="80" x2="26" y2="93" stroke="rgba(255,255,255,0.18)" stroke-width="5" stroke-linecap="round"/>
|
||
<text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Физика полёта · импульс · разрушение</text>`);
|
||
|
||
const P_WAVES = _svg(`${_grid()}
|
||
<line x1="0" y1="70" x2="270" y2="70" stroke="rgba(255,255,255,0.13)" stroke-width="1" stroke-dasharray="4,3"/>
|
||
<path d="M 0,70 C 17,26 33,26 67,70 C 101,114 117,114 135,70 C 153,26 169,26 202,70 C 236,114 252,114 270,70"
|
||
stroke="#9B5DE5" stroke-width="2" fill="none" opacity="0.7"/>
|
||
<path d="M 0,70 C 22,18 44,18 90,70 C 136,122 158,122 180,70 C 202,18 224,18 270,70"
|
||
stroke="#06D6E0" stroke-width="1.5" fill="none" opacity="0.5"/>
|
||
<path d="M 0,70 C 12,10 28,8 50,40 C 72,72 88,118 112,85 C 136,52 155,18 180,50 C 205,82 240,108 270,70"
|
||
stroke="#F15BB5" stroke-width="2.5" fill="none" opacity="0.9"/>
|
||
<text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">v = \u03bbf \u00b7 y = A sin(\u03c9t \u2212 kx) \u00b7 \u0441\u0442\u043e\u044f\u0447\u0438\u0435 \u0432\u043e\u043b\u043d\u044b</text>`);
|
||
|
||
/* Radioactive decay preview */
|
||
const P_RADIOACTIVE = _svg(`${_grid()}
|
||
<circle cx="55" cy="45" r="5" fill="#9B5DE5" opacity="0.9"/>
|
||
<circle cx="90" cy="65" r="5" fill="#9B5DE5" opacity="0.9"/>
|
||
<circle cx="38" cy="80" r="5" fill="#9B5DE5" opacity="0.7"/>
|
||
<circle cx="75" cy="95" r="5" fill="#EF476F" opacity="0.9"/>
|
||
<circle cx="110" cy="50" r="5" fill="#EF476F" opacity="0.85"/>
|
||
<circle cx="130" cy="85" r="5" fill="#4CAF50" opacity="0.85"/>
|
||
<circle cx="155" cy="55" r="5" fill="#9B5DE5" opacity="0.8"/>
|
||
<circle cx="170" cy="90" r="5" fill="#4CAF50" opacity="0.75"/>
|
||
<circle cx="200" cy="45" r="5" fill="#4CAF50" opacity="0.9"/>
|
||
<circle cx="215" cy="80" r="5" fill="#4CAF50" opacity="0.85"/>
|
||
<circle cx="240" cy="60" r="5" fill="#9B5DE5" opacity="0.7"/>
|
||
<path d="M 20,115 Q 67,42 135,52 Q 200,62 270,110"
|
||
fill="none" stroke="#9B5DE5" stroke-width="2" opacity="0.55" stroke-dasharray="5,3"/>
|
||
<path d="M 20,115 Q 100,110 175,100 Q 230,92 270,85"
|
||
fill="none" stroke="#4CAF50" stroke-width="1.5" opacity="0.5"/>
|
||
<text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">N(t) = N₀·e⁻λt · T½ · цепочки распада</text>`);
|
||
|
||
/* Heat Engines preview */
|
||
const P_HEATENGINE = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<line x1="30" y1="10" x2="30" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<line x1="30" y1="125" x2="265" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<path d="M 55,18 Q 100,30 140,75 Q 160,100 190,115" fill="none" stroke="#EF476F" stroke-width="2.2" opacity="0.85"/>
|
||
<path d="M 190,115 Q 205,118 215,110 Q 230,90 225,60" fill="none" stroke="#FFD166" stroke-width="2" opacity="0.8"/>
|
||
<path d="M 225,60 Q 200,40 160,32 Q 110,22 55,18" fill="none" stroke="#06D6E0" stroke-width="2.2" opacity="0.85"/>
|
||
<path d="M 55,18 Q 48,16 44,22 Q 38,38 45,60 Q 50,80 55,18" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1" opacity="0.5"/>
|
||
<circle cx="55" cy="18" r="4" fill="#EF476F"/>
|
||
<circle cx="190" cy="115" r="4" fill="#FFD166"/>
|
||
<circle cx="225" cy="60" r="4" fill="#06D6E0"/>
|
||
<text x="44" y="14" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif">A</text>
|
||
<text x="195" y="126" font-size="9" fill="#FFD166" font-family="Manrope,sans-serif">B</text>
|
||
<text x="230" y="58" font-size="9" fill="#06D6E0" font-family="Manrope,sans-serif">C</text>
|
||
<text x="255" y="128" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">V</text>
|
||
<text x="18" y="12" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">P</text>
|
||
<text x="135" y="115" font-size="8" fill="rgba(155,93,229,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">η = 1 − Tc/Th</text>`);
|
||
|
||
/* Geometry (planimetry) preview */
|
||
const P_GEOMETRY = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<circle cx="135" cy="70" r="50" fill="rgba(155,93,229,0.07)" stroke="#9B5DE5" stroke-width="1.5"/>
|
||
<polygon points="85,99 185,99 135,20" fill="rgba(6,214,224,0.08)" stroke="#06D6E0" stroke-width="1.8"/>
|
||
<line x1="85" y1="99" x2="162" y2="57" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
|
||
<line x1="185" y1="99" x2="109" y2="57" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
|
||
<line x1="135" y1="20" x2="135" y2="99" stroke="rgba(241,91,181,0.45)" stroke-width="1.2" stroke-dasharray="4,3"/>
|
||
<circle cx="135" cy="64" r="4" fill="#06D6E0" opacity="0.9"/>
|
||
<circle cx="85" cy="99" r="4" fill="#9B5DE5"/>
|
||
<circle cx="185" cy="99" r="4" fill="#9B5DE5"/>
|
||
<circle cx="135" cy="20" r="4" fill="#9B5DE5"/>
|
||
<text x="78" y="111" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">A</text>
|
||
<text x="188" y="111" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">B</text>
|
||
<text x="131" y="16" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">C</text>`);
|
||
|
||
|
||
/* Race sim preview — two objects on a track, x(t) lines */
|
||
const P_RACE = _svg(`${_grid('rgba(255,255,255,0.05)')}
|
||
<line x1="20" y1="75" x2="250" y2="75" stroke="rgba(255,255,255,0.18)" stroke-width="8" stroke-linecap="round"/>
|
||
<line x1="20" y1="75" x2="250" y2="75" stroke="rgba(30,32,50,0.9)" stroke-width="6" stroke-linecap="round"/>
|
||
<circle cx="75" cy="75" r="9" fill="rgba(6,214,224,0.2)" stroke="#06D6E0" stroke-width="2"/>
|
||
<circle cx="185" cy="75" r="9" fill="rgba(239,71,111,0.2)" stroke="#EF476F" stroke-width="2"/>
|
||
<line x1="30" y1="110" x2="130" y2="30" stroke="#06D6E0" stroke-width="2" opacity="0.85"/>
|
||
<line x1="30" y1="30" x2="200" y2="110" stroke="#EF476F" stroke-width="2" opacity="0.85"/>
|
||
<circle cx="107" cy="60" r="5" fill="#FF6B6B" stroke="#fff" stroke-width="1.5"/>
|
||
<text x="115" y="57" font-size="9" fill="#FF6B6B" font-family="Manrope,sans-serif" font-weight="700">встреча</text>
|
||
<text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">x = x₀ + v₀t + at²/2</text>`);
|
||
|
||
/* Logic Circuits preview */
|
||
const P_LOGIC = _svg(`${_grid('rgba(255,255,255,0.04)')}
|
||
<rect x="20" y="38" width="60" height="30" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1.5" rx="4"/>
|
||
<text x="50" y="57" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
|
||
<rect x="130" y="20" width="60" height="30" fill="rgba(6,214,224,0.12)" stroke="#06D6E0" stroke-width="1.5" rx="4"/>
|
||
<text x="160" y="39" font-size="9" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">XOR</text>
|
||
<rect x="130" y="60" width="60" height="30" fill="rgba(241,91,181,0.12)" stroke="#F15BB5" stroke-width="1.5" rx="4"/>
|
||
<text x="160" y="79" font-size="9" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
|
||
<circle cx="18" cy="45" r="4" fill="#4ADE80"/>
|
||
<circle cx="18" cy="58" r="4" fill="rgba(255,255,255,0.35)"/>
|
||
<line x1="22" y1="45" x2="80" y2="40" stroke="#4ADE80" stroke-width="1.5"/>
|
||
<line x1="22" y1="58" x2="80" y2="55" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<line x1="80" y1="53" x2="112" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
|
||
<line x1="80" y1="53" x2="112" y2="75" stroke="#4ADE80" stroke-width="1.5"/>
|
||
<line x1="190" y1="35" x2="230" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
|
||
<line x1="190" y1="75" x2="230" y2="75" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<circle cx="234" cy="35" r="5" fill="#4ADE80" opacity="0.9"/>
|
||
<circle cx="234" cy="75" r="5" fill="rgba(255,255,255,0.25)"/>
|
||
<text x="240" y="39" font-size="8" fill="#4ADE80" font-family="Manrope,sans-serif" font-weight="700">S</text>
|
||
<text x="240" y="79" font-size="8" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">S = A⊕B · C = A∧B · Таблица истинности`);
|
||
|
||
|
||
/* Qualitative Analysis preview */
|
||
const P_QUALANALYSIS = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
<rect x="30" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
|
||
<rect x="32" y="42" width="26" height="60" rx="4" fill="rgba(255,255,255,0.12)"/>
|
||
<rect x="32" y="77" width="26" height="25" rx="3" fill="rgba(100,180,255,0.4)"/>
|
||
<rect x="100" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
|
||
<rect x="102" y="42" width="26" height="60" rx="4" fill="rgba(255,200,80,0.1)"/>
|
||
<rect x="102" y="62" width="26" height="40" rx="3" fill="rgba(200,20,20,0.55)"/>
|
||
<rect x="170" y="25" width="30" height="80" rx="6" fill="none" stroke="rgba(200,210,255,0.55)" stroke-width="1.5"/>
|
||
<rect x="172" y="42" width="26" height="60" rx="4" fill="rgba(80,200,80,0.1)"/>
|
||
<rect x="172" y="70" width="26" height="32" rx="3" fill="rgba(255,255,150,0.35)"/>
|
||
<text x="45" y="118" font-size="7" fill="rgba(100,180,255,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">Cl</text>
|
||
<text x="115" y="118" font-size="7" fill="rgba(200,80,80,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">Fe(III)</text>
|
||
<text x="185" y="118" font-size="7" fill="rgba(200,200,80,0.9)" text-anchor="middle" font-family="Manrope,sans-serif">SO4</text>
|
||
<text x="135" y="18" font-size="8" fill="rgba(155,93,229,0.7)" text-anchor="middle" font-family="Manrope,sans-serif">AgNO3</text>
|
||
<line x1="45" y1="22" x2="45" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
|
||
<line x1="115" y1="22" x2="115" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
|
||
<line x1="185" y1="22" x2="185" y2="27" stroke="rgba(200,200,200,0.5)" stroke-width="1"/>
|
||
<text x="135" y="136" font-size="8" fill="rgba(255,255,255,0.3)" text-anchor="middle" font-family="Manrope,sans-serif">AgCl / Fe(SCN) / BaSO4`);
|
||
|
||
/* Organic Chemistry preview — benzene ring + OH group */
|
||
const P_ORGANIC = _svg(`
|
||
<rect width="270" height="140" fill="#0D0D1A"/>
|
||
${_grid('rgba(255,255,255,0.03)')}
|
||
<!-- benzene ring -->
|
||
<polygon points="115,44 145,44 160,70 145,96 115,96 100,70" fill="rgba(155,93,229,0.08)" stroke="#9B5DE5" stroke-width="1.8"/>
|
||
<!-- alternating double bonds (inner circle shorthand) -->
|
||
<circle cx="130" cy="70" r="18" fill="none" stroke="#9B5DE5" stroke-width="1.2" stroke-dasharray="5,4" opacity="0.55"/>
|
||
<!-- C labels -->
|
||
<text x="108" y="46" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="145" y="46" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="164" y="73" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="145" y="99" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="108" y="99" font-size="9" font-family="Manrope,sans-serif" font-weight="700" fill="#9B5DE5">C</text>
|
||
<text x="94" y="73" font-size="9" fill="#9B5DE5" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<!-- -OH substituent -->
|
||
<line x1="160" y1="70" x2="195" y2="55" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
|
||
<circle cx="200" cy="52" r="9" fill="rgba(239,71,111,0.25)" stroke="#EF476F" stroke-width="1.5"/>
|
||
<text x="200" y="56" font-size="9" fill="#EF476F" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">O</text>
|
||
<line x1="209" y1="48" x2="218" y2="40" stroke="rgba(255,255,255,0.3)" stroke-width="1.2"/>
|
||
<circle cx="222" cy="37" r="6" fill="rgba(224,224,224,0.2)" stroke="#E0E0E0" stroke-width="1.2"/>
|
||
<text x="222" y="41" font-size="8" fill="#E0E0E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">H</text>
|
||
<!-- chain fragment on right -->
|
||
<line x1="100" y1="70" x2="65" y2="70" stroke="rgba(255,255,255,0.35)" stroke-width="1.5"/>
|
||
<circle cx="58" cy="70" r="9" fill="rgba(155,93,229,0.2)" stroke="#9B5DE5" stroke-width="1.5"/>
|
||
<text x="58" y="74" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">C</text>
|
||
<text x="135" y="134" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">Конструктор · Ряды · Качественные реакции</text>`);
|
||
|
||
/* Solutions preview */
|
||
const P_SOLUTIONS = _svg(`
|
||
<rect x="88" y="20" width="58" height="88" rx="3" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="2"/>
|
||
<rect x="89" y="52" width="56" height="55" rx="2" fill="rgba(76,201,240,0.55)"/>
|
||
<path d="M89,52 Q108,47 117,52 Q127,57 145,52" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
|
||
<line x1="90" y1="66" x2="96" y2="66" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||
<line x1="90" y1="81" x2="96" y2="81" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||
<line x1="90" y1="96" x2="96" y2="96" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
|
||
<text x="117" y="80" font-size="15" fill="rgba(255,255,255,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="800">20%</text>
|
||
<path d="M172,32 Q175,24 179,32 Q183,41 179,45 Q175,49 172,45 Q168,41 172,32 Z" fill="#4CC9F0" opacity="0.85"/>
|
||
<line x1="193" y1="20" x2="250" y2="90" stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="3,3"/>
|
||
<text x="221" y="42" font-size="8" fill="rgba(76,201,240,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">ω%</text>
|
||
<text x="221" y="56" font-size="8" fill="rgba(155,93,229,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">C-M</text>
|
||
<text x="221" y="70" font-size="8" fill="rgba(241,91,181,0.9)" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">ν моль</text>
|
||
<text x="135" y="123" font-size="8" fill="rgba(76,201,240,0.75)" text-anchor="middle" font-family="Manrope,sans-serif">ω = m₀/m​ · 100%</text>
|
||
<text x="135" y="135" font-size="7" fill="rgba(255,255,255,0.3)" text-anchor="middle" font-family="Manrope,sans-serif">Калькулятор · Разбавление · Смешивание · S(T)</text>`);
|
||
|
||
const SIMS = [
|
||
/* ── Математика ── */
|
||
{ id: 'graph', cat: 'math',
|
||
title: 'График функции',
|
||
desc: 'Строй графики функций y = f(x) с параметрами, зумом и курсором координат.',
|
||
preview: P_GRAPH },
|
||
{ id: 'graphtransform', cat: 'math',
|
||
title: 'Трансформации графиков',
|
||
desc: 'Наблюдай, как сдвиги, растяжения и отражения меняют вид функции y = a·f(kx+b)+c.',
|
||
preview: P_TRANSFORM },
|
||
{ id: 'geometry', cat: 'math',
|
||
title: 'Планиметрия',
|
||
desc: 'Интерактивная среда построений: точки, отрезки, прямые, окружности, многоугольники. Полноценный чертёж с привязкой и измерениями.',
|
||
preview: P_GEOMETRY },
|
||
{ id: 'triangle', cat: 'math',
|
||
title: 'Геометрия треугольника',
|
||
desc: 'Интерактивный треугольник: медианы, высоты, биссектрисы, вписанная и описанная окружности.',
|
||
preview: P_TRIANGLE },
|
||
{ id: 'quadratic', cat: 'math',
|
||
title: 'Корни квадратного уравнения',
|
||
desc: 'Задай a, b, c ползунками — смотри дискриминант и корни анимированно на числовой оси.',
|
||
preview: P_QUADRATIC },
|
||
{ id: 'stereo', cat: 'math',
|
||
title: 'Стереометрия 3D',
|
||
desc: 'Вращаемые объёмные фигуры: куб, пирамида, цилиндр, конус с формулами объёма и площади. Сечения, развёртка, вписанные/описанные сферы.',
|
||
preview: P_3D },
|
||
{ id: 'probability', cat: 'math',
|
||
title: 'Теория вероятностей',
|
||
desc: 'Подброс монеты/кубика N раз — гистограмма частот и закон больших чисел в действии.',
|
||
preview: P_PROB },
|
||
{ id: 'trigcircle', cat: 'math',
|
||
title: 'Тригонометрическая окружность',
|
||
desc: 'Единичная окружность с sin, cos, tg, ctg. Перетаскивай точку — все функции обновляются мгновенно. График синхронизирован.',
|
||
preview: P_TRIGCIRCLE },
|
||
{ id: 'normaldist', cat: 'math',
|
||
title: 'Нормальное распределение',
|
||
desc: 'Двигай μ и σ ползунками — колокол Гаусса и площадь под кривой обновляются мгновенно.',
|
||
preview: P_NORMAL },
|
||
/* ── Физика ── */
|
||
{ id: 'projectile', cat: 'phys',
|
||
title: 'Бросок тела',
|
||
desc: 'Задай начальную скорость и угол — симулируй траекторию, дальность и высоту полёта.',
|
||
preview: P_PROJECTILE },
|
||
{ id: 'pendulum', cat: 'phys',
|
||
title: 'Маятник',
|
||
desc: 'Регулируй длину и угол отклонения — изучай период колебаний и затухание.',
|
||
preview: P_PENDULUM },
|
||
{ id: 'collision', cat: 'phys',
|
||
title: 'Столкновение шаров',
|
||
desc: 'Упругий и неупругий удар двух тел: законы сохранения импульса и энергии.',
|
||
preview: P_COLLISION },
|
||
{ id: 'emfield', cat: 'phys',
|
||
title: 'Электромагнитные поля',
|
||
desc: 'Электрическое и магнитное поля в одной симуляции: заряды, токи, силовые линии, эквипотенциали, частица Лоренца.',
|
||
preview: P_MAGNETIC },
|
||
{ id: 'circuit', cat: 'phys',
|
||
title: 'Электрические цепи',
|
||
desc: 'Конструктор цепей из резисторов и конденсаторов. Законы Ома и Кирхгофа наглядно.',
|
||
preview: P_CIRCUIT },
|
||
{ id: 'hydrostatics', cat: 'phys',
|
||
title: 'Гидростатика',
|
||
desc: 'Давление жидкости P=ρgh, закон Архимеда, сообщающиеся сосуды, поверхностное натяжение и капиллярность.',
|
||
preview: P_HYDRO },
|
||
{ id: 'dynamics', cat: 'phys',
|
||
title: 'Динамика',
|
||
desc: 'Законы Ньютона, песочница сил, наклонная плоскость — всё в одном интерактивном модуле.',
|
||
preview: P_SANDBOX },
|
||
{ id: 'opticsbench', cat: 'phys',
|
||
title: 'Оптическая скамья',
|
||
desc: 'Линза, зеркала и преломление в одной симуляции: формула линзы, зеркальное отражение, закон Снеллиуса, ПВО, дисперсия.',
|
||
preview: P_LENS },
|
||
{ id: 'isoprocess', cat: 'phys',
|
||
title: 'Изопроцессы',
|
||
desc: 'PV-диаграмма для четырёх изопроцессов идеального газа. Расчёт работы, теплоты и внутренней энергии.',
|
||
preview: P_ISOPROCESS },
|
||
{ id: 'waves', cat: 'phys',
|
||
title: 'Волны и звук',
|
||
desc: 'Поперечные и продольные волны, суперпозиция, стоячие волны. Частота, амплитуда, фаза, гармоники.',
|
||
preview: P_WAVES },
|
||
{ id: 'radioactive', cat: 'phys',
|
||
title: 'Радиоактивный распад',
|
||
desc: 'Период полураспада, цепочки распадов, активность. Визуализация ядер + кривая N(t). Радиоуглеродное датирование.',
|
||
preview: P_RADIOACTIVE },
|
||
{ id: 'race', cat: 'phys',
|
||
title: 'Гонка с задачами',
|
||
desc: 'Кинематика 1D: встреча, догон, кто первый. Реши задачу — проверь анимацией и графиком x(t).',
|
||
preview: P_RACE },
|
||
{ id: 'heatengine', cat: 'phys',
|
||
title: 'Тепловые двигатели',
|
||
desc: 'Циклы Карно, Отто, Дизеля, Брайтона. PV-диаграмма, поршень, КПД.',
|
||
preview: P_HEATENGINE },
|
||
{ id: 'logic', cat: 'phys',
|
||
title: 'Логические схемы',
|
||
desc: 'Конструктор цифровых схем: И/ИЛИ/НЕ/XOR, триггеры, сумматоры. Авто-таблица истинности.',
|
||
preview: P_LOGIC },
|
||
/* ── Химия / Молекулярная физика ── */
|
||
{ id: 'molphys', cat: 'chem',
|
||
title: 'Молекулярная физика',
|
||
desc: 'Идеальный газ, броуновское движение, агрегатные состояния и диффузия — всё в одном модуле.',
|
||
preview: P_GAS },
|
||
{ id: 'chemistry', cat: 'chem',
|
||
title: 'Химические реакции',
|
||
desc: 'Кинетика реакций, металл + кислота в колбе, ОВР с переносом электронов, ионный обмен — всё в одном модуле.',
|
||
preview: P_KINETICS },
|
||
{ id: 'equilibrium', cat: 'chem',
|
||
title: 'Химическое равновесие',
|
||
desc: 'Прямая и обратная реакция, принцип Ле Шателье: изменяй T, P, концентрацию и наблюдай сдвиг.',
|
||
preview: P_EQUILIBRIUM },
|
||
{ id: 'electrolysis', cat: 'chem',
|
||
title: 'Электролиз',
|
||
desc: 'Катод и анод в растворе электролита: движение ионов, выделение газа, закон Фарадея.',
|
||
preview: P_ELECTROLYSIS },
|
||
/* ── Скоро: Атомная структура ── */
|
||
{ id: 'bohratom', cat: 'chem',
|
||
title: 'Атом Бора',
|
||
desc: 'Электроны на орбитах, квантование энергии, эмиссия и поглощение фотонов при переходах.',
|
||
preview: P_BOHR },
|
||
{ id: 'orbitals', cat: 'chem',
|
||
title: 'Молекулярные орбитали',
|
||
desc: 'H₂, H₂O — ковалентная связь, перекрывание орбиталей, 3D-визуализация электронных облаков.',
|
||
preview: P_ORBITALS },
|
||
/* ── Скоро: Визуальная химия ── */
|
||
{ id: 'titration', cat: 'chem',
|
||
title: 'pH и кривая титрования',
|
||
desc: 'Добавляй кислоту или щёлочь — наблюдай изменение pH, цвет раствора и кривую нейтрализации.',
|
||
preview: P_PH },
|
||
{ id: 'chemsandbox', cat: 'chem',
|
||
title: 'Химическая песочница',
|
||
desc: 'Смешивай реагенты, наблюдай реакции: осадки, газы, изменение цвета. Свободное экспериментирование.',
|
||
preview: P_CHEMSANDBOX },
|
||
{ id: 'stoichiometry', cat: 'chem',
|
||
title: 'Стехиометрия',
|
||
desc: 'Расчёты по уравнениям: масса, моль, объём. Лимитирующий реагент, выход. 10 реакций.',
|
||
preview: P_STOICHIOMETRY },
|
||
{ id: 'crystal', cat: 'chem',
|
||
title: 'Кристаллическая решётка',
|
||
desc: 'NaCl, алмаз, металл — интерактивная 3D-решётка, типы связей, вращение структуры.',
|
||
preview: P_CRYSTAL },
|
||
{ id: 'qualanalysis', cat: 'chem',
|
||
title: 'Качественный анализ',
|
||
desc: 'Определяй катионы и анионы качественными реакциями: осадки, газы, пламя. Два режима: guided и свободный эксперимент.',
|
||
preview: P_QUALANALYSIS },
|
||
{ id: 'periodic', cat: 'chem',
|
||
title: 'Периодическая таблица',
|
||
desc: '118 элементов: подсветка по типу/блоку, карточка элемента, боровские оболочки, графики свойств.',
|
||
preview: P_PERIODIC },
|
||
{ id: 'organic', cat: 'chem',
|
||
title: 'Органическая химия',
|
||
desc: 'Конструктор молекул с проверкой валентности, гомологические ряды с таблицей свойств, качественные реакции (бромная вода, KMnO₄, зеркало Толленса, Cu(OH)₂, FeCl₃, Na).',
|
||
preview: P_ORGANIC },
|
||
{ id: 'solutions', cat: 'chem',
|
||
title: 'Растворы',
|
||
desc: 'Калькулятор раствора: ω, ν, C_M, плотность. Разбавление и смешивание с визуализацией. Кривые растворимости S(T) для 8 веществ + задача на перекристаллизацию.',
|
||
preview: P_SOLUTIONS },
|
||
/* ── Биология ── */
|
||
{ id: 'celldivision', cat: 'bio',
|
||
title: 'Деление клетки',
|
||
desc: 'Митоз и мейоз: анимированные фазы, хромосомы, веретено деления, ядерная оболочка.',
|
||
preview: P_CELLDIVISION },
|
||
{ id: 'photosynthesis', cat: 'bio',
|
||
title: 'Фотосинтез и дыхание',
|
||
desc: 'Световые реакции в тилакоидах, цикл Кальвина, митохондриальное дыхание — молекулярная анимация.',
|
||
preview: P_PHOTOSYNTHESIS },
|
||
|
||
/* ── Игры ── */
|
||
{ id: 'angrybirds', cat: 'game',
|
||
title: 'Angry Birds Physics',
|
||
desc: 'Запускай птиц из рогатки, разрушай блоки, побеждай свиней. Реальная физика: гравитация, ветер, импульс. 6 уровней.',
|
||
preview: P_ANGRYBIRDS },
|
||
];
|
||
|
||
var _theoryOpen = false;
|
||
function toggleTheory() {
|
||
_theoryOpen = !_theoryOpen;
|
||
document.getElementById('theory-panel').classList.toggle('open', _theoryOpen);
|
||
const btn = document.getElementById('theory-toggle');
|
||
btn.style.background = _theoryOpen ? 'rgba(155,93,229,0.15)' : '';
|
||
btn.style.borderColor = _theoryOpen ? 'var(--violet)' : '';
|
||
btn.style.color = _theoryOpen ? 'var(--violet)' : '';
|
||
}
|
||
|
||
function loadTheory(simId) {
|
||
// Контент-движок: теория мигрированных симуляций берётся из манифеста реестра.
|
||
const _rm = window.LabRegistry ? window.LabRegistry.get(simId) : null;
|
||
const t = (_rm && _rm.theory) ? _rm.theory : THEORY[simId];
|
||
const el = document.getElementById('theory-content');
|
||
if (!t) { el.innerHTML = '<div class="tp-text" style="text-align:center;padding:40px 0;color:var(--text-3)">Теория для этой симуляции пока не добавлена</div>'; return; }
|
||
let html = `<div class="tp-title">${LS.icon('book-open',16)} ${t.title}</div>`;
|
||
for (const s of t.sections) {
|
||
html += '<div class="tp-section">';
|
||
if (s.head) html += `<div class="tp-section-head">${s.head}</div>`;
|
||
if (s.formula) html += `<div class="tp-formula" data-formula="${s.formula.replace(/"/g,'"')}"></div>`;
|
||
if (s.text) html += `<div class="tp-text">${s.text}</div>`;
|
||
if (s.vars) html += `<div class="tp-var-list">${s.vars.map(([v,d]) => `<div class="tp-var"><b>${v}</b> — ${d}</div>`).join('')}</div>`;
|
||
html += '</div>';
|
||
}
|
||
el.innerHTML = html;
|
||
// render KaTeX formulas
|
||
el.querySelectorAll('.tp-formula[data-formula]').forEach(div => {
|
||
try { katex.render(div.dataset.formula, div, { displayMode: true, throwOnError: false }); }
|
||
catch(e) { div.textContent = div.dataset.formula; }
|
||
});
|
||
}
|
||
|
||
/* ── embed mode + auto-open from ?sim= ── */
|
||
const _qp = new URLSearchParams(location.search);
|
||
var _embedMode = _qp.get('embed') === '1';
|
||
var _autoSim = _qp.get('sim');
|
||
|
||
/* ── Sim state relay (embed mode only) ──────────────────────────────── */
|
||
// Map simId → { getState, applyState } registered by openSim handlers
|
||
const _simStateRegistry = {};
|
||
|
||
function _registerSimState(simId, getState, applyState) {
|
||
_simStateRegistry[simId] = { getState, applyState };
|
||
}
|
||
|
||
let _lastEmittedState = null;
|
||
let _stateEmitInterval = null;
|
||
|
||
function _startStateEmit(simId) {
|
||
if (_stateEmitInterval) clearInterval(_stateEmitInterval);
|
||
_lastEmittedState = null;
|
||
_stateEmitInterval = setInterval(() => {
|
||
const reg = _simStateRegistry[simId];
|
||
if (!reg) return;
|
||
try {
|
||
const state = reg.getState();
|
||
const json = JSON.stringify(state);
|
||
if (json === _lastEmittedState) return;
|
||
_lastEmittedState = json;
|
||
window.parent.postMessage({ type: 'sim_state', simId, state }, '*');
|
||
} catch {}
|
||
}, 400);
|
||
}
|
||
|
||
function _stopStateEmit() {
|
||
if (_stateEmitInterval) { clearInterval(_stateEmitInterval); _stateEmitInterval = null; }
|
||
_lastEmittedState = null;
|
||
}
|
||
|
||
// Receive apply_sim_state from parent (students)
|
||
window.addEventListener('message', e => {
|
||
if (!_embedMode) return;
|
||
const d = e.data;
|
||
if (!d || d.type !== 'apply_sim_state') return;
|
||
const reg = _simStateRegistry[_autoSim];
|
||
if (!reg) return;
|
||
try {
|
||
reg.applyState(d.state);
|
||
_lastEmittedState = JSON.stringify(d.state); // suppress echo
|
||
} catch {}
|
||
});
|
||
|
||
if (_embedMode) {
|
||
document.querySelector('.sidebar').style.display = 'none';
|
||
document.querySelector('.sb-content').style.marginLeft = '0';
|
||
document.querySelector('.app-layout').classList.add('embed-mode');
|
||
document.getElementById('lab-home').style.display = 'none';
|
||
document.getElementById('theory-toggle').style.display = 'none';
|
||
if (_autoSim) {
|
||
document.getElementById('lab-sim').classList.add('open');
|
||
document.querySelector('.sim-topbar').style.display = 'none';
|
||
// defer until all external scripts are loaded
|
||
window.addEventListener('load', () => openSim(_autoSim));
|
||
}
|
||
} else {
|
||
/* init — fetch sim settings + permissions in parallel, then render */
|
||
const _permFetch = (!isTeacher && !isAdmin)
|
||
? LS.api('/api/permissions/me').catch(() => null)
|
||
: Promise.resolve(null);
|
||
|
||
Promise.all([
|
||
LS.api('/api/settings/sims').catch(() => ({})),
|
||
_permFetch,
|
||
]).then(([cfg, permData]) => {
|
||
_simModuleDisabled = cfg.module_disabled || false;
|
||
_disabledSimIds = new Set(cfg.disabled_ids || []);
|
||
|
||
// check simulations.access for students
|
||
if (!isTeacher && !isAdmin && permData) {
|
||
const p = permData.permissions?.find(p => p.key === 'simulations.access');
|
||
if (p && p.effective === false) {
|
||
document.getElementById('sim-grid').innerHTML =
|
||
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
|
||
<div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
||
<div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:var(--text);margin-bottom:6px">Доступ к симуляциям закрыт</div>
|
||
<div style="font-size:.88rem">Администратор ограничил доступ к лаборатории</div>
|
||
</div>`;
|
||
return;
|
||
}
|
||
// store quiz permission for later use
|
||
const qp = permData.permissions?.find(p => p.key === 'simulations.quiz');
|
||
window._simQuizAllowed = !qp || qp.effective !== false;
|
||
} else {
|
||
window._simQuizAllowed = true;
|
||
}
|
||
|
||
if (_simModuleDisabled) {
|
||
document.getElementById('sim-grid').innerHTML =
|
||
`<div style="grid-column:1/-1;padding:60px 0;text-align:center;color:var(--text-3)">
|
||
<div style="font-size:2rem;margin-bottom:12px"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
||
<div style="font-family:'Unbounded',sans-serif;font-size:1rem;font-weight:800;color:var(--text);margin-bottom:6px">Модуль симуляций отключён</div>
|
||
<div style="font-size:.88rem">Администратор временно отключил лабораторию</div>
|
||
</div>`;
|
||
} else {
|
||
renderSims();
|
||
if (_autoSim) openSim(_autoSim);
|
||
// hash-router: activate sim from URL fragment after catalogue renders
|
||
else _activateFromHash();
|
||
}
|
||
});
|
||
lucide.createIcons();
|
||
LS.notif.init();
|
||
}
|
||
|
||
/* ─── Hash router for sim deep-links ─────────────────────────────────────
|
||
URL pattern: /lab#sim/<name>
|
||
<name> matches SIMS[i].id (e.g. 'projectile', 'graph', 'chemsandbox').
|
||
F5 restores sim. Browser back/forward switches between sims.
|
||
Click on sim-card updates URL via wrapped openSim.
|
||
──────────────────────────────────────────────────────────────────────── */
|
||
|
||
// Build valid-id set from SIMS catalogue (filters out "coming soon" entries)
|
||
const _SIM_HASH_MAP = {};
|
||
SIMS.forEach(function(s) { if (s.id) { _SIM_HASH_MAP[s.id] = s.id; } });
|
||
// backward-compat aliases: old URLs redirect to unified emfield sim
|
||
_SIM_HASH_MAP['magnetic'] = 'magnetic';
|
||
_SIM_HASH_MAP['coulomb'] = 'coulomb';
|
||
// backward-compat aliases: old optics sims redirect to opticsbench
|
||
_SIM_HASH_MAP['thinlens'] = 'opticsbench';
|
||
_SIM_HASH_MAP['mirrors'] = 'opticsbench';
|
||
_SIM_HASH_MAP['refraction'] = 'opticsbench';
|
||
|
||
var _routerNavigating = false;
|
||
|
||
function _activateFromHash() {
|
||
var m = (location.hash || '').match(/^#sim\/([\w-]+)/);
|
||
if (!m) return false;
|
||
var simName = m[1];
|
||
if (!_SIM_HASH_MAP[simName]) {
|
||
// eslint-disable-next-line no-console
|
||
window.console && window.console.warn('lab-router: unknown sim', simName);
|
||
return false;
|
||
}
|
||
openSim(simName);
|
||
return true;
|
||
}
|
||
|
||
// Intercept openSim to push URL hash on user-initiated navigation
|
||
var _origOpenSim = openSim;
|
||
openSim = function(id) {
|
||
_origOpenSim(id);
|
||
if (!_routerNavigating && !_embedMode) {
|
||
var baseId = id.includes(':') ? id.split(':')[0] : id;
|
||
if (_SIM_HASH_MAP[baseId]) {
|
||
_routerNavigating = true;
|
||
location.hash = '#sim/' + baseId;
|
||
// use setTimeout so hashchange fires after flag is set
|
||
setTimeout(function() { _routerNavigating = false; }, 0);
|
||
}
|
||
}
|
||
};
|
||
|
||
/* ─── Sim Fade Transition + View Transitions API ─────────────────────────
|
||
Wraps openSim with a fade-out (150ms) → swap → fade-in (200ms) sequence.
|
||
If document.startViewTransition is available it is used for GPU-composited
|
||
cross-fade; otherwise the manual .sim-fading CSS class is toggled.
|
||
The hash-router wrap above runs synchronously during the transition so URL
|
||
updates are not delayed.
|
||
──────────────────────────────────────────────────────────────────────── */
|
||
var _hashRouterOpenSim = openSim; // reference after hash-router wrap
|
||
openSim = function(id) {
|
||
var labSim = document.getElementById('lab-sim');
|
||
if (!labSim) { _hashRouterOpenSim(id); return; }
|
||
|
||
function _doSwitch() {
|
||
labSim.classList.add('sim-fading');
|
||
setTimeout(function() {
|
||
_hashRouterOpenSim(id);
|
||
labSim.classList.remove('sim-fading');
|
||
}, 150);
|
||
}
|
||
|
||
if (typeof document.startViewTransition === 'function' && !_embedMode) {
|
||
document.startViewTransition(function() {
|
||
_hashRouterOpenSim(id);
|
||
});
|
||
} else {
|
||
_doSwitch();
|
||
}
|
||
};
|
||
|
||
// Intercept closeSim to clear hash when returning to home grid
|
||
var _origCloseSim = closeSim;
|
||
closeSim = function() {
|
||
_origCloseSim();
|
||
if (!_embedMode) {
|
||
_routerNavigating = true;
|
||
history.pushState(null, '', location.pathname + location.search);
|
||
setTimeout(function() { _routerNavigating = false; }, 0);
|
||
}
|
||
};
|
||
|
||
// Browser back/forward navigation
|
||
window.addEventListener('hashchange', function() {
|
||
if (_routerNavigating) return;
|
||
var hasHash = _activateFromHash();
|
||
if (!hasHash && document.getElementById('lab-sim').classList.contains('open')) {
|
||
_origCloseSim();
|
||
}
|
||
});
|