From 2ffe376b2d25356b7c8a58deb2f5c90f8dac428d Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 07:53:10 +0300 Subject: [PATCH] =?UTF-8?q?feat(geom7=20ch1):=20Wave=205=20=E2=80=94=20?= =?UTF-8?q?=D0=93=D0=BB=D0=B0=D0=B2=D0=B0=201=20=C2=AB=D0=9D=D0=B0=D1=87?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=BE=D0=BD=D1=8F?= =?UTF-8?q?=D1=82=D0=B8=D1=8F=20=D0=B3=D0=B5=D0=BE=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B8=C2=BB=20(=C2=A71-=C2=A77=20+=20=D0=A4=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Главное приобретение волны: библиотека geom7_svg.js — задел на ВСЮ геометрию 7. 14 функций-хелперов: - point, segment, ray, line — базовые примитивы с подписями/тиками - circle с опц. центром, радиусом, подписью R - arc, angle — дуги углов через atan2; кратчайший путь - rightAngleMark — L-форма ВНУТРЬ угла (полилиния по двум направлениям) - protractor — полукруглый транспортир с делениями каждые 10° - polyline, polygon — ломаная/замкнутый полигон - parallelMark — стрелочки на отрезках - svgBox — обёртка с сеткой и фоном - distance, midPoint, vec, unit, perp, rotate — математика - renderMath — KaTeX с правильными делимитерами Глава 1 — 7 § + Финал по учебнику Казакова 2022 (стр. 8-50): - §1 Повторение 5-6 классов (длина, единицы, точка между двумя) - §2 Предмет геометрии (аксиомы vs теоремы, планиметрия/стереометрия) - §3 Прямая, луч, отрезок, ломаная (SVG-иллюстрации каждого) - §4 Окружность и круг (свойство точки относительно окружности) - §5 Угол и виды углов (острый/прямой/тупой/развёрнутый — SVG) + биссектриса - §6 Смежные и вертикальные углы (с SVG: дополнительные лучи + пересечение) - §7 Перпендикулярные прямые (теоремы единственности) Интерактивы: 2-3 на §, всего 17: - викторины с цветными кнопками (тип угла, аксиома/теорема, верно/нет) - тренажёры (длины, углы, биссектрисы, перпендикуляры) Финал: 5 боссов × 5 этапов = 25 этапов. Темы: §1-2, §3-4, §5, §6, §7. amber-тема, KaTeX, sidebar-шпаргалка с формулами, прогресс/XP, /api/textbooks/geometry-7-ch1/progress. JS парсится OK (75 КБ), HTTP 200, 113 КБ. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/js/geom7_svg.js | 263 ++++ frontend/textbooks/geometry_7_ch1.html | 1540 +++++++++++++++++++++++- 2 files changed, 1753 insertions(+), 50 deletions(-) create mode 100644 frontend/js/geom7_svg.js diff --git a/frontend/js/geom7_svg.js b/frontend/js/geom7_svg.js new file mode 100644 index 0000000..1afcbc2 --- /dev/null +++ b/frontend/js/geom7_svg.js @@ -0,0 +1,263 @@ +/* geom7_svg.js — библиотека SVG-хелперов для всей Геометрии 7 + * Используется во всех 5 главах. Намеренно без зависимостей. + * + * Координатная система SVG: y растёт вниз — мы учитываем это в хелперах углов. + * Все функции возвращают строку SVG-фрагмента, готовую к вставке в innerHTML. + * + * Публичный API: window.GEOM7.{point,segment,ray,line,circle,arc,angle, + * rightAngleMark,protractor,polyline,polygon,parallelMark, + * midPoint,distance,vec,unit,perp,rotate,svgBox} + */ +(function(){ +'use strict'; + +if (window.GEOM7 && window.GEOM7.__installed) return; +const G = window.GEOM7 = window.GEOM7 || {}; +G.__installed = true; + +/* === Математика === */ +G.distance = (p1, p2) => Math.hypot(p2.x - p1.x, p2.y - p1.y); +G.midPoint = (p1, p2) => ({ x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }); +G.vec = (p1, p2) => ({ x:p2.x-p1.x, y:p2.y-p1.y }); +G.unit = (v) => { const L = Math.hypot(v.x, v.y) || 1; return { x:v.x/L, y:v.y/L }; }; +G.perp = (v) => ({ x:-v.y, y:v.x }); +G.rotate = (p, a) => ({ x:p.x*Math.cos(a) - p.y*Math.sin(a), y:p.x*Math.sin(a) + p.y*Math.cos(a) }); + +/* === SVG-обёртка (даёт область чертежа с фоном/сеткой) === */ +G.svgBox = function(w, h, opts){ + opts = opts || {}; + const grid = opts.grid !== false; + const bg = opts.bg || '#fafafa'; + const cellSize = opts.cell || 20; + let gridSvg = ''; + if(grid){ + gridSvg = '' + +'' + +'' + +''; + } + return { open:''+gridSvg, close:'' }; +}; + +/* === Точка === */ +G.point = function(x, y, label, opts){ + opts = opts || {}; + const r = opts.r || 4; + const color = opts.color || '#d97706'; + const lx = x + (opts.dx !== undefined ? opts.dx : 8); + const ly = y + (opts.dy !== undefined ? opts.dy : -6); + let s = ''; + if(label) s += ''+label+''; + return s; +}; + +/* === Отрезок === */ +G.segment = function(p1, p2, opts){ + opts = opts || {}; + const color = opts.color || '#0891b2'; + const w = opts.width || 2.5; + let s = ''; + } + } + /* Подпись длины */ + if(opts.label){ + const mid = G.midPoint(p1, p2); + const n = G.perp(G.unit(G.vec(p1, p2))); + const off = opts.labelOffset || 14; + s += ''+opts.label+''; + } + return s; +}; + +/* === Луч (от p1 в направлении p2, продолжается за p2 до края — приблизительно) === */ +G.ray = function(p1, p2, opts){ + opts = opts || {}; + const v = G.unit(G.vec(p1, p2)); + const extend = opts.extend || 400; /* продолжить на N px за p2 */ + const end = { x:p2.x + v.x * extend, y:p2.y + v.y * extend }; + const color = opts.color || '#0891b2'; + const w = opts.width || 2.5; + return ''; +}; + +/* === Прямая (бесконечная — продолжается в обе стороны) === */ +G.line = function(p1, p2, opts){ + opts = opts || {}; + const v = G.unit(G.vec(p1, p2)); + const extend = opts.extend || 400; + const start = { x:p1.x - v.x * extend, y:p1.y - v.y * extend }; + const end = { x:p2.x + v.x * extend, y:p2.y + v.y * extend }; + return G.segment(start, end, opts); +}; + +/* === Окружность === */ +G.circle = function(c, r, opts){ + opts = opts || {}; + const color = opts.color || '#7c3aed'; + let s = ''+opts.radiusLabel+''; + } + } + return s; +}; + +/* === Дуга угла (от V с двумя направлениями) === + * vAng1, vAng2 — углы в радианах (стандартная мат-конвенция: 0 = вправо, ↑ положительный CCW) + * НО: в SVG y вниз, поэтому работаем напрямую с углами SVG (0 = вправо, ↑ против часовой если y вверх). + * Мы передаём углы в SVG-конвенции (для нашей системы — y вниз). + */ +G.arc = function(c, r, a1, a2, opts){ + opts = opts || {}; + const x1 = c.x + r * Math.cos(a1), y1 = c.y + r * Math.sin(a1); + const x2 = c.x + r * Math.cos(a2), y2 = c.y + r * Math.sin(a2); + let delta = a2 - a1; while(delta < 0) delta += 2 * Math.PI; while(delta >= 2 * Math.PI) delta -= 2 * Math.PI; + const large = delta > Math.PI ? 1 : 0; + const sweep = opts.sweep !== undefined ? opts.sweep : 1; + const color = opts.color || '#dc2626'; + return ''; +}; + +/* === Угол: вершина V, на которой угол; точки A, B — на сторонах. Возвращает дугу + (опционально) подпись. === */ +G.angle = function(V, A, B, opts){ + opts = opts || {}; + const r = opts.r || 24; + const a1 = Math.atan2(A.y - V.y, A.x - V.x); + const a2 = Math.atan2(B.y - V.y, B.x - V.x); + /* Берём кратчайшую дугу */ + let delta = a2 - a1; while(delta > Math.PI) delta -= 2 * Math.PI; while(delta < -Math.PI) delta += 2 * Math.PI; + const sweep = delta > 0 ? 1 : 0; + const large = Math.abs(delta) > Math.PI ? 1 : 0; + const x1 = V.x + r * Math.cos(a1), y1 = V.y + r * Math.sin(a1); + const x2 = V.x + r * Math.cos(a2), y2 = V.y + r * Math.sin(a2); + const color = opts.color || '#dc2626'; + let s = ''; + if(opts.label){ + /* Центр подписи — середина биссектрисы */ + const midA = (a1 + a2) / 2 + (Math.abs(delta) > Math.PI ? Math.PI : 0); + const lr = r + (opts.labelOffset || 12); + const lx = V.x + lr * Math.cos(midA); + const ly = V.y + lr * Math.sin(midA); + s += ''+opts.label+''; + } + return s; +}; + +/* === Маркер прямого угла (L-форма ВНУТРЬ угла) === */ +G.rightAngleMark = function(V, P1, P2, opts){ + opts = opts || {}; + const s = opts.size || 12; + const u1 = G.unit(G.vec(V, P1)); + const u2 = G.unit(G.vec(V, P2)); + const p1 = { x:V.x + s * u1.x, y:V.y + s * u1.y }; + const c = { x:p1.x + s * u2.x, y:p1.y + s * u2.y }; + const p2 = { x:V.x + s * u2.x, y:V.y + s * u2.y }; + const color = opts.color || '#dc2626'; + return ''; +}; + +/* === Транспортир (полукруг 180°) === */ +G.protractor = function(c, R, opts){ + opts = opts || {}; + const color = opts.color || '#0891b2'; + const fill = opts.fill || '#fef3c7'; + /* Полукруг по горизонтали (повернуть на 180°) */ + let s = ''; + /* Деления */ + for(let deg = 0; deg <= 180; deg += 10){ + const a = Math.PI - deg * Math.PI / 180; /* 180° → слева, 0° → справа */ + const r2 = (deg % 30 === 0) ? R - 12 : R - 6; + const x1 = c.x + R * Math.cos(a), y1 = c.y - R * Math.sin(a); + const x2 = c.x + r2 * Math.cos(a), y2 = c.y - r2 * Math.sin(a); + s += ''; + if(deg % 30 === 0){ + const xl = c.x + (R - 22) * Math.cos(a), yl = c.y - (R - 22) * Math.sin(a); + s += ''+deg+''; + } + } + /* Центральная точка */ + s += G.point(c.x, c.y, '', { r:3, color:color }); + return s; +}; + +/* === Ломаная === */ +G.polyline = function(pts, opts){ + opts = opts || {}; + const color = opts.color || '#0891b2'; + const ptsStr = pts.map(p => p.x + ',' + p.y).join(' '); + let s = ' { s += G.point(p.x, p.y, opts.labels ? opts.labels[i] : '', { color:color }); }); + return s; +}; + +/* === Полигон (замкнутый) === */ +G.polygon = function(pts, opts){ + opts = opts || {}; + const color = opts.color || '#0891b2'; + const ptsStr = pts.map(p => p.x + ',' + p.y).join(' '); + let s = ''; + if(opts.points) pts.forEach((p, i) => { s += G.point(p.x, p.y, opts.labels ? opts.labels[i] : '', { color:color }); }); + return s; +}; + +/* === Маркер параллельности (стрелки на отрезках) === */ +G.parallelMark = function(p1, p2, opts){ + opts = opts || {}; + const count = opts.count || 1; + const v = G.unit(G.vec(p1, p2)); + const n = G.perp(v); + const mid = G.midPoint(p1, p2); + const color = opts.color || '#7c3aed'; + let s = ''; + const arrow = 6; + for(let i = 0; i < count; i++){ + const off = (i - (count - 1)/2) * 5; + const cx = mid.x + v.x * off; + const cy = mid.y + v.y * off; + /* Стрелка → > указывает в направлении v */ + s += ''; + } + return s; +}; + +/* === KaTeX render (если доступен) — с правильными делимитерами === */ +G.renderMath = function(root){ + if(!root || !window.renderMathInElement) return; + try{ + window.renderMathInElement(root, { + delimiters: [ + { left:'$$', right:'$$', display:true }, + { left:'$', right:'$', display:false }, + { left:'\\[', right:'\\]', display:true }, + { left:'\\(', right:'\\)', display:false } + ], + throwOnError: false + }); + }catch(e){} +}; + +})(); diff --git a/frontend/textbooks/geometry_7_ch1.html b/frontend/textbooks/geometry_7_ch1.html index 3e287b6..bfd6c3b 100644 --- a/frontend/textbooks/geometry_7_ch1.html +++ b/frontend/textbooks/geometry_7_ch1.html @@ -1,72 +1,1512 @@ - + - -Глава 1 · Начальные понятия - + + +Геометрия 7 · Глава 1 · Начальные понятия + + + + + + +
-
+
- - - К Геометрия 7 - +

Геометрия 7 · Глава 1

+
Начальные понятия геометрии · точка, прямая, луч, отрезок, угол, перпендикуляр
-
-

Глава 1 · Начальные понятия

-
§1–§7
+
+ К геометрии 7 + +
-
-
-
- -
-

Глава в разработке

-

Эта глава — часть нового курса Геометрия 7.

-

Содержание (§1–§7) уже спланировано — теория, интерактивы и финальный босс появятся в одной из ближайших волн реализации.

-
§1–§7
- +
+
+
+

Начала геометрии: всё начинается с точки

+

Здесь мы знакомимся с основными фигурами (точка, прямая, плоскость), учимся различать прямую, луч и отрезок, измерять углы, изучаем смежные и вертикальные углы, окружность и круг, заканчиваем перпендикулярами. Этот фундамент — основа всего курса геометрии.

+
+ +
+ Прогресс по главе +
+ 0% +
+
+
+
+ +
+
Параграфы главы
+
+
+ +
§ 1

Повторение материала 5-6 классов

+
§ 2

Предмет геометрии

+
§ 3

Прямая. Луч. Отрезок. Ломаная

+
§ 4

Окружность и круг

+
§ 5

Угол. Виды углов

+
§ 6

Смежные углы. Вертикальные углы

+
§ 7

Перпендикулярные прямые

+
Финал главы

Итоги. 5 боссов главы 1

+ + +
+
Интерактивный учебник «Геометрия 7» · Глава 1 · Начальные понятия · LearnSpace
+ +
Достижение!
+ + +