feat(trigcircle): Фаза 1 — работа с углами + обзор (тренажёр тригонометрии)

План тренажёра в plans/trig-circle/PLAN.md (всё по теме на окружности, кроме графиков функций).
Фаза 1 (аддитивно к рабочему режиму):
- Ввод угла в градусах (поле + Enter/кнопка) → goToAngle (нормализует, показывает
  котерминальность). Подсказка «+360°·k» в бейдже угла.
- Тумблер «График/функции» — скрыть график (тема «функции») → круг на всю ширину
  (переиспользует существующий слой graph + _layout).
- Полная сетка табличных углов (16: 0…330°) вместо 8.
- Опорный (острый) угол к оси Ox в выводе (основа формул приведения) + знаки sin/cos/tg
  по текущей четверти. stats() расширен полями refAngle/refDeg.

Verified: node --check; headless-смоук (vm + canvas-Proxy) 9/9 — опорный угол 30/150/210→30°,
300→60°, 90→90°, 0→0; знаки по четвертям (II: sin+ cos− tg−; IV: sin− cos+); новые
глобальные glue-функции определены. Эмодзи нет (стрелка — inline SVG .ic, tg-неопр. — em-dash).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-24 10:23:31 +03:00
parent 40df8893cc
commit d395e1083b
3 changed files with 116 additions and 6 deletions
+45 -3
View File
@@ -130,7 +130,16 @@ class TrigCircleSim {
const ct = Math.abs(s) > 1e-9 ? co / s : undefined;
const deg = a * 180 / Math.PI;
const q = a < Math.PI/2 ? 1 : a < Math.PI ? 2 : a < 3*Math.PI/2 ? 3 : 4;
return { angle: a, deg, radLabel: this._radLbl(a), sin: s, cos: co, tan: t, cot: ct, quadrant: q };
// Опорный (острый) угол — к ближайшей оси Ox: основа формул приведения.
let ref;
if (a <= Math.PI / 2) ref = a;
else if (a <= Math.PI) ref = Math.PI - a;
else if (a <= 3 * Math.PI/2) ref = a - Math.PI;
else ref = 2 * Math.PI - a;
return {
angle: a, deg, radLabel: this._radLbl(a), sin: s, cos: co, tan: t, cot: ct, quadrant: q,
refAngle: ref, refDeg: ref * 180 / Math.PI,
};
}
/* ═══ Layout ═══════════════════════════════════════════════════════ */
@@ -1029,6 +1038,26 @@ if (typeof window !== 'undefined') window.TrigCircleSim = TrigCircleSim;
if (window.LabFX) LabFX.sound.play('click');
}
/* Ввод угла в градусах (поле + Enter/кнопка). Принимает любое число (включая <0 и >360),
goToAngle нормализует — заодно демонстрирует котерминальность. */
function trigSetAngleDeg(inp) {
if (!trigSim || !inp) return;
const v = parseFloat(String(inp.value || '').replace(',', '.'));
if (!isFinite(v)) return;
trigSim.goToAngle(v * Math.PI / 180);
}
function trigAngleKey(e, inp) { if (e && (e.key === 'Enter' || e.keyCode === 13)) trigSetAngleDeg(inp); }
/* Показать/скрыть график функций (тема «функции» — по умолчанию можно убрать,
круг займёт всю ширину). Переиспользует существующий слой 'graph'. */
function trigToggleGraph(rowEl) {
if (!trigSim) return;
const on = rowEl.classList.toggle('active');
trigSim.toggleLayer('graph', on);
const fns = document.getElementById('trig-graph-fns');
if (fns) fns.style.display = on ? '' : 'none';
}
function _trigUpdateUI(s) {
const _f = v => {
if (v === undefined) return '—';
@@ -1050,9 +1079,22 @@ if (typeof window !== 'undefined') window.TrigCircleSim = TrigCircleSim;
document.getElementById('trig-v-tan').textContent = _f(s.tan);
document.getElementById('trig-v-cot').textContent = _f(s.cot);
// Angle badge
// Angle badge + котерминальные углы (+360°·k)
document.getElementById('trig-angle-badge').innerHTML =
`${degStr} = ${s.radLabel}<br><span style="font-size:0.72rem;opacity:0.6">${s.angle.toFixed(4)} рад</span>`;
`${degStr} = ${s.radLabel}<br><span style="font-size:0.72rem;opacity:0.6">${s.angle.toFixed(4)} рад</span>` +
`<br><span style="font-size:0.68rem;opacity:0.5">+ 360°·k (котерминальные)</span>`;
// Опорный (острый) угол — guarded (панель может не иметь элемента)
const refEl = document.getElementById('trig-ref');
if (refEl) refEl.textContent = (Math.round(s.refDeg * 10) / 10) + '°';
// Знаки функций в текущей четверти
const signsEl = document.getElementById('trig-signs');
if (signsEl) {
const sg = v => (v > 1e-9 ? '+' : v < -1e-9 ? '' : '0');
signsEl.innerHTML =
`<b style="color:#EF476F">sin ${sg(s.sin)}</b> · <b style="color:#06D6E0">cos ${sg(s.cos)}</b> · ` +
`<b style="color:#FFD166">tg ${s.tan === undefined ? '—' : sg(s.tan)}</b>`;
}
// Stats bar (nice fractions)
document.getElementById('trigbar-angle').textContent = degStr;
+36 -3
View File
@@ -506,6 +506,16 @@
<!-- left panel -->
<div class="proj-panel" style="width:240px;gap:0">
<!-- Angle input -->
<div class="gp-section-title" style="margin-bottom:8px">Угол, °</div>
<div style="display:flex;gap:6px;margin-bottom:14px">
<input id="trig-angle-input" type="number" step="1" placeholder="напр. 150"
onkeydown="trigAngleKey(event,this)"
style="flex:1;min-width:0;padding:7px 10px;border:1.5px solid var(--border-h);border-radius:8px;background:#fff;color:var(--text);font-family:'Manrope',sans-serif;font-size:0.82rem;outline:none" />
<button class="preset-btn" style="flex-shrink:0;padding:7px 12px" title="Перейти к углу"
onclick="trigSetAngleDeg(document.getElementById('trig-angle-input'))"><svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></button>
</div>
<!-- Function toggles -->
<div class="gp-section-title" style="margin-bottom:10px">Отрезки</div>
<div style="display:flex;flex-direction:column;gap:5px;margin-bottom:14px">
@@ -535,9 +545,14 @@
</label>
</div>
<!-- Graph function selector -->
<div class="gp-section-title" style="margin-bottom:8px">График</div>
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:14px">
<!-- Graph (functions) — optional, can be hidden to focus on the circle -->
<label class="tri-layer-row active" style="margin-bottom:8px" onclick="trigToggleGraph(this)">
<span class="tri-dot" style="background:var(--violet);box-shadow:0 0 5px var(--violet)"></span>
<span class="tri-layer-name">График</span>
<span class="tri-layer-hint" style="color:var(--text-3)">функции</span>
<span class="tri-toggle"></span>
</label>
<div id="trig-graph-fns" style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:14px">
<button class="trig-fn-btn active" onclick="trigSetGraphFn('sin',this)" style="--fc:#EF476F">sin</button>
<button class="trig-fn-btn" onclick="trigSetGraphFn('cos',this)" style="--fc:#06D6E0">cos</button>
<button class="trig-fn-btn" onclick="trigSetGraphFn('tan',this)" style="--fc:#FFD166">tg</button>
@@ -553,6 +568,16 @@
<span class="tri-stat-k" style="color:#7BF5A4">ctg</span><span class="tri-stat-v" id="trig-v-cot"></span>
</div>
<!-- Reference (acute) angle + signs by quadrant -->
<div class="gp-section-title" style="margin-bottom:8px">Опорный угол · знаки</div>
<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:14px">
<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.78rem">
<span style="color:var(--text-3)">острый угол к оси</span>
<span id="trig-ref" style="font-weight:800;color:var(--violet)"></span>
</div>
<div id="trig-signs" style="text-align:center;font-size:0.72rem;color:var(--text-2)"></div>
</div>
<!-- Notable angles -->
<div class="gp-section-title" style="margin-bottom:8px">Табличные углы</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:14px">
@@ -562,8 +587,16 @@
<button class="preset-btn" onclick="trigGoTo(Math.PI/3)">60°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI/2)">90°</button>
<button class="preset-btn" onclick="trigGoTo(2*Math.PI/3)">120°</button>
<button class="preset-btn" onclick="trigGoTo(3*Math.PI/4)">135°</button>
<button class="preset-btn" onclick="trigGoTo(5*Math.PI/6)">150°</button>
<button class="preset-btn" onclick="trigGoTo(Math.PI)">180°</button>
<button class="preset-btn" onclick="trigGoTo(7*Math.PI/6)">210°</button>
<button class="preset-btn" onclick="trigGoTo(5*Math.PI/4)">225°</button>
<button class="preset-btn" onclick="trigGoTo(4*Math.PI/3)">240°</button>
<button class="preset-btn" onclick="trigGoTo(3*Math.PI/2)">270°</button>
<button class="preset-btn" onclick="trigGoTo(5*Math.PI/3)">300°</button>
<button class="preset-btn" onclick="trigGoTo(7*Math.PI/4)">315°</button>
<button class="preset-btn" onclick="trigGoTo(11*Math.PI/6)">330°</button>
</div>
<!-- Angle info -->
+35
View File
@@ -0,0 +1,35 @@
# Тригонометрическая окружность — план улучшения (тренажёр темы, без функций)
Цель: симуляция `frontend/js/labs/trigcircle.js` + панель `frontend/labs-bodies.html` (#sim-trigcircle)
покрывает всю школьную тригонометрию НА ОКРУЖНОСТИ. Графики y=f(x) («функции») — вне темы:
существующий showGraph оставляем опциональным/скрываемым.
Архитектура: рукописный canvas-sim (класс TrigCircleSim) + HTML-панель в labs-bodies.html +
glue-функции (`_openTrigCircle`, `trigToggle`, `trigGoTo`, `trigReset`, `_trigUpdateUI`) внизу
trigcircle.js; регистрация в `_register-all.js` (`trigcircle`). KaTeX, LabFX, _tasks.js доступны.
⛔ без eval, без эмодзи (inline SVG .ic), всё аддитивно (не ломать текущий режим).
## Уже есть
Окружность, перетаскиваемая точка, угол °/рад (метки π/6…), sin/cos/tan/cot отрезками (слои),
треугольник sin-cos, касательная/котангенс, 16 табличных углов + snap, подсветка четверти,
значения дробями (½,√2/2,√3/2,√3/3,√3), stat-bar, опциональный график (= «функции»).
## Фазы
- **Ф1 — Углы и обзор**: тумблер скрыть график (фокус на круге); ввод угла (° и π-доли);
полная сетка табличных кнопок (16); опорный (острый) угол; знаки по четвертям в выводе;
подсказка котерминальности (+360°k / +2πk).
- **Ф2 — Определения / 6 функций**: подписи sin=y, cos=x на осях; слой sec/csc; Пифагор
sin²+cos²=1 (гипотенуза=1) с формулой; тумблер «формула значения» (KaTeX).
- **Ф3 — Знаки**: режим со знаками +/− sin/cos/tg по четвертям, мнемоника, таблица.
- **Ф4 — Особые углы / таблица значений**: оверлей-таблица 0/30/45/60/90… с подсветкой текущего.
- **Ф5 — Симметрии и формулы приведения**: чётность (α→−α), приведение (π±α, π/2±α, 2π−α)
с анимацией отражения/поворота + KaTeX; период tg/ctg = π.
- **Ф6 — Простейшие уравнения**: задаёшь значение → все решения на круге + общая формула
(sin α=½ → π/6+2πk, 5π/6+2πk); для tg — шаг π; связка с arcsin/arccos геометрически.
- **Ф7 — Два угла (опц.)**: вторая точка β → α±β, формулы сложения.
- **Сквозное**: режимы-вкладки (Углы·Определения·Знаки·Особые·Приведение·Уравнения) с краткой
теорией и кнопкой «Задание» через _tasks.js; шпаргалка (значения+знаки+тождества+приведение).
## Проверка каждой фазы
node --check; headless-смоук математики (опорный угол, знаки, решения уравнений, приведение)
в vm с стабом canvas; коммит+push, без эмодзи, lint.