eca68e1a28
LabMeasure (_measure.js): SVG-оверлей поверх сцены с pointer-events:none (симуляция остаётся интерактивной), перетаскиваемые ручки. Линейка — длина px + ≈ метры (PX_PER_M) + угол; угломер — угол при вершине с дугой. Кнопка-тумблер в топбаре лаборатории. Самодостаточно, симуляции не трогает. Этим Фаза 2 закрыта. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
539 lines
42 KiB
HTML
539 lines
42 KiB
HTML
<!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:var(--violet);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>
|
||
|
||
<!-- emfield controls -->
|
||
<div id="ctrl-emfield" class="sim-zoom-btns" style="display:none">
|
||
<button class="zoom-btn" id="em-sign-pos" onclick="emSign(1)" title="Добавлять + заряды" style="font-size:1.1rem;font-weight:900;color:#EF476F">+</button>
|
||
<button class="zoom-btn" id="em-sign-neg" onclick="emSign(-1)" title="Добавлять − заряды" style="font-size:1.1rem;font-weight:900;color:#4CC9F0">−</button>
|
||
<button class="zoom-btn" id="em-dir-out" onclick="emWireDir('out')" title="Провод • (ток на нас)" style="font-size:1rem">•</button>
|
||
<button class="zoom-btn" id="em-dir-in" onclick="emWireDir('in')" title="Провод × (ток от нас)" style="font-size:1rem">×</button>
|
||
<button class="zoom-btn" onclick="emSim && emSim.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="var _as=_activeSim&&_activeSim();if(_as)_as.reset();_collSyncBtn();" 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 merged into ctrl-emfield) -->
|
||
|
||
<!-- 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-inductor" onclick="circTool('inductor',this)" title="Катушка индуктивности (I)" style="font-size:.65rem"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M2 12 Q4 8 6 12 Q8 8 10 12 Q12 8 14 12"/><line x1="14" y1="12" x2="22" y2="12"/><line x1="2" y1="12" x2="2" y2="12"/></svg></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"><svg class="ic" viewBox="0 0 24 24"><line x1="3" y1="12" x2="9" y2="12"/><line x1="15" y1="12" x2="21" y2="12"/><line x1="9" y1="12" x2="17" y2="6"/></svg></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 style="width:1px;height:20px;background:rgba(255,255,255,0.15);margin:0 2px"></div>
|
||
<button class="zoom-btn" id="ctool-heat" onclick="circToggleHeat()" title="Тепловая карта мощности" style="font-size:.6rem;font-weight:700">
|
||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 2C8 6 6 10 8 14c1 2 3 4 4 6"/><path d="M16 6c-2 3-3 6-1 9 1 1.5 1 3 0 4"/><path d="M8 6C6 9 5 12 7 15"/></svg>
|
||
</button>
|
||
<button class="zoom-btn" id="btn-osc-toggle" onclick="circToggleOsc()" title="Осциллограф U(t) / I(t)" style="font-size:.6rem;font-weight:700">
|
||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="6 16 8 10 11 14 13 8 16 16"/></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"><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="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:.78rem;font-weight:700"><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:.78rem;font-weight:700"><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:.78rem;font-weight:700"><svg class="ic" viewBox="0 0 24 24" fill="none"><path d="M3 12 L6 8 L9 16 L12 8 L15 16 L18 8 L21 12"/></svg> Пружина</button>
|
||
<button class="zoom-btn sb-tool-btn" id="sbt-rope" onclick="sbTool('rope',this)" style="font-size:.78rem;font-weight:700">— Нить</button>
|
||
<button class="zoom-btn sb-tool-btn" id="sbt-anchor" onclick="sbTool('anchor',this)" style="font-size:.78rem;font-weight:700"><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:.78rem;font-weight:700"><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:.82rem;font-weight:700;padding:4px 12px">A</button>
|
||
<button class="zoom-btn nscene-btn" id="nscn-B" onclick="newtonScene('B',this)" style="font-size:.82rem;font-weight:700;padding:4px 12px">B</button>
|
||
<button class="zoom-btn nscene-btn" id="nscn-C" onclick="newtonScene('C',this)" style="font-size:.82rem;font-weight:700;padding:4px 12px">C</button>
|
||
<!-- classic scenes (law 4, hidden by default) -->
|
||
<button class="zoom-btn nscene-btn cl-scene-btn" id="nscn-cl-atwood" data-scene="atwood" onclick="classicScene('atwood')" style="display:none;font-size:.78rem;font-weight:700">Атвуд</button>
|
||
<button class="zoom-btn nscene-btn cl-scene-btn" id="nscn-cl-ramp" data-scene="ramp" onclick="classicScene('ramp')" style="display:none;font-size:.78rem;font-weight:700">Наклон</button>
|
||
<button class="zoom-btn nscene-btn cl-scene-btn" id="nscn-cl-roll" data-scene="roll" onclick="classicScene('roll')" style="display:none;font-size:.78rem;font-weight:700">Качение</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:.78rem;font-weight:700;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:var(--violet)" 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:var(--violet);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>
|
||
|
||
<!-- radioactive controls -->
|
||
<div id="ctrl-radioactive" class="sim-zoom-btns" style="display:none">
|
||
<button class="zoom-btn" id="rd-ctrl-play" onclick="radioactivePlay()" title="Старт / Пауза">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="5,3 19,12 5,21"/></svg>
|
||
</button>
|
||
<button class="zoom-btn" onclick="radioactiveReset()" 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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- save screenshot to «Мои материалы» -->
|
||
<button class="zoom-btn" id="lab-save-btn" onclick="labSaveToMaterials(this)" title="Сохранить кадр в «Мои материалы»">
|
||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||
</button>
|
||
|
||
<!-- download PNG -->
|
||
<button class="zoom-btn" id="lab-png-btn" onclick="labDownloadPng()" title="Скачать кадр (PNG)">
|
||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>
|
||
|
||
<!-- measurement tools (ruler / angle) -->
|
||
<button class="zoom-btn" id="lab-measure-btn" onclick="window.LabMeasure&&LabMeasure.toggle()" title="Измерения: линейка и угломер">
|
||
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.3 8.7 8.7 21.3a1 1 0 0 1-1.4 0l-4.6-4.6a1 1 0 0 1 0-1.4L15.3 2.7a1 1 0 0 1 1.4 0l4.6 4.6a1 1 0 0 1 0 1.4Z"/><path d="m7.5 10.5 2 2M10.5 7.5l2 2M13.5 4.5l2 2M4.5 13.5l2 2"/></svg>
|
||
</button>
|
||
|
||
<!-- sound toggle -->
|
||
<button class="zoom-btn" id="labfx-sound-btn" onclick="(function(){var e=window.LabFX&&window.LabFX.sound;if(!e)return;e.setEnabled(!e.isEnabled());document.getElementById('labfx-sound-btn').setAttribute('aria-pressed',e.isEnabled());document.getElementById('labfx-sound-icon-on').style.display=e.isEnabled()?'':'none';document.getElementById('labfx-sound-icon-off').style.display=e.isEnabled()?'none':'';})()" title="Звук симуляций" style="position:relative" aria-pressed="true">
|
||
<!-- speaker on -->
|
||
<svg id="labfx-sound-icon-on" class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
|
||
<path d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
|
||
</svg>
|
||
<!-- speaker off (muted) -->
|
||
<svg id="labfx-sound-icon-off" class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
|
||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||
<line x1="23" y1="9" x2="17" y2="15"/>
|
||
<line x1="17" y1="9" x2="23" y2="15"/>
|
||
</svg>
|
||
</button>
|
||
|
||
<!-- economy / reduced-motion toggle -->
|
||
<button class="zoom-btn" id="labfx-eco-btn" onclick="(function(){var f=window.LabFX;if(!f)return;f.setEconomy(!f.reduced);var on=!!f.reduced;document.getElementById('labfx-eco-btn').setAttribute('aria-pressed',on);document.getElementById('labfx-eco-on').style.display=on?'':'none';document.getElementById('labfx-eco-off').style.display=on?'none':'';})()" title="Эконом-режим: меньше анимаций (для слабых устройств)" style="position:relative" aria-pressed="false">
|
||
<!-- economy ON (effects reduced) — leaf -->
|
||
<svg id="labfx-eco-on" class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
|
||
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/>
|
||
<path d="M2 21c0-3 1.85-5.36 5.08-6"/>
|
||
</svg>
|
||
<!-- economy OFF (full effects) — zap -->
|
||
<svg id="labfx-eco-off" class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Sim bodies вынесены в /labs-bodies.html (content-engine, Phase 2).
|
||
Синхронная инъекция во время парсинга: тела присутствуют до DOMContentLoaded,
|
||
что сохраняет обработчики (geometry.js) и порядок инициализации. -->
|
||
<div id="sim-bodies-host"></div>
|
||
<script>
|
||
(function () {
|
||
var host = document.getElementById("sim-bodies-host");
|
||
if (!host) return;
|
||
try {
|
||
var x = new XMLHttpRequest();
|
||
x.open("GET", "/labs-bodies.html?v=1", false);
|
||
x.send(null);
|
||
if (x.status >= 200 && x.status < 300) {
|
||
host.insertAdjacentHTML("beforebegin", x.responseText);
|
||
host.parentNode.removeChild(host);
|
||
} else {
|
||
if (window.console) console.error("[lab] sim bodies HTTP " + x.status);
|
||
}
|
||
} catch (e) {
|
||
if (window.console) console.error("[lab] sim bodies load failed", e);
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<!-- ── 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/material-save.js"></script>
|
||
<script src="/js/sidebar.js"></script>
|
||
<!-- ════════════════════════════════════════════════════════════════════════
|
||
Контент-движок, Фаза 3 — ЛЕНИВАЯ ЗАГРУЗКА КОДА СИМУЛЯЦИЙ.
|
||
На старте грузится только КАРКАС (~360 КБ): реестр, загрузчик, манифест,
|
||
fx-движки, общие визуалы (_phys_visuals/_chem_visuals/_graph_panel/_util),
|
||
graph.js (предоставляет GRID для 15 симуляций), lab-init/glue/register-all.
|
||
Код конкретной симуляции (~2.5 МБ суммарно) и three.js (~600 КБ) грузятся
|
||
по клику через LabLoader (см. _loader.js + _sim_deps.js). three.js — только
|
||
для 3D-симуляций (crystal/orbitals/stereo/periodic).
|
||
════════════════════════════════════════════════════════════════════════ -->
|
||
<script src="/js/labs/_registry.js"></script>
|
||
<script src="/js/labs/_loader.js"></script>
|
||
<script src="/js/labs/_sim_deps.js"></script>
|
||
<script src="/js/labs/_palette.js"></script>
|
||
<script src="/js/labs/_simbase.js"></script>
|
||
<script src="/js/labs/_fx_core.js"></script>
|
||
<script src="/js/labs/_fx_particles.js"></script>
|
||
<script src="/js/labs/_fx_motion.js"></script>
|
||
<script src="/js/labs/_fx_sound.js"></script>
|
||
<script src="/js/labs/_graph_panel.js"></script>
|
||
<!-- Конструктор симуляций (Фаза 0): движок выражений + рантайм + адаптер LabRegistry.
|
||
Лёгкие модули каркаса (~30 КБ), грузятся eager как _registry/_loader. -->
|
||
<script src="/js/labs/_sim_expr.js"></script>
|
||
<script src="/js/labs/_sim_engine.js"></script>
|
||
<script src="/js/labs/_sim_adapter.js"></script>
|
||
<script src="/js/labs/_tasks.js"></script>
|
||
<script src="/js/labs/_measure.js"></script>
|
||
<script src="/js/labs/_phys_visuals.js"></script>
|
||
<script src="/js/labs/_chem_visuals.js"></script>
|
||
<script src="/js/labs/graph.js"></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 src="/js/lab-previews.js"></script>
|
||
<script src="/js/labs/lab-glue.js"></script>
|
||
<script src="/js/labs/_register-all.js"></script>
|
||
<!-- Конструктор симуляций (Фаза 0): демо-спека за флагом (?simdemo=1). Грузится
|
||
после _register-all, чтобы LabRegistry/registerSpecSim уже существовали. -->
|
||
<script src="/js/labs/_sim_demo.js"></script>
|
||
<script>
|
||
/* Sync sound toggle button icon with localStorage state on load */
|
||
(function() {
|
||
var stored = localStorage.getItem('labfx-sound');
|
||
var on = stored === null ? true : stored === 'true';
|
||
var iconOn = document.getElementById('labfx-sound-icon-on');
|
||
var iconOff = document.getElementById('labfx-sound-icon-off');
|
||
var btn = document.getElementById('labfx-sound-btn');
|
||
if (!iconOn || !iconOff || !btn) return;
|
||
iconOn.style.display = on ? '' : 'none';
|
||
iconOff.style.display = on ? 'none' : '';
|
||
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||
})();
|
||
|
||
/* Sync economy-mode toggle with LabFX.reduced (prefers-reduced-motion + localStorage) */
|
||
(function() {
|
||
var eco = !!(window.LabFX && window.LabFX.reduced);
|
||
var on = document.getElementById('labfx-eco-on');
|
||
var off = document.getElementById('labfx-eco-off');
|
||
var btn = document.getElementById('labfx-eco-btn');
|
||
if (!on || !off || !btn) return;
|
||
on.style.display = eco ? '' : 'none';
|
||
off.style.display = eco ? 'none' : '';
|
||
btn.setAttribute('aria-pressed', eco ? 'true' : 'false');
|
||
})();
|
||
|
||
/* ── Снимок симуляции: «В мои материалы» / «Скачать PNG» (Фаза 2) ──
|
||
Берём самый крупный видимый canvas в области симуляции. Для 3D (WebGL)
|
||
кадр может выйти пустым без preserveDrawingBuffer — допустимо для v1. */
|
||
function _labSimTitle() {
|
||
var t = document.getElementById('sim-topbar-title');
|
||
return t ? (t.textContent || '').trim() : '';
|
||
}
|
||
function _labActiveCanvas() {
|
||
var best = null, bestArea = 0;
|
||
document.querySelectorAll('canvas').forEach(function (c) {
|
||
if (c.offsetParent === null) return; // скрытый
|
||
var r = c.getBoundingClientRect();
|
||
if (r.width < 60 || r.height < 60) return; // мелкие (иконки/спарклайны)
|
||
var area = r.width * r.height;
|
||
if (area > bestArea) { bestArea = area; best = c; }
|
||
});
|
||
return best;
|
||
}
|
||
function labDownloadPng() {
|
||
var c = _labActiveCanvas();
|
||
if (!c) { if (window.LS && LS.toast) LS.toast('Нет изображения', 'warn'); return; }
|
||
try {
|
||
var a = document.createElement('a');
|
||
a.href = c.toDataURL('image/png');
|
||
a.download = (_labSimTitle() || 'simulation') + '.png';
|
||
document.body.appendChild(a); a.click(); a.remove();
|
||
} catch (e) { if (window.LS && LS.toast) LS.toast('Не удалось сохранить кадр', 'error'); }
|
||
}
|
||
function labSaveToMaterials(btn) {
|
||
var c = _labActiveCanvas();
|
||
if (!c) { if (window.LS && LS.toast) LS.toast('Нет изображения', 'warn'); return; }
|
||
if (!window.MaterialSave) { if (window.LS && LS.toast) LS.toast('Модуль сохранения не загружен', 'error'); return; }
|
||
try {
|
||
c.toBlob(function (blob) {
|
||
if (!blob) { if (window.LS && LS.toast) LS.toast('Не удалось снять кадр', 'error'); return; }
|
||
MaterialSave.image({ blob: blob, title: _labSimTitle() || 'Симуляция', name: 'sim.png', sourceTitle: 'Лаборатория' }, btn);
|
||
}, 'image/png');
|
||
} catch (e) { if (window.LS && LS.toast) LS.toast('Не удалось снять кадр', 'error'); }
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|