Files
Learn_System/frontend/textbooks/algebra_11_ch1.html
T
Maxim Dolgolyov 9d5a2959e1 fix(textbooks): кнопка «Шпаргалка» не открывала контент на desktop
На десктопе (>980px) .col-side уже видна как sticky-колонка справа в grid 1fr 280px.
Клик по кнопке #sidebar-btn добавлял .col-side-backdrop.show — backdrop с
z-index:9990 затемнял всю страницу, перекрывая sticky-aside. Со стороны
выглядело как «ничего не открылось» — на самом деле появлялась чёрная вуаль.

Фикс: @media(min-width:981px) скрывает #sidebar-btn и подавляет показ backdrop.
На мобайле (≤980px) кнопка и overlay работают как раньше.

Применено в 51 файле: physics 8/9/10 chN, algebra 7/9/10/11 chN + 8 ch2-3,
geometry 7/8/9/11 chN, geometry_10 r1-4.
2026-05-30 09:51:04 +03:00

2009 lines
135 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">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Алгебра 11 · Глава 1 · «Обобщение понятия степени»</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>
<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:#fef9c3;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --ink:#fef9e7; --muted:#a39070; --border:#2a2512}
*{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%,#fbbf24 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(251,191,36,.2);min-height:130px}
.hdr::before{content:'ГЛАВА 1';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:'a&#x02E3;';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(180px,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:.86rem;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(--acc-soft),var(--pri-soft))}
.psel-card.final .psel-num{color:var(--warn)}
.sec[id="sec-p1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p2"]{ --sec-acc:#f59e0b; --sec-acc-d:#d97706; --sec-acc-soft:#fef9c3; }
.sec[id="sec-p3"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-final1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.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(--sec-acc-soft,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(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,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.6rem;font-weight:800;color:var(--sec-acc-d,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.oral{background:#06b6d4}
.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(--sec-acc-soft,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}
.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(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.btn:active{transform:scale(.96)}
.btn.primary{background:var(--sec-acc,var(--pri));color:#fff;border-color:var(--sec-acc,var(--pri))}
.btn.primary:hover{background:var(--sec-acc-d,var(--pri2));border-color:var(--sec-acc-d,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(--sec-acc-soft,var(--pri-soft)));border:1.5px solid var(--sec-acc,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(--sec-acc,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(--sec-acc-d,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,#fef3c7),var(--sec-acc-soft,var(--pri-soft)));border-left:4px solid var(--warn,#f59e0b);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}
.tinp:focus{outline:0;border-color:var(--sec-acc,var(--pri));box-shadow:0 0 0 3px var(--sec-acc-soft,var(--pri-soft))}
.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.sliders{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:10px}
.sliders label{display:block;font-size:.92rem;color:var(--muted);background:var(--card);padding:8px 12px;border-radius:8px;border:1px solid var(--border);line-height:1.5}
.sliders label b{font-family:'JetBrains Mono',monospace;font-size:1.05rem;color:var(--sec-acc-d,var(--pri2));margin-left:4px}
.sliders label input[type="range"]{display:block;width:100%;margin-top:6px;accent-color:var(--sec-acc,var(--pri))}
.score-display{display:flex;gap:14px;flex-wrap:wrap;align-items:center;padding:10px 14px;background:var(--sec-acc-soft,var(--pri-soft));border-radius:10px;margin-bottom:12px}
.score-display b{color:var(--sec-acc-d,var(--pri2));font-size:1.15rem}
.spoiler{border:1px solid var(--border);border-radius:10px;background:var(--card);margin:10px 0;overflow:hidden}
.spoiler summary{padding:8px 14px;background:var(--sec-acc-soft,var(--pri-soft));font-weight:700;cursor:pointer;font-size:.88rem;color:var(--sec-acc-d,var(--pri2));list-style:none;display:flex;align-items:center;gap:8px}
.spoiler summary::-webkit-details-marker{display:none}
.spoiler summary::before{content:'+';font-size:1.2rem;font-weight:900;color:var(--sec-acc,var(--pri));width:18px}
.spoiler[open] summary::before{content:'\2212'}
.spoiler-body{padding:10px 14px;font-size:.92rem;line-height:1.6}
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px;transition:border-color .18s,background .18s}
.dnd-pool.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
.dnd-pool.col{flex-direction:column;align-items:stretch}
.dnd-pool.col .dnd-chip{width:auto}
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;line-height:1.4;transition:transform .12s,box-shadow .12s,border-color .12s;touch-action:none;max-width:100%}
.dnd-chip:hover{transform:translateY(-1px);border-color:var(--sec-acc,var(--pri));box-shadow:var(--sh)}
.dnd-chip:active{cursor:grabbing}
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22);transform:translateY(-1px)}
.dnd-chip.dragging{opacity:.28}
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;font-size:1.05rem;border-radius:4px;cursor:pointer}
.dnd-chip .dnd-x:hover{color:var(--bad,var(--fail));background:var(--fail-bg)}
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px;transition:border-color .15s,background .15s}
.drop-box:hover{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft))}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid;transform:scale(1.015)}
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
.dnd-hint{font-size:.78rem;color:var(--muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
.dnd-hint svg{width:14px;height:14px;flex-shrink:0}
.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:.86rem;line-height:1.6}
.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(--acc-soft),var(--pri-soft));border:1.5px solid var(--acc);border-radius:12px;padding:14px;margin-bottom:14px}
.xp-card-title{font-size:.68rem;font-weight:800;color:var(--acc2);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:var(--acc2);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(--acc),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(--acc));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}
.col-side-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.42);z-index:9990;display:none}
.col-side-backdrop.show{display:block}
@media(min-width:981px){#sidebar-btn{display:none}.col-side-backdrop.show{display:none}}
@media(max-width:980px){
.col-side{position:fixed;top:0;right:0;height:100vh;width:300px;max-width:88vw;background:var(--bg);box-shadow:-12px 0 24px rgba(0,0,0,.18);padding:18px 16px;overflow-y:auto;transform:translateX(100%);transition:transform .25s ease;z-index:9991;max-height:none}
.col-side.open{transform:none}
}
.search-modal{position:fixed;inset:0;background:rgba(15,23,42,.55);backdrop-filter:blur(4px);z-index:9993;display:none;align-items:flex-start;justify-content:center;padding-top:14vh}
.search-modal.show{display:flex}
.search-box{background:var(--bg);border:1px solid var(--border);border-radius:14px;width:560px;max-width:92vw;max-height:70vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 64px rgba(0,0,0,.4)}
.search-input{padding:14px 16px;font-size:1rem;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);outline:none}
.search-results{flex:1;overflow-y:auto;padding:6px 0}
.search-row{display:block;padding:8px 16px;cursor:pointer;border-bottom:1px solid var(--border);text-align:left;background:transparent;border:0;width:100%;color:var(--text)}
.search-row:hover,.search-row.active{background:var(--sec-acc-soft,var(--pri-soft))}
.search-row .sr-kind{font-size:.7rem;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:2px}
.search-row .sr-title{font-weight:700;font-size:.92rem;color:var(--text)}
.search-row .sr-desc{font-size:.8rem;color:var(--muted);margin-top:2px}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
/* === ALG11 POLISH (visual + micro-interactions) === */
@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
.sec.active .wg:nth-of-type(1){animation-delay:.02s}
.sec.active .wg:nth-of-type(2){animation-delay:.08s}
.sec.active .wg:nth-of-type(3){animation-delay:.14s}
.sec.active .wg:nth-of-type(4){animation-delay:.20s}
.sec.active .wg:nth-of-type(5){animation-delay:.26s}
.sec.active .wg:nth-of-type(6){animation-delay:.32s}
.wg svg{transition:filter .25s ease}
.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
.wg input[type=range]{cursor:ew-resize}
.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
.score-display b.bump{transform:scale(1.28);color:var(--pri)}
.katex{transition:color .2s}
.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
.psel-card{position:relative}
.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}
.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
.boss-card.pulse{animation:bossPulse .8s ease-out}
.sec{transition:opacity .25s}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-row">
<div>
<h1>Алгебра 11 · Глава 1</h1>
<div class="hdr-sub">Степень с действительным показателем · степенная функция · определение логарифма</div>
</div>
<div class="hdr-side">
<a href="/textbook/algebra-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="search-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg> Поиск</button>
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
</div>
</div>
</header>
<main class="main">
<div class="col-main">
<section class="hero">
<h2>Степени и логарифмы — расширяем понятие</h2>
<p>Расширяем понятие степени — теперь показатель может быть рациональным, иррациональным и любым действительным. Изучаем <b>степенную функцию</b> и встречаемся с <b>логарифмом</b> — обратной операцией к возведению в степень.</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> Начать § 1</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="a^{m/n}"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Степень с рациональным показателем</h2></div><div id="p1-body"></div></section>
<section id="sec-p2" class="sec" data-watermark="x^&alpha;"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Степенная функция</h2></div><div id="p2-body"></div></section>
<section id="sec-p3" class="sec" data-watermark="log"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Определение логарифма</h2></div><div id="p3-body"></div></section>
<section id="sec-final1" 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">Финал главы</h2></div><div id="final1-body"></div></section>
</div>
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
<div class="col-side-backdrop" id="col-side-backdrop"></div>
</main>
<footer class="foot">Интерактивный учебник «Алгебра 11» · Глава 1 · «Обобщение понятия степени» · 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>
<div id="search-modal" class="search-modal" role="dialog">
<div class="search-box">
<input type="text" id="search-input" class="search-input" placeholder="Поиск…" autocomplete="off">
<div id="search-results" class="search-results"></div>
<div class="search-foot"><span><kbd>↑↓</kbd> навигация</span><span><kbd>Enter</kbd> открыть</span><span><kbd>Esc</kbd> закрыть</span></div>
</div>
</div>
<script>
'use strict';
const STATE = { current:'p1', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 4;
const _TB_SLUG = 'algebra-11-ch1';
const PARAS = [
{ id:'p1', num:'§ 1', name:'Степень с рациональным показателем', sub:'$a^{m/n} = \\sqrt[n]{a^m}$' },
{ id:'p2', num:'§ 2', name:'Степенная функция', sub:'$y = x^\\alpha$' },
{ id:'p3', num:'§ 3', name:'Определение логарифма', sub:'$\\log_a b = c$' },
{ id:'final1', num:'&#9733;', name:'Финал главы', sub:'Итоги · боссы главы 1', final: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 = {
start:"Начало главы 1!",
p2_done:"Степенная функция освоена!",
p3_done:"Логарифм освоен!",
ch1_done:"Глава 1 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra11_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra11_ch1_achievements');
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else 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('algebra11_xp')||0); STATE.level=calcLevel(STATE.xp);
}catch(e){}
}
function saveProgress(){
try{
localStorage.setItem('algebra11_ch1_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra11_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
localStorage.setItem('algebra11_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 loadServerReadState(){
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
fetch('/api/textbooks/'+_TB_SLUG,{headers:{'Authorization':'Bearer '+tok}}).then(r=>r.ok?r.json():null).then(d=>{
if(!d||!d.progress) return;
(d.progress.read||[]).forEach(k=>{_markedRead.add(k); if((STATE.progress[k]||0)<50) STATE.progress[k]=100;});
saveProgress(); refreshProgressUI();
}).catch(()=>{});
}
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,'algebra11-ch1-'+(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)+'%'; });
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':'');
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>';
card.addEventListener('click', ()=>goTo(p.id));
g.appendChild(card);
});
}
const BUILT=new Set();
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), final1:()=>buildFinal1() };
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:"Шпаргалка §1",rows:[["Корень","$\\\\sqrt[n]{a^m} = a^{m/n}$, $a \\\\ge 0$, $n \\\\in \\\\mathbb{N}$"],["Свойства","$a^p \\\\cdot a^q = a^{p+q}$, $(a^p)^q = a^{pq}$"],["Дробный","$a^{1/n} = \\\\sqrt[n]{a}$, $a^{-p} = 1/a^p$"],["Действ.","показатель — любое $x \\\\in \\\\mathbb{R}$"]]},
p2:{title:"Шпаргалка §2",rows:[["Вид","$y = x^\\\\alpha$, $\\\\alpha \\\\in \\\\mathbb{R}$"],["$\\\\alpha > 0$","возрастает на $(0; +\\\\infty)$"],["$\\\\alpha < 0$","убывает на $(0; +\\\\infty)$"],["Чёт./нечёт.","зависит от $\\\\alpha$"]]},
p3:{title:"Шпаргалка §3",rows:[["Опр.","$\\\\log_a b = c \\\\iff a^c = b$"],["Условия","$a > 0$, $a \\\\ne 1$, $b > 0$"],["Тождество","$a^{\\\\log_a b} = b$"],["$\\\\lg, \\\\ln$","$\\\\lg = \\\\log_{10}$, $\\\\ln = \\\\log_e$"]]},
final1:{title:"Финал главы 1",rows:[["§§13","теория главы 1"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p1',html:"Дробный показатель: $a^{m/n} = \\\\sqrt[n]{a^m}$. База $a \\\\ge 0$ для произвольного $m/n$."},
{sec:'p2',html:"Степенная функция $y = x^\\\\alpha$: при $\\\\alpha > 0$ возрастает на $(0; +\\\\infty)$, при $\\\\alpha < 0$ — убывает."},
{sec:'p3',html:"$\\\\log_a b$ — это показатель, в который надо возвести $a$, чтобы получить $b$."},
{sec:'final1',html:"Финал главы 1 — интегрированные задачи по §§1–3."}
];
function buildSidebar(id){
const box=document.getElementById('sidebar-content');
const sb=SIDEBARS[id]||SIDEBARS[PARAS[0].id];
let html='';
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+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,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><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:.84rem;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('algebra11_ch1_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('algebra11_ch1_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){} } }
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'&#10003; Верно!':'&#10007; Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
function fmt(n){ if(!isFinite(n)) return '?'; if(Number.isInteger(n)) return String(n); return Math.abs(n-Math.round(n))<1e-9?String(Math.round(n)):(+n.toFixed(6)).toString(); }
function ipow(base, exp){ let r=1; for(let i=0;i<Math.abs(exp);i++) r*=base; return exp<0 ? 1/r : r; }
function gcd(a,b){ a=Math.abs(a|0); b=Math.abs(b|0); while(b){ const t=b; b=a%b; a=t; } return a||1; }
function makeCard(kind, title, num, body){
const labels = {repeat:'Повторение',theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример',oral:'Устно'};
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 setupSorter(cfg){
const placed = {}; const pool = document.getElementById(cfg.poolId); const scope = document.querySelector(cfg.scopeSelector);
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
let armed = null;
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
function attach(elm,itId){ let ghost=null,dragging=false,sx=0,sy=0; elm.addEventListener('pointerdown',ev=>{ if(ev.button!==undefined&&ev.button!==0) return;
ev.preventDefault(); if(ev.target.classList&&ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; } sx=ev.clientX;sy=ev.clientY; const r=elm.getBoundingClientRect(); const ox=ev.clientX-r.left,oy=ev.clientY-r.top; try{elm.setPointerCapture(ev.pointerId);}catch(e){} function onMove(e){ const dx=e.clientX-sx,dy=e.clientY-sy; if(!dragging&&Math.hypot(dx,dy)>8){ dragging=true; ghost=elm.cloneNode(true); ghost.classList.remove('armed'); ghost.style.cssText='position:fixed;z-index:9999;pointer-events:none;opacity:.9;transform:rotate(-2.5deg);box-shadow:0 14px 36px rgba(0,0,0,.32);width:'+r.width+'px;left:'+(e.clientX-ox)+'px;top:'+(e.clientY-oy)+'px'; document.body.appendChild(ghost); elm.classList.add('dragging'); } if(dragging&&ghost){ ghost.style.left=(e.clientX-ox)+'px';ghost.style.top=(e.clientY-oy)+'px'; const under=document.elementsFromPoint(e.clientX,e.clientY); scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); const tgt=under.find(n=>n.classList&&(n.classList.contains('drop-box')||n.classList.contains('dnd-pool'))); if(tgt)tgt.classList.add('over'); } } function onUp(e){ elm.removeEventListener('pointermove',onMove);elm.removeEventListener('pointerup',onUp);elm.removeEventListener('pointercancel',onUp);elm.classList.remove('dragging'); if(ghost){ghost.remove();ghost=null;} scope.querySelectorAll('.drop-box.over,.dnd-pool.over').forEach(n=>n.classList.remove('over')); if(dragging){ const under=document.elementsFromPoint(e.clientX,e.clientY); const box=under.find(n=>n.classList&&n.classList.contains('drop-box')); const pl=under.find(n=>n.classList&&n.classList.contains('dnd-pool')); if(box){const di=box.querySelector('[data-cat]');if(di){placed[itId]=di.dataset.cat;armed=null;render();return;}}else if(pl){delete placed[itId];armed=null;render();return;} }else{ if(placed[itId]){delete placed[itId];armed=null;render();}else{armed=(armed===itId)?null:itId;render();} } dragging=false; } elm.addEventListener('pointermove',onMove);elm.addEventListener('pointerup',onUp);elm.addEventListener('pointercancel',onUp); }); }
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
attachBoxTaps(); render();
return {placed,render,reset(){ for(const k in placed)delete placed[k];armed=null;render(); }};
}
/* === SVG-хелперы (axes2D, plotFunc, pointWithDrop, asymptote, snapToValue, геом.) === */
/* Координатная плоскость в SVG */
function axes2D(W, H, pad, xmin, xmax, ymin, ymax){
const ux = (W - 2*pad) / (xmax - xmin);
const uy = (H - 2*pad) / (ymax - ymin);
const toX = v => pad + (v - xmin) * ux;
const toY = v => H - pad - (v - ymin) * uy;
let g = '';
g += '<g stroke="#e5e7eb" stroke-width="1">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
g += '<line x1="'+toX(x)+'" y1="'+pad+'" x2="'+toX(x)+'" y2="'+(H-pad)+'"/>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
g += '<line x1="'+pad+'" y1="'+toY(y)+'" x2="'+(W-pad)+'" y2="'+toY(y)+'"/>';
}
g += '</g>';
const y0 = toY(0), x0 = toX(0);
g += '<line x1="'+pad+'" y1="'+y0+'" x2="'+(W-pad)+'" y2="'+y0+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<line x1="'+x0+'" y1="'+pad+'" x2="'+x0+'" y2="'+(H-pad)+'" stroke="#0f172a" stroke-width="1.5"/>';
g += '<text x="'+(W-pad+2)+'" y="'+(y0-4)+'" font-size="11" fill="#0f172a">x</text>';
g += '<text x="'+(x0+4)+'" y="'+(pad-2)+'" font-size="11" fill="#0f172a">y</text>';
g += '<g font-size="10" fill="#64748b">';
for (let x = Math.ceil(xmin); x <= xmax; x++){
if (x !== 0) g += '<text x="'+(toX(x)-3)+'" y="'+(y0+12)+'">'+x+'</text>';
}
for (let y = Math.ceil(ymin); y <= ymax; y++){
if (y !== 0) g += '<text x="'+(x0+4)+'" y="'+(toY(y)+3)+'">'+y+'</text>';
}
g += '<text x="'+(x0+4)+'" y="'+(y0+12)+'">0</text>';
g += '</g>';
return { content: g, toX, toY, ux, uy };
}
/* График функции y=f(x) — строка <path ...> */
function plotFunc(f, xmin, xmax, toX, toY, color, N){
N = N || 200;
let d = '';
let prevValid = false;
for (let i = 0; i <= N; i++){
const x = xmin + (xmax - xmin) * i / N;
let y;
try { y = f(x); } catch(e){ y = NaN; }
if (!isFinite(y) || isNaN(y) || y < -1e4 || y > 1e4){ prevValid = false; continue; }
d += (prevValid ? ' L' : ' M') + toX(x).toFixed(2) + ',' + toY(y).toFixed(2);
prevValid = true;
}
return '<path d="'+d+'" stroke="'+color+'" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>';
}
/* Точка на графике с пунктирными проекциями на оси и подписями координат */
function pointWithDrop(x, fx, toX, toY, color, label){
const px = toX(x), py = toY(fx);
let s = '';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+px+'" y2="'+toY(0)+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<line x1="'+px+'" y1="'+py+'" x2="'+toX(0)+'" y2="'+py+'" stroke="'+color+'" stroke-width="1.2" stroke-dasharray="3 3" opacity=".7"/>';
s += '<circle cx="'+px+'" cy="'+py+'" r="4.5" fill="'+color+'" stroke="#fff" stroke-width="2"/>';
if (label){
s += '<text x="'+(px+8)+'" y="'+(py-8)+'" font-family="Inter,sans-serif" font-size="12" font-weight="700" fill="'+color+'">'+label+'</text>';
}
return s;
}
/* Горизонтальная или вертикальная асимптота */
function asymptote(orientation, value, toX, toY, xmin, xmax, ymin, ymax, color){
color = color || '#94a3b8';
if (orientation === 'h'){
const y = toY(value);
return '<line x1="'+toX(xmin)+'" y1="'+y+'" x2="'+toX(xmax)+'" y2="'+y+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
} else {
const x = toX(value);
return '<line x1="'+x+'" y1="'+toY(ymin)+'" x2="'+x+'" y2="'+toY(ymax)+'" stroke="'+color+'" stroke-width="1.3" stroke-dasharray="6 4"/>';
}
}
/* Snap-функция для slider'ов: возвращает ближайшую snap-точку, если значение близко */
function snapToValue(value, snapPoints, tolerance){
tolerance = tolerance || 0.1;
for (const sp of snapPoints){
if (Math.abs(value - sp) < tolerance) return sp;
}
return value;
}
/* Геометрические SVG-хелперы (для будущих интерактивов с углами) */
function rightAngleMark(V, uIn, wIn, s){
s = s || 9;
const p1 = {x: V.x + s*uIn.x, y: V.y + s*uIn.y};
const c = {x: p1.x + s*wIn.x, y: p1.y + s*wIn.y};
const p2 = {x: V.x + s*wIn.x, y: V.y + s*wIn.y};
return p1.x+','+p1.y+' '+c.x+','+c.y+' '+p2.x+','+p2.y;
}
function angleArcAuto(V, uA, uB, R){
const sA = {x: V.x + R*uA.x, y: V.y + R*uA.y};
const eB = {x: V.x + R*uB.x, y: V.y + R*uB.y};
const cross = uA.x*uB.y - uA.y*uB.x;
const sweep = cross > 0 ? 1 : 0;
return 'M'+sA.x+','+sA.y+' A'+R+','+R+' 0 0,'+sweep+' '+eB.x+','+eB.y;
}
function unitVec(p1, p2){
const dx = p2.x - p1.x, dy = p2.y - p1.y;
const len = Math.sqrt(dx*dx + dy*dy) || 1;
return {x: dx/len, y: dy/len};
}
function deg2rad(d){ return d * Math.PI / 180; }
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>',
oral:'<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
};
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;
return secNav(prev, next);
}
function secNav(prev, next){
const NAMES = {p1:'\xA71',p2:'\xA72',p3:'\xA73',final1:'Финал'};
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);
});
}
/* ===== STUB BUILDERS — наполнение в Phase 1+ ===== */
function buildP1(){
const box = document.getElementById('p1-body');
let html = '';
html += makeCard('theory', 'Определение степени с дробным показателем', '1.1', `
<p>Пусть $a > 0$, $m$ — целое число, $n$ — натуральное число, $n > 1$. Тогда:</p>
\\[ a^{m/n} = \\sqrt[n]{a^m} \\]
<p>В частности, при $m = 1$ получаем $a^{1/n} = \\sqrt[n]{a}$ — это <b>корень $n$-й степени</b> из $a$.</p>
<p><b>Пример.</b> $8^{2/3} = \\sqrt[3]{8^2} = \\sqrt[3]{64} = 4.$</p>
<p><b>Отрицательный показатель:</b> $a^{-m/n} = \\dfrac{1}{a^{m/n}} = \\dfrac{1}{\\sqrt[n]{a^m}}.$</p>
<details class="spoiler"><summary>Почему $a > 0$?</summary><div class="spoiler-body">
Если $a < 0$, то $\\sqrt[n]{a}$ при чётном $n$ не определён в $\\mathbb{R}$. Кроме того, одно и то же рациональное число может быть записано разными дробями ($\\tfrac{1}{2} = \\tfrac{2}{4}$), и для отрицательной базы значения корней $\\sqrt{a}$ и $\\sqrt[4]{a^2}$ не совпадают. Поэтому для $a < 0$ дробный показатель не определяют.<br>Для $a = 0$ полагают $0^{m/n} = 0$ при положительном показателе $m/n > 0$.
</div></details>`);
html += makeCard('rule', 'Свойства степени с рациональным показателем', '1.2', `
<p>Для $a > 0$, $b > 0$ и любых рациональных $p$, $q$ выполнены те же свойства, что и для целых показателей:</p>
\\[ a^p \\cdot a^q = a^{p+q}, \\qquad \\dfrac{a^p}{a^q} = a^{p-q}, \\qquad (a^p)^q = a^{pq} \\]
\\[ (ab)^p = a^p \\cdot b^p, \\qquad \\left(\\dfrac{a}{b}\\right)^p = \\dfrac{a^p}{b^p} \\]
<p>Это <b>те же самые</b> свойства, что и для натурального показателя — просто теперь $p$ и $q$ могут быть любыми рациональными числами (целыми, дробными, отрицательными).</p>
<p><b>Пример.</b> $3^{1/2} \\cdot 3^{3/2} = 3^{1/2 + 3/2} = 3^2 = 9.$</p>
<p><b>Пример.</b> $\\left(4^{3/4}\\right)^{4/3} = 4^{(3/4) \\cdot (4/3)} = 4^1 = 4.$</p>`);
html += makeCard('example', 'Степень с иррациональным и действительным показателем', '1.3', `
<p>А что такое $2^{\\sqrt{3}}$? Ведь $\\sqrt{3} \\approx 1{,}732...$ — иррациональное число.</p>
<p>Идея: возьмём последовательность <b>рациональных</b> приближений к $\\sqrt{3}$:</p>
\\[ r_1 = 1{,}7,\\ \\ r_2 = 1{,}73,\\ \\ r_3 = 1{,}732,\\ \\ r_4 = 1{,}7320,\\ \\ldots \\to \\sqrt{3} \\]
<p>Каждое $2^{r_n}$ — уже определённая степень с рациональным показателем. Эта последовательность сходится:</p>
\\[ 2^{\\sqrt{3}} = \\lim_{n \\to \\infty} 2^{r_n} \\approx 3{,}3219... \\]
<p>Так определяется <b>степень с любым действительным показателем</b> $a^\\alpha$ при $a > 0$ и $\\alpha \\in \\mathbb{R}$.</p>
<p><b>Все свойства степени сохраняются</b> и для действительных показателей — ими можно пользоваться так же, как привычными свойствами для целых степеней.</p>`);
/* INTERACTIVE 1 — конструктор a^(m/n) */
html += `<div class="wg" id="p1-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Конструктор степени $a^{m/n}$</div></div>
<div class="wg-help">Подвигай ползунки — увидишь, как вычисляется $a^{m/n} = \\sqrt[n]{a^m}$ при разных значениях.</div>
<div class="sliders">
<label>База $a$ = <b id="p1-iv1-a">4</b><input type="range" id="p1-iv1-sa" min="1" max="16" step="0.5" value="4"></label>
<label>Числитель $m$ = <b id="p1-iv1-m">2</b><input type="range" id="p1-iv1-sm" min="-5" max="5" step="1" value="2"></label>
<label>Знаменатель $n$ = <b id="p1-iv1-n">3</b><input type="range" id="p1-iv1-sn" min="2" max="6" step="1" value="3"></label>
</div>
<div id="p1-iv1-formula" style="text-align:center;font-size:1.15rem;padding:14px;background:var(--card);border-radius:9px;margin-bottom:10px;min-height:70px;line-height:1.9"></div>
<div id="p1-iv1-val" style="text-align:center;font-size:1.05rem;padding:10px 14px;background:var(--sec-acc-soft);border-radius:9px"></div>
</div>`;
/* INTERACTIVE 2 — калькулятор свойств */
html += `<div class="wg" id="p1-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор свойств степени</div></div>
<div class="wg-help">Введи базу $a$ и две дроби $p = m_1/n_1$, $q = m_2/n_2$. Нажми кнопку свойства — увидишь пошаговое применение.</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p1-iv2-a" class="tinp" style="width:64px;text-align:center" value="2" step="0.5">
<span style="font-family:'JetBrains Mono',monospace;margin-left:8px">$p$ =</span>
<input type="number" id="p1-iv2-pm" class="tinp" style="width:54px;text-align:center" value="1" step="1">
<span>/</span>
<input type="number" id="p1-iv2-pn" class="tinp" style="width:54px;text-align:center" value="2" min="1" step="1">
<span style="font-family:'JetBrains Mono',monospace;margin-left:8px">$q$ =</span>
<input type="number" id="p1-iv2-qm" class="tinp" style="width:54px;text-align:center" value="3" step="1">
<span>/</span>
<input type="number" id="p1-iv2-qn" class="tinp" style="width:54px;text-align:center" value="2" min="1" step="1">
</div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p1-iv2-mul">$a^p \\cdot a^q$</button>
<button class="btn primary" id="p1-iv2-div">$a^p / a^q$</button>
<button class="btn primary" id="p1-iv2-pow">$(a^p)^q$</button>
</div>
<div id="p1-iv2-out" style="margin-top:10px;padding:12px 14px;background:var(--card);border-radius:9px;text-align:center;font-size:1rem;min-height:60px;line-height:1.9"></div>
<div class="feedback" id="p1-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD: корень или степень */
html += `<div class="wg" id="p1-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Радикал или степень?</div></div>
<div class="wg-help">Перетащи каждую запись в ящик «через корень» или «через степень».</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 8 записей — 2 ящика</div>
<div id="p1-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="rad">Запись через корень</h5><div class="drop-items" data-cat="rad"></div></div>
<div class="drop-box"><h5 data-cat="pow">Запись через степень</h5><div class="drop-items" data-cat="pow"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p1-iv3-check">Проверить</button><button class="btn" id="p1-iv3-reset">Сначала</button></div>
<div class="feedback" id="p1-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — тренажёр упрощения */
html += `<div class="wg" id="p1-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: упрости степень</div></div>
<div class="wg-help">Вычисли значение и введи число (целое или десятичное до 2 знаков, допуск $\\pm 0{,}05$).</div>
<div class="score-display"><span>Задача <b id="p1-iv4-i">1</b> / 6</span><span>Очки: <b id="p1-iv4-s">0</b> / 6</span></div>
<div id="p1-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.15rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p1-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p1-iv4-go">Проверить</button>
<button class="btn" id="p1-iv4-start">Заново</button>
</div>
<div class="feedback" id="p1-iv4-fb"></div>
</div>`;
html += secNav(null, 'p2');
html += readButton('p1');
box.innerHTML = html;
renderMath(box);
/* IV1 — конструктор a^(m/n) */
(function(){
const sa = document.getElementById('p1-iv1-sa');
const sm = document.getElementById('p1-iv1-sm');
const sn = document.getElementById('p1-iv1-sn');
const aL = document.getElementById('p1-iv1-a');
const mL = document.getElementById('p1-iv1-m');
const nL = document.getElementById('p1-iv1-n');
const fEl = document.getElementById('p1-iv1-formula');
const vEl = document.getElementById('p1-iv1-val');
const seen = new Set();
function draw(){
const a = +sa.value, m = +sm.value, n = +sn.value;
aL.textContent = (Number.isInteger(a) ? a : a.toFixed(1));
mL.textContent = m;
nL.textContent = n;
let formula = '';
if(m === 0){
formula = '$a^{0} = 1$ при $a \\ne 0$. Здесь: $' + a + '^{0} = 1$.';
vEl.innerHTML = 'Значение: $\\mathbf{1}$';
} else if(m > 0){
const aPowM = Math.pow(a, m);
const val = Math.pow(a, m/n);
formula = '$' + a + '^{' + m + '/' + n + '} \\;=\\; \\sqrt[' + n + ']{' + a + '^{' + m + '}} \\;=\\; \\sqrt[' + n + ']{' + (+aPowM.toFixed(4)) + '} \\;\\approx\\; ' + (+val.toFixed(4)) + '$';
vEl.innerHTML = 'Значение: $\\mathbf{' + (+val.toFixed(4)) + '}$';
} else {
const am = Math.abs(m);
const aPowM = Math.pow(a, am);
const val = 1 / Math.pow(a, am/n);
formula = '$' + a + '^{' + m + '/' + n + '} \\;=\\; \\dfrac{1}{' + a + '^{' + am + '/' + n + '}} \\;=\\; \\dfrac{1}{\\sqrt[' + n + ']{' + (+aPowM.toFixed(4)) + '}} \\;\\approx\\; ' + (+val.toFixed(4)) + '$';
vEl.innerHTML = 'Значение: $\\mathbf{' + (+val.toFixed(4)) + '}$';
}
fEl.innerHTML = formula;
renderMath(fEl); renderMath(vEl);
const sig = a + ':' + m + ':' + n;
seen.add(sig);
if(seen.size >= 6 && !seen.has('_done')){ addXp(10,'p1-iv1'); bumpProgress('p1', 15); seen.add('_done'); }
}
sa.addEventListener('input', draw);
sm.addEventListener('input', draw);
sn.addEventListener('input', draw);
draw();
})();
/* IV2 — калькулятор свойств */
(function(){
const aI = document.getElementById('p1-iv2-a');
const pm = document.getElementById('p1-iv2-pm');
const pn = document.getElementById('p1-iv2-pn');
const qm = document.getElementById('p1-iv2-qm');
const qn = document.getElementById('p1-iv2-qn');
const out = document.getElementById('p1-iv2-out');
const fb = document.getElementById('p1-iv2-fb');
const bMul = document.getElementById('p1-iv2-mul');
const bDiv = document.getElementById('p1-iv2-div');
const bPow = document.getElementById('p1-iv2-pow');
const used = new Set();
function simplify(num, den){
if(den < 0){ num = -num; den = -den; }
const g = gcd(Math.abs(num), Math.abs(den)) || 1;
return [num/g, den/g];
}
function fracStr(num, den){
if(den === 1) return String(num);
if(num === 0) return '0';
return '\\dfrac{'+num+'}{'+den+'}';
}
function getVals(){
const a = +aI.value;
const pM = parseInt(pm.value,10), pN = parseInt(pn.value,10);
const qM = parseInt(qm.value,10), qN = parseInt(qn.value,10);
if(!isFinite(a) || a <= 0){ feedback(fb,false,'&#10007; База $a$ должна быть положительной.'); return null; }
if(isNaN(pM)||isNaN(pN)||isNaN(qM)||isNaN(qN)){ feedback(fb,false,'&#10007; Введи все числа.'); return null; }
if(pN <= 0 || qN <= 0){ feedback(fb,false,'&#10007; Знаменатели дробей должны быть положительными.'); return null; }
return { a, pM, pN, qM, qN };
}
function doOp(op){
const v = getVals(); if(!v) return;
const { a, pM, pN, qM, qN } = v;
const p = pM/pN, q = qM/qN;
let sumNum, sumDen, label, exprBefore, exprAfter, valNum;
if(op === 'mul'){
sumNum = pM*qN + qM*pN; sumDen = pN*qN;
label = '$a^p \\cdot a^q = a^{p+q}$';
exprBefore = a+'^{'+fracStr(pM,pN)+'} \\cdot '+a+'^{'+fracStr(qM,qN)+'}';
valNum = Math.pow(a, p+q);
} else if(op === 'div'){
sumNum = pM*qN - qM*pN; sumDen = pN*qN;
label = '$\\dfrac{a^p}{a^q} = a^{p-q}$';
exprBefore = '\\dfrac{'+a+'^{'+fracStr(pM,pN)+'}}{'+a+'^{'+fracStr(qM,qN)+'}}';
valNum = Math.pow(a, p-q);
} else {
sumNum = pM*qM; sumDen = pN*qN;
label = '$(a^p)^q = a^{pq}$';
exprBefore = '\\left('+a+'^{'+fracStr(pM,pN)+'}\\right)^{'+fracStr(qM,qN)+'}';
valNum = Math.pow(a, p*q);
}
const [sn, sd] = simplify(sumNum, sumDen);
const expSimpl = fracStr(sn, sd);
out.innerHTML = '<div style="font-size:.86rem;color:var(--muted);margin-bottom:6px">Свойство: '+label+'</div>'
+ '$'+exprBefore+' \\;=\\; '+a+'^{'+expSimpl+'} \\;\\approx\\; '+ (+valNum.toFixed(4)) +'$';
renderMath(out);
feedback(fb, true, '&#10003; Применено свойство.');
used.add(op);
if(used.size === 3 && !used.has('_done')){ addXp(10,'p1-iv2'); bumpProgress('p1', 15); used.add('_done'); }
}
bMul.addEventListener('click', ()=>doOp('mul'));
bDiv.addEventListener('click', ()=>doOp('div'));
bPow.addEventListener('click', ()=>doOp('pow'));
})();
/* IV3 — DnD радикал / степень */
(function(){
const items = [
{ id:'r1', cat:'rad', html:'$\\sqrt[3]{a^2}$' },
{ id:'r2', cat:'pow', html:'$a^{1/2}$' },
{ id:'r3', cat:'rad', html:'$\\sqrt{a^3}$' },
{ id:'r4', cat:'pow', html:'$a^{-1/4}$' },
{ id:'r5', cat:'rad', html:'$\\sqrt[5]{a}$' },
{ id:'r6', cat:'pow', html:'$a^{3/7}$' },
{ id:'r7', cat:'rad', html:'$\\dfrac{1}{\\sqrt[4]{a}}$' },
{ id:'r8', cat:'pow', html:'$a^{0{,}5}$' },
];
const sorter = setupSorter({
poolId:'p1-iv3-pool',
scopeSelector:'#p1-iv3',
items: items,
cats:['rad','pow'],
columnLayout:false,
});
document.getElementById('p1-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p1-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 8 записей.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 8 на месте! +15 XP'); addXp(15,'p1-iv3'); bumpProgress('p1', 25); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 8. Попробуй ещё.');
});
document.getElementById('p1-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p1-iv3-fb').style.display = 'none'; });
})();
/* IV4 — тренажёр упрощения */
(function(){
const Q = [
{ q:'$8^{2/3} = \\;?$', ans:4, hint:'$\\sqrt[3]{8^2} = \\sqrt[3]{64} = 4$' },
{ q:'$16^{3/4} = \\;?$', ans:8, hint:'$\\sqrt[4]{16^3} = \\sqrt[4]{4096} = 8$' },
{ q:'$27^{-1/3} = \\;?$', ans:1/3, hint:'$\\dfrac{1}{\\sqrt[3]{27}} = \\dfrac{1}{3} \\approx 0{,}33$' },
{ q:'$32^{0{,}4} = \\;?$', ans:4, hint:'$0{,}4 = \\tfrac{2}{5}$, $\\sqrt[5]{32^2} = \\sqrt[5]{1024} = 4$' },
{ q:'$\\left(\\dfrac{1}{4}\\right)^{-1/2} = \\;?$', ans:2, hint:'$\\left(\\tfrac{1}{4}\\right)^{-1/2} = 4^{1/2} = \\sqrt{4} = 2$' },
{ q:'$9^{1{,}5} = \\;?$', ans:27, hint:'$9^{3/2} = \\sqrt{9^3} = \\sqrt{729} = 27$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p1-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p1-iv4'); bumpProgress('p1', 25); }
else if(score >= 4){ addXp(8,'p1-iv4'); bumpProgress('p1', 15); }
return;
}
document.getElementById('p1-iv4-i').textContent = (i+1);
document.getElementById('p1-iv4-s').textContent = score;
document.getElementById('p1-iv4-q').innerHTML = Q[i].q;
document.getElementById('p1-iv4-ans').value = '';
renderMath(document.getElementById('p1-iv4-q'));
document.getElementById('p1-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p1-iv4-fb');
const raw = document.getElementById('p1-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) < 0.05){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ $\\approx '+(+Q[i].ans.toFixed(2))+'$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p1-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p1-iv4-go').addEventListener('click', go);
document.getElementById('p1-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p1-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p1');
}
function buildP2(){
const box = document.getElementById('p2-body');
let html = '';
html += makeCard('theory', 'Определение и классификация', '2.1', `
<p>Функция вида $y = x^\\alpha$, где $\\alpha$ — действительное число, называется <b>степенной функцией</b>.</p>
<p>Свойства функции $y = x^\\alpha$ (область определения, чётность, монотонность, вид графика) полностью зависят от того, какое число $\\alpha$:</p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li><b>Натуральное чётное</b> ($\\alpha = 2, 4, 6, \\ldots$) — парабола, чётная функция, $D = \\mathbb{R}$.</li>
<li><b>Натуральное нечётное</b> ($\\alpha = 1, 3, 5, \\ldots$) — нечётная функция, $D = \\mathbb{R}$.</li>
<li><b>Целое отрицательное</b> ($\\alpha = -1, -2, \\ldots$) — гипербола, есть асимптоты $x = 0$ и $y = 0$.</li>
<li><b>Дробное вида $1/n$</b> — корень $\\sqrt[n]{x}$.</li>
<li><b>Иррациональное</b> ($\\alpha = \\sqrt{2},\\ \\pi,\\ e,\\ \\ldots$) — определена только при $x > 0$.</li>
</ul>
<p>Несмотря на это разнообразие, все графики $y = x^\\alpha$ при $x = 1$ проходят через одну и ту же точку $(1; 1)$.</p>`);
html += makeCard('rule', 'Шесть ключевых случаев — свойства', '2.2', `
<p>Перед нами таблица свойств $y = x^\\alpha$ для самых важных значений $\\alpha$.</p>
<div style="overflow-x:auto;margin:10px 0">
<table style="width:100%;border-collapse:collapse;font-size:.86rem">
<thead>
<tr style="background:var(--sec-acc-soft)">
<th style="padding:8px;border:1px solid var(--border);text-align:left">Случай</th>
<th style="padding:8px;border:1px solid var(--border);text-align:left">$D$</th>
<th style="padding:8px;border:1px solid var(--border);text-align:left">$E$</th>
<th style="padding:8px;border:1px solid var(--border);text-align:left">Чётность</th>
<th style="padding:8px;border:1px solid var(--border);text-align:left">Монотонность</th>
</tr>
</thead>
<tbody>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = 2k$</b> (нат. чёт.)</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)">$[0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">чётная</td><td style="padding:7px;border:1px solid var(--border)">$\\searrow$ на $(-\\infty; 0]$, $\\nearrow$ на $[0; +\\infty)$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = 2k+1$</b> (нат. неч.)</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)">нечётная</td><td style="padding:7px;border:1px solid var(--border)">$\\nearrow$ на $\\mathbb{R}$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = -2k$</b> (целое отр. чёт.)</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R} \\setminus \\{0\\}$</td><td style="padding:7px;border:1px solid var(--border)">$(0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">чётная</td><td style="padding:7px;border:1px solid var(--border)">$\\nearrow$ на $(-\\infty; 0)$, $\\searrow$ на $(0; +\\infty)$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = -(2k+1)$</b> (целое отр. неч.)</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R} \\setminus \\{0\\}$</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R} \\setminus \\{0\\}$</td><td style="padding:7px;border:1px solid var(--border)">нечётная</td><td style="padding:7px;border:1px solid var(--border)">$\\searrow$ на каждом промежутке</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = 1/n$</b> ($n$ чёт.)</td><td style="padding:7px;border:1px solid var(--border)">$[0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">$[0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">—</td><td style="padding:7px;border:1px solid var(--border)">$\\nearrow$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha = 1/n$</b> ($n$ неч.)</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)">$\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)">нечётная</td><td style="padding:7px;border:1px solid var(--border)">$\\nearrow$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$\\alpha$ иррац.</b></td><td style="padding:7px;border:1px solid var(--border)">$(0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">$(0; +\\infty)$</td><td style="padding:7px;border:1px solid var(--border)">—</td><td style="padding:7px;border:1px solid var(--border)">$\\nearrow$ при $\\alpha > 0$; $\\searrow$ при $\\alpha < 0$</td></tr>
</tbody>
</table>
</div>
<p><b>Все графики проходят через $(1; 1)$:</b> ведь $1^\\alpha = 1$ при любом $\\alpha$.</p>`);
html += makeCard('example', 'Степенные функции в природе и физике', '2.3', `
<p>Степенная функция $y = x^\\alpha$ — не абстракция: она описывает множество законов природы и формул из геометрии.</p>
<ul style="margin:8px 0 8px 22px;line-height:1.85">
<li><b>Закон Гука:</b> $F = kx$ — степенная с $\\alpha = 1$ (прямая пропорциональность).</li>
<li><b>Площадь круга:</b> $S = \\pi r^2$ — степенная с $\\alpha = 2$.</li>
<li><b>Объём шара:</b> $V = \\dfrac{4}{3} \\pi r^3$ — степенная с $\\alpha = 3$.</li>
<li><b>Закон обратных квадратов</b> (гравитация, освещённость): $F = \\dfrac{k}{r^2}$ — степенная с $\\alpha = -2$.</li>
<li><b>Период математического маятника:</b> $T = 2\\pi \\sqrt{\\dfrac{l}{g}}$ — степенная по $l$ с $\\alpha = \\dfrac{1}{2}$.</li>
<li><b>Третий закон Кеплера:</b> $T^2 \\sim a^3$ или $T \\sim a^{3/2}$ — степенная с иррациональным показателем близкой формы.</li>
</ul>
<p>Знание свойств степенной функции — это <b>универсальный инструмент</b> для понимания физики, химии, биологии и техники.</p>`);
/* INTERACTIVE 1 — главный визуализатор y = x^alpha */
html += `<div class="wg" id="p2-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Главный визуализатор $y = x^\\alpha$</div></div>
<div class="wg-help">Меняй $\\alpha$ ползунком — наблюдай, как меняется график. Snap-точки: $-2,\\ -1,\\ -\\tfrac{1}{2},\\ 0,\\ \\tfrac{1}{2},\\ 1,\\ 2,\\ 3$. Точка $(1; 1)$ всегда на графике.</div>
<div class="sliders">
<label>Показатель $\\alpha$ = <b id="p2-iv1-a">2</b><input type="range" id="p2-iv1-sa" min="-3" max="3" step="0.1" value="2"></label>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;font-size:.82rem">
<label style="display:inline-flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="p2-iv1-cb-1"> $y = x$ <span style="color:#94a3b8">(серый)</span></label>
<label style="display:inline-flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="p2-iv1-cb-2"> $y = x^{2}$ <span style="color:#a78bfa">(фиолет.)</span></label>
<label style="display:inline-flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="p2-iv1-cb-3"> $y = x^{3}$ <span style="color:#3b82f6">(син.)</span></label>
<label style="display:inline-flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="p2-iv1-cb-h"> $y = 1/x$ <span style="color:#ef4444">(красн.)</span></label>
</div>
<div style="background:var(--card);border-radius:9px;padding:10px;text-align:center"><svg id="p2-iv1-svg" viewBox="0 0 480 360" width="100%" style="max-width:480px;height:auto"></svg></div>
<div id="p2-iv1-desc" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.94rem;line-height:1.65;min-height:90px"></div>
</div>`;
/* INTERACTIVE 2 — калькулятор D и E */
html += `<div class="wg" id="p2-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $D$ и $E$</div></div>
<div class="wg-help">Введи показатель $\\alpha$ (целое, дробное вида $p/q$ или иррациональное вроде $\\sqrt{2} \\approx 1{,}41$, $\\pi \\approx 3{,}14$). Получи области определения и значений.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">$\\alpha$ =</span>
<input type="number" id="p2-iv2-a" class="tinp" style="width:110px;text-align:center" value="2" step="0.01">
<button class="btn primary" id="p2-iv2-go">Определить $D$ и $E$</button>
</div>
<div id="p2-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.94rem;min-height:80px;line-height:1.75"></div>
<div class="feedback" id="p2-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — квикфайр «Какая функция?» */
html += `<div class="wg" id="p2-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какая функция? Угадай $\\alpha$</div></div>
<div class="wg-help">По описанию графика выбери правильное значение $\\alpha$ из 4 вариантов. 8 заданий.</div>
<div class="score-display"><span>Задача <b id="p2-iv3-i">1</b> / 8</span><span>Очки: <b id="p2-iv3-s">0</b> / 8</span></div>
<div id="p2-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p2-iv3-opts" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px"></div>
<div class="feedback" id="p2-iv3-fb"></div>
<div class="actions"><button class="btn" id="p2-iv3-restart">Начать заново</button></div>
</div>`;
/* INTERACTIVE 4 — тренажёр свойств */
html += `<div class="wg" id="p2-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: свойства степенной функции</div></div>
<div class="wg-help">6 задач: подсчёт корней, вычисление значения, поиск $\\alpha$. Допуск $\\pm 0{,}05$.</div>
<div class="score-display"><span>Задача <b id="p2-iv4-i">1</b> / 6</span><span>Очки: <b id="p2-iv4-s">0</b> / 6</span></div>
<div id="p2-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p2-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p2-iv4-go">Проверить</button>
<button class="btn" id="p2-iv4-start">Заново</button>
</div>
<div class="feedback" id="p2-iv4-fb"></div>
</div>`;
html += secNav('p1', 'p3');
html += readButton('p2');
box.innerHTML = html;
renderMath(box);
/* IV1 — главный визуализатор y = x^alpha */
(function(){
const sa = document.getElementById('p2-iv1-sa');
const aL = document.getElementById('p2-iv1-a');
const svg = document.getElementById('p2-iv1-svg');
const desc = document.getElementById('p2-iv1-desc');
const cb1 = document.getElementById('p2-iv1-cb-1');
const cb2 = document.getElementById('p2-iv1-cb-2');
const cb3 = document.getElementById('p2-iv1-cb-3');
const cbh = document.getElementById('p2-iv1-cb-h');
const SNAP = [-2, -1, -0.5, 0, 0.5, 1, 2, 3];
const seen = new Set();
let _done = false;
function alphaColor(a){
if(a < 0) return '#ef4444';
if(!Number.isInteger(a)) return '#f59e0b';
if(a % 2 === 0) return '#a78bfa';
return '#3b82f6';
}
function alphaLabel(a){
if(Math.abs(a) < 0.001) return '0';
if(Math.abs(a - Math.round(a)) < 0.001) return String(Math.round(a));
if(Math.abs(a - 0.5) < 0.001) return '\\tfrac{1}{2}';
if(Math.abs(a + 0.5) < 0.001) return '-\\tfrac{1}{2}';
if(Math.abs(a - 1/3) < 0.01) return '\\tfrac{1}{3}';
return (+a.toFixed(2)).toString();
}
function powSafe(x, a){
if(x === 0){
if(a > 0) return 0;
return NaN;
}
if(x > 0) return Math.pow(x, a);
// x < 0
if(Number.isInteger(a)) return Math.pow(x, a);
// дробное alpha с нечётным знаменателем → пробуем как корень
// ищем простую рациональную аппроксимацию для snap-значений
const snapMatch = SNAP.find(s => Math.abs(s - a) < 0.001);
if(snapMatch !== undefined){
// 1/3 - кубический корень определён для x<0
if(Math.abs(snapMatch - 1/3) < 0.001) return -Math.pow(-x, 1/3);
}
return NaN;
}
function describe(a){
const eps = 0.05;
if(Math.abs(a) < eps) return '<b>$\\alpha = 0$:</b> $y = x^0 = 1$ при $x \\ne 0$. Горизонтальная прямая (с выколотой точкой в $x = 0$).';
if(Math.abs(a - Math.round(a)) < eps){
const n = Math.round(a);
if(n > 0){
if(n % 2 === 0){
return '<b>Натуральное чётное</b> ($\\alpha = '+n+'$): парабола $'+n+'$-й степени.<br>$D = \\mathbb{R}$, $E = [0; +\\infty)$. <b>Чётная</b> функция (симметрия отн. $Oy$).<br>Убывает на $(-\\infty; 0]$, возрастает на $[0; +\\infty)$.';
} else {
return '<b>Натуральное нечётное</b> ($\\alpha = '+n+'$): $y = x^{'+n+'}$.<br>$D = \\mathbb{R}$, $E = \\mathbb{R}$. <b>Нечётная</b> функция (симметрия отн. начала координат).<br>Возрастает на всей $\\mathbb{R}$.';
}
} else {
if(n % 2 === 0){
return '<b>Целое отрицательное чётное</b> ($\\alpha = '+n+'$): $y = \\dfrac{1}{x^{'+(-n)+'}}$.<br>$D = \\mathbb{R} \\setminus \\{0\\}$, $E = (0; +\\infty)$. <b>Чётная</b>. Асимптоты: $x = 0$, $y = 0$.';
} else {
return '<b>Целое отрицательное нечётное</b> ($\\alpha = '+n+'$): гипербола $y = \\dfrac{1}{x^{'+(-n)+'}}$.<br>$D = \\mathbb{R} \\setminus \\{0\\}$, $E = \\mathbb{R} \\setminus \\{0\\}$. <b>Нечётная</b>. Асимптоты: $x = 0$, $y = 0$.';
}
}
}
if(Math.abs(a - 0.5) < eps){
return '<b>$\\alpha = \\tfrac{1}{2}$:</b> квадратный корень $y = \\sqrt{x}$.<br>$D = [0; +\\infty)$, $E = [0; +\\infty)$. Возрастает на области определения.';
}
if(Math.abs(a + 0.5) < eps){
return '<b>$\\alpha = -\\tfrac{1}{2}$:</b> $y = \\dfrac{1}{\\sqrt{x}}$.<br>$D = (0; +\\infty)$, $E = (0; +\\infty)$. Убывает. Асимптоты $x = 0$, $y = 0$.';
}
if(a > 0){
return '<b>Дробный положительный</b> ($\\alpha \\approx '+(+a.toFixed(2))+'$): $D = [0; +\\infty)$, возрастает.';
}
return '<b>Отрицательный</b> ($\\alpha \\approx '+(+a.toFixed(2))+'$): $D = (0; +\\infty)$, убывает. Асимптоты $x = 0$, $y = 0$.';
}
function draw(){
let a = +sa.value;
a = snapToValue(a, SNAP, 0.08);
aL.textContent = alphaLabel(a);
const xmin = -3.5, xmax = 3.5, ymin = -3.5, ymax = 3.5;
const W = 480, H = 360, pad = 24;
const ax = axes2D(W, H, pad, xmin, xmax, ymin, ymax);
let g = ax.content;
// эталоны
if(cb1.checked) g += plotFunc(x => x, xmin, xmax, ax.toX, ax.toY, '#94a3b8');
if(cb2.checked) g += plotFunc(x => x*x, xmin, xmax, ax.toX, ax.toY, '#a78bfa');
if(cb3.checked) g += plotFunc(x => x*x*x, xmin, xmax, ax.toX, ax.toY, '#3b82f6');
if(cbh.checked){
g += plotFunc(x => 1/x, 0.001, xmax, ax.toX, ax.toY, '#ef4444');
g += plotFunc(x => 1/x, xmin, -0.001, ax.toX, ax.toY, '#ef4444');
}
// главный график
const col = alphaColor(a);
const isIntA = Math.abs(a - Math.round(a)) < 0.001;
const intA = Math.round(a);
if(isIntA && intA !== 0){
// целое: рисуем на обеих сторонах
if(intA > 0){
g += '<path d="' + (function(){
// объединить два куска
return '';
})() + '" />';
g += plotFunc(x => powSafe(x, intA), xmin, xmax, ax.toX, ax.toY, col, 300);
} else {
// отрицательное целое
g += plotFunc(x => powSafe(x, intA), 0.05, xmax, ax.toX, ax.toY, col, 200);
g += plotFunc(x => powSafe(x, intA), xmin, -0.05, ax.toX, ax.toY, col, 200);
g += asymptote('v', 0, ax.toX, ax.toY, xmin, xmax, ymin, ymax, '#cbd5e1');
}
} else if(Math.abs(a) < 0.001){
// y = 1
g += plotFunc(x => 1, 0.001, xmax, ax.toX, ax.toY, col);
g += plotFunc(x => 1, xmin, -0.001, ax.toX, ax.toY, col);
} else {
// дробное / иррациональное
if(a > 0){
g += plotFunc(x => Math.pow(x, a), 0.001, xmax, ax.toX, ax.toY, col, 250);
// если a = 1/3 — рисуем и слева
if(Math.abs(a - 1/3) < 0.05){
g += plotFunc(x => -Math.pow(-x, a), xmin, -0.001, ax.toX, ax.toY, col, 250);
}
} else {
g += plotFunc(x => Math.pow(x, a), 0.05, xmax, ax.toX, ax.toY, col, 250);
g += asymptote('v', 0, ax.toX, ax.toY, xmin, xmax, ymin, ymax, '#cbd5e1');
}
}
// точка (1, 1)
g += pointWithDrop(1, 1, ax.toX, ax.toY, '#0f172a', '(1; 1)');
svg.innerHTML = g;
desc.innerHTML = describe(a);
renderMath(desc);
const key = (+a.toFixed(2)).toString();
seen.add(key);
if(!_done && seen.size >= 6){ _done = true; addXp(10, 'p2-iv1'); bumpProgress('p2', 15); }
}
sa.addEventListener('input', draw);
[cb1,cb2,cb3,cbh].forEach(c => c.addEventListener('change', draw));
draw();
})();
/* IV2 — калькулятор D и E */
(function(){
const aI = document.getElementById('p2-iv2-a');
const out = document.getElementById('p2-iv2-out');
const fb = document.getElementById('p2-iv2-fb');
const go = document.getElementById('p2-iv2-go');
const used = new Set();
let _done = false;
function findFraction(a, maxDen){
maxDen = maxDen || 20;
for(let n = 2; n <= maxDen; n++){
for(let m = -3*n; m <= 3*n; m++){
if(m === 0) continue;
if(Math.abs(m/n - a) < 0.005){
const g = gcd(Math.abs(m), n);
return { num: m/g, den: n/g };
}
}
}
return null;
}
function analyze(){
const a = +aI.value;
if(!isFinite(a)){ feedback(fb, false, '&#10007; Введи число.'); return; }
let D = '', E = '', kind = '';
const isInt = Math.abs(a - Math.round(a)) < 0.005;
const intA = Math.round(a);
if(isInt){
if(intA === 0){
kind = '$\\alpha = 0$: константа $y = 1$';
D = '$D = \\mathbb{R} \\setminus \\{0\\}$ (точка $x = 0$ исключена)';
E = '$E = \\{1\\}$';
} else if(intA > 0){
if(intA % 2 === 0){
kind = 'Натуральное чётное $\\alpha = '+intA+'$ → парабола';
D = '$D = \\mathbb{R}$ — определена везде';
E = '$E = [0; +\\infty)$ — значения неотрицательны';
} else {
kind = 'Натуральное нечётное $\\alpha = '+intA+'$';
D = '$D = \\mathbb{R}$ — определена везде';
E = '$E = \\mathbb{R}$ — принимает любые значения';
}
} else {
if(intA % 2 === 0){
kind = 'Целое отрицательное чётное $\\alpha = '+intA+'$ → гипербола';
D = '$D = \\mathbb{R} \\setminus \\{0\\}$';
E = '$E = (0; +\\infty)$';
} else {
kind = 'Целое отрицательное нечётное $\\alpha = '+intA+'$';
D = '$D = \\mathbb{R} \\setminus \\{0\\}$';
E = '$E = \\mathbb{R} \\setminus \\{0\\}$';
}
}
} else {
const fr = findFraction(a);
if(fr){
const { num, den } = fr;
const denOdd = den % 2 === 1;
if(num > 0){
if(denOdd){
kind = 'Дробный $\\alpha = \\dfrac{'+num+'}{'+den+'}$ — знаменатель нечётный';
D = '$D = \\mathbb{R}$ (корень $\\sqrt['+den+']{x^{'+num+'}}$ определён везде)';
E = (num % 2 === 0) ? '$E = [0; +\\infty)$' : '$E = \\mathbb{R}$';
} else {
kind = 'Дробный $\\alpha = \\dfrac{'+num+'}{'+den+'}$ — знаменатель чётный';
D = '$D = [0; +\\infty)$ (корень чётной степени)';
E = '$E = [0; +\\infty)$';
}
} else {
kind = 'Дробный отрицательный $\\alpha = \\dfrac{'+num+'}{'+den+'}$';
D = denOdd ? '$D = \\mathbb{R} \\setminus \\{0\\}$' : '$D = (0; +\\infty)$';
E = (Math.abs(num) % 2 === 0) ? '$E = (0; +\\infty)$' : (denOdd ? '$E = \\mathbb{R} \\setminus \\{0\\}$' : '$E = (0; +\\infty)$');
}
} else {
// иррациональное
if(a > 0){
kind = '$\\alpha \\approx '+(+a.toFixed(3))+'$ — рассматриваем как иррациональное';
D = '$D = [0; +\\infty)$ (для иррационального $\\alpha > 0$, включая $x = 0$)';
E = '$E = [0; +\\infty)$';
} else {
kind = '$\\alpha \\approx '+(+a.toFixed(3))+'$ — иррациональное отрицательное';
D = '$D = (0; +\\infty)$';
E = '$E = (0; +\\infty)$';
}
}
}
out.innerHTML = '<div style="font-weight:700;color:var(--sec-acc-d);margin-bottom:8px">'+kind+'</div>'
+ '<div style="margin-bottom:6px">' + D + '</div>'
+ '<div>' + E + '</div>';
renderMath(out);
feedback(fb, true, '&#10003; Готово.');
used.add((+a.toFixed(3)).toString());
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p2-iv2'); bumpProgress('p2', 15); }
}
go.addEventListener('click', analyze);
aI.addEventListener('keydown', e => { if(e.key === 'Enter') analyze(); });
})();
/* IV3 — квикфайр «Какая функция?» */
(function(){
const Q = [
{ q:'Парабола $y = x^2$', opts:['1','2','3','-1'], ans:1 },
{ q:'Кубическая функция $y = x^3$', opts:['1','2','3','-1'], ans:2 },
{ q:'Гипербола $y = \\dfrac{1}{x}$', opts:['1','-1','-2','1/2'], ans:1 },
{ q:'Прямая $y = x$', opts:['0','1','2','-1'], ans:1 },
{ q:'Корень $y = \\sqrt{x}$', opts:['2','-2','1/2','1/3'], ans:2 },
{ q:'Гипербола $y = \\dfrac{1}{x^2}$', opts:['-1','-2','2','1/2'], ans:1 },
{ q:'Кубический корень $y = \\sqrt[3]{x}$', opts:['3','-1/3','1/3','1/2'], ans:2 },
{ q:'Парабола 4-й степени $y = x^4$', opts:['2','3','4','-4'], ans:2 },
];
let i = 0, score = 0;
const qEl = document.getElementById('p2-iv3-q');
const oEl = document.getElementById('p2-iv3-opts');
const fb = document.getElementById('p2-iv3-fb');
const iEl = document.getElementById('p2-iv3-i');
const sEl = document.getElementById('p2-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p2-iv3'); bumpProgress('p2', 25); }
else if(score >= 5){ addXp(8, 'p2-iv3'); bumpProgress('p2', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = item.q;
oEl.innerHTML = item.opts.map((o, k) => '<button class="btn primary" data-k="'+k+'" style="font-family:JetBrains Mono,monospace">$\\alpha = '+o+'$</button>').join('');
fb.style.display = 'none';
renderMath(qEl); renderMath(oEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const k = +b.dataset.k;
if(k === item.ans){ score++; feedback(fb, true, '&#10003; Верно! Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: $\\alpha = '+item.opts[item.ans]+'$. Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1200);
});
});
}
document.getElementById('p2-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* IV4 — тренажёр свойств */
(function(){
const Q = [
{ q:'Сколько решений у уравнения $x^4 = 16$?', ans:2, hint:'$x = \\pm 2$ — два корня' },
{ q:'Сколько решений у уравнения $x^3 = -8$?', ans:1, hint:'$x = -2$ — единственный действительный корень' },
{ q:'Найдите $y(4)$ для $y = x^{3/2}$', ans:8, hint:'$4^{3/2} = (\\sqrt{4})^3 = 2^3 = 8$' },
{ q:'Чему равно $\\alpha$ у функции $y = \\sqrt[3]{x^4}$? (десятичная)', ans:4/3, hint:'$\\sqrt[3]{x^4} = x^{4/3} \\approx 1{,}33$' },
{ q:'Для $y = x^{-2}$ найди $y(0{,}5)$', ans:4, hint:'$0{,}5^{-2} = \\dfrac{1}{0{,}25} = 4$' },
{ q:'При $\\alpha = \\dfrac{1}{3}$ найди $y(-27)$', ans:-3, hint:'$\\sqrt[3]{-27} = -3$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p2-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15, 'p2-iv4'); bumpProgress('p2', 25); }
else if(score >= 4){ addXp(8, 'p2-iv4'); bumpProgress('p2', 15); }
return;
}
document.getElementById('p2-iv4-i').textContent = (i+1);
document.getElementById('p2-iv4-s').textContent = score;
document.getElementById('p2-iv4-q').innerHTML = Q[i].q;
document.getElementById('p2-iv4-ans').value = '';
renderMath(document.getElementById('p2-iv4-q'));
document.getElementById('p2-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p2-iv4-fb');
const raw = document.getElementById('p2-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) < 0.05){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ $\\approx '+(+Q[i].ans.toFixed(2))+'$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p2-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p2-iv4-go').addEventListener('click', go);
document.getElementById('p2-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p2-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p2');
}
function buildP3(){
const box = document.getElementById('p3-body');
let html = '';
html += makeCard('theory', 'Определение логарифма', '3.1', `
<p><b>Логарифмом</b> числа $b$ по основанию $a$ называется такой показатель степени $c$, в который нужно возвести $a$, чтобы получить $b$:</p>
\\[ \\log_a b = c \\;\\Leftrightarrow\\; a^c = b \\]
<p>при условиях $a > 0$, $a \\ne 1$, $b > 0$.</p>
<p><b>Примеры.</b></p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$\\log_2 8 = 3$, потому что $2^3 = 8$.</li>
<li>$\\log_3 81 = 4$, потому что $3^4 = 81$.</li>
<li>$\\log_5 \\dfrac{1}{25} = -2$, потому что $5^{-2} = \\dfrac{1}{25}$.</li>
<li>$\\log_{10} 1000 = 3$ (десятичный логарифм).</li>
</ul>
<details class="spoiler"><summary>Почему такие ограничения на $a$ и $b$?</summary><div class="spoiler-body">
Если $a = 1$, то $1^c = 1$ для любого $c$ — равенство $1^c = b$ имело бы решение только при $b = 1$, и оно было бы не единственным. Если $a \\le 0$, то $a^c$ не определена для всех действительных $c$. Если $b \\le 0$, то уравнение $a^c = b$ при $a > 0$ не имеет решений, ведь $a^c > 0$ всегда.
</div></details>`);
html += makeCard('rule', 'Основное логарифмическое тождество и базовые свойства', '3.2', `
<p><b>Основное логарифмическое тождество:</b></p>
\\[ a^{\\log_a b} = b \\]
<p>Оно означает буквально то же, что и определение: если возвести основание $a$ в степень $\\log_a b$, получим само $b$.</p>
<p><b>Базовые свойства</b> (следуют прямо из определения):</p>
<ul style="margin:6px 0 8px 22px;line-height:1.75">
<li>$\\log_a 1 = 0$ — потому что $a^0 = 1$.</li>
<li>$\\log_a a = 1$ — потому что $a^1 = a$.</li>
<li>$\\log_a a^n = n$ — потому что $a^n = a^n$.</li>
<li>$\\log_a \\dfrac{1}{a^n} = -n$ — потому что $a^{-n} = \\dfrac{1}{a^n}$.</li>
</ul>
<p><b>Пример.</b> $7^{\\log_7 5} = 5$ — это сразу следует из тождества, не нужно ничего вычислять.</p>
<p><b>Пример.</b> $\\log_2 2^{10} = 10$, $\\log_3 \\dfrac{1}{81} = -4$.</p>`);
html += makeCard('example', 'Десятичный и натуральный логарифмы', '3.3', `
<p><b>Десятичный логарифм</b> — это логарифм по основанию $10$. Для него ввели специальное обозначение:</p>
\\[ \\lg b = \\log_{10} b \\]
<p>Примеры: $\\lg 1000 = 3$, $\\lg 0{,}01 = -2$, $\\lg 1 = 0$, $\\lg 10 = 1$.</p>
<p><b>Натуральный логарифм</b> — это логарифм по основанию $e \\approx 2{,}71828\\ldots$ ($e$ — иррациональное число, постоянная Эйлера):</p>
\\[ \\ln b = \\log_e b \\]
<p>Натуральный логарифм встречается в физике, биологии, экономике — везде, где речь идёт о законах роста и распада (радиоактивный распад, рост колоний бактерий, сложные проценты).</p>
<p><b>Примеры.</b> $\\ln e = 1$, $\\ln e^2 = 2$, $\\ln 1 = 0$, $e^{\\ln 5} = 5$ (по основному тождеству).</p>`);
/* INTERACTIVE 1 — визуализатор логарифма через показательную функцию */
html += `<div class="wg" id="p3-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Логарифм как показатель в $a^x = b$</div></div>
<div class="wg-help">Двигай ползунки. Кривая $y = a^x$ пересекает горизонталь $y = b$ в точке с абсциссой $x = \\log_a b$ — это и есть значение логарифма.</div>
<div class="sliders">
<label>Основание $a$ = <b id="p3-iv1-a">2</b><input type="range" id="p3-iv1-sa" min="1.5" max="10" step="0.5" value="2"></label>
<label>Аргумент $b$ = <b id="p3-iv1-b">8</b><input type="range" id="p3-iv1-sb" min="0.1" max="10" step="0.1" value="8"></label>
</div>
<div style="background:var(--card);border-radius:9px;padding:10px;text-align:center"><svg id="p3-iv1-svg" viewBox="0 0 480 360" width="100%" style="max-width:480px;height:auto"></svg></div>
<div id="p3-iv1-formula" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;text-align:center;line-height:1.9"></div>
</div>`;
/* INTERACTIVE 2 — калькулятор логарифма */
html += `<div class="wg" id="p3-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $\\log_a b$</div></div>
<div class="wg-help">Введи основание $a$ и аргумент $b$. Если $b$ — точная степень $a$, увидишь целый ответ. Иначе — приближённое значение.</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:10px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p3-iv2-a" class="tinp" style="width:80px;text-align:center" value="2" step="0.1">
<span style="font-family:'JetBrains Mono',monospace;margin-left:8px">$b$ =</span>
<input type="number" id="p3-iv2-b" class="tinp" style="width:80px;text-align:center" value="8" step="0.1">
<button class="btn primary" id="p3-iv2-go">Вычислить $\\log_a b$</button>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-bottom:10px;font-size:.82rem">
<span style="color:var(--muted)">Быстрый ввод $a$:</span>
<button class="btn" data-aq="2" id="p3-iv2-a2" style="padding:3px 9px;font-size:.82rem">2</button>
<button class="btn" data-aq="3" id="p3-iv2-a3" style="padding:3px 9px;font-size:.82rem">3</button>
<button class="btn" data-aq="10" id="p3-iv2-a10" style="padding:3px 9px;font-size:.82rem">10</button>
<button class="btn" data-aq="2.71828" id="p3-iv2-ae" style="padding:3px 9px;font-size:.82rem">$e$</button>
</div>
<div id="p3-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;min-height:80px;line-height:1.8"></div>
<div class="feedback" id="p3-iv2-fb"></div>
</div>`;
/* INTERACTIVE 3 — DnD: логарифм → значение */
html += `<div class="wg" id="p3-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Сопоставь логарифм со значением</div></div>
<div class="wg-help">Перетащи каждый логарифм в правильный ящик. Помни: $\\log_a b$ — это показатель, в который надо возвести $a$, чтобы получить $b$.</div>
<div class="dnd-hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11V6a3 3 0 0 1 6 0v5"/><path d="M9 11h6v8a4 4 0 0 1-8 0z"/></svg> 8 логарифмов — 4 ящика</div>
<div id="p3-iv3-pool"></div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:8px">
<div class="drop-box"><h5 data-cat="m3">$=-3$</h5><div class="drop-items" data-cat="m3"></div></div>
<div class="drop-box"><h5 data-cat="m2">$=-2$</h5><div class="drop-items" data-cat="m2"></div></div>
<div class="drop-box"><h5 data-cat="p3">$=3$</h5><div class="drop-items" data-cat="p3"></div></div>
<div class="drop-box"><h5 data-cat="p4">$=4$</h5><div class="drop-items" data-cat="p4"></div></div>
</div>
<div class="actions"><button class="btn primary" id="p3-iv3-check">Проверить</button><button class="btn" id="p3-iv3-reset">Сначала</button></div>
<div class="feedback" id="p3-iv3-fb"></div>
</div>`;
/* INTERACTIVE 4 — тренажёр логарифмов */
html += `<div class="wg" id="p3-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр: вычисли логарифм</div></div>
<div class="wg-help">6 задач. Введи число — целое или десятичное (допуск $\\pm 0{,}05$).</div>
<div class="score-display"><span>Задача <b id="p3-iv4-i">1</b> / 6</span><span>Очки: <b id="p3-iv4-s">0</b> / 6</span></div>
<div id="p3-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.15rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">
<span style="font-family:'JetBrains Mono',monospace">ответ =</span>
<input type="number" id="p3-iv4-ans" class="tinp" style="width:110px;text-align:center" step="0.01">
<button class="btn primary" id="p3-iv4-go">Проверить</button>
<button class="btn" id="p3-iv4-start">Заново</button>
</div>
<div class="feedback" id="p3-iv4-fb"></div>
</div>`;
html += secNav('p2', 'final1');
html += readButton('p3');
box.innerHTML = html;
renderMath(box);
/* IV1 — визуализатор */
(function(){
const sa = document.getElementById('p3-iv1-sa');
const sb = document.getElementById('p3-iv1-sb');
const aL = document.getElementById('p3-iv1-a');
const bL = document.getElementById('p3-iv1-b');
const svg = document.getElementById('p3-iv1-svg');
const fEl = document.getElementById('p3-iv1-formula');
const seen = new Set();
let _done = false;
function draw(){
const a = +sa.value;
const b = +sb.value;
aL.textContent = (Number.isInteger(a) ? a : a.toFixed(1));
bL.textContent = (Math.abs(b - Math.round(b)) < 0.01) ? Math.round(b) : b.toFixed(1);
const W = 480, H = 360, pad = 36;
const xmin = -3, xmax = 5, ymin = -1, ymax = 12;
const ax = axes2D(W, H, pad, xmin, xmax, ymin, ymax);
let g = ax.content;
// y = a^x
g += plotFunc(x => Math.pow(a, x), xmin, xmax, ax.toX, ax.toY, '#3b82f6', 240);
// y = b (асимптота-горизонталь)
g += asymptote('h', b, ax.toX, ax.toY, xmin, xmax, ymin, ymax, '#ef4444');
// точка пересечения
const xVal = Math.log(b) / Math.log(a);
if(isFinite(xVal) && xVal >= xmin && xVal <= xmax){
g += pointWithDrop(xVal, b, ax.toX, ax.toY, '#10b981', 'log_'+ (Number.isInteger(a)?a:a.toFixed(1)) +' '+ (Math.abs(b - Math.round(b))<0.01?Math.round(b):b.toFixed(1)) +' ≈ ' + (+xVal.toFixed(3)));
}
// легенда
g += '<text x="'+(W-pad-110)+'" y="'+(pad+14)+'" font-size="12" font-weight="700" fill="#3b82f6">y = a^x</text>';
g += '<text x="'+(W-pad-110)+'" y="'+(pad+30)+'" font-size="12" font-weight="700" fill="#ef4444">y = b</text>';
svg.innerHTML = g;
const aTxt = Number.isInteger(a) ? a : (+a.toFixed(1));
const bTxt = Math.abs(b - Math.round(b)) < 0.01 ? Math.round(b) : (+b.toFixed(1));
const xTxt = (+xVal.toFixed(4));
fEl.innerHTML = '$\\log_{'+aTxt+'} '+bTxt+' \\approx '+xTxt+'$ — это показатель, в который надо возвести $'+aTxt+'$, чтобы получить $'+bTxt+'$.';
renderMath(fEl);
const sig = (Math.round(a*2)/2) + ':' + (Math.round(b*10)/10);
seen.add(sig);
if(!_done && seen.size >= 6){ _done = true; addXp(10, 'p3-iv1'); bumpProgress('p3', 15); }
}
sa.addEventListener('input', draw);
sb.addEventListener('input', draw);
draw();
})();
/* IV2 — калькулятор */
(function(){
const aI = document.getElementById('p3-iv2-a');
const bI = document.getElementById('p3-iv2-b');
const goBtn = document.getElementById('p3-iv2-go');
const out = document.getElementById('p3-iv2-out');
const fb = document.getElementById('p3-iv2-fb');
let usedOk = 0;
let _done = false;
document.querySelectorAll('#p3-iv2 [data-aq]').forEach(btn=>{
btn.addEventListener('click', ()=>{ aI.value = btn.dataset.aq; });
});
function compute(){
const a = parseFloat(aI.value);
const b = parseFloat(bI.value);
if(!isFinite(a) || a <= 0){ feedback(fb, false, '&#10007; Основание $a$ должно быть положительным.'); out.innerHTML=''; return; }
if(Math.abs(a - 1) < 1e-9){ feedback(fb, false, '&#10007; Основание $a$ не может быть равно 1.'); out.innerHTML=''; return; }
if(!isFinite(b) || b <= 0){ feedback(fb, false, '&#10007; Аргумент $b$ должен быть положительным.'); out.innerHTML=''; return; }
const val = Math.log(b) / Math.log(a);
// проверим целое значение
const intCand = Math.round(val);
const isExact = Math.abs(val - intCand) < 1e-6 && Math.abs(Math.pow(a, intCand) - b) < 1e-6;
let aShow = (Math.abs(a - 2.71828) < 0.001) ? 'e' : (Number.isInteger(a) ? String(a) : (+a.toFixed(3)).toString());
let bShow = Number.isInteger(b) ? String(b) : (+b.toFixed(3)).toString();
const check = Math.pow(a, val);
let html = '';
if(isExact){
html += '<div style="font-weight:700;color:var(--ok)">$\\log_{'+aShow+'} '+bShow+' = '+intCand+'$</div>';
html += '<div style="font-size:.88rem;color:var(--muted);margin-top:6px">Точное значение, так как $'+bShow+' = '+aShow+'^{'+intCand+'}$.</div>';
} else {
html += '<div style="font-weight:700;color:var(--sec-acc-d)">$\\log_{'+aShow+'} '+bShow+' \\approx '+(+val.toFixed(4))+'$</div>';
html += '<div style="font-size:.88rem;color:var(--muted);margin-top:6px">Приближённое значение (через формулу $\\dfrac{\\ln b}{\\ln a}$).</div>';
}
html += '<div style="margin-top:8px;font-size:.92rem">Проверка: $'+aShow+'^{'+(+val.toFixed(4))+'} \\approx '+(+check.toFixed(4))+'$.</div>';
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Вычислено.');
usedOk++;
if(!_done && usedOk >= 4){ _done = true; addXp(10, 'p3-iv2'); bumpProgress('p3', 15); }
}
goBtn.addEventListener('click', compute);
aI.addEventListener('keydown', e=>{ if(e.key === 'Enter') compute(); });
bI.addEventListener('keydown', e=>{ if(e.key === 'Enter') compute(); });
})();
/* IV3 — DnD: логарифм → значение */
(function(){
const items = [
{ id:'l1', cat:'p3', html:'$\\log_2 8$' },
{ id:'l2', cat:'p4', html:'$\\log_3 81$' },
{ id:'l3', cat:'m2', html:'$\\log_2 \\tfrac{1}{4}$' },
{ id:'l4', cat:'p3', html:'$\\log_4 64$' },
{ id:'l5', cat:'m3', html:'$\\log_{10} 0{,}001$' },
{ id:'l6', cat:'p3', html:'$\\log_5 125$' },
{ id:'l7', cat:'p4', html:'$\\log_2 16$' },
{ id:'l8', cat:'m2', html:'$\\log_3 \\tfrac{1}{9}$' },
];
const sorter = setupSorter({
poolId:'p3-iv3-pool',
scopeSelector:'#p3-iv3',
items: items,
cats:['m3','m2','p3','p4'],
columnLayout:false,
});
document.getElementById('p3-iv3-check').addEventListener('click', ()=>{
const fb = document.getElementById('p3-iv3-fb');
const placedCount = items.filter(it => sorter.placed[it.id]).length;
const correct = items.filter(it => sorter.placed[it.id] === it.cat).length;
if(placedCount < items.length){ feedback(fb, false, '&#10007; Размести все 8 логарифмов.'); return; }
if(correct === items.length){ feedback(fb, true, '&#10003; Все 8 на месте! +15 XP'); addXp(15,'p3-iv3'); bumpProgress('p3', 25); }
else feedback(fb, false, '&#10007; Правильно ' + correct + ' из 8. Попробуй ещё.');
});
document.getElementById('p3-iv3-reset').addEventListener('click', ()=>{ sorter.reset(); document.getElementById('p3-iv3-fb').style.display = 'none'; });
})();
/* IV4 — тренажёр */
(function(){
const Q = [
{ q:'$\\log_2 32 = \\;?$', ans:5, hint:'$2^5 = 32$' },
{ q:'$\\log_{10} 1000 = \\;?$', ans:3, hint:'$10^3 = 1000$, то есть $\\lg 1000 = 3$' },
{ q:'$\\log_3 \\dfrac{1}{27} = \\;?$', ans:-3, hint:'$3^{-3} = \\dfrac{1}{27}$' },
{ q:'$2^{\\log_2 7} = \\;?$', ans:7, hint:'Основное логарифмическое тождество $a^{\\log_a b} = b$' },
{ q:'$\\log_5 5^4 = \\;?$', ans:4, hint:'$\\log_a a^n = n$' },
{ q:'$\\log_{1/2} 8 = \\;?$', ans:-3, hint:'$\\left(\\tfrac{1}{2}\\right)^{-3} = 2^3 = 8$' },
];
let i = 0, score = 0;
function show(){
if(i >= Q.length){
document.getElementById('p3-iv4-q').innerHTML = '<b>Готово!</b> Результат: ' + score + ' / ' + Q.length;
if(score === Q.length){ addXp(15,'p3-iv4'); bumpProgress('p3', 25); }
else if(score >= 4){ addXp(8,'p3-iv4'); bumpProgress('p3', 15); }
return;
}
document.getElementById('p3-iv4-i').textContent = (i+1);
document.getElementById('p3-iv4-s').textContent = score;
document.getElementById('p3-iv4-q').innerHTML = Q[i].q;
document.getElementById('p3-iv4-ans').value = '';
renderMath(document.getElementById('p3-iv4-q'));
document.getElementById('p3-iv4-fb').style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p3-iv4-fb');
const raw = document.getElementById('p3-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(ans - Q[i].ans) < 0.05){ score++; feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Ответ: $'+Q[i].ans+'$ ('+Q[i].hint+'). Дальше ▶');
document.getElementById('p3-iv4-s').textContent = score;
i++;
setTimeout(show, 1300);
}
document.getElementById('p3-iv4-go').addEventListener('click', go);
document.getElementById('p3-iv4-ans').addEventListener('keydown', e=>{ if(e.key === 'Enter') go(); });
document.getElementById('p3-iv4-start').addEventListener('click', ()=>{ i=0; score=0; show(); });
show();
})();
wireReadBtn('p3');
}
function buildFinal1(){
const box = document.getElementById('final1-body');
let html = '';
/* Часть А — Шпаргалка главы (3 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon theory">${ICONS.theory}</span>
<span class="card-title">Шпаргалка главы 1</span>
<span class="card-num">Итог</span>
</div>
<div class="card-body">
<p>Ключевые формулы и идеи всех трёх параграфов в одном месте — просмотри перед битвой с боссами.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:10px">
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" style="width:18px;height:18px"><path d="M3 18l3-6 3 6"/><path d="M4 16h4"/><path d="M14 8l4 8"/><path d="M14 16l4-8"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 1 · Рац. степень</div>
</div>
<div style="font-size:.95rem">$a^{m/n} = \\sqrt[n]{a^m}$ при $a > 0$. Все свойства из 7 класса сохраняются: $a^p a^q = a^{p+q}$, $(a^p)^q = a^{pq}$. Показатель — любое $x \\in \\mathbb{R}$.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" style="width:18px;height:18px"><path d="M3 12c4-9 14-9 18 0"/><path d="M3 12c4 9 14 9 18 0"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 2 · Степ. функция</div>
</div>
<div style="font-size:.95rem">$y = x^\\alpha$ — 6 типов по $\\alpha$. Все графики проходят через $(1; 1)$. При нечётном $\\alpha$ — нечётная, при чётном — чётная. При $\\alpha < 0$ — гипербола.</div>
</div>
<div style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:11px;border-left:3px solid var(--pri)">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
<svg viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="2" style="width:18px;height:18px"><path d="M4 4h16"/><path d="M4 12h16"/><path d="M4 20h10"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 3 · Логарифм</div>
</div>
<div style="font-size:.95rem">$\\log_a b = c \\Leftrightarrow a^c = b$ ($a > 0$, $a \\ne 1$, $b > 0$). Тождество: $a^{\\log_a b} = b$. $\\lg = \\log_{10}$, $\\ln = \\log_e$.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов (intro) */
html += `<div class="card">
<div class="card-header">
<span class="card-icon rule">${ICONS.rule}</span>
<span class="card-title">Боссы главы 1</span>
<span class="card-num">5</span>
</div>
<div class="card-body">
<p>5 интегрированных задач — каждая комбинирует ≥ 2 темы. За каждого побеждённого босса: <b>+10 XP, +18% к прогрессу</b>. Победишь всех — ачивка <b>«Магистр степеней и логарифмов»</b> и <b>+50 XP бонус</b>.</p>
</div>
</div>`;
html += '<div id="ch1-bosses-container"></div>';
html += `<div style="margin-top:18px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--sec-acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center" id="ch1-final-summary">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>
<div id="ch1-boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>
<div style="height:12px;background:var(--card);border-radius:8px;overflow:hidden;border:1px solid var(--border)">
<div id="ch1-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#d97706,#fbbf24);transition:width .35s"></div>
</div>
<div id="ch1-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #f59e0b">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#92400e;font-size:1.05rem;margin-bottom:6px">Магистр степеней и логарифмов</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 1 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-11-ch2" style="text-decoration:none">Дальше: Глава 2 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p3', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Циклоп Степеней',
tag:'§ 1 + § 2',
q:'Вычислите: $\\left(\\dfrac{27}{8}\\right)^{2/3} \\cdot \\left(\\dfrac{9}{4}\\right)^{-1}$. Введите ответ числом.',
ans:1,
hint:'$\\left(\\dfrac{27}{8}\\right)^{2/3} = \\left(\\dfrac{3}{2}\\right)^2 = \\dfrac{9}{4}$. Тогда $\\dfrac{9}{4} \\cdot \\dfrac{4}{9} = 1$.'
},
{
n:2, color:'#0891b2',
title:'Минотавр Степенной',
tag:'§ 2',
q:'У функции $y = x^{-2}$ найдите $y(-0{,}5)$. Введите ответ числом.',
ans:4,
hint:'$(-0{,}5)^{-2} = \\dfrac{1}{(-0{,}5)^2} = \\dfrac{1}{0{,}25} = 4$.'
},
{
n:3, color:'#7c3aed',
title:'Гарпия Тождества',
tag:'§ 3',
q:'Вычислите: $9^{\\log_3 5}$. Введите ответ числом.',
ans:25,
hint:'$9 = 3^2$, поэтому $9^{\\log_3 5} = 3^{2\\log_3 5} = 3^{\\log_3 25} = 25$.'
},
{
n:4, color:'#dc2626',
title:'Дракон Логарифмический',
tag:'§ 3 + § 1',
q:'Найдите $\\log_2 \\sqrt[3]{16}$. Введите десятичное число (допуск $\\pm 0{,}05$).',
ans:4/3,
hint:'$\\sqrt[3]{16} = 16^{1/3} = (2^4)^{1/3} = 2^{4/3}$. Тогда $\\log_2 2^{4/3} = \\dfrac{4}{3} \\approx 1{,}33$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Степеней',
tag:'§ 1 + § 2 + § 3',
q:'Решите уравнение $x^{\\log_2 x} = 16$. Введите больший корень числом.',
ans:4,
hint:'Логарифмируем по основанию 2: $(\\log_2 x)^2 = \\log_2 16 = 4$, значит $\\log_2 x = \\pm 2$. Корни $x = 4$ и $x = \\tfrac{1}{4}$. Больший — $4$.'
},
];
const cont = document.getElementById('ch1-bosses-container');
const STATE_KEY = 'algebra11_ch1_bosses';
const BOSS_STATE = (function(){
try{ const s = localStorage.getItem(STATE_KEY); if(s){ const p = JSON.parse(s); if(Array.isArray(p) && p.length === BOSSES.length) return p; } }catch(e){}
return BOSSES.map(()=>({defeated:false}));
})();
function saveBosses(){ try{ localStorage.setItem(STATE_KEY, JSON.stringify(BOSS_STATE)); }catch(e){} }
cont.innerHTML = BOSSES.map((b, idx)=>{
return '<div class="boss-card" id="boss1-'+b.n+'-card" style="padding:16px;background:var(--card);border-radius:12px;border:2px solid '+b.color+';margin-bottom:14px">'
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap">'
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px;flex-shrink:0"><polygon points="12,2 22,20 2,20"/></svg>'
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:'+b.color+';font-size:1.05rem">Босс '+b.n+': '+b.title+'</div>'
+'<div style="margin-left:auto;font-size:.78rem;color:var(--muted);padding:3px 8px;background:var(--sec-acc-soft);border-radius:6px">'+b.tag+'</div>'
+'</div>'
+'<div class="boss-q" id="boss1-'+b.n+'-q" style="padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:1rem;line-height:1.5;margin-bottom:10px">'+b.q+'</div>'
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
+'<span style="font-family:\'JetBrains Mono\',monospace;font-size:.92rem">ответ =</span>'
+'<input type="number" id="boss1-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss1-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss1-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss1-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch1-boss-overall');
const fill = document.getElementById('ch1-boss-overall-fill');
if(txt) txt.textContent = won + ' / ' + BOSSES.length + ' боссов побеждено';
if(fill) fill.style.width = (won * 100 / BOSSES.length) + '%';
if(won >= BOSSES.length){
const reward = document.getElementById('ch1-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch1_done')){
achievement('ch1_done','Магистр степеней и логарифмов');
addXp(50, 'ch1-bonus');
bumpProgress('final1', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss1-'+b.n+'-card');
const goBtn = document.getElementById('boss1-'+b.n+'-go');
const hintBtn = document.getElementById('boss1-'+b.n+'-hint');
const ansInp = document.getElementById('boss1-'+b.n+'-ans');
if(BOSS_STATE[idx].defeated){
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow');
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.innerHTML = '&#10003; Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss1-'+b.n+'-fb');
const raw = ansInp.value.replace(',', '.');
const val = parseFloat(raw);
if(isNaN(val)){ feedback(fb, false, '&#10007; Введи число.'); return; }
if(Math.abs(val - b.ans) < 0.05){
BOSS_STATE[idx].defeated = true; saveBosses();
feedback(fb, true, '&#10003; Босс '+b.n+' повержен! +10 XP. '+b.hint);
addXp(10, 'boss-ch1-'+b.n);
bumpProgress('final1', 18);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.innerHTML = '&#10003; Повержен';
ansInp.disabled = true;
card.style.background = 'linear-gradient(135deg,var(--sec-acc-soft),var(--pri-soft))';
card.classList.add('glow','pulse');
setTimeout(()=>card.classList.remove('pulse'), 900);
refreshOverall();
} else {
feedback(fb, false, '&#10007; Промах. Попробуй ещё. Подсказка доступна.');
}
});
hintBtn.addEventListener('click', ()=>{
const fb = document.getElementById('boss1-'+b.n+'-fb');
fb.className = 'feedback ok';
fb.innerHTML = '<b>Подсказка:</b> '+b.hint;
fb.style.display = 'block';
fb.style.background = 'var(--warn-bg)';
fb.style.color = '#92400e';
fb.style.borderLeftColor = 'var(--warn)';
renderMath(fb);
});
ansInp.addEventListener('keydown', e=>{ if(e.key === 'Enter') goBtn.click(); });
});
refreshOverall();
}
/* ===== Search ===== */
const SEARCH_INDEX = (function(){
const arr=[];
PARAS.forEach(p=>arr.push({kind:'Параграф',title:p.num+' '+p.name,desc:p.sub||'',sec:p.id}));
return arr;
})();
function initSearch(){
const modal=document.getElementById('search-modal'),inp=document.getElementById('search-input'),out=document.getElementById('search-results'),btn=document.getElementById('search-btn');
if(!modal||!inp||!out) return;
let cur=0,rows=[];
function score(q,it){ const t=(it.title+' '+it.desc).toLowerCase(); if(t.includes(q)) return 100+(it.title.toLowerCase().startsWith(q)?50:0); let s=0; q.split(/\s+/).forEach(w=>{if(w&&t.includes(w))s+=10;}); return s; }
function rank(q){ q=q.trim().toLowerCase(); if(!q) return SEARCH_INDEX.slice(0,12); return SEARCH_INDEX.map(it=>({it,s:score(q,it)})).filter(x=>x.s>0).sort((a,b)=>b.s-a.s).slice(0,20).map(x=>x.it); }
function render(){ cur=0; if(!rows.length){out.innerHTML='<div class="search-empty">Ничего не найдено</div>';return;} out.innerHTML=rows.map((r,i)=>'<button class="search-row'+(i===0?' active':'')+'" data-i="'+i+'"><div class="sr-kind">'+r.kind+'</div><div class="sr-title">'+r.title+'</div>'+(r.desc?'<div class="sr-desc">'+(r.desc.length>90?r.desc.slice(0,90)+'…':r.desc)+'</div>':'')+'</button>').join(''); out.querySelectorAll('.search-row').forEach(b=>b.addEventListener('click',()=>{cur=+b.dataset.i;pick();})); }
function pick(){ const r=rows[cur]; if(!r) return; close(); goTo(r.sec); }
function move(d){ const items=out.querySelectorAll('.search-row'); if(!items.length) return; items[cur]&&items[cur].classList.remove('active'); cur=(cur+d+items.length)%items.length; items[cur].classList.add('active'); items[cur].scrollIntoView({block:'nearest'}); }
function open(){ modal.classList.add('show'); inp.value=''; rows=rank(''); render(); setTimeout(()=>inp.focus(),50); }
function close(){ modal.classList.remove('show'); }
btn&&btn.addEventListener('click',open);
modal.addEventListener('click',e=>{if(e.target===modal)close();});
inp.addEventListener('input',()=>{rows=rank(inp.value);render();});
inp.addEventListener('keydown',e=>{ if(e.key==='ArrowDown'){e.preventDefault();move(1);}else if(e.key==='ArrowUp'){e.preventDefault();move(-1);}else if(e.key==='Enter'){e.preventDefault();pick();}else if(e.key==='Escape'){e.preventDefault();close();} });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&(e.key==='k'||e.key==='K')){ e.preventDefault(); if(modal.classList.contains('show')) close(); else open(); } });
}
function initSidebarToggle(){
const side=document.getElementById('col-side'),back=document.getElementById('col-side-backdrop'),btn=document.getElementById('sidebar-btn');
if(!side||!btn) return;
function open(){ side.classList.add('open'); back.classList.add('show'); }
function close(){ side.classList.remove('open'); back.classList.remove('show'); }
btn.addEventListener('click',()=>{ if(side.classList.contains('open')) close(); else open(); });
back.addEventListener('click',close);
document.addEventListener('keydown',e=>{ if(e.key==='Escape') close(); });
}
function init(){
loadProgress(); initTheme(); initSidebarToggle(); initSearch();
buildParaSelector(); refreshProgressUI(); loadServerReadState(); goTo(PARAS[0].id);
setTimeout(()=>achievement('start'), 600);
if(window.LS&&window.LS.xp){
window.LS.xp.load().then(function(s){ if(s&&s.xp>STATE.xp){ STATE.xp=s.xp; STATE.level=calcLevel(STATE.xp); saveProgress(); refreshProgressUI(); if(STATE.current) buildSidebar(STATE.current); } });
}
}
document.addEventListener('DOMContentLoaded', init);
/* === ALG11 POLISH JS === */
(function(){
function bumpScore(el){
if(!el) return;
el.classList.remove('bump');
void el.offsetWidth;
el.classList.add('bump');
setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
}
window.__alg11BumpScore = bumpScore;
function observeScores(root){
root = root || document;
var nodes = root.querySelectorAll('.score-display b');
nodes.forEach(function(b){
if(b.__scoreObs) return;
b.__scoreObs = true;
var last = b.textContent;
try{
var mo = new MutationObserver(function(){
var nv = b.textContent;
if(nv !== last){ last = nv; bumpScore(b); }
});
mo.observe(b, {childList:true, characterData:true, subtree:true});
}catch(e){}
});
}
function rescanScores(){ try{ observeScores(document); }catch(e){} }
if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
else rescanScores();
try{
var rootObs = new MutationObserver(function(muts){
var need = false;
for(var i=0;i<muts.length && !need;i++){
var m = muts[i];
for(var j=0;j<m.addedNodes.length;j++){
var n = m.addedNodes[j];
if(n.nodeType===1){
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
}
}
}
if(need) rescanScores();
});
rootObs.observe(document.body, {childList:true, subtree:true});
}catch(e){}
function refreshDoneMarks(){
try{
if(typeof STATE === 'undefined' || !STATE.progress) return;
document.querySelectorAll('.psel-card').forEach(function(c){
var id = c.dataset.id || c.dataset.progCard;
if(!id) return;
var pct = +STATE.progress[id] || 0;
if(!c.querySelector('.psel-done')){
var s = document.createElement('span');
s.className = 'psel-done';
s.setAttribute('title','Прочитано');
s.innerHTML = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
c.appendChild(s);
}
c.classList.toggle('done', pct >= 50);
});
}catch(e){}
}
try{
if(typeof window.refreshProgressUI === 'function'){
var _origRP = window.refreshProgressUI;
window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
}
}catch(e){}
setTimeout(refreshDoneMarks, 600);
setTimeout(refreshDoneMarks, 1800);
window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
document.addEventListener('click', function(e){
var card = e.target.closest && e.target.closest('.psel-card');
if(!card) return;
var id = card.dataset.id;
if(!id) return;
setTimeout(function(){
var sec = document.getElementById('sec-' + id);
if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
}, 60);
});
})();
</script>
</body>
</html>