feat(labs): wave 2 — depth features across 6 sims

Электрические цепи (circuit):
- Индуктивность L как новый компонент (1–1000 мГн, шорт в DC, jωL в AC)
- RLC preset для демонстрации резонанса
- Осциллограф: U(t)/I(t) для выбранного компонента, 100 sample, dual-axis
- Heatmap мощности: радиальный градиент halo от blue→red пропорционально P=UI

Стереометрия 3D (stereo):
- Сечение через 3 произвольные точки: pick на гранях/рёбрах/вершинах
- Плоскость + полигон пересечения с авто-определением типа (3–6-угольник) и площадью
- Step-by-step режим: визуализация P1→линия→P2→линия→P3→плоскость→сечение
- Поддержка всех solids (включая cylinder/cone через sampling fallback)

Планиметрия (geometry):
- Задачник framework: CHALLENGES[] с setup/check функциями
- 5 стартовых задач: серединный перпендикуляр, биссектриса, описанная окружность, ГМТ, касательная
- Авто-checker: толерантности ±0.5° для углов, ±1–5% для расстояний
- UI: collapsible панель с статус-иконками, конфетти + «Молодец!» на success

Электромагнитные поля (emfield):
- Preset «Тороид»: 16+16 проводов в концентрических кольцах
- Поверхность Гаусса: draggable круг, считает Φ = q_enc/ε₀, подсвечивает охваченные заряды
- Motional EMF: draggable rod, arrow-keys управление, считает ε = ∫(v×B)·dl

Химическая песочница (chemsandbox):
- Live-overlay с уравнением реакции: молекулярное / полное ионное / сокращённое ионное
- Coverage: 49/49 молекулярных, 34/49 ионных, 36/49 сокращённых
- Auto-hide через 5 сек, fade-in animation, цветовая кодировка типов

