refactor(lab): phase 2 — extract inline glue script to /js/labs/lab-glue.js
lab.html 4324L → 3499L (-825L). 824 lines of glue code moved. Position preserved: lab-glue.js loads after lab-init.js, before newton.js / forcesandbox.js / etc. — same execution order as inline. node --check passes. /lab returns 200. /js/labs/lab-glue.js returns 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,825 @@
|
||||
'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() {
|
||||
const base = _catFilter === 'all' ? SIMS : SIMS.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}')"` : ''}>
|
||||
${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>`);
|
||||
|
||||
/* ── 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_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>`);
|
||||
|
||||
/* 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>`);
|
||||
|
||||
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: 'magnetic', cat: 'phys',
|
||||
title: 'Магнитное поле токов',
|
||||
desc: 'Размести провода с током — наблюдай суперпозицию полей: карта, силовые линии, вектора. Заряженная частица в поле.',
|
||||
preview: P_MAGNETIC },
|
||||
{ id: 'circuit', cat: 'phys',
|
||||
title: 'Электрические цепи',
|
||||
desc: 'Конструктор цепей из резисторов и конденсаторов. Законы Ома и Кирхгофа наглядно.',
|
||||
preview: P_CIRCUIT },
|
||||
{ id: 'coulomb', cat: 'phys',
|
||||
title: 'Закон Кулона',
|
||||
desc: 'Силовые линии и эквипотенциальные поверхности для системы точечных зарядов.',
|
||||
preview: P_FIELD },
|
||||
{ id: 'hydrostatics', cat: 'phys',
|
||||
title: 'Гидростатика',
|
||||
desc: 'Давление жидкости P=ρgh, закон Архимеда, сообщающиеся сосуды, поверхностное натяжение и капиллярность.',
|
||||
preview: P_SANDBOX },
|
||||
{ id: 'dynamics', cat: 'phys',
|
||||
title: 'Динамика',
|
||||
desc: 'Законы Ньютона, песочница сил, наклонная плоскость — всё в одном интерактивном модуле.',
|
||||
preview: P_SANDBOX },
|
||||
{ id: 'thinlens', cat: 'phys',
|
||||
title: 'Тонкая линза',
|
||||
desc: 'Двигай объект относительно линзы — формула линзы, мнимое и действительное изображение.',
|
||||
preview: P_LENS },
|
||||
{ id: 'refraction', cat: 'phys',
|
||||
title: 'Преломление света',
|
||||
desc: 'Луч на границе двух сред: закон Снеллиуса, угол Брюстера, полное внутреннее отражение.',
|
||||
preview: P_REFRACTION },
|
||||
{ id: 'mirrors', cat: 'phys',
|
||||
title: 'Зеркала',
|
||||
desc: 'Плоское, вогнутое и выпуклое зеркало: построение изображения тремя главными лучами.',
|
||||
preview: P_MIRROR },
|
||||
{ id: 'isoprocess', cat: 'phys',
|
||||
title: 'Изопроцессы',
|
||||
desc: 'PV-диаграмма для четырёх изопроцессов идеального газа. Расчёт работы, теплоты и внутренней энергии.',
|
||||
preview: P_ISOPROCESS },
|
||||
/* ── Химия / Молекулярная физика ── */
|
||||
{ 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: 'crystal', cat: 'chem',
|
||||
title: 'Кристаллическая решётка',
|
||||
desc: 'NaCl, алмаз, металл — интерактивная 3D-решётка, типы связей, вращение структуры.',
|
||||
preview: P_CRYSTAL },
|
||||
/* ── Биология ── */
|
||||
{ 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 },
|
||||
/* ── Физика: Волны ── */
|
||||
{ id: 'waves', cat: 'phys',
|
||||
title: 'Волны и звук',
|
||||
desc: 'Поперечные и продольные волны, суперпозиция, стоячие волны. Частота, амплитуда, фаза, гармоники.',
|
||||
preview: P_WAVES },
|
||||
];
|
||||
|
||||
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 ? '#9B5DE5' : '';
|
||||
btn.style.color = _theoryOpen ? '#9B5DE5' : '';
|
||||
}
|
||||
|
||||
function loadTheory(simId) {
|
||||
const t = 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:#0F172A;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:#0F172A;margin-bottom:6px">Модуль симуляций отключён</div>
|
||||
<div style="font-size:.88rem">Администратор временно отключил лабораторию</div>
|
||||
</div>`;
|
||||
} else {
|
||||
renderSims();
|
||||
if (_autoSim) openSim(_autoSim);
|
||||
}
|
||||
});
|
||||
lucide.createIcons();
|
||||
LS.notif.init();
|
||||
}
|
||||
+1
-826
@@ -3468,832 +3468,7 @@
|
||||
<script src="/js/search.js"></script>
|
||||
<script src="/js/mobile.js"></script>
|
||||
<script src="/js/labs/lab-init.js"></script>
|
||||
<script>
|
||||
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() {
|
||||
const base = _catFilter === 'all' ? SIMS : SIMS.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}')"` : ''}>
|
||||
${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>`);
|
||||
|
||||
/* ── 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_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>`);
|
||||
|
||||
/* 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>`);
|
||||
|
||||
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: 'magnetic', cat: 'phys',
|
||||
title: 'Магнитное поле токов',
|
||||
desc: 'Размести провода с током — наблюдай суперпозицию полей: карта, силовые линии, вектора. Заряженная частица в поле.',
|
||||
preview: P_MAGNETIC },
|
||||
{ id: 'circuit', cat: 'phys',
|
||||
title: 'Электрические цепи',
|
||||
desc: 'Конструктор цепей из резисторов и конденсаторов. Законы Ома и Кирхгофа наглядно.',
|
||||
preview: P_CIRCUIT },
|
||||
{ id: 'coulomb', cat: 'phys',
|
||||
title: 'Закон Кулона',
|
||||
desc: 'Силовые линии и эквипотенциальные поверхности для системы точечных зарядов.',
|
||||
preview: P_FIELD },
|
||||
{ id: 'hydrostatics', cat: 'phys',
|
||||
title: 'Гидростатика',
|
||||
desc: 'Давление жидкости P=ρgh, закон Архимеда, сообщающиеся сосуды, поверхностное натяжение и капиллярность.',
|
||||
preview: P_SANDBOX },
|
||||
{ id: 'dynamics', cat: 'phys',
|
||||
title: 'Динамика',
|
||||
desc: 'Законы Ньютона, песочница сил, наклонная плоскость — всё в одном интерактивном модуле.',
|
||||
preview: P_SANDBOX },
|
||||
{ id: 'thinlens', cat: 'phys',
|
||||
title: 'Тонкая линза',
|
||||
desc: 'Двигай объект относительно линзы — формула линзы, мнимое и действительное изображение.',
|
||||
preview: P_LENS },
|
||||
{ id: 'refraction', cat: 'phys',
|
||||
title: 'Преломление света',
|
||||
desc: 'Луч на границе двух сред: закон Снеллиуса, угол Брюстера, полное внутреннее отражение.',
|
||||
preview: P_REFRACTION },
|
||||
{ id: 'mirrors', cat: 'phys',
|
||||
title: 'Зеркала',
|
||||
desc: 'Плоское, вогнутое и выпуклое зеркало: построение изображения тремя главными лучами.',
|
||||
preview: P_MIRROR },
|
||||
{ id: 'isoprocess', cat: 'phys',
|
||||
title: 'Изопроцессы',
|
||||
desc: 'PV-диаграмма для четырёх изопроцессов идеального газа. Расчёт работы, теплоты и внутренней энергии.',
|
||||
preview: P_ISOPROCESS },
|
||||
/* ── Химия / Молекулярная физика ── */
|
||||
{ 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: 'crystal', cat: 'chem',
|
||||
title: 'Кристаллическая решётка',
|
||||
desc: 'NaCl, алмаз, металл — интерактивная 3D-решётка, типы связей, вращение структуры.',
|
||||
preview: P_CRYSTAL },
|
||||
/* ── Биология ── */
|
||||
{ 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 },
|
||||
/* ── Физика: Волны ── */
|
||||
{ id: 'waves', cat: 'phys',
|
||||
title: 'Волны и звук',
|
||||
desc: 'Поперечные и продольные волны, суперпозиция, стоячие волны. Частота, амплитуда, фаза, гармоники.',
|
||||
preview: P_WAVES },
|
||||
];
|
||||
|
||||
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 ? '#9B5DE5' : '';
|
||||
btn.style.color = _theoryOpen ? '#9B5DE5' : '';
|
||||
}
|
||||
|
||||
function loadTheory(simId) {
|
||||
const t = 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:#0F172A;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:#0F172A;margin-bottom:6px">Модуль симуляций отключён</div>
|
||||
<div style="font-size:.88rem">Администратор временно отключил лабораторию</div>
|
||||
</div>`;
|
||||
} else {
|
||||
renderSims();
|
||||
if (_autoSim) openSim(_autoSim);
|
||||
}
|
||||
});
|
||||
lucide.createIcons();
|
||||
LS.notif.init();
|
||||
}
|
||||
</script>
|
||||
<script src="/js/labs/lab-glue.js"></script>
|
||||
<script src="/js/labs/newton.js"></script>
|
||||
<script src="/js/labs/forcesandbox.js"></script>
|
||||
<script src="/js/labs/angrybirds.js"></script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 2: Extract inline `<script>` glue → `frontend/js/labs/lab-glue.js`
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Done
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
|
||||
Reference in New Issue
Block a user