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([]);