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 = ' ';
+ /* Метки длины (ticks) */
+ if(opts.ticks){
+ const mid = G.midPoint(p1, p2);
+ const v = G.unit(G.vec(p1, p2));
+ const n = G.perp(v);
+ const tickLen = opts.tickLen || 5;
+ for(let i = 0; i < opts.ticks; i++){
+ const off = (i - (opts.ticks - 1)/2) * 4;
+ const cx = mid.x + v.x * off;
+ const cy = mid.y + v.y * off;
+ 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 = ' ';
+ if(opts.center) s += G.point(c.x, c.y, opts.centerLabel || '', { color:color, dx:6, dy:-6 });
+ /* Радиус */
+ if(opts.radius){
+ const angle = (opts.radiusAngle !== undefined) ? opts.radiusAngle : Math.PI / 4;
+ const rEnd = { x:c.x + r * Math.cos(angle), y:c.y - r * Math.sin(angle) };
+ s += G.segment(c, rEnd, { color:color, width:1.5, dash:'4 3' });
+ if(opts.radiusLabel){
+ const mid = G.midPoint(c, rEnd);
+ 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 = ' ';
+ if(opts.points) pts.forEach((p, i) => { 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 .
-
Содержание (§1–§7) уже спланировано — теория, интерактивы и финальный босс появятся в одной из ближайших волн реализации.
-
§1–§7
-
+
+
+
+ Начала геометрии: всё начинается с точки
+ Здесь мы знакомимся с основными фигурами (точка, прямая, плоскость), учимся различать прямую, луч и отрезок , измерять углы , изучаем смежные и вертикальные углы , окружность и круг , заканчиваем перпендикулярами . Этот фундамент — основа всего курса геометрии.
+
+
Начать § 1
+
+
Прогресс по главе
+
+
0%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+