Files
Learn_System/backend/scripts/gen_geom10_stubs.js
Maxim Dolgolyov 0284611263 feat(geom10 W0): инфра — миграция БД, stereo3d.js, hub + 4 stub-раздела
- Миграция 027: textbooks hub geometry-10 + 4 ребёнка (r1 blue, r2 emerald, r3 rose, r4 amber)
- frontend/js/stereo3d.js: библиотека 3D-проекций (Scene, CABINET/ISOMETRIC, cube/box/prism/pyramid/tetrahedron/plane/arrow/angle, авто-видимость рёбер)
- geometry_10_hub.html: 4 карточки разделов, общий прогресс, превью 4 тел через stereo3d
- 4 stub-файла разделов (r1-r4) с list параграфов и плашкой 'в разработке'
- backend/scripts/gen_geom10_stubs.js: генератор stub-файлов
2026-05-29 14:37:07 +03:00

225 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
'use strict';
// Генератор stub-файлов разделов Геометрии 10. W0.
// Запуск: node backend/scripts/gen_geom10_stubs.js
const fs = require('fs');
const path = require('path');
const sections = [
{ file:'geometry_10_r1.html', num:1, slug:'geometry-10-r1',
title:'Введение в стереометрию',
sub:'Пространственные фигуры · Аксиомы · Сечения',
range:'§1–§3 + Финал', wm:'△',
primary:'#2563eb', primaryD:'#1d4ed8', soft:'#dbeafe', dark:'#1e3a8a',
paras:[
{ n:1, title:'Пространственные фигуры',
sub:'Призма, пирамида, цилиндр, конус, шар. Грани, рёбра, вершины.' },
{ n:2, title:'Прямые и плоскости',
sub:'Аксиомы стереометрии (A1–A3) и их следствия. 4 способа задания плоскости.' },
{ n:3, title:'Построения сечений',
sub:'Метод следов. Сечения куба, призмы, пирамиды.' }
] },
{ file:'geometry_10_r2.html', num:2, slug:'geometry-10-r2',
title:'Параллельность',
sub:'Прямые · Прямая и плоскость · Плоскости',
range:'§4–§6 + Финал', wm:'∥',
primary:'#059669', primaryD:'#047857', soft:'#d1fae5', dark:'#064e3b',
paras:[
{ n:4, title:'Расположение прямых в пространстве',
sub:'Пересекающиеся, параллельные, скрещивающиеся прямые. Угол между скрещивающимися.' },
{ n:5, title:'Прямая и плоскость',
sub:'Три случая. Признак параллельности прямой и плоскости.' },
{ n:6, title:'Две плоскости',
sub:'Пересекаются или параллельны. Признак параллельности плоскостей.' }
] },
{ file:'geometry_10_r3.html', num:3, slug:'geometry-10-r3',
title:'Перпендикулярность',
sub:'Прямая ⊥ плоскость · Расстояния · Углы · Двугранный угол',
range:'§7–§10 + Финал', wm:'⊥',
primary:'#e11d48', primaryD:'#be123c', soft:'#fee2e2', dark:'#7f1d1d',
paras:[
{ n:7, title:'Перпендикулярность прямой и плоскости',
sub:'Определение, признак перпендикулярности.' },
{ n:8, title:'Расстояния',
sub:'От точки до плоскости, между параллельными плоскостями, между скрещивающимися.' },
{ n:9, title:'Угол между прямой и плоскостью',
sub:'Наклонная и её проекция. Теорема о трёх перпендикулярах.' },
{ n:10, title:'Перпендикулярность плоскостей',
sub:'Двугранный угол. Признак перпендикулярности плоскостей.' }
] },
{ file:'geometry_10_r4.html', num:4, slug:'geometry-10-r4',
title:'Координаты и векторы',
sub:'ПДСК в пространстве · Векторы · Скалярное произведение',
range:'§11–§14 + Финал', wm:'→',
primary:'#d97706', primaryD:'#b45309', soft:'#fef3c7', dark:'#78350f',
paras:[
{ n:11, title:'Координаты в пространстве',
sub:'ПДСК. Точка (x; y; z). Расстояние между точками.' },
{ n:12, title:'Вектор. Действия над векторами',
sub:'Сложение, умножение на число, базис i, j, k. Разложение вектора.' },
{ n:13, title:'Скалярное произведение',
sub:'a · b = |a||b|cos α = x₁x₂ + y₁y₂ + z₁z₂. Условие перпендикулярности.' },
{ n:14, title:'Применение координат и векторов',
sub:'Уравнения, углы, расстояния. Векторно-координатный метод.' }
] }
];
function html(s){
const parasHtml = s.paras.map(p => `
<article class="para-card" data-para="${p.n}">
<div class="para-num">§ ${p.n}</div>
<div class="para-body">
<h2 class="para-title">${p.title}</h2>
<p class="para-sub">${p.sub}</p>
<div class="para-status">
<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
Будет добавлено в следующей волне реализации
</div>
</div>
</article>`).join('\n');
return `<!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>Геометрия 10 · ${s.title}</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>
<script src="/js/stereo3d.js?v=1" defer></script>
<style>
:root{
--bg:#f8fafc; --card:#fff;
--text:#0f172a; --muted:#475569;
--border:#e2e8f0;
--pri:${s.primary}; --pri-d:${s.primaryD};
--pri-soft:${s.soft};
--dark:${s.dark};
--sh:0 4px 16px rgba(0,0,0,.06);
}
html.dark{
--bg:#020617; --card:#0a1929;
--text:#dbeafe; --muted:#94a3b8;
--border:#1e293b;
}
*{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,var(--dark) 0%,var(--pri) 55%,var(--pri-soft) 100%);color:#fff;padding:32px 24px 28px;overflow:hidden}
.hdr::before{content:'${s.wm}';position:absolute;right:8px;top:-20%;font-family:'Outfit',sans-serif;font-size:clamp(8rem,22vw,18rem);font-weight:900;color:rgba(255,255,255,.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.7rem;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:980px;margin:0 auto;padding:32px 24px 60px}
.intro-card{background:var(--card);border:1.5px solid var(--border);border-radius:16px;padding:22px 26px;margin-bottom:28px;box-shadow:var(--sh)}
.intro-num{display:inline-block;padding:4px 10px;background:var(--pri-soft);color:var(--pri-d);border-radius:99px;font-size:.72rem;font-weight:800;letter-spacing:.06em;margin-bottom:8px;text-transform:uppercase}
.intro-card h2{font-family:'Outfit',sans-serif;font-size:1.4rem;font-weight:800;margin-bottom:6px}
.intro-card p{color:var(--muted);font-size:.95rem}
.para-grid{display:grid;grid-template-columns:1fr;gap:14px}
.para-card{background:var(--card);border:1.5px solid var(--border);border-radius:14px;padding:18px 20px;display:flex;gap:16px;align-items:flex-start;transition:transform .15s,box-shadow .15s,border-color .15s}
.para-card:hover{transform:translateY(-2px);box-shadow:var(--sh);border-color:var(--pri)}
.para-num{font-family:'Outfit',sans-serif;font-size:1rem;font-weight:900;color:#fff;background:linear-gradient(135deg,var(--pri),var(--pri-d));padding:8px 12px;border-radius:9px;min-width:56px;text-align:center;letter-spacing:-.02em;flex-shrink:0}
.para-body{flex:1}
.para-title{font-family:'Outfit',sans-serif;font-size:1.05rem;font-weight:800;margin-bottom:4px;color:var(--text)}
.para-sub{font-size:.88rem;color:var(--muted);margin-bottom:10px;line-height:1.55}
.para-status{display:inline-flex;align-items:center;gap:6px;font-size:.78rem;color:var(--muted);background:rgba(0,0,0,.04);padding:6px 10px;border-radius:8px;font-weight:600}
html.dark .para-status{background:rgba(255,255,255,.06)}
.para-status .ic{width:14px;height:14px}
.banner-soon{margin-top:30px;text-align:center;padding:20px;background:linear-gradient(135deg,var(--pri-soft),transparent);border:1px dashed var(--pri);border-radius:14px;color:var(--pri-d);font-weight:700;font-size:.92rem}
.banner-soon b{font-family:'Outfit',sans-serif}
.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="/textbook/geometry-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>Раздел ${s.num}. ${s.title}</h1>
<div class="hdr-sub">${s.sub} · ${s.range}</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>
<div class="intro-card">
<span class="intro-num">Раздел ${s.num}</span>
<h2>${s.title}</h2>
<p>${s.sub}. Раздел содержит ${s.paras.length} параграф${s.paras.length===1?'':(s.paras.length<5?'а':'ов')} и финальный этап с боссами.</p>
</div>
<div class="para-grid">
${parasHtml}
</div>
<div class="banner-soon">
<b>Раздел в разработке.</b> Полная реализация — в следующих волнах. Уже доступна 3D-библиотека <code>stereo3d.js</code>.
</div>
</main>
<footer class="foot">
Геометрия — 10 класс · Раздел ${s.num} · LearnSpace
</footer>
<script>
'use strict';
(function(){
var saved = localStorage.getItem('geometry10_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('geometry10_theme', dark ? 'dark' : 'light');
localStorage.setItem('theme', dark ? 'dark' : 'light');
if (lab) lab.textContent = dark ? 'Светлая' : 'Тёмная';
});
})();
</script>
</body>
</html>
`;
}
const outDir = path.join(__dirname, '..', '..', 'frontend', 'textbooks');
for (const s of sections){
const fp = path.join(outDir, s.file);
fs.writeFileSync(fp, html(s), 'utf8');
console.log('Wrote:', fp);
}
console.log('Done.');