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

948 lines
76 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 · Глава 6 · «Физика атома»</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:#16a34a; --pri2:#15803d; --pri-soft:#dcfce7;
--acc:#22c55e; --acc2:#166534; --acc-soft:#bbf7d0;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#052e16; --card:#0a401e; --card-soft:#0e4a25; --text:#dcfce7; --ink:#dcfce7; --muted:#86efac; --border:#166534}
*{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,#166534 0%,#16a34a 55%,#bbf7d0 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(187,247,208,.2);min-height:130px}
.hdr::before{content:'\2295';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:'\2295';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}
.psel{margin-bottom:24px}
.psel-title{font-size:.72rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}
.psel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.psel-card{background:var(--card);border:1.5px solid var(--border);border-radius:13px;padding:14px;cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;text-align:left;position:relative}
.psel-card:hover{transform:translateY(-3px);box-shadow:var(--sh2);border-color:var(--pri)}
.psel-card.active{border-color:var(--pri);background:linear-gradient(135deg,var(--pri-soft),var(--card));box-shadow:var(--sh2)}
.psel-card.active::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--pri),var(--acc));border-radius:13px 13px 0 0}
.psel-num{font-family:'Unbounded',sans-serif;font-size:.72rem;font-weight:800;color:var(--pri);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px}
.psel-name{font-size:.8rem;font-weight:700;color:var(--text);line-height:1.3;margin-bottom:8px}
.psel-prog{height:4px;background:rgba(0,0,0,.10);border-radius:3px;overflow:hidden}
.psel-prog-fill{height:100%;background:var(--pri);width:0%;transition:width .4s}
.psel-card.final{background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.psel-card.locked{opacity:.55}
.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.psel-card.done .psel-done{display:flex}
.sec{display:none;position:relative;animation:fadeIn .35s ease}
.sec.active{display:block}
@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:#16a34a}.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:active{transform:scale(.96)}
.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)}
.wg{background:linear-gradient(135deg,var(--card),var(--pri-soft));border:1.5px solid var(--pri);border-radius:14px;padding:18px 20px;margin-bottom:18px;box-shadow:var(--sh2);position:relative;z-index:1}
.wg-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}
.wg-badge{padding:4px 9px;background:var(--pri);color:#fff;border-radius:6px;font-family:'Unbounded',sans-serif;font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.wg-title{font-family:'Unbounded',sans-serif;font-size:1.05rem;font-weight:800;color:var(--pri2);flex:1}
.wg-help{font-size:.88rem;color:var(--text);margin-bottom:12px;line-height:1.55;background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-left:4px solid var(--warn);padding:9px 14px;border-radius:9px}
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);transition:border-color .15s;font-family:'JetBrains Mono',monospace;width:140px}
.tinp:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;align-items:center}
.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}
.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}
.sidecard-row:last-child{margin-bottom:0}
@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:#166534;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}
.sec-nav{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid var(--border);justify-content:space-between;flex-wrap:wrap}
.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}
.fx-holder{margin:10px 0;padding:0;text-align:center}
.fx-sliders{margin-top:10px;display:flex;flex-wrap:wrap;gap:4px;background:rgba(255,255,255,.5);border-radius:9px;padding:6px 4px}
.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)}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 11 · Глава 6</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>От ядерной модели Резерфорда до квантовых постулатов Бора, линейчатых спектров и работы лазеров. 5 параграфов + финал.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 30</button>
<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 class="psel">
<div class="psel-title">Параграфы главы</div>
<div id="psel-grid" class="psel-grid"></div>
</section>
<section id="sec-p1" class="sec" data-watermark="&#8853;"><div class="sec-header"><span class="sec-num">§ 30</span><h2 class="sec-h">Сложное строение атома. Ядерная модель</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="n"><div class="sec-header"><span class="sec-num">§ 31</span><h2 class="sec-h">Квантовые постулаты Бора</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="&#957;"><div class="sec-header"><span class="sec-num">§ 32</span><h2 class="sec-h">Излучение и поглощение света. Спектры</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="&#969;"><div class="sec-header"><span class="sec-num">§ 33</span><h2 class="sec-h">Спонтанное и индуцированное излучение</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="L"><div class="sec-header"><span class="sec-num">§ 34</span><h2 class="sec-h">Лазеры</h2></div><div id="p5-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,#16a34a,#15803d)">&#9733;</span><h2 class="sec-h">Финал главы 6</h2></div><div id="final-body"></div></section>
</div>
<aside class="col-side"><div id="sidebar-content"></div></aside>
</main>
<footer class="foot">Интерактивный учебник «Физика 11» · Глава 6 · «Физика атома» · 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 = { current:'p1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 5;
const _TB_SLUG = 'physics-11-ch6';
const PARAS = [
{ id:'p1', num:'§ 30', name:'Ядерная модель', sub:'Резерфорд', built:true },
{ id:'p2', num:'§ 31', name:'Постулаты Бора', sub:'$E_n = -13{,}6/n^2$', built:true },
{ id:'p3', num:'§ 32', name:'Спектры', sub:'$h\\nu = E_n - E_m$', built:true },
{ id:'p4', num:'§ 33', name:'Спонт. и инд. излуч.', sub:'Эйнштейн (1916)', built:true },
{ id:'p5', num:'§ 34', name:'Лазеры', sub:'Инверсная населённость', built:true },
{ id:'final', num:'★', name:'Финал главы 6', sub:'Интегральные боссы', final:true, built:true }
];
PARAS.forEach(p => { STATE.progress[p.id] = 0; });
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
const ACH_LABELS = {
p1_done:'§30 — ядерная модель освоена',
p2_done:'§31 — постулаты Бора освоены',
p3_done:'§32 — спектры освоены',
p4_done:'§33 — индуц. излучение освоено',
p5_done:'§34 — лазеры освоены',
ch6_master:'Магистр атома — пройден финал главы 6!',
start:'Начало главы 6!',
ch6_done:'Глава 6 пройдена — Физика атома!'
};
function loadProgress(){
try{
const s=localStorage.getItem('physics11_ch6_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics11_ch6_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_ch6_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics11_ch6_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('physics11_xp', String(STATE.xp));
}catch(e){}
}
function bumpProgress(key, delta){
STATE.progress[key]=Math.max(0,Math.min(100,(STATE.progress[key]||0)+delta));
saveProgress(); refreshProgressUI();
if(STATE.progress[key]>=50) markParaRead(key);
}
const _markedRead=new Set();
let _pendingProgressBody=null, _progressTimer=null;
function _flushProgress(){
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
}
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
function markLastPara(id){ _queueProgress({last_para:id}); }
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
window.addEventListener('beforeunload', _flushProgress);
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-ch6-'+(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 total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; if((STATE.progress[k]||0)>=100) el.classList.add('done'); });
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'; }
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
}
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 buildParaSelector(){
const g=document.getElementById('psel-grid'); g.innerHTML='';
PARAS.forEach(p=>{
const card=document.createElement('div');
card.className='psel-card'+(p.final?' final':'')+(p.built?'':' locked');
card.dataset.id=p.id; card.dataset.progCard=p.id;
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div><div class="psel-done"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg></div>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
if(window.renderMathInElement) try{ renderMath(g); }catch(e){}
}
const BUILT=new Set();
const BUILDERS = {
p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), p5:()=>buildP5(),
final:()=>buildFinal()
};
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
function goTo(id){
STATE.current=id; markLastPara(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
ensureBuilt(id);
const sec=document.getElementById('sec-'+id); if(sec) sec.classList.add('active');
buildSidebar(id);
window.scrollTo({top:0, behavior:'smooth'});
}
const SIDEBARS = {
p1:{title:'§ 30 — Ядерная модель', rows:[['Опыт','Резерфорд (1911)'],['Ядро','$\\sim 10^{-15}$ м'],['Атом','$\\sim 10^{-10}$ м']]},
p2:{title:'§ 31 — Постулаты Бора', rows:[['I постулат','Дискр. стац. состояния'],['II постулат','$h\\nu = E_n - E_m$'],['$E_n$','$-13{,}6/n^2$ эВ'],['$r_1$','$5{,}3 \\cdot 10^{-11}$ м']]},
p3:{title:'§ 32 — Спектры', rows:[['Серия Лаймана','$n \\to 1$ (УФ)'],['Серия Бальмера','$n \\to 2$ (видимый)'],['Серия Пашена','$n \\to 3$ (ИК)']]},
p4:{title:'§ 33 — Индуцированное излучение', rows:[['Эйнштейн','1916'],['Спонтанное','случайно'],['Индуц.','когерентно с возбуждающим']]},
p5:{title:'§ 34 — Лазеры', rows:[['Условие','Инверсная населённость'],['Резонатор','Зеркала'],['Свойства','когерентность, монохром., направл.']]},
final:{title:'Финал главы 6', rows:[['Боссов','3 интегральных'],['Покрытие','§30-§34'],['Награда','+150 XP + Магистр атома']]}
};
const TIPS=[
{sec:'p1',html:'§ 30 — α-частицы рассеиваются под большими углами потому, что натыкаются на массивное компактное ядро. Так Резерфорд открыл ядро (1911).'},
{sec:'p2',html:'§ 31 — атом может находиться только в дискретных состояниях с $E_n$. Излучение/поглощение света — при переходах между этими состояниями: $h\\nu = E_n - E_m$.'},
{sec:'p3',html:'§ 32 — атомы излучают линейчатые (а не сплошные) спектры. Для водорода: формула Ридберга $1/\\lambda = R(1/m^2 - 1/n^2)$.'},
{sec:'p4',html:'§ 33 — индуцированное излучение: фотон «выбивает» из возбуждённого атома идентичный фотон. Так работают лазеры.'},
{sec:'p5',html:'§ 34 — лазер = усиление света индуцированным излучением. Главное условие — инверсная населённость уровней.'},
{sec:'final',html:'Финал главы 6: 3 интегральных босса — последняя проверка перед ядерной физикой.'}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
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>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
html+='</div>';
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
if(tip){
html+='<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg),var(--pri-soft));border-color:var(--warn)"><h4 style="color:#92400e;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.82rem;line-height:1.55">'+tip.html+'</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 = {
repeat:'<svg class="ic" viewBox="0 0 24 24"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>',
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>',
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></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 = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',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 secNavFor(curId){
const idx = PARAS.findIndex(p => p.id === curId);
const prev = idx > 0 ? PARAS[idx-1].id : null;
const next = idx < PARAS.length - 1 ? PARAS[idx+1].id : null;
const NAMES = {p1:'\xA730',p2:'\xA731',p3:'\xA732',p4:'\xA733',p5:'\xA734',final:'Финал'};
let h='<div class="sec-nav">';
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
h+='</div>'; return h;
}
function readButton(paraId){
const p = PARAS.find(x => x.id === paraId);
const labelTail = p && p.final ? 'финал' : (p ? p.num : '\xA7?');
return '<div style="margin-top:18px;display:flex;justify-content:center"><button class="btn primary" id="'+paraId+'-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>'
+' Я прочитал — '+labelTail+' (+10 XP)</button></div>';
}
function wireReadBtn(paraId){
const btn = document.getElementById(paraId+'-read-btn'); if(!btn) return;
btn.addEventListener('click', ()=>{
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
btn.textContent='Прочитано! +10 XP'; btn.disabled=true; btn.style.opacity=.6;
const aId = paraId+'_done';
if(ACH_LABELS[aId]) achievement(aId);
});
}
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 ensureFx(cb){ if(window.PHYS) return cb(); setTimeout(()=>ensureFx(cb), 60); }
function buildSingleQuizPara(paraId, paraNum, paraName, theoryCards, calcItems, thItems, bossId){
const box = document.getElementById(paraId + '-body'); if(!box) return;
let html = theoryCards;
const idC = 'i' + paraNum + '-calc', idT = 'i' + paraNum + '-th';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. расчёт</span><span class="wg-title">Расчёты</span></div>'
+ '<div class="wg-help">Решено: <b id="' + idC + '-score">0</b> / 5.</div>'
+ '<div id="' + idC + '-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="' + idC + '-inp" placeholder="ответ"><button class="btn primary" id="' + idC + '-go">Проверить</button></div><div class="feedback" id="' + idC + '-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. теория</span><span class="wg-title">Теория</span></div>'
+ '<div class="wg-help">Решено: <b id="' + idT + '-score">0</b> / 5.</div>'
+ '<div id="' + idT + '-q" style="margin:8px 0"></div><div class="opts-row" id="' + idT + '-opts"></div><div class="feedback" id="' + idT + '-fb"></div></div>';
html += '<div id="boss-' + paraNum + '-slot"></div>';
html += readButton(paraId);
html += secNavFor(paraId);
box.innerHTML = html;
runQuizInput(idC, calcItems, 16);
runQuizMC(idT, thItems, 12);
const bs = loadBossState('boss-' + paraNum) || { stage:0, solved:false };
makeAndBindBoss('boss-' + paraNum + '-slot', String(paraNum), BOSS_DEFS[bossId], bs,
()=>saveBossState('boss-' + paraNum, bs),
()=>{ bumpProgress(paraId, 40); achievement(paraId + '_done'); });
wireReadBtn(paraId);
renderMath(box);
}
function buildP1(){
let theory = '';
theory += makeCard('theory', 'Открытие сложности атома — Дж. Дж. Томсон', '§ 30.1',
'<p>В конце XIX в. казалось, что атом — неделимая частица. Открытия:</p>'
+ '<ul>'
+ '<li>Электрон — <b>Дж. Дж. Томсон (1897)</b>: катодные лучи — поток отрицательно заряженных частиц с малой массой.</li>'
+ '<li>Радиоактивность — А. Беккерель (1896), П. и М. Кюри (1898).</li>'
+ '<li>Модель Томсона («пудинг с изюмом», 1903): атом — сфера + равномерно положительный заряд + электроны.</li>'
+ '</ul>');
theory += makeCard('example', 'Опыт Резерфорда (1909–1911) — открытие ядра', '§ 30.2',
'<p><b>Опыт:</b> α-частицы (Не²⁺) от радиоактивного источника пропускали через тонкую золотую фольгу. Регистрировали рассеяние на флуоресцирующем экране.</p>'
+ '<p><b>Результат:</b> большинство α-частиц проходило почти без отклонения, но <b>$\\sim 1/8000$</b> отклонялись на углы $> 90°$, а некоторые — даже назад.</p>'
+ '<p><b>Вывод (1911):</b> положительный заряд и почти вся масса атома сосредоточены в <b>крошечном плотном ядре</b>. Электроны движутся вокруг по орбитам.</p>'
+ '<p><b>Размеры:</b> атом $\\sim 10^{-10}$ м, ядро $\\sim 10^{-15}$ м (в $10^5$ раз меньше). Если атом — это стадион, ядро — горошина в центре.</p>');
theory += makeCard('rule', 'Состав ядра. Изотопы', '§ 30.3',
'<p>Ядро состоит из:</p>'
+ '<ul>'
+ '<li><b>Протонов</b> (Резерфорд, 1919) — положительный заряд $+e$, масса $m_p \\approx 1{,}67 \\cdot 10^{-27}$ кг.</li>'
+ '<li><b>Нейтронов</b> (Дж. Чедвик, 1932) — без заряда, масса $\\approx m_p$.</li>'
+ '</ul>'
+ '<p>Обозначение ядра: ${}^A_Z X$, где $Z$ — число протонов (зарядовое число = номер элемента), $A$ — массовое число (нуклоны = протоны + нейтроны), $N = A - Z$ — число нейтронов.</p>'
+ '<p><b>Изотопы</b> — ядра с одинаковым $Z$ (одного элемента), но разным $A$ (разное число нейтронов). Пример: ${}^1_1\\text{H}$ (протий), ${}^2_1\\text{H}$ (дейтерий), ${}^3_1\\text{H}$ (тритий).</p>');
buildSingleQuizPara('p1', 1, '§ 30', theory, I1_CALC_ITEMS, I1_TH_ITEMS, 'b1');
}
function buildP2(){
let theory = '';
theory += makeCard('theory', 'Кризис планетарной модели', '§ 31.1',
'<p>Планетарная модель Резерфорда противоречила классической электродинамике: <b>ускоренно движущийся заряд излучает ЭМ-волны</b>. Электрон, вращаясь, должен терять энергию и за $\\sim 10^{-8}$ с упасть на ядро.</p>'
+ '<p>Атомы должны были «коллапсировать», но в реальности они стабильны и излучают <b>линейчатые спектры</b> (определённые частоты), а не сплошные.</p>');
theory += makeCard('rule', 'Два постулата Бора (1913)', '§ 31.2',
'<p>Н. Бор предложил два постулата, нарушающих классику:</p>'
+ '<p><b>I постулат (стационарных состояний):</b></p>'
+ '<p style="text-align:center;margin:6px 0;font-weight:700;color:var(--pri2)">«Атом может находиться только в дискретных <b>стационарных</b> состояниях с энергиями $E_1, E_2, E_3, \\dots$, в которых он не излучает.»</p>'
+ '<p><b>II постулат (правило частот):</b></p>'
+ '<p style="text-align:center;margin:6px 0;font-weight:700;color:var(--pri2)">«При переходе из состояния с энергией $E_n$ в $E_m$ ($E_n > E_m$) излучается квант света с частотой:»</p>'
+ '<p style="text-align:center;margin:6px 0">$$h\\nu = E_n - E_m$$</p>'
+ '<p>При поглощении — наоборот: атом переходит в состояние с большей энергией.</p>');
theory += makeCard('rule', 'Энергетические уровни водорода', '§ 31.3',
'<p>Для атома водорода Бор вывел:</p>'
+ '<p style="text-align:center;margin:8px 0">$$E_n = -\\dfrac{13{,}6}{n^2} \\text{ эВ}, \\quad n = 1, 2, 3, \\dots$$</p>'
+ '<p>Состояние $n = 1$ — <b>основное</b> ($E_1 = -13{,}6$ эВ). Энергия ионизации атома водорода = 13,6 эВ — энергия для отрыва электрона.</p>'
+ '<p>Радиусы орбит: $r_n = n^2 \\cdot r_1$, где $r_1 = 5{,}3 \\cdot 10^{-11}$ м — <b>Боровский радиус</b>.</p>'
+ '<p>Скорости электрона: $v_n = v_1 / n$, где $v_1 \\approx 2{,}2 \\cdot 10^6$ м/с (≈ 1% от $c$).</p>');
theory += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив</span><span class="wg-title">Атом водорода — Боровская модель</span></div>'
+ '<div class="wg-help">Двигай ползунки: $n_{from}$ — начальный уровень, $n_{to}$ — конечный. При переходе вниз электрон излучает фотон $h\\nu$.</div>'
+ '<div class="fx-holder" id="fx-bohr"></div>'
+ '<div class="fx-sliders" id="fx-bohr-sl"></div></div>';
buildSingleQuizPara('p2', 2, '§ 31', theory, I2_CALC_ITEMS, I2_TH_ITEMS, 'b2');
ensureFx(()=>{
const ba = new PHYS.BohrAtom(document.getElementById('fx-bohr'), {width:540, height:380, n_from:3, n_to:2});
const slBox = document.getElementById('fx-bohr-sl');
const slF = PHYS.util.slider({label:'n_from', min:1, max:5, step:1, value:3, fmt:v=>v.toFixed(0), onChange:v=>ba.setFrom(Math.round(v))});
const slT = PHYS.util.slider({label:'n_to', min:1, max:5, step:1, value:2, fmt:v=>v.toFixed(0), onChange:v=>ba.setTo(Math.round(v))});
slBox.innerHTML = slF.html + slT.html;
slF.wire(slBox); slT.wire(slBox);
});
}
function buildP3(){
let theory = '';
theory += makeCard('theory', 'Спектры — паспорта атомов', '§ 32.1',
'<p>Спектр — это разложение света по частотам (длинам волн).</p>'
+ '<ul>'
+ '<li><b>Сплошной</b> спектр — все длины волн (нагретые твёрдые тела, плазма).</li>'
+ '<li><b>Линейчатый</b> спектр — отдельные узкие линии (нагретые газы из <b>атомов</b>).</li>'
+ '<li><b>Полосатый</b> спектр — группы линий (молекулы).</li>'
+ '<li><b>Спектр поглощения</b> — тёмные линии на сплошном (свет проходит через холодный газ).</li>'
+ '</ul>'
+ '<p>Каждый химический элемент имеет уникальный линейчатый спектр — <b>«отпечаток пальцев»</b> атома. Спектральный анализ открыл новые элементы (гелий — на Солнце, до Земли).</p>');
theory += makeCard('rule', 'Формула Ридберга для водорода', '§ 32.2',
'<p>До Бора эмпирически выведена <b>формула Ридберга</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{\\lambda} = R \\left( \\dfrac{1}{m^2} - \\dfrac{1}{n^2} \\right), \\quad n > m$$</p>'
+ '<p>где $R = 1{,}097 \\cdot 10^7$ м⁻¹ — <b>постоянная Ридберга</b>.</p>'
+ '<p><b>Серии спектра водорода:</b></p>'
+ '<ul>'
+ '<li><b>Лаймана</b> ($m = 1$, $n = 2, 3, \\dots$) — УФ.</li>'
+ '<li><b>Бальмера</b> ($m = 2$, $n = 3, 4, \\dots$) — видимый свет: H_α (656 нм, красная), H_β (486 нм), H_γ (434 нм), H_δ (410 нм).</li>'
+ '<li><b>Пашена</b> ($m = 3$) — ИК.</li>'
+ '<li>Брэккета, Пфунда, Хемфри — дальний ИК.</li>'
+ '</ul>'
+ '<p>Теория Бора <b>теоретически вывела</b> формулу Ридберга — это была её главная победа.</p>');
theory += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив</span><span class="wg-title">Энергетические уровни атома H</span></div>'
+ '<div class="wg-help">Диаграмма уровней. Стрелка — переход между $n_{from}$ и $n_{to}$. Отображается $h\\nu$ и $\\lambda \\approx 1240/E$ нм.</div>'
+ '<div class="fx-holder" id="fx-elev"></div>'
+ '<div class="fx-sliders" id="fx-elev-sl"></div></div>';
buildSingleQuizPara('p3', 3, '§ 32', theory, I3_CALC_ITEMS, I3_TH_ITEMS, 'b3');
ensureFx(()=>{
const el = new PHYS.EnergyLevels(document.getElementById('fx-elev'), {width:540, height:360, n_from:4, n_to:2});
const slBox = document.getElementById('fx-elev-sl');
const slF = PHYS.util.slider({label:'n_from', min:1, max:6, step:1, value:4, fmt:v=>v.toFixed(0), onChange:v=>el.setFrom(Math.round(v))});
const slT = PHYS.util.slider({label:'n_to', min:1, max:6, step:1, value:2, fmt:v=>v.toFixed(0), onChange:v=>el.setTo(Math.round(v))});
slBox.innerHTML = slF.html + slT.html;
slF.wire(slBox); slT.wire(slBox);
});
}
function buildP4(){
let theory = '';
theory += makeCard('theory', 'Три типа взаимодействия атома со светом', '§ 33.1',
'<p>В 1916 г. <b>А. Эйнштейн</b> ввёл понятия трёх процессов:</p>'
+ '<ol>'
+ '<li><b>Поглощение</b>: атом в состоянии $E_m$ поглощает фотон $h\\nu = E_n - E_m$ и переходит в $E_n$.</li>'
+ '<li><b>Спонтанное излучение</b>: возбуждённый атом сам по себе ($t \\sim 10^{-8}$ с) переходит в нижнее состояние и излучает фотон. Направление и фаза — случайные.</li>'
+ '<li><b>Индуцированное (вынужденное) излучение</b>: фотон $h\\nu$, пролетая мимо возбуждённого атома, «провоцирует» его перейти вниз. Излучённый фотон <b>идентичен</b> возбуждающему: та же частота, направление, фаза, поляризация.</li>'
+ '</ol>');
theory += makeCard('rule', 'Инверсная населённость', '§ 33.2',
'<p>В тепловом равновесии нижние уровни заселены сильнее верхних (распределение Больцмана): $N_1 > N_2$. Поглощение преобладает над индуцированным излучением.</p>'
+ '<p>Для усиления света нужна <b>инверсная населённость</b>:</p>'
+ '<p style="text-align:center;margin:8px 0;color:var(--pri)">$$N_2 > N_1$$</p>'
+ '<p>В таком состоянии вместо поглощения происходит усиление: один фотон $\\to$ два когерентных фотона $\\to$ четыре $\\to$ лавина.</p>'
+ '<p>Создать инверсную населённость в двухуровневой системе — <b>невозможно</b>. Используют <b>трёхуровневые</b> и <b>четырёхуровневые</b> системы с подкачкой энергии («накачкой»).</p>');
buildSingleQuizPara('p4', 4, '§ 33', theory, I4_CALC_ITEMS, I4_TH_ITEMS, 'b4');
}
function buildP5(){
let theory = '';
theory += makeCard('theory', 'Что такое лазер', '§ 34.1',
'<p><b>ЛАЗЕР</b> — Light Amplification by Stimulated Emission of Radiation — усиление света индуцированным излучением.</p>'
+ '<p>Первый лазер построил Т. Мейман в 1960 г. (рубиновый, λ = 694 нм). В СССР параллельно работали Н. Г. Басов и А. М. Прохоров (Нобель 1964 вместе с Ч. Таунсом за теорию).</p>'
+ '<p><b>Устройство:</b></p>'
+ '<ul>'
+ '<li><b>Активная среда</b> (рубин, газ, полупроводник) — атомы с трёх- или четырёхуровневой системой.</li>'
+ '<li><b>Накачка</b> — внешний источник энергии (лампа, ток, химическая реакция), создающий инверсную населённость.</li>'
+ '<li><b>Резонатор</b> — два зеркала (одно глухое, второе полупрозрачное), создающие стоячую волну.</li>'
+ '</ul>');
theory += makeCard('rule', 'Свойства лазерного излучения', '§ 34.2',
'<ul>'
+ '<li><b>Когерентность</b> — все фотоны в фазе.</li>'
+ '<li><b>Монохроматичность</b> — почти одна частота ($\\Delta\\nu/\\nu \\sim 10^{-10}$).</li>'
+ '<li><b>Направленность</b> — пучок с малой расходимостью (миллирадианы).</li>'
+ '<li><b>Высокая мощность</b> в импульсе (до петаватт).</li>'
+ '</ul>'
+ '<p><b>Применения:</b> чтение CD/DVD/Blu-ray, оптоволоконная связь, лазерные принтеры, считыватели штрих-кодов, медицина (хирургия глаза), резка металла, ядерный синтез, голография, лазерное оружие, измерение расстояний до Луны (лазерные ретрорефлекторы).</p>');
buildSingleQuizPara('p5', 5, '§ 34', theory, I5_CALC_ITEMS, I5_TH_ITEMS, 'b5');
}
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="card" style="background:linear-gradient(135deg,#dcfce7,#bbf7d0);border-color:#16a34a">'
+ '<div class="card-header"><div class="card-icon rule" style="background:#16a34a;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">Финал главы 6 — Физика атома</div><div class="card-num">★</div></div>'
+ '<div class="card-body"><p>3 интегральных босса покрывают §30-§34. За победу: <b>Магистр атома</b> + 150 XP.</p></div></div>';
for (let i = 1; i <= 3; i++) html += '<div id="boss-final-'+i+'-slot"></div>';
html += '<div class="card" id="final-victory" style="display:none;background:linear-gradient(135deg,#bbf7d0,#22c55e);border-color:#166534">'
+ '<div class="card-header"><div class="card-icon example" style="background:#166534;color:#fff">'
+ '<svg class="ic" viewBox="0 0 24 24" stroke-width="2"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>'
+ '</div><div class="card-title">Магистр атома!</div></div>'
+ '<div class="card-body"><p><b>Все 3 финальных босса побеждены!</b> Бонус: <b>+150 XP</b>. Впереди — Глава 7 (ядерная физика).</p></div></div>';
html += secNavFor('final');
box.innerHTML = html;
for (let i = 1; i <= 3; i++){
const key = 'boss-final-' + i;
const bs = loadBossState(key) || { stage:0, solved:false };
makeAndBindBoss(key+'-slot', 'final-'+i, FINAL_BOSS_DEFS['fb'+i], bs,
()=>{ saveBossState(key, bs); checkFinalDone(); }, ()=>{ checkFinalDone(); });
}
renderMath(box);
}
function checkFinalDone(){
let all = true;
for (let i = 1; i <= 3; i++){
const s = loadBossState('boss-final-'+i);
if (!s || !s.solved){ all = false; break; }
}
if (all){
const v = document.getElementById('final-victory'); if (v) v.style.display = '';
bumpProgress('final', 100);
if (!STATE.achievements.has('ch6_master')){
achievement('ch6_master');
addXp(150, 'ch6-master-bonus');
}
if (!STATE.achievements.has('ch6_done')) achievement('ch6_done');
}
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('physics11_ch6_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('physics11_ch6_'+key, JSON.stringify(state)); }catch(e){} }
function runQuizMC(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('physics11_ch6_quiz_'+id)||'null') || { idx:0, solved:0, awarded:false };
const qEl = document.getElementById(id+'-q');
const optsEl = document.getElementById(id+'-opts');
const fbEl = document.getElementById(id+'-fb');
const scoreEl = document.getElementById(id+'-score');
function save(){ localStorage.setItem('physics11_ch6_quiz_'+id, JSON.stringify(state)); }
function render(){
if(state.solved >= items.length){
qEl.innerHTML = '<b style="color:var(--ok)">Все задания решены!</b> +'+xpReward+' XP.';
optsEl.innerHTML = ''; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
if(!state.awarded){ state.awarded = true; save(); addXp(xpReward, 'quiz-'+id); }
return;
}
const it = items[state.idx % items.length];
qEl.innerHTML = it.q; optsEl.innerHTML = ''; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
it.opts.forEach((o,i)=>{
const b = document.createElement('button'); b.className = 'opt-btn'; b.innerHTML = o;
b.addEventListener('click', ()=>{
if(i === it.correct){
b.classList.add('correct'); state.solved++; state.idx++; save();
if(scoreEl) scoreEl.textContent = state.solved;
fbEl.className='feedback ok'; fbEl.innerHTML='&#10003; Верно. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
setTimeout(render, 850);
} else {
b.classList.add('wrong');
fbEl.className='feedback fail'; fbEl.innerHTML='&#10007; Не так. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
}
});
optsEl.appendChild(b);
});
renderMath(qEl);
}
render();
}
function runQuizInput(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('physics11_ch6_quiz_'+id)||'null') || { idx:0, solved:0, awarded:false };
const qEl = document.getElementById(id+'-q');
const inp = document.getElementById(id+'-inp');
const go = document.getElementById(id+'-go');
const fbEl = document.getElementById(id+'-fb');
const scoreEl = document.getElementById(id+'-score');
function save(){ localStorage.setItem('physics11_ch6_quiz_'+id, JSON.stringify(state)); }
function render(){
if(state.solved >= items.length){
qEl.innerHTML = '<b style="color:var(--ok)">Все задания решены!</b> +'+xpReward+' XP.';
inp.value=''; inp.disabled=true; go.disabled=true;
if(scoreEl) scoreEl.textContent = state.solved;
if(!state.awarded){ state.awarded = true; save(); addXp(xpReward, 'quiz-'+id); }
return;
}
const it = items[state.idx % items.length];
qEl.innerHTML = it.q; inp.value=''; inp.disabled=false; go.disabled=false; fbEl.style.display='none';
if(scoreEl) scoreEl.textContent = state.solved;
renderMath(qEl);
}
function check(){
const it = items[state.idx % items.length];
const v = normalizeAns(inp.value);
const ans = Array.isArray(it.answer) ? it.answer.map(normalizeAns) : [normalizeAns(it.answer)];
if(ans.indexOf(v) >= 0){
state.solved++; state.idx++; save();
if(scoreEl) scoreEl.textContent = state.solved;
fbEl.className='feedback ok'; fbEl.innerHTML='&#10003; Верно. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
setTimeout(render, 850);
} else {
fbEl.className='feedback fail'; fbEl.innerHTML='&#10007; Не так. '+(it.explain||''); fbEl.style.display='block'; renderMath(fbEl);
}
}
go.addEventListener('click', check);
inp.addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); check(); } });
render();
}
const I1_CALC_ITEMS = [
{ q:'Размер атома (м) ≈?', answer:['1e-10','10⁻¹⁰'], explain:'$\\sim 10^{-10}$.' },
{ q:'Размер ядра (м) ≈?', answer:['1e-15','10⁻¹⁵'], explain:'$\\sim 10^{-15}$.' },
{ q:'${}^{12}_6\\text{C}$ — число нейтронов?', answer:'6', explain:'$N = A - Z = 12 - 6 = 6$.' },
{ q:'${}^{235}_{92}\\text{U}$ — число нейтронов?', answer:'143', explain:'$235 - 92 = 143$.' },
{ q:'${}^4_2\\text{He}$ — общее число нуклонов?', answer:'4', explain:'$A = 4$.' }
];
const I1_TH_ITEMS = [
{ q:'Кто открыл электрон?', opts:['Резерфорд','Томсон','Бор','Резерфорд'], correct:1, explain:'Дж. Дж. Томсон (1897).' },
{ q:'Опыт Резерфорда показал:', opts:['Электроны в ядре','Маленькое плотное ядро','Атом сплошной','Атом — пудинг'], correct:1, explain:'Ядерная модель.' },
{ q:'Нейтрон открыл:', opts:['Резерфорд','Чедвик','Томсон','Бор'], correct:1, explain:'Дж. Чедвик (1932).' },
{ q:'Изотопы отличаются:', opts:['Зарядом','Числом нейтронов','Числом электронов','Всем'], correct:1, explain:'Разное $N$.' },
{ q:'Год открытия ядра:', opts:['1900','1905','1911','1932'], correct:2, explain:'1911 (Резерфорд).' }
];
const I2_CALC_ITEMS = [
{ q:'$n = 2$. $E_2$ (эВ)?', answer:'-3.4', explain:'$-13{,}6/4 = -3{,}4$.' },
{ q:'$n = 3$. $E_3$ (эВ)?', answer:['-1.51','-1.5'], explain:'$-13{,}6/9 \\approx -1{,}51$.' },
{ q:'Переход $n = 2 \\to 1$. $h\\nu$ (эВ)?', answer:'10.2', explain:'$E_1 - E_2 = -13{,}6 + 3{,}4 = -10{,}2$; $|hν| = 10{,}2$.' },
{ q:'Энергия ионизации атома H (эВ)?', answer:'13.6', explain:'$|E_1| = 13{,}6$.' },
{ q:'Боровский радиус (м)?', answer:['5.3e-11','5.29e-11'], explain:'$\\sim 5{,}3 \\cdot 10^{-11}$.' }
];
const I2_TH_ITEMS = [
{ q:'I постулат Бора:', opts:['Атом излучает непрерывно','Дискретные стац. состояния','Электрон падает на ядро','Любые орбиты'], correct:1, explain:'Дискретные.' },
{ q:'II постулат:', opts:['$h\\nu = E_n - E_m$','$E = mc^2$','$\\Delta E = 0$','$F = qE$'], correct:0, explain:'Правило частот.' },
{ q:'$n = 1$ — это:', opts:['Возбуждённое','Основное','Ионизованное','Нет такого'], correct:1, explain:'Основное состояние.' },
{ q:'При $n \\to \\infty$, $E_n$:', opts:['$\\to -\\infty$','$\\to 0$','$\\to +13{,}6$','$\\to const$'], correct:1, explain:'$E_n = -13{,}6/n^2 \\to 0$.' },
{ q:'Год постулатов Бора:', opts:['1900','1905','1913','1932'], correct:2, explain:'1913.' }
];
const I3_CALC_ITEMS = [
{ q:'Переход $n = 3 \\to 2$. $\\Delta E$ (эВ) ≈?', answer:['1.89','1.9'], explain:'$-3{,}4 + 1{,}51 = -1{,}89$.' },
{ q:'$\\Delta E = 2$ эВ. $\\lambda$ (нм)?', answer:'620', explain:'$1240/2 = 620$.' },
{ q:'Серия Бальмера переходит на уровень:', answer:'2', explain:'$m = 2$.' },
{ q:'Серия Лаймана — это:', answer:['уф','ультрафиолет'], explain:'$m = 1$, УФ.' },
{ q:'Постоянная Ридберга $R$ (м⁻¹) ≈?', answer:['1.097e7','1.1e7','10973731.6'], explain:'$1{,}097 \\cdot 10^7$.' }
];
const I3_TH_ITEMS = [
{ q:'Нагретый <b>газ</b> атомов даёт спектр:', opts:['Сплошной','Линейчатый','Полосатый','Поглощения'], correct:1, explain:'Линейчатый.' },
{ q:'Нагретое твёрдое тело — спектр:', opts:['Сплошной','Линейчатый','Полосатый','Нет'], correct:0, explain:'Сплошной.' },
{ q:'Молекулы дают спектр:', opts:['Сплошной','Линейчатый','Полосатый','Не дают'], correct:2, explain:'Полосатый.' },
{ q:'Спектральный анализ открыл:', opts:['Электрон','Гелий на Солнце','Радий','Нейтрон'], correct:1, explain:'Гелий — раньше, чем на Земле.' },
{ q:'H_α имеет λ ≈ ?', opts:['410 нм','486 нм','656 нм','1240 нм'], correct:2, explain:'656 нм — красный.' }
];
const I4_CALC_ITEMS = [
{ q:'Время жизни возбуждённого состояния атома ≈ (с)?', answer:['1e-8','10⁻⁸'], explain:'$\\sim 10^{-8}$ с.' },
{ q:'Год работы Эйнштейна о вынужд. излучении?', answer:'1916', explain:'1916.' },
{ q:'Минимум уровней для лазера?', answer:'3', explain:'Трёх- или четырёхуровневая.' },
{ q:'$E_{ind}$ фотон относительно $E_{absorb}$ фотона по фазе:', answer:['совпадает','та же','одинакова'], explain:'Когерентны.' },
{ q:'$E_{ind}$ фотон относительно по направлению:', answer:['совпадает','одно','та же'], explain:'Одно направление.' }
];
const I4_TH_ITEMS = [
{ q:'Сколько типов процессов ввёл Эйнштейн:', opts:['1','2','3','4'], correct:2, explain:'Поглощ., спонт. изл., индуц. изл.' },
{ q:'Спонтанное излучение:', opts:['Когерентно','Случайно','Только в лазере','Под действием поля'], correct:1, explain:'Случайно.' },
{ q:'Индуцированное излучение:', opts:['Случайно','Идентично возб. фотону','Только в твёрдых','Не существует'], correct:1, explain:'Когерентно.' },
{ q:'Инверсная населённость:', opts:['$N_1 > N_2$','$N_2 > N_1$','$N_1 = N_2$','$N = 0$'], correct:1, explain:'Верхний уровень заселён больше.' },
{ q:'Возможна ли инверсия в 2-уровн. системе?', opts:['Да','Нет','Только при низких T','Только при высоких T'], correct:1, explain:'Невозможна.' }
];
const I5_CALC_ITEMS = [
{ q:'Год создания первого лазера?', answer:'1960', explain:'Т. Мейман (1960).' },
{ q:'$\\lambda$ рубинового лазера (нм)?', answer:'694', explain:'694 нм.' },
{ q:'Год Нобеля за лазеры?', answer:'1964', explain:'1964 (Басов, Прохоров, Таунс).' },
{ q:'Зеркала в резонаторе обеспечивают:', answer:['обр. связь','усиление','стоячую волну'], explain:'Обр. связь.' },
{ q:'$\\Delta\\nu/\\nu$ для лазера ≈?', answer:['1e-10','10⁻¹⁰','0.0000000001'], explain:'$\\sim 10^{-10}$.' }
];
const I5_TH_ITEMS = [
{ q:'Лазер — расшифровка:', opts:['Любое излучение','Усиление света индуц. излучением','Лазерная сварка','Лазерная резка'], correct:1, explain:'LASER = Light Amplification by Stimulated Emission of Radiation.' },
{ q:'Первый лазер создал:', opts:['Эйнштейн','Бор','Мейман','Планк'], correct:2, explain:'Т. Мейман (1960).' },
{ q:'Лазерный свет:', opts:['Полихроматический, расходится','Монохром., когерент., направлен','Только в видимом','Поглощается воздухом'], correct:1, explain:'4 свойства.' },
{ q:'Применение лазера в медицине:', opts:['Только диагностика','Хирургия глаза','Не применяется','Только онколог.'], correct:1, explain:'Лазерная хирургия.' },
{ q:'CD/DVD используют:', opts:['Электронный пучок','Лазер','Рентген','Магнитное поле'], correct:1, explain:'Лазер.' }
];
const BOSS_DEFS = {
b1: { title:'Босс §30 — Ядерная модель', tag:'§30', xp:70, stages:[
{ q:'Электрон открыл:', type:'mc', opts:['Резерфорд','Томсон','Бор','Чедвик'], correct:1, explain:'Дж. Дж. Томсон (1897).' },
{ q:'Размер ядра (м)?', type:'input', a:['1e-15','10⁻¹⁵'], explain:'$\\sim 10^{-15}$.' },
{ q:'${}^{14}_7\\text{N}$ — число нейтронов?', type:'input', a:'7', explain:'$14 - 7 = 7$.' },
{ q:'Изотопы отличаются:', type:'mc', opts:['$Z$','$N$','$A$','Всем'], correct:1, explain:'Разное число нейтронов.' },
{ q:'Год открытия ядра:', type:'input', a:'1911', explain:'1911 (Резерфорд).' }
]},
b2: { title:'Босс §31 — Постулаты Бора', tag:'§31', xp:75, stages:[
{ q:'$E_n$ для атома H:', type:'mc', opts:['$-13{,}6/n$','$-13{,}6/n^2$','$13{,}6 n$','$-13{,}6 n^2$'], correct:1, explain:'$E_n = -13{,}6/n^2$.' },
{ q:'$E_1$ (эВ)?', type:'input', a:'-13.6', explain:'$-13{,}6$.' },
{ q:'$E_2$ (эВ)?', type:'input', a:'-3.4', explain:'$-13{,}6/4$.' },
{ q:'Боровский радиус (м)?', type:'input', a:['5.3e-11','5.29e-11'], explain:'$5{,}3 \\cdot 10^{-11}$.' },
{ q:'II постулат связывает:', type:'mc', opts:['$E$ и $p$','$E$ и $\\nu$','$F$ и $r$','$T$ и $V$'], correct:1, explain:'$h\\nu = E_n - E_m$.' }
]},
b3: { title:'Босс §32 — Спектры', tag:'§32', xp:75, stages:[
{ q:'Серия Лаймана — куда?', type:'input', a:'1', explain:'$m = 1$.' },
{ q:'Серия Бальмера — куда?', type:'input', a:'2', explain:'$m = 2$.' },
{ q:'H_α (нм)?', type:'input', a:'656', explain:'656 нм красный.' },
{ q:'Постоянная Ридберга (м⁻¹)?', type:'input', a:['1.097e7','1.1e7'], explain:'$1{,}097 \\cdot 10^7$.' },
{ q:'$\\Delta E = 2$ эВ. $\\lambda$ (нм)?', type:'input', a:'620', explain:'$1240/2$.' }
]},
b4: { title:'Босс §33 — Индуцированное излучение', tag:'§33', xp:70, stages:[
{ q:'Кто ввёл индуц. излучение?', type:'mc', opts:['Планк','Бор','Эйнштейн','Резерфорд'], correct:2, explain:'Эйнштейн (1916).' },
{ q:'Инверсная населённость:', type:'mc', opts:['$N_1 > N_2$','$N_2 > N_1$','$N_1 = 0$','$N_2 = N_1$'], correct:1, explain:'$N_2 > N_1$.' },
{ q:'$E_{инд}$ фотон по фазе с возб.:', type:'mc', opts:['Случайно','Совпадает','Противоположно','Разная частота'], correct:1, explain:'Когерентно.' },
{ q:'Время жизни возб. состояния (с)?', type:'input', a:['1e-8','10⁻⁸'], explain:'$\\sim 10^{-8}$.' },
{ q:'Минимум уровней для лазера:', type:'input', a:'3', explain:'3 уровня.' }
]},
b5: { title:'Босс §34 — Лазеры', tag:'§34', xp:75, stages:[
{ q:'Первый лазер (год)?', type:'input', a:'1960', explain:'1960 (Мейман).' },
{ q:'LASER расшифровывается:', type:'mc', opts:['Свет','Усиление света индуц. излуч.','Лазерное оружие','Лазер диод'], correct:1, explain:'Light Amplification by Stimulated Emission of Radiation.' },
{ q:'Свойство лазера:', type:'mc', opts:['Расходится','Когерентен','Полихроматический','Слабый'], correct:1, explain:'Когерент., монохром., направлен.' },
{ q:'$\\lambda$ рубина (нм)?', type:'input', a:'694', explain:'694 нм.' },
{ q:'Применение CD/DVD:', type:'mc', opts:['Электронный пучок','Лазер','Рентген','Магнитное поле'], correct:1, explain:'Полупроводниковый лазер.' }
]}
};
const FINAL_BOSS_DEFS = {
fb1: { title:'Финал §30-§31 — Атом и постулаты Бора', tag:'Финал I', xp:50, stages:[
{ q:'Опыт Резерфорда (год)?', type:'input', a:'1911', explain:'1911.' },
{ q:'Размер ядра (м)?', type:'input', a:['1e-15','10⁻¹⁵'], explain:'$10^{-15}$.' },
{ 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:'$E_2$ (эВ)?', type:'input', a:'-3.4', explain:'$-3{,}4$.' },
{ q:'Год постулатов Бора?', type:'input', a:'1913', explain:'1913.' }
]},
fb2: { title:'Финал §32-§33 — Спектры и индуц. излучение', tag:'Финал II', xp:50, stages:[
{ q:'Линейчатый спектр дают:', type:'mc', opts:['Газы атомов','Тверд. тела','Жидкости','Молекулы'], correct:0, explain:'Атомы.' },
{ q:'Серия Бальмера — на:', type:'input', a:'2', explain:'$m = 2$.' },
{ q:'Постоянная Ридберга (м⁻¹)?', type:'input', a:['1.097e7','1.1e7'], explain:'$1{,}097 \\cdot 10^7$.' },
{ 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:'1916', explain:'Эйнштейн (1916).' }
]},
fb3: { title:'Финал §34 — Лазеры', tag:'Финал III', xp:50, stages:[
{ q:'Первый лазер (год)?', type:'input', a:'1960', explain:'1960.' },
{ q:'Главное свойство лазера:', type:'mc', opts:['Высокая мощность','Когерентность','Полихромат.','Расходимость'], correct:1, explain:'Когерентность.' },
{ q:'Создал первый рабочий лазер:', type:'mc', opts:['Эйнштейн','Бор','Мейман','Планк'], correct:2, explain:'Т. Мейман.' },
{ q:'$\\lambda$ рубинового лазера (нм)?', type:'input', a:'694', explain:'694 нм.' },
{ q:'Минимум уровней для лазера:', type:'input', a:'3', explain:'3 уровня.' }
]}
};
function init(){
loadProgress(); initTheme(); buildParaSelector(); goTo('p1'); refreshProgressUI();
if(!STATE.achievements.has('start')) achievement('start');
}
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
</script>
</body>
</html>