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

931 lines
69 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 · Глава 5 · «Фотоны. Действия света»</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:#ec4899; --pri2:#be185d; --pri-soft:#fce7f3;
--acc:#f472b6; --acc2:#db2777; --acc-soft:#fbcfe8;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#1f0817; --card:#2a0e1f; --card-soft:#321125; --text:#fce7f3; --ink:#fce7f3; --muted:#f9a8d4; --border:#9d174d}
*{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,#9d174d 0%,#ec4899 55%,#fbcfe8 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(251,207,232,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 5';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:'h\03BD';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:#ec4899}.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:#9d174d;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 · Глава 5</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>Начало квантовой физики. Гипотеза Планка, фотоэффект Эйнштейна, давление света Лебедева и удивительный дуализм волна-частица. 3 параграфа + финал.</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> Начать § 27</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="h&#957;"><div class="sec-header"><span class="sec-num">§ 27</span><h2 class="sec-h">Фотоэффект. Гипотеза Планка</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="A"><div class="sec-header"><span class="sec-num">§ 28</span><h2 class="sec-h">Фотон. Уравнение Эйнштейна</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="&#955;"><div class="sec-header"><span class="sec-num">§ 29</span><h2 class="sec-h">Давление света. Дуализм</h2></div><div id="p3-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,#ec4899,#be185d)">&#9733;</span><h2 class="sec-h">Финал главы 5</h2></div><div id="final-body"></div></section>
</div>
<aside class="col-side"><div id="sidebar-content"></div></aside>
</main>
<footer class="foot">Интерактивный учебник «Физика 11» · Глава 5 · «Фотоны» · 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 = 3;
const _TB_SLUG = 'physics-11-ch5';
const PARAS = [
{ id:'p1', num:'§ 27', name:'Фотоэффект. Планк', sub:'$E = h\\nu$', built:true },
{ id:'p2', num:'§ 28', name:'Уравнение Эйнштейна', sub:'$h\\nu = A + E_к$', built:true },
{ id:'p3', num:'§ 29', name:'Давление света. Дуализм', sub:'$p_{фот} = h\\nu/c$', built:true },
{ id:'final', num:'★', name:'Финал главы 5', 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:'§27 — фотоэффект освоен',
p2_done:'§28 — уравнение Эйнштейна освоено',
p3_done:'§29 — давление света и дуализм освоены',
ch5_master:'Магистр фотонов — пройден финал главы 5!',
start:'Начало главы 5!',
ch5_done:'Глава 5 пройдена — Фотоны и действия света!'
};
function loadProgress(){
try{
const s=localStorage.getItem('physics11_ch5_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics11_ch5_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_ch5_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics11_ch5_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-ch5-'+(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(),
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:'§ 27 — Фотоэффект', rows:[['Гипотеза Планка','$E = h\\nu$'],['$h$ (Планк)','$6{,}63 \\cdot 10^{-34}$ Дж·с'],['Опыт','А. Г. Столетов (1888)']]},
p2:{title:'§ 28 — Уравнение Эйнштейна', rows:[['Формула','$h\\nu = A + \\dfrac{mv^2_{max}}{2}$'],['Граница','$\\nu_0 = A/h$'],['Запир. U','$eU_{зап} = E_{к,max}$']]},
p3:{title:'§ 29 — Дуализм', rows:[['$E$ фотона','$h\\nu$'],['$p$ фотона','$h\\nu/c = h/\\lambda$'],['де Бройль','$\\lambda = h/p$'],['Опыт Лебедева','1900']]},
final:{title:'Финал главы 5', rows:[['Боссов','3 интегральных'],['Покрытие','§27-§29'],['Награда','+150 XP + Магистр фотонов']]}
};
const TIPS=[
{sec:'p1',html:'§ 27 — Планк (1900): свет излучается порциями энергии $E = h\\nu$. Это объясняло излучение абс. чёрного тела и фотоэффект (Столетов).'},
{sec:'p2',html:'§ 28 — главное уравнение: $h\\nu = A + E_{к,max}$. Красная граница $\\nu_0 = A/h$ — частота, ниже которой фотоэффекта нет.'},
{sec:'p3',html:'§ 29 — фотон несёт импульс $p = h\\nu/c$. Поэтому свет давит! Опыт Лебедева доказал это в 1900 г. де Бройль (1923): любая частица — волна с $\\lambda = h/p$.'},
{sec:'final',html:'Финал главы 5: 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:'\xA727',p2:'\xA728',p3:'\xA729',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 buildP1(){
const box = document.getElementById('p1-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Излучение абсолютно чёрного тела — кризис классики', '§ 27.1',
'<p>В конце XIX в. физики не могли объяснить спектр излучения <b>абсолютно чёрного тела</b> (АЧТ).</p>'
+ '<p>Классическая физика (формула Рэлея – Джинса) приводила к «<b>ультрафиолетовой катастрофе</b>»: интенсивность излучения АЧТ при больших $\\nu$ стремилась к бесконечности.</p>'
+ '<p>В <b>1900 г.</b> <b>М. Планк</b> предложил <b>квантовую гипотезу</b>:</p>'
+ '<p style="text-align:center;margin:8px 0;font-weight:700;color:var(--pri)">«Атомы излучают электромагнитные волны <b>порциями</b> (квантами) с энергией $E = h\\nu$.»</p>'
+ '<p>где $h = 6{,}63 \\cdot 10^{-34}$ Дж·с — <b>постоянная Планка</b>, $\\nu$ — частота излучения.</p>'
+ '<p>Это решило проблему АЧТ и стало <b>началом квантовой физики</b>.</p>');
html += makeCard('theory', 'Открытие фотоэффекта — Г. Герц, А. Г. Столетов', '§ 27.2',
'<p><b>Фотоэффект</b> — испускание электронов веществом под действием света.</p>'
+ '<p><b>Открытие:</b> Г. Герц (1887). <b>А. Г. Столетов (1888)</b> исследовал явление количественно и установил <b>законы фотоэффекта</b>:</p>'
+ '<ol>'
+ '<li><b>I закон:</b> Сила фототока насыщения пропорциональна интенсивности падающего света (при $\\nu = const$).</li>'
+ '<li><b>II закон:</b> Максимальная кинетическая энергия фотоэлектронов зависит только от <b>частоты</b> света и не зависит от его интенсивности.</li>'
+ '<li><b>III закон (красная граница):</b> Для каждого вещества существует <b>минимальная частота</b> $\\nu_0$ света, ниже которой фотоэффект не наблюдается.</li>'
+ '</ol>'
+ '<p>Классическая волновая теория света <b>не смогла</b> объяснить II и III законы.</p>');
html += makeCard('rule', 'Схема установки и характеристика I(U)', '§ 27.3',
'<p>Вакуумный баллон: <b>катод</b> (на который падает свет) и <b>анод</b>. Цепь с амперметром и регулируемым напряжением.</p>'
+ '<p>При $U > 0$ ток насыщается — все вылетевшие электроны долетают до анода.</p>'
+ '<p>При $U < 0$ электроны замедляются. <b>Запирающее напряжение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$e U_{зап} = E_{к,max} = \\dfrac{m v^2_{max}}{2}$$</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Лаборатория фотоэффекта</span></div>'
+ '<div class="wg-help">Двигай частоту $\\nu$ и тормозящее $U$. При $\\nu < \\nu_0$ — фотоэффекта нет. При $\\nu > \\nu_0$ — электроны вылетают.</div>'
+ '<div class="fx-holder" id="fx-photo"></div>'
+ '<div class="fx-sliders" id="fx-photo-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Расчёты — энергия квантов</span></div>'
+ '<div class="wg-help">$E = h\\nu$, $h = 6{,}63 \\cdot 10^{-34}$ Дж·с. Решено: <b id="i1-calc-score">0</b> / 5.</div>'
+ '<div id="i1-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i1-calc-inp" placeholder="ответ"><button class="btn primary" id="i1-calc-go">Проверить</button></div><div class="feedback" id="i1-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Законы фотоэффекта</span></div>'
+ '<div class="wg-help">Решено: <b id="i1-th-score">0</b> / 5.</div>'
+ '<div id="i1-th-q" style="margin:8px 0"></div><div class="opts-row" id="i1-th-opts"></div><div class="feedback" id="i1-th-fb"></div></div>';
html += '<div id="boss-1-slot"></div>';
html += readButton('p1');
html += secNavFor('p1');
box.innerHTML = html;
ensureFx(()=>{
const pl = new PHYS.PhotoeffectLab(document.getElementById('fx-photo'), {width:580, height:280, nu:8e14, nu0:5.5e14, U:0});
const slBox = document.getElementById('fx-photo-sl');
const slNu = PHYS.util.slider({label:'ν (·10¹⁴ Гц)', min:3, max:12, step:0.1, value:8, fmt:v=>v.toFixed(1), onChange:v=>pl.setNu(v*1e14)});
const slNu0 = PHYS.util.slider({label:'ν₀ (·10¹⁴ Гц)', min:3, max:10, step:0.1, value:5.5, fmt:v=>v.toFixed(1), onChange:v=>pl.setNu0(v*1e14)});
const slU = PHYS.util.slider({label:'U (В)', min:-3, max:3, step:0.1, value:0, fmt:v=>v.toFixed(1), onChange:v=>pl.setU(v)});
slBox.innerHTML = slNu.html + slNu0.html + slU.html;
slNu.wire(slBox); slNu0.wire(slBox); slU.wire(slBox);
});
runQuizInput('i1-calc', I1_CALC_ITEMS, 16);
runQuizMC('i1-th', I1_TH_ITEMS, 12);
const bs = loadBossState('boss-1') || { stage:0, solved:false };
makeAndBindBoss('boss-1-slot', '1', BOSS_DEFS.b1, bs,
()=>saveBossState('boss-1', bs),
()=>{ bumpProgress('p1', 40); achievement('p1_done'); });
wireReadBtn('p1');
renderMath(box);
}
function buildP2(){
const box = document.getElementById('p2-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Фотон — квант ЭМ поля', '§ 28.1',
'<p>В <b>1905 г.</b> А. Эйнштейн распространил идею Планка на сам свет: свет — это не только излучается, но и <b>распространяется и поглощается</b> порциями. Эти порции назвали <b>фотонами</b>.</p>'
+ '<p><b>Свойства фотона</b>:</p>'
+ '<ul>'
+ '<li>Энергия: $E = h\\nu = hc/\\lambda$.</li>'
+ '<li>Импульс: $p = E/c = h\\nu/c = h/\\lambda$.</li>'
+ '<li>Масса покоя: $m_0 = 0$. Движется всегда со скоростью света $c$.</li>'
+ '<li>Спин: 1 (бозон).</li>'
+ '</ul>'
+ '<p>За эту работу Эйнштейн получил Нобелевскую премию 1921 г.</p>');
html += makeCard('rule', 'Уравнение Эйнштейна для фотоэффекта', '§ 28.2',
'<p>Энергия одного фотона $h\\nu$ при поглощении электроном идёт на:</p>'
+ '<ul>'
+ '<li><b>работу выхода</b> $A$ — энергию для отрыва электрона от металла;</li>'
+ '<li>кинетическую энергию вылетевшего электрона $E_к = mv^2/2$.</li>'
+ '</ul>'
+ '<p style="text-align:center;margin:8px 0;font-size:1.05rem">$$h\\nu = A + \\dfrac{mv^2_{max}}{2}$$</p>'
+ '<p>Это <b>уравнение Эйнштейна</b>. Оно полностью объясняет все три закона фотоэффекта.</p>');
html += makeCard('rule', 'Работа выхода и красная граница', '§ 28.3',
'<p><b>Работа выхода</b> $A$ — энергия для отрыва электрона от поверхности металла.</p>'
+ '<p>Типичные значения (эВ): Cs 1,9; K 2,3; Na 2,5; Zn 4,2; Cu 4,5; W 4,5.</p>'
+ '<p><b>Красная граница</b>: $\\nu_0 = A/h$, $\\lambda_0 = hc/A$.</p>'
+ '<p>Полезное соотношение: $E$ [эВ] $= 1240/\\lambda$ [нм].</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">График Eк,max(ν)</span></div>'
+ '<div class="wg-help">График <b>Eк,max = h(ν ν₀)</b>. Угловой коэффициент = постоянная Планка $h$. Точка пересечения с осью X — красная граница $\\nu_0$.</div>'
+ '<div class="fx-holder" id="fx-planck"></div>'
+ '<div class="fx-sliders" id="fx-planck-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Уравнение Эйнштейна — расчёты</span></div>'
+ '<div class="wg-help">$h\\nu = A + E_к$. Решено: <b id="i2-calc-score">0</b> / 5.</div>'
+ '<div id="i2-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i2-calc-inp" placeholder="ответ"><button class="btn primary" id="i2-calc-go">Проверить</button></div><div class="feedback" id="i2-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 3</span><span class="wg-title">Теория</span></div>'
+ '<div class="wg-help">Решено: <b id="i2-th-score">0</b> / 5.</div>'
+ '<div id="i2-th-q" style="margin:8px 0"></div><div class="opts-row" id="i2-th-opts"></div><div class="feedback" id="i2-th-fb"></div></div>';
html += '<div id="boss-2-slot"></div>';
html += readButton('p2');
html += secNavFor('p2');
box.innerHTML = html;
ensureFx(()=>{
const pl = new PHYS.PlanckLinear(document.getElementById('fx-planck'), {width:560, height:280, nu0:5e14, nu:8e14});
const slBox = document.getElementById('fx-planck-sl');
const slNu0 = PHYS.util.slider({label:'ν₀ (·10¹⁴ Гц)', min:3, max:10, step:0.1, value:5, fmt:v=>v.toFixed(1), onChange:v=>pl.setNu0(v*1e14)});
const slNu = PHYS.util.slider({label:'ν (·10¹⁴ Гц)', min:3, max:12, step:0.1, value:8, fmt:v=>v.toFixed(1), onChange:v=>pl.setNu(v*1e14)});
slBox.innerHTML = slNu0.html + slNu.html;
slNu0.wire(slBox); slNu.wire(slBox);
});
runQuizInput('i2-calc', I2_CALC_ITEMS, 18);
runQuizMC('i2-th', I2_TH_ITEMS, 14);
const bs = loadBossState('boss-2') || { stage:0, solved:false };
makeAndBindBoss('boss-2-slot', '2', BOSS_DEFS.b2, bs,
()=>saveBossState('boss-2', bs),
()=>{ bumpProgress('p2', 40); achievement('p2_done'); });
wireReadBtn('p2');
renderMath(box);
}
function buildP3(){
const box = document.getElementById('p3-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Импульс фотона. Давление света', '§ 29.1',
'<p>Из СТО для безмассовой частицы $E = pc$, поэтому импульс фотона:</p>'
+ '<p style="text-align:center;margin:8px 0">$$p_{фот} = \\dfrac{E}{c} = \\dfrac{h\\nu}{c} = \\dfrac{h}{\\lambda}$$</p>'
+ '<p>Поток фотонов, падающих на поверхность, передаёт ей импульс — это и есть <b>давление света</b>.</p>'
+ '<p>Для поверхности с коэф. отражения $R$:</p>'
+ '<p style="text-align:center;margin:8px 0">$$P = \\dfrac{I}{c}(1 + R)$$</p>'
+ '<p>где $I$ — интенсивность (Вт/м²). Для зеркала $R = 1$ давление вдвое больше, чем для чёрной поверхности.</p>');
html += makeCard('example', 'Опыт П. Н. Лебедева (1900)', '§ 29.2',
'<p>Идею давления света высказал Максвелл (1873). Но измерить его удалось только <b>П. Н. Лебедеву</b> в 1900 г.</p>'
+ '<p><b>Установка</b>: в вакуумированной камере на тонкой нити подвешен стерженёк с зеркальным и чёрным крылышками. Свет создавал крутящий момент.</p>'
+ '<p>Лебедев измерил давление света порядка <b>$\\sim 10^{-6}$ Па</b>.</p>'
+ '<p><b>Применения:</b> кометные хвосты направлены от Солнца; солнечный парус (зонд IKAROS 2010).</p>');
html += makeCard('rule', 'Корпускулярно-волновой дуализм', '§ 29.3',
'<p>Свет проявляет одновременно <b>волновые свойства</b> (интерференция, дифракция) и <b>корпускулярные</b> (фотоэффект, давление).</p>'
+ '<p style="text-align:center;margin:8px 0;font-weight:700;color:var(--pri)">«Свет — это и волна, и поток частиц (фотонов).»</p>'
+ '<p>Эти два описания <b>дополняют друг друга</b> (принцип <b>дополнительности</b> Н. Бора).</p>'
+ '<p>В <b>1923 г.</b> <b>Л. де Бройль</b>: дуализм присущ <b>любым</b> частицам. Электрон, протон, атом — это тоже волны:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\lambda = \\dfrac{h}{p} = \\dfrac{h}{mv}$$</p>'
+ '<p>В 1927 г. К. Дэвиссон и Л. Джермер наблюдали <b>дифракцию электронов</b> на кристаллах.</p>'
+ '<p><b>Принцип неопределённости Гейзенберга</b>: $\\Delta x \\cdot \\Delta p \\geq \\dfrac{h}{4\\pi}$.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 1</span><span class="wg-title">Расчёты — импульс фотона, де Бройль</span></div>'
+ '<div class="wg-help">$p = h\\nu/c$. $\\lambda_{дБ} = h/(mv)$. Решено: <b id="i3-calc-score">0</b> / 5.</div>'
+ '<div id="i3-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i3-calc-inp" placeholder="ответ"><button class="btn primary" id="i3-calc-go">Проверить</button></div><div class="feedback" id="i3-calc-fb"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Дуализм — теория</span></div>'
+ '<div class="wg-help">Решено: <b id="i3-th-score">0</b> / 5.</div>'
+ '<div id="i3-th-q" style="margin:8px 0"></div><div class="opts-row" id="i3-th-opts"></div><div class="feedback" id="i3-th-fb"></div></div>';
html += '<div id="boss-3-slot"></div>';
html += readButton('p3');
html += secNavFor('p3');
box.innerHTML = html;
runQuizInput('i3-calc', I3_CALC_ITEMS, 18);
runQuizMC('i3-th', I3_TH_ITEMS, 14);
const bs = loadBossState('boss-3') || { stage:0, solved:false };
makeAndBindBoss('boss-3-slot', '3', BOSS_DEFS.b3, bs,
()=>saveBossState('boss-3', bs),
()=>{ bumpProgress('p3', 40); achievement('p3_done'); });
wireReadBtn('p3');
renderMath(box);
}
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="card" style="background:linear-gradient(135deg,#fce7f3,#fbcfe8);border-color:#ec4899">'
+ '<div class="card-header"><div class="card-icon rule" style="background:#ec4899;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">Финал главы 5 — Фотоны</div><div class="card-num">★</div></div>'
+ '<div class="card-body">'
+ '<p>3 интегральных босса покрывают весь материал §27-§29. За победу: <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,#fbcfe8,#f472b6);border-color:#9d174d">'
+ '<div class="card-header"><div class="card-icon example" style="background:#9d174d;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>. Впереди — атом Бора (Глава 6).</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('ch5_master')){
achievement('ch5_master');
addXp(150, 'ch5-master-bonus');
}
if (!STATE.achievements.has('ch5_done')) achievement('ch5_done');
}
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('physics11_ch5_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('physics11_ch5_'+key, JSON.stringify(state)); }catch(e){} }
function runQuizMC(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('physics11_ch5_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_ch5_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_ch5_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_ch5_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:'$\\nu = 5 \\cdot 10^{14}$ Гц. $E$ фотона (Дж) ≈?', answer:['3.3e-19','3.31e-19'], explain:'$E = h\\nu \\approx 3{,}3 \\cdot 10^{-19}$.' },
{ q:'$\\lambda = 500$ нм. $E$ фотона (эВ) ≈?', answer:['2.48','2.5'], explain:'$E = 1240/500 = 2{,}48$ эВ.' },
{ q:'$\\nu_0 = 5 \\cdot 10^{14}$ Гц. $A$ выхода (Дж) ≈?', answer:['3.3e-19','3.31e-19'], explain:'$A = h\\nu_0$.' },
{ q:'Постоянная Планка $h$ (Дж·с)?', answer:['6.63e-34','6.626e-34'], explain:'$6{,}63 \\cdot 10^{-34}$.' },
{ q:'$\\lambda = 620$ нм. $E$ фотона (эВ)?', answer:'2', explain:'$1240/620 = 2$.' }
];
const I1_TH_ITEMS = [
{ q:'Кто предложил квантовую гипотезу?', opts:['Эйнштейн','Планк','Бор','Резерфорд'], correct:1, explain:'М. Планк (1900).' },
{ q:'Фотоэффект — это:', opts:['Поглощение света','Испускание электронов под действием света','Излучение нагретым телом','Распад атомов'], correct:1, explain:'Электроны вылетают.' },
{ q:'Что НЕ зависит от интенсивности (II закон)?', opts:['Сила тока','Eк,max электронов','Цвет света','λ'], correct:1, explain:'Eк,max ~ ν.' },
{ q:'Красная граница W $\\nu_0 \\approx 1{,}1\\cdot 10^{15}$ Гц. Свет с $\\nu = 8\\cdot 10^{14}$ Гц даст ф/э?', opts:['Да','Нет'], correct:1, explain:'$\\nu < \\nu_0$ — нет.' },
{ q:'Кто открыл фотоэффект?', opts:['Столетов','Лебедев','Герц','Бор'], correct:2, explain:'Г. Герц (1887).' }
];
const I2_CALC_ITEMS = [
{ q:'$A = 2$ эВ, $\\lambda = 400$ нм. $E_к$ (эВ)?', answer:['1.1','1.10'], explain:'$E = 1240/400 = 3{,}1$, $E_к = 1{,}1$.' },
{ q:'$A = 2{,}5$ эВ. $\\lambda_0$ (нм)?', answer:'496', explain:'$\\lambda_0 = 1240/2{,}5 = 496$.' },
{ q:'$U_{зап} = 1{,}5$ В. $E_{к,max}$ (эВ)?', answer:'1.5', explain:'$E = eU_{зап}/e = 1{,}5$.' },
{ q:'$h\\nu = 5$ эВ, $A = 2$ эВ. $U_{зап}$ (В)?', answer:'3', explain:'$E_к = 3$ эВ.' },
{ q:'$\\lambda = 248$ нм, $A = 3$ эВ. $E_к$ (эВ)?', answer:'2', explain:'$E = 1240/248 = 5$ эВ; $E_к = 2$ эВ.' }
];
const I2_TH_ITEMS = [
{ q:'Уравнение Эйнштейна:', opts:['$h\\nu = A$','$h\\nu = A + E_к$','$h\\nu = E_к - A$','$h\\nu = E_к$'], correct:1, explain:'$h\\nu = A + E_к$.' },
{ q:'Работа выхода зависит от:', opts:['$\\nu$','Интенсивности','Материала катода','Времени'], correct:2, explain:'$A$ — характеристика металла.' },
{ q:'Цезий имеет $A \\approx 1{,}9$ эВ. Это значит:', opts:['$\\lambda_0$ в УФ','$\\lambda_0$ в видимом красном','Нет ф/э','Любая λ'], correct:1, explain:'$\\lambda_0 \\approx 650$ нм.' },
{ q:'Если увеличить интенсивность вдвое:', opts:['$E_к$ вдвое','Ток насыщения вдвое','$\\nu_0$ меняется','Ничего'], correct:1, explain:'Больше фотонов.' },
{ q:'Угловой коэф. графика Eк(ν):', opts:['$A$','$h$','$\\nu_0$','$e$'], correct:1, explain:'Постоянная Планка.' }
];
const I3_CALC_ITEMS = [
{ q:'$\\nu = 6 \\cdot 10^{14}$ Гц. $p$ фотона (кг·м/с) ≈?', answer:['1.33e-27','1.32e-27'], explain:'$p = h\\nu/c \\approx 1{,}33 \\cdot 10^{-27}$.' },
{ q:'Электрон $v = 10^6$ м/с. $\\lambda_{дБ}$ (м) ≈?', answer:['7.3e-10','7.28e-10'], explain:'$\\lambda = h/(mv) \\approx 7{,}3 \\cdot 10^{-10}$.' },
{ q:'$\\lambda = 600$ нм. $p$ фотона (кг·м/с)?', answer:['1.1e-27','1.105e-27'], explain:'$p = h/\\lambda \\approx 1{,}1 \\cdot 10^{-27}$.' },
{ q:'Зеркало $R = 1$. $I = 1000$ Вт/м². $P$ (Па) ≈?', answer:['6.67e-6','6.7e-6'], explain:'$P = 2I/c \\approx 6{,}7 \\cdot 10^{-6}$.' },
{ q:'Год опыта Лебедева?', answer:'1900', explain:'1900.' }
];
const I3_TH_ITEMS = [
{ q:'Импульс фотона:', opts:['$p = mv$','$p = h\\nu/c$','$p = E$','$p = 0$'], correct:1, explain:'$h\\nu/c$.' },
{ q:'Кто измерил давление света?', opts:['Эйнштейн','Планк','Лебедев','Бор'], correct:2, explain:'П. Н. Лебедев (1900).' },
{ q:'Хвосты комет направлены:', opts:['К Солнцу','От Солнца','Вверх','Перпендикулярно'], correct:1, explain:'От Солнца.' },
{ q:'Гипотеза де Бройля (1923):', opts:['Только свет дуален','Любая частица — это волна','Только электрон','Только в атоме'], correct:1, explain:'Любая частица.' },
{ q:'Кто показал дифракцию электронов?', opts:['Резерфорд','Бор','Дэвиссон и Джермер','Эйнштейн'], correct:2, explain:'1927 г.' }
];
const BOSS_DEFS = {
b1: { title:'Босс §27 — Фотоэффект, Планк', tag:'§27', xp:75, stages:[
{ q:'Постоянная Планка (Дж·с)?', type:'input', a:['6.63e-34','6.626e-34'], explain:'$6{,}63 \\cdot 10^{-34}$.' },
{ q:'$\\nu = 4\\cdot 10^{14}$ Гц. $E$ (Дж) ≈?', type:'input', a:['2.65e-19','2.6e-19'], explain:'$h\\nu$.' },
{ q:'Кто исследовал законы фотоэффекта?', type:'mc', opts:['Планк','Эйнштейн','Столетов','Лебедев'], correct:2, explain:'Столетов (1888).' },
{ q:'III закон — это:', type:'mc', opts:['Закон сохр.','Красная граница','Закон сложения','Закон отражения'], correct:1, explain:'$\\nu_0$.' },
{ q:'Гипотеза Планка (год)?', type:'input', a:'1900', explain:'1900.' }
]},
b2: { title:'Босс §28 — Уравнение Эйнштейна', tag:'§28', xp:80, stages:[
{ q:'$h\\nu = A + ?$', type:'mc', opts:['$E_п$','$E_{к,max}$','$mc^2$','$U$'], correct:1, explain:'$E_{к,max}$.' },
{ q:'$A = 3$ эВ, $\\lambda = 248$ нм ($E = 5$ эВ). $E_к$ (эВ)?', type:'input', a:'2', explain:'$5 - 3 = 2$.' },
{ q:'$A = 2{,}48$ эВ. $\\lambda_0$ (нм)?', type:'input', a:'500', explain:'$1240/2{,}48 = 500$.' },
{ q:'$U_{зап} = 2$ В. $E_к$ (эВ)?', type:'input', a:'2', explain:'$eU_{зап}/e$.' },
{ q:'Угловой коэф. Eк(ν):', type:'mc', opts:['$A$','$h$','$\\nu_0$','$c$'], correct:1, explain:'$h$.' }
]},
b3: { title:'Босс §29 — Давление света, дуализм', tag:'§29', xp:75, stages:[
{ q:'$p$ фотона = ?', type:'mc', opts:['$mv$','$h\\nu/c$','$E$','$\\nu/h$'], correct:1, explain:'$h\\nu/c$.' },
{ q:'$\\lambda = 663$ нм. $p$ (кг·м/с)?', type:'input', a:'1e-27', explain:'$h/\\lambda = 10^{-27}$.' },
{ q:'Формула де Бройля:', type:'mc', opts:['$\\lambda = h/p$','$\\lambda = c/\\nu$','$\\lambda = pmc$','$\\lambda = hp$'], correct:0, explain:'$h/p$.' },
{ q:'Опыт Лебедева подтверждает:', type:'mc', opts:['Эфир','Давление света','Принцип Гюйгенса','Дифракцию'], correct:1, explain:'Давление.' },
{ q:'Дуализм означает:', type:'mc', opts:['Свет = поток воды','Свет = волна и частица','Свет = звук','Свет = эфир'], correct:1, explain:'Корпускулярно-волновой.' }
]}
};
const FINAL_BOSS_DEFS = {
fb1: { title:'Финал §27 — Гипотеза Планка', tag:'Финал I', xp:50, stages:[
{ q:'$E = h\\nu$, автор?', type:'mc', opts:['Эйнштейн','Планк','Бор','Резерфорд'], correct:1, explain:'Планк.' },
{ q:'$\\nu = 5 \\cdot 10^{14}$ Гц. $E$ (Дж) ≈?', type:'input', a:['3.3e-19','3.31e-19'], explain:'$h\\nu$.' },
{ q:'Красная граница — это:', type:'mc', opts:['Мин. ν для ф/э','Макс. ν','Средняя ν','Любая'], correct:0, explain:'$\\nu_0$.' },
{ q:'Сила тока в ф/э зависит от:', type:'mc', opts:['Интенсивности','Частоты','Цвета','Времени'], correct:0, explain:'I закон.' },
{ q:'$A$ для металла зависит от:', type:'mc', opts:['ν','Материала','Интенсивности','Времени'], correct:1, explain:'Материала.' }
]},
fb2: { title:'Финал §28 — Уравнение Эйнштейна', tag:'Финал II', xp:50, stages:[
{ q:'$h\\nu = A + ?$', type:'mc', opts:['$E_к$','$mc^2$','$U$','$\\nu_0$'], correct:0, explain:'$E_к$.' },
{ q:'$A = 4$ эВ, $\\lambda = 200$ нм. $E_к$ (эВ)?', type:'input', a:'2.2', explain:'$1240/200 = 6{,}2$; $E_к = 2{,}2$.' },
{ q:'$U_{зап} = 1$ В. $E_к$ (эВ)?', type:'input', a:'1', explain:'$eU$.' },
{ q:'$\\lambda_0$ — это:', type:'mc', opts:['Длина лазера','Длина красной границы','де Бройль','Длина в среде'], correct:1, explain:'$hc/A$.' },
{ q:'Угловой коэф. Eк(ν):', type:'mc', opts:['$A$','$h$','$\\nu_0$','$c$'], correct:1, explain:'$h$.' }
]},
fb3: { title:'Финал §29 — Дуализм', tag:'Финал III', xp:50, stages:[
{ q:'$p$ фотона:', type:'mc', opts:['$mv$','$h\\nu/c$','$E$','$h/\\nu$'], correct:1, explain:'$h\\nu/c$.' },
{ q:'$\\lambda_{дБ}$ для электрона $v = 2\\cdot 10^6$ м/с (м)?', type:'input', a:['3.64e-10','3.6e-10'], explain:'$\\approx 3{,}64 \\cdot 10^{-10}$.' },
{ q:'Кто измерил давление света?', type:'mc', opts:['Бор','Лебедев','Эйнштейн','Планк'], correct:1, explain:'Лебедев.' },
{ q:'Дуализм у:', type:'mc', opts:['Только света','Только электрона','Любых микрочастиц','Только в вакууме'], correct:2, explain:'Все.' },
{ q:'$P_{зеркало}$ относительно $P_{чёрной}$:', type:'mc', opts:['Меньше','Равно','Больше в 2 раза','Больше в 4 раза'], correct:2, explain:'$P_з = 2P_ч$.' }
]}
};
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>