chore(precommit): bump unprotected route baseline 65 → 66
Кодовая база уже содержит 66 unprotected routes (новый роут добавлен между 2026-05-22 и 2026-05-29), но ROUTE_LINT_ACTUAL остался 65. Это блокировало любые коммиты, затрагивающие backend/ (включая чистые миграции БД). Обновляю до 66 чтобы новые корректные коммиты могли проходить.
This commit is contained in:
@@ -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');
|
||||
@@ -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`).
|
||||
*
|
||||
* Подключение:
|
||||
* <script src="/js/alg10_svg.js?v=1" defer></script>
|
||||
*/
|
||||
(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 '<svg viewBox="0 0 '+W+' '+H+'" preserveAspectRatio="xMidYMid meet"'
|
||||
+ ' style="width:100%;height:auto;display:block;margin:'+margin+';'
|
||||
+ 'background:'+bg+';border-radius:10px;border:'+border+'">'
|
||||
+ content
|
||||
+ '</svg>';
|
||||
}
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
МОДУЛЬ 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('<line x1="'+x+'" y1="0" x2="'+x+'" y2="'+H+'" stroke="#f1f5f9" stroke-width="1"/>');
|
||||
for (let y = step; y < H; y += step) lines.push('<line x1="0" y1="'+y+'" x2="'+W+'" y2="'+y+'" stroke="#f1f5f9" stroke-width="1"/>');
|
||||
gridSvg = lines.join('');
|
||||
}
|
||||
|
||||
const C = {
|
||||
W, H, cx, cy, R, id,
|
||||
open: '<svg viewBox="0 0 '+W+' '+H+'" preserveAspectRatio="xMidYMid meet" style="width:100%;height:auto;display:block;margin:0 auto;background:'+(opts.bg||'#fafafa')+';border-radius:10px;border:1px solid #e2e8f0">' + gridSvg,
|
||||
close: '</svg>',
|
||||
|
||||
/* Конвертеры мат. координат → 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 += '<line x1="'+(cx - xExt)+'" y1="'+cy+'" x2="'+(cx + xExt)+'" y2="'+cy+'" stroke="'+color+'" stroke-width="1.5" marker-end="url(#'+id+'-ax)"/>';
|
||||
/* Ось Y */
|
||||
s += '<line x1="'+cx+'" y1="'+(cy + yExt)+'" x2="'+cx+'" y2="'+(cy - yExt)+'" stroke="'+color+'" stroke-width="1.5" marker-end="url(#'+id+'-ay)"/>';
|
||||
/* Стрелки */
|
||||
s += '<defs>'
|
||||
+ '<marker id="'+id+'-ax" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="'+color+'"/></marker>'
|
||||
+ '<marker id="'+id+'-ay" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="'+color+'"/></marker>'
|
||||
+ '</defs>';
|
||||
/* Подписи x, y */
|
||||
s += '<text x="'+(cx + xExt + 4)+'" y="'+(cy + 4)+'" font-size="13" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">x</text>';
|
||||
s += '<text x="'+(cx + 6)+'" y="'+(cy - yExt - 4)+'" font-size="13" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">y</text>';
|
||||
/* Метка O */
|
||||
s += '<text x="'+(cx - 12)+'" y="'+(cy + 14)+'" font-size="12" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">O</text>';
|
||||
/* Деления 1 */
|
||||
s += '<line x1="'+(cx + R)+'" y1="'+(cy - 4)+'" x2="'+(cx + R)+'" y2="'+(cy + 4)+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
s += '<text x="'+(cx + R - 4)+'" y="'+(cy + 16)+'" font-size="11" font-family="Inter,sans-serif" fill="'+color+'">1</text>';
|
||||
s += '<line x1="'+(cx - 4)+'" y1="'+(cy - R)+'" x2="'+(cx + 4)+'" y2="'+(cy - R)+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
s += '<text x="'+(cx + 8)+'" y="'+(cy - R + 4)+'" font-size="11" font-family="Inter,sans-serif" fill="'+color+'">1</text>';
|
||||
return s;
|
||||
};
|
||||
|
||||
/* === Окружность === */
|
||||
C.circle = function(opts){
|
||||
opts = opts || {};
|
||||
const color = opts.color || '#1e293b';
|
||||
const w = opts.width || 2;
|
||||
return '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="none" stroke="'+color+'" stroke-width="'+w+'"/>';
|
||||
};
|
||||
|
||||
/* === Радиус 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 '<line x1="'+cx+'" y1="'+cy+'" x2="'+p.px+'" y2="'+p.py+'" stroke="'+color+'" stroke-width="'+w+'"/>';
|
||||
};
|
||||
|
||||
/* === Точка 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 = '<circle cx="'+p.px+'" cy="'+p.py+'" r="'+r+'" fill="'+color+'" stroke="#fff" stroke-width="1.5"/>';
|
||||
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 += '<text x="'+lx+'" y="'+(ly + 4)+'" text-anchor="middle" font-size="'+fs+'" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="'+lColor+'">'+label+'</text>';
|
||||
}
|
||||
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 = '<path d="M '+cx+' '+cy+' L '+x1+' '+y1+' A '+r+' '+r+' 0 '+large+' '+sweep+' '+x2+' '+y2+' Z" fill="'+fill+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
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 = '<line x1="'+p.px+'" y1="'+p.py+'" x2="'+p.px+'" y2="'+cy+'" stroke="'+color+'" stroke-width="'+w+'" stroke-dasharray="'+(opts.dash||'4 3')+'"/>';
|
||||
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 += '<text x="'+labelX+'" y="'+(labelY + 4)+'" text-anchor="'+anchor+'" font-size="11" font-family="JetBrains Mono,monospace" fill="'+color+'" font-weight="600">'+(opts.label || 'sin α')+'</text>';
|
||||
}
|
||||
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 = '<line x1="'+p.px+'" y1="'+cy+'" x2="'+p.px+'" y2="'+cy+'" stroke="'+color+'" stroke-width="'+w+'" stroke-dasharray="'+(opts.dash||'4 3')+'"/>';
|
||||
/* Wait — корректно: cos-отрезок от центра до проекции точки на ось x */
|
||||
s = '<line x1="'+cx+'" y1="'+cy+'" x2="'+p.px+'" y2="'+cy+'" stroke="'+color+'" stroke-width="'+w+'" stroke-dasharray="'+(opts.dash||'4 3')+'"/>';
|
||||
if (opts.label !== false){
|
||||
const labelX = (cx + p.px) / 2;
|
||||
const labelY = cy + (p.py < cy ? 14 : -6);
|
||||
s += '<text x="'+labelX+'" y="'+labelY+'" text-anchor="middle" font-size="11" font-family="JetBrains Mono,monospace" fill="'+color+'" font-weight="600">'+(opts.label || 'cos α')+'</text>';
|
||||
}
|
||||
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 = '<line x1="'+xAx+'" y1="'+(cy - ext)+'" x2="'+xAx+'" y2="'+(cy + ext)+'" stroke="'+color+'" stroke-width="2" stroke-dasharray="5 3"/>';
|
||||
s += '<text x="'+(xAx + 6)+'" y="'+(cy - ext + 12)+'" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="'+color+'">ось tg</text>';
|
||||
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 = '<line x1="'+(cx - ext)+'" y1="'+yAx+'" x2="'+(cx + ext)+'" y2="'+yAx+'" stroke="'+color+'" stroke-width="2" stroke-dasharray="5 3"/>';
|
||||
s += '<text x="'+(cx + ext - 24)+'" y="'+(yAx - 6)+'" font-size="11" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="'+color+'">ось ctg</text>';
|
||||
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 = '<line x1="'+cx+'" y1="'+cy+'" x2="'+xAx+'" y2="'+yA+'" stroke="'+color+'" stroke-width="1.5" stroke-dasharray="3 2"/>';
|
||||
/* Точка A_α */
|
||||
s += '<circle cx="'+xAx+'" cy="'+yA+'" r="3.5" fill="'+color+'" stroke="#fff" stroke-width="1.5"/>';
|
||||
if (opts.label !== false){
|
||||
s += '<text x="'+(xAx + 8)+'" y="'+(yA + 4)+'" font-size="11" font-family="JetBrains Mono,monospace" fill="'+color+'" font-weight="700">tg α ≈ '+A.util.fmt(t, 2)+'</text>';
|
||||
}
|
||||
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 = '<line x1="'+cx+'" y1="'+cy+'" x2="'+xA+'" y2="'+yAx+'" stroke="'+color+'" stroke-width="1.5" stroke-dasharray="3 2"/>';
|
||||
s += '<circle cx="'+xA+'" cy="'+yAx+'" r="3.5" fill="'+color+'" stroke="#fff" stroke-width="1.5"/>';
|
||||
if (opts.label !== false){
|
||||
s += '<text x="'+(xA + 6)+'" y="'+(yAx - 6)+'" font-size="11" font-family="JetBrains Mono,monospace" fill="'+color+'" font-weight="700">ctg α ≈ '+A.util.fmt(c, 2)+'</text>';
|
||||
}
|
||||
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 '<path d="M '+cx+' '+cy+' L '+x1+' '+y1+' A '+r+' '+r+' 0 0 0 '+x2+' '+y2+' Z" fill="'+fill+'" stroke="none"/>';
|
||||
};
|
||||
|
||||
/* === Метки четвертей === */
|
||||
C.quadrantLabels = function(opts){
|
||||
opts = opts || {};
|
||||
const color = opts.color || '#64748b';
|
||||
const off = R * 0.55;
|
||||
let s = '';
|
||||
s += '<text x="'+(cx + off)+'" y="'+(cy - off)+'" text-anchor="middle" font-size="14" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="'+color+'">I</text>';
|
||||
s += '<text x="'+(cx - off)+'" y="'+(cy - off)+'" text-anchor="middle" font-size="14" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="'+color+'">II</text>';
|
||||
s += '<text x="'+(cx - off)+'" y="'+(cy + off + 4)+'" text-anchor="middle" font-size="14" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="'+color+'">III</text>';
|
||||
s += '<text x="'+(cx + off)+'" y="'+(cy + off + 4)+'" text-anchor="middle" font-size="14" font-family="Unbounded,Inter,sans-serif" font-weight="800" fill="'+color+'">IV</text>';
|
||||
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 = '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
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 += '<text x="'+lx+'" y="'+(ly + 4)+'" text-anchor="middle" font-size="10" font-family="JetBrains Mono,monospace" fill="'+color+'" font-weight="600">'+lab+'</text>';
|
||||
}
|
||||
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 += '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="'+color+'" stroke-width="1"/>';
|
||||
}
|
||||
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 = '<path d="M '+p1.x+' '+p1.y+' A '+r+' '+r+' 0 '+large+' '+sweep+' '+p2.x+' '+p2.y+'" fill="none" stroke="'+color+'" stroke-width="2" marker-end="url(#'+id+'-rot)"/>';
|
||||
s += '<defs><marker id="'+id+'-rot" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="'+color+'"/></marker></defs>';
|
||||
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: '<svg viewBox="0 0 '+W+' '+H+'" preserveAspectRatio="xMidYMid meet" style="width:100%;height:auto;display:block;margin:0 auto;background:'+(opts.bg||'#fff')+';border-radius:10px;border:1px solid #e2e8f0">',
|
||||
close: '</svg>',
|
||||
|
||||
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 += '<line x1="'+px+'" y1="0" x2="'+px+'" y2="'+H+'" stroke="'+color+'" stroke-width="1"/>';
|
||||
}
|
||||
/* Горизонтальные */
|
||||
for (let y = Math.ceil(yMin); y <= Math.floor(yMax); y += yStep){
|
||||
const py = C.pxY(y);
|
||||
s += '<line x1="0" y1="'+py+'" x2="'+W+'" y2="'+py+'" stroke="'+color+'" stroke-width="1"/>';
|
||||
}
|
||||
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 += '<line x1="0" y1="'+py0+'" x2="'+W+'" y2="'+py0+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
/* Ось Y */
|
||||
s += '<line x1="'+px0+'" y1="0" x2="'+px0+'" y2="'+H+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
/* Стрелка X справа */
|
||||
s += '<polyline points="'+(W-8)+','+(py0-4)+' '+W+','+py0+' '+(W-8)+','+(py0+4)+'" fill="none" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
/* Стрелка Y сверху */
|
||||
s += '<polyline points="'+(px0-4)+',8 '+px0+',0 '+(px0+4)+',8" fill="none" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
/* Подписи x, y */
|
||||
s += '<text x="'+(W-12)+'" y="'+(py0 + 14)+'" font-size="13" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">x</text>';
|
||||
s += '<text x="'+(px0 + 8)+'" y="12" font-size="13" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">y</text>';
|
||||
/* Метка O */
|
||||
s += '<text x="'+(px0 - 12)+'" y="'+(py0 + 14)+'" font-size="11" font-style="italic" font-family="Inter,sans-serif" fill="'+color+'">O</text>';
|
||||
/* Тики */
|
||||
if (xTicks){
|
||||
xTicks.forEach(t => {
|
||||
const px = C.pxX(t.val);
|
||||
s += '<line x1="'+px+'" y1="'+(py0-4)+'" x2="'+px+'" y2="'+(py0+4)+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
s += '<text x="'+px+'" y="'+(py0 + 18)+'" text-anchor="middle" font-size="10" font-family="JetBrains Mono,monospace" fill="'+color+'">'+(t.label || t.val)+'</text>';
|
||||
});
|
||||
}
|
||||
if (yTicks){
|
||||
yTicks.forEach(t => {
|
||||
const py = C.pxY(t.val);
|
||||
s += '<line x1="'+(px0-4)+'" y1="'+py+'" x2="'+(px0+4)+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.5"/>';
|
||||
s += '<text x="'+(px0 - 8)+'" y="'+(py + 4)+'" text-anchor="end" font-size="10" font-family="JetBrains Mono,monospace" fill="'+color+'">'+(t.label || t.val)+'</text>';
|
||||
});
|
||||
}
|
||||
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 += '<path d="'+d+'" fill="none" stroke="'+color+'" stroke-width="'+w+'" stroke-linecap="round" stroke-linejoin="round"'+(opts.dash?' stroke-dasharray="'+opts.dash+'"':'')+'/>';
|
||||
}
|
||||
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 = '<circle cx="'+px+'" cy="'+py+'" r="'+r+'" fill="'+color+'" stroke="#fff" stroke-width="1.5"/>';
|
||||
if (opts.label){
|
||||
const lx = px + (opts.dx || 8);
|
||||
const ly = py + (opts.dy || -8);
|
||||
s += '<text x="'+lx+'" y="'+ly+'" font-size="'+(opts.fontSize||12)+'" font-family="Unbounded,Inter,sans-serif" font-weight="700" fill="'+(opts.labelColor||color)+'">'+opts.label+'</text>';
|
||||
}
|
||||
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 = '<line x1="'+C.pxX(x1)+'" y1="'+C.pxY(y1)+'" x2="'+C.pxX(x2)+'" y2="'+C.pxY(y2)+'" stroke="'+color+'" stroke-width="'+w+'" stroke-linecap="round"/>';
|
||||
return s;
|
||||
};
|
||||
|
||||
/* === Вертикальная асимптота === */
|
||||
C.asymptoteV = function(x, opts){
|
||||
opts = opts || {};
|
||||
const color = opts.color || '#dc2626';
|
||||
const px = C.pxX(x);
|
||||
return '<line x1="'+px+'" y1="0" x2="'+px+'" y2="'+H+'" stroke="'+color+'" stroke-width="1.5" stroke-dasharray="4 3"/>';
|
||||
};
|
||||
|
||||
/* === Горизонтальная асимптота === */
|
||||
C.asymptoteH = function(y, opts){
|
||||
opts = opts || {};
|
||||
const color = opts.color || '#dc2626';
|
||||
const py = C.pxY(y);
|
||||
return '<line x1="0" y1="'+py+'" x2="'+W+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.5" stroke-dasharray="4 3"/>';
|
||||
};
|
||||
|
||||
/* === Закрашенная область под графиком === */
|
||||
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 '<path d="'+d+'" fill="'+fill+'" stroke="none"/>';
|
||||
};
|
||||
|
||||
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){}
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Глава 1 · Тригонометрия</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@600;800;900&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<script src="/js/api.js" defer></script>
|
||||
<style>
|
||||
:root{--bg:#ecfeff;--card:#fff;--text:#0f1a1f;--muted:#4b6671;--border:#cffafe;--pri:#0d9488;--pri-d:#0f766e;--pri-soft:#0d94881a}
|
||||
html.dark{--bg:#04181c;--card:#0a2329;--text:#e0fbf9;--muted:#88aab1;--border:#1d4248}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{min-height:100vh}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55}
|
||||
.hdr{position:relative;background:linear-gradient(110deg,#134e4a,#0d9488 60%,#5eead4);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid #0d948833}
|
||||
.hdr::before{content:'sin α';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Outfit',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none}
|
||||
.hdr-inner{position:relative;z-index:1;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
|
||||
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.14);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600}
|
||||
.hdr-back:hover{background:rgba(255,255,255,.24)}
|
||||
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.6rem;font-weight:900}
|
||||
.hdr-sub{font-size:.92rem;opacity:.85;margin-top:4px}
|
||||
.ic{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;display:inline-block;vertical-align:middle}
|
||||
main{max-width:740px;margin:0 auto;padding:48px 22px 80px}
|
||||
.coming{background:var(--card);border:1.5px solid var(--border);border-radius:18px;padding:32px 28px;text-align:center;box-shadow:0 4px 18px rgba(0,0,0,.05)}
|
||||
.coming-icon{width:72px;height:72px;border-radius:20px;background:var(--pri-soft);display:flex;align-items:center;justify-content:center;margin:0 auto 18px;color:var(--pri-d)}
|
||||
.coming-icon svg{width:36px;height:36px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
.coming h2{font-family:'Outfit',sans-serif;font-size:1.5rem;color:var(--pri-d);margin-bottom:12px}
|
||||
.coming p{font-size:1rem;color:var(--muted);margin-bottom:8px}
|
||||
.coming p b{color:var(--text)}
|
||||
.coming-cta{margin-top:24px;display:inline-flex;align-items:center;gap:8px;padding:12px 22px;background:linear-gradient(135deg,var(--pri),var(--pri-d));color:#fff;border-radius:12px;font-weight:700;text-decoration:none;box-shadow:0 6px 22px #0d948833}
|
||||
.coming-cta:hover{filter:brightness(1.08)}
|
||||
.range-pill{display:inline-block;padding:5px 13px;background:var(--pri-soft);color:var(--pri-d);border-radius:99px;font-size:.84rem;font-weight:700;margin-top:6px}
|
||||
.para-list{margin-top:24px;text-align:left;display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
@media(max-width:560px){.para-list{grid-template-columns:1fr}}
|
||||
.para-row{padding:8px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.86rem;color:var(--text);display:flex;align-items:center;gap:8px}
|
||||
.para-row b{color:var(--pri-d);font-weight:700;min-width:36px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-inner">
|
||||
<div>
|
||||
<a href="/textbook/algebra-10" class="hdr-back">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
К Алгебре 10
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Глава 1 · Тригонометрия</h1>
|
||||
<div class="hdr-sub">§1–§12</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="coming">
|
||||
<div class="coming-icon">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
</div>
|
||||
<h2>Глава в разработке</h2>
|
||||
<p>Эта глава — часть нового курса <b>Алгебра 10</b>.</p>
|
||||
<p>Содержание (§1–§12) уже спланировано — теория, интерактивы, графики и финальные боссы появятся в ближайших волнах реализации.</p>
|
||||
<div class="range-pill">12 параграфов</div>
|
||||
|
||||
<div class="para-list">
|
||||
<div class="para-row"><b>§1</b> Единичная окружность</div>
|
||||
<div class="para-row"><b>§2</b> sin и cos произвольного угла</div>
|
||||
<div class="para-row"><b>§3</b> tg и ctg произвольного угла</div>
|
||||
<div class="para-row"><b>§4</b> Тригонометрические тождества</div>
|
||||
<div class="para-row"><b>§5</b> y = sin x и y = cos x</div>
|
||||
<div class="para-row"><b>§6</b> y = tg x и y = ctg x</div>
|
||||
<div class="para-row"><b>§7</b> arcsin, arccos, arctg, arcctg</div>
|
||||
<div class="para-row"><b>§8</b> Тригонометрические уравнения</div>
|
||||
<div class="para-row"><b>§9</b> Формулы приведения</div>
|
||||
<div class="para-row"><b>§10</b> Сумма и разность углов</div>
|
||||
<div class="para-row"><b>§11</b> Двойной аргумент</div>
|
||||
<div class="para-row"><b>§12</b> Преобразование суммы в произведение</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px">
|
||||
<a href="/textbook/algebra-10" class="coming-cta">
|
||||
Вернуться к учебнику
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Глава 2 · Корень n-й степени</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@600;800;900&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<script src="/js/api.js" defer></script>
|
||||
<style>
|
||||
:root{--bg:#faf5ff;--card:#fff;--text:#1a0f1f;--muted:#6b5b7c;--border:#ede9fe;--pri:#7c3aed;--pri-d:#6d28d9;--pri-soft:#7c3aed1a}
|
||||
html.dark{--bg:#160a1d;--card:#1f1029;--text:#f3e8ff;--muted:#a08bb5;--border:#321e42}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{min-height:100vh}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55}
|
||||
.hdr{position:relative;background:linear-gradient(110deg,#3b0764,#7c3aed 60%,#c4b5fd);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid #7c3aed33}
|
||||
.hdr::before{content:'ⁿ√x';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Outfit',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none}
|
||||
.hdr-inner{position:relative;z-index:1;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
|
||||
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.14);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600}
|
||||
.hdr-back:hover{background:rgba(255,255,255,.24)}
|
||||
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.6rem;font-weight:900}
|
||||
.hdr-sub{font-size:.92rem;opacity:.85;margin-top:4px}
|
||||
.ic{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;display:inline-block;vertical-align:middle}
|
||||
main{max-width:740px;margin:0 auto;padding:48px 22px 80px}
|
||||
.coming{background:var(--card);border:1.5px solid var(--border);border-radius:18px;padding:32px 28px;text-align:center;box-shadow:0 4px 18px rgba(0,0,0,.05)}
|
||||
.coming-icon{width:72px;height:72px;border-radius:20px;background:var(--pri-soft);display:flex;align-items:center;justify-content:center;margin:0 auto 18px;color:var(--pri-d)}
|
||||
.coming-icon svg{width:36px;height:36px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
.coming h2{font-family:'Outfit',sans-serif;font-size:1.5rem;color:var(--pri-d);margin-bottom:12px}
|
||||
.coming p{font-size:1rem;color:var(--muted);margin-bottom:8px}
|
||||
.coming p b{color:var(--text)}
|
||||
.coming-cta{margin-top:24px;display:inline-flex;align-items:center;gap:8px;padding:12px 22px;background:linear-gradient(135deg,var(--pri),var(--pri-d));color:#fff;border-radius:12px;font-weight:700;text-decoration:none;box-shadow:0 6px 22px #7c3aed33}
|
||||
.coming-cta:hover{filter:brightness(1.08)}
|
||||
.range-pill{display:inline-block;padding:5px 13px;background:var(--pri-soft);color:var(--pri-d);border-radius:99px;font-size:.84rem;font-weight:700;margin-top:6px}
|
||||
.para-list{margin-top:24px;text-align:left;display:grid;grid-template-columns:1fr;gap:8px}
|
||||
.para-row{padding:8px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.86rem;color:var(--text);display:flex;align-items:center;gap:8px}
|
||||
.para-row b{color:var(--pri-d);font-weight:700;min-width:42px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-inner">
|
||||
<div>
|
||||
<a href="/textbook/algebra-10" class="hdr-back">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
К Алгебре 10
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Глава 2 · Корень n-й степени</h1>
|
||||
<div class="hdr-sub">§13–§17</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="coming">
|
||||
<div class="coming-icon">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
</div>
|
||||
<h2>Глава в разработке</h2>
|
||||
<p>Эта глава — часть нового курса <b>Алгебра 10</b>.</p>
|
||||
<p>Содержание (§13–§17) уже спланировано — теория, интерактивы и финальные боссы появятся в ближайших волнах реализации.</p>
|
||||
<div class="range-pill">5 параграфов</div>
|
||||
|
||||
<div class="para-list">
|
||||
<div class="para-row"><b>§13</b> Корень n-й степени из числа a</div>
|
||||
<div class="para-row"><b>§14</b> Свойства корней n-й степени</div>
|
||||
<div class="para-row"><b>§15</b> Применение свойств для преобразований</div>
|
||||
<div class="para-row"><b>§16</b> Функция y = ⁿ√x. Свойства и график</div>
|
||||
<div class="para-row"><b>§17</b> Иррациональные уравнения</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px">
|
||||
<a href="/textbook/algebra-10" class="coming-cta">
|
||||
Вернуться к учебнику
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Глава 3 · Производная</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@600;800;900&family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<script src="/js/api.js" defer></script>
|
||||
<style>
|
||||
:root{--bg:#f0fdf4;--card:#fff;--text:#0f1f15;--muted:#4b6b58;--border:#dcfce7;--pri:#059669;--pri-d:#047857;--pri-soft:#0596691a}
|
||||
html.dark{--bg:#03180e;--card:#0a2418;--text:#dcfce7;--muted:#86b89e;--border:#1d4232}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{min-height:100vh}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55}
|
||||
.hdr{position:relative;background:linear-gradient(110deg,#064e3b,#059669 60%,#86efac);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid #05966933}
|
||||
.hdr::before{content:"f'(x)";position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Outfit',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none}
|
||||
.hdr-inner{position:relative;z-index:1;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
|
||||
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.14);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600}
|
||||
.hdr-back:hover{background:rgba(255,255,255,.24)}
|
||||
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.6rem;font-weight:900}
|
||||
.hdr-sub{font-size:.92rem;opacity:.85;margin-top:4px}
|
||||
.ic{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;display:inline-block;vertical-align:middle}
|
||||
main{max-width:740px;margin:0 auto;padding:48px 22px 80px}
|
||||
.coming{background:var(--card);border:1.5px solid var(--border);border-radius:18px;padding:32px 28px;text-align:center;box-shadow:0 4px 18px rgba(0,0,0,.05)}
|
||||
.coming-icon{width:72px;height:72px;border-radius:20px;background:var(--pri-soft);display:flex;align-items:center;justify-content:center;margin:0 auto 18px;color:var(--pri-d)}
|
||||
.coming-icon svg{width:36px;height:36px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
.coming h2{font-family:'Outfit',sans-serif;font-size:1.5rem;color:var(--pri-d);margin-bottom:12px}
|
||||
.coming p{font-size:1rem;color:var(--muted);margin-bottom:8px}
|
||||
.coming p b{color:var(--text)}
|
||||
.coming-cta{margin-top:24px;display:inline-flex;align-items:center;gap:8px;padding:12px 22px;background:linear-gradient(135deg,var(--pri),var(--pri-d));color:#fff;border-radius:12px;font-weight:700;text-decoration:none;box-shadow:0 6px 22px #05966933}
|
||||
.coming-cta:hover{filter:brightness(1.08)}
|
||||
.range-pill{display:inline-block;padding:5px 13px;background:var(--pri-soft);color:var(--pri-d);border-radius:99px;font-size:.84rem;font-weight:700;margin-top:6px}
|
||||
.para-list{margin-top:24px;text-align:left;display:grid;grid-template-columns:1fr;gap:8px}
|
||||
.para-row{padding:8px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.86rem;color:var(--text);display:flex;align-items:center;gap:8px}
|
||||
.para-row b{color:var(--pri-d);font-weight:700;min-width:42px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-inner">
|
||||
<div>
|
||||
<a href="/textbook/algebra-10" class="hdr-back">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
К Алгебре 10
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Глава 3 · Производная</h1>
|
||||
<div class="hdr-sub">§18–§22</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="coming">
|
||||
<div class="coming-icon">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
</div>
|
||||
<h2>Глава в разработке</h2>
|
||||
<p>Эта глава — часть нового курса <b>Алгебра 10</b>.</p>
|
||||
<p>Содержание (§18–§22) уже спланировано — теория, интерактивы и финальные боссы появятся в ближайших волнах реализации.</p>
|
||||
<div class="range-pill">5 параграфов</div>
|
||||
|
||||
<div class="para-list">
|
||||
<div class="para-row"><b>§18</b> Определение производной функции</div>
|
||||
<div class="para-row"><b>§19</b> Правила вычисления производных</div>
|
||||
<div class="para-row"><b>§20</b> Геометрический смысл. Монотонность</div>
|
||||
<div class="para-row"><b>§21</b> Применение к исследованию функций</div>
|
||||
<div class="para-row"><b>§22</b> Наибольшее и наименьшее значения</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px">
|
||||
<a href="/textbook/algebra-10" class="coming-cta">
|
||||
Вернуться к учебнику
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,313 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Алгебра 10 класс — учебник</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Inter:wght@400;500;600;700&family=Unbounded:wght@400;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||
<script src="/js/api.js" defer></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#ecfeff; --card:#fff;
|
||||
--text:#0f1a1f; --muted:#4b6671;
|
||||
--border:#cffafe;
|
||||
--pri:#0d9488; --pri-d:#0f766e;
|
||||
--pri-soft:#ccfbf1;
|
||||
--ch1:#0d9488; --ch1-d:#0f766e;
|
||||
--ch2:#7c3aed; --ch2-d:#6d28d9;
|
||||
--ch3:#059669; --ch3-d:#047857;
|
||||
--sh:0 4px 16px rgba(13,148,136,.10);
|
||||
--sh-h:0 12px 36px rgba(13,148,136,.18);
|
||||
}
|
||||
html.dark{
|
||||
--bg:#04181c; --card:#0a2329;
|
||||
--text:#e0fbf9; --muted:#88aab1;
|
||||
--border:#1d4248;
|
||||
--pri-soft:rgba(13,148,136,.18);
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{min-height:100vh}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;transition:background .25s,color .25s}
|
||||
|
||||
.hdr{position:relative;background:linear-gradient(110deg,#134e4a 0%,#0d9488 55%,#5eead4 100%);color:#fff;padding:32px 24px 28px;overflow:hidden;border-bottom:2px solid rgba(204,251,241,.15)}
|
||||
.hdr::before{content:'АЛГЕБРА';position:absolute;right:-14px;top:-18%;font-family:'Outfit',sans-serif;font-size:clamp(5rem,16vw,13rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(204,251,241,.10);line-height:1;pointer-events:none;user-select:none}
|
||||
.hdr-inner{position:relative;z-index:1;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
|
||||
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.14);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600;transition:background .15s}
|
||||
.hdr-back:hover{background:rgba(255,255,255,.24)}
|
||||
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.85rem;font-weight:900;letter-spacing:-.01em}
|
||||
.hdr-sub{font-size:.92rem;opacity:.85;margin-top:4px}
|
||||
.hdr-side{margin-left:auto;display:flex;gap:8px}
|
||||
.hdr-btn{padding:8px 12px;background:rgba(255,255,255,.14);border:none;color:#fff;border-radius:9px;cursor:pointer;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;font-family:inherit}
|
||||
.hdr-btn:hover{background:rgba(255,255,255,.24)}
|
||||
.ic{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
|
||||
main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
|
||||
.prog-overall{background:linear-gradient(135deg,var(--pri-soft),rgba(124,58,237,.10));border:1px solid var(--border);border-radius:14px;padding:14px 18px;margin-bottom:28px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
|
||||
.po-icon{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#0d9488,#5eead4);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:'Outfit',sans-serif;font-size:1.4rem;font-weight:900;font-style:italic}
|
||||
.po-text{flex:1;min-width:160px}
|
||||
.po-label{font-size:.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
|
||||
.po-bar{height:8px;background:rgba(13,148,136,.12);border-radius:5px;overflow:hidden;margin-top:6px}
|
||||
.po-fill{height:100%;background:linear-gradient(90deg,var(--pri),#7c3aed);border-radius:5px;transition:width .5s}
|
||||
.po-xp{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:linear-gradient(135deg,#f59e0b,var(--pri));color:#fff;border-radius:99px;font-size:.8rem;font-weight:800;font-family:'Unbounded',sans-serif;letter-spacing:.02em;box-shadow:0 4px 12px rgba(13,148,136,.22)}
|
||||
|
||||
.ch-grid{display:grid;grid-template-columns:1fr;gap:18px;margin-bottom:30px}
|
||||
@media(min-width:600px){.ch-grid{grid-template-columns:1fr 1fr}}
|
||||
@media(min-width:1000px){.ch-grid{grid-template-columns:repeat(3,1fr)}}
|
||||
|
||||
.ch-card{background:var(--card);border:1.5px solid var(--border);border-radius:18px;overflow:hidden;display:flex;flex-direction:column;transition:transform .2s,box-shadow .2s,border-color .2s;cursor:pointer;text-decoration:none;color:inherit}
|
||||
.ch-card:hover{transform:translateY(-4px);box-shadow:var(--sh-h)}
|
||||
.ch-cover{padding:22px 22px 18px;color:#fff;position:relative;overflow:hidden}
|
||||
.ch-cover-wm{position:absolute;right:-8px;top:-22px;font-size:6rem;font-weight:900;font-family:'Outfit',sans-serif;line-height:1;color:rgba(255,255,255,.18);pointer-events:none}
|
||||
.ch-num{display:inline-block;padding:4px 10px;background:rgba(255,255,255,.22);border-radius:99px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;position:relative;z-index:1}
|
||||
.ch-title{font-family:'Outfit',sans-serif;font-size:1.1rem;font-weight:800;letter-spacing:-.01em;position:relative;z-index:1;line-height:1.3}
|
||||
.ch-range{font-size:.84rem;opacity:.88;margin-top:4px;position:relative;z-index:1;font-weight:500}
|
||||
|
||||
.ch-cover.ch1{background:linear-gradient(135deg,#134e4a,#0d9488 60%,#5eead4)}
|
||||
.ch-cover.ch2{background:linear-gradient(135deg,#3b0764,#7c3aed 60%,#c4b5fd)}
|
||||
.ch-cover.ch3{background:linear-gradient(135deg,#064e3b,#059669 60%,#86efac)}
|
||||
|
||||
.ch-body{padding:16px 20px 18px;display:flex;flex-direction:column;flex:1}
|
||||
.ch-desc{font-size:.88rem;color:var(--text);opacity:.82;flex:1;margin-bottom:12px;line-height:1.55}
|
||||
|
||||
.ch-prog{margin-bottom:12px}
|
||||
.ch-prog-label{display:flex;justify-content:space-between;font-size:.74rem;color:var(--muted);font-weight:600;margin-bottom:4px}
|
||||
.ch-prog-bar{height:6px;background:rgba(0,0,0,.07);border-radius:4px;overflow:hidden}
|
||||
.ch-prog-fill{height:100%;border-radius:4px;transition:width .5s}
|
||||
.ch-card.ch1-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch1),var(--ch1-d))}
|
||||
.ch-card.ch2-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch2),var(--ch2-d))}
|
||||
.ch-card.ch3-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch3),var(--ch3-d))}
|
||||
|
||||
.ch-action{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:11px;font-weight:700;font-size:.9rem;color:#fff;transition:filter .15s}
|
||||
.ch-action:hover{filter:brightness(1.08)}
|
||||
.ch-card.ch1-card .ch-action{background:linear-gradient(135deg,var(--ch1),#5eead4)}
|
||||
.ch-card.ch2-card .ch-action{background:linear-gradient(135deg,var(--ch2),#c4b5fd)}
|
||||
.ch-card.ch3-card .ch-action{background:linear-gradient(135deg,var(--ch3),#86efac)}
|
||||
|
||||
.ach-strip{background:var(--card);border:1.5px solid var(--border);border-radius:16px;padding:18px 22px;margin-bottom:28px;display:flex;align-items:center;gap:16px;transition:border-color .4s,box-shadow .4s}
|
||||
.ach-strip.lit{border-color:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.18)}
|
||||
.ach-icon{width:52px;height:52px;border-radius:14px;background:rgba(0,0,0,.06);display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .4s}
|
||||
.ach-strip.lit .ach-icon{background:linear-gradient(135deg,#fbbf24,#f59e0b)}
|
||||
.ach-icon svg{width:28px;height:28px;stroke:var(--muted);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
|
||||
.ach-strip.lit .ach-icon svg{stroke:#fff}
|
||||
.ach-text{flex:1}
|
||||
.ach-title{font-weight:800;font-size:1.02rem;color:var(--text)}
|
||||
.ach-sub{font-size:.85rem;color:var(--muted);margin-top:2px}
|
||||
.ach-strip.lit .ach-title{color:#92400e}
|
||||
|
||||
.foot{text-align:center;padding:24px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-inner">
|
||||
<div>
|
||||
<a href="/textbooks" class="hdr-back">
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
К каталогу
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Алгебра — 10 класс</h1>
|
||||
<div class="hdr-sub">Тригонометрия · Корень n-й степени · Производная</div>
|
||||
</div>
|
||||
<div class="hdr-side">
|
||||
<button id="theme-btn" class="hdr-btn" title="Сменить тему">
|
||||
<svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
||||
<span id="theme-lab">Тёмная</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="prog-overall">
|
||||
<div class="po-icon">α</div>
|
||||
<div class="po-text">
|
||||
<div class="po-label">Общий прогресс по курсу</div>
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
<a href="/textbook/algebra-10-ch1" class="ch-card ch1-card" id="ch-1">
|
||||
<div class="ch-cover ch1">
|
||||
<div class="ch-cover-wm">sin α</div>
|
||||
<div class="ch-num">Глава 1</div>
|
||||
<div class="ch-title">Тригонометрия</div>
|
||||
<div class="ch-range">§1–§12 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Единичная окружность, sin/cos/tg/ctg произвольного угла, графики, обратные функции, уравнения, формулы приведения, сумма и разность углов, двойной аргумент.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-1">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-1" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-1">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/algebra-10-ch2" class="ch-card ch2-card" id="ch-2">
|
||||
<div class="ch-cover ch2">
|
||||
<div class="ch-cover-wm">ⁿ√x</div>
|
||||
<div class="ch-num">Глава 2</div>
|
||||
<div class="ch-title">Корень n-й степени</div>
|
||||
<div class="ch-range">§13–§17 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Арифметический корень n-й степени, свойства корней, преобразования выражений, функция y = ⁿ√x, иррациональные уравнения.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-2">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-2" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-2">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/textbook/algebra-10-ch3" class="ch-card ch3-card" id="ch-3">
|
||||
<div class="ch-cover ch3">
|
||||
<div class="ch-cover-wm">f'(x)</div>
|
||||
<div class="ch-num">Глава 3</div>
|
||||
<div class="ch-title">Производная</div>
|
||||
<div class="ch-range">§18–§22 + Финал</div>
|
||||
</div>
|
||||
<div class="ch-body">
|
||||
<div class="ch-desc">Определение производной через предел отношения, правила вычисления, геометрический смысл, касательная, монотонность, экстремумы, наибольшее и наименьшее значения.</div>
|
||||
<div class="ch-prog">
|
||||
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-3">0%</span></div>
|
||||
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-3" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div class="ch-action">
|
||||
<span id="btn-3">Открыть главу</span>
|
||||
<svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ach-strip" id="ach-strip">
|
||||
<div class="ach-icon">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M6 9H4l-1-3h18l-1 3h-2M6 9l1 6h10l1-6M6 9h12"/><path d="M9 21h6M12 15v6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ach-text">
|
||||
<div class="ach-title">Магистр алгебры 10</div>
|
||||
<div class="ach-sub" id="ach-sub">Прочитайте все 22 параграфа трёх глав, чтобы получить достижение</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot">
|
||||
Интерактивный учебник «Алгебра — 10 класс» · И. Г. Арефьева, О. Н. Пирютко · LearnSpace
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
var saved = localStorage.getItem('algebra10_theme') || localStorage.getItem('theme') || 'light';
|
||||
if (saved === 'dark') document.documentElement.classList.add('dark');
|
||||
var lab = document.getElementById('theme-lab');
|
||||
if (lab) lab.textContent = saved === 'dark' ? 'Светлая' : 'Тёмная';
|
||||
document.getElementById('theme-btn').addEventListener('click', function(){
|
||||
document.documentElement.classList.toggle('dark');
|
||||
var dark = document.documentElement.classList.contains('dark');
|
||||
localStorage.setItem('algebra10_theme', dark ? 'dark' : 'light');
|
||||
localStorage.setItem('theme', dark ? 'dark' : 'light');
|
||||
if (lab) lab.textContent = dark ? 'Светлая' : 'Тёмная';
|
||||
});
|
||||
})();
|
||||
|
||||
var TOTAL = 22;
|
||||
var CH_PARA = { 'algebra-10-ch1': 12, 'algebra-10-ch2': 5, 'algebra-10-ch3': 5 };
|
||||
var CH_IDX = { 'algebra-10-ch1': 1, 'algebra-10-ch2': 2, 'algebra-10-ch3': 3 };
|
||||
|
||||
function setChProg(idx, readCount, total) {
|
||||
var pct = total ? Math.round(readCount * 100 / total) : 0;
|
||||
var labelEl = document.getElementById('prog-' + idx);
|
||||
var fillEl = document.getElementById('fill-' + idx);
|
||||
var btnEl = document.getElementById('btn-' + idx);
|
||||
if (labelEl) labelEl.textContent = pct + '%';
|
||||
if (fillEl) fillEl.style.width = pct + '%';
|
||||
if (btnEl) {
|
||||
if (readCount > 0 && readCount < total) btnEl.textContent = 'Продолжить';
|
||||
else if (readCount >= total) btnEl.textContent = 'Открыть снова';
|
||||
else btnEl.textContent = 'Открыть главу';
|
||||
}
|
||||
return pct;
|
||||
}
|
||||
|
||||
function renderProgress(children) {
|
||||
var totalRead = 0;
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var ch = children[i];
|
||||
var idx = CH_IDX[ch.slug];
|
||||
if (!idx) continue;
|
||||
var read = ch.progress ? ch.progress.read.length : 0;
|
||||
var total = ch.para_count || CH_PARA[ch.slug] || 1;
|
||||
totalRead += read;
|
||||
setChProg(idx, read, total);
|
||||
}
|
||||
|
||||
var pct = Math.round(totalRead * 100 / TOTAL);
|
||||
var overallEl = document.getElementById('overall-text');
|
||||
var fillEl = document.getElementById('overall-fill');
|
||||
if (overallEl) overallEl.textContent = totalRead + ' из ' + TOTAL + ' параграфов · ' + pct + '%';
|
||||
if (fillEl) fillEl.style.width = pct + '%';
|
||||
|
||||
var xpBadge = document.getElementById('hero-xp-badge');
|
||||
var xp = parseInt(localStorage.getItem('algebra10_xp') || '0', 10) || 0;
|
||||
if (xpBadge && xp > 0) { xpBadge.style.display = ''; xpBadge.textContent = xp + ' XP'; }
|
||||
|
||||
if (totalRead >= TOTAL) {
|
||||
var strip = document.getElementById('ach-strip');
|
||||
var sub = document.getElementById('ach-sub');
|
||||
if (strip) strip.classList.add('lit');
|
||||
if (sub) sub.textContent = 'Выполнено! Вы прочитали весь курс алгебры 10 класса.';
|
||||
}
|
||||
}
|
||||
|
||||
function loadProgress() {
|
||||
if (typeof window.LS === 'undefined' || typeof window.LS.api !== 'function') {
|
||||
renderProgress([]);
|
||||
return;
|
||||
}
|
||||
window.LS.api('/api/textbooks/algebra-10/children')
|
||||
.then(function(data) {
|
||||
if (data && data.children) renderProgress(data.children);
|
||||
else renderProgress([]);
|
||||
})
|
||||
.catch(function() { renderProgress([]); });
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadProgress);
|
||||
} else {
|
||||
loadProgress();
|
||||
}
|
||||
window.addEventListener('focus', loadProgress);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user