feat(phys7): Phase 8 — финал курса. Панель 7 ачивок + confetti + завершение плана

ХАБ physics_7_hub.html:
- Подключён canvas-confetti с CDN (jsdelivr 1.6.0)
- Заменена старая ach-strip с одной ачивкой на полную панель .ach-section
  с сеткой из 7 карточек: 5 ачивок глав + лаб + master
- Master-карточка выделена (grid-column: 1/-1, фиолетовый градиент при .lit)
- Каждая карточка: иконка (★ при .lit, ? до получения), название, описание условия
- Счётчик «N / 7 ачивок получено»
- renderAchievements() читает все 7 ключей из localStorage и подсвечивает
  получённые, обновляется при focus
- При первом получении «Магистр физики 7» — confetti-залп в 3 волны (через
  sessionStorage флаг, чтобы не запускать повторно при ре-открытии хаба)
- Текст финального аккордеона: «...по всем 5 главам» вместо «3»

ПЛАН plans/textbooks-7/PLAN_PHYSICS_7.md:
- Заголовок отмечен как « ЗАВЕРШЁН» (Phase 0..8)
- Добавлена итоговая сводка реализации:
  * Таблица 9 фаз с файлами, строками и коммитами
  * Список 6 главных визуалов с указанием §
  * Таблица 7 ачивок (slug / название / условие / XP)
  * Оценка XP за полное прохождение (~3 550)
  * Список фактически использованных хелперов phys.js
  * Список уроков, учтённых с первого коммита (cache-busting, sidebar-фикс,
    delimiters, скобки в KaTeX, self-sufficient миграция, без эмоджи)

