Files
Maxim Dolgolyov 660e7e2747 feat(gamification): Phase 1 — full kill-switch + textbook XP wrapping
Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.

Phase 1 closes every hole.

Backend (source of truth):
  • migration 029 seeds feature_gamification_enabled=1
  • new isGamificationEnabled() helper in gamification/_shared.js with a
    30s cache + invalidateGamificationCache() for instant admin toggles
  • awardXP / awardCoins / updateStreak / unlockAchievement /
    checkAchievements all bail out when the flag is off
  • /api/gamification/* and /api/shop/* (user routes) return 404 when
    disabled; admin routes remain open so the switch itself is reachable
  • adminController.updateFeatures gains 'gamification' in the allow-list
    and invalidates the cache on flip

Frontend:
  • LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
    so xp.js + applyCosmetics can bail without a round-trip
  • xp.js load/add/flush become no-ops when the flag is off
  • applyCosmetics skips the round-trip when off
  • CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
    .xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
    hook for future blocks

Textbooks (Variant 2 of the plan):
  • backend/scripts/wrap_textbook_xp.py — idempotent script that adds
    data-gamified to 167 XP tags across 63 textbook files (chapters +
    hubs, all subjects/grades). Single CSS rule now hides everything.

Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:43:24 +03:00

563 lines
42 KiB
HTML
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.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<title>Физика 11 · Глава 8 · «Картина мира + Финал курса»</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<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"
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys-fx.js?v=1"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fafafa; --card:#fff; --card-soft:#f8fafc; --text:#0f172a; --ink:#0f172a; --muted:#64748b;
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
--pri:#4f46e5; --pri2:#3730a3; --pri-soft:#e0e7ff;
--acc:#6366f1; --acc2:#312e81; --acc-soft:#c7d2fe;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a1f; --card:#13132c; --card-soft:#1a1a35; --text:#e0e7ff; --ink:#e0e7ff; --muted:#a5b4fc; --border:#3730a3}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
button,input,select,textarea{font-family:inherit;font-size:inherit}
button{cursor:pointer;border:0;background:transparent;color:inherit}
a{color:inherit;text-decoration:none}
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
.hdr{position:relative;background:linear-gradient(110deg,#312e81 0%,#4f46e5 55%,#c7d2fe 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(199,210,254,.2);min-height:130px}
.hdr::before{content:'\221E';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,255,255,.12);line-height:1;pointer-events:none;user-select:none;z-index:0}
.hdr-row{position:relative;z-index:1;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.hdr h1{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:900;letter-spacing:-.01em;line-height:1.3;padding-top:4px}
.hdr-sub{font-size:.85rem;opacity:.88;margin-top:6px;font-weight:500;line-height:1.4}
.hdr-side{margin-left:auto;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hdr-btn{padding:7px 12px;border-radius:9px;background:rgba(255,255,255,.14);color:#fff;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;text-decoration:none}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
.main{max-width:1240px;margin:0 auto;padding:22px;width:100%;display:grid;grid-template-columns:1fr 280px;gap:24px}
@media(max-width:980px){.main{grid-template-columns:1fr;padding:14px}}
.col-main{min-width:0}
.hero{background:linear-gradient(135deg,var(--pri-soft) 0%,var(--acc-soft) 50%,var(--pri-soft) 100%);background-size:200% 200%;animation:heroShift 12s ease-in-out infinite;border:1px solid var(--border);border-radius:18px;padding:24px 22px;margin-bottom:24px;position:relative;overflow:hidden}
@keyframes heroShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.hero::before{content:'\221E';position:absolute;right:0;top:-30px;font-size:clamp(2rem,12vw,8rem);font-weight:900;color:var(--pri);opacity:.10;line-height:1;pointer-events:none;font-family:'Unbounded',sans-serif}
.hero h2{font-family:'Unbounded',sans-serif;font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(0,0,0,.18)}
.hero-progress{flex:1;min-width:200px;max-width:280px}
.hp-label{font-size:.74rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;display:block;margin-bottom:5px}
.hp-bar{height:8px;background:rgba(0,0,0,.12);border-radius:5px;overflow:hidden}
.hp-fill{height:100%;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:5px;width:0%;transition:width .6s cubic-bezier(.16,1,.3,1)}
.hp-text{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:4px;display:block}
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(0,0,0,.18);font-family:'Unbounded',sans-serif}
.sec{display:block;position:relative;animation:fadeIn .35s ease}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--pri-soft);line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.45}
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--pri-soft);position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.5rem;font-weight:800;color:var(--pri2);letter-spacing:-.01em;line-height:1.25}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.04),0 8px 24px rgba(0,0,0,.04);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
.card:hover{transform:translateY(-2px);box-shadow:0 4px 10px rgba(0,0,0,.06),0 16px 36px rgba(0,0,0,.08)}
.card-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed var(--border)}
.card-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff}
.card-icon.repeat{background:#0ea5e9}.card-icon.theory{background:#8b5cf6}.card-icon.algo{background:#f59e0b}.card-icon.rule{background:#4f46e5}.card-icon.example{background:#10b981}
.card-icon .ic{width:18px;height:18px}
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--pri-soft);padding:3px 7px;border-radius:5px}
.card-body{font-size:.94rem;line-height:1.65}
.card-body p{margin-bottom:8px}
.card-body p:last-child{margin-bottom:0}
.card-body ul,.card-body ol{margin:6px 0 6px 22px;line-height:1.7}
.btn{padding:8px 16px;border-radius:8px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:600;font-size:.88rem;transition:background .15s,border-color .15s,transform .1s}
.btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.btn.primary{background:var(--pri);color:#fff;border-color:var(--pri)}
.btn.primary:hover{background:var(--pri2);border-color:var(--pri2)}
.feedback{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none}
.feedback.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.feedback.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.col-side{position:sticky;top:14px;align-self:start;height:fit-content;max-height:calc(100vh - 28px);overflow-y:auto}
.sidecard{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;box-shadow:var(--sh)}
.sidecard h4{font-family:'Unbounded',sans-serif;font-size:.74rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.07em;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}
.sidecard-row{margin-bottom:8px;font-size:.84rem;line-height:1.55}
.sidecard-row b{color:var(--pri);font-weight:700}
@media(max-width:980px){.col-side{position:static;max-height:none}}
.xp-card{background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border:1.5px solid var(--warn);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--warn);text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between}
.xp-level{font-size:1.1rem;font-weight:900;color:#3730a3;font-family:'Unbounded',sans-serif}
.xp-bar{height:9px;background:rgba(0,0,0,.10);border-radius:6px;overflow:hidden;margin:7px 0}
.xp-fill{height:100%;background:linear-gradient(90deg,var(--warn),var(--pri));border-radius:6px;transition:width .5s cubic-bezier(.4,0,.2,1)}
.xp-nums{font-size:.74rem;color:var(--muted);display:flex;justify-content:space-between}
.opts-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.opt-btn{padding:8px 14px;background:var(--card);border:1.5px solid var(--border);border-radius:9px;font-weight:700;font-size:.88rem;color:var(--text);cursor:pointer;transition:all .15s}
.opt-btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.opt-btn.correct{background:var(--ok-bg);border-color:var(--ok);color:#065f46}
.opt-btn.wrong{background:var(--fail-bg);border-color:var(--fail);color:#991b1b}
.foot{text-align:center;padding:30px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border);margin-top:30px}
.ach-popup{position:fixed;top:80px;right:18px;background:linear-gradient(135deg,var(--pri),var(--warn));color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
.ach-popup.show{display:flex}
.boss-card{background:var(--card);border:2px solid var(--border);border-radius:14px;padding:16px;margin-bottom:14px;transition:border-color .35s,box-shadow .35s,transform .2s}
.boss-card.solved{border-color:#10b981;box-shadow:0 0 0 3px rgba(16,185,129,.18)}
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap}
.boss-tag{font-size:.7rem;font-weight:800;padding:3px 9px;border-radius:99px;background:var(--pri-soft);color:var(--pri2);letter-spacing:.04em;text-transform:uppercase}
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:var(--text);font-size:1.02rem;flex:1;min-width:0}
.boss-q{padding:12px 14px;background:var(--pri-soft);border-radius:10px;font-size:.96rem;line-height:1.55;margin-bottom:10px;color:var(--text)}
.boss-input{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'JetBrains Mono',monospace;width:140px;text-align:center;font-size:.95rem}
.boss-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.boss-fb{padding:10px 14px;border-radius:9px;font-weight:600;font-size:.88rem;margin-top:8px;display:none;line-height:1.45}
.boss-fb.ok{display:block;background:var(--ok-bg);color:#065f46;border-left:4px solid var(--ok)}
.boss-fb.fail{display:block;background:var(--fail-bg);color:#7f1d1d;border-left:4px solid var(--fail)}
.master-trophy{text-align:center;padding:40px 20px;background:linear-gradient(135deg,#fcd34d,#f59e0b);border-radius:18px;border:3px solid #92400e;margin:28px 0;box-shadow:0 8px 32px rgba(245,158,11,.4)}
.master-trophy h2{font-family:'Unbounded',sans-serif;font-size:2.2rem;color:#7c2d12;margin-bottom:12px}
.master-trophy p{color:#7c2d12;font-size:1.05rem;line-height:1.6}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 11 · Глава 8</h1>
<div class="hdr-sub">Основы единой физической картины мира + Финал всего курса</div>
</div>
<div class="hdr-side">
<a href="/textbook/physics-11" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К физике 11</a>
<button id="theme-btn" class="hdr-btn"><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 class="main">
<div class="col-main">
<section class="hero">
<h2>Единая картина мира + ФИНАЛ КУРСА</h2>
<p>Финальный обзор: от Ньютона до Стандартной модели. И главное испытание — <b>12 интегральных боссов</b> по всему курсу физики 11 класса.</p>
<div class="hero-row">
<div class="hero-progress">
<span class="hp-label">Прогресс</span>
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
<span id="hero-hp-text" class="hp-text">0%</span>
</div>
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
</div>
</section>
<section id="sec-p1" class="sec" data-watermark="&#8734;">
<div class="sec-header"><span class="sec-num">§ 45</span><h2 class="sec-h">Современная картина мира</h2></div>
<div id="p1-body"></div>
</section>
<section id="sec-final" class="sec" data-watermark="&#9733;">
<div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#f59e0b,#dc2626);color:#fff">&#9733;</span><h2 class="sec-h">ФИНАЛ КУРСА — 12 интегральных боссов</h2></div>
<div id="final-body"></div>
</section>
</div>
<aside class="col-side"><div id="sidebar-content"></div></aside>
</main>
<footer class="foot">Интерактивный учебник «Физика 11» · Глава 8 · «Картина мира + Финал курса» · LearnSpace</footer>
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
<script>
'use strict';
const STATE = { progress:0, achievements:new Map(), xp:0, level:1 };
const _TB_SLUG = 'physics-11-ch8';
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
start:'Начало главы 8!',
ch8_done:'Глава 8 пройдена!',
phys11_master:'МАГИСТР ФИЗИКИ 11 — пройден весь курс!'
};
function loadProgress(){
try{
STATE.progress=+(localStorage.getItem('physics11_ch8_progress')||0);
const a=localStorage.getItem('physics11_ch8_achievements');
if(a){ const p=JSON.parse(a); if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
STATE.xp=+(localStorage.getItem('physics11_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('physics11_ch8_progress', String(STATE.progress));
localStorage.setItem('physics11_ch8_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('physics11_xp', String(STATE.xp));
}catch(e){}
}
function addXp(n,src){
if(!n) return;
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
saveProgress(); refreshProgressUI();
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'physics11-ch8-'+(src||'misc'));
if(STATE.level>prev){
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
}
}
function refreshProgressUI(){
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=STATE.progress+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=STATE.progress+'% пройдено';
const xpBadge=document.getElementById('hero-xp-badge');
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
buildSidebar();
}
function achievement(id,text){
if(STATE.achievements.has(id)) return;
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
const pop=document.getElementById('ach-popup');
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
addXp(20,'ach-'+id);
}
function buildSidebar(){
const box=document.getElementById('sidebar-content'); if(!box) return;
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>Курс физики 11</h4>'
+ '<div class="sidecard-row"><b>Глав</b> — 8</div>'
+ '<div class="sidecard-row"><b>Параграфов</b> — 45</div>'
+ '<div class="sidecard-row"><b>Боссов</b> — 60+</div>'
+ '<div class="sidecard-row"><b>Финал</b> — 12 интегральных боссов</div>'
+ '<div class="sidecard-row"><b>Цель</b> — Магистр физики 11</div>'
+ '</div>';
if(STATE.achievements.size>0){
html+='<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">'+STATE.achievements.size+'</span></h4>';
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">&#10003; '+text+'</div>'; });
html+='</div>';
}
box.innerHTML=html;
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
}
function initTheme(){
const t=localStorage.getItem('physics11_theme')||localStorage.getItem('theme')||'light';
if(t==='dark') document.documentElement.classList.add('dark');
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
document.getElementById('theme-btn').addEventListener('click', ()=>{
document.documentElement.classList.toggle('dark');
const dark=document.documentElement.classList.contains('dark');
localStorage.setItem('physics11_theme', dark?'dark':'light');
localStorage.setItem('theme', dark?'dark':'light');
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
});
}
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
const ICONS = {
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>'
};
function makeCard(kind, title, num, body){
const labels = {theory:'Теория',rule:'Правило',example:'Пример'};
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
}
function normalizeAns(s){ return String(s||'').toLowerCase().replace(/\s+/g,'').replace(/°/g,'').replace(/sqrt/g,'√').replace(/корень/g,'√').replace(/,/g,'.'); }
function makeBoss(id, def){
const stage = (typeof def.stage === 'number') ? def.stage : 0;
const solved = !!def.solved;
const total = def.stages.length;
const stageObj = def.stages[Math.min(stage, total-1)];
let optsHtml = '';
if(solved) return '<div class="boss-card solved" id="boss-'+id+'"><div class="boss-head"><span class="boss-tag">'+(def.tag||'Босс')+'</span><span class="boss-title">'+def.title+'</span></div><div class="boss-q">Побеждён! +'+def.xp+' XP получены.</div></div>';
if(stageObj.type === 'mc'){
optsHtml = '<div class="opts-row">';
stageObj.opts.forEach((o,i)=>{ optsHtml += '<button class="opt-btn" data-i="'+i+'">'+o+'</button>'; });
optsHtml += '</div>';
} else {
optsHtml = '<div class="boss-row"><input class="boss-input" id="boss-'+id+'-inp" placeholder="ответ"><button class="btn primary" id="boss-'+id+'-go">Атака</button></div>';
}
return '<div class="boss-card" id="boss-'+id+'"><div class="boss-head"><span class="boss-tag">'+(def.tag||'Босс')+'</span><span class="boss-title">'+def.title+' — этап '+(stage+1)+' / '+total+'</span></div><div class="boss-q">'+stageObj.q+'</div>'+optsHtml+'<div class="boss-fb" id="boss-'+id+'-fb"></div></div>';
}
function bindBoss(id, def, state, save, onWin){
const card = document.getElementById('boss-'+id);
if(!card || state.solved) return;
const stageObj = def.stages[state.stage];
const fb = document.getElementById('boss-'+id+'-fb');
function advance(){
state.stage++;
if(state.stage >= def.stages.length){
state.solved = true; save(); addXp(def.xp, 'boss-'+id);
if(onWin) onWin();
} else save();
rebuildBoss(id, def, state, save, onWin);
}
if(stageObj.type === 'mc'){
card.querySelectorAll('.opt-btn').forEach(btn=>{
btn.addEventListener('click', ()=>{
const i = +btn.dataset.i;
if(i === stageObj.correct){
btn.classList.add('correct');
fb.className='feedback ok'; fb.innerHTML='&#10003; Верно. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb);
setTimeout(advance, 700);
} else { btn.classList.add('wrong'); fb.className='feedback fail'; fb.innerHTML='&#10007; Не так. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb); }
});
});
} else {
const inp = document.getElementById('boss-'+id+'-inp');
const go = document.getElementById('boss-'+id+'-go');
function attack(){
const v = normalizeAns(inp.value);
const ans = Array.isArray(stageObj.a) ? stageObj.a.map(normalizeAns) : [normalizeAns(stageObj.a)];
if(ans.indexOf(v) >= 0){ fb.className='feedback ok'; fb.innerHTML='&#10003; Верно! '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb); setTimeout(advance, 600); }
else { fb.className='feedback fail'; fb.innerHTML='&#10007; Не то. '+(stageObj.explain||''); fb.style.display='block'; renderMath(fb); }
}
go.addEventListener('click', attack);
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); attack(); } });
}
}
function rebuildBoss(id, def, state, save, onWin){
const card = document.getElementById('boss-'+id);
if(!card) return;
card.outerHTML = makeBoss(id, Object.assign({}, def, state));
bindBoss(id, def, state, save, onWin);
renderMath(document.getElementById('boss-'+id));
}
function makeAndBindBoss(slotId, id, def, state, save, onWin){
const slot = document.getElementById(slotId); if(!slot) return;
slot.innerHTML = makeBoss(id, Object.assign({}, def, state));
bindBoss(id, def, state, save, onWin);
renderMath(slot);
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('physics11_ch8_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('physics11_ch8_'+key, JSON.stringify(state)); }catch(e){} }
function buildP1(){
const box = document.getElementById('p1-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Эволюция картины мира', '§ 45.1',
'<p>Современная физическая картина мира — это синтез знаний о природе, накопленных за последние 400 лет.</p>'
+ '<ul>'
+ '<li><b>Механика Ньютона</b> (XVII в.) — описание движения тел макромира.</li>'
+ '<li><b>Термодинамика</b> (XIX в.) — энергия, теплота, статистика.</li>'
+ '<li><b>Электродинамика Максвелла</b> (XIX в.) — электромагнетизм + свет как ЭМ-волна.</li>'
+ '<li><b>Специальная теория относительности</b> (1905) — пространство-время при $v \\sim c$.</li>'
+ '<li><b>Общая теория относительности</b> (1915) — гравитация как кривизна пространства-времени.</li>'
+ '<li><b>Квантовая механика</b> (1900-1930) — микромир.</li>'
+ '<li><b>Стандартная модель</b> (1970-е) — единое описание ЭМ, слабого, сильного взаимодействий.</li>'
+ '</ul>');
html += makeCard('rule', 'Иерархия структуры материи', '§ 45.2',
'<p>От малого к большому:</p>'
+ '<ul>'
+ '<li><b>Кварки и лептоны</b> — $\\sim 10^{-18}$ м (предел проверенного).</li>'
+ '<li><b>Нуклоны</b> (протон, нейтрон) — $\\sim 10^{-15}$ м.</li>'
+ '<li><b>Атомные ядра</b> — $\\sim 10^{-14}$ м.</li>'
+ '<li><b>Атомы</b> — $\\sim 10^{-10}$ м.</li>'
+ '<li><b>Молекулы, клетки, организмы</b> — $\\sim 10^{-9} - 10^0$ м.</li>'
+ '<li><b>Планеты</b> — $\\sim 10^7$ м.</li>'
+ '<li><b>Звёзды, галактики</b> — $\\sim 10^{18} - 10^{22}$ м.</li>'
+ '<li><b>Наблюдаемая Вселенная</b> — $\\sim 10^{27}$ м (~ 93 млрд св. лет).</li>'
+ '</ul>');
html += makeCard('example', 'Что мы не знаем (открытые проблемы)', '§ 45.3',
'<p>Современная физика, несмотря на успехи, имеет нерешённые задачи:</p>'
+ '<ul>'
+ '<li><b>Тёмная материя</b> — невидимая, но «весит» в 5 раз больше обычной.</li>'
+ '<li><b>Тёмная энергия</b> — заставляет Вселенную расширяться ускоренно. ~68% всей энергии.</li>'
+ '<li><b>Объединение теорий</b> — пока нет единой теории, включающей и квантовую механику, и гравитацию (СТО+ОТО).</li>'
+ '<li><b>Природа массы</b> — почему частицы имеют именно такие массы?</li>'
+ '<li><b>Жизнь, сознание</b> — как физика связана с этим?</li>'
+ '</ul>'
+ '<p>Физика — самая древняя наука и одновременно самая молодая. Перед вами открыты все вопросы.</p>');
html += '<div style="margin-top:18px;display:flex;justify-content:center"><button class="btn primary" id="p1-read-btn">'
+ '<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
+ ' Я прочитал §45 (+15 XP)</button></div>';
box.innerHTML = html;
const btn = document.getElementById('p1-read-btn');
btn.addEventListener('click', ()=>{
addXp(15, 'p1-read');
STATE.progress = Math.max(STATE.progress, 50); saveProgress(); refreshProgressUI();
btn.textContent='Прочитано! +15 XP'; btn.disabled=true; btn.style.opacity=.6;
achievement('ch8_done');
});
renderMath(box);
}
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="card" style="background:linear-gradient(135deg,#fee2e2,#fca5a5);border-color:#dc2626">'
+ '<div class="card-header"><div class="card-icon rule" style="background:#dc2626;color:#fff">'
+ '<svg class="ic" viewBox="0 0 24 24" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>'
+ '</div><div class="card-title">ФИНАЛ КУРСА ФИЗИКИ 11</div><div class="card-num">★ ★ ★</div></div>'
+ '<div class="card-body">'
+ '<p>Перед тобой — <b>12 интегральных боссов</b>, покрывающих все 8 глав и 45 параграфов курса физики 11 класса.</p>'
+ '<p>Каждый босс — 5 этапов. Победив всех 12 — ты получишь титул <b>МАГИСТР ФИЗИКИ 11</b> и +500 XP бонус.</p>'
+ '<p>Темы: колебания, ЭМ-индукция, оптика, СТО, фотоэффект, атом Бора, лазеры, ядро, радиоактивность, реактор, синтез, элементарные частицы.</p>'
+ '</div></div>';
for (let i = 1; i <= 12; i++){
html += '<div id="boss-final-'+i+'-slot"></div>';
}
html += '<div class="master-trophy" id="master-trophy" style="display:none">'
+ '<h2>МАГИСТР ФИЗИКИ 11</h2>'
+ '<p><b>Все 12 интегральных боссов финала курса побеждены!</b></p>'
+ '<p>Ты прошёл весь курс физики 11 класса: от механических колебаний до элементарных частиц, от классической электродинамики до Стандартной модели.</p>'
+ '<p>Бонус: <b>+500 XP</b>.</p>'
+ '<p style="margin-top:14px;font-size:.95rem">Впереди — высшее образование. Удачи!</p>'
+ '</div>';
box.innerHTML = html;
for (let i = 1; i <= 12; i++){
const key = 'boss-final-' + i;
const bs = loadBossState(key) || { stage:0, solved:false };
makeAndBindBoss(key+'-slot', 'final-'+i, COURSE_FINAL_DEFS['fb'+i], bs,
()=>{ saveBossState(key, bs); checkCourseFinal(); }, ()=>{ checkCourseFinal(); });
}
renderMath(box);
}
function checkCourseFinal(){
let all = true;
for (let i = 1; i <= 12; i++){
const s = loadBossState('boss-final-'+i);
if (!s || !s.solved){ all = false; break; }
}
if (all){
const v = document.getElementById('master-trophy'); if (v) v.style.display = '';
STATE.progress = 100; saveProgress(); refreshProgressUI();
if (!STATE.achievements.has('phys11_master')){
achievement('phys11_master');
addXp(500, 'phys11-master-bonus');
}
}
}
const COURSE_FINAL_DEFS = {
fb1: { title:'Босс I — Колебания (Гл. 1)', tag:'Гл. 1', xp:80, stages:[
{ q:'$T$ матем. маятника:', type:'mc', opts:['$2\\pi\\sqrt{l/g}$','$2\\pi\\sqrt{g/l}$','$\\sqrt{l/g}$','$lg$'], correct:0, explain:'$T = 2\\pi\\sqrt{l/g}$.' },
{ q:'$l = 1$ м, $g = 10$ м/с². $T$ (с) ≈?', type:'input', a:['1.99','2.0','2'], explain:'$\\approx 2$.' },
{ q:'Резонанс — это:', type:'mc', opts:['Расхождение фаз','Совпад. $\\omega$ с собств.','Затухание','Демпфирование'], correct:1, explain:'$\\omega = \\omega_0$.' },
{ q:'Скорость звука в воздухе (м/с) ≈?', type:'input', a:'340', explain:'$\\sim 340$.' },
{ q:'$v$ ЭМ-волны в вакууме = ?', type:'mc', opts:['340 м/с','$c = 3\\cdot 10^8$ м/с','$\\infty$','Завис. от $\\nu$'], correct:1, explain:'$c$.' }
]},
fb2: { title:'Босс II — ЭМ-индукция (Гл. 2)', tag:'Гл. 2', xp:80, stages:[
{ q:'Период LC-контура:', type:'mc', opts:['$2\\pi\\sqrt{LC}$','$\\sqrt{LC}$','$LC$','$1/LC$'], correct:0, explain:'$T = 2\\pi\\sqrt{LC}$.' },
{ q:'Закон Фарадея:', type:'mc', opts:['$E = -d\\Phi/dt$','$E = q/r$','$F = qE$','$\\Phi = BS$'], correct:0, explain:'ЭДС = $-d\\Phi/dt$.' },
{ q:'Трансформатор $k = N_2/N_1$. $U_2 = ?$', type:'mc', opts:['$U_1 k$','$U_1/k$','$U_1 k^2$','$U_1$'], correct:0, explain:'$U_2 = k U_1$.' },
{ q:'$L = 1$ Гн, $C = 1$ мкФ. $\\nu_0$ (Гц) ≈?', type:'input', a:'159', explain:'$1/(2\\pi\\sqrt{LC}) \\approx 159$.' },
{ q:'Спектр видимого света (нм):', type:'mc', opts:['100-400','380-760','760-1000','1-100'], correct:1, explain:'380-760 нм.' }
]},
fb3: { title:'Босс III — Оптика I (Гл. 3 §14-§17)', tag:'Гл. 3', xp:80, stages:[
{ q:'$c$ (м/с)?', type:'input', a:['3e8','3·10⁸','300000000'], explain:'$3 \\cdot 10^8$.' },
{ q:'Условие max интерференции:', type:'mc', opts:['$\\Delta = k\\lambda$','$\\Delta = \\lambda/2$','$\\Delta = 0$','$\\Delta = \\lambda^2$'], correct:0, explain:'$\\Delta = k\\lambda$.' },
{ q:'Формула решётки:', type:'mc', opts:['$d\\sin\\varphi = k\\lambda$','$\\lambda = d\\nu$','$\\sin\\varphi = d$','$d = \\lambda$'], correct:0, explain:'$d\\sin\\varphi = k\\lambda$.' },
{ q:'Закон отражения:', type:'mc', opts:['$\\alpha_п=\\alpha_о$','$\\alpha_п>\\alpha_о$','$\\alpha_п<\\alpha_о$','Не связ.'], correct:0, explain:'Равны.' },
{ q:'Изобр. в плоск. зеркале:', type:'mc', opts:['Действит.','Мнимое прямое равное','Уменьш.','Увелич.'], correct:1, explain:'Мнимое.' }
]},
fb4: { title:'Босс IV — Оптика II (Гл. 3 §18-§23)', tag:'Гл. 3', xp:80, stages:[
{ q:'Закон Снелла:', type:'mc', opts:['$n_1\\sin\\alpha = n_2\\sin\\beta$','$n_1\\alpha = n_2\\beta$','$\\sin\\alpha = n_1 n_2$','$\\cos\\alpha = n$'], correct:0, explain:'$n_1\\sin\\alpha = n_2\\sin\\beta$.' },
{ q:'$F = 0{,}25$ м. $D$ (дптр)?', type:'input', a:'4', explain:'$1/F$.' },
{ q:'Лупа $F = 5$ см. $\\Gamma$?', type:'input', a:'5', explain:'$25/5 = 5$.' },
{ q:'Телескоп $\\Gamma = ?$', type:'mc', opts:['$F_{ок}/F_{об}$','$F_{об}/F_{ок}$','$F_{об}+F_{ок}$','$F_{об}\\cdot F_{ок}$'], correct:1, explain:'$F_{об}/F_{ок}$.' },
{ q:'Оптоволокно использует:', type:'mc', opts:['Дифракцию','ПВО','Поляризацию','Дисперсию'], correct:1, explain:'ПВО.' }
]},
fb5: { title:'Босс V — СТО (Гл. 4)', tag:'Гл. 4', xp:80, stages:[
{ q:'II постулат СТО:', type:'mc', opts:['$c$ const во всех ИСО','$c$ зависит от ИСО','$c = \\infty$','$c$ зависит от источника'], correct:0, explain:'Инвариант. $c$.' },
{ q:'$\\beta = 0{,}6$. $\\gamma$?', type:'input', a:'1.25', explain:'$1/0{,}8$.' },
{ q:'$L_0 = 100$, $\\beta = 0{,}8$. $L$?', type:'input', a:'60', explain:'$L_0 / \\gamma = 60$.' },
{ q:'$E_0 = ?$', type:'mc', opts:['$mv^2/2$','$mc^2$','$mc$','$pc$'], correct:1, explain:'$mc^2$.' },
{ q:'$E^2 = ?$', type:'mc', opts:['$pc + mc^2$','$(pc)^2 + (mc^2)^2$','$mc^4$','$p^2 + m^2$'], correct:1, explain:'Релятив. Пифагор.' }
]},
fb6: { title:'Босс VI — Фотоны (Гл. 5)', tag:'Гл. 5', xp:80, stages:[
{ q:'$E$ фотона = ?', type:'mc', opts:['$mv^2/2$','$h\\nu$','$mc$','$E_0$'], correct:1, explain:'$h\\nu$.' },
{ q:'$\\lambda = 620$ нм. $E$ (эВ)?', type:'input', a:'2', explain:'$1240/620$.' },
{ q:'Уравнение Эйнштейна:', type:'mc', opts:['$h\\nu = A + E_к$','$h\\nu = mc^2$','$E = pc$','$\\lambda = h/p$'], correct:0, explain:'Ф/э.' },
{ q:'$p$ фотона = ?', type:'mc', opts:['$mv$','$h\\nu/c$','$h\\nu$','$E$'], correct:1, explain:'$h/\\lambda$.' },
{ q:'Длина волны де Бройля:', type:'mc', opts:['$\\lambda = h/p$','$\\lambda = c/\\nu$','$\\lambda = E/p$','$\\lambda = h\\nu$'], correct:0, explain:'$h/p$.' }
]},
fb7: { title:'Босс VII — Атом и спектры (Гл. 6 §30-§32)', tag:'Гл. 6', xp:80, stages:[
{ q:'Опыт Резерфорда (год)?', type:'input', a:'1911', explain:'1911.' },
{ q:'$E_n$ атома H:', type:'mc', opts:['$-13{,}6/n$','$-13{,}6/n^2$','$13{,}6 n^2$','$h\\nu$'], correct:1, explain:'$-13{,}6/n^2$.' },
{ q:'$n=2$. $E$ (эВ)?', type:'input', a:'-3.4', explain:'$-3{,}4$.' },
{ q:'Серия Бальмера на:', type:'input', a:'2', explain:'$m=2$.' },
{ q:'Энергия иониз. H (эВ)?', type:'input', a:'13.6', explain:'$|E_1|$.' }
]},
fb8: { title:'Босс VIII — Лазеры (Гл. 6 §33-§34)', tag:'Гл. 6', xp:80, stages:[
{ q:'Кто ввёл инд. излучение?', type:'mc', opts:['Планк','Бор','Эйнштейн','Резерфорд'], correct:2, explain:'1916.' },
{ q:'Инверсная населённость:', type:'mc', opts:['$N_1 > N_2$','$N_2 > N_1$','$N_1 = N_2$','$N=0$'], correct:1, explain:'$N_2 > N_1$.' },
{ q:'Лазер (год)?', type:'input', a:'1960', explain:'Мейман.' },
{ q:'Свойство лазера:', type:'mc', opts:['Расходится','Когерентен','Полихромат.','Случайная фаза'], correct:1, explain:'Когерент.' },
{ q:'$\\lambda$ рубина (нм)?', type:'input', a:'694', explain:'694.' }
]},
fb9: { title:'Босс IX — Ядро (Гл. 7 §35-§37)', tag:'Гл. 7', xp:80, stages:[
{ q:'Нуклоны это:', type:'mc', opts:['$e^-$','$p + n$','Только $p$','Кварки'], correct:1, explain:'$p$ + $n$.' },
{ q:'${}^{235}_{92}\\text{U}$ — $N$?', type:'input', a:'143', explain:'$235 - 92$.' },
{ q:'1 а.е.м. (МэВ)?', type:'input', a:'931.5', explain:'931,5.' },
{ q:'Дефект массы:', type:'mc', opts:['$m_я > \\sum m_n$','$m_я < \\sum m_n$','$m_я = 0$','$\\sum m_n = 0$'], correct:1, explain:'Меньше.' },
{ q:'Макс. $\\varepsilon$ у:', type:'mc', opts:['H','Fe','U','He'], correct:1, explain:'Fe-56.' }
]},
fb10: { title:'Босс X — Радиоактивность и реактор (Гл. 7 §38-§41)', tag:'Гл. 7', xp:80, stages:[
{ q:'α-частица:', type:'mc', opts:['$e^-$','${}^4\\text{He}$','γ','n'], correct:1, explain:'He-4.' },
{ q:'$N_0 = 800$, $t = 3T$. $N$?', type:'input', a:'100', explain:'$800/8$.' },
{ q:'$k = 1$ — режим:', type:'mc', opts:['Бомба','Стационар','Затухание','Пуск'], correct:1, explain:'Реактор.' },
{ q:'Делящееся:', type:'mc', opts:['U-238','U-235, Pu-239','Fe','C'], correct:1, explain:'U-235.' },
{ q:'Чернобыль (год)?', type:'input', a:'1986', explain:'1986.' }
]},
fb11: { title:'Босс XI — Синтез и дозиметрия (Гл. 7 §42-§43)', tag:'Гл. 7', xp:80, stages:[
{ q:'Топливо термояда:', type:'mc', opts:['U','D + T','Pu','C'], correct:1, explain:'D + T.' },
{ q:'Энергия D+T (МэВ)?', type:'input', a:'17.6', explain:'17,6.' },
{ q:'Зиверт измер.:', type:'mc', opts:['$D$','$H$','$A$','Время'], correct:1, explain:'$H$.' },
{ q:'Норма населения (мЗв/год)?', type:'input', a:'1', explain:'1 мЗв.' },
{ q:'Удержание плазмы:', type:'mc', opts:['Стенками','Магн. полем','Гравит.','Не нужно'], correct:1, explain:'Маг. поле.' }
]},
fb12: { title:'Босс XII — Элем. частицы и картина мира (Гл. 7-8)', tag:'Гл. 7-8', xp:80, stages:[
{ q:'Сколько кварков?', type:'input', a:'6', explain:'6.' },
{ q:'Сколько фунд. взаимод.?', type:'input', a:'4', explain:'4.' },
{ q:'Бозон Хиггса (год)?', type:'input', a:'2012', explain:'LHC.' },
{ q:'Тёмная материя:', type:'mc', opts:['Видна','Не видна, но «весит»','Антиматерия','Не существует'], correct:1, explain:'~85% массы.' },
{ q:'Тёмная энергия — % от всей энергии Вселенной ≈?', type:'mc', opts:['5%','25%','68%','100%'], correct:2, explain:'$\\sim 68$%.' }
]}
};
function init(){
loadProgress(); initTheme(); buildSidebar();
buildP1(); buildFinal(); refreshProgressUI();
if(!STATE.achievements.has('start')) achievement('start');
}
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
</script>
</body>
</html>