feat(labs): механика V2 — 4 ключевые симы школьной физики расширены

pendulum V2 (472 → 1651 строк):
- Математический (default, сохранён)
- Двойной маятник (Lagrangian RK4, ghost-копия для демо хаоса)
- Связанные маятники (биения, чарт θ₁/θ₂)
- Пружинный (вертикальный/горизонтальный, T=2π√(m/k))
- Физический (4 формы: стержень/обруч/диск/прямоугольник, с моментом инерции)
- Маятник Фуко (Кориолис, slider широты, период прецессии)
- Резонанс (внешняя F₀·cos(ω_d·t), резонансная кривая A(ω))
- Фазовый портрет (универсальный toggle для всех режимов)

collision V2 (~1000 → 2416 строк):
- 1D (default, сохранён)
- 2D под углом (импульс по осям, slider e, до/после стат)
- Multi-ball (N=2-10, стены с отскоками, перемешать)
- Бильярдный стол (6 луз, кий с прицелом, треугольник шаров, реалистичное трение)
- Реф.фрейм ЦМ (universal toggle)

newton V2 (1693 → 2585 строк):
- 4-й закон-таб «Классические задачи»
- Машина Атвуда (a=(m₂-m₁)g/(m₁+m₂), идеальный/массивный блок)
- Тело на наклонной плоскости (FBD, статика/скольжение, slider α/μ/F_app)
- Скатывание шара/цилиндра/обруча (момент инерции, гонка, наглядно почему обруч медленнее)

projectile V2 (1900 → 2400 строк):
- Парашют: F_d = ½C_d·ρ·A·v² с терминальной скоростью v_t = √(2mg/(C_d·ρ·A))
- C_d selector: парашют/куб/сфера/полусфера/диск; раскрытие парашюта на заданной высоте
- Горка-катапульта: v_0 = √(2gL(sinα-μcosα)) автомат
- 10 планет: Земля/Луна/Марс/Юпитер/Меркурий/Венера/Сатурн/Уран/Нептун/Плутон
  с реальными g + плотностью атмосферы (для drag)
- Сравнительный режим: 3 планеты одновременно с разными цветами

