Commit Graph

56 Commits

Author SHA1 Message Date
Maxim Dolgolyov 02009a8c94 fix(labs): unique preview for hydrostatics (was duplicate of dynamics)
Hydrostatics использовал P_SANDBOX как у dynamics — оба показывали одну
и ту же карточку с блоком/шаром и силами. Добавлен P_HYDRO: мензурка
с погруженным телом + F_A, U-образный манометр с Δh, сообщающиеся сосуды.
2026-05-26 11:52:13 +03:00
Maxim Dolgolyov 6afe928c0d feat(labs): visual polish wave — LabFX foundation + 33 sims juiced up
ФУНДАМЕНТ (4 новых файла):
- _fx_core.js: LabFX namespace, glow.drawGlow, glow.pulse, haptic, shake
- _fx_particles.js: пул 1500 объектов, 6 shapes (dot/spark/ring/smoke/splash/dust)
- _fx_motion.js: tween + 12 easings + critically-damped spring
- _fx_sound.js: 9 procedural synth-звуков (click/tick/whoosh/chime/fizz/spark/bounce/pour/drone), Web Audio API
- Sound toggle в шапке lab.html с localStorage-persist

UX МИКРО (CSS + JS):
- Button states: hover scale+brightness, active scale-down, disabled grayscale
- Slider polish: custom thumb с тенью, filled-track gradient, hover/active
- Focus rings через :focus-visible
- Tooltip system .tt-host data-tt= с 400ms hover, fade-in
- Marching ants для selection
- Loading skeleton с shimmer
- Empty state .sim-empty-* паттерн
- Toast: progress bar внизу, icons по типу
- Cursor states utility classes
- View Transitions API для smooth sim-switch, fallback на CSS fade

PHASE 2 — визуальные эффекты для 33 симуляций:

Physics motion: projectile (launch whoosh + landing splash/shake/haptic + target chime), pendulum (max-extension tick + bob glow), collision (bounce + sparks + shake), angrybirds (whoosh/bounce/fizz/chime + confetti), newton (rocket flame trail + scene transitions), forcesandbox (spring glow + impact sparks)

Physics fields: emfield (field-lines glow + particle trail + lightning при high field + Gauss-drag haptic + rod motion sparks), circuit (energized-wire glow ∝ current + LED bloom + short-circuit shake/spark + heat shimmer smoke + place/erase/switch sounds), opticsbench (beam glow на всех режимах + caustics dust у фокуса + TIR/Brewster one-shot sounds)

Thermo+waves: waves (mode-switch whoosh + Mach-cone particles + spectrum harmonic chime + waveform glow), hydrostatics (pour sound + splash при погружении + valve click), isoprocess (PV-trail dust), heatengine (drone цикла + hot/cold reservoir smoke/dust + phase-change ticks), radioactive (Geiger tick throttle + decay sparks + half-life chime + α/β/γ glow)

Chemistry: chemsandbox (pour splash + fizz bubbles/dust/spark по типу реакции + shake при горении), equilibrium/electrolysis/reactions/titration/flask/ionexchange/redox (pour/fizz/spark/chime по событиям, частицы при ключевых моментах), stoichiometry (fizz bubbles + recipe-change click)

Math+geom: geometry (tool-click + object-create tick + locus glow + challenge confetti via LabFX + haptic), triangle (vertex-drop tick + special-point glow), stereo (figure-change whoosh + cross-section chime, no particles — Three.js), trigcircle (drag-haptic + pitch∝angle tick + sin/cos glow), graph/graphtransform/quadratic (slider-tick throttled + curve glow + discriminant-cross chime), probability (bounce + finish-chime burst), normaldist (pulsing shade + bell glow)

Bio+misc: celldivision (phase-change whoosh + prophase dust + anaphase poles spark + cytokinesis chime), photosynthesis (photon tick + ATP chime + glucose sparkle), bohratom (electron-jump chime ∝ levels + photon spark), orbitals/crystal (mode-change whoosh — Three.js, sound only), logic (gate-place + wire-connect + LED bloom + HIGH wire flowing dots + preset chime), gas/brownian/diffusion/states (preset whoosh, throttled tick по событиям)

Все LabFX вызовы обёрнуты в if (window.LabFX) guard — graceful degradation если фундамент не загружен. 47 JS файлов прошли syntax check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:58:49 +03:00
Maxim Dolgolyov 8b3159b529 feat(labs): wave 3 — 5 new sims + optics merger
Оптическая скамья (opticsbench) — merger thinlens + mirror + refraction
- 4 режима: «Свободная сборка» / «Линза» / «Зеркало» / «Преломление»
- Все 3 движка слиты в OpticsBenchSim (1583 строк)
- Backward compat: #thinlens / #mirrors / #refraction → #opticsbench
- Удалены: thinlens.js, mirror.js, refraction.js

Радиоактивный распад (radioactive) — новая сима
- Monte-Carlo распад: λ·dt вероятность на тик, частицы меняют цвет, эмитируются α/β/γ
- Real-time N(t) график с теоретической кривой N₀·exp(-λt)
- 7 изотопов: ¹⁴C, ¹³¹I, ¹³⁷Cs, ²²⁶Ra, ⁴⁰K, ²³⁸U-chain, ²³⁵U-chain
- Цепочки распадов (U-238: 14 шагов сокращены до 5 ключевых)
- Dating mode для C-14: t = ln(N₀/N)/λ
- HUD: периодов прошло, % распалось, активность в Бк

Тепловые двигатели (heatengine) — новая сима
- 4 цикла: Карно / Отто / Дизель / Брайтон
- PV-диаграмма с замкнутым циклом, заполненной площадью работы
- Аналитически точные изотермы (PV=nRT) и адиабаты (PV^γ=const)
- Анимированный поршень с резервуарами (красный T_h / синий T_c)
- Частицы газа, скорость ∝ √T
- Hover-tooltips с формулами для каждого сегмента

Логические схемы (logic) — новая сима для информатики
- Drag-drop конструктор: 12 типов компонентов (INPUT/CLOCK/OUTPUT/AND/OR/NOT/XOR/NAND/NOR/XNOR/BUF/wire)
- Топологическая сортировка для propagation, цветовая подсветка HIGH/LOW
- Авто-генерация булевого выражения (∧ ∨ ¬ ⊕)
- Авто-таблица истинности (до 2^6 = 64 строк)
- 6 пресетов: полусумматор, полный сумматор, RS-триггер, D-триггер, декодер 2-в-4, мультиплексор 2-в-1