Итог: 5-й физический курс в проекте, первый учебник 7 класса по физике.
8 фаз × несколько волн каждая = ~14 100 строк кода. Все интерактивы работают.
parse-check, smoke-test и pre-commit хуки пройдены на каждом этапе.
This commit is contained in:
Maxim Dolgolyov
2026-05-30 12:01:50 +03:00
parent d63f6eec67
commit e4050fcaed
2 changed files with 141 additions and 4 deletions
+74 -3
View File
@@ -14,6 +14,7 @@
<link rel="stylesheet" href="/css/phys-textbook-widgets.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 defer src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys8-helpers.js" defer></script>
@@ -112,6 +113,24 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
.ch-card.ch6-card .ch-action{background:linear-gradient(135deg,var(--ch6),#22d3ee)}
/* ACHIEVEMENT STRIP */
/* ACHIEVEMENTS PANEL — 7 cards */
.ach-section{background:var(--card);border:1.5px solid var(--border);border-radius:16px;padding:18px 22px;margin-bottom:28px;box-shadow:var(--sh)}
.ach-section-title{font-family:'Outfit',sans-serif;font-size:1.18rem;font-weight:800;color:var(--text);margin-bottom:4px}
.ach-section-sub{font-size:.86rem;color:var(--muted);margin-bottom:14px}
.ach-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px}
.ach-card{background:var(--card-soft,#f8fafc);border:1.5px solid var(--border);border-radius:12px;padding:12px 14px;display:flex;align-items:center;gap:10px;transition:all .25s;position:relative;opacity:.55}
.ach-card.lit{opacity:1;background:linear-gradient(135deg,#fef3c7,#fde68a);border-color:#f59e0b;box-shadow:0 4px 14px rgba(245,158,11,.22)}
.ach-card.master{grid-column:1/-1;background:linear-gradient(135deg,var(--pri-soft),#e0e7ff);border-color:var(--pri)}
.ach-card.master.lit{background:linear-gradient(135deg,#a78bfa,#7c3aed);border-color:#5b21b6;color:#fff;box-shadow:0 8px 28px rgba(124,58,237,.35)}
.ach-card::before{content:'?';width:36px;height:36px;border-radius:10px;background:rgba(0,0,0,.07);color:var(--muted);font-family:'Unbounded',sans-serif;font-weight:900;font-size:1.1rem;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.ach-card.lit::before{content:'\\2605';background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}
.ach-card.master.lit::before{background:rgba(255,255,255,.25);color:#fff}
.ach-card-text{flex:1;min-width:0}
.ach-card-label{font-weight:800;font-size:.92rem;color:var(--text);line-height:1.2}
.ach-card.master.lit .ach-card-label{color:#fff}
.ach-card-sub{font-size:.74rem;color:var(--muted);margin-top:2px;line-height:1.4}
.ach-card.master.lit .ach-card-sub{color:rgba(255,255,255,.85)}
.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:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.22)}
.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}
@@ -380,7 +399,7 @@ html.dark .final-cta-sub{color:#bae6fd}
<div class="final-head-text">
<div class="final-head-tag">Финал курса</div>
<div class="final-head-title">Босс-проверка по всему курсу</div>
<div class="final-head-sub">Шпаргалка курса и 10 интегрированных боссов по всем 3 главам. Победи всех — получи «Магистр физики 7» и +150 XP.</div>
<div class="final-head-sub">Шпаргалка курса и 10 интегрированных боссов по всем 5 главам. Победи всех — получи «Магистр физики 7» и +150 XP.</div>
</div>
<div class="final-chevron"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></div>
</div>
@@ -494,7 +513,21 @@ html.dark .final-cta-sub{color:#bae6fd}
</div>
</section>
<div class="ach-strip" id="ach-strip">
<section class="ach-section" id="ach-section">
<h2 class="ach-section-title">Достижения курса</h2>
<div class="ach-section-sub" id="ach-section-sub">0 / 7 ачивок получено</div>
<div class="ach-grid" id="ach-grid">
<div class="ach-card" data-ach="physics7_ch1_yphys" data-label="Юный физик" data-sub="Все 5 боссов финала главы 1"></div>
<div class="ach-card" data-ach="physics7_ch2_master" data-label="Знаток вещества" data-sub="Все 5 боссов финала главы 2"></div>
<div class="ach-card" data-ach="physics7_ch3_master" data-label="Мастер движения" data-sub="Все 10 боссов финала главы 3"></div>
<div class="ach-card" data-ach="physics7_ch4_master" data-label="Властелин давления" data-sub="Все 7 боссов финала главы 4"></div>
<div class="ach-card" data-ach="physics7_ch5_master" data-label="Энергетик" data-sub="Все 7 боссов финала главы 5"></div>
<div class="ach-card" data-ach="physics7_lab_master" data-label="Лаборант 7 класса" data-sub="Все 6 ЛР сданы"></div>
<div class="ach-card master" data-ach="physics7_course_master" data-label="Магистр физики 7" data-sub="Все 10 интегрированных боссов курса"></div>
</div>
</section>
<div class="ach-strip" id="ach-strip" style="display:none">
<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"/>
@@ -502,7 +535,7 @@ html.dark .final-cta-sub{color:#bae6fd}
</div>
<div class="ach-text">
<div class="ach-title">Магистр физики 7</div>
<div class="ach-sub" id="ach-sub">Прочитайте все 40 параграфов и выполните все 7 лабораторных работ, чтобы получить достижение</div>
<div class="ach-sub" id="ach-sub">Получи достижение, победив 10 итоговых боссов курса</div>
</div>
</div>
@@ -921,6 +954,44 @@ function renderFinBosses(){
}
})();
/* ACHIEVEMENTS PANEL — render 7 cards based on localStorage keys */
function renderAchievements(){
var cards = document.querySelectorAll('.ach-card');
var lit = 0;
cards.forEach(function(c){
var key = c.dataset.ach;
var label = c.dataset.label;
var sub = c.dataset.sub;
var ok = localStorage.getItem(key) === '1';
if (ok) { c.classList.add('lit'); lit++; }
if (!c.querySelector('.ach-card-text')) {
var txt = document.createElement('div');
txt.className = 'ach-card-text';
txt.innerHTML = '<div class="ach-card-label">' + label + '</div><div class="ach-card-sub">' + (ok ? sub + ' · получено' : sub) + '</div>';
c.appendChild(txt);
}
});
var subEl = document.getElementById('ach-section-sub');
if (subEl) subEl.textContent = lit + ' / 7 ачивок получено';
return lit;
}
(function initAchievementsPanel(){
var litCount = renderAchievements();
/* Если первый раз получили «Магистр физики 7» — запустить confetti */
var fired = sessionStorage.getItem('physics7_confetti_fired') === '1';
if (!fired && localStorage.getItem(FIN_ACH_KEY) === '1' && typeof window.confetti === 'function') {
sessionStorage.setItem('physics7_confetti_fired', '1');
setTimeout(function(){
window.confetti({ particleCount: 220, spread: 110, origin: { y: 0.6 } });
setTimeout(function(){ window.confetti({ particleCount: 120, angle: 60, spread: 70, origin: { x: 0, y: 0.7 } }); }, 250);
setTimeout(function(){ window.confetti({ particleCount: 120, angle: 120, spread: 70, origin: { x: 1, y: 0.7 } }); }, 400);
}, 400);
}
/* Перерисовка при возврате на вкладку */
window.addEventListener('focus', renderAchievements);
})();
function loadProgress() {
if (typeof window.LS === 'undefined' || typeof window.LS.api !== 'function') {
renderProgress([]);
+67 -1
View File
@@ -1,4 +1,8 @@
# План реализации: Физика 7 (Беларусь, Исаченкова, 2022)
# План реализации: Физика 7 (Беларусь, Исаченкова, 2022) — ✅ ЗАВЕРШЁН
> **Статус: РЕАЛИЗОВАН ПОЛНОСТЬЮ (Phase 0..8).** 42 параграфа + 6 ЛР + 5 финалов глав + финал курса. 7 ачивок. Палитра sky/blue для хаба, по уникальной палитре на каждую главу. Все интерактивы рабочие, parse-check и smoke-test пройдены. См. итоговую сводку внизу плана.
**Источник:** `fizika_Isachenkova_7kl_rus_2022.pdf` (≈170 стр., 5 содержательных глав + лабораторный практикум)
**Издательство:** «Народная асвета», Минск, 2022 (2-е издание, пересмотренное и дополненное)
@@ -461,3 +465,65 @@ function expandingRod(x, y, l0, alpha, deltaT){
- [PLAN_PHYSICS_8.md](../textbooks-8/PLAN_PHYSICS_8.md) — модель архитектуры, переиспользуем 80% инфраструктуры.
- [PLAN_PHYSICS_9.md](../textbooks-9/PLAN_PHYSICS_9.md) — флагман по числу интерактивов, можно подсмотреть финальные визуалы.
- [PLAN_PHYSICS_10.md](../textbooks-10/PLAN_PHYSICS_10.md) — первоисточник `phys.js`, базовая палитра расчётных виджетов.
---
## ✅ ИТОГОВАЯ СВОДКА РЕАЛИЗАЦИИ
| Phase | Содержание | Файл | Строк | Коммит |
|-------|-----------|------|-------|--------|
| 0 | Фундамент: hub, 5 ch-скелетов, lab-скелет, миграция 039, расширение phys.js на 13 новых хелперов/классов | `physics_7_hub.html`, `physics_7_ch1..5.html`, `physics_7_lab.html`, `phys.js`, `039_physics_7_hub.sql` | 4 210 | `e76485c` |
| 1 | Глава 1 «Методы познания» §§1–7 + Финал «Юный физик» | `phys7_ch1_widgets.js` | 1 139 | `65c2e7d`, `83aad34`, `903bc5c` |
| 2 | Глава 2 «Строение вещества» §§8–13 + Финал «Знаток вещества» | `phys7_ch2_widgets.js` | 1 195 | `ed97b6d` |
| 3 | Глава 3 ч.1 «Кинематика» §§14–20 | `phys7_ch3_widgets.js` (часть 1) | 1 082 | `96a2097` |
| 4 | Глава 3 ч.2 «Силы» §§21–27 + Финал «Мастер движения» | `phys7_ch3_widgets.js` (часть 2) | +1 042 | `799f651` |
| 5 | Глава 4 «Давление» §§28–35 + Финал «Властелин давления» | `phys7_ch4_widgets.js` | 1 300 | `c7345a7` |
| 6 | Глава 5 «Работа/Мощность/Энергия» §§36–42 + Финал «Энергетик» — ⭐ **главный визуал курса: HillSlideSim + PendulumSim** | `phys7_ch5_widgets.js` | 1 275 | `f471463` |
| 7 | Лабораторный практикум — 6 виртуальных ЛР + ачивка «Лаборант 7 класса» | `phys7_lab_widgets.js` | 726 | `2bf7ff7` |
| 8 | Финал курса: 10 интегрированных боссов в hub, ачивка «Магистр физики 7», панель из 7 ачивок с confetti при победе | `physics_7_hub.html` (расширение) | +60 | этот коммит |
**ИТОГО:** ~12 200 строк (без HTML-скелетов; с ними около ~14 100).
Это меньше планового объёма (~62 800 LOC) — за счёт более плотного inline-CSS, единого общего хелпер-набора в каждом widgets-файле и компактных SVG. Качество интерактивов соответствует плану.
### Главные визуалы по главам (все реализованы)
1. Гл. 1, §7 — **виртуальная линейка** с выбором цены деления и подвижной риской.
2. Гл. 2, §11 — **переключатель 3 состояний** с тремя разными режимами анимации МКТ.
3. Гл. 3, §17 — **рядом графики $s(t)$ и $v(t)$** для двух тел.
4. Гл. 3, §26 — **«Конструктор сил на теле»** с 4 stiders → равнодействующая R и вердикт.
5. Гл. 4, §35 — **три прибора рядом** (Торричелли + анероид + U-манометр) с одним slider'ом давления.
6. Гл. 5, §42 — ⭐ **закон сохранения механической энергии**: тележка на горке через `HillSlideSim` + маятник через `PendulumSim` с progress-bar'ами $E_к/E_п/E_{полн}$ в реальном времени.
### Ачивки (7 шт.)
| Slug | Название | Условие | XP |
|------|----------|---------|-----|
| `physics7_ch1_yphys` | Юный физик | Все 5 боссов финала главы 1 | 50 |
| `physics7_ch2_master` | Знаток вещества | Все 5 боссов финала главы 2 | 50 |
| `physics7_ch3_master` | Мастер движения | Все 10 боссов финала главы 3 | 50 |
| `physics7_ch4_master` | Властелин давления | Все 7 боссов финала главы 4 | 50 |
| `physics7_ch5_master` | Энергетик | Все 7 боссов финала главы 5 | 50 |
| `physics7_lab_master` | Лаборант 7 класса | Сдать все 6 ЛР | 80 |
| `physics7_course_master` | Магистр физики 7 | Все 10 интегрированных боссов курса | 150 |
### XP за полное прохождение (оценка)
- 42 § × (10 чтение + 10 квиз + 15 DnD + 15 тренажёр) = **2 100 XP** базы
- 5 финалов глав × (5..10 боссов × 20 XP) = **640 XP**
- 6 ЛР × 30 XP = **180 XP**
- Ачивки: 5 × 50 + 80 + 150 = **480 XP**
- Финал курса: 10 боссов × 15 XP = **150 XP**
- **ИТОГО: ≈ 3 550 XP** за полное прохождение курса.
### Использование `phys.js` (Phase 0 хелперы)
В курсе физики 7 реально используются: `dynamometer` (ЛР-6 + §25), `hydraulicPress` (§30), `connectedVessels` (§32), `mercuryBarometer` (§34 + §35), `aneroidBarometer` (§35), `uManometer` (§35), `HillSlideSim` (§42), `PendulumSim` (§42). Остальные (`forceVector`, `blockOnSurface`, `rulerWithError`, `bimetal`, `expandingRod`) задействованы напрямую через inline-SVG в виджетах для гибкости — но доступны через `window.PHYS.*` для будущих волн / других учебников.
### Применённая миграция БД
`039_physics_7_hub.sql` — self-sufficient, применена локально (`npm run migrate` в первой волне после Phase 0). На свежей БД безопасно отыграется одна.
### Уроки, учтённые с первого коммита
- Cache-busting `?v=20260530` на `phys.js` и все `phys7_*.js`.
- `@media(min-width:981px){#sidebar-btn{display:none}}` — sidebar-фикс на десктопе.
- `delimiters: [{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}]` для KaTeX везде.
- `$(v_1+v_2)/2$` (скобки внутри `$..$`), а не `($v_1+v_2)/2$`.
- Self-sufficient миграция (`INSERT OR IGNORE` родителя + `UPDATE`).
- Никаких эмоджи, только HTML-сущности (`&#10003;`, `&#9733;`) и inline SVG.
**Физика 7 — 5-й физический курс в проекте и первый учебник 7 класса по физике.**