Все 4 симы — additive, существующая функциональность сохранена.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-26 14:14:42 +03:00
parent 7ffed45974
commit e46548d06b
5 changed files with 4485 additions and 349 deletions
+353 -30
View File
@@ -141,7 +141,7 @@
<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="Сброс">
<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>
@@ -237,6 +237,10 @@
<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>
<!-- 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:.6rem;font-weight:800">Атвуд</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:.6rem;font-weight:800">Наклон</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:.6rem;font-weight:800">Качение</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>
@@ -1499,19 +1503,84 @@
<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>
<button class="mag-mode-btn dyn-mode" id="dyn-mode-law4" onclick="dynMode('law4',this)" style="flex:1;font-size:.68rem;padding:5px 0">Классич.</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>
<!-- Scene selector (standard A/B/C — hidden for law 4) -->
<div class="gp-section-title" style="margin-bottom:8px" id="newton-scene-title">Сцена</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>
<!-- Classic scene selector (law 4 only) -->
<div id="newton-classic-panel" style="display:none">
<div style="display:flex;gap:5px;margin-bottom:10px">
<button class="mag-mode-btn cl-scene-btn active" id="nscn-panel-cl-atwood" data-scene="atwood" onclick="classicScene('atwood')" style="flex:1;font-size:.68rem">Атвуд</button>
<button class="mag-mode-btn cl-scene-btn" id="nscn-panel-cl-ramp" data-scene="ramp" onclick="classicScene('ramp')" style="flex:1;font-size:.68rem">Наклон</button>
<button class="mag-mode-btn cl-scene-btn" id="nscn-panel-cl-roll" data-scene="roll" onclick="classicScene('roll')" style="flex:1;font-size:.68rem">Качение</button>
</div>
<!-- Atwood sub-panel -->
<div id="cl-sub-atwood">
<div class="param-block">
<div class="param-header"><span class="param-name">m₁ (кг)</span><span class="param-val" id="atw-m1-val">5 кг</span></div>
<input type="range" class="param-slider" id="sl-atw-m1" min="1" max="20" value="5" oninput="atwM1Change()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">m₂ (кг)</span><span class="param-val" id="atw-m2-val">8 кг</span></div>
<input type="range" class="param-slider" id="sl-atw-m2" min="1" max="20" value="8" oninput="atwM2Change()">
</div>
<div class="gp-section-title" style="margin:4px 0">Блок</div>
<div style="display:flex;gap:5px;margin-bottom:8px">
<button class="mag-mode-btn atw-massive-btn active" data-val="false" onclick="atwMassiveToggle(false)" style="flex:1;font-size:.68rem">Идеальный</button>
<button class="mag-mode-btn atw-massive-btn" data-val="true" onclick="atwMassiveToggle(true)" style="flex:1;font-size:.68rem">Массивный</button>
</div>
</div>
<!-- Ramp sub-panel -->
<div id="cl-sub-ramp" style="display:none">
<div class="param-block">
<div class="param-header"><span class="param-name">Угол α</span><span class="param-val" id="ramp-alpha-val">30°</span></div>
<input type="range" class="param-slider" id="sl-ramp-alpha" min="1" max="89" value="30" oninput="rampAlphaChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Масса m (кг)</span><span class="param-val" id="newton-m1-ramp-val">5 кг</span></div>
<input type="range" class="param-slider" id="sl-ramp-mass" min="1" max="20" value="5" oninput="rampMassChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Коэфф. трения μ</span><span class="param-val" id="ramp-mu-val">0.20</span></div>
<input type="range" class="param-slider" id="sl-ramp-mu" min="0" max="1.5" step="0.01" value="0.20" oninput="rampMuChange()">
</div>
<div class="param-block">
<div class="param-header"><span class="param-name">Внеш. сила F (Н)</span><span class="param-val" id="ramp-force-val">0 Н</span></div>
<input type="range" class="param-slider" id="sl-ramp-force" min="0" max="80" value="0" oninput="rampForceChange()">
</div>
</div>
<!-- Roll sub-panel -->
<div id="cl-sub-roll" style="display:none">
<div class="param-block">
<div class="param-header"><span class="param-name">Угол α</span><span class="param-val" id="roll-alpha-val">20°</span></div>
<input type="range" class="param-slider" id="sl-roll-alpha" min="5" max="60" value="20" oninput="rollAlphaChange()">
</div>
<div class="gp-section-title" style="margin:4px 0">Режим</div>
<div style="display:flex;gap:5px;margin-bottom:8px">
<button class="mag-mode-btn roll-friction-btn active" data-val="false" onclick="rollFrictionToggle(false)" style="flex:1;font-size:.68rem">Качение</button>
<button class="mag-mode-btn roll-friction-btn" data-val="true" onclick="rollFrictionToggle(true)" style="flex:1;font-size:.68rem">+Трение</button>
</div>
<div style="font-size:0.68rem;color:var(--text-3);line-height:1.5;padding:4px 6px;background:rgba(255,255,255,0.03);border-radius:6px;border:1px solid var(--border);margin-bottom:6px">
Шар: a = (5/7)g·sinα<br>
Цилиндр: a = (2/3)g·sinα<br>
Обруч: a = (1/2)g·sinα
</div>
</div>
</div><!-- /#newton-classic-panel -->
<!-- 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)">
Закон инерции: тело движется равномерно при отсутствии сил.
@@ -1777,6 +1846,117 @@
</div>
</div>
<!-- Parachute -->
<div class="gp-section-title" style="margin-top:6px">Парашют</div>
<label class="tri-layer-row" id="chute-row" onclick="projToggleParachute(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:rgba(6,214,224,0.5)"></span>
<span class="tri-layer-name">Парашют</span>
<span class="tri-toggle" id="chute-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="chute-params" style="display:none">
<div class="param-block">
<div class="param-header">
<span class="param-name">Площадь A</span>
<span class="param-val" id="p-chute-area">1.0 м²</span>
</div>
<input type="range" class="param-slider" id="sl-chute-area" min="1" max="100" value="10" oninput="projChuteAreaChange()">
</div>
<div class="param-block" style="margin-bottom:6px">
<span class="param-name" style="font-size:.68rem">Форма (Cd)</span>
<select id="sel-chute-cd" onchange="projChuteCdChange()" style="width:100%;margin-top:4px;background:#0d0d2a;color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:6px;padding:4px 6px;font-size:.75rem">
<option value="1.5" selected>Парашют (1.5)</option>
<option value="1.05">Куб (1.05)</option>
<option value="0.47">Сфера (0.47)</option>
<option value="0.42">Полусфера (0.42)</option>
<option value="1.17">Плоский диск (1.17)</option>
</select>
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Раскрыть на h</span>
<span class="param-val" id="p-chute-height">Сразу</span>
</div>
<input type="range" class="param-slider" id="sl-chute-height" min="0" max="100" value="0" oninput="projChuteHeightChange()">
<div style="font-size:.6rem;color:var(--text-3);margin-top:2px">0 = немедленно, &gt;0 = раскрыть на этой высоте</div>
</div>
</div>
<!-- Ramp -->
<div class="gp-section-title" style="margin-top:6px">Наклонная горка</div>
<label class="tri-layer-row" id="ramp-row" onclick="projToggleRamp(this)" style="margin-bottom:6px">
<span class="tri-dot" style="background:rgba(255,180,50,0.5)"></span>
<span class="tri-layer-name">Старт с горки</span>
<span class="tri-toggle" id="ramp-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="ramp-params" style="display:none">
<div class="param-block">
<div class="param-header">
<span class="param-name">Угол горки &alpha;</span>
<span class="param-val" id="p-ramp-angle">30&deg;</span>
</div>
<input type="range" class="param-slider" id="sl-ramp-angle" min="5" max="85" value="30" oninput="projRampChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Длина L</span>
<span class="param-val" id="p-ramp-length">10 м</span>
</div>
<input type="range" class="param-slider" id="sl-ramp-length" min="1" max="50" value="10" oninput="projRampChange()">
</div>
<div class="param-block">
<div class="param-header">
<span class="param-name">Трение &mu;</span>
<span class="param-val" id="p-ramp-mu">0.10</span>
</div>
<input type="range" class="param-slider" id="sl-ramp-mu" min="0" max="80" value="10" oninput="projRampChange()">
<div style="font-size:.6rem;color:var(--text-3);margin-top:2px">v = sqrt(2gL(sin&alpha; &minus; &mu; cos&alpha;))</div>
</div>
</div>
<!-- Planet -->
<div class="gp-section-title" style="margin-top:6px">Планета / гравитация</div>
<div class="param-block" style="margin-bottom:6px">
<select id="sel-planet" onchange="projPlanetChange()" style="width:100%;background:#0d0d2a;color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:6px;padding:4px 6px;font-size:.75rem">
<option value="earth" selected>Земля &mdash; g=9.81</option>
<option value="moon">Луна &mdash; g=1.62</option>
<option value="mars">Марс &mdash; g=3.71</option>
<option value="venus">Венера &mdash; g=8.87</option>
<option value="jupiter">Юпитер &mdash; g=24.79</option>
<option value="mercury">Меркурий &mdash; g=3.7</option>
<option value="saturn">Сатурн &mdash; g=10.44</option>
<option value="uranus">Уран &mdash; g=8.69</option>
<option value="neptune">Нептун &mdash; g=11.15</option>
<option value="pluto">Плутон &mdash; g=0.62</option>
</select>
</div>
<button id="proj-planet-compare-btn" class="proj-preset-chip" onclick="projTogglePlanetCompare()" style="width:100%;text-align:left;display:flex;align-items:center;gap:6px;margin-bottom:4px">
<svg class="ic" viewBox="0 0 24 24"><circle cx="5" cy="12" r="3"/><circle cx="12" cy="5" r="3"/><circle cx="19" cy="19" r="3"/><line x1="5" y1="12" x2="12" y2="5"/><line x1="12" y1="5" x2="19" y2="19"/></svg>
<span>Сравн.планет: Выкл</span>
</button>
<div id="proj-planet-compare-panel" style="display:none;font-size:.65rem;color:rgba(255,255,255,.6);margin-bottom:4px">
<div style="display:flex;gap:4px;flex-direction:column">
<div style="display:flex;align-items:center;gap:6px">
<span style="color:#06D6E0;font-weight:700">1:</span>
<select onchange="projPlanetCompareChange(0,this.value)" style="flex:1;background:#0d0d2a;color:#06D6E0;border:1px solid rgba(6,214,224,.3);border-radius:4px;padding:2px 4px;font-size:.7rem">
<option value="earth" selected>Земля</option><option value="moon">Луна</option><option value="mars">Марс</option><option value="venus">Венера</option><option value="jupiter">Юпитер</option><option value="mercury">Меркурий</option><option value="saturn">Сатурн</option><option value="uranus">Уран</option><option value="neptune">Нептун</option><option value="pluto">Плутон</option>
</select>
</div>
<div style="display:flex;align-items:center;gap:6px">
<span style="color:#7BF5A4;font-weight:700">2:</span>
<select onchange="projPlanetCompareChange(1,this.value)" style="flex:1;background:#0d0d2a;color:#7BF5A4;border:1px solid rgba(123,245,164,.3);border-radius:4px;padding:2px 4px;font-size:.7rem">
<option value="earth">Земля</option><option value="moon" selected>Луна</option><option value="mars">Марс</option><option value="venus">Венера</option><option value="jupiter">Юпитер</option><option value="mercury">Меркурий</option><option value="saturn">Сатурн</option><option value="uranus">Уран</option><option value="neptune">Нептун</option><option value="pluto">Плутон</option>
</select>
</div>
<div style="display:flex;align-items:center;gap:6px">
<span style="color:#F15BB5;font-weight:700">3:</span>
<select onchange="projPlanetCompareChange(2,this.value)" style="flex:1;background:#0d0d2a;color:#F15BB5;border:1px solid rgba(241,91,181,.3);border-radius:4px;padding:2px 4px;font-size:.7rem">
<option value="earth">Земля</option><option value="moon">Луна</option><option value="mars" selected>Марс</option><option value="venus">Венера</option><option value="jupiter">Юпитер</option><option value="mercury">Меркурий</option><option value="saturn">Сатурн</option><option value="uranus">Уран</option><option value="neptune">Нептун</option><option value="pluto">Плутон</option>
</select>
</div>
</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>
@@ -2017,7 +2197,7 @@
</svg>
<span id="coll-launch-label">Запустить</span>
</button>
<button class="proj-reset-btn" onclick="cSim && cSim.reset(); _collSyncBtn()">
<button class="proj-reset-btn" onclick="var _rs=_activeSim&&_activeSim();if(_rs)_rs.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>
@@ -2444,44 +2624,187 @@
<!-- ── PENDULUM sim body ── -->
<div id="sim-pendulum" class="sim-proj-wrap" style="display:none">
<!-- mode bar -->
<div style="display:flex;gap:4px;padding:6px 12px;background:rgba(22,22,38,0.8);flex-wrap:wrap;border-bottom:1px solid rgba(255,255,255,0.07)">
<button class="pend-mode-btn active" data-mode="math" onclick="pendSetMode('math')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(155,93,229,0.2);color:#ccc;cursor:pointer">&#1052;&#1072;&#1090;&#1077;&#1084;&#1072;&#1090;.</button>
<button class="pend-mode-btn" data-mode="double" onclick="pendSetMode('double')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1044;&#1074;&#1086;&#1081;&#1085;&#1086;&#1081;</button>
<button class="pend-mode-btn" data-mode="coupled" onclick="pendSetMode('coupled')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1057;&#1074;&#1103;&#1079;&#1072;&#1085;&#1085;&#1099;&#1077;</button>
<button class="pend-mode-btn" data-mode="spring" onclick="pendSetMode('spring')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1055;&#1088;&#1091;&#1078;&#1080;&#1085;&#1085;&#1099;&#1081;</button>
<button class="pend-mode-btn" data-mode="physical" onclick="pendSetMode('physical')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1060;&#1080;&#1079;&#1080;&#1095;.</button>
<button class="pend-mode-btn" data-mode="foucault" onclick="pendSetMode('foucault')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1060;&#1091;&#1082;&#1086;</button>
<button class="pend-mode-btn" data-mode="resonance" onclick="pendSetMode('resonance')" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1056;&#1077;&#1079;&#1086;&#1085;&#1072;&#1085;&#1089;</button>
<button id="btn-pend-phase" onclick="pendTogglePhase()" style="font-size:.72rem;padding:3px 10px;border-radius:14px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer;margin-left:auto">&#1060;&#1072;&#1079;. &#1087;&#1086;&#1088;&#1090;&#1088;&#1077;&#1090;</button>
</div>
<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:var(--violet);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 class="proj-panel" style="width:230px;gap:0;overflow-y:auto;max-height:100%">
<!-- MATH params -->
<div class="pend-params" data-mode="math">
<div class="gp-section-title" style="margin-bottom:8px">&#1052;&#1072;&#1090;&#1077;&#1084;&#1072;&#1090;&#1080;&#1095;. &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:55px">&#952; = <span id="pend-theta-val" style="color:var(--violet);font-weight:700">45</span>&#176;</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:var(--cyan);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">&#1047;&#1072;&#1090;&#1091;&#1093;. <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 class="gp-section-title" style="margin-bottom:6px;margin-top:8px">&#1055;&#1088;&#1077;&#1089;&#1077;&#1090;&#1099;</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="pendPreset(45,200,9.81,0)">&#1047;&#1077;&#1084;&#1083;&#1103;</button>
<button class="preset-btn" onclick="pendPreset(45,200,1.62,0)">&#1051;&#1091;&#1085;&#1072;</button>
<button class="preset-btn" onclick="pendPreset(170,200,9.81,0)">&#1041;&#1086;&#1083;&#1100;&#1096;&#1086;&#1081; &#952;</button>
<button class="preset-btn" onclick="pendPreset(45,200,9.81,0.5)">&#1047;&#1072;&#1090;&#1091;&#1093;&#1072;&#1085;&#1080;&#1077;</button>
</div>
<div class="pp-hint">&#1058;&#1072;&#1097;&#1080; &#1075;&#1088;&#1091;&#1079;&#1080;&#1082; &#1084;&#1099;&#1096;&#1100;&#1102; &#1076;&#1083;&#1103; &#1091;&#1089;&#1090;&#1072;&#1085;&#1086;&#1074;&#1082;&#1080; &#1091;&#1075;&#1083;&#1072;</div>
</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:var(--cyan);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">
<!-- DOUBLE params -->
<div class="pend-params" data-mode="double" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1044;&#1074;&#1086;&#1081;&#1085;&#1086;&#1081; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">L1 = <span id="pend-d-L1-val" style="color:var(--cyan);font-weight:700">130</span></label>
<input type="range" min="50" max="200" step="5" value="130" oninput="pendDoubleParam('L1',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">L2 = <span id="pend-d-L2-val" style="color:var(--cyan);font-weight:700">100</span></label>
<input type="range" min="50" max="200" step="5" value="100" oninput="pendDoubleParam('L2',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">m1 = <span id="pend-d-m1-val" style="color:#FFD166;font-weight:700">1.5</span></label>
<input type="range" min="0.5" max="4" step="0.1" value="1.5" oninput="pendDoubleParam('m1',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">m2 = <span id="pend-d-m2-val" style="color:#FFD166;font-weight:700">1.0</span></label>
<input type="range" min="0.5" max="4" step="0.1" value="1.0" oninput="pendDoubleParam('m2',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#952;1 = <span id="pend-d-th1-val" style="color:var(--violet);font-weight:700">108</span>&#176;</label>
<input type="range" min="10" max="170" step="1" value="108" oninput="pendDoubleParam('th1',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#952;2 = <span id="pend-d-th2-val" style="color:var(--violet);font-weight:700">72</span>&#176;</label>
<input type="range" min="10" max="170" step="1" value="72" oninput="pendDoubleParam('th2',this.value)" style="flex:1">
</div>
<button id="btn-pend-ghost" onclick="pendToggleGhost()" style="width:100%;margin-top:6px;font-size:.75rem;padding:5px;border-radius:8px;border:1px solid rgba(239,71,111,0.4);background:rgba(239,71,111,0.1);color:#EF476F;cursor:pointer">&#1057;&#1088;&#1072;&#1074;&#1085;&#1080;&#1090;&#1100; &#1090;&#1088;&#1072;&#1077;&#1082;&#1090;&#1086;&#1088;&#1080;&#1080; (&#1093;&#1072;&#1086;&#1089;)</button>
<div class="pp-hint" style="margin-top:6px">&#1063;&#1091;&#1074;&#1089;&#1090;&#1074;&#1080;&#1090;&#1077;&#1083;&#1100;&#1085;&#1086;&#1089;&#1090;&#1100; &#1082; &#1085;&#1072;&#1095;. &#1091;&#1089;&#1083;&#1086;&#1074;&#1080;&#1103;&#1084; (+0.001 &#1088;&#1072;&#1076;)</div>
</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">
<!-- COUPLED params -->
<div class="pend-params" data-mode="coupled" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1057;&#1074;&#1103;&#1079;&#1072;&#1085;&#1085;&#1099;&#1077; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;&#1080;</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">L = <span id="pend-cp-L-val" style="color:var(--cyan);font-weight:700">160</span></label>
<input type="range" min="80" max="260" step="5" value="160" oninput="pendCoupledParam('L',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">k = <span id="pend-cp-k-val" style="color:#FFD166;font-weight:700">0.30</span></label>
<input type="range" min="0.01" max="2" step="0.01" value="0.3" oninput="pendCoupledParam('k',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#952;1 = <span id="pend-cp-th1-val" style="color:var(--violet);font-weight:700">36</span>&#176;</label>
<input type="range" min="5" max="90" step="1" value="36" oninput="pendCoupledParam('th1',this.value)" style="flex:1">
</div>
<div class="pp-hint">&#952;2=0: &#1101;&#1085;&#1077;&#1088;&#1075;&#1080;&#1103; &#1087;&#1077;&#1088;&#1077;&#1082;&#1072;&#1095;&#1080;&#1074;&#1072;&#1077;&#1090;&#1089;&#1103; &#1086;&#1090; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;&#1072; 1 &#1082; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;&#1091; 2 &#1080; &#1086;&#1073;&#1088;&#1072;&#1090;&#1085;&#1086;</div>
</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">
<!-- SPRING params -->
<div class="pend-params" data-mode="spring" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1055;&#1088;&#1091;&#1078;&#1080;&#1085;&#1085;&#1099;&#1081; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;</div>
<div style="display:flex;gap:4px;margin-bottom:10px">
<button class="sp-mode-btn active" data-m="vert" onclick="pendSpringMode('vert')" style="flex:1;font-size:.72rem;padding:4px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(6,214,224,0.15);color:#06D6E0;cursor:pointer">&#1042;&#1077;&#1088;&#1090;&#1080;&#1082;.</button>
<button class="sp-mode-btn" data-m="horiz" onclick="pendSpringMode('horiz')" style="flex:1;font-size:.72rem;padding:4px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1043;&#1086;&#1088;&#1080;&#1079;&#1086;&#1085;&#1090;.</button>
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">k = <span id="pend-sp-k-val" style="color:#FFD166;font-weight:700">20</span></label>
<input type="range" min="1" max="100" step="1" value="20" oninput="pendSpringParam('k',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">m = <span id="pend-sp-m-val" style="color:var(--violet);font-weight:700">1</span> &#1082;&#1075;</label>
<input type="range" min="0.1" max="5" step="0.1" value="1" oninput="pendSpringParam('m',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">x0 = <span id="pend-sp-x0-val" style="color:#EF476F;font-weight:700">8</span> &#1089;&#1084;</label>
<input type="range" min="1" max="20" step="0.5" value="8" oninput="pendSpringParam('x0',this.value)" style="flex:1">
</div>
<div class="pp-hint">T = 2&#960;&#8730;(m/k) &#1085;&#1077; &#1079;&#1072;&#1074;&#1080;&#1089;&#1080;&#1090; &#1086;&#1090; g (&#1075;&#1086;&#1088;&#1080;&#1079;&#1086;&#1085;&#1090;.)</div>
</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>
<!-- PHYSICAL params -->
<div class="pend-params" data-mode="physical" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1060;&#1080;&#1079;&#1080;&#1095;&#1077;&#1089;&#1082;&#1080;&#1081; &#1084;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082;</div>
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:10px">
<button class="ph-shape-btn active" data-s="rod" onclick="pendPhysShape('rod')" style="font-size:.72rem;padding:3px 8px;border-radius:8px;border:1px solid rgba(155,93,229,0.4);background:rgba(155,93,229,0.15);color:#9B5DE5;cursor:pointer">&#1057;&#1090;&#1077;&#1088;&#1078;&#1077;&#1085;&#1100;</button>
<button class="ph-shape-btn" data-s="hoop" onclick="pendPhysShape('hoop')" style="font-size:.72rem;padding:3px 8px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1054;&#1073;&#1088;&#1091;&#1095;</button>
<button class="ph-shape-btn" data-s="disk" onclick="pendPhysShape('disk')" style="font-size:.72rem;padding:3px 8px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1044;&#1080;&#1089;&#1082;</button>
<button class="ph-shape-btn" data-s="rect" onclick="pendPhysShape('rect')" style="font-size:.72rem;padding:3px 8px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(22,22,38,0.6);color:#ccc;cursor:pointer">&#1055;&#1088;&#1103;&#1084;&#1086;&#1091;&#1075;.</button>
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">L = <span id="pend-ph-L-val" style="color:var(--cyan);font-weight:700">200</span></label>
<input type="range" min="60" max="260" step="5" value="200" oninput="pendPhysParam('L',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#952; = <span id="pend-ph-theta-val" style="color:var(--violet);font-weight:700">36</span>&#176;</label>
<input type="range" min="5" max="120" step="1" value="36" oninput="pendPhysParam('theta',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#1047;&#1072;&#1090;&#1091;&#1093;. <span id="pend-ph-damping-val" style="color:#EF476F;font-weight:700">0</span></label>
<input type="range" min="0" max="1" step="0.02" value="0" oninput="pendPhysParam('damping',this.value)" style="flex:1">
</div>
<div class="pp-hint">T &#1079;&#1072;&#1074;&#1080;&#1089;&#1080;&#1090; &#1086;&#1090; &#1084;&#1086;&#1084;&#1077;&#1085;&#1090;&#1072; &#1080;&#1085;&#1077;&#1088;&#1094;&#1080;&#1080; I &#1080; &#1088;&#1072;&#1089;&#1089;&#1090;. &#1076;&#1086; &#1094;.&#1090;. d</div>
</div>
<div class="pp-hint">Тащи грузик мышью для установки угла</div>
<!-- FOUCAULT params -->
<div class="pend-params" data-mode="foucault" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1052;&#1072;&#1103;&#1090;&#1085;&#1080;&#1082; &#1060;&#1091;&#1082;&#1086;</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:65px">&#966; = <span id="pend-fc-phi-val" style="color:#FFD166;font-weight:700">45</span>&#176;</label>
<input type="range" min="1" max="90" step="1" value="45" oninput="pendFoucaultParam('phi',this.value)" style="flex:1">
</div>
<div class="pp-hint">&#1042;&#1080;&#1076; &#1089;&#1074;&#1077;&#1088;&#1093;&#1091;. &#1055;&#1083;&#1086;&#1089;&#1082;&#1086;&#1089;&#1090;&#1100; &#1082;&#1072;&#1095;&#1072;&#1085;&#1080;&#1081; &#1074;&#1088;&#1072;&#1097;&#1072;&#1077;&#1090;&#1089;&#1; &#1080;&#1079;-&#1079;&#1072; &#1074;&#1088;&#1072;&#1097;&#1077;&#1085;&#1080;&#1103; &#1047;&#1077;&#1084;&#1083;&#1080;. T = 24 &#1095; / sin(&#966;)</div>
<div class="pp-hint" style="color:#FFD166;margin-top:4px">&#1055;&#1086;&#1083;&#1102;&#1089; (90&#176;): &#1086;&#1073;&#1086;&#1088;&#1086;&#1090; &#1079;&#1072; 24 &#1095;. &#1069;&#1082;&#1074;&#1072;&#1090;&#1086;&#1088;: &#1087;&#1086;&#1095;&#1090;&#1080; &#1085;&#1077; &#1074;&#1088;&#1072;&#1097;&#1072;&#1077;&#1090;&#1089;&#1103;.</div>
</div>
<!-- RESONANCE params -->
<div class="pend-params" data-mode="resonance" style="display:none">
<div class="gp-section-title" style="margin-bottom:8px">&#1056;&#1077;&#1079;&#1086;&#1085;&#1072;&#1085;&#1089;</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">&#969; = <span id="pend-rs-dOmega-val" style="color:#EF476F;font-weight:700">1.50</span></label>
<input type="range" min="0.1" max="6" step="0.05" value="1.5" oninput="pendResonanceParam('dOmega',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">F0 = <span id="pend-rs-F0-val" style="color:#FFD166;font-weight:700">0.80</span></label>
<input type="range" min="0.1" max="3" step="0.05" value="0.8" oninput="pendResonanceParam('F0',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">&#947; = <span id="pend-rs-gamma-val" style="color:var(--cyan);font-weight:700">0.30</span></label>
<input type="range" min="0.01" max="2" step="0.01" value="0.3" oninput="pendResonanceParam('gamma',this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:8px">
<label style="font-size:.78rem;color:#ccc;width:70px">L = <span id="pend-rs-L-val" style="color:var(--violet);font-weight:700">180</span></label>
<input type="range" min="60" max="300" step="5" value="180" oninput="pendResonanceParam('L',this.value)" style="flex:1">
</div>
<div class="pp-hint">&#1050;&#1088;&#1072;&#1089;&#1085;&#1072;&#1103; &#1089;&#1090;&#1088;&#1077;&#1083;&#1082;&#1072;&#1074;&#1099;&#1085;&#1091;&#1078;&#1076;&#1072;&#1102;&#1097;&#1072;&#1103; &#1089;&#1080;&#1083;&#1072;. &#1055;&#1080;&#1082; &#1087;&#1088;&#1080; &#969; &#8776; &#969;&#8320;.</div>
</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:var(--violet)">45°</div></div>
<div class="pstat"><div class="pstat-label">ω</div><div class="pstat-val" id="pendbar-v2" style="color:var(--cyan)">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 class="pstat"><div class="pstat-label">&#1059;&#1075;&#1086;&#1083; / &#1082;&#1086;&#1086;&#1088;&#1076;.</div><div class="pstat-val" id="pendbar-v1" style="color:var(--violet)">45&#176;</div></div>
<div class="pstat"><div class="pstat-label">&#969;</div><div class="pstat-val" id="pendbar-v2" style="color:var(--cyan)">0</div></div>
<div class="pstat"><div class="pstat-label">&#1055;&#1077;&#1088;&#1080;&#1086;&#1076; T</div><div class="pstat-val" id="pendbar-v3" style="color:#FFD166"></div></div>
<div class="pstat"><div class="pstat-label">&#1056;&#1077;&#1078;&#1080;&#1084;</div><div class="pstat-val" id="pendbar-v4" style="color:#EF476F"></div></div>
</div>
</div>