Волны и звук (waves):
- Doppler: source+observer drag, expanding wavefronts, f_obs формула, Mach cone при v>c
- Beats: f1+f2, sum waveform с envelope, индикация f_beat=|f1-f2|
- Spectrum (DFT): N=256 samples pure JS, bar-chart с пиками и labels, «Добавить гармонику»

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-23 12:48:14 +03:00
parent 7f75c96acd
commit 8f30a8cef6
8 changed files with 2367 additions and 36 deletions
+222
View File
@@ -798,6 +798,92 @@
display: flex; align-items: center; gap: 6px;
}
.geo-toggle-label svg { width: 12px; height: 12px; stroke: currentColor; stroke-width: 2; opacity: .7; }
/* ════════════════════════════════
CHEMSANDBOX — EQUATION OVERLAY
════════════════════════════════ */
.chemsand-eq-overlay {
position: absolute;
top: 14px;
left: 50%;
transform: translateX(-50%);
min-width: 340px;
max-width: min(660px, calc(100% - 28px));
background: rgba(6, 6, 18, 0.82);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1.5px solid rgba(255, 255, 255, 0.10);
border-radius: 16px;
padding: 12px 18px 13px;
display: flex;
flex-direction: column;
gap: 7px;
z-index: 20;
pointer-events: none;
opacity: 0;
transition: opacity 0.28s ease;
}
.chemsand-eq-overlay.visible {
opacity: 1;
}
.chemsand-eq-type {
font-family: 'Manrope', sans-serif;
font-size: 0.65rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .09em;
border: 1px solid rgba(255,255,255,.18);
border-radius: 6px;
padding: 1px 8px;
align-self: flex-start;
margin-bottom: 2px;
}
.chemsand-eq-row {
display: flex;
flex-direction: column;
gap: 2px;
}
.chemsand-eq-label {
font-family: 'Manrope', sans-serif;
font-size: 0.6rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .07em;
color: rgba(255,255,255,.35);
}
.chemsand-eq-text {
font-family: 'JetBrains Mono', 'Fira Mono', monospace;
font-size: 0.9rem;
font-weight: 600;
line-height: 1.35;
color: rgba(255, 255, 255, 0.95);
word-break: break-word;
}
/* full ionic — slightly dimmer, smaller */
.chemsand-eq-text--full {
font-size: 0.78rem;
font-weight: 500;
color: rgba(155, 200, 255, 0.75);
}
/* net ionic — highlighted */
.chemsand-eq-text--net {
font-size: 0.82rem;
font-weight: 700;
color: var(--cyan, #06D6E0);
text-shadow: 0 0 12px rgba(6,214,224,.35);
}
/* separator between rows */
.chemsand-eq-row + .chemsand-eq-row {
border-top: 1px solid rgba(255,255,255,.06);
padding-top: 6px;
}
.geo-toggle {
width: 28px; height: 16px; border-radius: 8px;
background: rgba(255,255,255,.1); border: 1.5px solid var(--border-h);
@@ -865,3 +951,139 @@
.geo-del-btn-hard:hover { background: rgba(248,113,113,.18); }
.geo-del-btn-cancel{ border-color: rgba(255,255,255,.15); color: rgba(255,255,255,.5); background: transparent; }
.geo-del-btn-cancel:hover{ background: rgba(255,255,255,.06); }
/* ── Задачник (challenge panel) ── */
.geo-challenge-toggle {
display: flex; align-items: center; gap: 6px;
padding: 7px 9px; border-radius: 10px;
border: 1.5px solid rgba(74,222,128,.35);
background: rgba(74,222,128,.05); color: #4ADE80;
font-family: 'Manrope', sans-serif; font-size: 0.73rem; font-weight: 700;
cursor: pointer; transition: all .14s; width: 100%; margin-top: 6px;
}
.geo-challenge-toggle:hover { background: rgba(74,222,128,.12); border-color: rgba(74,222,128,.6); }
.geo-challenge-toggle svg { width: 13px; height: 13px; stroke: currentColor; flex-shrink: 0; }
.geo-challenge-toggle .chall-count {
margin-left: auto; font-size: 0.68rem; opacity: .75;
}
.geo-challenge-panel {
position: absolute; top: 0; right: 0; bottom: 0;
width: 300px; background: rgba(12,8,24,.97);
border-left: 1.5px solid rgba(74,222,128,.2);
display: flex; flex-direction: column;
z-index: 15; transform: translateX(100%);
transition: transform .22s cubic-bezier(.4,0,.2,1);
overflow: hidden;
}
.geo-challenge-panel.open { transform: translateX(0); }
.geo-chall-header {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 14px 10px;
border-bottom: 1px solid rgba(255,255,255,.08);
flex-shrink: 0;
}
.geo-chall-header-title {
font-family: 'Unbounded', sans-serif; font-size: 0.78rem; font-weight: 800;
color: #4ADE80; letter-spacing: .05em;
}
.geo-chall-close {
width: 26px; height: 26px; border-radius: 6px;
border: 1px solid rgba(255,255,255,.12); background: transparent;
color: rgba(255,255,255,.5); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .13s;
}
.geo-chall-close:hover { background: rgba(255,255,255,.06); color: #fff; }
.geo-chall-close svg { width: 13px; height: 13px; stroke: currentColor; }
.geo-chall-list {
flex: 1; overflow-y: auto; padding: 10px 12px;
display: flex; flex-direction: column; gap: 8px;
}
.geo-chall-item {
border-radius: 12px; border: 1.5px solid var(--border);
background: rgba(255,255,255,.02); overflow: hidden;
transition: border-color .15s;
}
.geo-chall-item.chall-current { border-color: rgba(74,222,128,.4); }
.geo-chall-item.chall-done { border-color: rgba(74,222,128,.25); }
.geo-chall-item.chall-locked { opacity: .45; pointer-events: none; }
.geo-chall-head {
display: flex; align-items: center; gap: 8px;
padding: 9px 11px; cursor: pointer;
}
.geo-chall-status {
width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
border: 1.5px solid rgba(255,255,255,.15);
display: flex; align-items: center; justify-content: center;
font-size: 0.65rem; font-weight: 800;
color: rgba(255,255,255,.3); background: rgba(255,255,255,.04);
}
.chall-current .geo-chall-status {
border-color: #4ADE80; color: #4ADE80; background: rgba(74,222,128,.1);
}
.chall-done .geo-chall-status {
border-color: #4ADE80; background: rgba(74,222,128,.18);
}
.geo-chall-name {
font-size: 0.73rem; font-weight: 700; color: var(--text-2);
flex: 1; min-width: 0;
}
.chall-current .geo-chall-name { color: var(--text); }
.chall-done .geo-chall-name { color: rgba(74,222,128,.8); }
.geo-chall-body {
padding: 0 11px 10px; display: none;
}
.geo-chall-item.chall-current .geo-chall-body,
.geo-chall-item.geo-chall-expanded .geo-chall-body { display: block; }
.geo-chall-desc {
font-size: 0.72rem; line-height: 1.55; color: rgba(255,255,255,.55);
margin-bottom: 9px;
}
.geo-chall-actions { display: flex; gap: 6px; }
.geo-chall-btn {
flex: 1; padding: 6px 0; border-radius: 8px; border: 1.5px solid;
font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 700;
cursor: pointer; transition: all .13s; text-align: center;
}
.geo-chall-btn-check {
border-color: rgba(74,222,128,.4); color: #4ADE80; background: rgba(74,222,128,.07);
}
.geo-chall-btn-check:hover { background: rgba(74,222,128,.18); }
.geo-chall-btn-reset {
border-color: rgba(255,255,255,.12); color: rgba(255,255,255,.45); background: transparent;
flex: 0 0 auto; padding: 6px 9px;
}
.geo-chall-btn-reset:hover { background: rgba(255,255,255,.06); color: rgba(255,255,255,.7); }
.geo-chall-hint {
font-size: 0.68rem; color: #FBBF24; margin-top: 7px; line-height: 1.45;
display: none;
}
.geo-chall-hint.visible { display: block; }
.geo-chall-feedback {
font-size: 0.7rem; font-weight: 700; margin-top: 6px; min-height: 14px;
transition: color .2s;
}
.geo-chall-feedback.ok { color: #4ADE80; }
.geo-chall-feedback.err { color: #f87171; }
@keyframes chall-success-fade {
0% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
60% { opacity: 1; transform: translate(-50%, -60%) scale(1.08); }
100% { opacity: 0; transform: translate(-50%, -70%) scale(1.12); }
}
.geo-chall-success-label {
position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
font-family: 'Unbounded', sans-serif; font-size: 1.6rem; font-weight: 800;
color: #4ADE80; pointer-events: none; z-index: 25;
text-shadow: 0 0 30px rgba(74,222,128,.7), 0 2px 6px rgba(0,0,0,.8);
animation: chall-success-fade 2.2s ease-out forwards;
}