Files
Learn_System/frontend/lab.html
T
Maxim Dolgolyov 46e6d82010 refactor(lab): phase 1 — extract inline style block to /css/lab.css
lab.html 5180L → 4324L (-856L). All CSS moved to frontend/css/lab.css

(855L). Added <link> tag after ls.css for proper cascade.

Visual rendering unchanged — pure file move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:35:07 +03:00

4325 lines
330 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Лаборатория — LearnSpace</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;700;800&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/css/ls.css" />
<link rel="stylesheet" href="/css/lab.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
</head>
<body>
<div class="app-layout">
<aside class="sidebar" id="app-sidebar"></aside>
<div class="notif-drop" id="notif-drop"></div>
<div class="sb-content">
<!-- ══════════ HOME VIEW ══════════ -->
<div id="lab-home">
<div class="lab-hero">
<div class="lab-hero-icon">
<i data-lucide="atom" style="width:30px;height:30px;stroke:#9B5DE5;stroke-width:1.5"></i>
</div>
<div>
<div class="lab-hero-title">Лаборатория</div>
<div class="lab-hero-sub">Интерактивные симуляции по математике и физике</div>
</div>
</div>
<div class="lab-filters">
<button class="lab-filter active" onclick="filterSims('all',this)">Все</button>
<button class="lab-filter" onclick="filterSims('math',this)">
<i data-lucide="sigma" style="width:12px;height:12px;vertical-align:-2px;margin-right:4px"></i>Математика
</button>
<button class="lab-filter" onclick="filterSims('phys',this)">
<i data-lucide="zap" style="width:12px;height:12px;vertical-align:-2px;margin-right:4px"></i>Физика
</button>
<button class="lab-filter" onclick="filterSims('chem',this)">
<i data-lucide="flask-conical" style="width:12px;height:12px;vertical-align:-2px;margin-right:4px"></i>Химия
</button>
<button class="lab-filter" onclick="filterSims('bio',this)">
<i data-lucide="dna" style="width:12px;height:12px;vertical-align:-2px;margin-right:4px"></i>Биология
</button>
<button class="lab-filter" onclick="filterSims('game',this)">
<i data-lucide="gamepad-2" style="width:12px;height:12px;vertical-align:-2px;margin-right:4px"></i>Игры
</button>
</div>
<div class="sim-grid" id="sim-grid"></div>
</div>
<!-- ══════════ SIM VIEW ══════════ -->
<div id="lab-sim">
<!-- top bar -->
<div class="sim-topbar">
<button class="sim-back" onclick="closeSim()">
<svg viewBox="0 0 24 24" fill="none"><polyline points="15 18 9 12 15 6"/></svg>
Назад
</button>
<div class="sim-topbar-title" id="sim-topbar-title">График функции</div>
<!-- graph controls -->
<div id="ctrl-graph" class="sim-zoom-btns">
<button class="zoom-btn" onclick="gSim.zoomIn()" title="Приблизить">
<svg viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<button class="zoom-btn" onclick="gSim.zoomOut()" title="Отдалить">
<svg viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<button class="zoom-btn" onclick="gSim.resetView()" title="Сброс вида">
<svg viewBox="0 0 24 24" fill="none"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- projectile controls -->
<div id="ctrl-proj" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" id="proj-play-btn" onclick="projPlayPause()" title="Запустить">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="zoom-btn" onclick="pSim && pSim.reset(); _projSyncPlayBtn()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 2px"></div>
<button class="zoom-btn" onclick="projSaveGhost()" title="Зафиксировать траекторию" style="font-size:.65rem;font-weight:800"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M12 2a7 7 0 00-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 00-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg></button>
<button class="zoom-btn" onclick="projClearGhosts()" title="Очистить следы" style="font-size:.65rem"><svg class="ic" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<!-- magnetic controls -->
<div id="ctrl-mag" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" id="mag-add-out" onclick="magMode('out')" title="Добавить провод • (ток на нас)" style="font-size:1rem"></button>
<button class="zoom-btn" id="mag-add-in" onclick="magMode('in')" title="Добавить провод × (ток от нас)" style="font-size:1rem">×</button>
<button class="zoom-btn" onclick="mSim && mSim.clearAll()" title="Очистить">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/></svg>
</button>
</div>
<!-- triangle controls -->
<div id="ctrl-tri" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="tSim && tSim.reset()" title="Сбросить треугольник">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- geometry controls -->
<div id="ctrl-geometry" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="geomSim&&geomSim.undo()" title="Отменить (Ctrl+Z)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 7v6h6"/><path d="M3 13A9 9 0 1 0 6 6.3L3 7"/></svg>
</button>
<button class="zoom-btn" onclick="geomSim&&geomSim.redo()" title="Повторить (Ctrl+Y)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 7v6h-6"/><path d="M21 13A9 9 0 1 1 18 6.3L21 7"/></svg>
</button>
<button class="zoom-btn" onclick="geomSim&&geomSim.deleteSelected()" title="Удалить выбранное">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
</button>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.1);margin:0 2px"></div>
<button class="zoom-btn" onclick="geomSim&&geomSim.resetView()" title="Сброс вида">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
<button class="zoom-btn" onclick="geomSim&&geomSim.exportPNG()" title="Экспорт PNG">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</button>
</div>
<!-- trig circle controls -->
<div id="ctrl-trigcircle" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="trigReset()" title="Сбросить на 45°">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- collision controls -->
<div id="ctrl-coll" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" id="coll-play-btn" onclick="collPlayPause()" title="Запустить">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</button>
<button class="zoom-btn" onclick="cSim && cSim.reset()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- molphys controls (unified: gas + brownian + states + diffusion) -->
<div id="ctrl-molphys" class="sim-zoom-btns" style="display:none">
<!-- diffusion-only: partition button -->
<span id="ctrl-mol-diff" style="display:none">
<button class="zoom-btn" onclick="diffSim && diffSim.togglePartition(); diffPartitionBtn()" title="Снять/поставить раздел" style="font-size:0.72rem;font-weight:800;font-family:Manrope,sans-serif" id="diffusion-part-btn">
‖ Раздел
</button>
</span>
<button class="zoom-btn" onclick="molReset()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- coulomb controls -->
<div id="ctrl-coulomb" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" id="csign-pos" onclick="coulombSign(1)" title="Добавить + заряд" style="font-size:1.1rem;font-weight:900;color:#EF476F">+</button>
<button class="zoom-btn" id="csign-neg" onclick="coulombSign(-1)" title="Добавить − заряд" style="font-size:1.1rem;font-weight:900;color:#4CC9F0"></button>
<button class="zoom-btn" onclick="csSim && csSim.reset(); csSim && csSim.draw(); _coulombUpdateUI(csSim&&csSim.info())" title="Очистить">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/></svg>
</button>
</div>
<!-- circuit controls -->
<div id="ctrl-circuit" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn circ-top-btn active" id="ctool-wire" onclick="circTool('wire',this)" title="Провод (W)" style="font-size:.7rem;font-weight:800">~</button>
<button class="zoom-btn circ-top-btn" id="ctool-resistor" onclick="circTool('resistor',this)" title="Резистор (R)" style="font-size:.6rem;font-weight:800">R</button>
<button class="zoom-btn circ-top-btn" id="ctool-battery" onclick="circTool('battery',this)" title="Батарея (B)" style="font-size:.6rem;font-weight:800">U</button>
<button class="zoom-btn circ-top-btn" id="ctool-capacitor" onclick="circTool('capacitor',this)" title="Конденсатор (C)" style="font-size:.6rem;font-weight:800">C</button>
<button class="zoom-btn circ-top-btn" id="ctool-diode" onclick="circTool('diode',this)" title="Диод (D)" style="font-size:.75rem"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg>|</button>
<button class="zoom-btn circ-top-btn" id="ctool-led" onclick="circTool('led',this)" title="LED" style="font-size:.6rem;font-weight:800">LED</button>
<button class="zoom-btn circ-top-btn" id="ctool-ac" onclick="circTool('ac',this)" title="AC источник" style="font-size:.65rem;font-weight:800">AC</button>
<button class="zoom-btn circ-top-btn" id="ctool-switch" onclick="circTool('switch',this)" title="Выключатель (S)" style="font-size:.7rem"></button>
<button class="zoom-btn circ-top-btn" id="ctool-lamp" onclick="circTool('lamp',this)" title="Лампа (L)" style="font-size:.75rem"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="zoom-btn circ-top-btn" id="ctool-ammeter" onclick="circTool('ammeter',this)" title="Амперметр (A)" style="font-size:.6rem;font-weight:800">А</button>
<button class="zoom-btn circ-top-btn" id="ctool-voltmeter" onclick="circTool('voltmeter',this)" title="Вольтметр (V)" style="font-size:.6rem;font-weight:800">V</button>
<button class="zoom-btn circ-top-btn" id="ctool-erase" onclick="circTool('erase',this)" title="Ластик (E)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M20 20H7L3 16l11.5-11.5a2 2 0 0 1 2.83 0l3.17 3.17a2 2 0 0 1 0 2.83L13 18"/><line x1="6" y1="14" x2="18" y2="2"/></svg>
</button>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 2px"></div>
<button class="zoom-btn" onclick="cirSim&&cirSim.undo()" title="Отменить (Ctrl+Z)" style="font-size:.65rem"><svg class="ic" viewBox="0 0 24 24"><polyline points="9 14 4 9 9 4"/><path d="M20 20v-7a4 4 0 0 0-4-4H4"/></svg></button>
<button class="zoom-btn" onclick="cirSim&&cirSim.redo()" title="Повторить (Ctrl+Y)" style="font-size:.65rem"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 14 20 9 15 4"/><path d="M4 20v-7a4 4 0 0 1 4-4h12"/></svg></button>
<button class="zoom-btn" onclick="cirSim&&cirSim.preset('clear')" title="Очистить">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/></svg>
</button>
</div>
<!-- reactions controls -->
<!-- chemistry controls (unified) -->
<div id="ctrl-chemistry" class="sim-zoom-btns" style="display:none">
<!-- kinetics tools -->
<span id="ctrl-chem-kin" style="display:contents">
<button class="zoom-btn" id="reac-pause-btn" onclick="reacTogglePause()" title="Пауза реакций" style="font-size:.68rem;font-weight:800;font-family:Manrope,sans-serif"><svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> Пауза</button>
</span>
<!-- flask tools -->
<span id="ctrl-chem-flask" style="display:none">
<button class="zoom-btn" onclick="flaskSim && flaskSim.dropMetal()" title="Бросить металл" style="font-size:.65rem;font-weight:800">⬇ Металл</button>
<button class="zoom-btn" id="flask-flame-btn" onclick="flaskToggleFlame()" title="Поджечь H₂" style="font-size:.75rem"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M12 2c.5 3.5-1.5 6-1.5 6 1 1.5 3 2 3 5a4 4 0 01-8 0c0-2 .5-3 1.5-4.5C8.5 6.5 7 4.5 7 4.5S9.5 2 12 2z"/></svg></button>
<button class="zoom-btn" id="flask-pause-btn" onclick="flaskTogglePause()" title="Пауза" style="font-size:.68rem;font-weight:800;font-family:Manrope,sans-serif"><svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg></button>
</span>
<!-- redox tools -->
<span id="ctrl-chem-redox" style="display:none">
<button class="zoom-btn" onclick="redoxStart()" title="Начать" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Старт</button>
</span>
<!-- ionex tools -->
<span id="ctrl-chem-ionex" style="display:none">
<button class="zoom-btn" onclick="ionexStart()" title="Смешать" style="font-size:.65rem;font-weight:800"><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> Смешать</button>
</span>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 3px"></div>
<button class="zoom-btn" onclick="chemReset()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- newton controls -->
<!-- dynamics controls (unified newton + sandbox) -->
<div id="ctrl-dynamics" class="sim-zoom-btns" style="display:none">
<!-- sandbox tools (shown in sandbox mode) -->
<span id="ctrl-dyn-sb" style="display:contents">
<button class="zoom-btn sb-tool-btn active" id="sbt-box" onclick="sbTool('box',this)" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/></svg> Блок</button>
<button class="zoom-btn sb-tool-btn" id="sbt-ball" onclick="sbTool('ball',this)" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="currentColor" stroke="none"/></svg> Шар</button>
<button class="zoom-btn sb-tool-btn" id="sbt-spring" onclick="sbTool('spring',this)" style="font-size:.65rem;font-weight:800">〜 Пружина</button>
<button class="zoom-btn sb-tool-btn" id="sbt-rope" onclick="sbTool('rope',this)" style="font-size:.65rem;font-weight:800">— Нить</button>
<button class="zoom-btn sb-tool-btn" id="sbt-anchor" onclick="sbTool('anchor',this)" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><path d="M12 2 2 12 12 22 22 12Z"/></svg> Якорь</button>
<button class="zoom-btn sb-tool-btn" id="sbt-erase" onclick="sbTool('erase',this)" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Ластик</button>
</span>
<!-- newton tools (shown in law modes) -->
<span id="ctrl-dyn-nw" style="display:none">
<button class="zoom-btn nscene-btn active" id="nscn-A" onclick="newtonScene('A',this)" style="font-size:.65rem;font-weight:800">A</button>
<button class="zoom-btn nscene-btn" id="nscn-B" onclick="newtonScene('B',this)" style="font-size:.65rem;font-weight:800">B</button>
<button class="zoom-btn nscene-btn" id="nscn-C" onclick="newtonScene('C',this)" style="font-size:.65rem;font-weight:800">C</button>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 3px"></div>
<button class="zoom-btn" id="newton-action-top" onclick="newtonAction()" style="font-size:.65rem;font-weight:800;font-family:Manrope,sans-serif"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Действие</button>
</span>
<div style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 3px"></div>
<button class="zoom-btn" onclick="dynPause()" title="Пауза" style="font-size:.75rem"><svg class="ic" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg></button>
<button class="zoom-btn" onclick="dynReset()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- chemsandbox controls -->
<div id="ctrl-chemsandbox" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="chemSandResetReaction()" title="Сбросить реакцию" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сброс реакции</button>
<button class="zoom-btn" onclick="chemSandReset()" title="Очистить всё" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Очистить</button>
</div>
<!-- celldivision controls -->
<div id="ctrl-celldivision" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="cdPrevPhase()" title="Предыдущая фаза" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polygon points="19 20 9 12 19 4 19 20"/></svg> Назад</button>
<button class="zoom-btn" onclick="cdNextPhase()" title="Следующая фаза" style="font-size:.65rem;font-weight:800">Далее <svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg></button>
<button class="zoom-btn" id="ctrl-cd-auto" onclick="cdAutoPlay(document.getElementById('cd-auto-btn'))" title="Авто" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Авто</button>
</div>
<!-- photosynthesis controls -->
<div id="ctrl-photosynthesis" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="psReset()" title="Сброс" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сброс</button>
</div>
<div id="ctrl-angrybirds" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" onclick="angryBirdsRestart()" title="Начать уровень заново" style="font-size:.65rem;font-weight:800"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сначала</button>
</div>
<!-- waves controls -->
<div id="ctrl-waves" class="sim-zoom-btns" style="display:none">
<button class="zoom-btn" id="waves-play-btn" onclick="wavesPlayPause()" title="Пауза/Старт">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
</button>
<button class="zoom-btn" onclick="wavesSim && wavesSim.reset()" title="Сброс">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
</button>
</div>
<!-- hydrostatics controls -->
<div id="ctrl-hydro" class="sim-zoom-btns" style="display:none">
<select id="hydro-mode-sel" onchange="hydroMode(this.value)" style="background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.15);border-radius:7px;padding:3px 8px;font-size:.72rem;cursor:pointer">
<option value="pressure">Давление P=ρgh</option>
<option value="surface">Пов. натяжение</option>
<option value="communicating">Сообщ. сосуды</option>
<option value="archimedes">Архимед</option>
</select>
<select id="hydro-liq-sel" onchange="hydroSim&&hydroSim.setLiquid(this.value)" style="background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.15);border-radius:7px;padding:3px 8px;font-size:.72rem;cursor:pointer">
<option value="water">Вода</option>
<option value="saltwater">Солёная вода</option>
<option value="oil">Масло</option>
<option value="alcohol">Спирт</option>
<option value="glycerin">Глицерин</option>
<option value="mercury">Ртуть</option>
</select>
<div id="hydro-arch-ctrl" style="display:none;gap:4px;align-items:center">
<select id="hydro-mat-sel" onchange="hydroSim&&hydroSim.setMaterial(this.value)" style="background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.15);border-radius:7px;padding:3px 8px;font-size:.72rem;cursor:pointer">
<option value="styrofoam">Пенопласт</option>
<option value="cork">Пробка</option>
<option value="wood">Дерево</option>
<option value="ice">Лёд</option>
<option value="plastic">Пластик</option>
<option value="glass">Стекло</option>
<option value="aluminum">Алюминий</option>
<option value="iron">Железо</option>
<option value="gold">Золото</option>
</select>
<button class="zoom-btn" onclick="hydroSim&&hydroSim.addBody()" title="Добавить тело">+ Тело</button>
<button class="zoom-btn" onclick="hydroSim&&hydroSim.clearBodies()" title="Очистить">Очистить</button>
</div>
<div id="hydro-comm-ctrl" style="display:none;gap:4px;align-items:center">
<label style="font-size:.72rem;color:rgba(255,255,255,.5)">Сосудов:</label>
<select onchange="hydroSim&&hydroSim.setNumVessels(+this.value)" style="background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.15);border-radius:7px;padding:3px 6px;font-size:.72rem;cursor:pointer">
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<button class="zoom-btn" id="hydro-valve-btn" onclick="hydroToggleValve()" title="Кран">Кран: откр.</button>
</div>
<div id="hydro-surf-ctrl" style="display:none;gap:4px;align-items:center">
<label style="font-size:.72rem;color:rgba(255,255,255,.5);white-space:nowrap">θ:</label>
<input type="range" min="0" max="160" value="20" step="5" style="width:72px;accent-color:#9B5DE5" oninput="hydroSim&&hydroSim.setContactAngle(+this.value);document.getElementById('hydro-theta-val').textContent=this.value+'\u00B0';document.getElementById('hydro-theta-lbl').textContent=this.value+'\u00B0';document.querySelector('#hydro-panel-theta input[type=range]').value=this.value">
<span id="hydro-theta-val" style="font-size:.72rem;color:#9B5DE5;min-width:28px;white-space:nowrap">20°</span>
<button class="zoom-btn" id="hydro-surf-toggle" onclick="hydroToggleSurface()" title="Переключить: капилляры / капля" style="white-space:nowrap">Капилляры</button>
</div>
</div>
<!-- theory toggle (all sims) -->
<button class="zoom-btn" id="theory-toggle" onclick="toggleTheory()" title="Теория и формулы" style="margin-left:auto">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
</button>
</div>
<!-- ── GRAPH sim body ── -->
<div id="sim-graph" class="sim-body-wrap">
<!-- left panel -->
<div class="graph-panel">
<div class="gp-section-title">Функции</div>
<!-- fn 1 -->
<div style="--fn-color:#9B5DE5">
<div class="fn-row">
<div class="fn-dot"></div>
<span class="fn-label">y =</span>
<input class="fn-input" id="fn0" placeholder="sin(x)" autocomplete="off" spellcheck="false" oninput="updateFn(0)" />
</div>
<div class="fn-preview" id="fn0-prev"></div>
<div class="fn-err" id="fn0-err">Синтаксическая ошибка</div>
</div>
<!-- fn 2 -->
<div style="--fn-color:#06D6E0">
<div class="fn-row">
<div class="fn-dot"></div>
<span class="fn-label">y =</span>
<input class="fn-input" id="fn1" placeholder="x^2 - 4" autocomplete="off" spellcheck="false" oninput="updateFn(1)" />
</div>
<div class="fn-preview" id="fn1-prev"></div>
<div class="fn-err" id="fn1-err">Синтаксическая ошибка</div>
</div>
<!-- fn 3 -->
<div style="--fn-color:#F15BB5">
<div class="fn-row">
<div class="fn-dot"></div>
<span class="fn-label">y =</span>
<input class="fn-input" id="fn2" placeholder="tg(x)" autocomplete="off" spellcheck="false" oninput="updateFn(2)" />
</div>
<div class="fn-preview" id="fn2-prev"></div>
<div class="fn-err" id="fn2-err">Синтаксическая ошибка</div>
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title">Примеры</div>
<div class="gp-preset-group">
<div class="gp-preset-label">Линейные / степенные</div>
<div class="presets-wrap">
<button class="preset-btn" onclick="applyPreset('2x-1')">2x1</button>
<button class="preset-btn" onclick="applyPreset('x^2')"></button>
<button class="preset-btn" onclick="applyPreset('x^2-4')">x²−4</button>
<button class="preset-btn" onclick="applyPreset('x^3-3x')">x³−3x</button>
<button class="preset-btn" onclick="applyPreset('x^4-4x^2+3')">x⁴−4x²+3</button>
</div>
</div>
<div class="gp-preset-group">
<div class="gp-preset-label">Тригонометрия</div>
<div class="presets-wrap">
<button class="preset-btn" onclick="applyPreset('sin(x)')">sin x</button>
<button class="preset-btn" onclick="applyPreset('cos(x)')">cos x</button>
<button class="preset-btn" onclick="applyPreset('tg(x)')">tg x</button>
<button class="preset-btn" onclick="applyPreset('sin(2x)')">sin 2x</button>
<button class="preset-btn" onclick="applyPreset('x*sin(x)')">x·sin x</button>
<button class="preset-btn" onclick="applyPreset('sin(x)/x')">sin(x)/x</button>
</div>
</div>
<div class="gp-preset-group">
<div class="gp-preset-label">Показательные / логарифмы</div>
<div class="presets-wrap">
<button class="preset-btn" onclick="applyPreset('exp(x)')"></button>
<button class="preset-btn" onclick="applyPreset('2^x')"></button>
<button class="preset-btn" onclick="applyPreset('ln(x)')">ln x</button>
<button class="preset-btn" onclick="applyPreset('log(x)')">log x</button>
</div>
</div>
<div class="gp-preset-group">
<div class="gp-preset-label">Прочие</div>
<div class="presets-wrap">
<button class="preset-btn" onclick="applyPreset('sqrt(x)')">√x</button>
<button class="preset-btn" onclick="applyPreset('1/x')">1/x</button>
<button class="preset-btn" onclick="applyPreset('abs(x)')">|x|</button>
<button class="preset-btn" onclick="applyPreset('floor(x)')">⌊x⌋</button>
<button class="preset-btn" onclick="applyPreset('1/(1+exp(-x))')">σ(x)</button>
</div>
</div>
<div style="margin-top:auto;padding-top:14px"></div>
<button class="gp-btn" onclick="clearAll()">
<svg viewBox="0 0 24 24" fill="none"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
Очистить всё
</button>
</div>
<!-- canvas area -->
<div class="graph-canvas-outer">
<div class="graph-canvas-wrap">
<canvas id="graph-canvas"></canvas>
</div>
<div class="graph-info-bar" id="graph-info-bar">
<div class="info-coord">
<span class="ic-label">x =</span>
<span class="ic-val" id="info-x"></span>
</div>
<div class="info-fn-val">
<div class="info-fn-dot" style="background:#9B5DE5;box-shadow:0 0 5px #9B5DE5"></div>
<span class="ic-label">y₁ =</span>
<span class="ic-val" id="info-y0"></span>
</div>
<div class="info-fn-val">
<div class="info-fn-dot" style="background:#06D6E0;box-shadow:0 0 5px #06D6E0"></div>
<span class="ic-label">y₂ =</span>
<span class="ic-val" id="info-y1"></span>
</div>
<div class="info-fn-val">
<div class="info-fn-dot" style="background:#F15BB5;box-shadow:0 0 5px #F15BB5"></div>
<span class="ic-label">y₃ =</span>
<span class="ic-val" id="info-y2"></span>
</div>
<div class="info-hint">Скролл — зум · Перетащи — панорама</div>
</div>
</div>
</div><!-- /#sim-graph -->
<!-- ── MAGNETIC sim body ── -->
<div id="sim-mag" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:248px;gap:0">
<!-- Mode -->
<div class="gp-section-title" style="margin-bottom:8px">Режим добавления</div>
<div style="display:flex;gap:6px;margin-bottom:12px">
<button id="mag-mode-out" class="mag-mode-btn active" onclick="magMode('out')">
<span style="font-size:1.2rem;font-weight:900;color:#06D6E0"></span>
Ток на нас
</button>
<button id="mag-mode-in" class="mag-mode-btn" onclick="magMode('in')">
<span style="font-size:1.1rem;font-weight:900;color:#F15BB5">×</span>
Ток от нас
</button>
</div>
<!-- Current -->
<div class="param-block">
<div class="param-header">
<span class="param-name">Сила тока I</span>
<span class="param-val" id="m-curI">6 А</span>
</div>
<input type="range" class="param-slider" id="sl-curI" min="1" max="20" value="6" oninput="magCurrentChange()">
</div>
<!-- Layers -->
<div class="gp-section-title" style="margin-top:4px;margin-bottom:8px">Визуализация</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label class="tri-layer-row active" id="ml-colormap" onclick="magLayer('colormap',this)">
<span class="tri-dot" style="background:linear-gradient(90deg,#9B5DE5,#06D6E0,#F15BB5)"></span>
<span class="tri-layer-name">Карта поля</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,.4)">hue = направление</span>
<span class="tri-toggle" style="background:var(--violet)"><span style="content:'';display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row active" id="ml-fieldlines" onclick="magLayer('fieldlines',this)">
<span class="tri-dot" style="background:#06D6E0;box-shadow:0 0 5px #06D6E0"></span>
<span class="tri-layer-name">Силовые линии</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,.4)">+ стрелки</span>
<span class="tri-toggle" style="background:var(--violet)"><span style="content:'';display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row" id="ml-vectors" onclick="magLayer('vectors',this)">
<span class="tri-dot" style="background:#9B5DE5;box-shadow:0 0 5px #9B5DE5"></span>
<span class="tri-layer-name">Векторное поле</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,.4)">сетка стрелок</span>
<span class="tri-toggle"></span>
</label>
</div>
<!-- Particle -->
<div class="gp-section-title" style="margin-bottom:8px">Частица</div>
<label class="tri-layer-row" id="ml-particle" onclick="magParticle(this)" style="margin-bottom:10px">
<span class="tri-dot" style="background:#ffff50;box-shadow:0 0 5px #ffff50"></span>
<span class="tri-layer-name">Заряженная частица</span>
<span class="tri-layer-hint" style="color:#ffff50">Сила Лоренца</span>
<span class="tri-toggle" id="ml-particle-toggle"></span>
</label>
<!-- Conductor -->
<div class="gp-section-title" style="margin-bottom:8px">Проводник в поле</div>
<label class="tri-layer-row" id="ml-cond" onclick="magCondToggle(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:#fbbf24;box-shadow:0 0 5px #fbbf24"></span>
<span class="tri-layer-name">Проводник (Ампер)</span>
<span class="tri-layer-hint" style="color:#fbbf24">F = I·L×B</span>
<span class="tri-toggle" id="ml-cond-toggle"></span>
</label>
<div class="param-block" id="cond-I-block" style="display:none;margin-bottom:10px">
<div class="param-header">
<span class="param-name">Ток проводника I꜀</span>
<span class="param-val" id="m-condI">8 А</span>
</div>
<input type="range" class="param-slider" id="sl-condI" min="1" max="20" value="8" oninput="magCondCurrentChange()">
</div>
<!-- Flux -->
<div class="gp-section-title" style="margin-bottom:8px">Магнитный поток</div>
<label class="tri-layer-row" id="ml-flux" onclick="magFluxToggle(this)" style="margin-bottom:10px">
<span class="tri-dot" style="background:#34d399;box-shadow:0 0 5px #34d399"></span>
<span class="tri-layer-name">Индикатор потока</span>
<span class="tri-layer-hint" style="color:#34d399">Φ = B·S</span>
<span class="tri-toggle" id="ml-flux-toggle"></span>
</label>
<!-- Presets -->
<div class="gp-section-title" style="margin-bottom:8px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="mSim && mSim.preset('single')">Один провод</button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('parallel')">Параллельные <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg><svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('anti')">Антипараллельные <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg><svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('solenoid')">Соленоид</button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('quadrupole')">Квадруполь</button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('ring')">Кольцо</button>
<button class="proj-preset-chip" onclick="mSim && mSim.preset('dipole')">Диполь</button>
</div>
<!-- Stats -->
<div style="margin-top:auto;padding-top:6px;display:flex;flex-direction:column;gap:5px">
<div class="tri-stats-grid" style="grid-template-columns:1fr 1fr">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Провода •</div>
<div class="tri-stat-v" id="ms-out" style="text-align:center;color:#06D6E0">0</div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Провода ×</div>
<div class="tri-stat-v" id="ms-in" style="text-align:center;color:#F15BB5">0</div>
</div>
<div style="font-size:0.68rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:4px">
Клик — добавить &nbsp;·&nbsp; ПКМ / 2×клик — удалить<br>
Перетащи провод для перемещения
</div>
</div>
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="mag-canvas"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar">
<div class="pstat">
<div class="pstat-label">Проводов</div>
<div class="pstat-val" id="mbar-total">0</div>
</div>
<div class="pstat">
<div class="pstat-label">• Ток на нас</div>
<div class="pstat-val" id="mbar-out" style="color:#06D6E0">0</div>
</div>
<div class="pstat">
<div class="pstat-label">× Ток от нас</div>
<div class="pstat-val" id="mbar-in" style="color:#F15BB5">0</div>
</div>
<div class="pstat">
<div class="pstat-label">Ток I</div>
<div class="pstat-val" id="mbar-I">6 А</div>
</div>
<div class="pstat">
<div class="pstat-label">Частица</div>
<div class="pstat-val" id="mbar-particle">выкл</div>
</div>
<div class="pstat">
<div class="pstat-label">Сила Ампера</div>
<div class="pstat-val" id="mbar-ampere" style="color:#fbbf24"></div>
</div>
<div class="pstat">
<div class="pstat-label">Поток Φ</div>
<div class="pstat-val" id="mbar-flux" style="color:#34d399"></div>
</div>
</div>
</div><!-- /#sim-mag -->
<!-- ── TRIANGLE sim body ── -->
<div id="sim-tri" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- left panel -->
<div class="proj-panel" style="width:240px;gap:0">
<!-- Layer toggles -->
<div class="gp-section-title" style="margin-bottom:10px">Слои</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label class="tri-layer-row" onclick="triToggle('medians',this)">
<span class="tri-dot" style="background:#22d55e;box-shadow:0 0 5px #22d55e"></span>
<span class="tri-layer-name">Медианы</span>
<span class="tri-layer-hint" style="color:#22d55e">G — центроид</span>
<span class="tri-toggle" id="tl-medians"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('altitudes',this)">
<span class="tri-dot" style="background:#f59e0b;box-shadow:0 0 5px #f59e0b"></span>
<span class="tri-layer-name">Высоты</span>
<span class="tri-layer-hint" style="color:#f59e0b">H — ортоцентр</span>
<span class="tri-toggle" id="tl-altitudes"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('bisectors',this)">
<span class="tri-dot" style="background:#ec4899;box-shadow:0 0 5px #ec4899"></span>
<span class="tri-layer-name">Биссектрисы</span>
<span class="tri-layer-hint" style="color:#ec4899">I — инцентр</span>
<span class="tri-toggle" id="tl-bisectors"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('circumcircle',this)">
<span class="tri-dot" style="background:#F15BB5;box-shadow:0 0 5px #F15BB5"></span>
<span class="tri-layer-name">Описанная окружность</span>
<span class="tri-layer-hint" style="color:#F15BB5">O, R</span>
<span class="tri-toggle" id="tl-circumcircle"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('incircle',this)">
<span class="tri-dot" style="background:#06D6E0;box-shadow:0 0 5px #06D6E0"></span>
<span class="tri-layer-name">Вписанная окружность</span>
<span class="tri-layer-hint" style="color:#06D6E0">I, r</span>
<span class="tri-toggle" id="tl-incircle"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('eulerLine',this)">
<span class="tri-dot" style="background:rgba(255,255,100,0.8);box-shadow:0 0 5px yellow"></span>
<span class="tri-layer-name">Прямая Эйлера</span>
<span class="tri-layer-hint" style="color:rgba(255,255,100,0.7)">O<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>G<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>H</span>
<span class="tri-toggle" id="tl-eulerLine"></span>
</label>
</div>
<div class="gp-section-title" style="margin-bottom:10px">Теоремы</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label class="tri-layer-row" onclick="triToggle('sineLaw',this)">
<span class="tri-dot" style="background:#60a5fa;box-shadow:0 0 5px #60a5fa"></span>
<span class="tri-layer-name">Теорема синусов</span>
<span class="tri-layer-hint" style="color:#60a5fa">a/sinA = 2R</span>
<span class="tri-toggle" id="tl-sineLaw"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('cosineLaw',this)">
<span class="tri-dot" style="background:#fbbf24;box-shadow:0 0 5px #fbbf24"></span>
<span class="tri-layer-name">Теорема косинусов</span>
<span class="tri-layer-hint" style="color:#fbbf24">c²=a²+b²−2ab·cosC</span>
<span class="tri-toggle" id="tl-cosineLaw"></span>
</label>
<label class="tri-layer-row" onclick="triToggle('pythagorean',this)">
<span class="tri-dot" style="background:#EF476F;box-shadow:0 0 5px #EF476F"></span>
<span class="tri-layer-name">Теорема Пифагора</span>
<span class="tri-layer-hint" style="color:#EF476F">a²+b² = c²</span>
<span class="tri-toggle" id="tl-pythagorean"></span>
</label>
</div>
<!-- Stats -->
<div class="gp-section-title" style="margin-bottom:8px">Стороны</div>
<div class="tri-stats-grid" style="margin-bottom:10px">
<span class="tri-stat-k" style="color:#9B5DE5">a</span><span class="tri-stat-v" id="ts-a"></span>
<span class="tri-stat-k" style="color:#06D6E0">b</span><span class="tri-stat-v" id="ts-b"></span>
<span class="tri-stat-k" style="color:#F15BB5">c</span><span class="tri-stat-v" id="ts-c"></span>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Углы</div>
<div class="tri-stats-grid" style="margin-bottom:10px">
<span class="tri-stat-k" style="color:#9B5DE5">∠A</span><span class="tri-stat-v" id="ts-A"></span>
<span class="tri-stat-k" style="color:#06D6E0">∠B</span><span class="tri-stat-v" id="ts-B"></span>
<span class="tri-stat-k" style="color:#F15BB5">∠C</span><span class="tri-stat-v" id="ts-C"></span>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Вычисляемые</div>
<div class="tri-stats-grid" style="margin-bottom:10px">
<span class="tri-stat-k">S</span><span class="tri-stat-v" id="ts-S"></span>
<span class="tri-stat-k">P</span><span class="tri-stat-v" id="ts-P"></span>
<span class="tri-stat-k">R</span><span class="tri-stat-v" id="ts-R"></span>
<span class="tri-stat-k">r</span><span class="tri-stat-v" id="ts-r"></span>
</div>
<!-- Type badge -->
<div style="margin-top:auto;padding-top:10px">
<div style="font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3);margin-bottom:6px">Тип</div>
<div id="ts-type" style="
padding:8px 14px;border-radius:12px;
background:rgba(155,93,229,0.12);border:1.5px solid rgba(155,93,229,0.25);
font-family:'Manrope',sans-serif;font-size:0.82rem;font-weight:700;
color:#9B5DE5;text-align:center;
"></div>
<div style="margin-top:8px;font-size:0.7rem;color:var(--text-3);text-align:center;line-height:1.5">
Перетащи вершины<br>A, B, C для изменения
</div>
</div>
</div><!-- /.proj-panel -->
<!-- canvas -->
<div class="proj-canvas-outer">
<canvas id="tri-canvas"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<!-- stats bar -->
<div class="proj-stats-bar">
<div class="pstat">
<div class="pstat-label">Сторона a</div>
<div class="pstat-val" id="tbar-a"></div>
</div>
<div class="pstat">
<div class="pstat-label">Сторона b</div>
<div class="pstat-val" id="tbar-b"></div>
</div>
<div class="pstat">
<div class="pstat-label">Сторона c</div>
<div class="pstat-val" id="tbar-c"></div>
</div>
<div class="pstat">
<div class="pstat-label">Площадь S</div>
<div class="pstat-val" id="tbar-S"></div>
</div>
<div class="pstat">
<div class="pstat-label">Периметр P</div>
<div class="pstat-val" id="tbar-P"></div>
</div>
<div class="pstat">
<div class="pstat-label">R / r</div>
<div class="pstat-val" id="tbar-Rr"></div>
</div>
</div>
</div><!-- /#sim-tri -->
<!-- ══════════════════════════════════════════════
ТРИГОНОМЕТРИЧЕСКАЯ ОКРУЖНОСТЬ
══════════════════════════════════════════════ -->
<div id="sim-trigcircle" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- left panel -->
<div class="proj-panel" style="width:240px;gap:0">
<!-- Function toggles -->
<div class="gp-section-title" style="margin-bottom:10px">Отрезки</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:14px">
<label class="tri-layer-row active" onclick="trigToggle('sin',this)">
<span class="tri-dot" style="background:#EF476F;box-shadow:0 0 5px #EF476F"></span>
<span class="tri-layer-name">sin</span>
<span class="tri-layer-hint" style="color:#EF476F">вертикаль</span>
<span class="tri-toggle" id="trig-tl-sin"></span>
</label>
<label class="tri-layer-row active" onclick="trigToggle('cos',this)">
<span class="tri-dot" style="background:#06D6E0;box-shadow:0 0 5px #06D6E0"></span>
<span class="tri-layer-name">cos</span>
<span class="tri-layer-hint" style="color:#06D6E0">горизонталь</span>
<span class="tri-toggle" id="trig-tl-cos"></span>
</label>
<label class="tri-layer-row" onclick="trigToggle('tan',this)">
<span class="tri-dot" style="background:#FFD166;box-shadow:0 0 5px #FFD166"></span>
<span class="tri-layer-name">tg</span>
<span class="tri-layer-hint" style="color:#FFD166">касательная</span>
<span class="tri-toggle" id="trig-tl-tan"></span>
</label>
<label class="tri-layer-row" onclick="trigToggle('cot',this)">
<span class="tri-dot" style="background:#7BF5A4;box-shadow:0 0 5px #7BF5A4"></span>
<span class="tri-layer-name">ctg</span>
<span class="tri-layer-hint" style="color:#7BF5A4">кокасательная</span>
<span class="tri-toggle" id="trig-tl-cot"></span>
</label>
</div>
<!-- Graph function selector -->
<div class="gp-section-title" style="margin-bottom:8px">График</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:14px">
<button class="trig-fn-btn active" onclick="trigSetGraphFn('sin',this)" style="--fc:#EF476F">sin</button>
<button class="trig-fn-btn" onclick="trigSetGraphFn('cos',this)" style="--fc:#06D6E0">cos</button>
<button class="trig-fn-btn" onclick="trigSetGraphFn('tan',this)" style="--fc:#FFD166">tg</button>
<button class="trig-fn-btn" onclick="trigSetGraphFn('cot',this)" style="--fc:#7BF5A4">ctg</button>
</div>
<!-- Values display -->
<div class="gp-section-title" style="margin-bottom:8px">Значения</div>
<div class="tri-stats-grid" style="margin-bottom:14px">
<span class="tri-stat-k" style="color:#EF476F">sin</span><span class="tri-stat-v" id="trig-v-sin"></span>
<span class="tri-stat-k" style="color:#06D6E0">cos</span><span class="tri-stat-v" id="trig-v-cos"></span>
<span class="tri-stat-k" style="color:#FFD166">tg</span><span class="tri-stat-v" id="trig-v-tan"></span>
<span class="tri-stat-k" style="color:#7BF5A4">ctg</span><span class="tri-stat-v" id="trig-v-cot"></span>
</div>
<!-- Notable angles -->
<div class="gp-section-title" style="margin-bottom:8px">Табличные углы</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:14px">
<button class="preset-btn" onclick="trigGoTo(0)"></button>
<button class="preset-btn" onclick="trigGoTo(Math.PI/6)">30°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI/4)">45°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI/3)">60°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI/2)">90°</button>
<button class="preset-btn" onclick="trigGoTo(2*Math.PI/3)">120°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI)">180°</button>
<button class="preset-btn" onclick="trigGoTo(3*Math.PI/2)">270°</button>
</div>
<!-- Angle info -->
<div style="margin-top:auto;padding-top:10px">
<div style="font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3);margin-bottom:6px">Угол</div>
<div id="trig-angle-badge" style="
padding:10px 14px;border-radius:12px;
background:rgba(155,93,229,0.12);border:1.5px solid rgba(155,93,229,0.25);
font-family:'Manrope',sans-serif;font-size:0.85rem;font-weight:700;
color:#9B5DE5;text-align:center;line-height:1.6;
">45° = π/4</div>
<div style="margin-top:8px;font-size:0.7rem;color:var(--text-3);text-align:center;line-height:1.5">
Перетащи точку по окружности<br>или выбери табличный угол
</div>
</div>
</div><!-- /.proj-panel -->
<!-- canvas -->
<div class="proj-canvas-outer">
<canvas id="trigcircle-canvas"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<!-- stats bar -->
<div class="proj-stats-bar">
<div class="pstat">
<div class="pstat-label">Угол</div>
<div class="pstat-val" id="trigbar-angle" style="color:#9B5DE5">45°</div>
</div>
<div class="pstat">
<div class="pstat-label">sin</div>
<div class="pstat-val" id="trigbar-sin" style="color:#EF476F"></div>
</div>
<div class="pstat">
<div class="pstat-label">cos</div>
<div class="pstat-val" id="trigbar-cos" style="color:#06D6E0"></div>
</div>
<div class="pstat">
<div class="pstat-label">tg</div>
<div class="pstat-val" id="trigbar-tan" style="color:#FFD166"></div>
</div>
<div class="pstat">
<div class="pstat-label">ctg</div>
<div class="pstat-val" id="trigbar-cot" style="color:#7BF5A4"></div>
</div>
<div class="pstat">
<div class="pstat-label">Четверть</div>
<div class="pstat-val" id="trigbar-quad" style="color:#9B5DE5"></div>
</div>
</div>
</div><!-- /#sim-trigcircle -->
<!-- ══════════════════════════════════════════════
ИДЕАЛЬНЫЙ ГАЗ
══════════════════════════════════════════════ -->
<!-- ══════════════════════════════════════════════
МОЛЕКУЛЯРНАЯ ФИЗИКА (газ + броуновское + состояния + диффузия)
══════════════════════════════════════════════ -->
<div id="sim-molphys" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:248px;gap:0">
<!-- Mode selector -->
<div style="display:flex;gap:3px;margin-bottom:12px;padding:3px;background:rgba(255,255,255,0.04);border-radius:10px;border:1px solid var(--border)">
<button class="mag-mode-btn mol-mode active" id="mol-mode-gas" onclick="molMode('gas',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Газ</button>
<button class="mag-mode-btn mol-mode" id="mol-mode-brownian" onclick="molMode('brownian',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Броуновское</button>
<button class="mag-mode-btn mol-mode" id="mol-mode-states" onclick="molMode('states',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Фазы</button>
<button class="mag-mode-btn mol-mode" id="mol-mode-diffusion" onclick="molMode('diffusion',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Диффузия</button>
</div>
<!-- ── Gas panel ── -->
<div id="mol-panel-gas">
<div class="gp-section-title" style="margin-bottom:8px">Параметры газа</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Число молекул N</span><span class="param-val" id="g-N">80</span></div>
<input type="range" class="param-slider" id="sl-gN" min="20" max="200" value="80" oninput="gasNChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Температура T</span><span class="param-val" id="g-T">1.0 у.е.</span></div>
<input type="range" class="param-slider" id="sl-gT" min="2" max="30" value="10" oninput="gasTChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Поршень (объём)</span><span class="param-val" id="g-piston">100%</span></div>
<input type="range" class="param-slider" id="sl-gPiston" min="30" max="100" value="100" oninput="gasPistonChange()">
</div>
<div style="margin-bottom:10px">
<button class="proj-preset-chip" id="gas-vec-btn" onclick="gasToggleVectors(this)" style="width:100%">Векторы скоростей: Выкл</button>
</div>
<div class="gp-section-title" style="margin-top:4px;margin-bottom:8px">Состояние</div>
<div class="tri-stats-grid" style="grid-template-columns:1fr 1fr;margin-bottom:8px">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Давление P</div>
<div class="tri-stat-v" id="gstat-P" style="text-align:center;color:#9B5DE5"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Объём V</div>
<div class="tri-stat-v" id="gstat-V" style="text-align:center;color:#06D6E0"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">PV</div>
<div class="tri-stat-v" id="gstat-PV" style="text-align:center;color:#34d399"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">⟨v⟩ средн.</div>
<div class="tri-stat-v" id="gstat-v" style="text-align:center;color:#FFD166"></div>
</div>
<div style="font-size:.65rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:auto">Стенки светятся по P · Поршень перетащи мышью</div>
</div>
<!-- ── Brownian panel ── -->
<div id="mol-panel-brownian" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Молекул газа N</span><span class="param-val" id="br-N">120</span></div>
<input type="range" class="param-slider" id="sl-brN" min="30" max="300" value="120" oninput="brownNChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Температура T</span><span class="param-val" id="br-T">1.0 у.е.</span></div>
<input type="range" class="param-slider" id="sl-brT" min="2" max="20" value="10" oninput="brownTChange()">
</div>
<div class="gp-section-title" style="margin-top:10px;margin-bottom:8px">Статистика частицы B</div>
<div class="tri-stats-grid" style="grid-template-columns:1fr 1fr;margin-bottom:8px">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">|Δr| смещение</div>
<div class="tri-stat-v" id="brstat-dr" style="text-align:center;color:#FFD166">0</div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">MSD</div>
<div class="tri-stat-v" id="brstat-msd" style="text-align:center;color:#9B5DE5">0</div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Скорость v</div>
<div class="tri-stat-v" id="brstat-v" style="text-align:center;color:#06D6E0">0</div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Шагов</div>
<div class="tri-stat-v" id="brstat-steps" style="text-align:center;color:#34d399">0</div>
</div>
<div style="margin-bottom:10px">
<button class="proj-preset-chip" onclick="if(brownSim) brownSim.resetOrigin()" style="width:100%">Сбросить отсчёт (Origin)</button>
</div>
<div style="font-size:.65rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:auto">График MSD нарастает линейно — закон диффузии</div>
</div>
<!-- ── States panel ── -->
<div id="mol-panel-states" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Управление</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Температура T</span><span class="param-val" id="st-T">0.15</span></div>
<input type="range" class="param-slider" id="sl-stT" min="1" max="100" value="15" oninput="statesTChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Частиц N</span><span class="param-val" id="st-N">64</span></div>
<input type="range" class="param-slider" id="sl-stN" min="16" max="100" step="4" value="64" oninput="statesNChange()">
</div>
<div style="display:flex;gap:6px;margin-bottom:14px;flex-wrap:wrap">
<button class="proj-preset-chip" onclick="statesPreset(0.05)">Твёрдое</button>
<button class="proj-preset-chip" onclick="statesPreset(0.30)">Жидкость</button>
<button class="proj-preset-chip" onclick="statesPreset(0.75)">Газ</button>
</div>
<button id="states-vec-btn" class="proj-preset-chip" style="margin-bottom:10px;width:100%" onclick="statesToggleVectors(this)">Векторы скоростей: Выкл</button>
<div class="gp-section-title" style="margin-bottom:8px">Фаза и энергия</div>
<div class="tri-stats-grid" style="grid-template-columns:1fr 1fr;margin-bottom:8px">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Фаза</div>
<div class="tri-stat-v" id="ststat-phase" style="text-align:center"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Кин. энергия</div>
<div class="tri-stat-v" id="ststat-KE" style="text-align:center;color:#FFD166"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Пот. энергия</div>
<div class="tri-stat-v" id="ststat-PE" style="text-align:center;color:#9B5DE5"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Давление</div>
<div class="tri-stat-v" id="ststat-P" style="text-align:center;color:#EF476F"></div>
</div>
<div style="font-size:.65rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:auto">LJ потенциал · g(r) — структура · цвет = скорость</div>
</div>
<!-- ── Diffusion panel ── -->
<div id="mol-panel-diffusion" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Молекул каждого вида</span><span class="param-val" id="df-N">60</span></div>
<input type="range" class="param-slider" id="sl-dfN" min="20" max="120" value="60" oninput="diffNChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Температура T</span><span class="param-val" id="df-T">1.0 у.е.</span></div>
<input type="range" class="param-slider" id="sl-dfT" min="2" max="20" value="10" oninput="diffTChange()">
</div>
<label class="tri-layer-row active" id="df-part-row" onclick="diffPartitionToggle(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:rgba(255,255,255,0.5)"></span>
<span class="tri-layer-name">Перегородка</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,.4)"></span>
<span class="tri-toggle" id="df-part-toggle" style="background:var(--violet)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row" id="df-pore-row" onclick="diffPoreToggle(this)" style="margin-bottom:10px">
<span class="tri-dot" style="background:rgba(255,179,71,0.5)"></span>
<span class="tri-layer-name">Пора (щель)</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,.4)"></span>
<span class="tri-toggle" id="df-pore-toggle" style="background:rgba(255,255,255,0.15)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:2px"></span></span>
</label>
<div class="gp-section-title" style="margin-bottom:8px">Концентрации</div>
<div class="tri-stats-grid" style="grid-template-columns:1fr 1fr;margin-bottom:8px">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#06D6E0">Лево A</div>
<div class="tri-stat-v" id="dfstat-LA" style="text-align:center;color:#06D6E0"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#F15BB5">Лево B</div>
<div class="tri-stat-v" id="dfstat-LB" style="text-align:center;color:#F15BB5"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#06D6E0">Право A</div>
<div class="tri-stat-v" id="dfstat-RA" style="text-align:center;color:#06D6E0"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#F15BB5">Право B</div>
<div class="tri-stat-v" id="dfstat-RB" style="text-align:center;color:#F15BB5"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Смешивание</div>
<div class="tri-stat-v" id="dfstat-mix" style="text-align:center;color:#34d399"></div>
</div>
<div style="font-size:.65rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:auto">A (cyan) — лево · B (розовый) — право</div>
</div>
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="gas-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
<canvas id="brownian-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
<canvas id="states-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
<canvas id="diffusion-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar" id="mol-stats-bar">
<div class="pstat"><div class="pstat-label" id="mpbar-l1"></div><div class="pstat-val" id="mpbar-v1" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label" id="mpbar-l2"></div><div class="pstat-val" id="mpbar-v2" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label" id="mpbar-l3"></div><div class="pstat-val" id="mpbar-v3" style="color:#9B5DE5"></div></div>
<div class="pstat"><div class="pstat-label" id="mpbar-l4"></div><div class="pstat-val" id="mpbar-v4" style="color:#34d399"></div></div>
<div class="pstat"><div class="pstat-label" id="mpbar-l5"></div><div class="pstat-val" id="mpbar-v5"></div></div>
</div>
</div><!-- /#sim-molphys -->
<!-- ══════════════════════════════════════════════
ЗАКОН КУЛОНА
══════════════════════════════════════════════ -->
<div id="sim-coulomb" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:240px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Знак заряда</div>
<div style="display:flex;gap:6px;margin-bottom:12px">
<button class="mag-mode-btn active" id="cbtn-pos" onclick="coulombSign(1)" style="flex:1" title="Добавить положительный заряд">
<span style="font-size:1.3rem;font-weight:900;color:#EF476F">+</span> Положит.
</button>
<button class="mag-mode-btn" id="cbtn-neg" onclick="coulombSign(-1)" style="flex:1" title="Добавить отрицательный заряд">
<span style="font-size:1.3rem;font-weight:900;color:#4CC9F0"></span> Отрицат.
</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Слои</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label class="tri-layer-row active" id="cl-colormap" onclick="coulombLayer('colormap',this)">
<span class="tri-dot" style="background:#9B5DE5;box-shadow:0 0 5px #9B5DE5"></span>
<span class="tri-layer-name">Карта потенциала</span>
<span class="tri-layer-hint" style="color:#9B5DE5">V</span>
<span class="tri-toggle" style="background:var(--violet)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row active" id="cl-fieldlines" onclick="coulombLayer('fieldlines',this)">
<span class="tri-dot" style="background:rgba(255,255,255,0.8);box-shadow:0 0 5px rgba(255,255,255,0.6)"></span>
<span class="tri-layer-name">Линии поля</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,0.5)">E</span>
<span class="tri-toggle" style="background:var(--violet)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row" id="cl-vectors" onclick="coulombLayer('vectors',this)">
<span class="tri-dot" style="background:rgba(255,255,255,0.4)"></span>
<span class="tri-layer-name">Векторы E</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,0.3)"><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></span>
<span class="tri-toggle"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:2px"></span></span>
</label>
<label class="tri-layer-row active" id="cl-equipotentials" onclick="coulombLayer('equipotentials',this)">
<span class="tri-dot" style="background:rgba(255,255,255,0.5)"></span>
<span class="tri-layer-name">Эквипотенциали</span>
<span class="tri-layer-hint" style="color:rgba(255,255,255,0.3)">V=const</span>
<span class="tri-toggle" style="background:var(--violet)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:14px"></span></span>
</label>
<label class="tri-layer-row" id="cl-forces" onclick="coulombLayer('forces',this)">
<span class="tri-dot" style="background:#FFD166;box-shadow:0 0 5px #FFD166"></span>
<span class="tri-layer-name">Силы Кулона</span>
<span class="tri-layer-hint" style="color:#FFD166">F</span>
<span class="tri-toggle"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:2px"></span></span>
</label>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="coulombPreset('dipole')">Диполь ±</button>
<button class="proj-preset-chip" onclick="coulombPreset('equal')">Два + заряда</button>
<button class="proj-preset-chip" onclick="coulombPreset('quadrupole')">Квадруполь</button>
<button class="proj-preset-chip" onclick="coulombPreset('ring')">Кольцо</button>
</div>
<div style="margin-top:auto;display:flex;flex-direction:column;gap:5px">
<div class="tri-stats-grid" style="grid-template-columns:auto 1fr">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Зарядов</div>
<div class="tri-stat-v" id="cs-total" style="color:#9B5DE5">0</div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Курсор E</div>
<div class="tri-stat-v" id="cs-curE" style="color:rgba(255,255,255,0.6)"></div>
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)">Курсор V</div>
<div class="tri-stat-v" id="cs-curV" style="color:rgba(255,255,255,0.5)"></div>
</div>
<div style="font-size:0.68rem;color:var(--text-3);text-align:center;line-height:1.6;margin-top:4px">
Клик — добавить &nbsp;·&nbsp; ПКМ — удалить<br>
Перетащи заряд для перемещения
</div>
</div>
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="coulomb-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar">
<div class="pstat"><div class="pstat-label">Зарядов</div><div class="pstat-val" id="csbar-total">0</div></div>
<div class="pstat"><div class="pstat-label">+ Позитивных</div><div class="pstat-val" id="csbar-pos" style="color:#EF476F">0</div></div>
<div class="pstat"><div class="pstat-label"> Негативных</div><div class="pstat-val" id="csbar-neg" style="color:#4CC9F0">0</div></div>
<div class="pstat"><div class="pstat-label">max |E|</div><div class="pstat-val" id="csbar-maxE"></div></div>
<div class="pstat"><div class="pstat-label">E курсора</div><div class="pstat-val" id="csbar-curE" style="color:rgba(255,255,255,0.7)"></div></div>
</div>
</div><!-- /#sim-coulomb -->
<!-- ══════════════════════════════════════════════
ЭЛЕКТРИЧЕСКИЕ ЦЕПИ
══════════════════════════════════════════════ -->
<div id="sim-circuit" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:240px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Инструмент</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip circ-tool-btn active" id="ptool-wire" onclick="circTool('wire',this)" data-tool="wire">Провод</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-resistor" onclick="circTool('resistor',this)" data-tool="resistor">Резистор</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-battery" onclick="circTool('battery',this)" data-tool="battery">Батарея</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-capacitor" onclick="circTool('capacitor',this)" data-tool="capacitor">Конденсатор</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-diode" onclick="circTool('diode',this)" data-tool="diode">Диод</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-led" onclick="circTool('led',this)" data-tool="led">LED</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-ac" onclick="circTool('ac',this)" data-tool="ac">AC источник</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-switch" onclick="circTool('switch',this)" data-tool="switch">Выключатель</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-lamp" onclick="circTool('lamp',this)" data-tool="lamp">Лампа</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-ammeter" onclick="circTool('ammeter',this)" data-tool="ammeter">Амперметр</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-voltmeter" onclick="circTool('voltmeter',this)" data-tool="voltmeter">Вольтметр</button>
<button class="proj-preset-chip circ-tool-btn" id="ptool-erase" onclick="circTool('erase',this)" data-tool="erase">Ластик</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Сопротивление R</span>
<span class="param-val" id="circ-R-val">10 Ω</span>
</div>
<input type="range" class="param-slider" id="sl-circR" min="1" max="100" value="10" oninput="circRChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Напряжение U</span>
<span class="param-val" id="circ-U-val">9 В</span>
</div>
<input type="range" class="param-slider" id="sl-circU" min="1" max="24" value="9" oninput="circUChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Ёмкость C</span>
<span class="param-val" id="circ-C-val">100 µF</span>
</div>
<input type="range" class="param-slider" id="sl-circC" min="10" max="1000" value="100" step="10" oninput="circCChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Частота AC</span>
<span class="param-val" id="circ-F-val">2 Гц</span>
</div>
<input type="range" class="param-slider" id="sl-circF" min="1" max="20" value="2" oninput="circFChange()">
</div>
<div class="gp-section-title" style="margin-bottom:8px;margin-top:4px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="circPreset('serial')">Последовательное</button>
<button class="proj-preset-chip" onclick="circPreset('parallel')">Параллельное</button>
<button class="proj-preset-chip" onclick="circPreset('lamp')">Лампа + выкл</button>
<button class="proj-preset-chip" onclick="circPreset('divider')">Делитель V</button>
<button class="proj-preset-chip" onclick="circPreset('bridge')">Мост Уитстона</button>
<button class="proj-preset-chip" onclick="circPreset('diode')">Диод</button>
<button class="proj-preset-chip" onclick="circPreset('led')">LED</button>
<button class="proj-preset-chip" onclick="circPreset('rc')">RC-цепь</button>
<button class="proj-preset-chip" onclick="circPreset('ac')">AC-цепь</button>
</div>
<div style="margin-top:auto;font-size:0.68rem;color:var(--text-3);text-align:center;line-height:1.7;padding-top:4px">
Тяни узлы для рисования · ПКМ — удалить<br>
2×клик по выключателю — вкл/выкл · Ctrl+Z отмена
</div>
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="circuit-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar">
<div class="pstat"><div class="pstat-label">Компонентов</div><div class="pstat-val" id="cirbar-comps">0</div></div>
<div class="pstat"><div class="pstat-label">Напряжение U</div><div class="pstat-val" id="cirbar-U" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Ток I</div><div class="pstat-val" id="cirbar-I" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Мощность P</div><div class="pstat-val" id="cirbar-P" style="color:#7BF5A4"></div></div>
<div class="pstat"><div class="pstat-label">Статус</div><div class="pstat-val" id="cirbar-status"></div></div>
</div>
</div><!-- /#sim-circuit -->
<!-- ══════════════════════════════════════════════
КИНЕТИКА РЕАКЦИЙ
══════════════════════════════════════════════ -->
<!-- ══════════════════════════════════════════════
ХИМИЯ (unified: кинетика + колба + ОВР + ионный обмен)
══════════════════════════════════════════════ -->
<div id="sim-chemistry" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:248px;gap:0">
<!-- Mode selector -->
<div style="display:flex;gap:3px;margin-bottom:12px;padding:3px;background:rgba(255,255,255,0.04);border-radius:10px;border:1px solid var(--border)">
<button class="mag-mode-btn chem-mode active" id="chem-mode-kinetics" onclick="chemMode('kinetics',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Кинетика</button>
<button class="mag-mode-btn chem-mode" id="chem-mode-flask" onclick="chemMode('flask',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Колба</button>
<button class="mag-mode-btn chem-mode" id="chem-mode-redox" onclick="chemMode('redox',this)" style="flex:1;font-size:.63rem;padding:5px 2px">ОВР</button>
<button class="mag-mode-btn chem-mode" id="chem-mode-ionex" onclick="chemMode('ionex',this)" style="flex:1;font-size:.63rem;padding:5px 2px">Ионный</button>
</div>
<!-- ── Kinetics panel ── -->
<div id="chem-panel-kinetics">
<div class="gp-section-title" style="margin-bottom:6px">Параметры</div>
<div class="param-block" style="margin-bottom:7px">
<div class="param-header"><span class="param-name">Молекул N</span><span class="param-val" id="reac-N-val">28</span></div>
<input type="range" class="param-slider" id="sl-reacN" min="5" max="60" value="28" oninput="reacNChange()">
</div>
<div class="param-block" style="margin-bottom:7px">
<div class="param-header"><span class="param-name">Температура T</span><span class="param-val" id="reac-T-val">1.2</span></div>
<input type="range" class="param-slider" id="sl-reacT" min="2" max="40" value="12" oninput="reacTChange()">
</div>
<div class="param-block" style="margin-bottom:7px">
<div class="param-header"><span class="param-name">Энергия активации Ea</span><span class="param-val" id="reac-Ea-val">2.0</span></div>
<input type="range" class="param-slider" id="sl-reacEa" min="5" max="50" value="20" oninput="reacEaChange()">
</div>
<div class="gp-section-title" style="margin-bottom:6px">Режим</div>
<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:8px">
<button class="proj-preset-chip reac-mode-btn active" id="rmode-forward" onclick="reacMode('forward',this)" style="text-align:left"><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> Прямая (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)</button>
<button class="proj-preset-chip reac-mode-btn" id="rmode-reversible" onclick="reacMode('reversible',this)" style="text-align:left">⇌ Обратимая</button>
<button class="proj-preset-chip reac-mode-btn" id="rmode-chain" onclick="reacMode('chain',this)" style="text-align:left"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg> Цепная</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:5px">
<button class="proj-preset-chip" onclick="reacPreset('simple')">Простая</button>
<button class="proj-preset-chip" onclick="reacPreset('reversible')">Равновесие</button>
<button class="proj-preset-chip" onclick="reacPreset('hot')">Горячая</button>
<button class="proj-preset-chip" onclick="reacPreset('cold')">Холодная</button>
<button class="proj-preset-chip" onclick="reacPreset('chain')">Цепная</button>
</div>
</div><!-- /chem-panel-kinetics -->
<!-- ── Flask panel ── -->
<div id="chem-panel-flask" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Металл</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip flask-metal-btn active" id="fmetal-Zn" onclick="flaskMetal('Zn',this)">Zn</button>
<button class="proj-preset-chip flask-metal-btn" id="fmetal-Fe" onclick="flaskMetal('Fe',this)">Fe</button>
<button class="proj-preset-chip flask-metal-btn" id="fmetal-Mg" onclick="flaskMetal('Mg',this)">Mg</button>
<button class="proj-preset-chip flask-metal-btn" id="fmetal-Cu" onclick="flaskMetal('Cu',this)">Cu</button>
<button class="proj-preset-chip flask-metal-btn" id="fmetal-Na" onclick="flaskMetal('Na',this)">Na</button>
<button class="proj-preset-chip flask-metal-btn" id="fmetal-Al" onclick="flaskMetal('Al',this)">Al</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Реагент</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip flask-acid-btn active" id="facid-HCl" onclick="flaskAcid('HCl',this)">HCl</button>
<button class="proj-preset-chip flask-acid-btn" id="facid-H2SO4" onclick="flaskAcid('H2SO4',this)">H₂SO₄</button>
<button class="proj-preset-chip flask-acid-btn" id="facid-H2O" onclick="flaskAcid('H2O',this)">H₂O</button>
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Концентрация</span><span class="param-val" id="flask-conc-val">35%</span></div>
<input type="range" class="param-slider" id="sl-flask-conc" min="5" max="98" value="35" oninput="flaskConcChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Темп. окружения</span><span class="param-val" id="flask-temp-val">20°C</span></div>
<input type="range" class="param-slider" id="sl-flask-temp" min="5" max="80" value="20" oninput="flaskTempChange()">
</div>
<div style="display:flex;gap:6px;margin-bottom:10px;margin-top:2px">
<button class="proj-preset-chip" style="flex:1;background:rgba(239,71,111,0.18);border-color:rgba(239,71,111,0.4)"
onclick="flaskSim && flaskSim.dropMetal()">⬇ Бросить металл</button>
<button class="proj-preset-chip" id="flask-flame-panel" style="flex:0 0 auto"
onclick="flaskToggleFlame()"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M12 2c.5 3.5-1.5 6-1.5 6 1 1.5 3 2 3 5a4 4 0 01-8 0c0-2 .5-3 1.5-4.5C8.5 6.5 7 4.5 7 4.5S9.5 2 12 2z"/></svg></button>
</div>
<div style="font-size:.65rem;color:var(--text-3);text-align:center;line-height:1.7;margin-top:auto;padding-top:4px">
Выбери металл + кислоту<br>H₂ накапливается — поднеси огонь!
</div>
</div><!-- /chem-panel-flask -->
<!-- ── Redox panel ── -->
<div id="chem-panel-redox" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Реакция</div>
<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:12px">
<button class="proj-preset-chip redox-rxn-btn active" id="rdxrxn-fe_cu" onclick="redoxRxn('fe_cu',this)" style="text-align:left;font-size:.72rem">Fe + CuSO₄ <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> FeSO₄ + Cu<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip redox-rxn-btn" id="rdxrxn-zn_hcl" onclick="redoxRxn('zn_hcl',this)" style="text-align:left;font-size:.72rem">Zn + 2HCl <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> ZnCl₂ + H₂<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></button>
<button class="proj-preset-chip redox-rxn-btn" id="rdxrxn-cl2_ki" onclick="redoxRxn('cl2_ki',this)" style="text-align:left;font-size:.72rem">Cl₂ + 2KI <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> I₂<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg> + 2KCl</button>
<button class="proj-preset-chip redox-rxn-btn" id="rdxrxn-kmno4" onclick="redoxRxn('kmno4',this)" style="text-align:left;font-size:.72rem">KMnO₄ + FeSO₄ (кислая)</button>
<button class="proj-preset-chip redox-rxn-btn" id="rdxrxn-cu_fecl3" onclick="redoxRxn('cu_fecl3',this)" style="text-align:left;font-size:.72rem">Cu + 2FeCl₃ <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> CuCl₂ + 2FeCl₂</button>
</div>
<button class="proj-launch-btn" style="margin-bottom:8px" onclick="redoxStart()"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Начать реакцию</button>
<button class="proj-reset-btn" onclick="redoxReset()"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сброс</button>
<div style="margin-top:auto;padding-top:6px;font-size:.65rem;color:var(--text-3);line-height:1.7;text-align:center">
Синие e⁻ — перенос электронов<br>Цифры — степень окисления
</div>
</div><!-- /chem-panel-redox -->
<!-- ── Ion exchange panel ── -->
<div id="chem-panel-ionex" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">Реакция</div>
<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:12px">
<button class="proj-preset-chip ionex-rxn-btn active" id="ioxrxn-ba_so4" onclick="ionexRxn('ba_so4',this)" style="text-align:left;font-size:.72rem">BaCl₂ + Na₂SO₄ <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> BaSO₄<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip ionex-rxn-btn" id="ioxrxn-ag_cl" onclick="ionexRxn('ag_cl',this)" style="text-align:left;font-size:.72rem">AgNO₃ + NaCl <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> AgCl<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip ionex-rxn-btn" id="ioxrxn-co3_hcl" onclick="ionexRxn('co3_hcl',this)" style="text-align:left;font-size:.72rem">Na₂CO₃ + 2HCl <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> CO₂<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></button>
<button class="proj-preset-chip ionex-rxn-btn" id="ioxrxn-pb_i" onclick="ionexRxn('pb_i',this)" style="text-align:left;font-size:.72rem">Pb(NO₃)₂ + 2KI <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> PbI₂<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip ionex-rxn-btn" id="ioxrxn-ca_co3" onclick="ionexRxn('ca_co3',this)" style="text-align:left;font-size:.72rem">CaCl₂ + Na₂CO₃ <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> CaCO₃<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
</div>
<button class="proj-launch-btn" style="margin-bottom:8px" onclick="ionexStart()"><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> Смешать растворы</button>
<button class="proj-reset-btn" onclick="ionexReset()"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сброс</button>
<div style="margin-top:auto;padding-top:6px;font-size:.65rem;color:var(--text-3);line-height:1.7;text-align:center">
Яркие ионы = реагируют<br>Тусклые = зрители · <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg> осадок · <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg> газ
</div>
</div><!-- /chem-panel-ionex -->
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="reactions-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
<canvas id="flask-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;cursor:default"></canvas>
<canvas id="redox-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;cursor:default"></canvas>
<canvas id="ionexchange-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;cursor:default"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar" id="chem-stats-bar">
<div class="pstat"><div class="pstat-label" id="chbar-l1"></div><div class="pstat-val" id="chbar-v1" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label" id="chbar-l2"></div><div class="pstat-val" id="chbar-v2" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label" id="chbar-l3"></div><div class="pstat-val" id="chbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label" id="chbar-l4"></div><div class="pstat-val" id="chbar-v4"></div></div>
<div class="pstat"><div class="pstat-label" id="chbar-l5"></div><div class="pstat-val" id="chbar-v5" style="color:#7BF5A4"></div></div>
</div>
</div><!-- /#sim-chemistry -->
<!-- ── DYNAMICS sim body (Newton + Sandbox unified) ── -->
<div id="sim-dynamics" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:248px;gap:0">
<!-- ══ Mode selector ══ -->
<div style="display:flex;gap:4px;margin-bottom:12px;padding:3px;background:rgba(255,255,255,0.04);border-radius:10px;border:1px solid var(--border)">
<button class="mag-mode-btn dyn-mode active" id="dyn-mode-sandbox" onclick="dynMode('sandbox',this)" style="flex:1;font-size:.68rem;padding:5px 0">Песочница</button>
<button class="mag-mode-btn dyn-mode" id="dyn-mode-law1" onclick="dynMode('law1',this)" style="flex:1;font-size:.68rem;padding:5px 0">I закон</button>
<button class="mag-mode-btn dyn-mode" id="dyn-mode-law2" onclick="dynMode('law2',this)" style="flex:1;font-size:.68rem;padding:5px 0">II закон</button>
<button class="mag-mode-btn dyn-mode" id="dyn-mode-law3" onclick="dynMode('law3',this)" style="flex:1;font-size:.68rem;padding:5px 0">III закон</button>
</div>
<!-- ══ Newton controls (shown in law modes) ══ -->
<div id="dyn-newton-panel" style="display:none">
<!-- Scene selector -->
<div class="gp-section-title" style="margin-bottom:8px">Сцена</div>
<div style="display:flex;gap:5px;margin-bottom:12px" id="newton-scene-row">
<button class="mag-mode-btn nscene-btn active" id="nscn-panel-A" onclick="newtonScene('A',null,this)" style="flex:1;font-size:.72rem">A</button>
<button class="mag-mode-btn nscene-btn" id="nscn-panel-B" onclick="newtonScene('B',null,this)" style="flex:1;font-size:.72rem">B</button>
<button class="mag-mode-btn nscene-btn" id="nscn-panel-C" onclick="newtonScene('C',null,this)" style="flex:1;font-size:.72rem">C</button>
</div>
<!-- Scene description -->
<div id="newton-scene-desc" style="font-size:0.71rem;color:var(--text-3);line-height:1.6;margin-bottom:10px;padding:6px 8px;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid var(--border)">
Закон инерции: тело движется равномерно при отсутствии сил.
</div>
<!-- Parameter sliders -->
<div id="newton-mu-block" class="param-block">
<div class="param-header"><span class="param-name">Коэфф. трения μ</span><span class="param-val" id="newton-mu-val">0.20</span></div>
<input type="range" class="param-slider" id="sl-newton-mu" min="0" max="1" step="0.01" value="0.20" oninput="newtonMuChange()">
</div>
<div id="newton-mass1-block" class="param-block" style="display:none">
<div class="param-header"><span class="param-name">Масса m₁ (кг)</span><span class="param-val" id="newton-m1-val">5 кг</span></div>
<input type="range" class="param-slider" id="sl-newton-m1" min="1" max="20" value="5" oninput="newtonMass1Change()">
</div>
<div id="newton-mass2-block" class="param-block" style="display:none">
<div class="param-header"><span class="param-name">Масса m₂ (кг)</span><span class="param-val" id="newton-m2-val">5 кг</span></div>
<input type="range" class="param-slider" id="sl-newton-m2" min="1" max="20" value="5" oninput="newtonMass2Change()">
</div>
<div id="newton-force-block" class="param-block" style="display:none">
<div class="param-header"><span class="param-name">Сила F (Н)</span><span class="param-val" id="newton-F-val">20 Н</span></div>
<input type="range" class="param-slider" id="sl-newton-F" min="1" max="60" value="20" oninput="newtonForceChange()">
</div>
<!-- Presets -->
<div class="gp-section-title" style="margin-top:6px">Пресеты</div>
<div id="newton-presets" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="newtonPreset('space')">Космос</button>
<button class="proj-preset-chip" onclick="newtonPreset('ice')">Лёд</button>
<button class="proj-preset-chip" onclick="newtonPreset('asphalt')">Асфальт</button>
<button class="proj-preset-chip" onclick="newtonPreset('rubber')">Резина</button>
</div>
<!-- Action -->
<div style="margin-top:auto;padding-top:10px;display:flex;flex-direction:column;gap:6px">
<button class="proj-launch-btn" id="newton-action-main" onclick="newtonAction()">
<span id="newton-action-label"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Действие</span>
</button>
<button class="proj-reset-btn" onclick="_resetNewtonScene()">
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
Сброс сцены
</button>
</div>
</div><!-- /#dyn-newton-panel -->
<!-- ══ Sandbox controls (shown in sandbox mode) ══ -->
<div id="dyn-sandbox-panel">
<div class="gp-section-title" style="margin-bottom:8px">Инструмент</div>
<div style="display:flex;gap:5px;margin-bottom:4px">
<button class="mag-mode-btn sb-panel-tool active" id="sbpt-box" onclick="sbTool('box',this)" style="flex:1;font-size:.72rem"><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/></svg> Блок</button>
<button class="mag-mode-btn sb-panel-tool" id="sbpt-ball" onclick="sbTool('ball',this)" style="flex:1;font-size:.72rem"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="currentColor" stroke="none"/></svg> Шар</button>
<button class="mag-mode-btn sb-panel-tool" id="sbpt-erase" onclick="sbTool('erase',this)" style="flex:1;font-size:.72rem"><svg class="ic" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div style="display:flex;gap:5px;margin-bottom:10px">
<button class="mag-mode-btn sb-panel-tool" id="sbpt-spring" onclick="sbTool('spring',this)" style="flex:1;font-size:.72rem">〜 Пружина</button>
<button class="mag-mode-btn sb-panel-tool" id="sbpt-rope" onclick="sbTool('rope',this)" style="flex:1;font-size:.72rem">— Нить</button>
<button class="mag-mode-btn sb-panel-tool" id="sbpt-anchor" onclick="sbTool('anchor',this)" style="flex:1;font-size:.72rem"><svg class="ic" viewBox="0 0 24 24"><path d="M12 2 2 12 12 22 22 12Z"/></svg> Якорь</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Режим силы</div>
<div style="display:flex;gap:5px;margin-bottom:10px">
<button class="mag-mode-btn sb-fmode active" id="sbfm-constant" onclick="sbForceMode('constant',this)" style="flex:1;font-size:.72rem">Постоянная</button>
<button class="mag-mode-btn sb-fmode" id="sbfm-impulse" onclick="sbForceMode('impulse',this)" style="flex:1;font-size:.72rem">Импульс</button>
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Масса нового тела</span><span class="param-val" id="sb-mass-val">5 кг</span></div>
<input type="range" class="param-slider" id="sl-sb-mass" min="1" max="30" value="5" oninput="sbMassChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Упругость</span><span class="param-val" id="sb-rest-val">0.65</span></div>
<input type="range" class="param-slider" id="sl-sb-rest" min="0" max="1" step="0.05" value="0.65" oninput="sbRestChange()">
</div>
<div class="param-block" id="sb-spring-block" style="display:none">
<div class="param-header"><span class="param-name">Жёсткость пружины k</span><span class="param-val" id="sb-springk-val">120 Н</span></div>
<input type="range" class="param-slider" id="sl-sb-springk" min="10" max="600" step="10" value="120" oninput="sbSpringKChange()">
</div>
<div class="gp-section-title" style="margin-top:6px;margin-bottom:6px">Мир</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-gravity" checked onchange="sbWorldToggle()"> Гравитация</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-floor" checked onchange="sbWorldToggle()"> Пол</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-walls" checked onchange="sbWorldToggle()"> Стенки</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-airdrag" onchange="sbWorldToggle()"> Сопротивление воздуха</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-ramp" onchange="sbRampToggle()"> Наклонная плоскость</label>
</div>
<div id="sb-ramp-block" style="display:none">
<div class="param-block">
<div class="param-header"><span class="param-name">Угол α</span><span class="param-val" id="sb-angle-val">30°</span></div>
<input type="range" class="param-slider" id="sl-sb-angle" min="5" max="75" value="30" oninput="sbAngleChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Трение горки μ</span><span class="param-val" id="sb-rampmu-val">0.20</span></div>
<input type="range" class="param-slider" id="sl-sb-rampmu" min="0" max="1" step="0.01" value="0.20" oninput="sbRampMuChange()">
</div>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer;margin-bottom:6px"><input type="checkbox" id="sb-decomp" checked onchange="sbDecompToggle()"> Разложение сил</label>
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Трение пола μ</span><span class="param-val" id="sb-floormu-val">0.30</span></div>
<input type="range" class="param-slider" id="sl-sb-floormu" min="0" max="1" step="0.01" value="0.30" oninput="sbFloorMuChange()">
</div>
<div class="gp-section-title" style="margin-top:6px;margin-bottom:6px">Отображение</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:10px">
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-forces" checked onchange="sbDisplayToggle()"> Силы</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-vel" checked onchange="sbDisplayToggle()"> Скорости</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-fbd" onchange="sbDisplayToggle()"> FBD-диаграмма</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-energy" checked onchange="sbDisplayToggle()"> Энергия</label>
<label style="display:flex;align-items:center;gap:6px;font-size:.72rem;color:var(--text-2);cursor:pointer"><input type="checkbox" id="sb-trail" checked onchange="sbDisplayToggle()"> Стробоскоп</label>
</div>
<div class="gp-section-title" style="margin-top:2px;margin-bottom:6px">Время</div>
<div style="display:flex;gap:5px;margin-bottom:10px">
<button class="mag-mode-btn sb-time" onclick="sbTimeScale(0.25,this)" style="flex:1;font-size:.68rem">×0.25</button>
<button class="mag-mode-btn sb-time" onclick="sbTimeScale(0.5,this)" style="flex:1;font-size:.68rem">×0.5</button>
<button class="mag-mode-btn sb-time active" onclick="sbTimeScale(1,this)" style="flex:1;font-size:.68rem">×1</button>
<button class="mag-mode-btn sb-time" onclick="sbTimeScale(2,this)" style="flex:1;font-size:.68rem">×2</button>
</div>
<div class="gp-section-title" style="margin-top:2px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="sbPreset('freefall')">Падение</button>
<button class="proj-preset-chip" onclick="sbPreset('collision')">Столкновение</button>
<button class="proj-preset-chip" onclick="sbPreset('friction')">Трение</button>
<button class="proj-preset-chip" onclick="sbPreset('tug')">Перетягивание</button>
<button class="proj-preset-chip" onclick="sbPreset('balance')">Равновесие</button>
<button class="proj-preset-chip" onclick="sbPreset('ramp_slide')">Горка</button>
<button class="proj-preset-chip" onclick="sbPreset('ramp_angle')"><svg class="ic" viewBox="0 0 24 24"><path d="m8 3 4 8 5-5 5 15H2L8 3z"/></svg> Крутой спуск</button>
<button class="proj-preset-chip" onclick="sbPreset('ramp_friction')"><svg class="ic" viewBox="0 0 24 24"><rect width="20" height="5" x="2" y="3" rx="1"/><rect width="8" height="5" x="2" y="11" rx="1"/><rect width="8" height="5" x="14" y="11" rx="1"/><rect width="20" height="5" x="2" y="19" rx="1"/></svg> Трение на горке</button>
<button class="proj-preset-chip" onclick="sbPreset('spring_bounce')">〜 Пружина</button>
<button class="proj-preset-chip" onclick="sbPreset('spring_chain')">〜 Цепочка</button>
<button class="proj-preset-chip" onclick="sbPreset('pendulum')">⬤ Маятник</button>
<button class="proj-preset-chip" onclick="sbPreset('atwood')"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93 17.66 6.34M21 12h-2M19.07 19.07l-1.41-1.41M12 21v-2M6.34 17.66 4.93 19.07M3 12h2M4.93 4.93l1.41 1.41M12 3v2"/><circle cx="12" cy="12" r="7"/></svg> Машина Атвуда</button>
<button class="proj-preset-chip" onclick="sbPreset('two_body')"><svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="21" x2="12" y2="3"/><polyline points="7 8 12 3 17 8"/><polyline points="17 16 12 21 7 16"/></svg> Два тела</button>
<button class="proj-preset-chip" onclick="sbPreset('elastic_collision')"><svg class="ic" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg> Упругий удар</button>
<button class="proj-preset-chip" onclick="sbPreset('inelastic_collision')"><svg class="ic" viewBox="0 0 24 24"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z"/></svg> Неупругий</button>
<button class="proj-preset-chip" onclick="sbPreset('newton_cradle')"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93 17.66 6.34M21 12h-2M19.07 19.07l-1.41-1.41M12 21v-2M6.34 17.66 4.93 19.07M3 12h2M4.93 4.93l1.41 1.41M12 3v2"/><circle cx="12" cy="12" r="7"/></svg> Колыбель Ньютона</button>
<button class="proj-preset-chip" onclick="sbPreset('harmonic_oscillator')">〜 Осциллятор</button>
<button class="proj-preset-chip" onclick="sbPreset('double_pendulum')">⬤⬤ Двойной маятник</button>
<button class="proj-preset-chip" onclick="sbPreset('coupled_oscillators')">〜〜 Связанные</button>
<button class="proj-preset-chip" onclick="sbPreset('stacked_boxes')"><svg class="ic" viewBox="0 0 24 24"><rect width="20" height="5" x="2" y="3" rx="1"/><rect width="8" height="5" x="2" y="11" rx="1"/><rect width="8" height="5" x="14" y="11" rx="1"/><rect width="20" height="5" x="2" y="19" rx="1"/></svg> Стопка</button>
<button class="proj-preset-chip" onclick="sbPreset('pulley_ramp')"><svg class="ic" viewBox="0 0 24 24"><path d="m8 3 4 8 5-5 5 15H2L8 3z"/></svg> Горка+блок</button>
<button class="proj-preset-chip" onclick="sbPreset('circular_motion')">⭕ Круговое</button>
<button class="proj-preset-chip" onclick="sbPreset('projectile_angle')"><svg class="ic" viewBox="0 0 24 24"><line x1="7" y1="17" x2="17" y2="7"/><polyline points="7 7 17 7 17 17"/></svg> Снаряд 45°</button>
</div>
<div style="font-size:.65rem;color:var(--text-3);line-height:1.5;margin-top:auto;padding:6px 8px;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid var(--border)">
ЛКМ — создать тело · Drag — сила<br>
Shift+drag — импульс · ПКМ — удалить<br>
DblClick — закрепить / открепить<br>
Пружина / Нить — кликни 2 тела
</div>
</div><!-- /#dyn-sandbox-panel -->
</div><!-- /.proj-panel -->
<div class="proj-canvas-outer">
<canvas id="newton-canvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;cursor:default"></canvas>
<canvas id="sandbox-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<div class="proj-stats-bar" id="dyn-stats-bar">
<div class="pstat"><div class="pstat-label" id="dbar-l1">Тел</div><div class="pstat-val" id="dbar-v1" style="color:#06D6E0">0</div></div>
<div class="pstat"><div class="pstat-label" id="dbar-l2">KE (Дж)</div><div class="pstat-val" id="dbar-v2" style="color:#4CC9F0">0</div></div>
<div class="pstat"><div class="pstat-label" id="dbar-l3">PE (Дж)</div><div class="pstat-val" id="dbar-v3" style="color:#7BF5A4">0</div></div>
<div class="pstat"><div class="pstat-label" id="dbar-l4">ΣF</div><div class="pstat-val" id="dbar-v4" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label" id="dbar-l5">Время</div><div class="pstat-val" id="dbar-v5" style="color:#FFD166">0 с</div></div>
</div>
</div><!-- /#sim-dynamics -->
<!-- ── PROJECTILE sim body ── -->
<div id="sim-proj" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- controls panel -->
<div class="proj-panel">
<div class="gp-section-title">Параметры</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Скорость v₀</span>
<span class="param-val" id="p-v0">20 м/с</span>
</div>
<input type="range" class="param-slider" id="sl-v0" min="1" max="100" value="20" oninput="projParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Угол θ</span>
<span class="param-val" id="p-angle">45°</span>
</div>
<input type="range" class="param-slider" id="sl-angle" min="0" max="90" value="45" oninput="projParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Высота h₀</span>
<span class="param-val" id="p-h0">2 м</span>
</div>
<input type="range" class="param-slider" id="sl-h0" min="0" max="50" value="2" oninput="projParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Сила тяжести g</span>
<span class="param-val" id="p-g">9.81 м/с²</span>
</div>
<input type="range" class="param-slider" id="sl-g" min="1" max="25" step="0.01" value="9.81" oninput="projParam()">
</div>
<!-- Air resistance -->
<div class="gp-section-title" style="margin-top:6px">Сопротивление воздуха</div>
<label class="tri-layer-row" id="drag-row" onclick="projToggleDrag(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:rgba(239,71,111,0.5)"></span>
<span class="tri-layer-name">Воздух</span>
<span class="tri-toggle" id="drag-toggle" style="background:rgba(255,255,255,0.12)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:2px"></span></span>
</label>
<div id="drag-params" style="display:none">
<div class="param-block">
<div class="param-header">
<span class="param-name">Коэф. Cd</span>
<span class="param-val" id="p-cd">0.30</span>
</div>
<input type="range" class="param-slider" id="sl-cd" min="1" max="100" value="30" oninput="projCdChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Масса тела m</span>
<span class="param-val" id="p-mass">1 кг</span>
</div>
<input type="range" class="param-slider" id="sl-mass" min="1" max="20" value="1" oninput="projMassChange()">
</div>
</div>
<!-- Wind -->
<div class="gp-section-title" style="margin-top:6px">Ветер</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Скорость ветра</span>
<span class="param-val" id="p-wind">0 м/с</span>
</div>
<input type="range" class="param-slider" id="sl-wind" min="-20" max="20" value="0" step="1" oninput="projWindChange()">
</div>
<!-- Bounce -->
<div class="gp-section-title" style="margin-top:6px">Отскок</div>
<label class="tri-layer-row" id="bounce-row" onclick="projToggleBounce(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:rgba(123,245,164,0.5)"></span>
<span class="tri-layer-name">Отскок при ударе</span>
<span class="tri-toggle" id="bounce-toggle" style="background:rgba(255,255,255,0.12)"><span style="display:block;width:12px;height:12px;border-radius:50%;background:#fff;margin:2px;margin-left:2px;transition:margin-left .15s"></span></span>
</label>
<div id="bounce-params" style="display:none">
<div class="param-block">
<div class="param-header">
<span class="param-name">Коэф. упругости e</span>
<span class="param-val" id="p-restitution">0.70</span>
</div>
<input type="range" class="param-slider" id="sl-restitution" min="0" max="100" value="70" oninput="projRestitutionChange()">
</div>
</div>
<div class="gp-section-title" style="margin-top:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:6px">
<button class="proj-preset-chip" onclick="projPreset(20,45,0,9.81)">Земля 45°</button>
<button class="proj-preset-chip" onclick="projPreset(20,30,0,9.81)">30°</button>
<button class="proj-preset-chip" onclick="projPreset(20,60,0,9.81)">60°</button>
<button class="proj-preset-chip" onclick="projPreset(20,45,15,9.81)">С высоты</button>
<button class="proj-preset-chip" onclick="projPreset(30,45,0,1.62)">Луна</button>
<button class="proj-preset-chip" onclick="projPreset(30,45,0,3.72)">Марс</button>
<button class="proj-preset-chip" onclick="projPreset(50,45,0,9.81)">Дальний</button>
<button class="proj-preset-chip" onclick="projPreset(20,90,0,9.81)">Вверх</button>
</div>
<!-- Speed -->
<div class="gp-section-title" style="margin-top:8px">Скорость симуляции</div>
<div style="display:flex;gap:5px;margin-bottom:8px">
<button class="mag-mode-btn proj-speed" onclick="projSetSpeed(0.25,this)" style="flex:1;font-size:.68rem">×0.25</button>
<button class="mag-mode-btn proj-speed" onclick="projSetSpeed(0.5,this)" style="flex:1;font-size:.68rem">×0.5</button>
<button class="mag-mode-btn proj-speed active" onclick="projSetSpeed(1,this)" style="flex:1;font-size:.68rem">×1</button>
<button class="mag-mode-btn proj-speed" onclick="projSetSpeed(2,this)" style="flex:1;font-size:.68rem">×2</button>
<button class="mag-mode-btn proj-speed" onclick="projSetSpeed(4,this)" style="flex:1;font-size:.68rem">×4</button>
</div>
<!-- Ghost trails -->
<div class="gp-section-title" style="margin-top:10px">Сравнение</div>
<div style="display:flex;gap:6px;flex-wrap:wrap">
<button class="proj-preset-chip" onclick="projSaveGhost()" style="border-color:rgba(255,214,102,.4);color:#FFD166">Зафиксировать</button>
<button class="proj-preset-chip" onclick="projClearGhosts()" style="border-color:rgba(255,255,255,.15)"><svg class="ic" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> Очистить следы</button>
</div>
<div style="font-size:.6rem;color:var(--text-3);margin-top:4px">Сохрани траекторию, измени параметры и сравни</div>
<!-- LAUNCH BUTTON -->
<div style="margin-top:auto; padding-top:16px; display:flex; flex-direction:column; gap:8px;">
<button class="proj-launch-btn" id="proj-launch-main" onclick="projPlayPause()">
<svg id="proj-launch-icon" viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<span id="proj-launch-label">Запустить</span>
</button>
<button class="proj-reset-btn" onclick="pSim && pSim.reset(); _projSyncPlayBtn()">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/>
</svg>
Сброс
</button>
</div>
</div>
<!-- canvas -->
<div class="proj-canvas-outer">
<canvas id="proj-canvas"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<!-- stats bar -->
<div class="proj-stats-bar">
<div class="pstat">
<div class="pstat-label">Дальность</div>
<div class="pstat-val" id="ps-range"></div>
</div>
<div class="pstat">
<div class="pstat-label">Макс. высота</div>
<div class="pstat-val" id="ps-hmax"></div>
</div>
<div class="pstat">
<div class="pstat-label">Время полёта</div>
<div class="pstat-val" id="ps-tf"></div>
</div>
<div class="pstat">
<div class="pstat-label">Скор. удара</div>
<div class="pstat-val" id="ps-vland"></div>
</div>
<div class="pstat">
<div class="pstat-label">Текущее t</div>
<div class="pstat-val" id="ps-t">0.00 с</div>
</div>
<div class="pstat">
<div class="pstat-label">Угол посадки</div>
<div class="pstat-val" id="ps-land-angle"></div>
</div>
<div class="pstat" id="ps-loss-wrap" style="display:none">
<div class="pstat-label">Δ дальность</div>
<div class="pstat-val" id="ps-loss"></div>
</div>
</div>
</div><!-- /#sim-proj -->
<!-- ── COLLISION sim body ── -->
<div id="sim-coll" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- controls panel -->
<div class="proj-panel">
<div class="gp-section-title">Параметры</div>
<div class="param-block">
<div class="param-header">
<span class="param-name" style="color:#9B5DE5">Масса m₁</span>
<span class="param-val" id="c-m1">4 кг</span>
</div>
<input type="range" class="param-slider" id="sl-m1" min="1" max="20" value="4" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name" style="color:#06D6E0">Масса m₂</span>
<span class="param-val" id="c-m2">4 кг</span>
</div>
<input type="range" class="param-slider" id="sl-m2" min="1" max="20" value="4" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name" style="color:#9B5DE5">Скорость v₁</span>
<span class="param-val" id="c-v1">8 м/с</span>
</div>
<input type="range" class="param-slider" id="sl-cv1" min="0" max="30" value="8" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name" style="color:#06D6E0">Скорость v₂</span>
<span class="param-val" id="c-v2">8 м/с</span>
</div>
<input type="range" class="param-slider" id="sl-cv2" min="0" max="30" value="8" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Угол v₂</span>
<span class="param-val" id="c-angle"></span>
</div>
<input type="range" class="param-slider" id="sl-cangle" min="-60" max="60" value="0" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Упругость e</span>
<span class="param-val" id="c-e">1.00</span>
</div>
<input type="range" class="param-slider" id="sl-e" min="0" max="1" step="0.01" value="1" oninput="collParam()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name"><svg class="ic" viewBox="0 0 24 24"><polygon points="13 19 22 12 13 5 13 19"/><polygon points="2 19 11 12 2 5 2 19"/></svg> Скорость</span>
<span class="param-val" id="c-speed" style="color:#06D6E0">1.00×</span>
</div>
<input type="range" class="param-slider" id="sl-speed" min="0.1" max="4" step="0.05" value="1"
oninput="collParam()" style="--track-color:#06D6E0">
</div>
<div class="gp-section-title" style="margin-top:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:6px">
<button class="proj-preset-chip" onclick="collPreset(4,4,8,8,0,1)">Упругий 1:1</button>
<button class="proj-preset-chip" onclick="collPreset(4,4,8,8,0,0)">Абс. неупругий</button>
<button class="proj-preset-chip" onclick="collPreset(4,4,8,8,0,0.5)">e = 0.5</button>
<button class="proj-preset-chip" onclick="collPreset(4,4,12,0,0,1)">Бильярд</button>
<button class="proj-preset-chip" onclick="collPreset(2,8,14,0,0,1)">Лёгкий<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>тяжёлый</button>
<button class="proj-preset-chip" onclick="collPreset(8,2,6,0,0,1)">Тяжёлый<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>лёгкий</button>
<button class="proj-preset-chip" onclick="collPreset(2,14,18,0,0,1)">Большая разница</button>
<button class="proj-preset-chip" onclick="collPreset(5,5,8,8,30,1)">Косой 30°</button>
<button class="proj-preset-chip" onclick="collPreset(5,5,8,8,50,0.8)">Скользящий</button>
<button class="proj-preset-chip" onclick="collPreset(6,6,10,10,0,0)">Слипание</button>
</div>
<!-- launch button -->
<div style="margin-top:auto; padding-top:16px; display:flex; flex-direction:column; gap:8px;">
<button class="proj-launch-btn" id="coll-launch-main" onclick="collPlayPause()">
<svg id="coll-launch-icon" viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<span id="coll-launch-label">Запустить</span>
</button>
<button class="proj-reset-btn" onclick="cSim && cSim.reset(); _collSyncBtn()">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/>
</svg>
Сброс
</button>
</div>
</div>
<!-- canvas -->
<div class="proj-canvas-outer">
<canvas id="coll-canvas"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
<!-- stats bar -->
<div class="proj-stats-bar">
<div class="pstat">
<div class="pstat-label">Импульс до</div>
<div class="pstat-val" id="cs-pbefore"></div>
</div>
<div class="pstat">
<div class="pstat-label">Импульс после</div>
<div class="pstat-val" id="cs-pafter"></div>
</div>
<div class="pstat">
<div class="pstat-label">КЭ до</div>
<div class="pstat-val" id="cs-kebefore"></div>
</div>
<div class="pstat">
<div class="pstat-label">КЭ после</div>
<div class="pstat-val" id="cs-keafter"></div>
</div>
<div class="pstat">
<div class="pstat-label">Столкновений</div>
<div class="pstat-val" id="cs-count">0</div>
</div>
</div>
</div><!-- /#sim-coll -->
<!-- ── CRYSTAL sim body ── -->
<div id="sim-crystal" class="sim-body-wrap" style="display:none">
<div class="graph-panel">
<div class="gp-section-title">Тип решётки</div>
<button class="gp-btn crystal-type-btn active" id="crys-nacl" onclick="setCrystal('nacl',this)" style="margin-bottom:6px"><svg class="ic" viewBox="0 0 24 24"><path d="M3 18a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7H3z"/><path d="M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2"/><path d="M12 11v4M10 13h4"/></svg> NaCl (ионная)</button>
<button class="gp-btn crystal-type-btn" id="crys-diamond" onclick="setCrystal('diamond',this)" style="margin-bottom:6px"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M6 3l-3 7 9 11 9-11-3-7H6z"/><path d="M3 10h18"/><path d="M12 21L6 3"/><path d="M12 21l6-18"/></svg> Алмаз (ковалентная)</button>
<button class="gp-btn crystal-type-btn" id="crys-bcc" onclick="setCrystal('bcc',this)" style="margin-bottom:6px">ОЦК (металл)</button>
<button class="gp-btn crystal-type-btn" id="crys-fcc" onclick="setCrystal('fcc',this)" style="margin-bottom:6px">ГЦК (металл)</button>
<div class="gp-section-title" style="margin-top:12px">Управление</div>
<div class="tp-text" style="font-size:0.72rem">Вращение: зажмите и тяните<br>Зум: колёсико мыши</div>
</div>
<div class="graph-canvas-outer">
<div class="graph-canvas-wrap" id="crystal-container"></div>
</div>
</div>
<!-- ── ORBITALS sim body ── -->
<div id="sim-orbitals" class="sim-body-wrap" style="display:none">
<div class="graph-panel">
<div class="gp-section-title">Орбиталь</div>
<button class="gp-btn orbital-mode-btn active" id="orb-s" onclick="setOrbital('s',this)" style="margin-bottom:6px"<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#4CC9F0;margin-right:4px"></span>s-орбиталь</button>
<button class="gp-btn orbital-mode-btn" id="orb-p" onclick="setOrbital('p',this)" style="margin-bottom:6px"<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#06D6A0;margin-right:4px"></span>p-орбитали</button>
<button class="gp-btn orbital-mode-btn" id="orb-d" onclick="setOrbital('d',this)" style="margin-bottom:6px"<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#FFD166;margin-right:4px"></span>d-орбитали</button>
<div class="gp-section-title" style="margin-top:12px">Молекулы</div>
<button class="gp-btn orbital-mode-btn" id="orb-h2" onclick="setOrbital('h2',this)" style="margin-bottom:6px">H₂ (σ-связь)</button>
<button class="gp-btn orbital-mode-btn" id="orb-h2o" onclick="setOrbital('h2o',this)" style="margin-bottom:6px">H₂O (угловая)</button>
<div class="gp-section-title" style="margin-top:12px">Управление</div>
<div class="tp-text" style="font-size:0.72rem">Вращение: зажмите и тяните<br>Зум: колёсико мыши</div>
</div>
<div class="graph-canvas-outer">
<div class="graph-canvas-wrap" id="orbitals-container"></div>
</div>
</div>
<!-- ── CHEM SANDBOX sim body ── -->
<div id="sim-chemsandbox" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<!-- Category filter -->
<div class="gp-section-title" style="margin-bottom:6px">Реагенты</div>
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:10px">
<button class="proj-preset-chip reac-mode-btn chemsand-cat active" onclick="chemSandCat('all',this)">Все</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('acid',this)">Кислоты</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('base',this)">Основания</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('salt',this)">Соли</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('metal',this)">Металлы</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('indicator',this)">Индикаторы</button>
<button class="proj-preset-chip reac-mode-btn chemsand-cat" onclick="chemSandCat('other',this)">Другое</button>
</div>
<!-- Reagent list -->
<div id="chemsand-reagents" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px;max-height:180px;overflow-y:auto"></div>
<!-- Presets -->
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="proj-preset-chip" onclick="chemSandPreset('neutralization')">Нейтрализация</button>
<button class="proj-preset-chip" onclick="chemSandPreset('gas_evolution')">Газ CO₂</button>
<button class="proj-preset-chip" onclick="chemSandPreset('precipitate')">Осадок AgCl</button>
<button class="proj-preset-chip" onclick="chemSandPreset('displacement')">Замещение Cu</button>
<button class="proj-preset-chip" onclick="chemSandPreset('indicator')">Индикатор</button>
<button class="proj-preset-chip" onclick="chemSandPreset('violent')">Na + H₂O</button>
<button class="proj-preset-chip" onclick="chemSandPreset('yellow_precip')">PbCrO₄<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
<button class="proj-preset-chip" onclick="chemSandPreset('blue_precip')">Cu(OH)₂<svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg></button>
</div>
<!-- Sliders -->
<div class="gp-section-title" style="margin-bottom:4px;margin-top:4px">Условия</div>
<div class="param-block" style="margin-bottom:5px">
<div class="param-header"><span class="param-name" style="font-size:.7rem">Концентрация</span><span class="param-val" id="csand-conc-val" style="font-size:.7rem;min-width:40px">35%</span></div>
<input type="range" class="param-slider" id="sl-csand-conc" min="5" max="95" value="35" oninput="chemSandConcChange()">
</div>
<div class="param-block" style="margin-bottom:5px">
<div class="param-header"><span class="param-name" style="font-size:.7rem">Температура</span><span class="param-val" id="csand-temp-val" style="font-size:.7rem;min-width:40px">20°C</span></div>
<input type="range" class="param-slider" id="sl-csand-temp" min="0" max="100" value="20" oninput="chemSandTempChange()">
</div>
<!-- Quiz mode -->
<div class="pp-group" style="margin-top:8px">
<div class="pp-label" style="font-size:.65rem">Режим</div>
<div style="display:flex;gap:6px;align-items:center">
<button class="proj-preset-chip reac-mode-btn chemsand-mode active" id="csand-mode-free" onclick="chemSandSetMode('free',this)">Свободный</button>
<button class="proj-preset-chip reac-mode-btn chemsand-mode" id="csand-mode-quiz" onclick="chemSandSetMode('quiz',this)">Задания</button>
<span id="csand-quiz-score" style="font-size:.7rem;color:rgba(255,255,255,.4);margin-left:8px"></span>
</div>
<div id="csand-quiz-question" style="display:none;margin-top:6px;padding:6px 10px;border-radius:8px;background:rgba(155,93,229,.10);border:1px solid rgba(155,93,229,.25);font-size:.72rem;color:#C9A0FF;line-height:1.3"></div>
<div id="csand-quiz-result" style="display:none;margin-top:4px;font-size:.72rem;font-weight:700"></div>
<button id="csand-quiz-next" style="display:none;margin-top:4px" class="proj-preset-chip" onclick="chemSandQuizNext()">Следующее задание</button>
</div>
<!-- Hint -->
<div class="tp-text" style="font-size:.65rem;opacity:.4;line-height:1.3">Клик на панели — добавить/убрать · Drag с полки в колбу · ПКМ — сбросить</div>
</div>
<div class="proj-canvas-outer">
<canvas id="chemsandbox-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%;cursor:default"></canvas>
</div>
</div>
<!-- Stats bar -->
<div class="proj-stats-bar" id="chemsand-stats-bar">
<div class="pstat"><div class="pstat-label">В зоне</div><div class="pstat-val" id="csbar-v1" style="color:#06D6E0">0</div></div>
<div class="pstat"><div class="pstat-label">Тип</div><div class="pstat-val" id="csbar-v3" style="color:#7BF5A4"></div></div>
<div class="pstat"><div class="pstat-label">Уравнение</div><div class="pstat-val" id="csbar-v4" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Сокр. ионное</div><div class="pstat-val" id="csbar-v6" style="color:#9BD4FF"></div></div>
<div class="pstat"><div class="pstat-label">Продукты</div><div class="pstat-val" id="csbar-v5" style="color:#4CC9F0"></div></div>
</div>
</div>
<!-- ── CELL DIVISION sim body ── -->
<div id="sim-celldivision" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:200px;gap:0">
<div class="gp-section-title" style="margin-bottom:6px">Режим</div>
<div style="display:flex;gap:4px;margin-bottom:12px">
<button class="proj-preset-chip reac-mode-btn cd-mode-btn active" onclick="cdSetMode('mitosis',this)">Митоз</button>
<button class="proj-preset-chip reac-mode-btn cd-mode-btn" onclick="cdSetMode('meiosis',this)">Мейоз</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Фазы</div>
<div id="cd-phase-dots" style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px"></div>
<div style="display:flex;gap:4px;margin-bottom:12px">
<button class="gp-btn" onclick="cdPrevPhase()" style="flex:1"><svg class="ic" viewBox="0 0 24 24"><polygon points="19 20 9 12 19 4 19 20"/></svg> Назад</button>
<button class="gp-btn" onclick="cdNextPhase()" style="flex:1">Далее <svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg></button>
</div>
<button class="gp-btn" id="cd-auto-btn" onclick="cdAutoPlay(this)" style="width:100%;margin-bottom:10px"><svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> Авто</button>
<div class="pp-hint">Нажми фазу или используй кнопки для пошагового просмотра</div>
</div>
<div class="proj-canvas-outer">
<canvas id="celldiv-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="cdbar">
<div class="pstat"><div class="pstat-label">Фаза</div><div class="pstat-val" id="cdbar-v1" style="color:#7BF5A4"></div></div>
<div class="pstat"><div class="pstat-label">Хромосомы</div><div class="pstat-val" id="cdbar-v2" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">ДНК</div><div class="pstat-val" id="cdbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Шаг</div><div class="pstat-val" id="cdbar-v4" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="cdbar-v5" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── PHOTOSYNTHESIS sim body ── -->
<div id="sim-photosynthesis" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:200px;gap:0">
<div class="gp-section-title" style="margin-bottom:6px">Процесс</div>
<div style="display:flex;gap:4px;margin-bottom:12px">
<button class="proj-preset-chip reac-mode-btn ps-mode-btn active" onclick="psSetMode('photo',this)">Фотосинтез</button>
<button class="proj-preset-chip reac-mode-btn ps-mode-btn" onclick="psSetMode('resp',this)">Дыхание</button>
</div>
<div class="gp-section-title" style="margin-bottom:4px">Интенсивность света</div>
<div class="sl-row" style="margin-bottom:10px">
<input type="range" id="sl-ps-light" min="0" max="100" value="70" oninput="psLightChange()" style="flex:1">
<span class="sl-val" id="ps-light-val">70%</span>
</div>
<div class="gp-section-title" style="margin-bottom:4px">Концентрация CO₂</div>
<div class="sl-row" style="margin-bottom:10px">
<input type="range" id="sl-ps-co2" min="0" max="100" value="50" oninput="psCO2Change()" style="flex:1">
<span class="sl-val" id="ps-co2-val">50%</span>
</div>
<button class="gp-btn" onclick="psReset()" style="width:100%;margin-top:4px"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сброс</button>
<div class="pp-hint" style="margin-top:10px">Меняй параметры и наблюдай за скоростью реакций</div>
</div>
<div class="proj-canvas-outer">
<canvas id="photosyn-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="psbar">
<div class="pstat"><div class="pstat-label">АТФ/с</div><div class="pstat-val" id="psbar-v1" style="color:#FFD166">0</div></div>
<div class="pstat"><div class="pstat-label">O₂ выд.</div><div class="pstat-val" id="psbar-v2" style="color:#7BF5A4">0</div></div>
<div class="pstat"><div class="pstat-label">CO₂ усв.</div><div class="pstat-val" id="psbar-v3" style="color:#06D6E0">0</div></div>
<div class="pstat"><div class="pstat-label">КПД</div><div class="pstat-val" id="psbar-v4" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="psbar-v5" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── ANGRY BIRDS sim body ── -->
<div id="sim-angrybirds" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:200px;gap:0">
<div class="gp-section-title" style="margin-bottom:6px">Уровень</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px" id="ab-level-btns">
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn active" onclick="abLevel(0,this)">1</button>
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn" onclick="abLevel(1,this)">2</button>
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn" onclick="abLevel(2,this)">3</button>
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn" onclick="abLevel(3,this)">4</button>
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn" onclick="abLevel(4,this)">5</button>
<button class="proj-preset-chip reac-mode-btn ab-lvl-btn" onclick="abLevel(5,this)">6</button>
</div>
<button onclick="angryBirdsRestart()" style="width:100%;padding:6px 10px;border-radius:8px;border:1.5px solid rgba(255,255,255,.15);background:rgba(255,255,255,.06);color:#e0e0e0;font-size:.76rem;font-weight:700;cursor:pointer;margin-bottom:12px" title="Начать уровень заново"><svg class="ic" viewBox="0 0 24 24"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.12"/></svg> Сначала</button>
<div class="gp-section-title" style="margin-bottom:6px">Планеты</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:14px">
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="font-size:.75rem;color:#e0e0e0"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> Земля</span><span style="font-size:.72rem;color:#06D6E0;font-weight:700">9.81 м/с²</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="font-size:.75rem;color:#e0e0e0"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg> Луна</span><span style="font-size:.72rem;color:#06D6E0;font-weight:700">1.62 м/с²</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="font-size:.75rem;color:#e0e0e0"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg> Марс</span><span style="font-size:.72rem;color:#06D6E0;font-weight:700">3.71 м/с²</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="font-size:.75rem;color:#e0e0e0"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg> Юпитер</span><span style="font-size:.72rem;color:#06D6E0;font-weight:700">24.8 м/с²</span>
</div>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Птицы</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:14px">
<div style="display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="width:14px;height:14px;border-radius:50%;background:#e63946;flex-shrink:0;display:inline-block"></span>
<span style="font-size:.73rem;color:#e0e0e0">Красная — обычная</span>
</div>
<div style="display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="width:14px;height:14px;border-radius:50%;background:#888;flex-shrink:0;display:inline-block"></span>
<span style="font-size:.73rem;color:#e0e0e0">Тяжёлая — высокий урон</span>
</div>
<div style="display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:7px;background:rgba(255,255,255,.06)">
<span style="width:14px;height:14px;border-radius:50%;background:#ffd166;flex-shrink:0;display:inline-block"></span>
<span style="font-size:.73rem;color:#e0e0e0">Жёлтая — быстрая</span>
</div>
</div>
<div class="pp-hint">Тяни птицу мышью · отпусти — выстрел</div>
</div>
<div class="proj-canvas-outer">
<canvas id="angrybirds-canvas" style="display:block;position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="abbar">
<div class="pstat"><div class="pstat-label">Уровень</div><div class="pstat-val" id="abbar-v1" style="color:#06D6E0">1</div></div>
<div class="pstat"><div class="pstat-label">Птиц</div><div class="pstat-val" id="abbar-v2" style="color:#ffd166">3</div></div>
<div class="pstat"><div class="pstat-label">Свиней</div><div class="pstat-val" id="abbar-v3" style="color:#7bf5a4">1</div></div>
<div class="pstat"><div class="pstat-label">Очки</div><div class="pstat-val" id="abbar-v4" style="color:#ef476f">0</div></div>
<div class="pstat"><div class="pstat-label">Планета</div><div class="pstat-val" id="abbar-v5" style="color:#9b5de5">Земля <svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div></div>
</div>
</div>
<!-- ── QUADRATIC sim body ── -->
<div id="sim-quadratic" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Коэффициенты</div>
<div class="proj-slider-row" style="margin-bottom:10px">
<label style="font-size:.78rem;color:#ccc;width:60px">a = <span id="quad-a-val" style="color:#9B5DE5;font-weight:700">1</span></label>
<input type="range" id="sl-quad-a" min="-5" max="5" step="0.1" value="1" oninput="quadParam('a',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:10px">
<label style="font-size:.78rem;color:#ccc;width:60px">b = <span id="quad-b-val" style="color:#06D6E0;font-weight:700">0</span></label>
<input type="range" id="sl-quad-b" min="-10" max="10" step="0.1" value="0" oninput="quadParam('b',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:10px">
<label style="font-size:.78rem;color:#ccc;width:60px">c = <span id="quad-c-val" style="color:#F15BB5;font-weight:700">-1</span></label>
<input type="range" id="sl-quad-c" min="-10" max="10" step="0.1" value="-1" oninput="quadParam('c',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Примеры</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn" onclick="quadPreset(1,0,-4)">x²−4</button>
<button class="preset-btn" onclick="quadPreset(1,-2,1)">(x1)²</button>
<button class="preset-btn" onclick="quadPreset(1,0,1)">x²+1</button>
<button class="preset-btn" onclick="quadPreset(-1,0,4)">x²+4</button>
<button class="preset-btn" onclick="quadPreset(2,-3,-2)">2x²−3x2</button>
<button class="preset-btn" onclick="quadPreset(0.5,1,-3)">½x²+x3</button>
</div>
<div class="pp-hint">Скролл — зум · Тащи — панорама</div>
</div>
<div class="proj-canvas-outer">
<canvas id="quadratic-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="quadbar">
<div class="pstat"><div class="pstat-label">Дискриминант</div><div class="pstat-val" id="qbar-v1" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Корни</div><div class="pstat-val" id="qbar-v2" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Вершина</div><div class="pstat-val" id="qbar-v3" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">Уравнение</div><div class="pstat-val" id="qbar-v4" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── NORMAL DISTRIBUTION sim body ── -->
<div id="sim-normaldist" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:10px">
<label style="font-size:.78rem;color:#ccc;width:60px">μ = <span id="nd-mu-val" style="color:#06D6E0;font-weight:700">0</span></label>
<input type="range" id="sl-nd-mu" min="-5" max="5" step="0.1" value="0" oninput="ndParam('mu',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:10px">
<label style="font-size:.78rem;color:#ccc;width:60px">σ = <span id="nd-sigma-val" style="color:#9B5DE5;font-weight:700">1</span></label>
<input type="range" id="sl-nd-sigma" min="0.2" max="4" step="0.1" value="1" oninput="ndParam('sigma',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Закрасить область</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn nd-shade-btn active" onclick="ndShade('1s',this)">μ ± 1σ</button>
<button class="preset-btn nd-shade-btn" onclick="ndShade('2s',this)">μ ± 2σ</button>
<button class="preset-btn nd-shade-btn" onclick="ndShade('3s',this)">μ ± 3σ</button>
<button class="preset-btn nd-shade-btn" onclick="ndShade('none',this)">Нет</button>
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn" onclick="ndPreset(0,1)">Стандартное</button>
<button class="preset-btn" onclick="ndPreset(0,0.5)">Узкое σ=0.5</button>
<button class="preset-btn" onclick="ndPreset(0,2)">Широкое σ=2</button>
<button class="preset-btn" onclick="ndPreset(3,1)">Сдвиг μ=3</button>
</div>
<div class="pp-hint">Наведи курсор — Z-score и плотность</div>
</div>
<div class="proj-canvas-outer">
<canvas id="normaldist-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="ndbar">
<div class="pstat"><div class="pstat-label">μ</div><div class="pstat-val" id="ndbar-v1" style="color:#06D6E0">0</div></div>
<div class="pstat"><div class="pstat-label">σ</div><div class="pstat-val" id="ndbar-v2" style="color:#9B5DE5">1</div></div>
<div class="pstat"><div class="pstat-label">Пик f(μ)</div><div class="pstat-val" id="ndbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Область</div><div class="pstat-val" id="ndbar-v4" style="color:#7BF5A4"></div></div>
</div>
</div>
<!-- ── GRAPH TRANSFORM sim body ── -->
<div id="sim-graphtransform" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:230px;gap:0">
<div class="gp-section-title" style="margin-bottom:6px">Базовая функция f(x)</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px">
<button class="preset-btn gt-base-btn active" onclick="gtBase('sin',this)">sin x</button>
<button class="preset-btn gt-base-btn" onclick="gtBase('cos',this)">cos x</button>
<button class="preset-btn gt-base-btn" onclick="gtBase('x^2',this)"></button>
<button class="preset-btn gt-base-btn" onclick="gtBase('x^3',this)"></button>
<button class="preset-btn gt-base-btn" onclick="gtBase('sqrt',this)">√x</button>
<button class="preset-btn gt-base-btn" onclick="gtBase('|x|',this)">|x|</button>
<button class="preset-btn gt-base-btn" onclick="gtBase('1/x',this)">1/x</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">y = a · f(k·x + b) + c</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">a = <span id="gt-a-val" style="color:#9B5DE5;font-weight:700">1</span></label>
<input type="range" id="sl-gt-a" min="-3" max="3" step="0.1" value="1" oninput="gtParam('a',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">k = <span id="gt-k-val" style="color:#06D6E0;font-weight:700">1</span></label>
<input type="range" id="sl-gt-k" min="-3" max="3" step="0.1" value="1" oninput="gtParam('k',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">b = <span id="gt-b-val" style="color:#FFD166;font-weight:700">0</span></label>
<input type="range" id="sl-gt-b" min="-5" max="5" step="0.1" value="0" oninput="gtParam('b',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">c = <span id="gt-c-val" style="color:#EF476F;font-weight:700">0</span></label>
<input type="range" id="sl-gt-c" min="-5" max="5" step="0.1" value="0" oninput="gtParam('c',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Эффекты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="gtEffect(2,1,0,0)">Растяжение <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="21" x2="12" y2="3"/><polyline points="7 8 12 3 17 8"/><polyline points="17 16 12 21 7 16"/></svg></button>
<button class="preset-btn" onclick="gtEffect(1,2,0,0)">Сжатие <svg class="ic" viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><polyline points="8 17 3 12 8 7"/><polyline points="16 7 21 12 16 17"/></svg></button>
<button class="preset-btn" onclick="gtEffect(-1,1,0,0)">Отражение <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="21" x2="12" y2="3"/><polyline points="7 8 12 3 17 8"/><polyline points="17 16 12 21 7 16"/></svg></button>
<button class="preset-btn" onclick="gtEffect(1,-1,0,0)">Отражение <svg class="ic" viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><polyline points="8 17 3 12 8 7"/><polyline points="16 7 21 12 16 17"/></svg></button>
<button class="preset-btn" onclick="gtEffect(1,1,2,0)">Сдвиг <svg class="ic" viewBox="0 0 24 24"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg></button>
<button class="preset-btn" onclick="gtEffect(1,1,0,3)">Сдвиг <svg class="ic" viewBox="0 0 24 24"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg></button>
</div>
<button class="preset-btn" onclick="gtEffect(1,1,0,0)" style="width:100%;margin-top:4px">Сброс</button>
<div class="pp-hint">Скролл — зум · Тащи — панорама</div>
</div>
<div class="proj-canvas-outer">
<canvas id="graphtransform-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="gtbar">
<div class="pstat"><div class="pstat-label">f(x)</div><div class="pstat-val" id="gtbar-v1" style="color:#06D6E0">sin(x)</div></div>
<div class="pstat"><div class="pstat-label">a</div><div class="pstat-val" id="gtbar-v2" style="color:#9B5DE5">1</div></div>
<div class="pstat"><div class="pstat-label">k</div><div class="pstat-val" id="gtbar-v3" style="color:#06D6E0">1</div></div>
<div class="pstat"><div class="pstat-label">b</div><div class="pstat-val" id="gtbar-v4" style="color:#FFD166">0</div></div>
<div class="pstat"><div class="pstat-label">c</div><div class="pstat-val" id="gtbar-v5" style="color:#EF476F">0</div></div>
</div>
</div>
<!-- ── PENDULUM sim body ── -->
<div id="sim-pendulum" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">θ = <span id="pend-theta-val" style="color:#9B5DE5;font-weight:700">45</span>°</label>
<input type="range" id="sl-pend-theta" min="5" max="170" step="1" value="45" oninput="pendParam('theta',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">L = <span id="pend-L-val" style="color:#06D6E0;font-weight:700">200</span></label>
<input type="range" id="sl-pend-L" min="60" max="300" step="5" value="200" oninput="pendParam('L',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">g = <span id="pend-g-val" style="color:#FFD166;font-weight:700">9.81</span></label>
<input type="range" id="sl-pend-g" min="1" max="25" step="0.1" value="9.81" oninput="pendParam('g',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">Затух. <span id="pend-damp-val" style="color:#EF476F;font-weight:700">0</span></label>
<input type="range" id="sl-pend-damp" min="0" max="2" step="0.05" value="0" oninput="pendParam('damping',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="pendPreset(45,200,9.81,0)">Земля</button>
<button class="preset-btn" onclick="pendPreset(45,200,1.62,0)">Луна</button>
<button class="preset-btn" onclick="pendPreset(170,200,9.81,0)">Большой θ</button>
<button class="preset-btn" onclick="pendPreset(45,200,9.81,0.5)">Затухание</button>
</div>
<div class="pp-hint">Тащи грузик мышью для установки угла</div>
</div>
<div class="proj-canvas-outer">
<canvas id="pendulum-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="pendbar">
<div class="pstat"><div class="pstat-label">Угол</div><div class="pstat-val" id="pendbar-v1" style="color:#9B5DE5">45°</div></div>
<div class="pstat"><div class="pstat-label">ω</div><div class="pstat-val" id="pendbar-v2" style="color:#06D6E0">0</div></div>
<div class="pstat"><div class="pstat-label">Период T</div><div class="pstat-val" id="pendbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Энергия</div><div class="pstat-val" id="pendbar-v4" style="color:#EF476F"></div></div>
</div>
</div>
<!-- ── EQUILIBRIUM sim body ── -->
<div id="sim-equilibrium" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">T = <span id="eq-T-val" style="color:#FFD166;font-weight:700">300</span> K</label>
<input type="range" id="sl-eq-T" min="200" max="500" step="10" value="300" oninput="eqParam('T',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">Ea<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> <span id="eq-Eaf-val" style="color:#7BF5A4;font-weight:700">50</span></label>
<input type="range" id="sl-eq-Eaf" min="20" max="80" step="1" value="50" oninput="eqParam('Ea_f',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">Ea<svg class="ic" viewBox="0 0 24 24"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> <span id="eq-Ear-val" style="color:#EF476F;font-weight:700">55</span></label>
<input type="range" id="sl-eq-Ear" min="20" max="80" step="1" value="55" oninput="eqParam('Ea_r',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="eqPreset('default')">По умолч.</button>
<button class="preset-btn" onclick="eqPreset('exothermic')">Экзо</button>
<button class="preset-btn" onclick="eqPreset('endothermic')">Эндо</button>
<button class="preset-btn" onclick="eqPreset('excess_A')">Избыток A</button>
</div>
<div class="pp-hint">A + B ⇌ C + D — принцип Ле Шателье</div>
</div>
<div class="proj-canvas-outer">
<canvas id="equilibrium-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="eqbar">
<div class="pstat"><div class="pstat-label">Keq</div><div class="pstat-val" id="eqbar-v1" style="color:#7BF5A4"></div></div>
<div class="pstat"><div class="pstat-label">Q</div><div class="pstat-val" id="eqbar-v2" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Направление</div><div class="pstat-val" id="eqbar-v3" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">A|B|C|D</div><div class="pstat-val" id="eqbar-v4" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── THIN LENS sim body ── -->
<div id="sim-thinlens" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">f = <span id="lens-f-val" style="color:#06D6E0;font-weight:700">100</span></label>
<input type="range" id="sl-lens-f" min="-200" max="200" step="5" value="100" oninput="lensParam('f',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">d = <span id="lens-d-val" style="color:#9B5DE5;font-weight:700">200</span></label>
<input type="range" id="sl-lens-d" min="30" max="400" step="5" value="200" oninput="lensParam('d',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">h = <span id="lens-h-val" style="color:#EF476F;font-weight:700">50</span></label>
<input type="range" id="sl-lens-h" min="20" max="80" step="2" value="50" oninput="lensParam('h',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="lensPreset(100,200,50)">Собирающая</button>
<button class="preset-btn" onclick="lensPreset(-100,200,50)">Рассеивающая</button>
<button class="preset-btn" onclick="lensPreset(100,100,50)">d = f</button>
<button class="preset-btn" onclick="lensPreset(100,60,50)">d &lt; f</button>
</div>
<div class="pp-hint">Тащи стрелку-предмет или фокус мышью</div>
</div>
<div class="proj-canvas-outer">
<canvas id="thinlens-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="lensbar">
<div class="pstat"><div class="pstat-label">f</div><div class="pstat-val" id="lensbar-v1" style="color:#06D6E0">100</div></div>
<div class="pstat"><div class="pstat-label">d'</div><div class="pstat-val" id="lensbar-v2" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">M</div><div class="pstat-val" id="lensbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Тип</div><div class="pstat-val" id="lensbar-v4" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── MIRRORS sim body ── -->
<div id="sim-mirrors" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:264px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Тип зеркала</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn mirror-type-btn" id="mtype-flat" onclick="mirrorType('flat',this)" style="font-size:.72rem">Плоское</button>
<button class="preset-btn mirror-type-btn active" id="mtype-concave" onclick="mirrorType('concave',this)" style="font-size:.72rem">Вогнутое</button>
<button class="preset-btn mirror-type-btn" id="mtype-convex" onclick="mirrorType('convex',this)" style="font-size:.72rem">Выпуклое</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px" id="mirror-f-row">
<label style="font-size:.78rem;color:#ccc;width:60px">f = <span id="mirror-f-val" style="color:#06D6E0;font-weight:700">120</span></label>
<input type="range" id="sl-mirror-f" min="30" max="300" step="5" value="120" oninput="mirrorParam('f',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:60px">d = <span id="mirror-d-val" style="color:#9B5DE5;font-weight:700">240</span></label>
<input type="range" id="sl-mirror-d" min="30" max="500" step="5" value="240" oninput="mirrorParam('d',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:60px">h = <span id="mirror-h-val" style="color:#EF476F;font-weight:700">60</span></label>
<input type="range" id="sl-mirror-h" min="20" max="80" step="2" value="60" oninput="mirrorParam('h',this.value)" style="flex:1">
</div>
<div style="display:flex;gap:6px;margin-top:8px;margin-bottom:8px;align-items:center">
<button id="mirror-play-btn" onclick="mirrorTogglePlay(this)" style="flex:1;padding:6px 0;border-radius:8px;border:none;background:linear-gradient(135deg,#06D6E0,#9B5DE5);color:#fff;font-size:.78rem;font-weight:700;cursor:pointer">▶ Анимация</button>
<div style="display:flex;flex-direction:column;align-items:center;gap:2px">
<span style="font-size:.62rem;color:#888">скорость</span>
<select id="mirror-speed-sel" onchange="mirrorSetSpeed(this.value)" style="background:#1a1a2e;color:#ccc;border:1px solid #333;border-radius:4px;font-size:.7rem;padding:2px 4px">
<option value="0.25">×¼</option><option value="0.5">×½</option>
<option value="1" selected>×1</option><option value="2">×2</option>
</select>
</div>
</div>
<div style="display:flex;gap:4px;margin-bottom:10px">
<button onclick="mirrorStepNext()" style="flex:1;padding:5px 0;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#7BF5A4;font-size:.73rem;cursor:pointer" title="Показать следующий луч">① Пошагово</button>
<button onclick="mirrorStepReset()" style="padding:5px 9px;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#888;font-size:.78rem;cursor:pointer" title="Показать все лучи"></button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Отображение</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:3px 10px;margin-bottom:10px">
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-normals" checked onchange="mirrorToggle('normals',this.checked)"> Нормали</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-dims" checked onchange="mirrorToggle('dims',this.checked)"> Размеры</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-angles" checked onchange="mirrorToggle('angles',this.checked)"> Углы θ</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-photons" checked onchange="mirrorToggle('photons',this.checked)"> Фотоны</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-grid" onchange="mirrorToggle('grid',this.checked)"> Сетка</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer"><input type="checkbox" id="mtog-zones" checked onchange="mirrorToggle('zones',this.checked)"> Зоны</label>
<label style="display:flex;align-items:center;gap:4px;font-size:.72rem;color:#ccc;cursor:pointer;grid-column:span 2"><input type="checkbox" id="mtog-point" onchange="mirrorSetPointMode(this.checked)"> Точечный объект</label>
</div>
<button onclick="if(mirrorSim)mirrorSim.exportPng()" style="width:100%;padding:5px 0;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#888;font-size:.72rem;cursor:pointer;margin-bottom:8px">📷 Экспорт PNG</button>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="mirrorPreset('flat')">Плоское</button>
<button class="preset-btn" onclick="mirrorPreset('far')">d &gt; 2f</button>
<button class="preset-btn" onclick="mirrorPreset('2f')">d = 2f</button>
<button class="preset-btn" onclick="mirrorPreset('between')">f &lt; d &lt; 2f</button>
<button class="preset-btn" onclick="mirrorPreset('near')">d &lt; f</button>
<button class="preset-btn" onclick="mirrorPreset('convex')">Выпуклое</button>
</div>
<div class="pp-hint">Тащи предмет, фокус или изображение мышью</div>
</div>
<div class="proj-canvas-outer">
<canvas id="mirror-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="mirrorbar">
<div class="pstat"><div class="pstat-label">f</div><div class="pstat-val" id="mirrorbar-v1" style="color:#06D6E0">120</div></div>
<div class="pstat"><div class="pstat-label">d</div><div class="pstat-val" id="mirrorbar-v5" style="color:#9B5DE5">240</div></div>
<div class="pstat"><div class="pstat-label">d'</div><div class="pstat-val" id="mirrorbar-v2" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">M</div><div class="pstat-val" id="mirrorbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Тип изобр.</div><div class="pstat-val" id="mirrorbar-v4" style="color:#9B5DE5"></div></div>
</div>
</div>
<!-- ── ISOPROCESS sim body ── -->
<div id="sim-isoprocess" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Процесс</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn iso-proc-btn active" id="iproc-isothermal" onclick="isoProc('isothermal',this)" style="font-size:.72rem">Изотерма</button>
<button class="preset-btn iso-proc-btn" id="iproc-isochoric" onclick="isoProc('isochoric',this)" style="font-size:.72rem">Изохора</button>
<button class="preset-btn iso-proc-btn" id="iproc-isobaric" onclick="isoProc('isobaric',this)" style="font-size:.72rem">Изобара</button>
<button class="preset-btn iso-proc-btn" id="iproc-adiabatic" onclick="isoProc('adiabatic',this)" style="font-size:.72rem">Адиабата</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Газ γ</div>
<div style="display:flex;gap:4px;margin-bottom:10px">
<button class="preset-btn iso-gamma-btn" id="igamma-14" onclick="isoGamma(1.4,this)" style="font-size:.72rem">Двухат. 1.4</button>
<button class="preset-btn iso-gamma-btn active" id="igamma-167" onclick="isoGamma(1.667,this)" style="font-size:.72rem">Одноат. 5/3</button>
</div>
<div class="gp-section-title" style="margin-bottom:8px">Начальное состояние</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">P₁ = <span id="iso-p1-val" style="color:#9B5DE5;font-weight:700">3.0</span></label>
<input type="range" id="sl-iso-p1" min="0.5" max="8" step="0.1" value="3.0" oninput="isoParam('P1',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">V₁ = <span id="iso-v1-val" style="color:#9B5DE5;font-weight:700">10</span></label>
<input type="range" id="sl-iso-v1" min="2" max="28" step="1" value="10" oninput="isoParam('V1',this.value)" style="flex:1">
</div>
<div class="gp-section-title" style="margin-bottom:8px">Конечное состояние</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px;white-space:nowrap">Сжат./расш.</label>
<input type="range" id="sl-iso-ratio" min="0.01" max="0.99" step="0.01" value="0.5" oninput="isoRatio(this.value)" style="flex:1">
</div>
<div style="margin-top:6px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="isoPreset('iso_expand')">Изотерма расш.</button>
<button class="preset-btn" onclick="isoPreset('iso_comp')">Изотерма сжат.</button>
<button class="preset-btn" onclick="isoPreset('heat_iso')">Изохора нагрев</button>
<button class="preset-btn" onclick="isoPreset('adiab_exp')">Адиабата расш.</button>
</div>
<div class="pp-hint">Тащи точки 1 и 2 по диаграмме</div>
</div>
<div class="proj-canvas-outer">
<canvas id="isoprocess-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="isobar">
<div class="pstat"><div class="pstat-label">T₁, K</div><div class="pstat-val" id="isobar-t1" style="color:#9B5DE5"></div></div>
<div class="pstat"><div class="pstat-label">T₂, K</div><div class="pstat-val" id="isobar-t2" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">W, Дж</div><div class="pstat-val" id="isobar-w" style="color:#7BF5A4"></div></div>
<div class="pstat"><div class="pstat-label">Q, Дж</div><div class="pstat-val" id="isobar-q" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">ΔU, Дж</div><div class="pstat-val" id="isobar-du" style="color:#EF476F"></div></div>
</div>
</div>
<!-- ── TITRATION sim body ── -->
<div id="sim-titration" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:75px">C(кисл) <span id="titr-ac-val" style="color:#EF476F;font-weight:700">0.10</span> М</label>
<input type="range" id="sl-titr-ac" min="0.05" max="1" step="0.05" value="0.1" oninput="titrParam('acidConc',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:75px">C(осн) <span id="titr-bc-val" style="color:#9B5DE5;font-weight:700">0.10</span> М</label>
<input type="range" id="sl-titr-bc" min="0.05" max="1" step="0.05" value="0.1" oninput="titrParam('baseConc',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:75px">V(кисл) <span id="titr-vol-val" style="color:#06D6E0;font-weight:700">50</span> мл</label>
<input type="range" id="sl-titr-vol" min="25" max="100" step="5" value="50" oninput="titrParam('acidVol',this.value)" style="flex:1">
</div>
<div style="margin-top:4px;margin-bottom:8px">
<div class="gp-section-title" style="margin-bottom:4px">Индикатор</div>
<div style="display:flex;flex-wrap:wrap;gap:4px">
<button class="preset-btn titr-ind-btn active" onclick="titrIndicator('phenolphthalein',this)" style="font-size:.72rem">Фенолф.</button>
<button class="preset-btn titr-ind-btn" onclick="titrIndicator('methyl_orange',this)" style="font-size:.72rem">Метилор.</button>
<button class="preset-btn titr-ind-btn" onclick="titrIndicator('litmus',this)" style="font-size:.72rem">Лакмус</button>
</div>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="titrPreset('strong_strong')">HCl+NaOH</button>
<button class="preset-btn" onclick="titrPreset('weak_strong')">CH₃COOH</button>
<button class="preset-btn" onclick="titrPreset('concentrated')">Конц.</button>
</div>
<div class="pp-hint">Нажми <svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> — основание добавляется каплями</div>
</div>
<div class="proj-canvas-outer">
<canvas id="titration-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="titrbar">
<div class="pstat"><div class="pstat-label">pH</div><div class="pstat-val" id="titrbar-v1" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">Добавлено</div><div class="pstat-val" id="titrbar-v2" style="color:#9B5DE5">0.0 мл</div></div>
<div class="pstat"><div class="pstat-label">Точка экв.</div><div class="pstat-val" id="titrbar-v3" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">Тип</div><div class="pstat-val" id="titrbar-v4" style="color:#FFD166"></div></div>
</div>
</div>
<!-- ── REFRACTION sim body ── -->
<div id="sim-refraction" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">n₁ = <span id="refr-n1-val" style="color:#9B5DE5;font-weight:700">1.00</span></label>
<input type="range" id="sl-refr-n1" min="1" max="2.5" step="0.01" value="1" oninput="refrParam('n1',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">n₂ = <span id="refr-n2-val" style="color:#06D6E0;font-weight:700">1.50</span></label>
<input type="range" id="sl-refr-n2" min="1" max="2.5" step="0.01" value="1.5" oninput="refrParam('n2',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">θ = <span id="refr-angle-val" style="color:#FFD166;font-weight:700">30</span>°</label>
<input type="range" id="sl-refr-angle" min="0" max="89" step="1" value="30" oninput="refrParam('angle',this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="refrPreset(1,1.5,30)">Воздух<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>Стекло</button>
<button class="preset-btn" onclick="refrPreset(1.5,1,30)">Стекло<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>Воздух</button>
<button class="preset-btn" onclick="refrPreset(1.33,1.5,30)">Вода<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>Стекло</button>
<button class="preset-btn" onclick="refrPreset(1,2.42,45)">Алмаз</button>
</div>
<div class="pp-hint">Тащи луч мышью для изменения угла</div>
</div>
<div class="proj-canvas-outer">
<canvas id="refraction-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="refrbar">
<div class="pstat"><div class="pstat-label">θ₁</div><div class="pstat-val" id="refrbar-v1" style="color:#9B5DE5">30°</div></div>
<div class="pstat"><div class="pstat-label">θ₂</div><div class="pstat-val" id="refrbar-v2" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">Крит. угол</div><div class="pstat-val" id="refrbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">ПВО</div><div class="pstat-val" id="refrbar-v4" style="color:#EF476F">Нет</div></div>
</div>
</div>
<!-- ── PROBABILITY sim body ── -->
<div id="sim-probability" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Режим</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn prob-mode-btn active" onclick="probMode('coin',this)">Монета</button>
<button class="preset-btn prob-mode-btn" onclick="probMode('dice',this)">Кубик</button>
<button class="preset-btn prob-mode-btn" onclick="probMode('dice2',this)">2 кубика</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="probPreset('coin',100)">100 бросков</button>
<button class="preset-btn" onclick="probPreset('coin',1000)">1000</button>
<button class="preset-btn" onclick="probPreset('dice',100)">Кубик ×100</button>
<button class="preset-btn" onclick="probPreset('dice2',500)">2 куб. ×500</button>
</div>
<div class="pp-hint">Нажми <svg class="ic" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg> — броски идут автоматически</div>
</div>
<div class="proj-canvas-outer">
<canvas id="probability-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="probbar">
<div class="pstat"><div class="pstat-label">Бросков</div><div class="pstat-val" id="probbar-v1" style="color:#9B5DE5">0</div></div>
<div class="pstat"><div class="pstat-label">Макс. отклон.</div><div class="pstat-val" id="probbar-v2" style="color:#EF476F"></div></div>
<div class="pstat"><div class="pstat-label">χ²</div><div class="pstat-val" id="probbar-v3" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="probbar-v4" style="color:#FFD166">Монета</div></div>
</div>
</div>
<!-- ── BOHR ATOM sim body ── -->
<div id="sim-bohratom" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Уровень</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn" onclick="bohrLevel(1)">n=1</button>
<button class="preset-btn" onclick="bohrLevel(2)">n=2</button>
<button class="preset-btn" onclick="bohrLevel(3)">n=3</button>
<button class="preset-btn" onclick="bohrLevel(4)">n=4</button>
<button class="preset-btn" onclick="bohrLevel(5)">n=5</button>
<button class="preset-btn" onclick="bohrLevel(6)">n=6</button>
</div>
<div class="gp-section-title" style="margin-bottom:6px">Переходы</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="bohrTransition(2,1)">2<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>1 (УФ)</button>
<button class="preset-btn" onclick="bohrTransition(3,2)">3<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>2 (красн.)</button>
<button class="preset-btn" onclick="bohrTransition(4,2)">4<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>2 (голуб.)</button>
<button class="preset-btn" onclick="bohrTransition(4,3)">4<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>3 (ИК)</button>
<button class="preset-btn" onclick="bohrTransition(1,3)">1<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>3 (поглощ.)</button>
</div>
<div class="pp-hint">Кликни на уровень для перехода электрона</div>
</div>
<div class="proj-canvas-outer">
<canvas id="bohratom-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="bohrbar">
<div class="pstat"><div class="pstat-label">Уровень n</div><div class="pstat-val" id="bohrbar-v1" style="color:#9B5DE5">1</div></div>
<div class="pstat"><div class="pstat-label">E (эВ)</div><div class="pstat-val" id="bohrbar-v2" style="color:#06D6E0">-13.6</div></div>
<div class="pstat"><div class="pstat-label">λ (нм)</div><div class="pstat-val" id="bohrbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Серия</div><div class="pstat-val" id="bohrbar-v4" style="color:#EF476F"></div></div>
</div>
</div>
<!-- ── ELECTROLYSIS sim body ── -->
<div id="sim-electrolysis" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:220px;gap:0">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">U = <span id="elec-V-val" style="color:#FFD166;font-weight:700">6</span> В</label>
<input type="range" id="sl-elec-V" min="1" max="12" step="0.5" value="6" oninput="elecParam('voltage',this.value)" style="flex:1">
</div>
<div style="margin-top:4px;margin-bottom:8px">
<div class="gp-section-title" style="margin-bottom:4px">Электролит</div>
<div style="display:flex;flex-wrap:wrap;gap:4px">
<button class="preset-btn elec-type-btn active" onclick="elecPreset('nacl',this)">NaCl</button>
<button class="preset-btn elec-type-btn" onclick="elecPreset('cuso4',this)">CuSO₄</button>
<button class="preset-btn elec-type-btn" onclick="elecPreset('h2so4',this)">H₂SO₄</button>
</div>
</div>
<div class="pp-hint">Ионы движутся к электродам, на электродах — газ и осадок</div>
</div>
<div class="proj-canvas-outer">
<canvas id="electrolysis-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="elecbar">
<div class="pstat"><div class="pstat-label">I (А)</div><div class="pstat-val" id="elecbar-v1" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">Масса</div><div class="pstat-val" id="elecbar-v2" style="color:#9B5DE5"></div></div>
<div class="pstat"><div class="pstat-label">Газ (мл)</div><div class="pstat-val" id="elecbar-v3" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">Время</div><div class="pstat-val" id="elecbar-v4" style="color:#EF476F">0 с</div></div>
</div>
</div>
<!-- ── WAVES sim body ── -->
<div id="sim-waves" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<div class="proj-panel" style="width:228px;gap:0;overflow-y:auto">
<!-- Mode selector -->
<div class="gp-section-title" style="margin-bottom:6px">Режим</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:12px">
<button class="wave-mode-btn active" onclick="wavesMode('transverse',this)">Поперечная</button>
<button class="wave-mode-btn" onclick="wavesMode('longitudinal',this)">Продольная</button>
<button class="wave-mode-btn" onclick="wavesMode('superposition',this)">Суперпозиция</button>
<button class="wave-mode-btn" onclick="wavesMode('standing',this)">Стоячая</button>
</div>
<!-- Wave 1 -->
<div class="gp-section-title" style="margin-bottom:6px">Волна 1</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Амплитуда A₁</span>
<span class="param-val" id="waves-A1-val" style="color:#9B5DE5">50</span>
</div>
<input type="range" id="sl-waves-A1" class="param-slider" min="10" max="90" step="2" value="50" oninput="wavesParam('A1',this.value)">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Частота f₁</span>
<span class="param-val" id="waves-f1-val" style="color:#9B5DE5">1.0 Гц</span>
</div>
<input type="range" id="sl-waves-f1" class="param-slider" min="0.3" max="4" step="0.1" value="1.0" oninput="wavesParam('f1',this.value)">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Фаза φ₁</span>
<span class="param-val" id="waves-phi1-val" style="color:#9B5DE5">0</span>
</div>
<input type="range" id="sl-waves-phi1" class="param-slider" min="0" max="6.28" step="0.1" value="0" oninput="wavesParam('phi1',this.value)">
</div>
<!-- Wave 2 (superposition only) -->
<div id="waves-w2-section" style="display:none">
<div class="gp-section-title" style="margin-top:4px;margin-bottom:6px">Волна 2</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Амплитуда A₂</span>
<span class="param-val" id="waves-A2-val" style="color:#06D6E0">40</span>
</div>
<input type="range" id="sl-waves-A2" class="param-slider" min="10" max="90" step="2" value="40" oninput="wavesParam('A2',this.value)">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Частота f₂</span>
<span class="param-val" id="waves-f2-val" style="color:#06D6E0">1.5 Гц</span>
</div>
<input type="range" id="sl-waves-f2" class="param-slider" min="0.3" max="4" step="0.1" value="1.5" oninput="wavesParam('f2',this.value)">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Фаза φ₂</span>
<span class="param-val" id="waves-phi2-val" style="color:#06D6E0">0</span>
</div>
<input type="range" id="sl-waves-phi2" class="param-slider" min="0" max="6.28" step="0.1" value="0" oninput="wavesParam('phi2',this.value)">
</div>
<div class="pp-hint" style="margin-bottom:8px">φ₂=0: конструктивная · φ₂=π: деструктивная интерференция</div>
</div>
<!-- Standing wave harmonics -->
<div id="waves-n-section" style="display:none">
<div class="gp-section-title" style="margin-top:4px;margin-bottom:6px">Гармоника n</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn wave-n-btn active" onclick="wavesN(1,this)">n=1</button>
<button class="preset-btn wave-n-btn" onclick="wavesN(2,this)">n=2</button>
<button class="preset-btn wave-n-btn" onclick="wavesN(3,this)">n=3</button>
<button class="preset-btn wave-n-btn" onclick="wavesN(4,this)">n=4</button>
<button class="preset-btn wave-n-btn" onclick="wavesN(5,this)">n=5</button>
</div>
</div>
<!-- Presets -->
<div class="gp-section-title" style="margin-top:4px;margin-bottom:6px">Пресеты</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px">
<button class="preset-btn" onclick="wavesPreset('constructive')">Конструктивная</button>
<button class="preset-btn" onclick="wavesPreset('destructive')">Деструктивная</button>
<button class="preset-btn" onclick="wavesPreset('beats')">Биения</button>
</div>
<!-- Speed -->
<div class="param-block" style="margin-top:4px">
<div class="param-header">
<span class="param-name">Скорость анимации</span>
<span class="param-val" id="waves-speed-val" style="color:#FFD166">×2.0</span>
</div>
<input type="range" id="sl-waves-speed" class="param-slider" min="0.3" max="5" step="0.1" value="2.0" oninput="wavesParam('speed',this.value)" style="accent-color:#FFD166">
</div>
<div class="pp-hint" style="margin-top:10px">v = λ·f — основное волновое уравнение</div>
</div>
<div class="proj-canvas-outer">
<canvas id="waves-canvas"></canvas>
</div>
</div>
<div class="proj-stats-bar" id="wavesbar">
<div class="pstat"><div class="pstat-label">T (с)</div><div class="pstat-val" id="wavesbar-T" style="color:#9B5DE5"></div></div>
<div class="pstat"><div class="pstat-label">λ (px)</div><div class="pstat-val" id="wavesbar-lam" style="color:#06D6E0"></div></div>
<div class="pstat"><div class="pstat-label">v (px/с)</div><div class="pstat-val" id="wavesbar-v" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">f (Гц)</div><div class="pstat-val" id="wavesbar-f" style="color:#F15BB5"></div></div>
</div>
</div>
<!-- ── STEREO sim body ── -->
<div id="sim-stereo" class="sim-body-wrap" style="display:none">
<div class="stereo-panel">
<!-- ── Фигуры ── -->
<div class="gp-section-title" style="margin-bottom:6px">Многогранники</div>
<div class="stereo-fig-grid">
<button class="st-fig-btn stereo-fig-btn active" onclick="setStereoFigure('cube',this)" title="Куб">
<svg viewBox="0 0 24 24"><rect x="3" y="7" width="12" height="12"/><path d="M3 7l4-4h12v12l-4 4"/><path d="M15 3v12M3 19l4-4"/></svg>Куб
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('parallelepiped',this)" title="Прямоугольный параллелепипед">
<svg viewBox="0 0 24 24"><rect x="2" y="8" width="13" height="11"/><path d="M2 8l4-5h13v11l-4 5"/><path d="M15 3v11M2 19l4-5"/></svg>Параллел.
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('prism',this)" title="Правильная призма">
<svg viewBox="0 0 24 24"><polygon points="12,3 3,8 3,19 12,21 21,19 21,8"/><line x1="12" y1="3" x2="12" y2="21"/></svg>Призма
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('pyramid',this)" title="Правильная пирамида">
<svg viewBox="0 0 24 24"><polygon points="12,2 22,20 2,20"/><line x1="12" y1="2" x2="12" y2="20"/></svg>Пирамида
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('truncpyramid',this)" title="Усечённая пирамида">
<svg viewBox="0 0 24 24"><polygon points="6,6 18,6 22,20 2,20"/><line x1="9" y1="6" x2="4" y2="20"/><line x1="15" y1="6" x2="20" y2="20"/></svg>Усеч. пир.
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('tetrahedron',this)" title="Правильный тетраэдр">
<svg viewBox="0 0 24 24"><polygon points="12,2 22,20 2,20"/><line x1="12" y1="2" x2="12" y2="20"/><line x1="2" y1="20" x2="12" y2="14"/><line x1="22" y1="20" x2="12" y2="14"/></svg>Тетраэдр
</button>
</div>
<div class="gp-section-title" style="margin-top:6px;margin-bottom:6px">Правильные многогранники</div>
<div class="stereo-fig-grid">
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('octahedron',this)" title="Правильный октаэдр">
<svg viewBox="0 0 24 24"><polygon points="12,2 22,12 12,22 2,12"/><line x1="12" y1="2" x2="2" y2="12"/><line x1="12" y1="2" x2="22" y2="12"/><line x1="2" y1="12" x2="12" y2="22"/><line x1="22" y1="12" x2="12" y2="22"/></svg>Октаэдр
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('icosahedron',this)" title="Правильный икосаэдр">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><polygon points="12,3 20,8 18,17 6,17 4,8"/><line x1="12" y1="3" x2="12" y2="21"/></svg>Икосаэдр
</button>
<button class="st-fig-btn stereo-fig-btn st-fig-btn-wide" onclick="setStereoFigure('dodecahedron',this)" title="Правильный додекаэдр">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-dasharray="4,2"/><polygon points="12,2 20,8 17,17 7,17 4,8"/></svg>Додекаэдр
</button>
</div>
<div class="gp-section-title" style="margin-top:6px;margin-bottom:6px">Тела вращения</div>
<div class="stereo-fig-grid">
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('cylinder',this)" title="Цилиндр">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><ellipse cx="12" cy="19" rx="9" ry="3"/><line x1="3" y1="5" x2="3" y2="19"/><line x1="21" y1="5" x2="21" y2="19"/></svg>Цилиндр
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('cone',this)" title="Конус">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="19" rx="9" ry="3"/><line x1="3" y1="19" x2="12" y2="4"/><line x1="21" y1="19" x2="12" y2="4"/></svg>Конус
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('trunccone',this)" title="Усечённый конус">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="18" rx="9" ry="3"/><ellipse cx="12" cy="7" rx="5" ry="2"/><line x1="3" y1="18" x2="7" y2="7"/><line x1="21" y1="18" x2="17" y2="7"/></svg>Усеч. конус
</button>
<button class="st-fig-btn stereo-fig-btn" onclick="setStereoFigure('sphere',this)" title="Шар">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="9" ry="3"/></svg>Шар
</button>
</div>
<!-- ── Параметры ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Параметры</div>
<div id="stereo-params">
<div class="stereo-sl-row" id="sp-a-row">
<label>Сторона <b>a</b> <span id="sp-a-val">4</span></label>
<input type="range" id="sl-sp-a" min="1" max="10" step="0.5" value="4" oninput="stereoParamChange('a',this.value)">
</div>
<div class="stereo-sl-row" id="sp-b-row" style="display:none">
<label>Сторона <b>b</b> <span id="sp-b-val">3</span></label>
<input type="range" id="sl-sp-b" min="1" max="10" step="0.5" value="3" oninput="stereoParamChange('b',this.value)">
</div>
<div class="stereo-sl-row" id="sp-c-row" style="display:none">
<label>Сторона <b>c</b> <span id="sp-c-val">5</span></label>
<input type="range" id="sl-sp-c" min="1" max="10" step="0.5" value="5" oninput="stereoParamChange('c',this.value)">
</div>
<div class="stereo-sl-row" id="sp-h-row" style="display:none">
<label>Высота <b>h</b> <span id="sp-h-val">5</span></label>
<input type="range" id="sl-sp-h" min="1" max="12" step="0.5" value="5" oninput="stereoParamChange('h',this.value)">
</div>
<div class="stereo-sl-row" id="sp-r-row" style="display:none">
<label>Радиус <b>r</b> <span id="sp-r-val">2</span></label>
<input type="range" id="sl-sp-r" min="0.5" max="8" step="0.5" value="2" oninput="stereoParamChange('r',this.value)">
</div>
<div class="stereo-sl-row" id="sp-R-row" style="display:none">
<label>Радиус осн. <b>R</b> <span id="sp-R-val">3</span></label>
<input type="range" id="sl-sp-R" min="0.5" max="8" step="0.5" value="3" oninput="stereoParamChange('R',this.value)">
</div>
<div class="stereo-sl-row" id="sp-n-row" style="display:none">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
<span style="font-size:0.72rem;color:var(--text-2)">Граней основания <b>n</b></span>
<div class="st-n-ctrl">
<button class="st-n-btn" onclick="stereoNChange(-1)"><svg viewBox="0 0 16 16" fill="none"><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg></button>
<span class="st-n-val" id="sp-n-val">4</span>
<button class="st-n-btn" onclick="stereoNChange(+1)"><svg viewBox="0 0 16 16" fill="none"><line x1="8" y1="3" x2="8" y2="13" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg></button>
</div>
</div>
</div>
</div>
<div class="stereo-sl-row" style="margin-top:2px">
<label>Прозрачность <span id="sp-opacity-val">0.3</span></label>
<input type="range" id="sl-sp-opacity" min="0" max="1" step="0.05" value="0.3" oninput="stereoOpacityChange(this.value)">
</div>
<!-- ── Отображение ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:4px">Отображение</div>
<div onclick="stereoToggleSt('edges',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18"/></svg>Рёбра</span>
<div class="st-toggle on" id="stg-edges"></div>
</div>
<div onclick="stereoToggleSt('vertices',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" fill="currentColor"/></svg>Вершины</span>
<div class="st-toggle on" id="stg-vertices"></div>
</div>
<div onclick="stereoToggleSt('labels',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h10M4 17h7"/></svg>Подписи</span>
<div class="st-toggle on" id="stg-labels"></div>
</div>
<div onclick="stereoToggleSt('axes',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="2" y1="12" x2="22" y2="12"/><line x1="12" y1="2" x2="12" y2="22"/></svg>Оси</span>
<div class="st-toggle on" id="stg-axes"></div>
</div>
<div onclick="stereoToggleSt('grid',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>Сетка</span>
<div class="st-toggle on" id="stg-grid"></div>
</div>
<!-- ── Элементы фигуры ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:4px">Элементы</div>
<div onclick="stereoToggleElem('height',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="12" y1="3" x2="12" y2="21" stroke-dasharray="4,2"/><line x1="9" y1="21" x2="15" y2="21"/></svg>Высота</span>
<div class="st-toggle" id="stg-height"></div>
</div>
<div onclick="stereoToggleElem('apothem',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="6" y1="19" x2="18" y2="19"/></svg>Апофема</span>
<div class="st-toggle" id="stg-apothem"></div>
</div>
<div onclick="stereoToggleElem('diagonals',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18"/><line x1="3" y1="3" x2="21" y2="21" stroke-dasharray="4,2"/><line x1="21" y1="3" x2="3" y2="21" stroke-dasharray="4,2"/></svg>Диагонали</span>
<div class="st-toggle" id="stg-diagonals"></div>
</div>
<div onclick="stereoToggleElem('midpoints',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>Середины рёбер</span>
<div class="st-toggle" id="stg-midpoints"></div>
</div>
<div onclick="stereoToggleElem('inscribed',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="5" stroke-dasharray="3,2"/><rect x="3" y="3" width="18" height="18"/></svg>Вписанная сфера</span>
<div class="st-toggle" id="stg-inscribed"></div>
</div>
<div onclick="stereoToggleElem('circumscribed',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-dasharray="4,2"/><polygon points="6,18 18,18 12,6"/></svg>Описанная сфера</span>
<div class="st-toggle" id="stg-circumscribed"></div>
</div>
<div onclick="stereoToggleElem('edgelengths',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="9" x2="4" y2="15"/><line x1="20" y1="9" x2="20" y2="15"/></svg>Длины рёбер</span>
<div class="st-toggle" id="stg-edgelengths"></div>
</div>
<div id="sect-area-display" style="font-size:0.7rem;color:#06D6E0;margin-top:2px;display:none"></div>
<div id="sphere-radius-info" style="font-size:0.7rem;color:#F59E0B;margin-top:2px;display:none"></div>
<!-- ── Сечение ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Сечение</div>
<div class="st-tool-grid" style="margin-bottom:4px">
<button class="st-tool-btn stereo-sect-btn st-tool-btn-wide" id="sect-toggle" onclick="stereoSectionToggle(this)">
<svg viewBox="0 0 24 24"><path d="M3 12h18M12 3v18" stroke-dasharray="4,2"/></svg>Показать сечение
</button>
<button class="st-tool-btn stereo-sect-type active" data-type="horizontal" onclick="stereoSectionType('horizontal',this)">
<svg viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/></svg>Горизонт.
</button>
<button class="st-tool-btn stereo-sect-type" data-type="diagonal" onclick="stereoSectionType('diagonal',this)">
<svg viewBox="0 0 24 24"><line x1="4" y1="18" x2="20" y2="6"/></svg>Диагональ
</button>
<button class="st-tool-btn stereo-sect-type st-tool-btn-wide" data-type="custom" onclick="stereoSectionType('custom',this)">
<svg viewBox="0 0 24 24"><circle cx="5" cy="19" r="2" fill="currentColor"/><circle cx="12" cy="5" r="2" fill="currentColor"/><circle cx="19" cy="15" r="2" fill="currentColor"/><polyline points="5,19 12,5 19,15 5,19"/></svg>По 3+ точкам
</button>
</div>
<div class="stereo-sl-row">
<label>Высота <span id="sp-sect-val">50%</span></label>
<input type="range" id="sl-sp-sect" min="0" max="100" step="1" value="50" oninput="stereoSectionHeight(this.value)">
</div>
<div class="stereo-sl-row" id="sp-angle-row" style="display:none">
<label>Наклон <span id="sp-angle-val">50%</span></label>
<input type="range" id="sl-sp-angle" min="0" max="100" step="1" value="50" oninput="stereoSectionAngle(this.value)">
</div>
<!-- ── Инструменты ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Инструменты</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-point-btn" onclick="stereoPointMode(this)" title="Поставить точку на ребро">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" fill="currentColor"/><line x1="3" y1="12" x2="21" y2="12"/></svg>Точка
</button>
<button class="st-tool-btn" id="stereo-connect-btn" onclick="stereoConnectMode(this)" title="Соединить две точки отрезком">
<svg viewBox="0 0 24 24"><circle cx="5" cy="19" r="2.5" fill="currentColor"/><circle cx="19" cy="5" r="2.5" fill="currentColor"/><line x1="5" y1="19" x2="19" y2="5" stroke-dasharray="4,2"/></svg>Соединить
</button>
<button class="st-tool-btn" id="stereo-measure-btn" onclick="stereoMeasure(this)" title="Измерить расстояние между двумя точками">
<svg viewBox="0 0 24 24"><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="9" x2="4" y2="15"/><line x1="20" y1="9" x2="20" y2="15"/></svg>Измерение
</button>
<button class="st-tool-btn" id="stereo-unfold-btn" onclick="stereoUnfold(this)" title="Развернуть в сетку">
<svg viewBox="0 0 24 24"><rect x="3" y="10" width="18" height="11"/><path d="M8 10V6a4 4 0 0 1 8 0v4"/></svg>Развёртка
</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoUndoPoint()">Удалить точку</button>
<button class="st-action-btn" onclick="stereoClearPoints()">Очист. точки</button>
<button class="st-action-btn" onclick="stereoMeasureUndo()">Удалить изм.</button>
<button class="st-action-btn" onclick="stereoMeasureClear()">Очист. изм.</button>
</div>
<div id="points-info" style="font-size:0.65rem;color:rgba(255,255,255,0.4);margin-top:2px"></div>
<!-- ── Метки рёбер ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Метки рёбер</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-mark-tick-btn" onclick="stereoMarkMode('ticks',this)" title="Засечки равных рёбер — кликните ребро (до 3 штрихов)">
<svg viewBox="0 0 24 24"><line x1="5" y1="19" x2="19" y2="5"/><line x1="9" y1="9" x2="11" y2="11"/><line x1="13" y1="13" x2="15" y2="15"/></svg>Засечки =
</button>
<button class="st-tool-btn" id="stereo-mark-par-btn" onclick="stereoMarkMode('parallel',this)" title="Метки параллельных рёбер — кликните ребро">
<svg viewBox="0 0 24 24"><line x1="3" y1="8" x2="21" y2="8"/><line x1="3" y1="16" x2="21" y2="16"/><polyline points="15 5 18 8 15 11"/><polyline points="15 13 18 16 15 19"/></svg>Паралл. →
</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn st-tool-btn-wide" onclick="stereoMarkClear()" style="grid-column:span 2">Очистить метки</button>
</div>
<!-- ── Производные точки ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Производные точки</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-derive-mid-btn" onclick="stereoDerive('midpoint',this)" title="Середина ребра — кликните ребро">
<svg viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><circle cx="12" cy="12" r="3.5" fill="currentColor"/></svg>Сер. ребра
</button>
<button class="st-tool-btn" id="stereo-derive-fc-btn" onclick="stereoDerive('face_centroid',this)" title="Центр грани — кликните грань">
<svg viewBox="0 0 24 24"><polygon points="12,3 21,21 3,21"/><circle cx="12" cy="15" r="2.5" fill="currentColor"/></svg>Центр грани
</button>
<button class="st-tool-btn" id="stereo-derive-alt-btn" onclick="stereoDerive('alt_foot',this)" title="Основание высоты: вершина → ребро">
<svg viewBox="0 0 24 24"><line x1="3" y1="20" x2="21" y2="20"/><line x1="10" y1="4" x2="10" y2="20" stroke-dasharray="4,2"/><path d="M10 20 L13 20 L13 17" fill="none"/></svg>Осн. высоты
</button>
<button class="st-tool-btn" id="stereo-derive-cen-btn" onclick="stereoDerive('solid_centroid',this)" title="Центроид тела — точка G">
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18"/><circle cx="12" cy="12" r="3" fill="currentColor"/><line x1="3" y1="3" x2="21" y2="21" stroke-dasharray="4,2" opacity=".4"/><line x1="21" y1="3" x2="3" y2="21" stroke-dasharray="4,2" opacity=".4"/></svg>Центроид G
</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoDeriveUndo()">Удалить послед.</button>
<button class="st-action-btn" onclick="stereoDeriveClear()">Очистить</button>
</div>
<!-- ── Углы и расстояния ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Углы и расстояния</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-angle-edge-btn" onclick="stereoAngleMode('edge',this)" title="Угол между рёбрами: 3 точки A-B-C">
<svg viewBox="0 0 24 24"><line x1="12" y1="19" x2="4" y2="5"/><line x1="12" y1="19" x2="20" y2="6"/><path d="M7 16 Q12 13 17 15" fill="none" stroke-dasharray="3,2"/></svg>∠ рёбер
</button>
<button class="st-tool-btn" id="stereo-angle-lp-btn" onclick="stereoAngleMode('linePlane',this)" title="Угол прямой к плоскости: 2 точки → грань">
<svg viewBox="0 0 24 24"><line x1="4" y1="20" x2="20" y2="4"/><ellipse cx="12" cy="18" rx="9" ry="3" fill="none" opacity=".6"/></svg>∠ пр.–пл.
</button>
<button class="st-tool-btn" id="stereo-angle-dih-btn" onclick="stereoAngleMode('dihedral',this)" title="Двугранный угол: 2 точки общего ребра">
<svg viewBox="0 0 24 24"><line x1="12" y1="3" x2="12" y2="21"/><line x1="3" y1="12" x2="12" y2="21"/><line x1="21" y1="7" x2="12" y2="21"/></svg>∠ двугр.
</button>
<button class="st-tool-btn" id="stereo-angle-pp-btn" onclick="stereoAngleMode('pointPlane',this)" title="Расстояние точка–плоскость">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="18" rx="9" ry="3" fill="none"/><line x1="12" y1="5" x2="12" y2="18" stroke-dasharray="3,2"/><circle cx="12" cy="5" r="2.5" fill="currentColor"/></svg>d(т→пл)
</button>
<button class="st-tool-btn st-tool-btn-wide" id="stereo-angle-skew-btn" onclick="stereoAngleMode('skewLines',this)" title="Угол и расстояние скрещивающихся прямых: P1,P2→P3,P4">
<svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="12" opacity=".5"/><circle cx="12" cy="9" r="2" fill="currentColor"/></svg>∠ скрещивающихся прямых
</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" style="grid-column:span 2" onclick="stereoAngleClear()">Очистить углы</button>
</div>
<div id="angle-hint" style="font-size:0.63rem;color:rgba(255,255,255,0.38);margin-top:3px;line-height:1.4"></div>
<!-- ── Формулы ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:4px">Формулы</div>
<div id="stereo-formulas" style="font-size:0.72rem;color:rgba(255,255,255,0.7);line-height:1.5;margin-bottom:6px"></div>
</div>
<div class="graph-canvas-outer">
<div class="graph-canvas-wrap" id="stereo-container"></div>
</div>
</div>
<!-- stats bar for stereo -->
<div class="proj-stats-bar" id="stereo-stats" style="display:none">
<div class="pstat"><div class="pstat-label">Объём</div><div class="pstat-val" id="stbar-vol"></div></div>
<div class="pstat"><div class="pstat-label">Полная S</div><div class="pstat-val" id="stbar-area"></div></div>
<div class="pstat"><div class="pstat-label">Бок. S</div><div class="pstat-val" id="stbar-side"></div></div>
<div class="pstat"><div class="pstat-label">Высота</div><div class="pstat-val" id="stbar-h"></div></div>
<div class="pstat"><div class="pstat-label">Диагональ</div><div class="pstat-val" id="stbar-d"></div></div>
</div>
<!-- ── HYDROSTATICS sim body ── -->
<div id="sim-hydro" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- left panel -->
<div class="proj-panel" style="width:230px;gap:0;overflow-y:auto">
<div class="gp-section-title" style="margin-bottom:8px">Параметры</div>
<!-- liquid -->
<div style="margin-bottom:10px">
<div style="font-size:.72rem;color:rgba(255,255,255,.4);margin-bottom:4px">Жидкость</div>
<select onchange="hydroSim&&hydroSim.setLiquid(this.value);document.getElementById('hydro-liq-sel').value=this.value" style="width:100%;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:5px 8px;font-size:.78rem">
<option value="water">Вода (1000 кг/м³)</option>
<option value="saltwater">Солёная вода (1030)</option>
<option value="oil">Масло (900)</option>
<option value="alcohol">Спирт (790)</option>
<option value="glycerin">Глицерин (1260)</option>
<option value="mercury">Ртуть (13600)</option>
</select>
</div>
<!-- material (Archimedes only) -->
<div id="hydro-panel-mat" style="margin-bottom:10px;display:none">
<div style="font-size:.72rem;color:rgba(255,255,255,.4);margin-bottom:4px">Материал тела</div>
<select onchange="hydroSim&&hydroSim.setMaterial(this.value)" style="width:100%;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:5px 8px;font-size:.78rem">
<option value="styrofoam">Пенопласт (30 кг/м³)</option>
<option value="cork">Пробка (120)</option>
<option value="wood">Дерево (500)</option>
<option value="ice">Лёд (900)</option>
<option value="plastic">Пластик (1100)</option>
<option value="glass">Стекло (2500)</option>
<option value="aluminum">Алюминий (2700)</option>
<option value="iron">Железо (7800)</option>
<option value="gold">Золото (19300)</option>
</select>
<div style="display:flex;gap:5px;margin-top:6px">
<button class="gp-btn" onclick="hydroSim&&hydroSim.addBody()" style="flex:1">+ Тело</button>
<button class="gp-btn" onclick="hydroSim&&hydroSim.clearBodies()" style="flex:1">Очистить</button>
</div>
</div>
<!-- contact angle (surface tension) -->
<div id="hydro-panel-theta" style="margin-bottom:10px;display:none">
<div style="display:flex;justify-content:space-between;font-size:.72rem;color:rgba(255,255,255,.4);margin-bottom:4px">
<span>Краевой угол θ</span>
<span id="hydro-theta-lbl" style="color:#9B5DE5">20°</span>
</div>
<input type="range" min="0" max="160" value="20" step="5" style="width:100%;accent-color:#9B5DE5" oninput="hydroSim&&hydroSim.setContactAngle(+this.value);document.getElementById('hydro-theta-lbl').textContent=this.value+'\u00B0';document.getElementById('hydro-theta-val').textContent=this.value+'\u00B0';document.querySelector('#hydro-surf-ctrl input[type=range]').value=this.value">
<div style="display:flex;justify-content:space-between;font-size:.65rem;color:rgba(255,255,255,.25);margin-top:2px">
<span>Смачивание</span><span>Несмачивание</span>
</div>
<div style="margin-top:6px">
<button class="gp-btn" id="hydro-surf-toggle-panel" onclick="hydroToggleSurface()" style="width:100%">Капилляры</button>
</div>
</div>
<!-- communicating vessels -->
<div id="hydro-panel-comm" style="margin-bottom:10px;display:none">
<div style="font-size:.72rem;color:rgba(255,255,255,.4);margin-bottom:4px">Сосудов</div>
<div style="display:flex;gap:5px">
<button class="gp-btn hydro-nv active" onclick="hydroSetVessels(2,this)" style="flex:1">2</button>
<button class="gp-btn hydro-nv" onclick="hydroSetVessels(3,this)" style="flex:1">3</button>
<button class="gp-btn hydro-nv" onclick="hydroSetVessels(4,this)" style="flex:1">4</button>
</div>
<div style="margin-top:8px">
<button class="gp-btn" id="hydro-valve-panel-btn" onclick="hydroToggleValve()" style="width:100%;color:#06D6A0;border-color:rgba(6,214,160,.3)">Кран: открыт</button>
</div>
<div style="margin-top:6px;display:flex;gap:5px">
<button class="gp-btn" onclick="hydroSim&&hydroSim.addLiquid(0)" style="flex:1">+ Жидкость</button>
<button class="gp-btn" onclick="hydroSim&&hydroSim.removeLiquid()" style="flex:1">- Жидкость</button>
</div>
</div>
<!-- formula display -->
<div class="gp-section-title" style="margin-top:4px;margin-bottom:6px">Формулы</div>
<div id="hydro-formulas" style="font-size:.72rem;font-family:'JetBrains Mono',monospace;color:rgba(255,255,255,.6);line-height:1.7;background:rgba(255,255,255,.03);border-radius:8px;padding:8px 10px;min-height:80px"></div>
<!-- result badge -->
<div id="hydro-result" style="margin-top:8px;font-size:.82rem;font-weight:700;text-align:center;padding:8px;border-radius:8px;display:none"></div>
</div><!-- /.proj-panel -->
<!-- canvas area -->
<div style="flex:1;min-width:0;position:relative">
<canvas id="hydro-canvas" style="width:100%;height:100%;display:block"></canvas>
</div>
</div><!-- /.sim-body-wrap -->
</div><!-- /#sim-hydro -->
<!-- ══════════════════════════════════════════════
ПЛАНИМЕТРИЯ
══════════════════════════════════════════════ -->
<div id="sim-geometry" class="sim-proj-wrap" style="display:none">
<div class="sim-body-wrap">
<!-- left panel -->
<div class="geo-panel">
<!-- Tool: select + point -->
<div class="gp-section-title">Инструмент</div>
<div class="geo-tool-grid">
<button id="geo-btn-select" class="geo-tool-btn active" onclick="geoSetTool('select',this)" title="Выделить / переместить (Esc)">
<svg viewBox="0 0 24 24" fill="none"><path d="M5 3l14 9-7 1-4 7z" stroke-width="2"/></svg>
Выбор
</button>
<button id="geo-btn-point" class="geo-tool-btn" onclick="geoSetTool('point',this)" title="Поставить точку">
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="4" fill="currentColor"/></svg>
Точка
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Построения</div>
<div class="geo-tool-grid">
<button id="geo-btn-segment" class="geo-tool-btn" onclick="geoSetTool('segment',this)" title="Отрезок — 2 точки">
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="20" x2="20" y2="4" stroke-width="2.5"/><circle cx="4" cy="20" r="2.5" fill="currentColor"/><circle cx="20" cy="4" r="2.5" fill="currentColor"/></svg>
Отрезок
</button>
<button id="geo-btn-line" class="geo-tool-btn" onclick="geoSetTool('line',this)" title="Прямая — 2 точки">
<svg viewBox="0 0 24 24" fill="none"><line x1="2" y1="22" x2="22" y2="2" stroke-width="2" stroke-dasharray="3,2"/></svg>
Прямая
</button>
<button id="geo-btn-ray" class="geo-tool-btn" onclick="geoSetTool('ray',this)" title="Луч — начало + направление">
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="20" x2="22" y2="4" stroke-width="2"/><polyline points="17 4 22 4 22 9" stroke-width="2"/><circle cx="4" cy="20" r="2.5" fill="currentColor"/></svg>
Луч
</button>
<button id="geo-btn-circle" class="geo-tool-btn" onclick="geoSetTool('circle',this)" title="Окружность — центр + радиус">
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="8" stroke-width="2"/><circle cx="12" cy="12" r="2" fill="currentColor"/></svg>
Круг
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Фигуры</div>
<div class="geo-tool-grid">
<button id="geo-btn-triangle" class="geo-tool-btn" onclick="geoSetTool('triangle',this)" title="Треугольник — 3 точки">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 22,21 2,21" stroke-width="2"/></svg>
Треуг.
</button>
<button id="geo-btn-quad" class="geo-tool-btn" onclick="geoSetTool('quad',this)" title="Четырёхугольник — 4 точки">
<svg viewBox="0 0 24 24" fill="none"><polygon points="3,6 21,4 20,19 4,20" stroke-width="2"/></svg>
Четырёх.
</button>
<button id="geo-btn-polygon" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('polygon',this)" title="Многоугольник — N точек, Enter/двойной клик для завершения">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,2 22,8 19,21 5,21 2,8" stroke-width="2"/></svg>
Многоугольник
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Построения</div>
<div class="geo-tool-grid">
<button id="geo-btn-midpoint" class="geo-tool-btn" onclick="geoSetTool('midpoint',this)" title="Середина отрезка — 2 точки">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="12" x2="21" y2="12" stroke-width="2"/><circle cx="12" cy="12" r="3.5" fill="currentColor"/></svg>
Середина
</button>
<button id="geo-btn-perpbisect" class="geo-tool-btn" onclick="geoSetTool('perpbisect',this)" title="Серединный перпендикуляр — 2 точки">
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="18" x2="20" y2="6" stroke-width="2"/><line x1="12" y1="2" x2="12" y2="22" stroke-width="1.5" stroke-dasharray="3,2"/><circle cx="12" cy="12" r="2.5" fill="currentColor"/></svg>
⊥ биссект.
</button>
<button id="geo-btn-anglebisect" class="geo-tool-btn" onclick="geoSetTool('anglebisect',this)" title="Биссектриса угла — 3 точки: A, вершина, B">
<svg viewBox="0 0 24 24" fill="none"><polyline points="4,20 12,4 20,20" stroke-width="2"/><line x1="12" y1="4" x2="12" y2="20" stroke-width="1.5" stroke-dasharray="3,2"/></svg>
∠ биссект.
</button>
<button id="geo-btn-parallel" class="geo-tool-btn" onclick="geoSetTool('parallel',this)" title="Параллельная прямая — клик на линию, затем на точку">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="8" x2="21" y2="8" stroke-width="2"/><line x1="3" y1="16" x2="21" y2="16" stroke-width="2" opacity=".5" stroke-dasharray="4,3"/></svg>
|| прямая
</button>
<button id="geo-btn-perpendicular" class="geo-tool-btn" onclick="geoSetTool('perpendicular',this)" title="Перпендикулярная прямая — клик на линию, затем на точку">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="12" x2="21" y2="12" stroke-width="2"/><line x1="12" y1="4" x2="12" y2="20" stroke-width="2" opacity=".5" stroke-dasharray="4,3"/></svg>
⊥ прямая
</button>
<button id="geo-btn-intersect" class="geo-tool-btn" onclick="geoSetTool('intersect',this)" title="Точка пересечения — клик на две прямые">
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="20" x2="20" y2="4" stroke-width="2"/><line x1="4" y1="4" x2="20" y2="20" stroke-width="2"/><circle cx="12" cy="12" r="3.5" fill="currentColor"/></svg>
Пересеч.
</button>
<button id="geo-btn-foot" class="geo-tool-btn" onclick="geoSetTool('foot',this)" title="Основание перпендикуляра — клик на прямую, затем на точку">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="18" x2="21" y2="18" stroke-width="2"/><line x1="12" y1="18" x2="12" y2="4" stroke-width="1.5" stroke-dasharray="3,2"/><path d="M12 18 L15 18 L15 15" stroke-width="1.5" fill="none"/><circle cx="12" cy="4" r="2.5" fill="currentColor"/></svg>
Основание
</button>
<button id="geo-btn-circumcircle" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('circumcircle',this)" title="Описанная окружность — 3 точки треугольника">
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke-width="1.5" stroke-dasharray="4,3"/><polygon points="6,18 18,18 12,6" stroke-width="1.5" fill="none"/></svg>
Описанная ☉
</button>
<button id="geo-btn-incircle" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('incircle',this)" title="Вписанная окружность — 3 точки треугольника">
<svg viewBox="0 0 24 24" fill="none"><polygon points="4,20 20,20 12,4" stroke-width="1.5" fill="none"/><circle cx="12" cy="15" r="5" stroke-width="1.5" stroke-dasharray="4,3"/></svg>
Вписанная ○
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Преобразования</div>
<div class="geo-tool-grid">
<button id="geo-btn-reflect" class="geo-tool-btn" onclick="geoSetTool('reflect',this)" title="Симметрия точки относительно прямой — клик на ось, затем на точку">
<svg viewBox="0 0 24 24" fill="none"><line x1="12" y1="2" x2="12" y2="22" stroke-width="1.5" stroke-dasharray="3,2"/><circle cx="6" cy="12" r="3" stroke-width="1.5"/><circle cx="18" cy="12" r="2.5" stroke-width="1.5" opacity=".5"/><line x1="9" y1="12" x2="15" y2="12" stroke-width="1" opacity=".6"/></svg>
Симметрия
</button>
<button id="geo-btn-translate" class="geo-tool-btn" onclick="geoSetTool('translate',this)" title="Параллельный перенос — вектор AB, затем точка P">
<svg viewBox="0 0 24 24" fill="none"><circle cx="6" cy="18" r="2.5" stroke-width="1.5"/><circle cx="18" cy="6" r="2.5" stroke-width="1.5"/><line x1="6" y1="18" x2="18" y2="6" stroke-width="1.5" marker-end="url(#arrow)"/><circle cx="14" cy="18" r="2.5" stroke-width="1.5" opacity=".4"/><circle cx="21" cy="9" r="2" stroke-width="1.5" stroke-dasharray="3,2"/></svg>
Перенос
</button>
<button id="geo-btn-tangent" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('tangent',this)" title="Касательные из точки к окружности — клик на окружность, затем на точку">
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="7" stroke-width="1.5"/><line x1="4" y1="4" x2="19" y2="12" stroke-width="1.5" stroke-dasharray="4,3"/><line x1="4" y1="20" x2="19" y2="12" stroke-width="1.5" stroke-dasharray="4,3"/><circle cx="4" cy="12" r="2.5" fill="currentColor"/></svg>
Касательные
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Элементы треугольника</div>
<div class="geo-tool-grid">
<button id="geo-btn-altitude" class="geo-tool-btn" onclick="geoSetTool('altitude',this)" title="Высота — клик на сторону, затем на вершину">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="20" x2="21" y2="20" stroke-width="1.5"/><line x1="10" y1="4" x2="10" y2="20" stroke-width="1.5" stroke-dasharray="4,3"/><rect x="10" y="14" width="4" height="4" stroke-width="1.2"/><polygon points="10,4 3,20 21,20" stroke-width="1.5" fill="none"/></svg>
Высота
</button>
<button id="geo-btn-median" class="geo-tool-btn" onclick="geoSetTool('median',this)" title="Медиана — клик вершина A, B, C">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 3,20 21,20" stroke-width="1.5" fill="none"/><line x1="12" y1="3" x2="12" y2="20" stroke-width="1.5"/><circle cx="12" cy="20" r="2.5" fill="currentColor"/></svg>
Медиана
</button>
<button id="geo-btn-centroid" class="geo-tool-btn" onclick="geoSetTool('centroid',this)" title="Центроид — 3 точки треугольника, строит 3 медианы">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 3,20 21,20" stroke-width="1.5" fill="none"/><line x1="12" y1="3" x2="12" y2="20" stroke-width="1.2" opacity=".6"/><line x1="3" y1="20" x2="16.5" y2="11.5" stroke-width="1.2" opacity=".6"/><line x1="21" y1="20" x2="7.5" y2="11.5" stroke-width="1.2" opacity=".6"/><circle cx="12" cy="14.3" r="2.5" fill="currentColor"/></svg>
Центроид
</button>
<button id="geo-btn-orthocenter" class="geo-tool-btn" onclick="geoSetTool('orthocenter',this)" title="Ортоцентр — 3 точки треугольника, строит 3 высоты">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 3,20 21,20" stroke-width="1.5" fill="none"/><line x1="12" y1="3" x2="12" y2="20" stroke-width="1.2" stroke-dasharray="3,2" opacity=".6"/><line x1="3" y1="20" x2="16" y2="12" stroke-width="1.2" stroke-dasharray="3,2" opacity=".6"/><line x1="21" y1="20" x2="8" y2="12" stroke-width="1.2" stroke-dasharray="3,2" opacity=".6"/><circle cx="12" cy="14" r="2.5" fill="currentColor"/></svg>
Ортоцентр
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Средняя линия и четырёхугольники</div>
<div class="geo-tool-grid">
<button id="geo-btn-midline" class="geo-tool-btn" onclick="geoSetTool('midline',this)" title="Средняя линия треугольника — 3 вершины A, B, C">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 3,20 21,20" stroke-width="1.5" fill="none"/><line x1="7.5" y1="11.5" x2="16.5" y2="11.5" stroke-width="2"/><circle cx="7.5" cy="11.5" r="2" fill="currentColor"/><circle cx="16.5" cy="11.5" r="2" fill="currentColor"/></svg>
Средняя линия
</button>
<button id="geo-btn-parallelogram" class="geo-tool-btn" onclick="geoSetTool('parallelogram',this)" title="Параллелограмм — 3 точки A, B, C → вычисляет D">
<svg viewBox="0 0 24 24" fill="none"><polygon points="6,19 3,5 18,5 21,19" stroke-width="1.5" fill="none"/></svg>
Параллелограмм
</button>
<button id="geo-btn-diagonal" class="geo-tool-btn" onclick="geoSetTool('diagonal',this)" title="Диагонали — клик внутри многоугольника (4+ вершин)">
<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" stroke-width="1.5" fill="none"/><line x1="3" y1="3" x2="21" y2="21" stroke-width="1.5" stroke-dasharray="4,2"/><line x1="21" y1="3" x2="3" y2="21" stroke-width="1.5" stroke-dasharray="4,2"/></svg>
Диагонали
</button>
<button id="geo-btn-scale" class="geo-tool-btn" onclick="geoSetTool('scale',this)" title="Подобие — клик центр O, затем клик точку P → P&#39; = O + k·(P O)">
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="2" fill="currentColor"/><line x1="12" y1="12" x2="20" y2="6" stroke-width="1.5"/><circle cx="20" cy="6" r="2.5" stroke-width="1.5"/><line x1="12" y1="12" x2="17" y2="18" stroke-width="1.5" stroke-dasharray="3,2"/><circle cx="17" cy="18" r="2.5" stroke-width="1.5" stroke-dasharray="3,2"/></svg>
Подобие
</button>
</div>
<div class="geo-ngon-ctrl" id="geo-scale-ctrl" style="gap:6px;padding:2px 0 4px">
<span style="font-size:11px;opacity:.7">k =</span>
<button class="geo-ngon-btn" onclick="geoScaleK(-0.5)">
<svg viewBox="0 0 16 16" fill="none"><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<span id="geo-scale-k">2</span>
<button class="geo-ngon-btn" onclick="geoScaleK(+0.5)">
<svg viewBox="0 0 16 16" fill="none"><line x1="8" y1="3" x2="8" y2="13" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
<div class="gp-section-title" style="margin-top:4px">Правильный многоугольник</div>
<div class="geo-tool-grid">
<button id="geo-btn-ngon" class="geo-tool-btn" onclick="geoSetTool('ngon',this)" title="Правильный n-угольник — клик центр, клик вершина">
<svg viewBox="0 0 24 24" fill="none"><polygon points="12,3 20.2,8.5 17.3,18 6.7,18 3.8,8.5" stroke-width="1.5" fill="none"/></svg>
n-угольник
</button>
<div class="geo-ngon-ctrl" id="geo-ngon-ctrl">
<button class="geo-ngon-btn" onclick="geoNgonN(-1)">
<svg viewBox="0 0 16 16" fill="none"><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<span id="geo-ngon-n">6</span>
<button class="geo-ngon-btn" onclick="geoNgonN(+1)">
<svg viewBox="0 0 16 16" fill="none"><line x1="8" y1="3" x2="8" y2="13" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="8" x2="13" y2="8" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<!-- Thales theorem -->
<div class="gp-section-title" style="margin-top:4px">Теорема Фалеса</div>
<div class="geo-tool-grid">
<button id="geo-btn-thales" class="geo-tool-btn" onclick="geoSetTool('thales',this)" title="Теорема Фалеса — клик O, затем A, затем B → A&#39;B&#39; ∥ AB">
<svg viewBox="0 0 24 24" fill="none"><circle cx="4" cy="20" r="2" fill="currentColor"/><line x1="4" y1="20" x2="22" y2="4" stroke-width="1.5"/><line x1="4" y1="20" x2="22" y2="12" stroke-width="1.5"/><line x1="10" y1="15" x2="13" y2="12" stroke-width="2" stroke-dasharray="0"/><line x1="17" y1="9" x2="20" y2="7" stroke-width="2" opacity=".6"/></svg>
Фалес
</button>
</div>
<!-- Mark tools -->
<div class="gp-section-title" style="margin-top:4px">Метки</div>
<div class="geo-tool-grid">
<button id="geo-btn-tick" class="geo-tool-btn" onclick="geoSetTool('tick',this)" title="Метки равных сторон — клик на отрезок или сторону (1–3 штриха)">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="20" x2="21" y2="4" stroke-width="1.5"/><line x1="11" y1="7" x2="8" y2="11" stroke-width="2" stroke-linecap="round"/><line x1="13" y1="9" x2="10" y2="13" stroke-width="2" stroke-linecap="round"/></svg>
Штрихи
</button>
<button id="geo-btn-arcmark" class="geo-tool-btn" onclick="geoSetTool('arcmark',this)" title="Метки равных углов — клик на вершину полигона (1–3 дуги)">
<svg viewBox="0 0 24 24" fill="none"><path d="M4 20 L20 20 L20 4" stroke-width="1.5" fill="none"/><path d="M8 20 A12 12 0 0 1 20 8" stroke-width="1.5" fill="none"/><path d="M11 20 A9 9 0 0 1 20 11" stroke-width="1.5" fill="none"/></svg>
Дуги
</button>
<button id="geo-btn-parallelmark" class="geo-tool-btn" onclick="geoSetTool('parallelmark',this)" title="Метки параллельных сторон — клик на отрезок (1–2 стрелки)">
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="8" x2="21" y2="8" stroke-width="1.5"/><polyline points="9,5 13,8 9,11" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="3" y1="16" x2="21" y2="16" stroke-width="1.5"/><polyline points="9,13 13,16 9,19" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
Параллельность
</button>
</div>
<!-- Display options -->
<div class="gp-section-title" style="margin-top:6px">Параметры</div>
<label class="geo-toggle-row" onclick="geoToggle('showGrid',this)">
<span class="geo-toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
Сетка
</span>
<div class="geo-toggle on" id="geo-tog-showGrid"></div>
</label>
<label class="geo-toggle-row" onclick="geoToggle('showAxes',this)">
<span class="geo-toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><line x1="12" y1="20" x2="12" y2="4"/><line x1="4" y1="12" x2="20" y2="12"/></svg>
Оси
</span>
<div class="geo-toggle on" id="geo-tog-showAxes"></div>
</label>
<label class="geo-toggle-row" onclick="geoToggle('showLabels',this)">
<span class="geo-toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
Метки
</span>
<div class="geo-toggle on" id="geo-tog-showLabels"></div>
</label>
<label class="geo-toggle-row" onclick="geoToggle('showLengths',this)">
<span class="geo-toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="8" x2="4" y2="16"/><line x1="20" y1="8" x2="20" y2="16"/></svg>
Длины
</span>
<div class="geo-toggle" id="geo-tog-showLengths"></div>
</label>
<label class="geo-toggle-row" onclick="geoToggle('showAngles',this)">
<span class="geo-toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 20 L20 20 L20 4"/><path d="M7 20 A13 13 0 0 1 20 9"/></svg>
Углы
</span>
<div class="geo-toggle" id="geo-tog-showAngles"></div>
</label>
<!-- Stats -->
<div class="gp-section-title" style="margin-top:6px">Объектов</div>
<div style="display:flex;flex-direction:column;gap:0">
<div class="geo-stat-row"><span>Точки</span><b id="geo-st-pts">0</b></div>
<div class="geo-stat-row"><span>Отрезки</span><b id="geo-st-segs">0</b></div>
<div class="geo-stat-row"><span>Окружности</span><b id="geo-st-circs">0</b></div>
<div class="geo-stat-row"><span>Многоугольники</span><b id="geo-st-polys">0</b></div>
<div class="geo-stat-row"><span>Построения</span><b id="geo-st-constr">0</b></div>
</div>
<!-- Actions -->
<div style="margin-top:auto;padding-top:8px;display:flex;flex-direction:column;gap:4px">
<button class="gp-btn" onclick="geomSim&&geomSim.reset()" title="Очистить всё">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/></svg>
Очистить
</button>
</div>
</div><!-- /.geo-panel -->
<!-- canvas area -->
<div class="geo-canvas-outer">
<canvas id="geo-canvas"></canvas>
<div class="geo-hint-bar" id="geo-hint">Кликни для добавления точки</div>
<div class="geo-del-confirm" id="geo-del-confirm">
<span id="geo-del-msg"></span>
<button class="geo-del-btn geo-del-btn-soft" id="geo-del-soft">Только этот</button>
<button class="geo-del-btn geo-del-btn-hard" id="geo-del-hard">Со всеми зависимыми</button>
<button class="geo-del-btn geo-del-btn-cancel" id="geo-del-cancel">Отмена</button>
</div>
</div>
</div><!-- /.sim-body-wrap -->
</div><!-- /#sim-geometry -->
<!-- ── Theory panel (overlay right) ── -->
<div class="theory-panel" id="theory-panel">
<div class="theory-panel-inner" id="theory-content"></div>
</div>
</div><!-- /#lab-sim -->
</div><!-- /.sb-content -->
</div><!-- /.app-layout -->
<script src="/js/api.js"></script>
<script src="/js/sidebar.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.min.js"></script>
<script src="/js/labs/graph.js"></script>
<script src="/js/labs/magnetic.js"></script>
<script src="/js/labs/triangle.js"></script>
<script src="/js/labs/projectile.js"></script>
<script src="/js/labs/collision.js"></script>
<script src="/js/labs/gas.js"></script>
<script src="/js/labs/states.js"></script>
<script src="/js/labs/brownian.js"></script>
<script src="/js/labs/diffusion.js"></script>
<script src="/js/labs/coulomb.js"></script>
<script src="/js/labs/circuit.js"></script>
<script src="/js/labs/reactions.js"></script>
<script src="/js/labs/flask.js"></script>
<script src="/js/labs/redox.js"></script>
<script src="/js/labs/ionexchange.js"></script>
<script src="/js/labs/stereo.js?v=2"></script>
<script src="/js/notifications.js"></script>
<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,'&quot;')}"></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/newton.js"></script>
<script src="/js/labs/forcesandbox.js"></script>
<script src="/js/labs/angrybirds.js"></script>
<script src="/js/labs/waves.js"></script>
<script src="/js/labs/chemsandbox.js"></script>
<script src="/js/labs/celldivision.js"></script>
<script src="/js/labs/photosynthesis.js"></script>
<script src="/js/labs/crystal.js"></script>
<script src="/js/labs/orbitals.js"></script>
<script src="/js/labs/trigcircle.js"></script>
<script src="/js/labs/_util.js"></script>
<script src="/js/labs/quadratic.js"></script>
<script src="/js/labs/normaldist.js"></script>
<script src="/js/labs/graphtransform.js"></script>
<script src="/js/labs/pendulum.js"></script>
<script src="/js/labs/equilibrium.js"></script>
<script src="/js/labs/thinlens.js"></script>
<script src="/js/labs/mirror.js"></script>
<script src="/js/labs/isoprocess.js"></script>
<script src="/js/labs/titration.js"></script>
<script src="/js/labs/refraction.js"></script>
<script src="/js/labs/probability.js"></script>
<script src="/js/labs/bohratom.js"></script>
<script src="/js/labs/electrolysis.js"></script>
<script src="/js/labs/hydrostatics.js"></script>
<script src="/js/labs/geometry.js"></script>
</body>
</html>