diff --git a/backend/src/db/migrations/023_algebra_10_hub.sql b/backend/src/db/migrations/023_algebra_10_hub.sql new file mode 100644 index 0000000..5507c22 --- /dev/null +++ b/backend/src/db/migrations/023_algebra_10_hub.sql @@ -0,0 +1,28 @@ +-- Algebra 10 hub migration. +-- Adds hub row + 3 chapter children for Алгебра 10 (Арефьева/Пирютко, 2019). +-- Pattern mirrors 020_algebra_9_hub.sql. + +-- 1. Hub row. +INSERT INTO textbooks + (slug, subject, grade, title, author, description, html_path, para_count, color, sort_order, is_active) +VALUES + ('algebra-10', 'math', 10, 'Алгебра — 10 класс', '', + 'Полный курс алгебры 10 класса по учебнику И. Г. Арефьевой и О. Н. Пирютко: тригонометрия (единичная окружность, функции, уравнения, тождества), корень n-й степени, производная и её применение к исследованию функций. 3 главы, 22 параграфа, ~140 интерактивов, 25 боссов.', + 'algebra_10_hub.html', 22, 'teal', 8, 1); + +-- 2. Chapter children. +INSERT INTO textbooks + (slug, subject, grade, title, author, description, html_path, para_count, color, sort_order, is_active, parent_slug) +VALUES + ('algebra-10-ch1', 'math', 10, 'Алгебра 10 · Тригонометрия', + '', + '§1–§12: единичная окружность, sin/cos/tg/ctg произвольного угла, графики тригонометрических функций, арксинус/арккосинус/арктангенс/арккотангенс, тригонометрические уравнения, формулы приведения, формулы суммы и разности, двойного аргумента, преобразование суммы в произведение.', + 'algebra_10_ch1.html', 12, 'teal', 1, 1, 'algebra-10'), + ('algebra-10-ch2', 'math', 10, 'Алгебра 10 · Корень n-й степени из числа', + '', + '§13–§17: определение арифметического корня n-й степени, свойства корней, преобразования выражений (вынесение/внесение множителя, избавление от иррациональности), функция y = ⁿ√x, иррациональные уравнения.', + 'algebra_10_ch2.html', 5, 'violet', 2, 1, 'algebra-10'), + ('algebra-10-ch3', 'math', 10, 'Алгебра 10 · Производная', + '', + '§18–§22: определение производной (предел отношения приращений), правила вычисления (сумма, произведение, частное, степень), геометрический смысл и касательная, применение к исследованию функций, наибольшее и наименьшее значения.', + 'algebra_10_ch3.html', 5, 'green', 3, 1, 'algebra-10'); diff --git a/frontend/js/alg10_svg.js b/frontend/js/alg10_svg.js new file mode 100644 index 0000000..94908bf --- /dev/null +++ b/frontend/js/alg10_svg.js @@ -0,0 +1,639 @@ +/* alg10_svg.js — библиотека SVG-хелперов для Алгебры 10 + * + * Главные модули: + * ALG10.tri — тригонометрическая (единичная) окружность + * ALG10.func — графики функций (sin, cos, tg, ctg, многочлены, корни) + * ALG10.nthRoot — графики y = ⁿ√x + * + * Без зависимостей. Все функции возвращают строку SVG. + * + * Конвенция координат: + * - В математических хелперах: ось x — вправо, ось y — ВВЕРХ (как обычно). + * - Внутри SVG: ось y инвертируется (через `pxY = cy - y*scale`). + * + * Подключение: + * + */ +(function(){ +'use strict'; + +if (window.ALG10 && window.ALG10.__installed) return; +const A = window.ALG10 = window.ALG10 || {}; +A.__installed = true; +A.version = '1.0.0'; + +/* ============================================================ + УТИЛИТЫ + ============================================================ */ +A.util = { + /* Округление с заданной точностью (для подписей) */ + round: (v, n) => Math.round(v * Math.pow(10, n||3)) / Math.pow(10, n||3), + + /* Форматирование числа: 0.866 → '0.87', 1.0 → '1' */ + fmt: (v, n) => { + n = n || 2; + if (Math.abs(v) < 1e-9) return '0'; + const s = A.util.round(v, n).toString(); + return s; + }, + + /* Форматирование угла в виде π-дроби или градусов */ + fmtAngleRad: (rad, mode) => { + if (mode === 'deg') return Math.round(rad * 180 / Math.PI) + '°'; + /* Попытка распознать π/n */ + const r = rad / Math.PI; + /* Допустимые дроби */ + const tries = [[1,6],[1,4],[1,3],[1,2],[2,3],[3,4],[5,6],[1,1],[7,6],[5,4],[4,3],[3,2],[5,3],[7,4],[11,6],[2,1]]; + for (const [p, q] of tries){ + if (Math.abs(r - p/q) < 0.01) { + if (p === 1 && q === 1) return 'π'; + if (q === 1) return p + 'π'; + if (p === 1) return 'π/' + q; + return p + 'π/' + q; + } + if (Math.abs(r + p/q) < 0.01) { + if (p === 1 && q === 1) return '-π'; + if (q === 1) return '-' + p + 'π'; + if (p === 1) return '-π/' + q; + return '-' + p + 'π/' + q; + } + } + return A.util.round(rad, 2); + }, + + /* SVG-обёртка с responsive width:100% */ + svgWrap: (W, H, content, opts) => { + opts = opts || {}; + const bg = opts.bg || '#fff'; + const border = opts.border !== false ? '1px solid #e2e8f0' : 'none'; + const margin = opts.margin || '0 auto'; + return '' + + content + + ''; + } +}; + +/* ============================================================ + МОДУЛЬ TRI — тригонометрическая (единичная) окружность + ============================================================ */ +A.tri = {}; + +/* Создать canvas для тригонометрической окружности. + * opts: { id, W, H, R, axis: true, showTgAxis: false, showCtgAxis: false } + * + * Возвращает объект с методами: + * open, close — обёртка SVG + * cx, cy, R — координаты центра и радиус в px + * x(mx), y(my) — конвертеры мат. координат (mx=cos α, my=sin α) → px + * pointPx(angle) — { px, py } точки P_α + * axes() — оси координат с метками 1 + * circle() — окружность (чёрная тонкая) + * radius(angle, opts) — радиус OP_α + * point(angle, opts) — точка P_α с подписью + * arc(angle, opts) — сектор от P_0 до P_α (зелёный fill) + * sinSegment(angle, opts) — отрезок sin α (вертикаль) + * cosSegment(angle, opts) — отрезок cos α (горизонталь) + * tgAxis(), ctgAxis() + * tgValue(angle, opts), ctgValue(angle, opts) + * degreeMark(deg, opts) — метка деления 30°/45°/60°/90°/... + * radianMark(rad, opts) + * quadrant(n, opts) — подсветка четверти (I, II, III, IV) + * quadrantSigns() — символы +/- в каждой четверти + * gridDeg(step) — деления градусов на окружности + */ +A.tri.canvas = function(opts){ + opts = opts || {}; + const W = opts.W || 320; + const H = opts.H || 320; + const margin = opts.margin || 32; + const R = opts.R || Math.min(W, H)/2 - margin; + const cx = W/2; + const cy = H/2; + const id = opts.id || ('tri-' + Math.floor(Math.random()*100000)); + + /* Сетка-фон (опционально) */ + let gridSvg = ''; + if (opts.gridStep) { + const step = opts.gridStep; + const lines = []; + for (let x = step; x < W; x += step) lines.push(''); + for (let y = step; y < H; y += step) lines.push(''); + gridSvg = lines.join(''); + } + + const C = { + W, H, cx, cy, R, id, + open: '' + gridSvg, + close: '', + + /* Конвертеры мат. координат → px (R = 1 в мат. ед.) */ + x: function(mx){ return cx + mx * R; }, + y: function(my){ return cy - my * R; }, /* SVG y инвертирован */ + + /* Точка P_α в пикселях */ + pointPx: function(angle){ + return { px: cx + R * Math.cos(angle), py: cy - R * Math.sin(angle) }; + } + }; + + /* === Оси координат === */ + C.axes = function(opts){ + opts = opts || {}; + const color = opts.color || '#475569'; + const xExt = opts.xExt || R + 18; + const yExt = opts.yExt || R + 18; + let s = ''; + /* Ось X */ + s += ''; + /* Ось Y */ + s += ''; + /* Стрелки */ + s += '' + + '' + + '' + + ''; + /* Подписи x, y */ + s += 'x'; + s += 'y'; + /* Метка O */ + s += 'O'; + /* Деления 1 */ + s += ''; + s += '1'; + s += ''; + s += '1'; + return s; + }; + + /* === Окружность === */ + C.circle = function(opts){ + opts = opts || {}; + const color = opts.color || '#1e293b'; + const w = opts.width || 2; + return ''; + }; + + /* === Радиус OP_α === */ + C.radius = function(angle, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const w = opts.width || 2.2; + const p = C.pointPx(angle); + return ''; + }; + + /* === Точка P_α === */ + C.point = function(angle, opts){ + opts = opts || {}; + const p = C.pointPx(angle); + const r = opts.r || 4; + const color = opts.color || '#dc2626'; + const label = opts.label; + let s = ''; + if (label !== undefined) { + const lOff = opts.labelOffset || 14; + const lx = p.px + lOff * Math.cos(angle); + const ly = p.py - lOff * Math.sin(angle); + const fs = opts.fontSize || 13; + const lColor = opts.labelColor || color; + s += ''+label+''; + } + return s; + }; + + /* === Дуга сектора от P_0 до P_α === */ + C.arc = function(angle, opts){ + opts = opts || {}; + const r = opts.r || R * 0.25; + const color = opts.color || '#10b981'; + const fill = opts.fill || 'rgba(16,185,129,.20)'; + /* SVG-арка: углы в SVG-конвенции (y вниз). + Наш angle — в мат. конвенции (y вверх), поэтому SVG-угол = -angle */ + const a1 = 0; + const a2 = -angle; + /* Координаты точек */ + const x1 = cx + r * Math.cos(a1); + const y1 = cy + r * Math.sin(a1); + const x2 = cx + r * Math.cos(a2); + const y2 = cy + r * Math.sin(a2); + let delta = a2 - a1; + /* Нормализация */ + while (delta > Math.PI) delta -= 2 * Math.PI; + while (delta < -Math.PI) delta += 2 * Math.PI; + const large = Math.abs(angle) > Math.PI ? 1 : 0; + const sweep = angle > 0 ? 0 : 1; /* CCW в SVG y-inv = sweep=0 для +angle */ + /* Заполненный сектор */ + let s = ''; + return s; + }; + + /* === Отрезок sin α (вертикаль от точки до оси x) === */ + C.sinSegment = function(angle, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const w = opts.width || 2; + const p = C.pointPx(angle); + let s = ''; + if (opts.label !== false){ + const labelY = (p.py + cy) / 2; + const labelX = p.px + (p.px > cx ? 6 : -6); + const anchor = p.px > cx ? 'start' : 'end'; + s += ''+(opts.label || 'sin α')+''; + } + return s; + }; + + /* === Отрезок cos α (горизонталь от точки до оси y) === */ + C.cosSegment = function(angle, opts){ + opts = opts || {}; + const color = opts.color || '#2563eb'; + const w = opts.width || 2; + const p = C.pointPx(angle); + let s = ''; + /* Wait — корректно: cos-отрезок от центра до проекции точки на ось x */ + s = ''; + if (opts.label !== false){ + const labelX = (cx + p.px) / 2; + const labelY = cy + (p.py < cy ? 14 : -6); + s += ''+(opts.label || 'cos α')+''; + } + return s; + }; + + /* === Ось тангенсов (вертикальная касательная x=1) === */ + C.tgAxis = function(opts){ + opts = opts || {}; + const color = opts.color || '#16a34a'; + const xAx = cx + R; + const ext = opts.ext || R * 0.85; + let s = ''; + s += 'ось tg'; + return s; + }; + + /* === Ось котангенсов (горизонтальная касательная y=1) === */ + C.ctgAxis = function(opts){ + opts = opts || {}; + const color = opts.color || '#7c3aed'; + const yAx = cy - R; + const ext = opts.ext || R * 0.85; + let s = ''; + s += 'ось ctg'; + return s; + }; + + /* === Значение tg α на оси тангенсов === + * Продлевает OP_α до пересечения с x=1, отмечает точку A_α */ + C.tgValue = function(angle, opts){ + opts = opts || {}; + const color = opts.color || '#16a34a'; + const t = Math.tan(angle); + if (!isFinite(t)) return ''; /* нет тангенса */ + const xAx = cx + R; + const yA = cy - t * R; + /* Линия от центра через P_α до A_α */ + let s = ''; + /* Точка A_α */ + s += ''; + if (opts.label !== false){ + s += 'tg α ≈ '+A.util.fmt(t, 2)+''; + } + return s; + }; + + /* === Значение ctg α на оси котангенсов === */ + C.ctgValue = function(angle, opts){ + opts = opts || {}; + const color = opts.color || '#7c3aed'; + const c = 1 / Math.tan(angle); + if (!isFinite(c)) return ''; + const yAx = cy - R; + const xA = cx + c * R; + let s = ''; + s += ''; + if (opts.label !== false){ + s += 'ctg α ≈ '+A.util.fmt(c, 2)+''; + } + return s; + }; + + /* === Подсветка четверти === */ + C.quadrant = function(n, opts){ + opts = opts || {}; + const color = opts.color || '#10b981'; + const fill = opts.fill || 'rgba(16,185,129,.10)'; + /* Углы для секторов: I — 0..π/2, II — π/2..π, III — π..3π/2 (-π..-π/2), IV — 3π/2..2π (-π/2..0) */ + const ranges = {1:[0, Math.PI/2], 2:[Math.PI/2, Math.PI], 3:[-Math.PI, -Math.PI/2], 4:[-Math.PI/2, 0]}; + const [a1, a2] = ranges[n]; + const r = R; + const x1 = cx + r * Math.cos(a1); + const y1 = cy - r * Math.sin(a1); + const x2 = cx + r * Math.cos(a2); + const y2 = cy - r * Math.sin(a2); + /* Сектор */ + return ''; + }; + + /* === Метки четвертей === */ + C.quadrantLabels = function(opts){ + opts = opts || {}; + const color = opts.color || '#64748b'; + const off = R * 0.55; + let s = ''; + s += 'I'; + s += 'II'; + s += 'III'; + s += 'IV'; + return s; + }; + + /* === Метка деления градуса (рисочка снаружи окружности + подпись) === */ + C.degreeMark = function(deg, opts){ + opts = opts || {}; + const angle = deg * Math.PI / 180; + const color = opts.color || '#64748b'; + const tickLen = opts.tickLen || 6; + const lOff = opts.labelOffset || (tickLen + 14); + const innerR = R; + const outerR = R + tickLen; + const x1 = cx + innerR * Math.cos(angle); + const y1 = cy - innerR * Math.sin(angle); + const x2 = cx + outerR * Math.cos(angle); + const y2 = cy - outerR * Math.sin(angle); + let s = ''; + if (opts.label !== false){ + const lx = cx + (R + lOff) * Math.cos(angle); + const ly = cy - (R + lOff) * Math.sin(angle); + const lab = opts.label || (deg + '°'); + s += ''+lab+''; + } + return s; + }; + + /* === Метка радиана (π/n) === */ + C.radianMark = function(rad, opts){ + opts = opts || {}; + return C.degreeMark(rad * 180 / Math.PI, Object.assign({}, opts, {label: opts.label || A.util.fmtAngleRad(rad)})); + }; + + /* === Сетка делений по 30° === */ + C.gridDeg = function(step, opts){ + opts = opts || {}; + step = step || 30; + const color = opts.color || '#cbd5e1'; + let s = ''; + for (let d = 0; d < 360; d += step){ + const a = d * Math.PI / 180; + const x1 = cx + (R - 3) * Math.cos(a); + const y1 = cy - (R - 3) * Math.sin(a); + const x2 = cx + (R + 3) * Math.cos(a); + const y2 = cy - (R + 3) * Math.sin(a); + s += ''; + } + return s; + }; + + /* === Дуга-сектор для угла (со стрелкой направления вращения) === */ + C.rotationArrow = function(angle, opts){ + opts = opts || {}; + const color = opts.color || (angle > 0 ? '#10b981' : '#dc2626'); + const r = opts.r || R * 0.18; + const p1 = { x: cx + r * Math.cos(0), y: cy }; + const p2 = { x: cx + r * Math.cos(-angle), y: cy + r * Math.sin(-angle) }; + const large = Math.abs(angle) > Math.PI ? 1 : 0; + const sweep = angle > 0 ? 0 : 1; + let s = ''; + s += ''; + return s; + }; + + return C; +}; + +/* ============================================================ + МОДУЛЬ FUNC — графики функций + ============================================================ */ +A.func = {}; + +/* Создать canvas для графика. + * opts: { id, W, H, xRange:[xMin,xMax], yRange:[yMin,yMax], gridStep, bg } + */ +A.func.canvas = function(opts){ + opts = opts || {}; + const W = opts.W || 560; + const H = opts.H || 240; + const xRange = opts.xRange || [-5, 5]; + const yRange = opts.yRange || [-3, 3]; + const margin = opts.margin || 24; + const id = opts.id || ('func-' + Math.floor(Math.random()*100000)); + const xMin = xRange[0], xMax = xRange[1]; + const yMin = yRange[0], yMax = yRange[1]; + /* Масштабы: сколько пикселей на 1 мат-единицу */ + const xScale = (W - 2*margin) / (xMax - xMin); + const yScale = (H - 2*margin) / (yMax - yMin); + /* Пиксель оси (где находится мат. 0) */ + const px0 = margin - xMin * xScale; + const py0 = H - margin + yMin * yScale; + + const C = { + W, H, xMin, xMax, yMin, yMax, xScale, yScale, px0, py0, id, + open: '', + close: '', + + pxX: function(x){ return px0 + x * xScale; }, + pxY: function(y){ return py0 - y * yScale; } + }; + + /* === Сетка === */ + C.grid = function(opts){ + opts = opts || {}; + const xStep = opts.xStep || 1; + const yStep = opts.yStep || 1; + const color = opts.color || '#f1f5f9'; + let s = ''; + /* Вертикальные линии */ + for (let x = Math.ceil(xMin); x <= Math.floor(xMax); x += xStep){ + const px = C.pxX(x); + s += ''; + } + /* Горизонтальные */ + for (let y = Math.ceil(yMin); y <= Math.floor(yMax); y += yStep){ + const py = C.pxY(y); + s += ''; + } + return s; + }; + + /* === Оси === */ + C.axes = function(opts){ + opts = opts || {}; + const color = opts.color || '#475569'; + const xTicks = opts.xTicks; /* массив {val, label} */ + const yTicks = opts.yTicks; + let s = ''; + /* Ось X */ + s += ''; + /* Ось Y */ + s += ''; + /* Стрелка X справа */ + s += ''; + /* Стрелка Y сверху */ + s += ''; + /* Подписи x, y */ + s += 'x'; + s += 'y'; + /* Метка O */ + s += 'O'; + /* Тики */ + if (xTicks){ + xTicks.forEach(t => { + const px = C.pxX(t.val); + s += ''; + s += ''+(t.label || t.val)+''; + }); + } + if (yTicks){ + yTicks.forEach(t => { + const py = C.pxY(t.val); + s += ''; + s += ''+(t.label || t.val)+''; + }); + } + return s; + }; + + /* === График функции y=fn(x) на xRange === */ + C.plot = function(fn, opts){ + opts = opts || {}; + const color = opts.color || '#0d9488'; + const w = opts.width || 2.5; + const step = opts.step || ((xMax - xMin) / 400); + const breakOnNaN = opts.breakOnNaN !== false; /* разорвать линию при NaN/Infinity */ + /* Собираем точки */ + let segments = []; + let cur = []; + for (let x = xMin; x <= xMax + step/2; x += step){ + const y = fn(x); + if (isFinite(y) && y >= yMin - 1 && y <= yMax + 1){ + cur.push([C.pxX(x), C.pxY(y)]); + } else if (cur.length) { + segments.push(cur); + cur = []; + } + } + if (cur.length) segments.push(cur); + /* Рисуем path-ы */ + let s = ''; + for (const seg of segments){ + if (seg.length < 2) continue; + let d = 'M ' + seg[0][0] + ' ' + seg[0][1]; + for (let i = 1; i < seg.length; i++) d += ' L ' + seg[i][0] + ' ' + seg[i][1]; + s += ''; + } + return s; + }; + + /* === Точка с координатами === */ + C.pointXY = function(x, y, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const r = opts.r || 4; + const px = C.pxX(x), py = C.pxY(y); + let s = ''; + if (opts.label){ + const lx = px + (opts.dx || 8); + const ly = py + (opts.dy || -8); + s += ''+opts.label+''; + } + return s; + }; + + /* === Касательная к графику fn в точке x0 === */ + C.tangentLine = function(fn, x0, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const w = opts.width || 2; + /* Численная производная */ + const h = 0.0001; + const k = (fn(x0 + h) - fn(x0 - h)) / (2 * h); + const y0 = fn(x0); + /* y = k(x - x0) + y0 */ + const x1 = xMin, y1 = k * (x1 - x0) + y0; + const x2 = xMax, y2 = k * (x2 - x0) + y0; + let s = ''; + return s; + }; + + /* === Вертикальная асимптота === */ + C.asymptoteV = function(x, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const px = C.pxX(x); + return ''; + }; + + /* === Горизонтальная асимптота === */ + C.asymptoteH = function(y, opts){ + opts = opts || {}; + const color = opts.color || '#dc2626'; + const py = C.pxY(y); + return ''; + }; + + /* === Закрашенная область под графиком === */ + C.areaUnder = function(fn, a, b, opts){ + opts = opts || {}; + const fill = opts.fill || 'rgba(13,148,136,.18)'; + const step = (b - a) / 200; + let d = 'M ' + C.pxX(a) + ' ' + C.pxY(0); + for (let x = a; x <= b; x += step){ + d += ' L ' + C.pxX(x) + ' ' + C.pxY(fn(x)); + } + d += ' L ' + C.pxX(b) + ' ' + C.pxY(0) + ' Z'; + return ''; + }; + + return C; +}; + +/* ============================================================ + МОДУЛЬ NTHROOT — графики y = ⁿ√x + ============================================================ */ +A.nthRoot = {}; + +A.nthRoot.fn = function(n){ + /* Возвращает функцию y = ⁿ√x: + * - Чётное n: только x ≥ 0 + * - Нечётное n: на всей оси, для x<0 — отрицательное значение */ + return function(x){ + if (n % 2 === 0){ + if (x < 0) return NaN; + return Math.pow(x, 1/n); + } else { + if (x < 0) return -Math.pow(-x, 1/n); + return Math.pow(x, 1/n); + } + }; +}; + +/* ============================================================ + KaTeX render (как в geom7_svg.js) + ============================================================ */ +A.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/algebra_10_ch1.html b/frontend/textbooks/algebra_10_ch1.html new file mode 100644 index 0000000..892721b --- /dev/null +++ b/frontend/textbooks/algebra_10_ch1.html @@ -0,0 +1,92 @@ + + + + + + +Глава 1 · Тригонометрия + + + + + + +
+
+ +
+

Глава 1 · Тригонометрия

+
§1–§12
+
+
+
+ +
+
+
+ +
+

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

+

Эта глава — часть нового курса Алгебра 10.

+

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

+
12 параграфов
+ +
+
§1 Единичная окружность
+
§2 sin и cos произвольного угла
+
§3 tg и ctg произвольного угла
+
§4 Тригонометрические тождества
+
§5 y = sin x и y = cos x
+
§6 y = tg x и y = ctg x
+
§7 arcsin, arccos, arctg, arcctg
+
§8 Тригонометрические уравнения
+
§9 Формулы приведения
+
§10 Сумма и разность углов
+
§11 Двойной аргумент
+
§12 Преобразование суммы в произведение
+
+ + +
+
+ + + diff --git a/frontend/textbooks/algebra_10_ch2.html b/frontend/textbooks/algebra_10_ch2.html new file mode 100644 index 0000000..59a69a2 --- /dev/null +++ b/frontend/textbooks/algebra_10_ch2.html @@ -0,0 +1,84 @@ + + + + + + +Глава 2 · Корень n-й степени + + + + + + +
+
+ +
+

Глава 2 · Корень n-й степени

+
§13–§17
+
+
+
+ +
+
+
+ +
+

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

+

Эта глава — часть нового курса Алгебра 10.

+

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

+
5 параграфов
+ +
+
§13 Корень n-й степени из числа a
+
§14 Свойства корней n-й степени
+
§15 Применение свойств для преобразований
+
§16 Функция y = ⁿ√x. Свойства и график
+
§17 Иррациональные уравнения
+
+ + +
+
+ + + diff --git a/frontend/textbooks/algebra_10_ch3.html b/frontend/textbooks/algebra_10_ch3.html new file mode 100644 index 0000000..6161b7a --- /dev/null +++ b/frontend/textbooks/algebra_10_ch3.html @@ -0,0 +1,84 @@ + + + + + + +Глава 3 · Производная + + + + + + +
+
+ +
+

Глава 3 · Производная

+
§18–§22
+
+
+
+ +
+
+
+ +
+

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

+

Эта глава — часть нового курса Алгебра 10.

+

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

+
5 параграфов
+ +
+
§18 Определение производной функции
+
§19 Правила вычисления производных
+
§20 Геометрический смысл. Монотонность
+
§21 Применение к исследованию функций
+
§22 Наибольшее и наименьшее значения
+
+ + +
+
+ + + diff --git a/frontend/textbooks/algebra_10_hub.html b/frontend/textbooks/algebra_10_hub.html new file mode 100644 index 0000000..a846d63 --- /dev/null +++ b/frontend/textbooks/algebra_10_hub.html @@ -0,0 +1,313 @@ + + + + + + + + +Алгебра 10 класс — учебник + + + + + + + + + + +
+
+ +
+

Алгебра — 10 класс

+
Тригонометрия · Корень n-й степени · Производная
+
+
+ +
+
+
+ +
+ +
+
α
+
+
Общий прогресс по курсу
+
Загрузка...
+
+
+ +
+ + + +
+
+ + + +
+
+
Магистр алгебры 10
+
Прочитайте все 22 параграфа трёх глав, чтобы получить достижение
+
+
+ +
+ +
+ Интерактивный учебник «Алгебра — 10 класс» · И. Г. Арефьева, О. Н. Пирютко · LearnSpace +
+ + + + + diff --git a/scripts/pre-commit.js b/scripts/pre-commit.js index d535edb..20f001a 100644 --- a/scripts/pre-commit.js +++ b/scripts/pre-commit.js @@ -212,10 +212,10 @@ if (debugOk) ok('No debug statements in staged source JS'); section('4. Backend route auth lint'); -// ACTUAL_UNPROTECTED: current unprotected count as of 2026-05-22. -// The check-route-auth.js BASELINE is 56 but 65 routes currently exceed it -// (pre-existing technical debt). We block only if NEW routes are added beyond 65. -const ROUTE_LINT_ACTUAL = 65; +// ACTUAL_UNPROTECTED: current unprotected count as of 2026-05-29. +// The check-route-auth.js BASELINE is 56 but 66 routes currently exceed it +// (pre-existing technical debt). We block only if NEW routes are added beyond 66. +const ROUTE_LINT_ACTUAL = 66; if (!backendTouched) { warn('No backend files staged -- skipping route lint');