feat(geom10 W10): hub переписан в стиле geom11 — 4-кол grid + финал курса + шпаргалка

- KaTeX: onload-инициализация (фикс race с DOMContentLoaded)
- 4-кол grid карточек разделов (mobile/tablet/desktop responsive)
- Цвета карточек: r1 blue, r2 emerald, r3 rose, r4 amber (соответствуют разделам)
- Watermarks: △ ∥ ⊥ →
- Финал курса (аккордеон):
  - Шпаргалка курса: 4 карточки с формулами по разделам (Эйлер, признаки парал./перп., ТТП, расстояние, скаляр.)
  - 9 интегрированных боссов с подсказками + tolerance
  - Master ачивка stereo10_course_master + 100 XP
  - CTA при прохождении
- Прогресс: TOTAL=14, обновлены CH_PARA/CH_IDX
- localStorage keys: geometry10_course_master, geometry10_course_bosses, geometry10_xp
This commit is contained in:
Maxim Dolgolyov
2026-05-29 15:45:22 +03:00
parent 3869cebe95
commit 4533ef14ed
+576 -95
View File
@@ -10,41 +10,42 @@
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Inter:wght@400;500;600;700&family=Unbounded:wght@400;700;800;900&display=swap" rel="stylesheet">
<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"></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/stereo3d.js?v=1" defer></script>
<style>
:root{
--bg:#eff6ff; --card:#fff;
--text:#0b1d33; --muted:#475569;
--border:#dbeafe;
--text:#0f172a; --muted:#475569;
--border:#bfdbfe;
--pri:#2563eb; --pri-d:#1d4ed8;
--pri-soft:#dbeafe;
--r1:#2563eb; --r1-d:#1d4ed8;
--r2:#059669; --r2-d:#047857;
--r3:#e11d48; --r3-d:#be123c;
--r4:#d97706; --r4-d:#b45309;
--ch1:#2563eb; --ch1-d:#1d4ed8;
--ch2:#059669; --ch2-d:#047857;
--ch3:#e11d48; --ch3-d:#be123c;
--ch4:#d97706; --ch4-d:#b45309;
--sh:0 4px 16px rgba(37,99,235,.10);
--sh-h:0 12px 36px rgba(37,99,235,.18);
}
html.dark{
--bg:#020617; --card:#0a1929;
--text:#dbeafe; --muted:#94a3b8;
--border:#1e293b;
--text:#dbeafe; --muted:#93c5fd;
--border:#1e3a5f;
--pri-soft:rgba(37,99,235,.18);
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{min-height:100vh}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;transition:background .25s,color .25s}
.hdr{position:relative;background:linear-gradient(110deg,#1e3a8a 0%,#2563eb 55%,#93c5fd 100%);color:#fff;padding:32px 24px 28px;overflow:hidden;border-bottom:2px solid rgba(219,234,254,.15)}
.hdr::before{content:'СТЕРЕОМЕТРИЯ';position:absolute;right:-14px;top:-18%;font-family:'Outfit',sans-serif;font-size:clamp(4rem,12vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(219,234,254,.10);line-height:1;pointer-events:none;user-select:none}
/* HEADER */
.hdr{position:relative;background:linear-gradient(110deg,#1e3a8a 0%,#2563eb 55%,#93c5fd 100%);color:#fff;padding:32px 24px 28px;overflow:hidden;border-bottom:2px solid rgba(191,219,254,.18)}
.hdr::before{content:'ГЕОМЕТРИЯ';position:absolute;right:-14px;top:-18%;font-family:'Outfit',sans-serif;font-size:clamp(5rem,16vw,13rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(219,234,254,.12);line-height:1;pointer-events:none;user-select:none}
.hdr-inner{position:relative;z-index:1;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.hdr-back{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;background:rgba(255,255,255,.14);border-radius:9px;color:#fff;text-decoration:none;font-size:.85rem;font-weight:600;transition:background .15s}
.hdr-back:hover{background:rgba(255,255,255,.24)}
.hdr h1{font-family:'Outfit',sans-serif;font-size:1.85rem;font-weight:900;letter-spacing:-.01em}
.hdr-sub{font-size:.92rem;opacity:.85;margin-top:4px}
.hdr-sub{font-size:.92rem;opacity:.88;margin-top:4px}
.hdr-side{margin-left:auto;display:flex;gap:8px}
.hdr-btn{padding:8px 12px;background:rgba(255,255,255,.14);border:none;color:#fff;border-radius:9px;cursor:pointer;font-weight:600;font-size:.82rem;display:inline-flex;align-items:center;gap:6px;transition:background .15s;font-family:inherit}
.hdr-btn:hover{background:rgba(255,255,255,.24)}
@@ -52,50 +53,53 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--t
main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
.prog-overall{background:linear-gradient(135deg,var(--pri-soft),rgba(225,29,72,.10));border:1px solid var(--border);border-radius:14px;padding:14px 18px;margin-bottom:28px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
/* OVERALL PROGRESS */
.prog-overall{background:linear-gradient(135deg,var(--pri-soft),rgba(147,197,253,.12));border:1px solid var(--border);border-radius:14px;padding:14px 18px;margin-bottom:28px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.po-icon{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,#2563eb,#93c5fd);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:'Outfit',sans-serif;font-size:1.4rem;font-weight:900}
.po-text{flex:1;min-width:160px}
.po-label{font-size:.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
.po-bar{height:8px;background:rgba(37,99,235,.12);border-radius:5px;overflow:hidden;margin-top:6px}
.po-fill{height:100%;background:linear-gradient(90deg,var(--pri),#e11d48);border-radius:5px;transition:width .5s}
.po-xp{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:linear-gradient(135deg,#f59e0b,#dc2626);color:#fff;border-radius:99px;font-size:.8rem;font-weight:800;font-family:'Unbounded',sans-serif;letter-spacing:.02em;box-shadow:0 4px 12px rgba(220,38,38,.22)}
.po-bar{height:8px;background:rgba(37,99,235,.14);border-radius:5px;overflow:hidden;margin-top:6px}
.po-fill{height:100%;background:linear-gradient(90deg,var(--pri),#93c5fd);border-radius:5px;transition:width .5s}
.po-xp{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:linear-gradient(135deg,#f59e0b,var(--pri));color:#fff;border-radius:99px;font-size:.8rem;font-weight:800;font-family:'Unbounded',sans-serif;letter-spacing:.02em;box-shadow:0 4px 12px rgba(37,99,235,.24)}
/* CHAPTER GRID — 4 sections */
.ch-grid{display:grid;grid-template-columns:1fr;gap:18px;margin-bottom:30px}
@media(min-width:600px){.ch-grid{grid-template-columns:1fr 1fr}}
@media(min-width:1000px){.ch-grid{grid-template-columns:repeat(2,1fr)}}
@media(min-width:680px){.ch-grid{grid-template-columns:1fr 1fr}}
@media(min-width:1100px){.ch-grid{grid-template-columns:repeat(4,1fr)}}
.ch-card{background:var(--card);border:1.5px solid var(--border);border-radius:18px;overflow:hidden;display:flex;flex-direction:column;transition:transform .2s,box-shadow .2s,border-color .2s;cursor:pointer;text-decoration:none;color:inherit}
.ch-card:hover{transform:translateY(-4px);box-shadow:var(--sh-h)}
.ch-cover{padding:22px 22px 18px;color:#fff;position:relative;overflow:hidden}
.ch-cover-wm{position:absolute;right:-8px;top:-22px;font-size:6rem;font-weight:900;font-family:'Outfit',sans-serif;line-height:1;color:rgba(255,255,255,.18);pointer-events:none}
.ch-cover-wm{position:absolute;right:-8px;top:-22px;font-size:5.2rem;font-weight:900;font-family:'Outfit',sans-serif;line-height:1;color:rgba(255,255,255,.20);pointer-events:none;letter-spacing:-.04em}
.ch-num{display:inline-block;padding:4px 10px;background:rgba(255,255,255,.22);border-radius:99px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;position:relative;z-index:1}
.ch-title{font-family:'Outfit',sans-serif;font-size:1.1rem;font-weight:800;letter-spacing:-.01em;position:relative;z-index:1;line-height:1.3}
.ch-range{font-size:.84rem;opacity:.88;margin-top:4px;position:relative;z-index:1;font-weight:500}
.ch-cover.r1{background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#93c5fd)}
.ch-cover.r2{background:linear-gradient(135deg,#064e3b,#059669 60%,#86efac)}
.ch-cover.r3{background:linear-gradient(135deg,#7f1d1d,#e11d48 60%,#fda4af)}
.ch-cover.r4{background:linear-gradient(135deg,#78350f,#d97706 60%,#fcd34d)}
.ch-cover.ch1{background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#93c5fd)}
.ch-cover.ch2{background:linear-gradient(135deg,#064e3b,#059669 60%,#86efac)}
.ch-cover.ch3{background:linear-gradient(135deg,#7f1d1d,#e11d48 60%,#fda4af)}
.ch-cover.ch4{background:linear-gradient(135deg,#78350f,#d97706 60%,#fcd34d)}
.ch-body{padding:16px 20px 18px;display:flex;flex-direction:column;flex:1}
.ch-desc{font-size:.88rem;color:var(--text);opacity:.82;flex:1;margin-bottom:12px;line-height:1.55}
.ch-desc{font-size:.88rem;color:var(--text);opacity:.84;flex:1;margin-bottom:12px;line-height:1.55}
.ch-prog{margin-bottom:12px}
.ch-prog-label{display:flex;justify-content:space-between;font-size:.74rem;color:var(--muted);font-weight:600;margin-bottom:4px}
.ch-prog-bar{height:6px;background:rgba(0,0,0,.07);border-radius:4px;overflow:hidden}
.ch-prog-fill{height:100%;border-radius:4px;transition:width .5s}
.ch-card.r1-card .ch-prog-fill{background:linear-gradient(90deg,var(--r1),var(--r1-d))}
.ch-card.r2-card .ch-prog-fill{background:linear-gradient(90deg,var(--r2),var(--r2-d))}
.ch-card.r3-card .ch-prog-fill{background:linear-gradient(90deg,var(--r3),var(--r3-d))}
.ch-card.r4-card .ch-prog-fill{background:linear-gradient(90deg,var(--r4),var(--r4-d))}
.ch-card.ch1-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch1),var(--ch1-d))}
.ch-card.ch2-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch2),var(--ch2-d))}
.ch-card.ch3-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch3),var(--ch3-d))}
.ch-card.ch4-card .ch-prog-fill{background:linear-gradient(90deg,var(--ch4),var(--ch4-d))}
.ch-action{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:11px;font-weight:700;font-size:.9rem;color:#fff;transition:filter .15s}
.ch-action:hover{filter:brightness(1.08)}
.ch-card.r1-card .ch-action{background:linear-gradient(135deg,var(--r1),#93c5fd)}
.ch-card.r2-card .ch-action{background:linear-gradient(135deg,var(--r2),#86efac)}
.ch-card.r3-card .ch-action{background:linear-gradient(135deg,var(--r3),#fda4af)}
.ch-card.r4-card .ch-action{background:linear-gradient(135deg,var(--r4),#fcd34d)}
.ch-card.ch1-card .ch-action{background:linear-gradient(135deg,var(--ch1),#93c5fd)}
.ch-card.ch2-card .ch-action{background:linear-gradient(135deg,var(--ch2),#86efac)}
.ch-card.ch3-card .ch-action{background:linear-gradient(135deg,var(--ch3),#fda4af)}
.ch-card.ch4-card .ch-action{background:linear-gradient(135deg,var(--ch4),#fcd34d)}
/* ACHIEVEMENT STRIP */
.ach-strip{background:var(--card);border:1.5px solid var(--border);border-radius:16px;padding:18px 22px;margin-bottom:28px;display:flex;align-items:center;gap:16px;transition:border-color .4s,box-shadow .4s}
.ach-strip.lit{border-color:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.18)}
.ach-icon{width:52px;height:52px;border-radius:14px;background:rgba(0,0,0,.06);display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .4s}
@@ -107,11 +111,92 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
.ach-sub{font-size:.85rem;color:var(--muted);margin-top:2px}
.ach-strip.lit .ach-title{color:#92400e}
.preview-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:30px}
.preview-cell{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:12px;text-align:center}
.preview-cell-label{font-size:.78rem;color:var(--muted);font-weight:700;margin-top:6px;text-transform:uppercase;letter-spacing:.06em}
.foot{text-align:center;padding:24px 16px;color:var(--muted);font-size:.78rem;border-top:1px solid var(--border)}
/* COURSE FINAL */
.final-wrap{margin:0 0 28px;background:var(--card);border:1.5px solid var(--border);border-radius:18px;overflow:hidden;box-shadow:var(--sh)}
.final-head{padding:18px 22px;background:linear-gradient(135deg,#1e3a8a 0%,#2563eb 55%,#7c3aed 100%);color:#fff;cursor:pointer;display:flex;align-items:center;gap:14px;user-select:none;transition:filter .15s}
.final-head:hover{filter:brightness(1.06)}
.final-head-icon{width:46px;height:46px;border-radius:12px;background:rgba(255,255,255,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.final-head-icon svg{width:26px;height:26px;stroke:#fff;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.final-head-text{flex:1;min-width:0}
.final-head-tag{display:inline-block;padding:3px 9px;background:rgba(255,255,255,.22);border-radius:99px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px}
.final-head-title{font-family:'Outfit',sans-serif;font-size:1.18rem;font-weight:800;letter-spacing:-.01em;line-height:1.25}
.final-head-sub{font-size:.84rem;opacity:.9;margin-top:2px}
.final-chevron{flex-shrink:0;transition:transform .25s}
.final-chevron svg{width:24px;height:24px;stroke:#fff;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.final-wrap.open .final-chevron{transform:rotate(180deg)}
.final-body{display:none;padding:22px}
.final-wrap.open .final-body{display:block}
.fin-section-title{font-family:'Outfit',sans-serif;font-size:1.18rem;font-weight:800;color:var(--text);margin:8px 0 14px;letter-spacing:-.005em;display:flex;align-items:center;gap:9px}
.fin-section-title svg{width:20px;height:20px;stroke:var(--pri);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* CHEAT SHEET */
.cheat-grid{display:grid;grid-template-columns:1fr;gap:14px;margin-bottom:28px}
@media(min-width:680px){.cheat-grid{grid-template-columns:1fr 1fr}}
@media(min-width:1000px){.cheat-grid{grid-template-columns:repeat(4,1fr)}}
.cheat-card{border:1.5px solid var(--border);border-radius:13px;padding:14px 16px;background:var(--card);position:relative;overflow:hidden}
.cheat-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px}
.cheat-card.c1::before{background:linear-gradient(180deg,var(--ch1),var(--ch1-d))}
.cheat-card.c2::before{background:linear-gradient(180deg,var(--ch2),var(--ch2-d))}
.cheat-card.c3::before{background:linear-gradient(180deg,var(--ch3),var(--ch3-d))}
.cheat-card.c4::before{background:linear-gradient(180deg,var(--ch4),var(--ch4-d))}
.cheat-head{display:flex;align-items:center;gap:9px;margin-bottom:9px;padding-left:6px}
.cheat-badge{font-size:.7rem;font-weight:800;padding:2px 8px;border-radius:99px;color:#fff;letter-spacing:.05em;text-transform:uppercase}
.cheat-card.c1 .cheat-badge{background:var(--ch1)}
.cheat-card.c2 .cheat-badge{background:var(--ch2)}
.cheat-card.c3 .cheat-badge{background:var(--ch3)}
.cheat-card.c4 .cheat-badge{background:var(--ch4)}
.cheat-title{font-weight:800;color:var(--text);font-size:.98rem}
.cheat-list{list-style:none;padding-left:6px;margin:0}
.cheat-list li{padding:6px 0;border-bottom:1px dashed var(--border);font-size:.92rem;line-height:1.5;color:var(--text)}
.cheat-list li:last-child{border-bottom:0}
/* BOSS PROGRESS */
.boss-overall-bar{background:linear-gradient(135deg,rgba(37,99,235,.08),rgba(147,197,253,.06));border:1px solid var(--border);border-radius:12px;padding:13px 16px;margin:6px 0 18px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.boss-overall-bar .lab{font-weight:700;font-size:.95rem;color:var(--text);min-width:200px}
.boss-overall-bar .bar{flex:1;min-width:160px;height:9px;background:rgba(37,99,235,.14);border-radius:5px;overflow:hidden}
.boss-overall-bar .fill{height:100%;background:linear-gradient(90deg,var(--pri),#93c5fd,#f59e0b);transition:width .5s;border-radius:5px}
/* BOSS CARDS */
.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:rgba(37,99,235,.14);color:var(--pri-d);letter-spacing:.04em;text-transform:uppercase}
html.dark .boss-tag{color:#93c5fd}
.boss-title{font-family:'Outfit',sans-serif;font-weight:800;color:var(--text);font-size:1.02rem;flex:1;min-width:0}
.boss-q{padding:12px 14px;background:rgba(37,99,235,.06);border-radius:10px;font-size:.96rem;line-height:1.55;margin-bottom:10px;color:var(--text)}
.boss-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.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:130px;text-align:center;font-size:.95rem;transition:border-color .15s}
.boss-input:focus{outline:0;border-color:var(--pri);box-shadow:0 0 0 3px var(--pri-soft)}
.boss-btn{padding:8px 16px;border-radius:9px;background:var(--card);color:var(--text);border:1.5px solid var(--border);font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s,transform .1s}
.boss-btn:hover{background:var(--pri-soft);border-color:var(--pri)}
.boss-btn:active{transform:scale(.96)}
.boss-btn.primary{background:linear-gradient(135deg,var(--pri),#2563eb);color:#fff;border-color:transparent}
.boss-btn.primary:hover{filter:brightness(1.08)}
.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:#d1fae5;color:#065f46;border-left:4px solid #10b981}
.boss-fb.fail{display:block;background:#fee2e2;color:#7f1d1d;border-left:4px solid #dc2626}
html.dark .boss-fb.ok{background:rgba(16,185,129,.18);color:#a7f3d0}
html.dark .boss-fb.fail{background:rgba(220,38,38,.18);color:#fecaca}
.boss-hint-txt{margin-top:8px;padding:9px 13px;background:rgba(245,158,11,.12);border-left:3px solid #f59e0b;border-radius:6px;font-size:.86rem;color:var(--text);display:none;line-height:1.5}
.boss-hint-txt.show{display:block}
/* FINAL CTA */
.final-cta{margin-top:24px;padding:18px 20px;border-radius:14px;background:linear-gradient(135deg,#fef3c7,#fde68a);border:1.5px solid #fbbf24;display:none;align-items:center;gap:14px;flex-wrap:wrap}
.final-cta.show{display:flex}
html.dark .final-cta{background:linear-gradient(135deg,rgba(245,158,11,.18),rgba(217,119,6,.15));border-color:#d97706}
.final-cta-icon{width:48px;height:48px;border-radius:12px;background:linear-gradient(135deg,#fbbf24,#f59e0b);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.final-cta-icon svg{width:28px;height:28px;stroke:#fff;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.final-cta-txt{flex:1;min-width:180px}
.final-cta-title{font-weight:800;color:#92400e;font-size:1.05rem;font-family:'Outfit',sans-serif}
html.dark .final-cta-title{color:#fde68a}
.final-cta-sub{font-size:.86rem;color:#78350f;margin-top:2px}
html.dark .final-cta-sub{color:#fcd34d}
.final-cta-btn{padding:10px 18px;border-radius:10px;background:linear-gradient(135deg,var(--pri),#2563eb);color:#fff;text-decoration:none;font-weight:800;font-size:.9rem;display:inline-flex;align-items:center;gap:7px;transition:filter .15s}
.final-cta-btn:hover{filter:brightness(1.1)}
.final-cta-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
</style>
</head>
<body>
@@ -126,7 +211,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
<div>
<h1>Геометрия — 10 класс</h1>
<div class="hdr-sub">Стереометрия · 3D-фигуры · Координаты и векторы</div>
<div class="hdr-sub">Полный курс стереометрии: введение, параллельность, перпендикулярность, координаты и векторы</div>
</div>
<div class="hdr-side">
<button id="theme-btn" class="hdr-btn" title="Сменить тему">
@@ -140,7 +225,9 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
<main>
<section class="prog-overall">
<div class="po-icon"></div>
<div class="po-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:28px;height:28px"><path d="M3 7l9-5 9 5-9 5z"/><path d="M3 7v10l9 5 9-5V7"/><path d="M12 12v10"/></svg>
</div>
<div class="po-text">
<div class="po-label">Общий прогресс по курсу</div>
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
@@ -149,21 +236,14 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
</section>
<div class="preview-row" id="preview-row">
<div class="preview-cell"><div id="pv-cube"></div><div class="preview-cell-label">Куб</div></div>
<div class="preview-cell"><div id="pv-pyr"></div><div class="preview-cell-label">Пирамида</div></div>
<div class="preview-cell"><div id="pv-prism"></div><div class="preview-cell-label">Призма</div></div>
<div class="preview-cell"><div id="pv-tetra"></div><div class="preview-cell-label">Тетраэдр</div></div>
</div>
<div class="ch-grid">
<a href="/textbook/geometry-10-r1" class="ch-card r1-card" id="r-1">
<div class="ch-cover r1">
<div class="ch-cover-wm"></div>
<a href="/textbook/geometry-10-r1" class="ch-card ch1-card" id="ch-1">
<div class="ch-cover ch1">
<div class="ch-cover-wm">&#9651;</div>
<div class="ch-num">Раздел 1</div>
<div class="ch-title">Введение в стереометрию</div>
<div class="ch-range">§1–§3 + Финал</div>
<div class="ch-range">&sect;1&ndash;&sect;3 + Финал</div>
</div>
<div class="ch-body">
<div class="ch-desc">Пространственные фигуры (призма, пирамида, цилиндр, конус, шар), аксиомы стереометрии и их следствия, метод сечений многогранников.</div>
@@ -178,15 +258,15 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
</a>
<a href="/textbook/geometry-10-r2" class="ch-card r2-card" id="r-2">
<div class="ch-cover r2">
<div class="ch-cover-wm"></div>
<a href="/textbook/geometry-10-r2" class="ch-card ch2-card" id="ch-2">
<div class="ch-cover ch2">
<div class="ch-cover-wm">&#8741;</div>
<div class="ch-num">Раздел 2</div>
<div class="ch-title">Параллельность</div>
<div class="ch-range">§4–§6 + Финал</div>
<div class="ch-range">&sect;4&ndash;&sect;6 + Финал</div>
</div>
<div class="ch-body">
<div class="ch-desc">Взаимное расположение прямых в пространстве (скрещивающиеся), прямой и плоскости, двух плоскостей. Признаки параллельности.</div>
<div class="ch-desc">Взаимное расположение прямых (скрещивающиеся), прямой и плоскости, двух плоскостей. Признаки параллельности.</div>
<div class="ch-prog">
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-2">0%</span></div>
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-2" style="width:0%"></div></div>
@@ -198,15 +278,15 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
</a>
<a href="/textbook/geometry-10-r3" class="ch-card r3-card" id="r-3">
<div class="ch-cover r3">
<div class="ch-cover-wm"></div>
<a href="/textbook/geometry-10-r3" class="ch-card ch3-card" id="ch-3">
<div class="ch-cover ch3">
<div class="ch-cover-wm">&#8869;</div>
<div class="ch-num">Раздел 3</div>
<div class="ch-title">Перпендикулярность</div>
<div class="ch-range">§7–§10 + Финал</div>
<div class="ch-range">&sect;7&ndash;&sect;10 + Финал</div>
</div>
<div class="ch-body">
<div class="ch-desc">Перпендикулярность прямой и плоскости, расстояния в пространстве, угол между прямой и плоскостью (теорема о трёх перпендикулярах), двугранный угол.</div>
<div class="ch-desc">Перпендикулярность прямой и плоскости, расстояния в пространстве, угол между прямой и плоскостью (ТТП), двугранный угол.</div>
<div class="ch-prog">
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-3">0%</span></div>
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-3" style="width:0%"></div></div>
@@ -218,15 +298,15 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
</a>
<a href="/textbook/geometry-10-r4" class="ch-card r4-card" id="r-4">
<div class="ch-cover r4">
<div class="ch-cover-wm"></div>
<a href="/textbook/geometry-10-r4" class="ch-card ch4-card" id="ch-4">
<div class="ch-cover ch4">
<div class="ch-cover-wm">&rarr;</div>
<div class="ch-num">Раздел 4</div>
<div class="ch-title">Координаты и векторы</div>
<div class="ch-range">§11–§14 + Финал</div>
<div class="ch-range">&sect;11&ndash;&sect;14 + Финал</div>
</div>
<div class="ch-body">
<div class="ch-desc">Прямоугольная система координат в пространстве, векторы и действия над ними, скалярное произведение, применение векторно-координатного метода.</div>
<div class="ch-desc">ПДСК в пространстве, векторы и действия над ними, скалярное произведение, применение векторно-координатного метода.</div>
<div class="ch-prog">
<div class="ch-prog-label"><span>Прогресс</span><span id="prog-4">0%</span></div>
<div class="ch-prog-bar"><div class="ch-prog-fill" id="fill-4" style="width:0%"></div></div>
@@ -240,6 +320,106 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
<section class="final-wrap" id="course-final">
<div class="final-head" id="final-head" tabindex="0" role="button" aria-expanded="false" aria-controls="final-body">
<div class="final-head-icon">
<svg viewBox="0 0 24 24"><path d="M7 4h10v6a5 5 0 0 1-10 0V4z"/><path d="M5 4h2v2H5a2 2 0 0 1 0-4M19 4h-2v2h2a2 2 0 0 0 0-4M9 20h6M12 15v5"/></svg>
</div>
<div class="final-head-text">
<div class="final-head-tag">Финал курса</div>
<div class="final-head-title">Итоговая проверка по всему курсу</div>
<div class="final-head-sub">Шпаргалка курса и 9 интегрированных боссов. Победи всех — получи «Магистр геометрии 10» и +100 XP.</div>
</div>
<div class="final-chevron"><svg viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></div>
</div>
<div class="final-body" id="final-body">
<div class="fin-section-title">
<svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
Шпаргалка курса
</div>
<div class="cheat-grid">
<div class="cheat-card c1">
<div class="cheat-head">
<span class="cheat-badge">Р. 1</span>
<span class="cheat-title">Введение</span>
</div>
<ul class="cheat-list">
<li>Формула Эйлера: $В - Р + Г = 2$</li>
<li>Призма ($n$-уг.): $3n$ рёбер, $n+2$ грани</li>
<li>Пирамида ($n$-уг.): $2n$ рёбер, $n+1$ грань</li>
<li>Куб: сечение max — правильный 6-угольник</li>
</ul>
</div>
<div class="cheat-card c2">
<div class="cheat-head">
<span class="cheat-badge">Р. 2</span>
<span class="cheat-title">Параллельность</span>
</div>
<ul class="cheat-list">
<li>$a \parallel b \subset \alpha, a \not\subset \alpha \Rightarrow a \parallel \alpha$</li>
<li>Признак $\alpha \parallel \beta$: 2 пересек. $a, b \subset \alpha$, $a, b \parallel \beta$</li>
<li>Скрещ. прямые: не лежат в одной плоскости</li>
<li>Транзитивность: $a \parallel b, b \parallel c \Rightarrow a \parallel c$</li>
</ul>
</div>
<div class="cheat-card c3">
<div class="cheat-head">
<span class="cheat-badge">Р. 3</span>
<span class="cheat-title">Перпендикулярность</span>
</div>
<ul class="cheat-list">
<li>Признак $l \perp \alpha$: $l \perp m, l \perp n, m \cap n \in \alpha$</li>
<li>ТТП: $AH \perp \alpha, HB \perp BC \Rightarrow AB \perp BC$</li>
<li>Признак $\alpha \perp \beta$: $\alpha \supset l \perp \beta$</li>
<li>Куб (ребро $a$): $AC_1 = a\sqrt{3}$, $AC = a\sqrt{2}$</li>
</ul>
</div>
<div class="cheat-card c4">
<div class="cheat-head">
<span class="cheat-badge">Р. 4</span>
<span class="cheat-title">Координаты и векторы</span>
</div>
<ul class="cheat-list">
<li>$|AB| = \sqrt{(\Delta x)^2 + (\Delta y)^2 + (\Delta z)^2}$</li>
<li>$\vec{a} \cdot \vec{b} = x_1 x_2 + y_1 y_2 + z_1 z_2$</li>
<li>$\vec{a} \perp \vec{b} \Leftrightarrow \vec{a} \cdot \vec{b} = 0$</li>
<li>$\cos\varphi = \dfrac{\vec{a} \cdot \vec{b}}{|\vec{a}| \cdot |\vec{b}|}$</li>
</ul>
</div>
</div>
<div class="fin-section-title">
<svg viewBox="0 0 24 24"><path d="M14.5 3.5l-5 5L4 4l1.5 6L3 12l5 1 1 5 2.5-2.5 6 1.5-4.5-5.5 5-5"/></svg>
9 интегрированных боссов
</div>
<div class="boss-overall-bar">
<div class="lab" id="fin-boss-lab">Боссов побеждено: 0 / 9</div>
<div class="bar"><div class="fill" id="fin-boss-fill" style="width:0%"></div></div>
</div>
<div id="fin-bosses-container"></div>
<div class="final-cta" id="final-cta">
<div class="final-cta-icon">
<svg viewBox="0 0 24 24"><path d="M6 9H4l-1-3h18l-1 3h-2M6 9l1 6h10l1-6M6 9h12"/><path d="M9 21h6M12 15v6"/></svg>
</div>
<div class="final-cta-txt">
<div class="final-cta-title">Курс Геометрия 10 пройден!</div>
<div class="final-cta-sub">Вы прошли всю итоговую проверку курса. +100 XP, ачивка «Магистр геометрии 10» получена.</div>
</div>
<a href="/textbooks" class="final-cta-btn">
К каталогу учебников
<svg viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
</a>
</div>
</div>
</section>
<div class="ach-strip" id="ach-strip">
<div class="ach-icon">
<svg viewBox="0 0 24 24">
@@ -248,19 +428,20 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
</div>
<div class="ach-text">
<div class="ach-title">Магистр геометрии 10</div>
<div class="ach-sub" id="ach-sub">Прочитайте все 14 параграфов четырёх разделов, чтобы получить достижение</div>
<div class="ach-sub" id="ach-sub">Прочитайте все 14 параграфов курса, чтобы получить достижение</div>
</div>
</div>
</main>
<footer class="foot">
Интерактивный учебник «Геометрия — 10 класс» · LearnSpace
Интерактивный учебник «Геометрия — 10 класс» &middot; LearnSpace
</footer>
<script>
'use strict';
/* THEME */
(function(){
var saved = localStorage.getItem('geometry10_theme') || localStorage.getItem('theme') || 'light';
if (saved === 'dark') document.documentElement.classList.add('dark');
@@ -275,9 +456,20 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
});
})();
/* PROGRESS */
var TOTAL = 14;
var CH_PARA = { 'geometry-10-r1': 3, 'geometry-10-r2': 3, 'geometry-10-r3': 4, 'geometry-10-r4': 4 };
var CH_IDX = { 'geometry-10-r1': 1, 'geometry-10-r2': 2, 'geometry-10-r3': 3, 'geometry-10-r4': 4 };
var CH_PARA = {
'geometry-10-r1': 3,
'geometry-10-r2': 3,
'geometry-10-r3': 4,
'geometry-10-r4': 4,
};
var CH_IDX = {
'geometry-10-r1': 1,
'geometry-10-r2': 2,
'geometry-10-r3': 3,
'geometry-10-r4': 4,
};
function setChProg(idx, readCount, total) {
var pct = total ? Math.round(readCount * 100 / total) : 0;
@@ -294,6 +486,8 @@ function setChProg(idx, readCount, total) {
return pct;
}
var FIN_ACH_KEY = 'geometry10_course_master';
function renderProgress(children) {
var totalRead = 0;
for (var i = 0; i < children.length; i++) {
@@ -309,21 +503,329 @@ function renderProgress(children) {
var pct = Math.round(totalRead * 100 / TOTAL);
var overallEl = document.getElementById('overall-text');
var fillEl = document.getElementById('overall-fill');
if (overallEl) overallEl.textContent = totalRead + ' из ' + TOTAL + ' параграфов · ' + pct + '%';
if (overallEl) overallEl.textContent = totalRead + ' из ' + TOTAL + ' параграфов \xb7 ' + pct + '%';
if (fillEl) fillEl.style.width = pct + '%';
var xpBadge = document.getElementById('hero-xp-badge');
var xp = parseInt(localStorage.getItem('geometry10_xp') || '0', 10) || 0;
if (xpBadge && xp > 0) { xpBadge.style.display = ''; xpBadge.textContent = xp + ' XP'; }
if (xpBadge && xp > 0) {
xpBadge.style.display = '';
xpBadge.textContent = xp + ' XP';
}
if (totalRead >= TOTAL) {
var mastered = localStorage.getItem(FIN_ACH_KEY) === '1';
if (totalRead >= TOTAL || mastered) {
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы прочитали весь курс геометрии 10 класса.';
if (sub) {
if (mastered) sub.textContent = 'Выполнено! Вы — Магистр геометрии 10.';
else sub.textContent = 'Выполнено! Вы прочитали весь курс геометрии 10 класса.';
}
}
}
/* COURSE FINAL — lazy bosses */
var FIN_BOSS_KEY = 'geometry10_course_bosses';
var FIN_BOSSES = [
{
n: 1,
title: 'Формула Эйлера',
tag: 'Раздел 1',
q: 'Многогранник имеет $В = 8$ вершин и $Р = 18$ рёбер. Сколько у него граней?',
hint: 'По формуле Эйлера $В - Р + Г = 2$: $Г = 2 - В + Р = 2 - 8 + 18 = 12$.',
ans: 12,
step: '1'
},
{
n: 2,
title: 'Сечение куба',
tag: 'Раздел 1',
q: 'Какое максимальное число сторон может иметь сечение куба плоскостью?',
hint: 'Куб имеет 6 граней. Плоскость может пересечь все 6 граней — получится правильный 6-угольник.',
ans: 6,
step: '1'
},
{
n: 3,
title: 'Параллельность',
tag: 'Раздел 2',
q: 'Сколько рёбер куба параллельно плоскости одной из граней (но не лежит в этой плоскости)?',
hint: '4 ребра противоположной грани параллельны заданной плоскости. Ещё 4 рёбра лежат в плоскости, 4 — перпендикулярны.',
ans: 4,
step: '1'
},
{
n: 4,
title: 'Расстояние в кубе',
tag: 'Раздел 3',
q: 'Куб с ребром $1$. Найдите длину диагонали куба $AC_1$ (округлите до сотых, допуск 0{,}01).',
hint: 'Диагональ куба $= a\\sqrt{3} = \\sqrt{3} \\approx 1{,}73$.',
ans: 1.73,
tol: 0.01,
step: '0.01'
},
{
n: 5,
title: 'Угол наклонной (ТТП)',
tag: 'Раздел 3',
q: 'Куб с ребром $1$. Найдите $\\tg$ угла между диагональю $AC_1$ и плоскостью $ABCD$ (десятичная дробь, точность 0{,}01).',
hint: 'Перпендикуляр $C_1C = 1$, проекция $AC = \\sqrt{2}$, $\\tg \\varphi = \\dfrac{1}{\\sqrt{2}} = \\dfrac{\\sqrt{2}}{2} \\approx 0{,}71$.',
ans: 0.71,
tol: 0.01,
step: '0.01'
},
{
n: 6,
title: 'Перпендикулярность плоскостей',
tag: 'Раздел 3',
q: 'Сколько граней куба перпендикулярно одной заданной грани?',
hint: '4 смежные грани перпендикулярны заданной (общее ребро + признак). 1 противоположная параллельна.',
ans: 4,
step: '1'
},
{
n: 7,
title: 'Расстояние между точками',
tag: 'Раздел 4',
q: 'Найдите $|AB|$, где $A(1; 2; 2)$, $B(4; 6; 2)$.',
hint: '$|AB| = \\sqrt{(4-1)^2 + (6-2)^2 + (2-2)^2} = \\sqrt{9 + 16 + 0} = 5$.',
ans: 5,
step: '1'
},
{
n: 8,
title: 'Скалярное произведение',
tag: 'Раздел 4',
q: 'Найдите $\\vec{a} \\cdot \\vec{b}$, где $\\vec{a} = (2; 3; 1)$, $\\vec{b} = (1; -2; 4)$.',
hint: '$\\vec{a} \\cdot \\vec{b} = 2 \\cdot 1 + 3 \\cdot (-2) + 1 \\cdot 4 = 2 - 6 + 4 = 0$.',
ans: 0,
step: '1'
},
{
n: 9,
title: 'Магистр стереометрии',
tag: 'синтез всего курса',
q: 'Куб с ребром $1$, поместим $A$ в начало координат: $A(0;0;0)$, $B_1(1;0;1)$, $D_1(0;1;1)$. Найдите $\\cos$ угла между $\\vec{AB_1}$ и $\\vec{AD_1}$ (десятичная дробь, точность 0{,}01).',
hint: '$\\vec{AB_1} \\cdot \\vec{AD_1} = 0 + 0 + 1 = 1$, $|\\vec{AB_1}| = |\\vec{AD_1}| = \\sqrt{2}$. $\\cos\\varphi = \\dfrac{1}{2} = 0{,}50$.',
ans: 0.5,
tol: 0.01,
step: '0.01'
}
];
function loadFinBossState(){
try { return JSON.parse(localStorage.getItem(FIN_BOSS_KEY) || '{}') || {}; }
catch(e) { return {}; }
}
function saveFinBossState(s){
try { localStorage.setItem(FIN_BOSS_KEY, JSON.stringify(s)); } catch(e){}
}
function finRenderKatex(root){
if (typeof window.renderMathInElement !== 'function') return;
try {
window.renderMathInElement(root, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
],
throwOnError: false
});
} catch(e){}
}
function updateFinBossBar(state){
var won = 0;
for (var k in state) if (state[k]) won++;
var lab = document.getElementById('fin-boss-lab');
var fill = document.getElementById('fin-boss-fill');
if (lab) lab.textContent = 'Боссов побеждено: ' + won + ' / ' + FIN_BOSSES.length;
if (fill) fill.style.width = Math.round(won * 100 / FIN_BOSSES.length) + '%';
return won;
}
function maybeUnlockMaster(state){
if (localStorage.getItem(FIN_ACH_KEY) === '1') return;
var won = 0;
for (var k in state) if (state[k]) won++;
if (won < FIN_BOSSES.length) return;
localStorage.setItem(FIN_ACH_KEY, '1');
var xp = parseInt(localStorage.getItem('geometry10_xp') || '0', 10) || 0;
localStorage.setItem('geometry10_xp', String(xp + 100));
try {
if (window.LS && typeof window.LS.addXp === 'function') {
window.LS.addXp(100, 'geometry10-master');
} else if (typeof window.addXp === 'function') {
window.addXp(100, 'geometry10-master');
}
} catch(e){}
try { if (typeof window.confetti === 'function') window.confetti({particleCount: 200, spread: 100, origin: {y: .6}}); } catch(e){}
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 10.';
var cta = document.getElementById('final-cta');
if (cta) cta.classList.add('show');
var xpBadge = document.getElementById('hero-xp-badge');
if (xpBadge) {
var newXp = parseInt(localStorage.getItem('geometry10_xp') || '0', 10) || 0;
xpBadge.style.display = '';
xpBadge.textContent = newXp + ' XP';
}
}
function buildFinBoss(b, state){
var solvedClass = state[b.n] ? ' solved' : '';
var step = b.step || '1';
var displayAns = (typeof b.ans === 'number' && step !== '1') ? b.ans.toFixed(2) : b.ans;
return '<div class="boss-card' + solvedClass + '" id="fin-boss-' + b.n + '-card">'
+ '<div class="boss-head">'
+ '<span class="boss-tag">' + b.tag + '</span>'
+ '<span class="boss-title">Босс ' + b.n + '. ' + b.title + '</span>'
+ '</div>'
+ '<div class="boss-q" id="fin-boss-' + b.n + '-q">' + b.q + '</div>'
+ '<div class="boss-row">'
+ '<input type="number" step="' + step + '" class="boss-input" id="fin-boss-' + b.n + '-inp" placeholder="число"' + (state[b.n] ? ' value="' + displayAns + '" disabled' : '') + '>'
+ '<button class="boss-btn primary" id="fin-boss-' + b.n + '-go"' + (state[b.n] ? ' disabled' : '') + '>Атаковать</button>'
+ '<button class="boss-btn" id="fin-boss-' + b.n + '-hint">Подсказка</button>'
+ '</div>'
+ '<div class="boss-hint-txt" id="fin-boss-' + b.n + '-hinttxt">' + b.hint + '</div>'
+ '<div class="boss-fb' + (state[b.n] ? ' ok' : '') + '" id="fin-boss-' + b.n + '-fb">' + (state[b.n] ? 'Победа! +15 XP. Босс уже повержен.' : '') + '</div>'
+ '</div>';
}
function bindFinBoss(b){
var state = loadFinBossState();
var goBtn = document.getElementById('fin-boss-' + b.n + '-go');
var hintBtn = document.getElementById('fin-boss-' + b.n + '-hint');
var inp = document.getElementById('fin-boss-' + b.n + '-inp');
var fb = document.getElementById('fin-boss-' + b.n + '-fb');
var hintTx = document.getElementById('fin-boss-' + b.n + '-hinttxt');
var card = document.getElementById('fin-boss-' + b.n + '-card');
if (!goBtn) return;
if (hintBtn) hintBtn.addEventListener('click', function(){
if (hintTx) hintTx.classList.toggle('show');
});
if (state[b.n]) return;
goBtn.addEventListener('click', function(){
var v = parseFloat((inp.value || '').replace(',', '.'));
if (isNaN(v)) {
fb.className = 'boss-fb fail';
fb.textContent = 'Введите число.';
return;
}
var tol = (typeof b.tol === 'number') ? b.tol : 1e-9;
if (Math.abs(v - b.ans) < tol) {
fb.className = 'boss-fb ok';
fb.textContent = 'Победа! +15 XP. Босс повержен.';
card.classList.add('solved');
goBtn.disabled = true;
inp.disabled = true;
var s = loadFinBossState();
if (!s[b.n]) {
s[b.n] = true;
saveFinBossState(s);
var xp = parseInt(localStorage.getItem('geometry10_xp') || '0', 10) || 0;
localStorage.setItem('geometry10_xp', String(xp + 15));
try {
if (window.LS && typeof window.LS.addXp === 'function') window.LS.addXp(15, 'fin-boss-' + b.n);
else if (typeof window.addXp === 'function') window.addXp(15, 'fin-boss-' + b.n);
} catch(e){}
var xpBadge = document.getElementById('hero-xp-badge');
if (xpBadge) {
var nXp = parseInt(localStorage.getItem('geometry10_xp') || '0', 10) || 0;
xpBadge.style.display = '';
xpBadge.textContent = nXp + ' XP';
}
updateFinBossBar(s);
maybeUnlockMaster(s);
}
} else {
fb.className = 'boss-fb fail';
fb.textContent = 'Не то. Перепроверь решение и попробуй снова.';
}
});
inp.addEventListener('keydown', function(e){
if (e.key === 'Enter') { e.preventDefault(); goBtn.click(); }
});
}
var FIN_BOSSES_RENDERED = false;
function renderFinBosses(){
if (FIN_BOSSES_RENDERED) return;
var cont = document.getElementById('fin-bosses-container');
if (!cont) return;
var state = loadFinBossState();
var html = '';
for (var i = 0; i < FIN_BOSSES.length; i++) html += buildFinBoss(FIN_BOSSES[i], state);
cont.innerHTML = html;
for (var j = 0; j < FIN_BOSSES.length; j++) bindFinBoss(FIN_BOSSES[j]);
var wrap = document.getElementById('course-final');
finRenderKatex(wrap);
updateFinBossBar(state);
if (localStorage.getItem(FIN_ACH_KEY) === '1') {
var cta = document.getElementById('final-cta');
if (cta) cta.classList.add('show');
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 10.';
}
FIN_BOSSES_RENDERED = true;
}
/* FINAL ACCORDION */
(function bindFinalAccordion(){
var head = document.getElementById('final-head');
var wrap = document.getElementById('course-final');
if (!head || !wrap) return;
function toggle(){
var willOpen = !wrap.classList.contains('open');
wrap.classList.toggle('open');
head.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
if (willOpen) {
renderFinBosses();
finRenderKatex(wrap);
}
}
head.addEventListener('click', toggle);
head.addEventListener('keydown', function(e){
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); }
});
})();
(function syncMasterOnLoad(){
if (localStorage.getItem(FIN_ACH_KEY) === '1') {
var strip = document.getElementById('ach-strip');
var sub = document.getElementById('ach-sub');
if (strip) strip.classList.add('lit');
if (sub) sub.textContent = 'Выполнено! Вы — Магистр геометрии 10.';
}
})();
function loadProgress() {
if (typeof window.LS === 'undefined' || typeof window.LS.api !== 'function') {
renderProgress([]);
@@ -337,33 +839,12 @@ function loadProgress() {
.catch(function() { renderProgress([]); });
}
function renderPreviews(){
if (typeof window.STEREO3D === 'undefined') return;
var S = window.STEREO3D;
var pairs = [
{id:'pv-cube', fn:function(sc){sc.addCube({center:[0,0,0],size:1.8,labels:false,color:'#dbeafe'});}},
{id:'pv-pyr', fn:function(sc){sc.addPyramid({n:4,baseRadius:1.3,height:2,color:'#fee2e2'});}},
{id:'pv-prism', fn:function(sc){sc.addPrism({n:6,baseRadius:1.2,height:2,color:'#d1fae5'});}},
{id:'pv-tetra', fn:function(sc){sc.addTetrahedron({size:1.4,color:'#fef3c7'});}}
];
for (var i = 0; i < pairs.length; i++){
var el = document.getElementById(pairs[i].id);
if (!el) continue;
var sc = new S.Scene(180, 150, {view:'CABINET', scale:32, bg:'transparent', border:'none'});
pairs[i].fn(sc);
el.innerHTML = sc.render();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function(){ loadProgress(); renderPreviews(); });
document.addEventListener('DOMContentLoaded', loadProgress);
} else {
loadProgress();
renderPreviews();
}
window.addEventListener('focus', loadProgress);
// stereo3d loads with defer — повторная попытка после загрузки
window.addEventListener('load', renderPreviews);
</script>
</body>