Стехиометрия (stoichiometry) — новая сима
- 10 реакций: Zn+HCl, H₂+O₂, CH₄+O₂, N₂+H₂ (Габер), Al+CuSO₄, Mg+O₂, CaCO₃→, HCl+NaOH, KMnO₄→, C₂H₅OH+O₂
- Sliders с переключением m/n/V (для газов V=n·22.4 при н.у.)
- Анимация частиц при реакции, подсветка лимитирующего реагента
- Пошаговый расчёт m→n→n_product→m_product с KaTeX
- HUD: лимит, избытки, теоретический выход

Каталог: 33 → 35 сим (5 новых − 3 удалённых merger)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:25:16 +03:00
Maxim Dolgolyov 8f30a8cef6 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>
2026-05-23 12:48:14 +03:00
Maxim Dolgolyov 7f75c96acd feat(labs): planimetry locus + emfield merger + projectile graphs + UI cleanup
Геометрия (планиметрия):
- Живые измерения как объекты: длина / угол / площадь — auto-recompute, draggable chips
- Инструмент ГМТ: sweep мовера через параметр, рисует кривую места точек
- Новые типы точек: on_segment (скользит по отрезку, _t), on_circle (по окружности, _theta)
- Toolbar: «Длина», «Угол», «Площадь», «ГМТ», «На отрезке», «На окружности»

Электромагнитные поля (emfield):
- Merge magnetic.js + coulomb.js в один EMFieldSim с 3 режимами (E / B / комбинированное)
- Унифицированный pipeline: colormap, field lines, vectors, equipotentials, flux loop, test particle
- Combined-режим: полная сила Лоренца F=q(E+v×B)
- Backward compat: #coulomb и #magnetic хеши и ?sim= параметры редиректят в emfield
- Удалены: magnetic.js, coulomb.js. Добавлен: emfield.js

Бросок тела (projectile):
- Режим целей: 3 окна, hit-детекция, HUD «Цели: N/M / Попыток: K»
- Графики x(t), y(t), vx(t), vy(t) — 2×2 Canvas 2D, real-time
- Двойной бросок: одновременно 2 траектории для сравнения (cyan vs gold)

UI fixes (по результатам аудита):
- Заменены emoji/unicode на inline SVG .ic: switch ⌇, spring 〜 (5 мест), download ⬇ (2), camera 📷
- Убраны декоративные символы ☉ ○ из geometry tool labels
- Добавлены THEORY entries: geometry, hydrostatics (раньше показывали fallback)
- Стандартизирована ширина panel для sim-proj и sim-coll (240px)
- waves перенесён в физический блок SIMS catalog (был после биологии)
- Очищен дефолтный sim-topbar-title (был «График функции»)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:09:44 +03:00
Maxim Dolgolyov 0b9685bc5e feat(lab): phase 4 -- hash-router for sim deep-links
URL #sim/<name> deep-links: F5 restores sim, browser back/forward
switches between sims, click on sim-card updates URL.

