Files
Learn_System/frontend/textbooks/physics_11_ch3.html
T
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

1688 lines
143 KiB
HTML
Raw 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 · Глава 3 · «Оптика»</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:#d97706; --pri2:#b45309; --pri-soft:#fef3c7;
--acc:#f59e0b; --acc2:#d97706; --acc-soft:#fde68a;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#1a0e02; --card:#241704; --card-soft:#2b1d08; --text:#fef3c7; --ink:#fef3c7; --muted:#fcd34d; --border:#92400e}
*{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,#92400e 0%,#d97706 55%,#fcd34d 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(252,211,77,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 3';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:'\25C7';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(140px,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:.35}
.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{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:#92400e;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)}
.stub-note{padding:18px 22px;background:linear-gradient(135deg,var(--pri-soft),var(--warn-bg));border:1.5px dashed var(--pri);border-radius:13px;text-align:center;color:var(--text);margin-bottom:14px}
.stub-note h3{font-family:'Unbounded',sans-serif;color:var(--pri2);margin-bottom:8px;font-size:1.05rem}
.stub-note p{color:var(--muted);font-size:.9rem;line-height:1.55}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Физика 11 · Глава 3</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>Оптика — наука о свете</hsearch>
<p>Самая большая глава курса. От электромагнитной природы света до интерференции, дифракции, преломления и оптических приборов (микроскоп, телескоп). 10 параграфов + финал.</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> Начать § 14</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="c"><div class="sec-header"><span class="sec-num">§ 14</span><h2 class="sec-h">ЭМ природа света. Скорость света</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="&#8767;"><div class="sec-header"><span class="sec-num">§ 15</span><h2 class="sec-h">Интерференция света</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="&#9783;"><div class="sec-header"><span class="sec-num">§ 16</span><h2 class="sec-h">Принцип Гюйгенса – Френеля. Дифракция</h2></div><div id="p3-body"></div></section>
<section id="sec-p4" class="sec" data-watermark="&#9700;"><div class="sec-header"><span class="sec-num">§ 17</span><h2 class="sec-h">Отражение света. Зеркала</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="&#8634;"><div class="sec-header"><span class="sec-num">§ 18</span><h2 class="sec-h">Сферические зеркала</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec" data-watermark="&#8736;"><div class="sec-header"><span class="sec-num">§ 19</span><h2 class="sec-h">Закон преломления. Полное отражение</h2></div><div id="p6-body"></div></section>
<section id="sec-p7" class="sec" data-watermark="&#9651;"><div class="sec-header"><span class="sec-num">§ 20</span><h2 class="sec-h">Прохождение света через опт. элементы</h2></div><div id="p7-body"></div></section>
<section id="sec-p8" class="sec" data-watermark="&#9678;"><div class="sec-header"><span class="sec-num">§ 21</span><h2 class="sec-h">Формула тонкой линзы</h2></div><div id="p8-body"></div></section>
<section id="sec-p9" class="sec" data-watermark="&#9728;"><div class="sec-header"><span class="sec-num">§ 22</span><h2 class="sec-h">Действительные изображения</h2></div><div id="p9-body"></div></section>
<section id="sec-p10" class="sec" data-watermark="&#128301;"><div class="sec-header"><span class="sec-num">§ 23</span><h2 class="sec-h">Увеличение угла зрения</h2></div><div id="p10-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,#d97706,#f59e0b)">&#9733;</span><h2 class="sec-h">Финал главы 3</h2></div><div id="final-body"></div></section>
</div>
<aside class="col-side"><div id="sidebar-content"></div></aside>
</main>
<footer class="foot">Интерактивный учебник «Физика 11» · Глава 3 · «Оптика» · 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 = 10;
const _TB_SLUG = 'physics-11-ch3';
const PARAS = [
{ id:'p1', num:'§ 14', name:'ЭМ природа света', sub:'$c = 3 \\cdot 10^8$ м/с', built:true },
{ id:'p2', num:'§ 15', name:'Интерференция', sub:'$\\Delta = k\\lambda$', built:true },
{ id:'p3', num:'§ 16', name:'Дифракция', sub:'$d\\sin\\varphi = k\\lambda$', built:true },
{ id:'p4', num:'§ 17', name:'Отражение, зеркала', sub:'$\\angle_{пад} = \\angle_{отр}$', built:true },
{ id:'p5', num:'§ 18', name:'Сферические зеркала', sub:'$1/d + 1/f = 1/F$', built:true },
{ id:'p6', num:'§ 19', name:'Преломление', sub:'$n_1\\sin\\alpha = n_2\\sin\\beta$', built:true },
{ id:'p7', num:'§ 20', name:'Опт. элементы', sub:'Призма, оптоволокно', built:true },
{ id:'p8', num:'§ 21', name:'Тонкая линза', sub:'$1/d + 1/f = 1/F$', built:true },
{ id:'p9', num:'§ 22', name:'Действ. изображения', sub:'Фотоаппарат, проектор', built:true },
{ id:'p10', num:'§ 23', name:'Угол зрения', sub:'Лупа, микроскоп, телескоп', built:true },
{ id:'final', num:'★', name:'Финал главы 3', sub:'Все 10 параграфов', 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:'§14 — ЭМ природа света освоена',
p2_done:'§15 — интерференция освоена',
p3_done:'§16 — дифракция освоена',
p4_done:'§17 — отражение и зеркала освоены',
p5_done:'§18 — сферические зеркала освоены',
p6_done:'§19 — закон Снелла освоен',
p7_done:'§20 — призма и оптоволокно освоены',
p8_done:'§21 — тонкая линза освоена',
p9_done:'§22 — фотоаппарат и проектор освоены',
p10_done:'§23 — оптические приборы освоены',
ch3_master:'Магистр оптики — пройден финал главы 3!',
start:'Начало главы 3!',
ch3_done:'Глава 3 пройдена — Оптика!'
};
function loadProgress(){
try{
const s=localStorage.getItem('physics11_ch3_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('physics11_ch3_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_ch3_progress', JSON.stringify(STATE.progress));
localStorage.setItem('physics11_ch3_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-ch3-'+(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(), p6:()=>buildP6(), p7:()=>buildP7(),
p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(),
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; ensureBuilt(id);
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
buildSidebar(id);
window.scrollTo({top:0,behavior:'smooth'});
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
markLastPara(id);
}
const SIDEBARS = {
p1:{title:'Шпаргалка § 14', rows:[['$c$ в вакууме','$3 \\cdot 10^8$ м/с'],['Опыт Рёмера','затмения Юпитера'],['Опыт Майкельсона','зеркала, $\\Delta t$'],['Природа','ЭМ волна, поперечная'],['Связь','$c = \\lambda \\nu$']]},
p2:{title:'Шпаргалка § 15', rows:[['Условие max','$\\Delta = k\\lambda$'],['Условие min','$\\Delta = (2k+1)\\lambda/2$'],['Когерентность','$\\omega$ и $\\Delta\\varphi$ = const'],['Опыт Юнга','2 щели → полосы'],['Кольца Ньютона','тонкие плёнки']]},
p3:{title:'Шпаргалка § 16', rows:[['Гюйгенс','каждая точка фронта — источник'],['Френель','+ интерференция вторичных'],['Решётка','$d\\sin\\varphi = k\\lambda$'],['$k$','порядок'],['$d$','период решётки']]},
p4:{title:'Шпаргалка § 17', rows:[['Прямолинейное','тени'],['Закон отражения','$\\angle_{пад} = \\angle_{отр}$'],['Плоское зеркало','мнимое, прямое, равное'],['Симметрия','$d_{объект} = d_{изобр}$ от зеркала']]},
p5:{title:'§ 18 — Сферические зеркала', rows:[['Формула','$\\dfrac{1}{d}+\\dfrac{1}{f}=\\dfrac{1}{F}$'],['Фокус','$F = R/2$'],['Увелич.','$\\Gamma = -f/d$']]},
p6:{title:'§ 19 — Преломление', rows:[['Закон','$n_1\\sin\\alpha = n_2\\sin\\beta$'],['Пок. прелом.','$n = c/v$'],['ПВО','$\\sin\\alpha_{кр}=n_2/n_1$ (если $n_1>n_2$)']]},
p7:{title:'§ 20 — Опт. элементы', rows:[['Призма','дисперсия $n(\\lambda)$'],['Пластинка','параллельный сдвиг'],['Оптоволокно','полное внутр. отражение']]},
p8:{title:'§ 21 — Тонкая линза', rows:[['Формула','$\\dfrac{1}{d}+\\dfrac{1}{f}=\\dfrac{1}{F}$'],['Опт. сила','$D = 1/F$ (дптр)'],['Увелич.','$\\Gamma = -f/d$']]},
p9:{title:'§ 22 — Фотоаппарат, проектор', rows:[['Фото','$d > 2F$, изобр. уменьш. перевёрнутое'],['Проектор','$F < d < 2F$, увелич. перевёрнутое']]},
p10:{title:'§ 23 — Оптические приборы', rows:[['Лупа','$\\Gamma = 25\\text{см}/F$'],['Микроскоп','$\\Gamma \\approx \\Gamma_{об} \\cdot 25\\text{см}/F_{ок}$'],['Телескоп','$\\Gamma = F_{об}/F_{ок}$']]},
final:{title:'Финал главы 3 — Оптика', rows:[['Боссов','5 интегрированных'],['Покрытие','§14-§23 (10 параграфов)'],['Награда','+200 XP + Магистр оптики']]}
};
const TIPS=[
{sec:'p1',html:'§ 14 — главное: свет — это ЭМ волна, $c = 3 \\cdot 10^8$ м/с в вакууме. Первое определение скорости — Рёмер по затмениям спутников Юпитера.'},
{sec:'p2',html:'§ 15 — интерференция = наложение когерентных волн. Условие max: $\\Delta = k\\lambda$. Опыт Юнга с двумя щелями.'},
{sec:'p3',html:'§ 16 — двигай $d$ и $\\lambda$. Чем меньше $d$, тем шире разлёт спектров. Главный max — $k=0$ (прямо).'},
{sec:'p4',html:'§ 17 — закон отражения: угол падения = углу отражения. Изображение в плоском зеркале — мнимое, симметричное.'},
{sec:'p5',html:'§ 18 — фокус сферического зеркала $F = R/2$. Формула $1/d + 1/f = 1/F$ работает для любого зеркала. Если $f<0$ — изображение мнимое (за зеркалом).'},
{sec:'p6',html:'§ 19 — закон Снелла $n_1\\sin\\alpha = n_2\\sin\\beta$. Если идём из плотной среды в менее плотную и $\\alpha > \\alpha_{кр}$ — полное внутреннее отражение.'},
{sec:'p7',html:'§ 20 — призма разлагает белый свет в спектр, потому что $n(\\lambda)$ для разных $\\lambda$ разный. Оптоволокно работает за счёт ПВО.'},
{sec:'p8',html:'§ 21 — тонкая линза. Если $f>0$ — действительное изображение; если $f<0$ — мнимое. Оптическая сила $D=1/F$ измеряется в дптр (м⁻¹).'},
{sec:'p9',html:'§ 22 — фотоаппарат: $d > 2F$ ⇒ уменьшенное действительное. Проектор: $F < d < 2F$ ⇒ увеличенное действительное.'},
{sec:'p10',html:'§ 23 — лупа = одна линза, $\\Gamma = 25/F$ см. Микроскоп = двойная система. Телескоп: чем больше $F_{об}/F_{ок}$, тем больше увеличение.'},
{sec:'final',html:'Финал главы 3: 5 интегрированных боссов покрывают весь материал §14–§23.'}
];
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:'\xA714',p2:'\xA715',p3:'\xA716',p4:'\xA717',p5:'\xA718',p6:'\xA719',p7:'\xA720',p8:'\xA721',p9:'\xA722',p10:'\xA723',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); }
/* ===== §14 ЭМ природа света ===== */
function buildP1(){
const box = document.getElementById('p1-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Природа света', '§ 14.1',
'<p>На протяжении столетий шёл спор о природе света:</p>'
+ '<ul>'
+ '<li><b>Корпускулярная теория</b> (Ньютон, XVII в.) — свет как поток частиц.</li>'
+ '<li><b>Волновая теория</b> (Гюйгенс, Юнг, Френель) — свет как волна.</li>'
+ '</ul>'
+ '<p>В XIX в. <b>Максвелл</b> теоретически показал, что свет — это <b>электромагнитная волна</b>. Это подтвердил Г. Герц (1888).</p>'
+ '<p>В XX в. (фотоны, квантовая теория) выяснилось, что свет обладает и волновыми, и корпускулярными свойствами — <b>корпускулярно-волновой дуализм</b> (см. §29).</p>'
+ '<p>Видимый свет — это ЭМ волны с длиной волны $\\lambda \\approx 380{-}760$ нм.</p>');
html += makeCard('rule', 'Скорость света', '§ 14.2',
'<p>Скорость света в вакууме — фундаментальная константа:</p>'
+ '<p style="text-align:center;margin:8px 0">$$c = 2{,}998 \\cdot 10^8 \\text{ м/с} \\approx 3 \\cdot 10^8 \\text{ м/с}$$</p>'
+ '<p>В среде с показателем преломления $n$ скорость света:</p>'
+ '<p style="text-align:center;margin:6px 0">$$v = \\dfrac{c}{n}$$</p>'
+ '<p><b>Первое определение скорости света — О. Рёмер (1676)</b>: по запаздыванию затмений спутника Юпитера Ио в зависимости от положения Земли на орбите. Получил $\\approx 2{,}1 \\cdot 10^8$ м/с.</p>'
+ '<p><b>А. Майкельсон (1879-1926)</b>: с помощью системы вращающихся зеркал получил $c = (2{,}9977 \\pm 0{,}0001) \\cdot 10^8$ м/с.</p>'
+ '<p>Скорость света связана с электрической и магнитной постоянными:</p>'
+ '<p style="text-align:center;margin:6px 0">$$c = \\dfrac{1}{\\sqrt{\\varepsilon_0 \\mu_0}}$$</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">$v = c/n$. Решено: <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">Инт. 2</span><span class="wg-title">Природа света</span></div>'
+ '<div class="wg-help">Решено: <b id="i1-nat-score">0</b> / 5.</div>'
+ '<div id="i1-nat-q" style="margin:8px 0"></div><div class="opts-row" id="i1-nat-opts"></div><div class="feedback" id="i1-nat-fb"></div></div>';
html += '<div id="boss-1-slot"></div>';
html += readButton('p1');
html += secNavFor('p1');
box.innerHTML = html;
runQuizInput('i1-calc', I1_CALC_ITEMS, 14);
runQuizMC('i1-nat', I1_NAT_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);
}
/* ===== §15 Интерференция ===== */
function buildP2(){
const box = document.getElementById('p2-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Сложение волн. Когерентность', '§ 15.1',
'<p><b>Интерференция</b> — устойчивое в пространстве распределение чередующихся максимумов и минимумов амплитуд при сложении когерентных волн.</p>'
+ '<p><b>Когерентные волны</b> — волны с одинаковой частотой и постоянной разностью фаз.</p>'
+ '<p>Если разность хода двух волн $\\Delta = r_2 - r_1$, то:</p>'
+ '<p style="text-align:center;margin:6px 0">$$\\boxed{\\Delta = k\\lambda \\text{ (max)}, \\quad \\Delta = (2k+1)\\dfrac{\\lambda}{2} \\text{ (min)}}$$</p>'
+ '<p>где $k = 0, \\pm 1, \\pm 2, \\ldots$ — порядок максимума/минимума.</p>'
+ '<p>В точках максимума волны приходят <b>в фазе</b> (усиливают друг друга), в минимумах — <b>в противофазе</b> (гасят).</p>');
html += makeCard('example', 'Опыт Юнга (1801)', '§ 15.2',
'<p>Свет от одного источника проходит через 2 узкие близкие щели. На экране наблюдаются <b>интерференционные полосы</b> — чередование светлых и тёмных полос.</p>'
+ '<p>Это окончательно доказало волновую природу света. До этого опыта (после Ньютона) преобладала корпускулярная теория.</p>'
+ '<p>Другие примеры интерференции:</p>'
+ '<ul>'
+ '<li><b>Кольца Ньютона</b> — в тонкой воздушной прослойке между линзой и стеклом.</li>'
+ '<li><b>Радужные плёнки</b> на мыльных пузырях, бензине на воде.</li>'
+ '<li><b>Просветление оптики</b> — антибликовое покрытие объективов.</li>'
+ '</ul>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Опыт Юнга с двумя щелями</span></div>'
+ '<div class="wg-help">Лазер проходит через 2 щели, на экране — интерференционная картина. Двигай $d$ (расстояние между щелями) и $\\lambda$. При уменьшении $d$ полосы шире, при увеличении $\\lambda$ — тоже шире.</div>'
+ '<div class="fx-holder" id="fx-ts"></div>'
+ '<div class="fx-sliders" id="fx-ts-sl"></div></div>';
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Инт. 2</span><span class="wg-title">Расчёт условий max/min</span></div>'
+ '<div class="wg-help">$\\Delta = k\\lambda$ — max; $\\Delta = (2k+1)\\lambda/2$ — min. Решено: <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="max/min или число"><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 tsEl = document.getElementById('fx-ts');
const ts = new PHYS.TwoSlit(tsEl, {width:540, height:200, d:0.5, lambda:0.05});
const slBox = document.getElementById('fx-ts-sl');
const slD = PHYS.util.slider({label:'d (отн.)', min:0.2, max:1.5, step:0.05, value:0.5, fmt:v=>v.toFixed(2), onChange:v=>ts.setD(v)});
const slL = PHYS.util.slider({label:'λ (отн.)', min:0.02, max:0.10, step:0.005, value:0.05, fmt:v=>v.toFixed(3), onChange:v=>ts.setLambda(v)});
slBox.innerHTML = slD.html + slL.html;
slD.wire(slBox); slL.wire(slBox);
});
runQuizInput('i2-calc', I2_CALC_ITEMS, 14);
runQuizMC('i2-th', I2_TH_ITEMS, 12);
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);
}
/* ===== §16 Дифракция ===== */
function buildP3(){
const box = document.getElementById('p3-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Принцип Гюйгенса – Френеля', '§ 16.1',
'<p><b>Принцип Гюйгенса (1678):</b> каждая точка волнового фронта является источником вторичных сферических волн, огибающая которых даёт новое положение волнового фронта.</p>'
+ '<p><b>Дополнение Френеля (1818):</b> вторичные волны когерентны и интерферируют между собой.</p>'
+ '<p>Принцип Гюйгенса – Френеля объясняет <b>дифракцию</b> — отклонение волн от прямолинейного распространения, огибание препятствий.</p>');
html += makeCard('rule', 'Дифракционная решётка', '§ 16.2',
'<p><b>Дифракционная решётка</b> — пластинка с большим числом параллельных штрихов на одинаковом расстоянии $d$ (<b>период решётки</b>).</p>'
+ '<p>Если на решётку падает свет с длиной волны $\\lambda$, главные максимумы дифракции наблюдаются под углами $\\varphi$:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\boxed{d \\sin\\varphi = k\\lambda}$$</p>'
+ '<p>где $k = 0, \\pm 1, \\pm 2, \\ldots$ — <b>порядок главного максимума</b>.</p>'
+ '<ul>'
+ '<li>$k = 0$ — центральный максимум (прямо).</li>'
+ '<li>$k = \\pm 1$ — первый порядок.</li>'
+ '<li>$d \\sin\\varphi \\le 1$, поэтому max порядка $k_{max} \\le d/\\lambda$.</li>'
+ '</ul>'
+ '<p>Применения: спектральный анализ (разложение света на спектр), CD/DVD-диски (радужные блики).</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">Свет проходит через решётку и расходится под углами разных порядков $k$. Меняй $d$ и $\\lambda$. Цвет луча соответствует длине волны (видимый спектр 380-760 нм).</div>'
+ '<div class="fx-holder" id="fx-dg"></div>'
+ '<div class="fx-sliders" id="fx-dg-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">$d \\sin\\varphi = k\\lambda$. Помни: 1 нм = $10^{-9}$ м, 1 мкм = $10^{-6}$ м. Решено: <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">Инт. 3</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;
ensureFx(()=>{
const dgEl = document.getElementById('fx-dg');
const dg = new PHYS.DiffractionGrating(dgEl, {width:540, height:220, d:2e-6, lambda:550e-9});
const slBox = document.getElementById('fx-dg-sl');
const slD = PHYS.util.slider({label:'d (мкм)', min:0.5, max:5.0, step:0.1, value:2.0, fmt:v=>v.toFixed(1), onChange:v=>dg.setD(v*1e-6)});
const slL = PHYS.util.slider({label:'λ (нм)', min:380, max:760, step:10, value:550, fmt:v=>v.toFixed(0), onChange:v=>dg.setLambda(v*1e-9)});
slBox.innerHTML = slD.html + slL.html;
slD.wire(slBox); slL.wire(slBox);
});
runQuizInput('i3-calc', I3_CALC_ITEMS, 14);
runQuizMC('i3-th', I3_TH_ITEMS, 12);
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);
}
/* ===== §17 Отражение, зеркала ===== */
function buildP4(){
const box = document.getElementById('p4-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Прямолинейное распространение и отражение', '§ 17.1',
'<p>В однородной прозрачной среде свет распространяется <b>прямолинейно</b>. Это следствие того, что фронт волны идёт по нормали к самому себе. Этим объясняются <b>тени</b> и <b>полутени</b>.</p>'
+ '<p>На границе раздела двух сред свет частично <b>отражается</b>, частично <b>преломляется</b>.</p>'
+ '<p><b>Закон отражения:</b></p>'
+ '<ol>'
+ '<li>Луч падающий, отражённый и перпендикуляр к границе в точке падения лежат в одной плоскости.</li>'
+ '<li><b>Угол падения равен углу отражения</b>: $\\angle_{пад} = \\angle_{отр}$.</li>'
+ '</ol>');
html += makeCard('rule', 'Плоское зеркало', '§ 17.2',
'<p><b>Плоское зеркало</b> даёт изображение, обладающее свойствами:</p>'
+ '<ul>'
+ '<li><b>Мнимое</b> — лучи не сходятся, изображение «за зеркалом» получается продолжением отражённых лучей.</li>'
+ '<li><b>Прямое</b> — не перевёрнуто.</li>'
+ '<li><b>Симметрично</b> объекту относительно плоскости зеркала.</li>'
+ '<li><b>Равное</b> — размеры объекта и изображения совпадают.</li>'
+ '</ul>'
+ '<p>Расстояние от изображения до зеркала равно расстоянию от объекта до зеркала: $d_{объект} = d_{изобр}$.</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">Жёлтая стрелка — объект, прозрачная — мнимое изображение. Двигай объект ползунком, чтобы увидеть симметрию. Зелёные лучи показывают, как наблюдатель «видит» изображение «за зеркалом».</div>'
+ '<div class="fx-holder" id="fx-mir"></div>'
+ '<div class="fx-sliders" id="fx-mir-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">Углы измеряются от <b>нормали</b> к зеркалу (не от поверхности!). Решено: <b id="i4-calc-score">0</b> / 5.</div>'
+ '<div id="i4-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i4-calc-inp" placeholder="ответ (°)"><button class="btn primary" id="i4-calc-go">Проверить</button></div><div class="feedback" id="i4-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="i4-img-score">0</b> / 5.</div>'
+ '<div id="i4-img-q" style="margin:8px 0"></div><div class="opts-row" id="i4-img-opts"></div><div class="feedback" id="i4-img-fb"></div></div>';
html += '<div id="boss-4-slot"></div>';
html += readButton('p4');
html += secNavFor('p4');
box.innerHTML = html;
ensureFx(()=>{
const mEl = document.getElementById('fx-mir');
const m = new PHYS.FlatMirror(mEl, {width:540, height:240, objX:120, objH:50});
const slBox = document.getElementById('fx-mir-sl');
const slX = PHYS.util.slider({label:'расстояние объекта (px)', min:40, max:220, step:10, value:120, fmt:v=>v.toFixed(0), onChange:v=>m.setObjX(v)});
slBox.innerHTML = slX.html;
slX.wire(slBox);
});
runQuizInput('i4-calc', I4_CALC_ITEMS, 14);
runQuizMC('i4-img', I4_IMG_ITEMS, 12);
const bs = loadBossState('boss-4') || { stage:0, solved:false };
makeAndBindBoss('boss-4-slot', '4', BOSS_DEFS.b4, bs,
()=>saveBossState('boss-4', bs),
()=>{ bumpProgress('p4', 40); achievement('p4_done'); });
wireReadBtn('p4');
renderMath(box);
}
/* ===== §18 Сферические зеркала ===== */
function buildP5(){
const box = document.getElementById('p5-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Сферические зеркала', '§ 18.1',
'<p><b>Сферическое зеркало</b> — отполированная часть сферической поверхности. Бывают двух типов:</p>'
+ '<ul>'
+ '<li><b>Вогнутое</b> (собирающее) — отражающая поверхность с внутренней стороны сферы.</li>'
+ '<li><b>Выпуклое</b> (рассеивающее) — отражающая поверхность с наружной стороны.</li>'
+ '</ul>'
+ '<p>Основные точки зеркала: <b>оптический центр</b> $O$ (середина зеркала), <b>центр сферы</b> $C$, <b>главный фокус</b> $F$. Прямая, проходящая через $O$ и $C$, — <b>главная оптическая ось</b>.</p>'
+ '<p><b>Главный фокус</b> $F$ — точка, в которой пересекаются (или кажутся пересекающимися) отражённые лучи, параллельные главной оси.</p>'
+ '<p style="text-align:center;margin:8px 0">$$F = \\dfrac{R}{2}$$</p>'
+ '<p>где $R$ — радиус кривизны зеркала. Для выпуклого зеркала $F < 0$ (мнимый фокус).</p>');
html += makeCard('rule', 'Формула зеркала и увеличение', '§ 18.2',
'<p><b>Формула сферического зеркала</b> связывает расстояние до объекта $d$, расстояние до изображения $f$ и фокусное расстояние $F$:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{d} + \\dfrac{1}{f} = \\dfrac{1}{F}$$</p>'
+ '<p><b>Линейное увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{h_{изобр}}{h_{объект}} = -\\dfrac{f}{d}$$</p>'
+ '<p><b>Знаки:</b></p>'
+ '<ul>'
+ '<li>$f > 0$ — действительное изображение (перед зеркалом).</li>'
+ '<li>$f < 0$ — мнимое (за зеркалом).</li>'
+ '<li>$\\Gamma < 0$ — перевёрнутое; $\\Gamma > 0$ — прямое.</li>'
+ '</ul>');
html += makeCard('algo', 'Построение изображения — 3 луча', '§ 18.3',
'<p>Чтобы найти изображение объекта в сферическом зеркале, используют <b>три характерных луча</b> от верхней точки объекта:</p>'
+ '<ol>'
+ '<li><b>Параллельный главной оси</b> → отражается через фокус $F$.</li>'
+ '<li><b>Через фокус $F$</b> → отражается параллельно главной оси.</li>'
+ '<li><b>Через центр сферы $C$</b> → отражается обратно по тому же пути (нормаль к поверхности).</li>'
+ '</ol>'
+ '<p>Точка пересечения отражённых лучей — изображение верха объекта. Достаточно <b>двух</b> любых из трёх лучей.</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">Двигай ползунки: <b>F</b> — фокусное расстояние, <b>d</b> — расстояние до объекта. Переключай тип зеркала. Зелёный луч — параллельный, синий — через фокус. Тёмно-коричневая стрелка — изображение (пунктир = мнимое).</div>'
+ '<div class="fx-holder" id="fx-mir2"></div>'
+ '<div class="fx-sliders" id="fx-mir2-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">$1/d + 1/f = 1/F$. Решено: <b id="i5-calc-score">0</b> / 5.</div>'
+ '<div id="i5-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i5-calc-inp" placeholder="ответ"><button class="btn primary" id="i5-calc-go">Проверить</button></div><div class="feedback" id="i5-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="i5-th-score">0</b> / 5.</div>'
+ '<div id="i5-th-q" style="margin:8px 0"></div><div class="opts-row" id="i5-th-opts"></div><div class="feedback" id="i5-th-fb"></div></div>';
html += '<div id="boss-5-slot"></div>';
html += readButton('p5');
html += secNavFor('p5');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-mir2');
const m = new PHYS.SphericalMirror(el, {width:620, height:280, F:80, d:180, objH:50, mode:'concave'});
const slBox = document.getElementById('fx-mir2-sl');
const slMode = '<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#475569;font-weight:600;margin:4px 8px">'
+ '<span style="min-width:90px">Тип зеркала</span>'
+ '<select id="fx-mir2-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="concave">Вогнутое</option><option value="convex">Выпуклое</option>'
+ '</select></label>';
const slF = PHYS.util.slider({label:'F (px)', min:30, max:140, step:5, value:80, fmt:v=>v.toFixed(0), onChange:v=>m.setF(v)});
const slD = PHYS.util.slider({label:'d (px)', min:40, max:280, step:10, value:180, fmt:v=>v.toFixed(0), onChange:v=>m.setD(v)});
slBox.innerHTML = slMode + slF.html + slD.html;
document.getElementById('fx-mir2-mode').addEventListener('change', e => m.setMode(e.target.value));
slF.wire(slBox); slD.wire(slBox);
});
runQuizInput('i5-calc', I5_CALC_ITEMS, 16);
runQuizMC('i5-th', I5_TH_ITEMS, 12);
const bs = loadBossState('boss-5') || { stage:0, solved:false };
makeAndBindBoss('boss-5-slot', '5', BOSS_DEFS.b5, bs,
()=>saveBossState('boss-5', bs),
()=>{ bumpProgress('p5', 40); achievement('p5_done'); });
wireReadBtn('p5');
renderMath(box);
}
/* ===== §19 Преломление света ===== */
function buildP6(){
const box = document.getElementById('p6-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Показатель преломления', '§ 19.1',
'<p>При переходе света из одной прозрачной среды в другую на границе луч <b>преломляется</b> — изменяет направление. Причина — разная скорость света в средах.</p>'
+ '<p><b>Абсолютный показатель преломления</b> среды:</p>'
+ '<p style="text-align:center;margin:8px 0">$$n = \\dfrac{c}{v}$$</p>'
+ '<p>где $c$ — скорость света в вакууме, $v$ — в данной среде. Поскольку $v < c$, всегда $n > 1$.</p>'
+ '<p><b>Типичные значения:</b> воздух $\\approx 1{,}00$ (≈ вакуум), вода 1,33, стекло 1,5, алмаз 2,42.</p>'
+ '<p><b>Относительный показатель</b> для перехода из среды 1 в среду 2:</p>'
+ '<p style="text-align:center;margin:6px 0">$$n_{21} = \\dfrac{n_2}{n_1} = \\dfrac{v_1}{v_2}$$</p>');
html += makeCard('rule', 'Закон преломления (Снелл)', '§ 19.2',
'<p><b>Закон преломления света (Снелла – Декарта):</b></p>'
+ '<ol>'
+ '<li>Луч падающий, преломлённый и нормаль к границе в точке падения лежат в одной плоскости.</li>'
+ '<li>Отношение синуса угла падения к синусу угла преломления равно отношению показателей преломления:</li>'
+ '</ol>'
+ '<p style="text-align:center;margin:8px 0">$$n_1 \\sin\\alpha = n_2 \\sin\\beta$$</p>'
+ '<p>или эквивалентно: $\\dfrac{\\sin\\alpha}{\\sin\\beta} = n_{21} = \\dfrac{n_2}{n_1}$.</p>'
+ '<p><b>Запоминалка:</b> луч «прижимается» к нормали при переходе в более плотную среду ($n_2 > n_1$ ⇒ $\\beta < \\alpha$).</p>');
html += makeCard('example', 'Полное внутреннее отражение', '§ 19.3',
'<p>Если свет идёт из более плотной среды в менее плотную ($n_1 > n_2$), угол преломления $\\beta > \\alpha$. При некотором угле падения $\\beta = 90°$ — луч скользит по границе.</p>'
+ '<p><b>Критический угол</b> $\\alpha_{кр}$ (предельный угол ПВО):</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\sin\\alpha_{кр} = \\dfrac{n_2}{n_1}$$</p>'
+ '<p>При $\\alpha > \\alpha_{кр}$ преломление отсутствует — весь свет отражается обратно. Это <b>полное внутреннее отражение (ПВО)</b>.</p>'
+ '<p><b>Применения:</b> оптоволокно, перископы, бинокли, призмы оборачивающие.</p>'
+ '<p><b>Пример:</b> вода → воздух ($n_1=1{,}33$, $n_2=1$): $\\sin\\alpha_{кр} = 1/1{,}33 \\approx 0{,}75$, $\\alpha_{кр} \\approx 48{,}6°$.</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">Меняй угол падения $\\alpha$ и показатели сред. Красный — падающий, зелёный — преломлённый, пунктир — отражённый. При $n_1 > n_2$ можно поймать полное внутреннее отражение.</div>'
+ '<div class="fx-holder" id="fx-ref"></div>'
+ '<div class="fx-sliders" id="fx-ref-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">$n_1\\sin\\alpha = n_2\\sin\\beta$. Решено: <b id="i6-calc-score">0</b> / 5.</div>'
+ '<div id="i6-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i6-calc-inp" placeholder="ответ"><button class="btn primary" id="i6-calc-go">Проверить</button></div><div class="feedback" id="i6-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="i6-th-score">0</b> / 5.</div>'
+ '<div id="i6-th-q" style="margin:8px 0"></div><div class="opts-row" id="i6-th-opts"></div><div class="feedback" id="i6-th-fb"></div></div>';
html += '<div id="boss-6-slot"></div>';
html += readButton('p6');
html += secNavFor('p6');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-ref');
const r = new PHYS.RefractionLab(el, {width:540, height:320, n1:1, n2:1.5, alpha:35});
const slBox = document.getElementById('fx-ref-sl');
const slA = PHYS.util.slider({label:'α (град)', min:0, max:89, step:1, value:35, fmt:v=>v.toFixed(0), onChange:v=>r.setAlpha(v)});
const slN2 = PHYS.util.slider({label:'n₂', min:0.5, max:2.5, step:0.05, value:1.5, fmt:v=>v.toFixed(2), onChange:v=>r.setN2(v)});
const slN1 = PHYS.util.slider({label:'n₁', min:1, max:2.5, step:0.05, value:1, fmt:v=>v.toFixed(2), onChange:v=>r.setN1(v)});
slBox.innerHTML = slA.html + slN1.html + slN2.html;
slA.wire(slBox); slN1.wire(slBox); slN2.wire(slBox);
});
runQuizInput('i6-calc', I6_CALC_ITEMS, 16);
runQuizMC('i6-th', I6_TH_ITEMS, 12);
const bs = loadBossState('boss-6') || { stage:0, solved:false };
makeAndBindBoss('boss-6-slot', '6', BOSS_DEFS.b6, bs,
()=>saveBossState('boss-6', bs),
()=>{ bumpProgress('p6', 40); achievement('p6_done'); });
wireReadBtn('p6');
renderMath(box);
}
/* ===== §20 Оптические элементы ===== */
function buildP7(){
const box = document.getElementById('p7-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Призма и дисперсия', '§ 20.1',
'<p><b>Призма</b> — прозрачное тело с двумя плоскими преломляющими гранями, образующими двугранный угол $A$ (преломляющий угол призмы).</p>'
+ '<p>Луч, проходя через призму, преломляется на обеих гранях и отклоняется от исходного направления на угол отклонения $\\delta$.</p>'
+ '<p><b>Дисперсия света</b> — зависимость показателя преломления от длины волны (частоты): $n = n(\\lambda)$.</p>'
+ '<p>Для большинства прозрачных сред $n$ <b>растёт с уменьшением $\\lambda$</b>: фиолетовый свет ($\\lambda\\approx 400$ нм) преломляется сильнее красного ($\\lambda\\approx 700$ нм).</p>'
+ '<p>Поэтому призма <b>разлагает белый свет в спектр</b> — Ньютон (1666).</p>'
+ '<p><b>Радуга</b> — природный пример дисперсии: капля воды работает как призма + зеркало (внутреннее отражение).</p>');
html += makeCard('rule', 'Плоскопараллельная пластинка', '§ 20.2',
'<p><b>Плоскопараллельная пластинка</b> — стекло с двумя параллельными гранями.</p>'
+ '<p>Луч, проходящий через пластинку, выходит <b>параллельно</b> падающему, но смещённым в сторону. Смещение:</p>'
+ '<p style="text-align:center;margin:8px 0">$$x = h \\cdot \\dfrac{\\sin(\\alpha - \\beta)}{\\cos\\beta}$$</p>'
+ '<p>где $h$ — толщина пластинки, $\\alpha$ — угол падения, $\\beta$ — угол преломления внутри.</p>'
+ '<p>При $\\alpha = 0$ смещения нет — луч идёт перпендикулярно.</p>');
html += makeCard('example', 'Оптоволокно — полное внутреннее отражение', '§ 20.3',
'<p><b>Оптоволокно</b> — тонкая стеклянная или пластиковая нить, передающая свет на большие расстояния благодаря многократному <b>полному внутреннему отражению</b>.</p>'
+ '<p>Строение: <b>сердцевина</b> (core) с показателем $n_1$ и <b>оболочка</b> (cladding) с $n_2 < n_1$. Свет, попавший в сердцевину под углом $\\alpha > \\alpha_{кр}$, отражается от границы без потерь и распространяется вдоль волокна.</p>'
+ '<p><b>Применения:</b></p>'
+ '<ul>'
+ '<li>Интернет и телефонная связь (магистральные сети).</li>'
+ '<li>Эндоскопы в медицине.</li>'
+ '<li>Освещение в зданиях, декоративные подсветки.</li>'
+ '</ul>'
+ '<p>Потери в современных волокнах — менее 0,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">Белый луч слева попадает на грань стеклянной призмы. Из-за дисперсии $n(\\lambda)$ цвета преломляются под разными углами. Двигай ползунок угла падения — наблюдай, как меняется спектр.</div>'
+ '<div class="fx-holder" id="fx-pr"></div>'
+ '<div class="fx-sliders" id="fx-pr-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">$\\sin\\alpha_{кр} = n_2/n_1$ (если $n_1 > n_2$). Решено: <b id="i7-calc-score">0</b> / 5.</div>'
+ '<div id="i7-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i7-calc-inp" placeholder="ответ"><button class="btn primary" id="i7-calc-go">Проверить</button></div><div class="feedback" id="i7-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="i7-th-score">0</b> / 5.</div>'
+ '<div id="i7-th-q" style="margin:8px 0"></div><div class="opts-row" id="i7-th-opts"></div><div class="feedback" id="i7-th-fb"></div></div>';
html += '<div id="boss-7-slot"></div>';
html += readButton('p7');
html += secNavFor('p7');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-pr');
const p = new PHYS.PrismSpectrum(el, {width:580, height:260, alpha:50});
const slBox = document.getElementById('fx-pr-sl');
const slA = PHYS.util.slider({label:'α (град)', min:20, max:75, step:1, value:50, fmt:v=>v.toFixed(0), onChange:v=>p.setAlpha(v)});
slBox.innerHTML = slA.html;
slA.wire(slBox);
});
runQuizInput('i7-calc', I7_CALC_ITEMS, 16);
runQuizMC('i7-th', I7_TH_ITEMS, 12);
const bs = loadBossState('boss-7') || { stage:0, solved:false };
makeAndBindBoss('boss-7-slot', '7', BOSS_DEFS.b7, bs,
()=>saveBossState('boss-7', bs),
()=>{ bumpProgress('p7', 40); achievement('p7_done'); });
wireReadBtn('p7');
renderMath(box);
}
/* ===== §21 Тонкая линза ===== */
function buildP8(){
const box = document.getElementById('p8-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Тонкая линза — виды и характеристики', '§ 21.1',
'<p><b>Линза</b> — прозрачное тело, ограниченное двумя сферическими (или одной сферической и плоской) поверхностями.</p>'
+ '<p>«<b>Тонкая</b>» — толщина мала по сравнению с радиусами кривизны поверхностей.</p>'
+ '<p>Различают:</p>'
+ '<ul>'
+ '<li><b>Собирающие</b> (выпуклые) — действуют как «солнечные стеклы», $F > 0$.</li>'
+ '<li><b>Рассеивающие</b> (вогнутые) — расходящие линзы, $F < 0$.</li>'
+ '</ul>'
+ '<p>Основные точки: <b>оптический центр</b> $O$, два <b>фокуса</b> $F_1, F_2$ (симметричны относительно $O$), <b>главная оптическая ось</b>.</p>'
+ '<p><b>Оптическая сила</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$D = \\dfrac{1}{F}, \\quad [D] = \\text{дптр} = \\text{м}^{-1}$$</p>'
+ '<p>Положительная для собирающей, отрицательная для рассеивающей.</p>');
html += makeCard('rule', 'Формула линзы и увеличение', '§ 21.2',
'<p><b>Формула тонкой линзы</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\dfrac{1}{d} + \\dfrac{1}{f} = \\dfrac{1}{F}$$</p>'
+ '<p>где $d$ — расстояние до объекта, $f$ — до изображения, $F$ — фокусное.</p>'
+ '<p><b>Линейное увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{h_{изобр}}{h_{объект}} = -\\dfrac{f}{d}$$</p>'
+ '<p><b>Знаки:</b> $f > 0$ — изображение действительное (за линзой); $f < 0$ — мнимое (с той же стороны, что и объект). $\\Gamma < 0$ — перевёрнутое, $\\Gamma > 0$ — прямое.</p>');
html += makeCard('algo', 'Построение изображения — характерные лучи', '§ 21.3',
'<p>От верха объекта проводят два из трёх лучей:</p>'
+ '<ol>'
+ '<li><b>Параллельный главной оси</b> — после линзы идёт через дальний фокус $F$.</li>'
+ '<li><b>Через оптический центр</b> $O$ — проходит без преломления (прямо).</li>'
+ '<li><b>Через ближний фокус</b> — после линзы выходит параллельно оси.</li>'
+ '</ol>'
+ '<p>Пересечение лучей — изображение верха объекта. Достаточно двух любых.</p>'
+ '<p>Для <b>рассеивающей линзы</b>: лучи расходятся; изображение строится продолжением расходящихся лучей назад (всегда мнимое, прямое, уменьшенное).</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">Меняй $F$ и $d$, переключай тип линзы. Зелёный — параллельный луч, синий — через центр $O$. Коричневая стрелка — изображение (пунктир = мнимое).</div>'
+ '<div class="fx-holder" id="fx-lens"></div>'
+ '<div class="fx-sliders" id="fx-lens-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">$1/d + 1/f = 1/F$. Решено: <b id="i8-calc-score">0</b> / 5.</div>'
+ '<div id="i8-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i8-calc-inp" placeholder="ответ"><button class="btn primary" id="i8-calc-go">Проверить</button></div><div class="feedback" id="i8-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="i8-th-score">0</b> / 5.</div>'
+ '<div id="i8-th-q" style="margin:8px 0"></div><div class="opts-row" id="i8-th-opts"></div><div class="feedback" id="i8-th-fb"></div></div>';
html += '<div id="boss-8-slot"></div>';
html += readButton('p8');
html += secNavFor('p8');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-lens');
const l = new PHYS.ThinLens(el, {width:640, height:300, F:70, d:160, mode:'converging'});
const slBox = document.getElementById('fx-lens-sl');
const slMode = '<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#475569;font-weight:600;margin:4px 8px">'
+ '<span style="min-width:90px">Тип линзы</span>'
+ '<select id="fx-lens-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="converging">Собирающая</option><option value="diverging">Рассеивающая</option>'
+ '</select></label>';
const slF = PHYS.util.slider({label:'F (px)', min:30, max:140, step:5, value:70, fmt:v=>v.toFixed(0), onChange:v=>l.setF(v)});
const slD = PHYS.util.slider({label:'d (px)', min:30, max:280, step:10, value:160, fmt:v=>v.toFixed(0), onChange:v=>l.setD(v)});
slBox.innerHTML = slMode + slF.html + slD.html;
document.getElementById('fx-lens-mode').addEventListener('change', e => l.setMode(e.target.value));
slF.wire(slBox); slD.wire(slBox);
});
runQuizInput('i8-calc', I8_CALC_ITEMS, 18);
runQuizMC('i8-th', I8_TH_ITEMS, 14);
const bs = loadBossState('boss-8') || { stage:0, solved:false };
makeAndBindBoss('boss-8-slot', '8', BOSS_DEFS.b8, bs,
()=>saveBossState('boss-8', bs),
()=>{ bumpProgress('p8', 40); achievement('p8_done'); });
wireReadBtn('p8');
renderMath(box);
}
/* ===== §22 Фотоаппарат, проектор ===== */
function buildP9(){
const box = document.getElementById('p9-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Действительные изображения', '§ 22.1',
'<p>Если объект находится <b>дальше фокуса</b> ($d > F$), собирающая линза даёт <b>действительное</b> изображение — лучи реально пересекаются и его можно поймать на экран.</p>'
+ '<p>Возможны три случая:</p>'
+ '<ul>'
+ '<li><b>$d > 2F$</b> — изображение действительное, перевёрнутое, <b>уменьшенное</b> ($|\\Gamma| < 1$), находится между $F$ и $2F$ с другой стороны.</li>'
+ '<li><b>$d = 2F$</b> — действительное, перевёрнутое, <b>равное</b> ($|\\Gamma| = 1$), $f = 2F$.</li>'
+ '<li><b>$F < d < 2F$</b> — действительное, перевёрнутое, <b>увеличенное</b> ($|\\Gamma| > 1$), $f > 2F$.</li>'
+ '</ul>');
html += makeCard('example', 'Фотоаппарат', '§ 22.2',
'<p><b>Фотоаппарат</b> создаёт уменьшенное действительное изображение далёких объектов на матрице (или плёнке).</p>'
+ '<ul>'
+ '<li>Объект находится далеко: $d \\gg F$. По формуле $1/f \\approx 1/F \\Rightarrow f \\approx F$.</li>'
+ '<li>Изображение в фокальной плоскости — на матрице.</li>'
+ '<li>Увеличение $|\\Gamma| = F/d \\ll 1$ — изображение уменьшенное.</li>'
+ '<li><b>Фокусировка</b> — изменение расстояния между объективом и матрицей.</li>'
+ '<li><b>Диафрагма</b> регулирует освещённость и глубину резкости.</li>'
+ '</ul>'
+ '<p><b>Угол поля зрения</b> зависит от $F$: длиннофокусные («теле») — узкое поле; короткофокусные («широкоугольные») — большое.</p>');
html += makeCard('example', 'Проектор', '§ 22.3',
'<p><b>Проектор</b> создаёт увеличенное действительное изображение слайда (или экрана LCD) на удалённом экране.</p>'
+ '<ul>'
+ '<li>Объект (слайд) располагается чуть дальше $F$: $F < d < 2F$.</li>'
+ '<li>Изображение получается перевёрнутым, увеличенным — поэтому слайд вставляют <b>«вверх ногами»</b>.</li>'
+ '<li>Увеличение $|\\Gamma| = f/d$, где $f$ — расстояние до экрана.</li>'
+ '<li>Конденсор (короткофокусная собирающая линза) равномерно освещает слайд.</li>'
+ '</ul>'
+ '<p>Принцип проектора используется в кинопроекторах, оверхедах, LCD-проекторах.</p>');
html += '<div class="wg"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><span class="wg-title">Сценарии: фотоаппарат vs проектор</span></div>'
+ '<div class="wg-help">Ползунок $d$: при $d > 2F$ — режим «фото» (уменьш.), при $F < d < 2F$ — режим «проектор» (увелич.). Смотри изменения $\\Gamma$ и $f$.</div>'
+ '<div class="fx-holder" id="fx-cam"></div>'
+ '<div class="fx-sliders" id="fx-cam-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">Решено: <b id="i9-calc-score">0</b> / 5.</div>'
+ '<div id="i9-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i9-calc-inp" placeholder="ответ"><button class="btn primary" id="i9-calc-go">Проверить</button></div><div class="feedback" id="i9-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="i9-th-score">0</b> / 5.</div>'
+ '<div id="i9-th-q" style="margin:8px 0"></div><div class="opts-row" id="i9-th-opts"></div><div class="feedback" id="i9-th-fb"></div></div>';
html += '<div id="boss-9-slot"></div>';
html += readButton('p9');
html += secNavFor('p9');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-cam');
const l = new PHYS.ThinLens(el, {width:640, height:300, F:60, d:200, mode:'converging'});
const slBox = document.getElementById('fx-cam-sl');
const slD = PHYS.util.slider({label:'d (px)', min:80, max:300, step:10, value:200, fmt:v=>v.toFixed(0), onChange:v=>l.setD(v)});
const slF = PHYS.util.slider({label:'F (px)', min:30, max:120, step:5, value:60, fmt:v=>v.toFixed(0), onChange:v=>l.setF(v)});
slBox.innerHTML = slD.html + slF.html;
slD.wire(slBox); slF.wire(slBox);
});
runQuizInput('i9-calc', I9_CALC_ITEMS, 18);
runQuizMC('i9-th', I9_TH_ITEMS, 14);
const bs = loadBossState('boss-9') || { stage:0, solved:false };
makeAndBindBoss('boss-9-slot', '9', BOSS_DEFS.b9, bs,
()=>saveBossState('boss-9', bs),
()=>{ bumpProgress('p9', 40); achievement('p9_done'); });
wireReadBtn('p9');
renderMath(box);
}
/* ===== §23 Угол зрения. Лупа, микроскоп, телескоп ===== */
function buildP10(){
const box = document.getElementById('p10-body'); if(!box) return;
let html = '';
html += makeCard('theory', 'Угол зрения и угловое увеличение', '§ 23.1',
'<p>Глаз — оптическая система с переменной аккомодацией. Видимый размер предмета определяется не его абсолютной высотой, а <b>углом зрения</b> $\\varphi$, под которым предмет виден из точки наблюдения.</p>'
+ '<p>Минимальный угол, при котором глаз различает две точки как раздельные, $\\varphi_{мин} \\approx 1\'$ (одна угловая минута).</p>'
+ '<p><b>Оптические приборы</b> позволяют увеличить угол зрения и рассматривать мелкие или далёкие объекты.</p>'
+ '<p><b>Угловое увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{\\varphi}{\\varphi_0}$$</p>'
+ '<p>где $\\varphi$ — угол зрения через прибор, $\\varphi_0$ — без прибора.</p>'
+ '<p><b>Расстояние наилучшего зрения</b> для нормального глаза $L_0 = 25$ см.</p>');
html += makeCard('rule', 'Лупа', '§ 23.2',
'<p><b>Лупа</b> — короткофокусная собирающая линза. Объект помещают <b>между линзой и её фокусом</b>: $d < F$. Тогда $f < 0$ — изображение мнимое, прямое, увеличенное (на расстоянии $L_0 = 25$ см от глаза).</p>'
+ '<p><b>Угловое увеличение лупы</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{25 \\text{ см}}{F}$$</p>'
+ '<p>Чем меньше $F$, тем больше увеличение, но и меньше поле зрения.</p>'
+ '<p>Лупа $F = 5$ см даёт $\\Gamma = 5$ крат.</p>');
html += makeCard('rule', 'Микроскоп', '§ 23.3',
'<p><b>Микроскоп</b> — двухступенчатая система:</p>'
+ '<ul>'
+ '<li><b>Объектив</b> (короткофокусный) даёт <b>действительное увеличенное</b> промежуточное изображение $A_1B_1$ за фокусом окуляра.</li>'
+ '<li><b>Окуляр</b> работает как <b>лупа</b>, рассматривая это промежуточное изображение.</li>'
+ '</ul>'
+ '<p><b>Общее увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma_{мкс} \\approx \\Gamma_{об} \\cdot \\dfrac{25 \\text{ см}}{F_{ок}}$$</p>'
+ '<p>Современные микроскопы достигают увеличений до 1000–2000 крат. Предел разрешения ограничен дифракцией: $\\sim \\lambda/2 \\approx 0{,}2$ мкм.</p>');
html += makeCard('rule', 'Телескоп (Кеплера)', '§ 23.4',
'<p><b>Телескоп Кеплера</b> — двухступенчатая система для рассматривания <b>удалённых</b> объектов.</p>'
+ '<ul>'
+ '<li>Лучи от далёкого объекта идут <b>параллельно</b>. Объектив даёт изображение $A_1B_1$ в своей фокальной плоскости.</li>'
+ '<li>Окуляр расположен так, что его передняя фокальная плоскость совпадает с задней объектива: $L = F_{об} + F_{ок}$.</li>'
+ '<li>Окуляр превращает изображение в <b>параллельный пучок</b> — глаз воспринимает как «бесконечно далёкое».</li>'
+ '</ul>'
+ '<p><b>Угловое увеличение</b>:</p>'
+ '<p style="text-align:center;margin:8px 0">$$\\Gamma = \\dfrac{F_{об}}{F_{ок}}$$</p>'
+ '<p>Чем больше $F_{об}$, тем больше увеличение. Изображение перевёрнутое — астрономов это не смущает.</p>'
+ '<p>В <b>зрительной трубе</b> (Галилея) окуляр — рассеивающая линза, изображение прямое.</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">Переключай <b>режим</b>: микроскоп (короткий объектив + лупа-окуляр) или телескоп (длинный объектив + короткий окуляр). Меняй $F_1, F_2$ — наблюдай ход лучей.</div>'
+ '<div class="fx-holder" id="fx-tel"></div>'
+ '<div class="fx-sliders" id="fx-tel-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">Решено: <b id="i10-calc-score">0</b> / 5.</div>'
+ '<div id="i10-calc-q" style="margin:8px 0"></div><div class="actions"><input class="tinp" id="i10-calc-inp" placeholder="ответ"><button class="btn primary" id="i10-calc-go">Проверить</button></div><div class="feedback" id="i10-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="i10-th-score">0</b> / 5.</div>'
+ '<div id="i10-th-q" style="margin:8px 0"></div><div class="opts-row" id="i10-th-opts"></div><div class="feedback" id="i10-th-fb"></div></div>';
html += '<div id="boss-10-slot"></div>';
html += readButton('p10');
html += secNavFor('p10');
box.innerHTML = html;
ensureFx(()=>{
const el = document.getElementById('fx-tel');
const t = new PHYS.TwoLensSystem(el, {width:700, height:280, F1:120, F2:40, L:200, mode:'telescope'});
const slBox = document.getElementById('fx-tel-sl');
const slMode = '<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#475569;font-weight:600;margin:4px 8px">'
+ '<span style="min-width:90px">Режим</span>'
+ '<select id="fx-tel-mode" style="flex:1;padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;font-family:inherit">'
+ '<option value="telescope">Телескоп</option><option value="microscope">Микроскоп</option>'
+ '</select></label>';
const slF1 = PHYS.util.slider({label:'F₁ (объектив)', min:30, max:200, step:5, value:120, fmt:v=>v.toFixed(0), onChange:v=>t.setF1(v)});
const slF2 = PHYS.util.slider({label:'F₂ (окуляр)', min:20, max:100, step:5, value:40, fmt:v=>v.toFixed(0), onChange:v=>t.setF2(v)});
slBox.innerHTML = slMode + slF1.html + slF2.html;
document.getElementById('fx-tel-mode').addEventListener('change', e=>{
t.setMode(e.target.value);
if(e.target.value === 'microscope'){ t.setF1(50); t.setF2(80); t.setL(280); }
else { t.setF1(120); t.setF2(40); t.setL(200); }
});
slF1.wire(slBox); slF2.wire(slBox);
});
runQuizInput('i10-calc', I10_CALC_ITEMS, 18);
runQuizMC('i10-th', I10_TH_ITEMS, 14);
const bs = loadBossState('boss-10') || { stage:0, solved:false };
makeAndBindBoss('boss-10-slot', '10', BOSS_DEFS.b10, bs,
()=>saveBossState('boss-10', bs),
()=>{ bumpProgress('p10', 40); achievement('p10_done'); });
wireReadBtn('p10');
renderMath(box);
}
/* ===== Финал главы 3 — 5 интегрированных боссов ===== */
function buildFinal(){
const box = document.getElementById('final-body'); if(!box) return;
let html = '';
html += '<div class="card" style="background:linear-gradient(135deg,#fef3c7,#fde68a);border-color:#d97706">'
+ '<div class="card-header"><div class="card-icon rule" style="background:#d97706;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">Финал главы 3 — Оптика</div><div class="card-num">★</div></div>'
+ '<div class="card-body">'
+ '<p><b>Поздравляем!</b> Ты добрался до финала главы об оптике. Впереди — <b>5 интегрированных боссов</b>, покрывающих весь материал §14-§23: от природы света и его скорости до сложных оптических приборов.</p>'
+ '<p>Каждый босс — <b>5 этапов</b>. Победив всех пятерых, получишь ачивку <b>Магистр оптики</b> и +200 XP бонус.</p>'
+ '<p style="margin-top:14px;color:#92400e;font-weight:700">Если что-то забыл — пройди ещё раз нужный параграф через карточки слева.</p>'
+ '</div></div>';
for (let i = 1; i <= 5; i++){
html += '<div id="boss-final-'+i+'-slot"></div>';
}
html += '<div class="card" id="final-victory" style="display:none;background:linear-gradient(135deg,#fcd34d,#f59e0b);border-color:#92400e">'
+ '<div class="card-header"><div class="card-icon example" style="background:#92400e;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>Все 5 интегральных боссов главы 3 побеждены!</b> Ты разобрался в природе света, интерференции, дифракции, отражении, преломлении, призмах, линзах, фотоаппаратах, проекторах, микроскопах и телескопах.</p>'
+ '<p>Бонус: <b>+200 XP</b>. Впереди — Глава 4 (СТО) и квантовая физика.</p></div></div>';
html += secNavFor('final');
box.innerHTML = html;
for (let i = 1; i <= 5; 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 <= 5; 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('ch3_master')){
achievement('ch3_master');
addXp(200, 'ch3-master-bonus');
}
if (!STATE.achievements.has('ch3_done')) achievement('ch3_done');
}
}
/* ===== Stubs (для будущих заглушек, если понадобятся) ===== */
function buildStubP(id, label, message){
const box = document.getElementById(id + '-body'); if(!box) return;
let html = '<div class="stub-note"><h3>' + label + ' — в разработке</h3><p>' + message + '</p></div>';
html += secNavFor(id);
box.innerHTML = html;
renderMath(box);
}
function loadBossState(key){ try{ return JSON.parse(localStorage.getItem('physics11_ch3_'+key)||'null'); }catch(e){ return null; } }
function saveBossState(key, state){ try{ localStorage.setItem('physics11_ch3_'+key, JSON.stringify(state)); }catch(e){} }
function runQuizMC(id, items, xpReward){
const state = JSON.parse(localStorage.getItem('physics11_ch3_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_ch3_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_ch3_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_ch3_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();
}
/* ===== Quiz items ===== */
const I1_CALC_ITEMS = [
{ q:'$c$ в вакууме (м/с)?', answer:['3e8','3·10⁸','300000000','3*10^8'], explain:'$3 \\cdot 10^8$ м/с.' },
{ q:'$n_{воды} = 1{,}33$. Скорость света в воде (м/с)? (округли до $10^7$)', answer:['2.26e8','2.26·10⁸','226000000'], explain:'$v = c/n \\approx 2{,}26 \\cdot 10^8$ м/с.' },
{ q:'$n_{стекла} = 1{,}5$. Скорость света в стекле (м/с)?', answer:['2e8','2·10⁸','200000000'], explain:'$v = 3/1{,}5 \\cdot 10^8 = 2 \\cdot 10^8$ м/с.' },
{ q:'Свет от Солнца до Земли (150 млн км) идёт (с)?', answer:'500', explain:'$t = 1{,}5\\cdot 10^{11}/3\\cdot 10^8 = 500$ с (≈8 мин).' },
{ q:'$\\lambda = 600$ нм. $\\nu$ (Гц)?', answer:['5e14','5·10¹⁴','500000000000000'], explain:'$\\nu = c/\\lambda = 3 \\cdot 10^8 / 6 \\cdot 10^{-7} = 5 \\cdot 10^{14}$ Гц.' }
];
const I1_NAT_ITEMS = [
{ q:'Свет — это:', opts:['Звук','ЭМ волна','Поток воды','Тепло'], correct:1, explain:'ЭМ волна (по Максвеллу).' },
{ q:'Опыт Рёмера использовал:', opts:['Зеркала','Лазер','Затмения Юпитера','Призму'], correct:2, explain:'По запаздыванию затмений Ио.' },
{ q:'Скорость света в воздухе:', opts:['$3 \\cdot 10^8$ м/с','$340$ м/с','$3 \\cdot 10^6$ м/с','$\\infty$'], correct:0, explain:'Почти как в вакууме.' },
{ q:'В среде с $n > 1$ скорость света:', opts:['Больше $c$','Меньше $c$','Равна $c$','Зависит от частоты'], correct:1, explain:'$v = c/n < c$.' },
{ q:'$c$ связана с $\\varepsilon_0, \\mu_0$ как:', opts:['$c = \\varepsilon_0\\mu_0$','$c = 1/(\\varepsilon_0\\mu_0)$','$c = 1/\\sqrt{\\varepsilon_0\\mu_0}$','$c = \\sqrt{\\varepsilon_0/\\mu_0}$'], correct:2, explain:'$c = 1/\\sqrt{\\varepsilon_0\\mu_0}$.' }
];
const I2_CALC_ITEMS = [
{ q:'$\\lambda = 500$ нм, $\\Delta = 1$ мкм = 1000 нм. Это max или min?', answer:['max','максимум'], explain:'$\\Delta/\\lambda = 2$ — целое ⇒ $k = 2$, max.' },
{ q:'$\\lambda = 400$ нм, $\\Delta = 600$ нм. Max или min?', answer:['min','минимум'], explain:'$\\Delta = 1{,}5\\lambda = (2 \\cdot 1 + 1)\\lambda/2$ — min.' },
{ q:'$k = 3$, $\\lambda = 500$ нм. $\\Delta_{max}$ (нм)?', answer:'1500', explain:'$\\Delta = k\\lambda = 1500$ нм.' },
{ q:'$\\Delta = 2500$ нм, $\\lambda = 500$ нм. Порядок max $k$?', answer:'5', explain:'$k = \\Delta/\\lambda = 5$.' },
{ q:'$\\lambda = 600$ нм. $\\Delta$ для min при $k = 1$? (нм)', answer:'900', explain:'$\\Delta = 3\\lambda/2 = 900$ нм.' }
];
const I2_TH_ITEMS = [
{ q:'Интерференция возможна для:', opts:['Любых волн','Когерентных волн','Только звука','Только света'], correct:1, explain:'Когерентность — необходимое условие.' },
{ q:'Условие max при интерференции:', opts:['$\\Delta = k\\lambda$','$\\Delta = (2k+1)\\lambda/2$','$\\Delta = \\lambda/4$','$\\Delta = 0$'], correct:0, explain:'$\\Delta = k\\lambda$.' },
{ q:'Опыт Юнга использует:', opts:['1 щель','2 щели','3 щели','Зеркало'], correct:1, explain:'2 близкие щели.' },
{ q:'Радужные плёнки бензина — это:', opts:['Дифракция','Интерференция','Преломление','Отражение'], correct:1, explain:'Интерференция в тонкой плёнке.' },
{ q:'Когерентные волны имеют:', opts:['Разные $\\nu$','Одинаковые $\\nu$ и постоянную $\\Delta\\varphi$','Случайные фазы','Разную скорость'], correct:1, explain:'Когерентность.' }
];
const I3_CALC_ITEMS = [
{ q:'$d = 2$ мкм, $\\lambda = 500$ нм, $k = 1$. $\\sin\\varphi$?', answer:'0.25', explain:'$\\sin\\varphi = k\\lambda/d = 500 \\cdot 10^{-9}/(2 \\cdot 10^{-6}) = 0{,}25$.' },
{ q:'$d = 1$ мкм, $\\lambda = 500$ нм. Max порядок $k$?', answer:'2', explain:'$k \\le d/\\lambda = 2$.' },
{ q:'$d = 5$ мкм, $\\lambda = 500$ нм, $k = 2$. $\\sin\\varphi$?', answer:'0.2', explain:'$\\sin\\varphi = 2 \\cdot 500/5000 = 0{,}2$.' },
{ q:'$d = 4$ мкм, $\\sin\\varphi = 0{,}1$, $k = 1$. $\\lambda$ (нм)?', answer:'400', explain:'$\\lambda = d\\sin\\varphi/k = 400$ нм.' },
{ q:'$N = 500$ штрихов на 1 мм. $d$ (мкм)?', answer:'2', explain:'$d = 1$ мм $/500 = 2 \\cdot 10^{-6}$ м = 2 мкм.' }
];
const I3_TH_ITEMS = [
{ q:'Принцип Гюйгенса – Френеля:', opts:['Прямая в зеркале','Каждая точка фронта — источник вторичных волн','Свет — частицы','Скорость = const'], correct:1, explain:'Главная идея.' },
{ q:'Дифракционная решётка:', opts:['Призма','Зеркало','Пластина со штрихами','Линза'], correct:2, explain:'Параллельные штрихи.' },
{ q:'$k = 0$ это:', opts:['Центральный max','Не существует','Минимум','Максимум 1-го порядка'], correct:0, explain:'Прямо.' },
{ q:'Дифракция — это:', opts:['Отражение','Огибание препятствий','Преломление','Гашение'], correct:1, explain:'Отклонение от прямолинейного распр.' },
{ q:'Радужные блики на CD — это:', opts:['Интерф. в плёнке','Дифракция','Полное отражение','Преломление в призме'], correct:1, explain:'CD = периодич. решётка.' }
];
const I4_CALC_ITEMS = [
{ q:'Угол падения 30°. Угол отражения (°)?', answer:'30', explain:'$\\angle_{пад} = \\angle_{отр}$.' },
{ q:'Угол между падающим и отражённым лучами 60°. Угол падения (°)?', answer:'30', explain:'Симметрия ⇒ каждый 30°.' },
{ q:'Объект на расстоянии 2 м от зеркала. Расстояние до изображения (м)?', answer:'2', explain:'$d_{изобр} = d_{объект}$.' },
{ q:'Луч падает перпендикулярно зеркалу. Угол отражения (°)?', answer:'0', explain:'Возвращается обратно.' },
{ q:'Объект 1,7 м перед зеркалом. Расстояние от объекта до изображения (м)?', answer:'3.4', explain:'$2d = 3{,}4$ м.' }
];
const I4_IMG_ITEMS = [
{ q:'Изображение в плоском зеркале:', opts:['Действительное','Мнимое'], correct:1, explain:'Мнимое.' },
{ q:'Размер изображения в плоском зеркале относительно объекта:', opts:['Больше','Меньше','Равен'], correct:2, explain:'Равен.' },
{ q:'Изображение в плоском зеркале:', opts:['Прямое','Перевёрнутое','Боком'], correct:0, explain:'Прямое.' },
{ q:'Если повернуть зеркало на 10°, отражённый луч повернётся на:', opts:['10°','20°','5°','0°'], correct:1, explain:'На двойной угол — $2\\alpha$.' },
{ q:'Угол падения отсчитывается от:', opts:['Поверхности','Нормали','Любого направления','Зеркала'], correct:1, explain:'От перпендикуляра к поверхности.' }
];
const I5_CALC_ITEMS = [
{ q:'$R = 40$ см. Фокус $F$ (см)?', answer:'20', explain:'$F = R/2 = 20$ см.' },
{ q:'$F = 10$ см, $d = 30$ см. $f$ (см)?', answer:'15', explain:'$1/f = 1/10 - 1/30 = 2/30 \\Rightarrow f = 15$ см.' },
{ q:'$F = 20$ см, $d = 60$ см. $\\Gamma$?', answer:['-0.5','-1/2'], explain:'$1/f = 1/20 - 1/60 = 1/30 \\Rightarrow f=30$; $\\Gamma = -30/60 = -0{,}5$.' },
{ q:'$F = 15$ см, $d = 10$ см. $f$ (см)?', answer:['-30','-30см'], explain:'$1/f = 1/15 - 1/10 = -1/30 \\Rightarrow f = -30$ (мнимое).' },
{ q:'$d = 2F$. $f$ относительно $F$?', answer:['2f','2','2*f'], explain:'$1/f = 1/F - 1/(2F) = 1/(2F) \\Rightarrow f = 2F$.' }
];
const I5_TH_ITEMS = [
{ q:'Фокусное расстояние сферического зеркала:', opts:['$F = R$','$F = R/2$','$F = 2R$','$F = R^2$'], correct:1, explain:'$F = R/2$.' },
{ q:'Выпуклое зеркало даёт изображение:', opts:['Действительное, перевёрнутое','Мнимое, прямое, уменьшенное','Действительное, увеличенное','Точечное'], correct:1, explain:'Мнимое за зеркалом.' },
{ q:'Луч через центр сферы $C$:', opts:['Отражается обратно','Параллельно оси','Через фокус','Поглощается'], correct:0, explain:'По нормали — обратно.' },
{ q:'Если объект между $F$ и зеркалом (вогнутое), изображение:', opts:['Действительное','Мнимое, увеличенное, прямое','Уменьшенное','Не образуется'], correct:1, explain:'Лупа — мнимое прямое.' },
{ q:'$\\Gamma = -2$ означает:', opts:['Уменьшенное прямое','Увеличенное прямое','Увеличенное перевёрнутое в 2 раза','Без увеличения'], correct:2, explain:'$|\\Gamma|=2$, минус = перевёрнутое.' }
];
const I6_CALC_ITEMS = [
{ q:'$n_1 = 1$, $\\alpha = 30°$, $n_2 = 1{,}5$. $\\sin\\beta$?', answer:['1/3','0.333','0.33'], explain:'$\\sin\\beta = \\sin 30°/1{,}5 = 0{,}5/1{,}5 = 1/3$.' },
{ q:'Вода → воздух ($n_1=1{,}33$). $\\sin\\alpha_{кр}$?', answer:['0.75','3/4'], explain:'$\\sin\\alpha_{кр} = 1/1{,}33 \\approx 0{,}75$.' },
{ q:'Алмаз $n = 2{,}42$, $\\sin\\alpha_{кр}$?', answer:['0.41','0.413'], explain:'$1/2{,}42 \\approx 0{,}413$ ⇒ $\\alpha_{кр} \\approx 24{,}4°$.' },
{ q:'$n_{ст} = 1{,}5$. $v$ в стекле (м/с)?', answer:['2e8','2·10⁸','200000000'], explain:'$v = c/n = 3 \\cdot 10^8/1{,}5 = 2 \\cdot 10^8$.' },
{ q:'$\\alpha = 60°$, $n_2/n_1 = 1{,}5$. $\\sin\\beta$?', answer:['0.577','√3/3','0.58'], explain:'$\\sin\\beta = \\sin 60°/1{,}5 = (\\sqrt 3/2)/1{,}5 \\approx 0{,}577$.' }
];
const I6_TH_ITEMS = [
{ q:'Закон Снелла:', opts:['$n_1 \\sin\\alpha = n_2 \\sin\\beta$','$n_1 \\cos\\alpha = n_2 \\cos\\beta$','$n_1/\\alpha = n_2/\\beta$','$n_1 + n_2 = const$'], correct:0, explain:'Закон Снелла.' },
{ q:'$n = c/v$ означает: в среде свет идёт:', opts:['Быстрее $c$','Медленнее $c$','С такой же скоростью','Не движется'], correct:1, explain:'$v = c/n < c$ при $n>1$.' },
{ q:'ПВО возможно при переходе:', opts:['Воздух → стекло','Стекло → воздух','Вода → стекло','Никогда'], correct:1, explain:'Из более плотной в менее плотную ($n_1 > n_2$).' },
{ q:'Если $n_2 > n_1$, луч:', opts:['Прижимается к нормали','Удаляется от нормали','Идёт по оригинальному пути','Останавливается'], correct:0, explain:'$\\beta < \\alpha$ — прижимается.' },
{ q:'Палка в воде кажется сломанной из-за:', opts:['Отражения','Преломления','Дисперсии','Дифракции'], correct:1, explain:'Преломления на границе вода–воздух.' }
];
const I7_CALC_ITEMS = [
{ q:'Стекло $n = 1{,}5$, воздух $n=1$. $\\sin\\alpha_{кр}$?', answer:['0.667','2/3','0.67'], explain:'$1/1{,}5 = 2/3$.' },
{ q:'$n_{сердцевины} = 1{,}5$, $n_{оболочки} = 1{,}4$. $\\sin\\alpha_{кр}$?', answer:['0.933','14/15','0.93'], explain:'$1{,}4/1{,}5 = 14/15 \\approx 0{,}933$ ⇒ $\\alpha_{кр} \\approx 69°$.' },
{ q:'$\\alpha_{кр} = 30°$. $n_{отношение}$?', answer:'0.5', explain:'$\\sin 30° = 0{,}5 = n_2/n_1$.' },
{ q:'Толщина пластинки 4 см, $\\alpha=30°$, $\\beta = 19°$ (стекло). $x$ ≈ толщина $\\cdot \\sin(\\alpha-\\beta)/\\cos\\beta$ (см)?', answer:['0.8','0.79','0.81'], explain:'$x = 4 \\cdot \\sin 11°/\\cos 19° \\approx 4 \\cdot 0{,}19/0{,}945 \\approx 0{,}8$ см.' },
{ q:'$\\lambda_{красн} = 700$ нм отклоняется призмой <b>слабее</b> или сильнее фиолетового 400 нм?', answer:['слабее','меньше'], explain:'$n(700) < n(400)$ ⇒ красный отклоняется слабее.' }
];
const I7_TH_ITEMS = [
{ q:'Дисперсия — это:', opts:['Отражение','Зависимость $n$ от $\\lambda$','Разложение в спектр интерферометром','Дифракция в кристалле'], correct:1, explain:'$n = n(\\lambda)$.' },
{ q:'Призма разлагает белый свет потому что:', opts:['Разные $\\lambda$ имеют разные $n$','Свет частично отражается','Кристаллы поглощают','Стекло поляризует'], correct:0, explain:'Дисперсия.' },
{ q:'Плоскопараллельная пластинка:', opts:['Меняет направление луча','Смещает луч параллельно','Поглощает свет','Поворачивает поляризацию'], correct:1, explain:'Луч сдвигается, но не меняет угла.' },
{ q:'Оптоволокно работает на:', opts:['Интерференции','Дифракции','Полном внутр. отражении','Поляризации'], correct:2, explain:'ПВО внутри сердцевины.' },
{ q:'В радуге наблюдатель видит красный <b>сверху</b>, фиолетовый снизу потому что:', opts:['Красный преломляется сильнее','Фиолетовый преломляется сильнее','Цвета не отличаются','Капля поглощает синий'], correct:1, explain:'Фиолетовый ($\\lambda<$) преломляется сильнее.' }
];
const I8_CALC_ITEMS = [
{ q:'$F = 0{,}25$ м. $D$ (дптр)?', answer:'4', explain:'$D = 1/F = 1/0{,}25 = 4$ дптр.' },
{ q:'$D = -2$ дптр. $F$ (м)?', answer:['-0.5','-1/2'], explain:'$F = 1/D = -0{,}5$ м (рассеивающая).' },
{ q:'$F = 10$ см, $d = 15$ см. $f$ (см)?', answer:'30', explain:'$1/f = 1/10 - 1/15 = 1/30$.' },
{ q:'$F = 20$ см, $d = 60$ см. $\\Gamma$?', answer:['-0.5','-1/2'], explain:'$f = 30$ см; $\\Gamma = -30/60 = -0{,}5$.' },
{ q:'$F = 15$ см, $d = 10$ см (лупа). $f$ (см)?', answer:['-30','-30см'], explain:'$1/f = 1/15 - 1/10 = -1/30$, мнимое.' }
];
const I8_TH_ITEMS = [
{ q:'Собирающая линза имеет $F$:', opts:['$F < 0$','$F > 0$','$F = 0$','$F = \\infty$'], correct:1, explain:'$F > 0$.' },
{ q:'Рассеивающая линза даёт изображение:', opts:['Действ. перевёрнут.','Мнимое прямое уменьшен.','Действ. увеличен.','Равное'], correct:1, explain:'Всегда мнимое, прямое, уменьшенное.' },
{ q:'Луч через оптический центр:', opts:['Преломляется','Идёт без преломления','Поглощается','Удваивается'], correct:1, explain:'Через $O$ — прямо.' },
{ q:'Оптическая сила измеряется в:', opts:['Дж','Н/м','Дптр (м⁻¹)','Гц'], correct:2, explain:'Дптр = м⁻¹.' },
{ q:'$\\Gamma < 0$ означает:', opts:['Прямое','Перевёрнутое','Без увеличения','Мнимое'], correct:1, explain:'Минус = перевёрнутое.' }
];
const I9_CALC_ITEMS = [
{ q:'$F = 5$ см, $d = 5$ м. $f$ ≈ (см)?', answer:'5', explain:'При $d \\gg F$: $f \\approx F = 5$ см (фото).' },
{ q:'$F = 10$ см, $d = 15$ см (проектор). $\\Gamma$?', answer:'-2', explain:'$f = 30$ см, $\\Gamma = -30/15 = -2$.' },
{ q:'Слайд 5×5 см на проектор. Экран на 5 м. $F = 10$ см. Изображение (см)?', answer:['250','2.5м','250см'], explain:'$\\Gamma \\approx f/d \\approx 500/10 = 50$; изобр. 5×50 = 250 см.' },
{ q:'$d = 2F = 20$ см. $f$ (см)?', answer:'20', explain:'$f = 2F = 20$.' },
{ q:'Фотокамера $F = 50$ мм, объект на 2 м. $f$ (мм)?', answer:['51','51.3','51мм'], explain:'$1/f = 1/50 - 1/2000 \\approx 1/51{,}3$.' }
];
const I9_TH_ITEMS = [
{ q:'В фотоаппарате слайд (объект) находится на расстоянии:', opts:['$d < F$','$F < d < 2F$','$d > 2F$','$d = F$'], correct:2, explain:'$d \\gg F$.' },
{ q:'В проекторе:', opts:['$d > 2F$','$F < d < 2F$','$d < F$','$d = F$'], correct:1, explain:'Чтобы получить увелич. изобр.' },
{ q:'Изображение на матрице фотоаппарата:', opts:['Прямое увелич.','Перевёрнут. уменьш.','Мнимое','Не образуется'], correct:1, explain:'Действ. перевёрн. уменьш.' },
{ q:'Слайды в проекторе вставляют:', opts:['Прямо','Вверх ногами','Боком','Любо'], correct:1, explain:'Изобр. перевёрнутое.' },
{ q:'Чем короче $F$ объектива, тем поле зрения:', opts:['Уже','Шире','Не меняется','Исчезает'], correct:1, explain:'Широкоугольный объектив.' }
];
const I10_CALC_ITEMS = [
{ q:'Лупа $F = 5$ см. $\\Gamma$?', answer:'5', explain:'$\\Gamma = 25/5 = 5$.' },
{ q:'Лупа $\\Gamma = 10$. $F$ (см)?', answer:'2.5', explain:'$F = 25/10 = 2{,}5$ см.' },
{ q:'Микроскоп: $\\Gamma_{об} = 40$, $F_{ок} = 2{,}5$ см. $\\Gamma_{мкс}$?', answer:'400', explain:'$40 \\cdot 25/2{,}5 = 400$.' },
{ q:'Телескоп $F_{об} = 1$ м, $F_{ок} = 2$ см. $\\Gamma$?', answer:'50', explain:'$\\Gamma = 100/2 = 50$.' },
{ q:'Расстояние между линзами телескопа: $F_{об}=80$, $F_{ок}=20$ (мм). $L$ (мм)?', answer:'100', explain:'$L = F_{об} + F_{ок} = 100$ мм.' }
];
const I10_TH_ITEMS = [
{ q:'Угол зрения — это:', opts:['Размер объекта','Угол под которым виден объект','Угол падения','Угол отражения'], correct:1, explain:'Углов. размер.' },
{ q:'Расстояние наилучшего зрения:', opts:['10 см','25 см','50 см','1 м'], correct:1, explain:'$L_0 = 25$ см.' },
{ q:'Лупа использует:', opts:['Рассеивающую линзу','Короткофокусную собирающую','Зеркало','Призму'], correct:1, explain:'Собирающая, $d < F$.' },
{ q:'В телескопе Кеплера окуляр:', opts:['Собирающая','Рассеивающая','Зеркало','Без линзы'], correct:0, explain:'Собирающая.' },
{ q:'Изображение в астрономич. телескопе:', opts:['Прямое','Перевёрнутое','Боком'], correct:1, explain:'Перевёрнутое.' }
];
/* ===== Boss defs ===== */
const BOSS_DEFS = {
b1: { title:'Босс §14 — Скорость света', tag:'§14', xp:65, stages:[
{ q:'$c$ в вакууме (м/с)?', type:'input', a:['3e8','3·10⁸','300000000','3*10^8'], explain:'$3 \\cdot 10^8$.' },
{ q:'Свет — это:', type:'mc', opts:['Звук','ЭМ волна','Магнитное поле','Поток воды'], correct:1, explain:'ЭМ волна.' },
{ q:'$n = 1{,}5$. $v$ (м/с)?', type:'input', a:['2e8','2·10⁸','200000000'], explain:'$c/n = 2 \\cdot 10^8$.' },
{ q:'Первым определил скорость света:', type:'mc', opts:['Ньютон','Рёмер','Максвелл','Юнг'], correct:1, explain:'Рёмер (1676).' },
{ q:'$c = 1/\\sqrt{?}$', type:'mc', opts:['$\\varepsilon_0$','$\\mu_0$','$\\varepsilon_0\\mu_0$','$\\varepsilon_0/\\mu_0$'], correct:2, explain:'$\\varepsilon_0\\mu_0$.' }
]},
b2: { title:'Босс §15 — Интерференция', tag:'§15', xp:70, stages:[
{ q:'Условие max:', type:'mc', opts:['$\\Delta = k\\lambda$','$\\Delta = (2k+1)\\lambda/2$','$\\Delta = 0$','$\\Delta = \\lambda^2$'], correct:0, explain:'max при $\\Delta = k\\lambda$.' },
{ q:'$\\Delta = 1{,}5\\lambda$. Это:', type:'mc', opts:['max','min','Ни то ни другое'], correct:1, explain:'$1{,}5\\lambda = (2 \\cdot 1 + 1)\\lambda/2$.' },
{ q:'Опыт с 2 щелями принадлежит:', type:'mc', opts:['Ньютону','Юнгу','Френелю','Гюйгенсу'], correct:1, explain:'Юнг (1801).' },
{ q:'Условие интерференции:', type:'mc', opts:['Громкость','Когерентность','Скорость','Цвет'], correct:1, explain:'Когерентные источники.' },
{ q:'$k = 2$, $\\lambda = 600$ нм. $\\Delta_{max}$ (нм)?', type:'input', a:'1200', explain:'$2 \\cdot 600 = 1200$ нм.' }
]},
b3: { title:'Босс §16 — Дифракция', tag:'§16', xp:70, stages:[
{ q:'Формула решётки:', type:'mc', opts:['$d/\\lambda = k$','$d\\sin\\varphi = k\\lambda$','$\\lambda = k$','$\\sin\\varphi = d$'], correct:1, explain:'$d\\sin\\varphi = k\\lambda$.' },
{ q:'$d = 2$ мкм, $\\lambda = 500$ нм, $k = 1$. $\\sin\\varphi$?', type:'input', a:'0.25', explain:'$500/2000 = 0{,}25$.' },
{ q:'Дифракция — это:', type:'mc', opts:['Отражение','Огибание препятствий','Преломление','Гашение'], correct:1, explain:'Отклонение от прямолин.' },
{ q:'Принцип Гюйгенса:', type:'mc', opts:['Корпускулы','Каждая точка фронта — источник вторичных волн','Скорость постоянна','Свет = частицы'], correct:1, explain:'Гюйгенс.' },
{ q:'$d = 1$ мкм, $\\lambda = 600$ нм. $k_{max}$?', type:'input', a:'1', explain:'$k \\le d/\\lambda = 1{,}67$ ⇒ $k_{max} = 1$.' }
]},
b4: { title:'Босс §17 — Отражение, зеркала', tag:'§17', xp:65, stages:[
{ q:'Закон отражения:', type:'mc', opts:['$\\angle_п = \\angle_о$','$\\angle_п > \\angle_о$','$\\angle_п < \\angle_о$','Не связаны'], correct:0, explain:'$\\angle_{пад} = \\angle_{отр}$.' },
{ q:'Угол падения 25°. Угол отражения (°)?', type:'input', a:'25', explain:'25°.' },
{ q:'Изображение в плоском зеркале:', type:'mc', opts:['Действ., перевёрнутое','Мнимое, прямое, равное','Уменьшенное','Увеличенное'], correct:1, explain:'Мнимое, прямое, равное.' },
{ q:'Объект на 3 м от зеркала. Изображение на сколько метров от объекта?', type:'input', a:'6', explain:'$d + d = 6$ м.' },
{ q:'Если повернуть зеркало на 15°, отражённый луч повернётся на:', type:'input', a:'30', explain:'$2\\alpha = 30°$.' }
]},
b5: { title:'Босс §18 — Сферические зеркала', tag:'§18', xp:75, stages:[
{ q:'$R = 60$ см. $F$ (см)?', type:'input', a:'30', explain:'$F = R/2 = 30$ см.' },
{ q:'$F=10$ см, $d=20$ см. $f$ (см)?', type:'input', a:'20', explain:'$1/f = 1/10 - 1/20 = 1/20 \\Rightarrow f = 20$.' },
{ q:'Изображение в выпуклом зеркале:', type:'mc', opts:['Действительное увеличенное','Мнимое прямое уменьшенное','Перевёрнутое','Равное'], correct:1, explain:'Всегда мнимое, прямое, уменьшенное.' },
{ q:'$\\Gamma = -f/d$. $f = 30$, $d = 15$. $\\Gamma$?', type:'input', a:['-2','-2.0'], explain:'$-30/15 = -2$.' },
{ q:'Луч через фокус $F$ после отражения идёт:', type:'mc', opts:['Через $C$','Параллельно главной оси','Обратно','Через $O$'], correct:1, explain:'Стандартный луч 2.' }
]},
b6: { title:'Босс §19 — Закон Снелла', tag:'§19', xp:75, stages:[
{ q:'$n_1\\sin\\alpha = ?$', type:'mc', opts:['$n_2\\cos\\beta$','$n_2\\sin\\beta$','$n_2/\\sin\\beta$','$\\sin\\beta/n_2$'], correct:1, explain:'Снелл.' },
{ q:'$n = c/v$. Если $n = 2$, то $v = ?$ (м/с)', type:'input', a:['1.5e8','1.5·10⁸','150000000'], explain:'$v = c/n = 1{,}5 \\cdot 10^8$.' },
{ q:'$\\sin\\alpha_{кр} = n_2/n_1$ при условии:', type:'mc', opts:['$n_1 < n_2$','$n_1 > n_2$','$n_1 = n_2$','Любое'], correct:1, explain:'ПВО только из плотной в менее плотную.' },
{ q:'$n_{вода} = 1{,}33$. $\\sin\\alpha_{кр}$ (округлить до 0,01)?', type:'input', a:['0.75','3/4'], explain:'$1/1{,}33 \\approx 0{,}75$.' },
{ q:'Луч из стекла в воздух под $\\alpha = 60°$, $n_{ст}=1{,}5$. Что произойдёт?', type:'mc', opts:['Преломится','Полное внутр. отражение','Поглотится','Удвоится'], correct:1, explain:'$\\sin 60° = 0{,}866 > 0{,}667$ ⇒ ПВО.' }
]},
b7: { title:'Босс §20 — Призма, оптоволокно', tag:'§20', xp:75, stages:[
{ q:'Призма разлагает белый свет в спектр благодаря:', type:'mc', opts:['Дифракции','Интерференции','Дисперсии','Отражению'], correct:2, explain:'Дисперсия $n(\\lambda)$.' },
{ q:'В стекле фиолетовый свет отклоняется ... красного:', type:'mc', opts:['Слабее','Сильнее','Одинаково','Не отклоняется'], correct:1, explain:'$n_{фиол} > n_{кр}$.' },
{ q:'$n_{серд}=1{,}5$, $n_{обол}=1{,}4$. $\\sin\\alpha_{кр}$?', type:'input', a:['0.933','14/15','0.93'], explain:'$1{,}4/1{,}5 \\approx 0{,}933$.' },
{ q:'Плоскопараллельная пластинка ... луч:', type:'mc', opts:['Поворачивает на 90°','Смещает параллельно','Поглощает','Разлагает в спектр'], correct:1, explain:'Параллельное смещение.' },
{ q:'Оптоволокно использует:', type:'mc', opts:['Поляризацию','Полное внутр. отражение','Дифракцию','Радугу'], correct:1, explain:'ПВО в сердцевине.' }
]},
b8: { title:'Босс §21 — Тонкая линза', tag:'§21', xp:80, stages:[
{ q:'Формула тонкой линзы:', type:'mc', opts:['$1/d + 1/f = 1/F$','$d + f = F$','$d \\cdot f = F$','$d - f = F$'], correct:0, explain:'Формула линзы.' },
{ q:'$F = 0{,}5$ м. $D$ (дптр)?', type:'input', a:'2', explain:'$1/0{,}5 = 2$.' },
{ q:'$D = -4$ дптр — линза:', type:'mc', opts:['Собирающая','Рассеивающая','Плоская','Не существует'], correct:1, explain:'$D < 0$ — рассеивающая.' },
{ q:'$F=20$ см, $d=30$ см. $f$ (см)?', type:'input', a:'60', explain:'$1/f = 1/20 - 1/30 = 1/60$.' },
{ q:'Луч через оптический центр $O$:', type:'mc', opts:['Преломляется','Идёт без преломления','Отражается','Поглощается'], correct:1, explain:'Через $O$ прямо.' }
]},
b9: { title:'Босс §22 — Фотоаппарат, проектор', tag:'§22', xp:80, stages:[
{ q:'В фотокамере объект на расстоянии:', type:'mc', opts:['$d < F$','$F < d < 2F$','$d > 2F$','$d = F$'], correct:2, explain:'Далеко.' },
{ q:'$F = 50$ мм, $d = \\infty$. $f \\approx$ (мм)?', type:'input', a:'50', explain:'$f \\approx F$.' },
{ q:'В проекторе слайд вставляют:', type:'mc', opts:['Прямо','Вверх ногами','Боком'], correct:1, explain:'Изобр. перевёрнутое.' },
{ q:'$d = 2F$ — изображение:', type:'mc', opts:['Уменьшенное','Равное','Увеличенное','Мнимое'], correct:1, explain:'$f = 2F$, $|\\Gamma| = 1$.' },
{ q:'$F=10$ см, $d=12$ см. $|\\Gamma|$?', type:'input', a:['5','5.0'], explain:'$f = 60$, $\\Gamma = -5$.' }
]},
b10: { title:'Босс §23 — Оптические приборы', tag:'§23', xp:80, stages:[
{ q:'Лупа $F = 5$ см. $\\Gamma$?', type:'input', a:'5', explain:'$25/5 = 5$.' },
{ q:'Телескоп: $\\Gamma = ?$', type:'mc', opts:['$F_{ок}/F_{об}$','$F_{об}/F_{ок}$','$F_{об} \\cdot F_{ок}$','$F_{об}+F_{ок}$'], correct:1, explain:'$\\Gamma = F_{об}/F_{ок}$.' },
{ q:'Микроскоп: $\\Gamma_{об} = 20$, $F_{ок} = 5$ см. $\\Gamma_{мкс}$?', type:'input', a:'100', explain:'$20 \\cdot 25/5 = 100$.' },
{ q:'Расстояние наилучшего зрения (см)?', type:'input', a:'25', explain:'$L_0 = 25$ см.' },
{ q:'В телескопе Кеплера расстояние между линзами:', type:'mc', opts:['$F_{об} - F_{ок}$','$F_{об} + F_{ок}$','$F_{об} \\cdot F_{ок}$','любое'], correct:1, explain:'Фокусы совпадают.' }
]}
};
const FINAL_BOSS_DEFS = {
fb1: { title:'Финал §14–§16 — Свет и его свойства', tag:'Финал I', xp:50, stages:[
{ q:'$c$ в вакууме (м/с)?', type:'input', a:['3e8','3·10⁸','300000000','3*10^8'], explain:'$3 \\cdot 10^8$.' },
{ q:'Условие max интерференции:', type:'mc', opts:['$\\Delta = k\\lambda$','$\\Delta = (2k+1)\\lambda/2$','$\\Delta = 0$ всегда','$\\Delta = \\lambda^2$'], correct:0, explain:'$\\Delta = k\\lambda$.' },
{ q:'$d = 2$ мкм, $\\lambda = 500$ нм, $k = 1$. $\\sin\\varphi$?', type:'input', a:'0.25', explain:'$500/2000 = 0{,}25$.' },
{ q:'Дифракция — это:', type:'mc', opts:['Отражение','Огибание препятствий','Преломление','Поглощение'], correct:1, explain:'Огибание препятствий.' },
{ q:'Свет в стекле $n = 1{,}5$. $v$ (м/с)?', type:'input', a:['2e8','2·10⁸','200000000'], explain:'$v = c/n = 2 \\cdot 10^8$.' }
]},
fb2: { title:'Финал §17–§19 — Зеркала и преломление', tag:'Финал II', xp:50, stages:[
{ q:'Закон отражения:', type:'mc', opts:['$\\angle_п=\\angle_о$','$\\angle_п>\\angle_о$','$\\angle_п<\\angle_о$','Не связаны'], correct:0, explain:'Равны.' },
{ q:'Сферич. зеркало: $R = 40$ см. $F$ (см)?', type:'input', a:'20', explain:'$R/2 = 20$.' },
{ q:'$n_1\\sin\\alpha = ?$', type:'mc', opts:['$n_2\\sin\\beta$','$n_2\\cos\\beta$','$n_2$','$\\sin\\beta$'], correct:0, explain:'Снелл.' },
{ q:'Вода $n=1{,}33$. $\\sin\\alpha_{кр}$?', type:'input', a:['0.75','3/4'], explain:'$1/1{,}33 \\approx 0{,}75$.' },
{ q:'Изображение в плоском зеркале:', type:'mc', opts:['Действ. перевёрнутое','Мнимое прямое равное','Уменьшенное','Увеличенное'], correct:1, explain:'Мнимое прямое равное.' }
]},
fb3: { title:'Финал §20–§21 — Призма, линза', tag:'Финал III', xp:50, stages:[
{ q:'Дисперсия — это:', type:'mc', opts:['Отражение','$n = n(\\lambda)$','Дифракция','Поляризация'], correct:1, explain:'Зависимость $n$ от $\\lambda$.' },
{ q:'Оптоволокно работает на:', type:'mc', opts:['Дисперсии','Интерференции','ПВО','Поляризации'], correct:2, explain:'Полное внутреннее отражение.' },
{ q:'$F = 0{,}25$ м. $D$ (дптр)?', type:'input', a:'4', explain:'$1/F = 4$.' },
{ q:'Рассеивающая линза имеет:', type:'mc', opts:['$F > 0$','$F < 0$','$F = 0$','$F = \\infty$'], correct:1, explain:'Отрицательное $F$.' },
{ q:'$F = 10$ см, $d = 30$ см. $\\Gamma$?', type:'input', a:['-0.5','-1/2'], explain:'$f = 15$, $-15/30$.' }
]},
fb4: { title:'Финал §22 — Фотоаппарат, проектор', tag:'Финал IV', xp:50, stages:[
{ q:'В фотокамере $d$:', type:'mc', opts:['$< F$','$F < d < 2F$','$> 2F$'], correct:2, explain:'Далеко.' },
{ q:'В проекторе $d$:', type:'mc', opts:['$< F$','$F < d < 2F$','$> 2F$'], correct:1, explain:'Между $F$ и $2F$.' },
{ q:'$F = 50$ мм, объект на 1 м. $f$ ≈ (мм)?', type:'input', a:['52.6','53','52'], explain:'$1/f = 1/50 - 1/1000 = 19/1000 \\Rightarrow f \\approx 52{,}6$.' },
{ q:'Изображение в проекторе:', type:'mc', opts:['Прямое','Перевёрнутое','Мнимое','Уменьшенное'], correct:1, explain:'Действ. перевёрн. увеличенное.' },
{ q:'$d = 15$ см, $F = 10$ см. $|\\Gamma|$?', type:'input', a:'2', explain:'$f = 30$, $|\\Gamma| = 2$.' }
]},
fb5: { title:'Финал §23 — Лупа, микроскоп, телескоп', tag:'Финал V', xp:50, stages:[
{ q:'$L_0$ — расстояние наилучшего зрения (см)?', type:'input', a:'25', explain:'25 см.' },
{ q:'Лупа $\\Gamma = 25/F$. $F = 2{,}5$ см. $\\Gamma$?', type:'input', a:'10', explain:'$25/2{,}5 = 10$.' },
{ q:'Телескоп: $\\Gamma = ?$', type:'mc', opts:['$F_{об}/F_{ок}$','$F_{ок}/F_{об}$','$F_{об}+F_{ок}$','$F_{об}\\cdot F_{ок}$'], correct:0, explain:'$\\Gamma = F_{об}/F_{ок}$.' },
{ q:'$F_{об} = 1{,}5$ м, $F_{ок} = 3$ см. $\\Gamma$?', type:'input', a:'50', explain:'$150/3 = 50$.' },
{ q:'Микроскоп: $\\Gamma_{об}=25$, $F_{ок}=5$ см. $\\Gamma$ всего?', type:'input', a:'125', explain:'$25 \\cdot 25/5 = 125$.' }
]}
};
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>