34 sims mapped via _SIM_HASH_MAP (built dynamically from SIMS array).
Unknown hash -> console.warn fallback.
Recursion guard prevents double-activation on programmatic navigate.
closeSim clears hash via history.pushState (no hashchange loop).
Embed mode excluded from hash updates (?embed=1 workflow unaffected).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 22:56:18 +03:00
Maxim Dolgolyov 6792a4a5c7 refactor(lab): phase 3 — token purification across lab.html / lab.css / lab-glue.js
Replaced ~100 hardcoded brand colors with var() tokens across 3 files.
Kept: tinted-alpha colors (rgba(155,93,229,.x)), canvas fillStyle (vars
don't resolve there), curated SVG card-preview palettes (P_GRAPH, P_MAGNETIC,
etc.), physics-convention colors (#EF476F for positive charges, #4CC9F0 for
negative), slightly-different decorative shades (#EF476F ≠ #F15BB5).

lab.css: #9B5DE5→var(--violet), #06D6E0→var(--cyan), #F15BB5/nscene→var(--pink),
  #0F172A (filter.active)→var(--text), fn-color defaults, trig btn defaults.
lab.html: stat bars, slider labels, info-fn-dot, toggle-dot, panel badges,
  section-specific color coding (violet=wave1/param1, cyan=wave2/param2).
lab-glue.js: toggleTheory() JS style assignments, template-literal HTML headings.

Before: ~265 hardcodes   After: ~60 (canvas+curated+rgba+physics-convention)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 22:51:28 +03:00
Maxim Dolgolyov 92b5c39860 refactor(lab): phase 2 — extract inline glue script to /js/labs/lab-glue.js
lab.html 4324L → 3499L (-825L). 824 lines of glue code moved.

Position preserved: lab-glue.js loads after lab-init.js, before

newton.js / forcesandbox.js / etc. — same execution order as inline.

node --check passes. /lab returns 200. /js/labs/lab-glue.js returns 200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:36:26 +03:00
Maxim Dolgolyov 703c4b5edf feat(admin): surface classroom feature toggle in tab-games
The feature_classroom_enabled flag was fully wired in backend
(classroom/sessions.js:11-14 returns 403 when '0', initialized in
legacy-migrate.js:870 to '1') but had no UI control — admin could
only flip it via direct SQL.

- adminController.updateFeatures: classroom was ALREADY in allowed list
- admin/sections/games.js: new toggle row with video icon added to GAME_FEATURES
- js/api.js hideDisabledFeatures: classroom path mapping added ('/classroom')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 20:16:26 +03:00
Maxim Dolgolyov d1d20c4c86 polish(admin-dash): avatar pills, skeleton loader, mobile breakpoints, palette kept
- Avatar circles in top/worst-5 tables: initials from name, hsl color from hash of name
- Structural skeleton on first load: 4 shimmer card boxes + 5 row placeholders (replaces
  LS.state.loading spinner for better layout-anchored feedback)
- @media ≤640px: 2-column main grid, hero card reverts to normal size, quick-grid 2-col
- Palette: existing per-card colors (violet/cyan/green/amber) already form a good muted
  hue family with vivid pink/amber for alert cards — kept as is to avoid regression

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:07:18 +03:00
Maxim Dolgolyov 124236db58 feat(admin-dash): P1 — sparklines, content inventory, subject distribution, worst-5 sessions
- 7d sparkline per 3 main metric cards (inline SVG polyline, renderSparkline helper)
- "Контент проекта" row: questions/tests/courses/classes totals (compact .ov-inv-grid)
- Per-subject stacked bar (24h) with hue-cycle colors and legend below
- "Худшие 5 сегодня" mirrors top-5 table; both side-by-side ≥1100px via .ov-results-grid
- renderSessionRows() shared helper for top/worst table rows

Backend: 5 new prepared statements (worstSessions24h, sparkUsers7d, sparkSessions7d,
sparkActiveUsers7d, inventory, sessionsBySubject24h)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:05:57 +03:00
Maxim Dolgolyov 64112e56ed feat(admin-dash): P0 — honest zeros, refresh+timestamp, hero hierarchy, stuck-sessions alert
- fmtNum: 0 no longer renders as "—" (muted "0" via .ov-zero instead)
- backend: classesTotal (renamed from activeClasses — was already full count, label fixed)
- backend: abandonedSessions24h (was failedSessions24h status!=completed; now only status=abandoned)
- backend: stuckSessions[] — in_progress > 1h with user/subject join, limit 5
- header: timestamp + manual refresh button (.ov-header flex layout), updates every 30s via interval
- newSessions24h card promoted to hero (2.6rem value, 52px icon, 2fr column ≥720px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:03:30 +03:00
Maxim Dolgolyov 8269f5b145 Merge feature/permissions-hardening: RBAC hardening + B-lite + P0 UX
Phase A (security): permission registry, audit log on perm/feature changes,

token_version bump on permission changes.

B-lite: requireFeature middleware blocks API on disabled global flags.

P0 UX: search, modified-dot, confirm on critical perms, badge wording.

Conflict resolution: admin.js monolith was restructured into

frontend/js/admin/sections/* by feature/admin-redesign merge. P0 UX

edits (originally in monolith) were manually ported to:

- sections/permissions.js — modDot, confirm gate, filterPermissions

- sections/users.js — 'Инд.' → 'Индивидуально' badge in user-perms modal

admin.html search input + dot CSS auto-merged cleanly.
2026-05-17 14:51:05 +03:00
Maxim Dolgolyov 7eea33a135 feat(perm-ui): P0 usability improvements (search, default-dot, confirm-critical, wording)
- registry.js: добавлен флаг requireConfirmOff для 7 критичных прав (questions.manage, classes.manage, library.upload, courses.manage, sessions.reset, theory.access, simulations.access); byRole() теперь возвращает это поле
- admin.html: subtitle в модале прав — «учителя» → «пользователя»; tooltip на кнопке «Сбросить всё по умолчанию»; поле поиска над сеткой прав; CSS .perm-modified-dot (amber, 8px)
- admin.js: badge «Инд.» → «Индивидуально» (font-size 11px); renderPermissions() рисует .perm-modified-dot когда значение отличается от registry default; togglePermission() показывает LS.confirm перед выключением критичных прав; window.filterPermissions() скрывает карточки и role-блоки по поисковому запросу

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:43:49 +03:00
Maxim Dolgolyov 6de91f7595 fix(labs): SVG markup rendered as text in 6 simulations
Hardcoded inline <svg class="ic"> markers used as arrow replacements

(left over from emoji removal) were displayed as raw HTML text where

the consumer used textContent or canvas fillText:

- chemsandbox: csbar-v5 (Продукты cell) used textContent → SVG visible.

  Switched to innerHTML for consistency with eq/ionNet cells.

  Quiz question (qEl.textContent) and answer also receiving SVG —

  cleaned via _csClean at source.

- reactions: modeTxt drawn via canvas fillText — replaced SVG with →.

- ionexchange: REACTIONS data + canvas labels — bulk SVG → Unicode arrows.

- newton: action button labels used textContent → switched to innerHTML;

  canvas arrow labels: SVG → Unicode →/↓.

- collision: 'KE сохранена' canvas label — SVG checkmark → ✓.

- projectile: canvas badges + textContent wind label — SVG → Unicode ←/→/↩.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:47:50 +03:00
Maxim Dolgolyov bf70c3d7d7 fix(admin-redesign): security — stored XSS via user name in onclick
Security review caught: per-row hover actions (users.js) and async

user picker (shop.js, gam.js) interpolated user-controlled name into

JS string literals inside onclick. LS.esc() escapes & < > " but

NOT backslash; the .replace(/'/g, '\'') fallback was broken.

Attack: any authenticated user could set their name to

  a\'); alert(1); //

via PATCH /api/auth/profile (stripTags doesn't strip \) — admin

viewing the users/shop/gam picker would execute arbitrary JS.

Fix: switch from JS-string interpolation to data-uid/data-name

attributes, read via dataset in handler. esc() correctly escapes

for HTML-attribute context; dataset returns the raw string with

zero parse re-entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:30:34 +03:00
Maxim Dolgolyov 3f89030b6e feat(admin): Phase 6 sub-commit 2 — remove .user-panel overlay
Now that the deep pages (sub-commit 1) work, retire the legacy
.user-panel inline overlay entirely.

* admin.html: removed <div class="user-panel" id="user-panel"> block
  inside #tab-users, removed dead .user-panel* CSS (kept .btn-close
  for any external use).
* users.js: removed openUserPanel / closeUserPanel / reloadUserPanel
  and their closure state (activeTr, activeUserRole). User row onclick
  switched from openUserPanel(...) → AdminRouter.navigate('#users/N').
  clearUserHistory / toggleBanUser / confirmDeleteUser / openEditUserModal
  / openUserPermsModal / doSet/doReset* all refactored to use the
  getActiveUid() helper (reads window.activeUid, set by user-detail.init)
  + reloadDetailAndList() helper (refreshes deep page + list together).
* sessions.js: row click + eye-button switched from toggleDrawer(id)
  → gotoSession(id) → AdminRouter.navigate('#sessions/N'). Removed
  toggleDrawer + renderDrawer functions (~60L) and openDrawerId state.
  Inline drawer markup removed from the row template.

Verified node --check on all touched JS. ast-index confirms zero
remaining usages of openUserPanel / closeUserPanel / reloadUserPanel /
toggleDrawer across the repo.

This completes Phase 6 and the admin-redesign feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:08:13 +03:00
Maxim Dolgolyov bd3020067b feat(admin): Phase 6 sub-commit 1 — add deep-page sections (overlay still works)
Add user-detail.js (~370L) and session-detail.js (~180L) section
modules that render full pages for #users/:id and #sessions/:id, plus
admin.js dispatch and HTML tab-panes. The legacy .user-panel overlay
is intentionally still in place — sub-commit 2 will remove it once the
deep pages are verified.

* admin.js: DEEP_ROUTES map + activateDeepPane(); activate(route, params)
  signature; initial dispatch respects hash params (so F5 on #users/123
  goes straight to the deep page).
* admin.html: new tab-panes #tab-user-detail / #tab-session-detail and
  two script tags. Old #user-panel overlay untouched.
* user-detail.js: header (avatar/role/email/meta) + sub-tabs
  (Обзор/Сессии/Классы/Audit) with URL-synced sub-tab routing
  (#users/N/sessions etc). Overview: 4 stat cards + per-subject SVG
  bar chart. Sessions: clickable rows that navigate to #sessions/N.
  Classes: placeholder empty-state (no per-user classes endpoint).
  Audit: client-side filter of /admin/audit-log by uid match. Header
  action buttons (Изменить/Права/История/Бан/Удалить) call existing
  overlay handlers; window.activeUid is set before opening any modal.
* session-detail.js: full header (user/subject/score/stats) + per-
  question correctness layout reusing the drawer renderer. Delete
  button uses LS.adminDeleteSession then navigates to #sessions.
  Clicking the user name opens the user deep page.
* users.js: quickOpenUserSessions now navigates to
  #users/<uid>/sessions instead of the bare #sessions list.

Verified node --check on all new/modified JS. baseline npm test still
shows pre-existing 3 auth failures unrelated to this change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:01:22 +03:00
Maxim Dolgolyov 69113ab35e feat(admin): phase 5 — per-row quick actions for users + sessions
Hover-only action buttons (right-aligned, opacity transition, hidden on mobile).

- users.js: 4 actions (ban/unban, award coins, sessions, delete) — replaces `>` glyph cell, falls back to glyph for non-admin / self

- sessions.js: 2 actions (view, delete)

- DELETE /api/admin/sessions/:id (NEW): transactional (assignment_sessions=NULL, user_answers, session_questions, test_sessions), audit-logged, admin-only

- event.stopPropagation defence-in-depth (each button + parent .row-actions)

- LS.confirm for destructive ops; LS.modal for award-coins amount/reason

- CSS injected once via #row-actions-style id-dedup (same content in both sections)

Existing user-panel overlay + session toggle-drawer flows untouched (Phase 6 removes overlay).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:53:19 +03:00
Maxim Dolgolyov f562fe4a71 feat(admin): phase 4 — Cmd+K command palette
Global search modal: actions + users + tests + classes.

- GET /api/admin/search?q=X (~50L controller): 3 parameterized LIKE queries, admin-only

- frontend/js/admin/palette.js (~366L): custom lightweight modal (not LS.modal — footer-button oriented), Ctrl+K/Cmd+K capture-phase override of generic /js/search.js, debounce 150ms, race-guard via _reqSeq, min-query 2 chars, 8 hardcoded actions, ↑↓ wrap + Enter, click-outside close

- adminGlobalSearch helper: drop ignored 'limit' param (server hardcodes 5/3/3)

window.AdminPalette = { open, close, isOpen } exposed for Phase 5/6 use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:39:59 +03:00
Maxim Dolgolyov 41acbdd0d0 feat(admin): phase 3 — dashboard #overview landing
GET /api/admin/overview returns 24h digest (~0.08ms/call).

- adminController.getOverview: 7 prepared statements (users 24h, sessions 24h, active users, classes count, failed sessions, banned this week, top-5 sessions)

- new section frontend/js/admin/sections/overview.js (~205L): bento-grid cards, alerts (only when >0), top-5 table, quick-links

- nav-item + tab-pane reordered: #overview is now default; #stats remains routable

Auth: admin-only (inside requireRole('admin') block, sibling of /stats).

Backward compat: all 13 existing routes unchanged.

Known follow-ups (post-merge polish):

- activeClasses counts all (label could be 'Всего классов')

- failedSessions24h includes in_progress (could tighten to abandoned only)

- topSessions24h drops NULL-score completed rows

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:26:59 +03:00
Maxim Dolgolyov fa67ad1294 fix(admin): expose updateCharCounter for Q-modal oninput handler
Phase 2 review caught this: updateCharCounter was defined inside

questions.js IIFE but never exposed via window.X; admin.html:1672

calls it via oninput, would throw ReferenceError on every keypress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:58:59 +03:00
Maxim Dolgolyov 92030b462c feat(admin): phase 2 — split admin.js into 13 section modules
Replace ~3500L admin.js monolith with thin orchestrator (~700L) +

14 IIFE-wrapped per-section modules under /js/admin/sections/.

Section modules expose AdminSections.<name>.init/reload (lazy init via

switchTab/router) and re-expose onclick handlers via window.X for

backward compat. Shared helpers (MODES/DIFFS, fmtDate, pctClass,

renderMath, qTypeBadge, pagination) live in /js/admin/_shared.js

exposed on window.AdminCtx.

switchTab now dispatches to AdminSections via ROUTE_TO_SECTION map;

non-extracted system tabs (topics/audit/errors/health/classroom/avatars)

remain inline in admin.js. user-panel overlay markup untouched — Phase 6

will remove it.
2026-05-16 22:50:14 +03:00
Maxim Dolgolyov 8a7bed487f feat(admin): phase 1 — hash-router
AdminRouter wraps existing switchTab for deep-linking.

- frontend/js/admin/router.js (new, 102L): parse/navigate/current/on/off, recursion guard via _navigating flag

- admin.html: +1 <script> before admin.js

- admin.js: switchTab(btn, opts) + initAdminRouter IIFE for hashchange dispatch

Backward compat: all 21 onclick=switchTab(this) callsites continue working.

F5 / back / forward / deep-link verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:22:20 +03:00
Maxim Dolgolyov bd7a9dbee2 feat(admin): pagination для users-таблицы (50/стр)
- adminGetUsers возвращает { users, total, page, limit }

- pagination-controls (← 1 … N →) с ellipsis для длинных списков

- shop/gam search callers адаптированы под новый формат ответа

- helper _renderPgnControls переиспользуем для sessions/shop

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:24:18 +03:00
Maxim Dolgolyov bcee5a57e3 ux(admin): Q-modal ergonomic improvements — формулы скрыты, preview по требованию
Полный wizard-refactor Q-modal был отложен как высокорискованный
(сложная форма с многими типами вопросов, риск регрессии). Вместо
этого — безопасные ergonomic-улучшения:

1) FORMULA BAR — collapsed by default
   Раньше: 18 кнопок формул всегда занимали ~50px вертикали в модалке,
   но нужны только при создании math-вопросов.
   Теперь: маленькая кнопка «Вставить формулу» с chevron. Click → bar
   разворачивается. Состояние сохраняется в пределах сессии (DOM-стейт).

2) PREVIEW — показывается только когда есть текст
   Раньше: пустой preview-блок с placeholder «Введите текст вопроса…»
   занимал ~80px независимо от состояния.
   Теперь: .q-preview-wrap.hidden скрывается полностью пока textarea
   пуста. Появляется по input с debounce 150ms (уже было).

Эффект: модал стал ~130px ниже в типичном кейсе (создание non-math
вопроса). На 1080p теперь умещается без скролла для single/multi
с 4 опциями.

Без wizard'а, без риска регрессии — но visible UX-win. Wizard-refactor
по-прежнему доступен как опция, если понадобится дальнейшее снижение
когнитивной нагрузки.
2026-05-16 20:04:08 +03:00
Maxim Dolgolyov 6b7d0355b6 ux(admin): lock-icons на admin-only табах + LS.state helpers
1) LOCK-ICONS на admin-only табах
   Раньше: 7 табов (Магазин, Геймификация, Шаблоны, Симуляции, Игры,
   Доступные тесты, Права доступа) скрывались от учителей через
   display:none. Учитель не знал что они существуют — discoverability 0.

   Теперь:
   - Все табы видны всем, но для не-админа добавлен .locked класс
   - .locked: opacity .42, cursor not-allowed, lock-icon справа
   - title=\"Только для администраторов\" — нативный tooltip
   - switchTab() при клике на .locked показывает toast вместо
     переключения

   Эффект: учитель видит границы своих прав; знает что есть в системе,
   но не доступно ему — может попросить админа дать доступ.

2) LS.state — общий helper для loading/empty/error состояний
   api.js:381 — добавлен LS.state с тремя методами:

   LS.state.loading(el, msg?)           — спиннер + опц. текст
   LS.state.empty(el, msg, icon='inbox') — пустое состояние с иконкой
   LS.state.error(el, err, retryFn?)    — красная иконка + текст
                                          + опц. кнопка «Повторить»

   Все три используют один CSS (.ls-state*) с одним визуальным языком.
   inject стилей лениво (id=ls-state-style).

   Демо-миграция: 3 error-handler'а в admin.js (Stats / Users /
   Sessions) переписаны на LS.state.error с retry-функцией. Юзер
   теперь может нажать «Повторить» вместо перезагрузки страницы.

   Остальные 20+ inline error/empty/spinner'ов в admin.js — для
   постепенной миграции (паттерн установлен).
2026-05-16 19:56:58 +03:00
Maxim Dolgolyov ffd7bac0ac ux(admin): sticky table headers + collapsible nav + унификация лейблов
3 победы из аудита админ-панели за один заход:

1) STICKY TABLE HEADERS
   admin.html:142 — добавлен position:sticky; top:0; z-index:5; на <th>
   Заголовки колонок теперь остаются видны при scroll длинных таблиц
   (Users, Sessions, Shop, Gam — 100+ строк). Background фон поменян
   на opaque #E5EAF7 чтобы строки скроллились чисто за header'ом.
   Стоимость: 1 CSS-правило. Эффект: пользователи не теряют контекст
   столбцов при просмотре длинного списка.

2) COLLAPSIBLE NAV GROUPS
   admin.html:875+ — 4 группы (Аналитика, Контент, Пользователи,
   Система) вместо плоского списка 21 кнопки с просто визуальными
   сепараторами. Каждая группа сворачивается кликом по заголовку.
   Состояние per-группа в localStorage (ls_adm_g_<slug>).
   Группа «Система» (только админ) теперь объединяет shop, gam, sims,
   games, audit, errors, health — раньше они шли вперемешку с
   teacher-видимыми табами (sublog, topics, broadcast). Переместил
   sublog/broadcast в группу «Пользователи», topics в «Контент» —
   логичнее по смыслу.
   Паттерн один-в-один как у sidebar.js (где мы это сделали ранее).

3) УНИФИКАЦИЯ ЛЕЙБЛОВ
   Правило: «+ Добавить» для атомов (вопрос, тема, опция, товар),
              «+ Создать» для составных объектов (тест, задание, курс).
   Изменения:
   - admin.html:1431 — «Создать» → «Добавить» (форма темы — атом)
   - admin.html:1195 — «Новый товар» → «Добавить товар»
   - admin.js:415 — q-modal title «Новый вопрос» → «Добавить вопрос»
   - admin.js:2239 — shop-form-title «Новый товар» → «Добавить товар»
   Теперь кнопка в toolbar и заголовок модалки/формы согласованы.

Остались крупные пункты из аудита (на отдельный заход):
  - Q-modal wizard (split на 2 шага) — 🔴 высокий приоритет
  - Pagination в больших таблицах — 🟡
  - Standardized error/loading states — 🔵
2026-05-16 19:48:31 +03:00
Maxim Dolgolyov f7b6785050 refactor: admin.html — извлечён inline <script> в /js/admin/admin.js
admin.html: 5368 → 1922 строк (−64%, −3446 строк)
frontend/js/admin/admin.js: новый файл 3449 строк

Inline <script> блок (1915-5361) был полностью внутри HTML и не
кешировался отдельно — любое изменение HTML инвалидировало
огромный JS, и наоборот. Теперь:

  - HTML загружается быстро (122 КБ vs 270 КБ)
  - JS кешируется независимо (190 КБ; 7d max-age в prod)
  - Любой ctrl+F по JS в редакторе теперь не требует пробираться
    через тысячи строк HTML

Порядок выполнения сохранён байт-в-байт:
  <script src="/js/api.js"></script>
  <script src="/js/sidebar.js"></script>
  <script src="/js/admin/admin.js"></script>      ← было inline
  ... (далее остаётся как было)
  <script src="/js/notifications.js"></script>
  <script src="/js/search.js"></script>
  <script src="/js/mobile.js"></script>

Никаких изменений в логике, scope, DOM-ready timing — чистая
эстетическая операция. Все 22 вкладки + все модалки и обработчики
продолжают работать ровно как раньше.

Это фундамент для дальнейшего сплита (если понадобится): можно
будет в /js/admin/ разнести по табам (sessions.js, classroom.js,
gamification.js и т.д.) с lazy-load по клику. Сейчас не сделано,
т.к. ROI на эстетику ниже, чем у других задач.
2026-05-16 18:59:38 +03:00
Maxim Dolgolyov bc22715734 feat: LS.modal — общий компонент модалок + миграция /exam9 + /my-students
Новый общий компонент LS.modal (api.js) — companion к LS.confirm.
Универсальная form/content-модалка с консистентным поведением:

  LS.modal({
    title, content, size: 'sm'|'md'|'lg',
    actions: [{label, primary, danger, onClick}],
    onClose,
  });
  // Returns { close, root, body, setBody, setActions, setError }

Стандартное поведение:
  - ESC и backdrop-click закрывают (опциональный dismissible:false)
  - z-index 9000 (тот же что LS.confirm — без конфликтов)
  - Auto-focus первого input/select/textarea/button в body
  - prevFocus restore при закрытии
  - Анимация scale+translateY .22s
  - Адаптив: на мобилках padding уменьшается

CSS-классы .ls-mov / .ls-mod / .ls-mod-hdr / .ls-mod-body / .ls-mod-act
впрыскиваются один раз из api.js (id=ls-modal-style), как и стили
toast/confirm.

Миграция exam9 «Назначить вариант»:
  - Убран inline <div class="ex-overlay" id="assign-overlay">…</div>
  - Убраны .ax-actions, .ax-btn, .ax-btn-primary, .ax-error, .ax-success
    CSS (теперь в общих .ls-mod-* стилях)
  - openAssignModal → LS.modal({ title, content: form, actions: [...] })
  - Удалены closeAssignModal/onAssignOverlayClick/onAssignEsc — теперь
    handle'ит LS.modal
  - Удалена unused переменная assignVariantNum (closure теперь над varNum)

  exam9.html:  −53 строк (CSS + HTML модалки)
  app.js:      переписан 90 строк → 70 строк

Миграция my-students «Убрать ученика»:
  - native confirm() → LS.confirm() с danger-стилизацией
  - alert() → LS.toast() для согласованности

Сохранён классroom-овский «ex-overlay»/«ex-panel» CSS (используется
для picker'а вариантов в exam9). Не трогаем classroom.html — у него
своя ecosystem cr-*-overlay.

Дальше — postupенная миграция модалок в textbooks/classes/admin
по мере касания этих страниц. Шаблон установлен.
2026-05-16 18:41:27 +03:00
Maxim Dolgolyov 3ff2f01178 feat: textbooks Phase 4 — A1+A2+A3+B4+C7 + назначение ученику
A1 — карточка ДЗ-чтения у ученика на /dashboard:
  - Новая ветка в buildAssignCard для assignments с textbook_id
  - Прогресс-бар «X из Y §», цвет берётся из textbook.color
  - Кнопка «Открыть / Продолжить» с deep-link на первый требуемый параграф
  - В classify(): textbook_all_read → done, deadline → overdue

A2 — авто-проверка выполнения:
  - При POST /:slug/progress с mark_read: проверяются активные textbook-assignments
  - Если все требуемые § прочитаны → INSERT в assignment_completion
  - SSE-уведомление учителю «Ученик завершил чтение: <title>»
  - myAssignments возвращает completed_at и textbook_all_read

A3 — учительский UI прогресса класса:
  - Новая страница /textbook-progress (учитель/админ)
  - Селекторы «учебник × класс» → таблица учеников с прогрессом
  - Сортировка по количеству прочитанного, дата last_at
  - Кнопка «Прогресс класса» добавлена в /textbooks (видна учителям)

B4 — admin-UI управления учебниками:
  - /admin-textbooks (только admin) — таблица всех учебников
  - Inline-редактирование title/author, тоггл is_active
  - Колонка «Читателей» (count из textbook_progress)
  - Endpoints: GET /api/textbooks/admin/all, PATCH /admin/:id

C7 — закладки/заметки внутри учебника:
  - Таблица textbook_bookmarks (user, textbook, para, text, note, color)
  - API: GET/POST/PATCH/DELETE для CRUD закладок
  - В tracker: при выделении текста (8-400 симв) появляется плавающая «+ Закладка»
  - Кнопка-иконка в overlay top-left открывает панель «Мои закладки»
  - Хранится paragraph-якорь, цвет, заметка, кнопка удалить

Назначение ученику (в дополнение к классу):
  - В модалке /textbooks — переключатель «Классу / Ученику»
  - Поиск ученика по имени/email через /api/classes/students
  - Submit использует POST /api/assignments (createDirectAssignment)
  - createDirectAssignment расширен textbook_slug + textbook_paragraphs
  - Учитель может назначать только ученикам своих классов

myAssignments расширен: возвращает textbook fields + post-process
  считает textbook_required_count, textbook_read_count, textbook_all_read.

Deep-link поддержка: /textbook/<slug>#pN в tracker.js — на load и hashchange
вызывает setParaTab(pN) (нативная функция учебника).

Миграция 005: assignment_completion + textbook_bookmarks + индексы.
2026-05-16 16:37:11 +03:00
Maxim Dolgolyov e8018d85c1 feat: textbooks — модуль учебников + чтение как ДЗ (3 фазы)
Фаза 1 — структура и каталог:
  - frontend/textbooks/chemistry_9.html (Шиманович, 60 §) + physics_9.html (Исаченкова, 38 §)
  - frontend/textbooks.html — каталог в стиле LearnSpace (карточки с обложками)
  - Маршруты: /textbooks (каталог), /textbook/<slug> (полноэкранный учебник)
  - Сайдбар: пункт «Учебники» (book-open-text)
  - Feature flag feature_textbooks_enabled, hideDisabledFeatures map

Фаза 2 — прогресс в localStorage + UI чтения:
  - frontend/js/textbook-tracker.js — инжектится в каждый учебник:
    - «← Учебники» overlay-кнопка (top-left, semi-transparent)
    - «Прочитано» чекбокс рядом с каждым §-заголовком
    - Зелёный dot на pill уже прочитанных параграфов
    - Авто-открытие последнего параграфа при возврате
  - Каталог показывает прогресс-бар «X из Y прочитано» + кнопку «Продолжить»

Фаза 3 — серверный прогресс + назначение чтения как ДЗ:
  - Таблица textbooks (slug, subject, grade, title, author, color, ...)
  - Таблица textbook_progress (user_id, textbook_id, JSON read[], last_para)
  - Колонки assignments.textbook_id + textbook_paragraphs
  - API: GET /api/textbooks (с прогрессом), GET /:slug, POST /:slug/progress,
    GET /:slug/class-progress (учитель)
  - tracker.js синхронизирует прогресс через POST /progress (если залогинен)
  - На каталоге у учителей кнопка «Назначить чтение» — модалка с выбором
    классов + параграфы («1-5» или «1,3,5») + deadline
  - bulkCreateAssignment расширен: принимает textbook_slug, резолвит в id

Миграция 004 идемпотентная; сиды двух учебников включены.
2026-05-16 14:05:19 +03:00
Maxim Dolgolyov 31a51956b6 feat: exam9 — назначение варианта как ДЗ + импорт нечётных в банк
Импорт 40 нечётных вариантов (v01, v03, ..., v79) в банк вопросов:
- 400 questions с allow_html=1, source_type='экзамен 9', year=2025
- 540 options (single-choice) + correct_text (short_answer)
- 40 tests (по 1 на вариант), title="Экзамен 9 — Вариант N"
- exam9_variant_tests маппинг для назначения

Назначение варианта как ДЗ на /exam9 (для учителей/админов):
- Кнопка «Назначить как ДЗ» под заголовком варианта (только если test_id есть)
- Модалка выбора классов + опциональный deadline
- POST /api/assignments/bulk с test_id из exam9_variant_tests

Поддержка HTML/SVG в вопросах банка через флаг questions.allow_html:
- Миграция 003: ALTER TABLE questions ADD COLUMN allow_html
- sessionController: SELECT возвращают allow_html и image
- test-run.html: рендер q.text и opt.text как HTML при allow_html=1
- test-result.html: то же для explanation и opt.text
- KaTeX: добавлены $...$ и $$...$$ delimiters в обеих страницах

Бонус-фикс: bulkSchema требовал class_id (single), контроллер ждёт
class_ids (array). Существующий вызов из classes.html был сломан;
исправлено вместе.

Команда: node backend/scripts/import-exam9.js  (--all для всех 80)
2026-05-16 13:13:06 +03:00
Maxim Dolgolyov 6cff327e88 feat: exam9 — Экзамен 9 класс по математике (80 вариантов)
Новый отдельный модуль /exam9 в стиле LearnSpace:
- 80 вариантов × 10 заданий = 800 задач с разбором (KaTeX + SVG)
- Сайдбар: пункт «Экзамен 9 класс» (clipboard-check)
- Feature flag: feature_exam9_enabled (мигр. 002)
- Видим всем авторизованным; рендер на стороне клиента
- Прогресс в localStorage: подсветка вариантов (done/partial)
- Возобновление последнего варианта при возврате

Структура:
  frontend/exam9.html              — страница (LearnSpace layout)
  frontend/js/exam9/app.js         — рендерер
  frontend/js/exam9/variants/      — 80 файлов с данными
  frontend/img/exam9/              — 22 PNG/JPG фигур заданий

Картинки путей _tmp/ → /img/exam9/ переписаны автоматически.

Все маршруты проверены: 200 OK на /exam9, /js/exam9/*, /img/exam9/*.
2026-05-16 12:53:49 +03:00
Maxim Dolgolyov ae31e4c4e8 refactor: distribute lab-init.js into 34 engine files
lab-init.js: 4098 -> 543 lines (infrastructure + THEORY only)

Each sim's _open*() + UI helpers moved to its engine file:
graph.js, projectile.js, collision.js, magnetic.js, triangle.js,
geometry.js, trigcircle.js, gas.js (molphys), coulomb.js, circuit.js,
reactions.js (chemistry), newton.js (dynamics), chemsandbox.js,
celldivision.js, photosynthesis.js, angrybirds.js, quadratic.js,
normaldist.js, graphtransform.js, pendulum.js, equilibrium.js,
thinlens.js, mirror.js, isoprocess.js, titration.js, refraction.js,
probability.js, bohratom.js, electrolysis.js, waves.js,
crystal.js, orbitals.js, stereo.js, hydrostatics.js

All 34 engine files syntax-checked OK.
2026-05-08 14:54:54 +03:00
Maxim Dolgolyov d5f77bb648 refactor: extract lab.html sim initializers to lab-init.js
lab.html: 9273 → 5180 lines (-44%)
frontend/js/labs/lab-init.js: new file, 4098 lines

All 34 _open*() functions + sim instance vars + openSim/closeSim/
_addTouchSupport/_simShow + THEORY data extracted to lab-init.js.
Shared globals (FN_COLORS, ALL_SIM_BODIES, ALL_CTRL_BARS, sim vars)
converted from const/let to var for cross-script accessibility.
Three vars in lab.html made global: _disabledSimIds, _theoryOpen, _embedMode.
Load order preserved: lab-init.js added before main <script> block.
2026-05-08 13:41:38 +03:00
Maxim Dolgolyov 952a54f97c security+perf: полное ревью — 17 фиксов P0/P1 (XSS, IDOR, race conditions, rate limits, TURN, WAL)
## P0
- admin.html:2608, red-book-ecosystem.html:489-495 — XSS: u.name/node.name_ru/description обернуты в LS.esc()
- classController.js getAnnouncements — добавлена проверка teacher_id (B14: учитель A не может читать объявления класса B)

## P1 — auth & validation
- authController.js — минимум пароля 6→8 символов (register + change password + login.html)
- gamificationController adminAward — валидация max XP/coins (1M), Number coercion
- shopController adminAwardCoins — валидация max + проверка changes>0

## P1 — race conditions
- petController.buyBg — atomic UPDATE WHERE coins>=? (race-safe)
- shopController.purchaseItem — atomic conditional UPDATE
- liveController — добавлен question_id в live_answers (миграция с пересозданием таблицы), история ответов сохраняется при смене вопроса учителем
- ws-server: invalidateDrawCache экспортирован, classroomController grant/revoke вызывают его → permission revoke применяется мгновенно (раньше до 10s stale)

## P1 — rate limits & retry
- rateLimit middleware: новый параметр byUser=true (использует req.user.id вместо IP — не блокирует пользователей за NAT)
- routes/classroom.js: reactionLimiter (15/5s) на /chat/:msgId/react, handLimiter (5/5s) на raise/lower hand
- api.js sendAnswer — retry 3x с exp backoff (300/1200/2700ms), не повторяет на 4xx (F5)

## P1 — performance
- classroomController.getStrokes — LIMIT 5000 + флаг hasMore (защита от OOM на 10K+ strokes)
- whiteboard.js _liveStrokes — TTL 1.5s на каждый live preview (auto-cleanup при крашe ремоут юзера)

## Infrastructure
- config.js: TURN_URL/USER/PASS env vars
- server.js: GET /api/ice-servers возвращает STUN + опциональный TURN из env
- classroom-rtc.js: фетчит /api/ice-servers вместо хардкода (поддержка TURN для NAT/CGNAT школьных сетей)
- .env.example: документация TURN
- db.js: PRAGMA synchronous=NORMAL (5x быстрее с WAL), cache_size 16MB, temp_store=MEMORY
- ws-server.js closeAll() + server.js shutdown — graceful WS shutdown при SIGTERM

## False positives (не баги, агенты ошиблись)
- assignmentController FK на tests — на самом деле users (migrate.js:317-318)
- .env в git — gitignore корректно исключает
- admin.html без requireAuth — есть LS.initPage() который вызывает requireAuth
- submissionsController IDOR — обе ручки уже проверяют teacher_id
- screenSender = null inside try/catch — на самом деле снаружи
- SSE без backoff — есть exponential 2s→30s
- sessionController NOT IN на пустом массиве — есть guard usedIds.length>0
- getChat без LIMIT — есть LIMIT 100/200
- trust proxy — установлен на server.js:105

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 12:16:08 +03:00
Maxim Dolgolyov 8a86ff3fe5 debug: add RTC diagnostic logs (ICE state, ontrack, sendOffer, signal flow) 2026-04-15 13:45:07 +03:00
Maxim Dolgolyov c3050b9e43 fix: WebRTC audio — glare handling (Perfect Negotiation) + autoplay + signalingState guard 2026-04-15 13:28:38 +03:00
Maxim Dolgolyov d822b705c4 feat: удаление последнего измерения и очистка всех измерений 2026-04-14 14:05:40 +03:00
Maxim Dolgolyov 74fe2c4179 fix: добавить дуги оснований конуса/цилиндра в _edges для постановки точек 2026-04-14 14:02:22 +03:00
Maxim Dolgolyov fff22f7331 feat: засечки рёбер, производные точки 3D, длины рёбер в стереометрии 2026-04-14 13:59:32 +03:00
Maxim Dolgolyov 481a9aeb02 feat: стереометрия — усечённая пирамида, правильные многогранники, скрещивающиеся прямые
- Добавлены фигуры: усечённая пирамида, октаэдр, икосаэдр, додекаэдр
- Формулы V, S, r_вп, R_оп для всех новых фигур
- Инструмент '∠ скрещ. прям.' — угол и расстояние между скрещивающимися прямыми (4 клика)
- Для икосаэдра/додекаэдра — THREE.IcosahedronGeometry/DodecahedronGeometry с извлечением рёбер
- Вписанная/описанная сфера поддерживает октаэдр, икосаэдр, додекаэдр
- Параметр n добавлен для пирамиды

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:59:42 +03:00
Maxim Dolgolyov b520f4b849 feat: режим аннотации поверх симуляции в онлайн-уроке + fix планиметрии (arcmark, triangle tools)
Онлайн-урок:
- Кнопка «Рисовать» в баре симуляции (только учителю)
- При активации: холст доски показывается поверх iframe (z-index), фон прозрачный
- Учитель рисует прямо поверх симуляции обычными инструментами
- Студенты видят то же самое через SSE (classroom_sim_annotate)
- Выход из режима → кнопка «Вернуться к симуляции»

Планиметрия (bugfix):
- arcmark теперь рисуется всегда (не зависит от showAngles)
- altitude/median: 1 клик на вершину треугольника (авто-находит противоположную сторону)
- centroid/orthocenter: 1 клик внутри/на треугольник

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:31:39 +03:00
Maxim Dolgolyov 84dac03e53 fix: баги штрихов/дуг планиметрии + переработка инструментов треугольника; фаза 10.1 (теорема Фалеса)
- arcmark: рисуется всегда (не только при showAngles=true)
- altitude/median: 1 клик на вершину треугольника (авто-определение)
- centroid/orthocenter: 1 клик внутри/на треугольник
- thales: 3 клика O, A, B → A'B' параллельно AB, коэф. k

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:24:43 +03:00
Maxim Dolgolyov bdb81ba9c8 feat: планиметрия фазы 8.3–10.2 — метки параллельности, средняя линия, параллелограмм, диагонали, подобие; geometry в онлайн-уроке
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:15:46 +03:00
Maxim Dolgolyov 2191e53529 feat: Фазы 7–8 планиметрии — элементы треугольника + метки
Фаза 7:
- altitude: высота (клик на сторону → клик вершина → foot + отрезок + прямой угол)
- median: медиана (3 клика A,B,C → midpoint + отрезок)
- centroid: 3 клика → 3 медианы + точка G (centroid constr)
- orthocenter: 3 клика → 3 высоты + точка H (orthocenter constr + altitude_foot constr)
- gOrthocenter() math function
- Прямые углы для altitude_foot в _drawAngleMeasures
- Исправлен баг onHintChange: передавался boolean вместо numeric phase

Фаза 8:
- tick tool: метки равных сторон на отрезках и сторонах полигонов (1–3 штриха)
- arcmark tool: метки равных углов на вершинах полигонов (1–3 дуги)
- _drawTickMark(), sideMarks[], angleMarks[] на полигонах
- Новая секция «Метки» в панели инструментов

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:08:16 +03:00
Maxim Dolgolyov 76428db513 feat: Фазы 6.1–6.3 планиметрии — высоты, прямые углы, удаление объектов
6.1 Стороны полигонов теперь выбираются как опорные линии (_hitTestLine),
    через виртуальные сегменты (virtual:true, polyId). Cascade-удаление
    исправлено на BFS (transitive deps). Теперь можно строить высоты треугольников.

6.2 Прямой угол (квадратный маркер) рисуется для всех foot-конструкций
    в _drawAngleMeasures, независимо от полигона.

6.3 Удаление отдельных объектов: onDeleteRequest callback, диалог
    «Только этот» (derived-точки → свободные) / «Со всеми зависимыми»
    (cascade) / «Отмена». CSS-панель .geo-del-confirm поверх canvas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:00:19 +03:00
Maxim Dolgolyov 0523734898 feat: Phase 5 планиметрии — касательные (tangent) + параллельный перенос (translate)
- gTangentPoints(O, P, r): касательные через полярно-полярную точку M=O+v*r²/d, h=r√(d²-r²)/d
- tangent: 2 derived_line (which=0/1) из внешней точки к окружности; оба пересчитываются
  при движении точки или изменении радиуса/центра; _pendingCircRef хранит окружность-источник
- translate: derived point P'=P+(B-A) по вектору AB; 3-фазный ввод с onHintChange(tool,2/3)
- _hitTestCircle(): найти окружность под курсором (HIT=12px)
- _drawLineRefHighlight(): расширен для circle (рисует дугу подсветки)
- _pendingCircRef очищается в setTool()
- lab.html: кнопки Симметрия/Перенос/Касательные, _GEO_PHASE_HINTS словарь,
  _geoShowHint(name, phase) принимает числовой phase вместо boolean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 10:28:09 +03:00
Maxim Dolgolyov e2e351d9c2 feat: Phase 4 планиметрии — симметрия (reflect) + правильный n-угольник (ngon)
- GeoEngine: _dependsOn/recompute для constr='reflect' и 'ngon_vertex'
- reflect: производная точка-отражение (P'=2·foot-P), зависит от axis+srcPt
- ngon: правильный n-угольник по центру и вершине; вершины v1..vn-1 = derived
  points (constr='ngon_vertex', хранят srcCenter/srcVertex/k/n); при движении
  центра/вершины все вершины автоматически пересчитываются
- GeoSim: _ngonSides=6, setNgonSides(n), инструменты 'reflect'/'ngon' в _handleToolClick
- lab.html: кнопки Симметрия и n-угольник, +/- контроллер сторон, хинты Phase 2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 10:22:49 +03:00