Files
Learn_System/frontend/textbooks/algebra_11_ch2.html
T
Maxim Dolgolyov 660e7e2747 feat(gamification): Phase 1 — full kill-switch + textbook XP wrapping
Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.

Phase 1 closes every hole.

Backend (source of truth):
  • migration 029 seeds feature_gamification_enabled=1
  • new isGamificationEnabled() helper in gamification/_shared.js with a
    30s cache + invalidateGamificationCache() for instant admin toggles
  • awardXP / awardCoins / updateStreak / unlockAchievement /
    checkAchievements all bail out when the flag is off
  • /api/gamification/* and /api/shop/* (user routes) return 404 when
    disabled; admin routes remain open so the switch itself is reachable
  • adminController.updateFeatures gains 'gamification' in the allow-list
    and invalidates the cache on flip

Frontend:
  • LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
    so xp.js + applyCosmetics can bail without a round-trip
  • xp.js load/add/flush become no-ops when the flag is off
  • applyCosmetics skips the round-trip when off
  • CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
    .xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
    hook for future blocks

Textbooks (Variant 2 of the plan):
  • backend/scripts/wrap_textbook_xp.py — idempotent script that adds
    data-gamified to 167 XP tags across 63 textbook files (chapters +
    hubs, all subjects/grades). Single CSS rule now hides everything.

Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:43:24 +03:00

2301 lines
145 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 · Глава 2 · «Показательная функция»</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:#7c3aed; --pri2:#6d28d9; --pri-soft:#ede9fe;
--acc:#8b5cf6; --acc2:#7c3aed; --acc-soft:#f3e8ff;
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
}
.dark{--bg:#0a0814; --card:#15101f; --card-soft:#1a1224; --text:#f5f0ff; --ink:#f5f0ff; --muted:#a094c5; --border:#2a1f4a}
*{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,#3b0764 0%,#7c3aed 55%,#a78bfa 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(167,139,250,.22);min-height:130px}
.hdr::before{content:'ГЛАВА 2';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:'e&#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-p4"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
.sec[id="sec-p5"]{ --sec-acc:#f59e0b; --sec-acc-d:#d97706; --sec-acc-soft:#fef9c3; }
.sec[id="sec-p6"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
.sec[id="sec-final2"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
.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}
.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(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 · Глава 2</h1>
<div class="hdr-sub">Функция $y=a^x$ · показательные уравнения · показательные неравенства</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>Изучаем показательную функцию $y = a^x$ и её свойства. Решаем <b>показательные уравнения</b> четырьмя методами (приведение к одному основанию, замена, вынесение, графически) и <b>показательные неравенства</b> с учётом монотонности.</p>
<div class="hero-row">
<button class="btn-primary" onclick="goTo('p4')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 4</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-p4" class="sec" data-watermark="a^x"><div class="sec-header"><span class="sec-num">§ 4</span><h2 class="sec-h">Показательная функция</h2></div><div id="p4-body"></div></section>
<section id="sec-p5" class="sec" data-watermark="="><div class="sec-header"><span class="sec-num">§ 5</span><h2 class="sec-h">Показательные уравнения</h2></div><div id="p5-body"></div></section>
<section id="sec-p6" class="sec" data-watermark=">"><div class="sec-header"><span class="sec-num">§ 6</span><h2 class="sec-h">Показательные неравенства</h2></div><div id="p6-body"></div></section>
<section id="sec-final2" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#7c3aed,#8b5cf6)"></span><h2 class="sec-h">Финал главы</h2></div><div id="final2-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» · Глава 2 · «Показательная функция» · 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:'p4', progress:{}, achievements:new Map(), xp:0, level:1 };
const TOTAL_PARAS = 4;
const _TB_SLUG = 'algebra-11-ch2';
const PARAS = [
{ id:'p4', num:'§ 4', name:'Показательная функция', sub:'$y = a^x$' },
{ id:'p5', num:'§ 5', name:'Показательные уравнения', sub:'$a^{f(x)} = a^{g(x)}$' },
{ id:'p6', num:'§ 6', name:'Показательные неравенства', sub:'$a^x > b$' },
{ id:'final2', num:'★', name:'Финал главы', sub:'Итоги · боссы главы 2', 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:"Начало главы 2!",
p5_done:"Показательные уравнения освоены!",
p6_done:"Показательные неравенства освоены!",
ch2_done:"Глава 2 пройдена!"
};
function loadProgress(){
try{
const s=localStorage.getItem('algebra11_ch2_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
const a=localStorage.getItem('algebra11_ch2_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_ch2_progress', JSON.stringify(STATE.progress));
localStorage.setItem('algebra11_ch2_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-ch2-'+(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 = { p4:()=>buildP4(), p5:()=>buildP5(), p6:()=>buildP6(), final2:()=>buildFinal2() };
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 = {
p4:{title:"Шпаргалка §4",rows:[["Вид","$y = a^x$, $a > 0$, $a \\\\ne 1$"],["D(f)","$x \\\\in \\\\mathbb{R}$"],["E(f)","$y > 0$ (всегда положит.)"],["$a > 1$","возрастает"],["$0 < a < 1$","убывает"]]},
p5:{title:"Шпаргалка §5",rows:[["Базовый","$a^{f(x)} = a^{g(x)} \\\\iff f(x) = g(x)$"],["Замена","$t = a^x$, $t > 0$"],["Вынес.","$a^x(\\\\ldots) = 0$ — разделить"],["Графич.","пересечение графиков"]]},
p6:{title:"Шпаргалка §6",rows:[["$a > 1$","$a^{f} > a^{g} \\\\iff f > g$"],["$0 < a < 1$","$a^{f} > a^{g} \\\\iff f < g$ (знак меняется!)"],["Замена","$t = a^x$, $t > 0$"]]},
final2:{title:"Финал главы 2",rows:[["§§46","теория главы 2"],["Награда","+50 XP"]]}
};
const TIPS=[
{sec:'p4',html:"График $y = a^x$ всегда проходит через $(0; 1)$ и горизонтальная асимптота — ось $x$."},
{sec:'p5',html:"Приведи к одному основанию или сделай замену $t = a^x$ (где $t > 0$)."},
{sec:'p6',html:"При $0 < a < 1$ знак неравенства <b>меняется на противоположный</b> при отбрасывании оснований."},
{sec:'final2',html:"Финал главы 2 — синтез показательной функции и уравнений."}
];
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_ch2_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_ch2_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>';
}
/* === 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 = {p4:'\xA74',p5:'\xA75',p6:'\xA76',final2:'Финал'};
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 buildP4(){
const box = document.getElementById('p4-body');
let html = '';
/* === ТЕОРИЯ === */
html += makeCard('theory', 'Определение и графики', '4.1', `
<p>Функция вида $y = a^x$, где $a > 0$, $a \\ne 1$, называется <b>показательной</b>.</p>
<p>Условия $a > 0$ и $a \\ne 1$ — обязательны:</p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li>при $a \\le 0$ степень $a^x$ не определена для большинства $x$ (например, $(-2)^{1/2} = \\sqrt{-2}$ не существует в $\\mathbb{R}$);</li>
<li>при $a = 1$ получаем $y = 1^x = 1$ — постоянная функция, лишённая показательного характера.</li>
</ul>
<p>Примеры показательных функций:</p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li>$y = 2^x$, $y = 3^x$, $y = 10^x$ — основания больше единицы;</li>
<li>$y = \\left(\\dfrac{1}{2}\\right)^x$, $y = (0{,}3)^x$ — основания между нулём и единицей;</li>
<li>$y = e^x$, где $e \\approx 2{,}718$ — <b>натуральная экспонента</b> (играет центральную роль в анализе).</li>
</ul>
<p>График показательной функции:</p>
<ul style="margin:8px 0 8px 22px;line-height:1.75">
<li>при $a > 1$ — <b>возрастающая</b> кривая, круто стремящаяся вверх при $x \\to +\\infty$;</li>
<li>при $0 < a < 1$ — <b>убывающая</b> кривая, круто стремящаяся вверх при $x \\to -\\infty$.</li>
</ul>
<p>Все графики $y = a^x$ проходят через точку $(0; 1)$ — ведь $a^0 = 1$ при любом $a > 0$.</p>`);
html += makeCard('rule', 'Свойства показательной функции', '4.2', `
<p>Сведём все ключевые свойства функции $y = a^x$ в одну таблицу — отдельно для двух случаев $a > 1$ и $0 < a < 1$:</p>
<div style="overflow-x:auto;margin:10px 0">
<table style="width:100%;border-collapse:collapse;font-size:.88rem">
<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;color:#1d4ed8">$a > 1$</th>
<th style="padding:8px;border:1px solid var(--border);text-align:left;color:#c2410c">$0 < a < 1$</th>
</tr>
</thead>
<tbody>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$D(y)$ — область определения</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">$\\mathbb{R}$ — вся числовая прямая</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>$E(y)$ — область значений</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">$(0; +\\infty)$ — только положительные значения</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>Нули</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">нет — график не пересекает ось $Ox$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>Знакопостоянство</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">$y > 0$ при всех $x \\in \\mathbb{R}$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>Монотонность</b></td><td style="padding:7px;border:1px solid var(--border)"><b>возрастает</b> на $\\mathbb{R}$</td><td style="padding:7px;border:1px solid var(--border)"><b>убывает</b> на $\\mathbb{R}$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>Точка пересечения с $Oy$</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">$(0; 1)$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>Асимптота</b></td><td style="padding:7px;border:1px solid var(--border)" colspan="2" align="center">$y = 0$ (ось абсцисс)</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>При $x \\to +\\infty$</b></td><td style="padding:7px;border:1px solid var(--border)">$y \\to +\\infty$</td><td style="padding:7px;border:1px solid var(--border)">$y \\to 0$</td></tr>
<tr><td style="padding:7px;border:1px solid var(--border)"><b>При $x \\to -\\infty$</b></td><td style="padding:7px;border:1px solid var(--border)">$y \\to 0$</td><td style="padding:7px;border:1px solid var(--border)">$y \\to +\\infty$</td></tr>
</tbody>
</table>
</div>
<p><b>Ключевая идея:</b> два графика $y = a^x$ и $y = (1/a)^x$ симметричны относительно оси $Oy$, потому что $(1/a)^x = a^{-x}$.</p>`);
html += makeCard('example', 'Показательная функция в реальной жизни', '4.3', `
<p>Показательная функция описывает <b>огромное число</b> процессов в природе и технике — везде, где скорость изменения пропорциональна текущему значению величины.</p>
<ul style="margin:8px 0 8px 22px;line-height:1.85">
<li><b>Радиоактивный распад:</b> $N(t) = N_0 \\cdot 2^{-t/T}$, где $T$ — период полураспада (для $^{14}\\text{C}$ это $\\approx 5730$ лет — основа радиоуглеродного датирования).</li>
<li><b>Сложные проценты (банковский вклад):</b> $S = S_0 (1 + r)^n$, где $r$ — ставка, $n$ — число лет.</li>
<li><b>Рост популяции:</b> $P(t) = P_0 \\cdot e^{kt}$ (модель Мальтуса), описывает рост числа бактерий, клеток в чашке Петри.</li>
<li><b>Барометрическая формула:</b> $p(h) = p_0 \\cdot e^{-h/H}$ — атмосферное давление убывает с высотой по показательному закону.</li>
<li><b>Закон охлаждения Ньютона:</b> $T(t) = T_{ср} + (T_0 - T_{ср}) \\cdot e^{-kt}$ — кофе в чашке остывает по показательному закону.</li>
<li><b>Разряд конденсатора:</b> $U(t) = U_0 \\cdot e^{-t/(RC)}$ — основа всей радиотехники.</li>
</ul>
<p>Поэтому изучение функции $y = a^x$ — это <b>фундамент</b> для физики, химии, биологии, экономики и информатики.</p>`);
/* === ИНТЕРАКТИВЫ === */
/* IV1 — двухпанельный сравнительный визуализатор */
html += `<div class="wg" id="p4-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Двухпанельный визуализатор $y = a^x$</div></div>
<div class="wg-help">Меняй основание $a$ ползунком — наблюдай, как меняется характер функции. <b>Левая панель</b> для $a > 1$ (возрастающая, синий), <b>правая</b> — для $0 < a < 1$ (убывающая, оранжевый). Snap-точки: $a \\in \\{0{,}1;\\ 0{,}25;\\ 0{,}5;\\ 1;\\ 2;\\ e;\\ 3;\\ 10\\}$. Точка $(0; 1)$ — всегда на графике.</div>
<div class="sliders">
<label>Основание $a$ = <b id="p4-iv1-a">2</b><input type="range" id="p4-iv1-sa" min="0.1" max="10" step="0.05" value="2"></label>
</div>
<div style="background:var(--card);border-radius:9px;padding:10px;text-align:center"><svg id="p4-iv1-svg" viewBox="0 0 720 360" width="100%" style="max-width:720px;height:auto"></svg></div>
<div id="p4-iv1-desc" style="margin-top:10px;padding:12px 14px;background:var(--sec-acc-soft);border-radius:9px;font-size:.92rem;line-height:1.6;min-height:80px"></div>
</div>`;
/* IV2 — калькулятор y = a^x */
html += `<div class="wg" id="p4-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $y = a^x$</div></div>
<div class="wg-help">Введи основание $a$ ($a > 0$, $a \\ne 1$) и показатель $x$. Получи $y = a^x$ и характер функции. Под результатом — мини-график с отметкой точки $(x; y)$.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:8px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p4-iv2-a" class="tinp" style="width:90px;text-align:center" value="2" step="0.1">
<span style="font-family:'JetBrains Mono',monospace">$x$ =</span>
<input type="number" id="p4-iv2-x" class="tinp" style="width:90px;text-align:center" value="3" step="0.1">
<button class="btn primary" id="p4-iv2-go">Вычислить $y = a^x$</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-qa="2" style="padding:4px 10px;font-size:.82rem">$a = 2$</button>
<button class="btn" data-qa="3" style="padding:4px 10px;font-size:.82rem">$a = 3$</button>
<button class="btn" data-qa="10" style="padding:4px 10px;font-size:.82rem">$a = 10$</button>
<button class="btn" data-qa="2.71828" style="padding:4px 10px;font-size:.82rem">$a = e$</button>
</div>
<div id="p4-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;min-height:60px;line-height:1.7;text-align:center"></div>
<div style="text-align:center;margin-top:8px"><svg id="p4-iv2-mini" viewBox="0 0 240 140" width="240" height="140" style="background:var(--card-soft);border-radius:8px"></svg></div>
<div class="feedback" id="p4-iv2-fb"></div>
</div>`;
/* IV3 — квикфайр «Возрастает или убывает?» */
html += `<div class="wg" id="p4-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Возрастает или убывает?</div></div>
<div class="wg-help">Для каждой показательной функции определи характер монотонности. 8 заданий.</div>
<div class="score-display"><span>Задача <b id="p4-iv3-i">1</b> / 8</span><span>Очки: <b id="p4-iv3-s">0</b> / 8</span></div>
<div id="p4-iv3-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 id="p4-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:420px;margin:0 auto"></div>
<div class="feedback" id="p4-iv3-fb"></div>
<div class="actions" style="justify-content:center"><button class="btn" id="p4-iv3-restart">Начать заново</button></div>
</div>`;
/* IV4 — тренажёр значений */
html += `<div class="wg" id="p4-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр значений $a^x$</div></div>
<div class="wg-help">Вычисли значение $a^x$ (десятичное до 4 знаков, допуск $\\pm 0{,}05$). 6 задач.</div>
<div class="score-display"><span>Задача <b id="p4-iv4-i">1</b> / 6</span><span>Очки: <b id="p4-iv4-s">0</b> / 6</span></div>
<div id="p4-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="p4-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.0001">
<button class="btn primary" id="p4-iv4-go">Проверить</button>
<button class="btn" id="p4-iv4-start">Заново</button>
</div>
<div class="feedback" id="p4-iv4-fb"></div>
</div>`;
html += secNavFor('p4');
html += readButton('p4');
box.innerHTML = html;
renderMath(box);
/* === IV1 — двухпанельный визуализатор === */
(function(){
const sa = document.getElementById('p4-iv1-sa');
const aL = document.getElementById('p4-iv1-a');
const svg = document.getElementById('p4-iv1-svg');
const desc = document.getElementById('p4-iv1-desc');
const E = Math.E;
const SNAP = [0.1, 0.25, 0.5, 1, 2, E, 3, 10];
const seen = new Set();
let _done = false;
const COL_GROW = '#2563eb';
const COL_DECAY = '#ea580c';
const COL_NEUTRAL = '#94a3b8';
const COL_POINT01 = '#10b981';
const COL_POINT1A = '#dc2626';
function aLabel(a){
if(Math.abs(a - E) < 0.03) return 'e \\approx 2{,}72';
if(Math.abs(a - Math.round(a)) < 0.005) return String(Math.round(a));
if(Math.abs(a - 0.5) < 0.005) return '\\tfrac{1}{2}';
if(Math.abs(a - 0.25) < 0.005) return '\\tfrac{1}{4}';
if(Math.abs(a - 0.1) < 0.005) return '\\tfrac{1}{10}';
return (+a.toFixed(2)).toString();
}
function aPlain(a){
if(Math.abs(a - E) < 0.03) return 'e';
return (+a.toFixed(2)).toString();
}
function drawPanel(active, isGrow, a, ox){
const W = 360, H = 360, pad = 28;
const xmin = -3, xmax = 3, ymin = -0.5, ymax = 8;
const ax = axes2D(W, H, pad, xmin, xmax, ymin, ymax);
// сдвинуть всё содержимое axes2D на ox по X через обёртку <g transform>
let g = '<g transform="translate('+ox+',0)">';
g += ax.content;
const col = isGrow ? COL_GROW : COL_DECAY;
const title = isGrow ? 'a > 1 \\u2014 возрастающая' : '0 < a < 1 \\u2014 убывающая';
// заголовок панели
g += '<text x="'+(W/2)+'" y="16" font-family="Inter,sans-serif" font-size="13" font-weight="700" text-anchor="middle" fill="'+(active?col:COL_NEUTRAL)+'">'
+ (isGrow ? 'a &gt; 1 — возрастающая' : '0 &lt; a &lt; 1 — убывающая')
+ '</text>';
if(!active){
// серая заглушка
g += '<rect x="'+pad+'" y="'+(pad+8)+'" width="'+(W-2*pad)+'" height="'+(H-2*pad-8)+'" fill="#f1f5f9" opacity=".55"/>';
g += '<text x="'+(W/2)+'" y="'+(H/2)+'" font-family="Inter,sans-serif" font-size="12" font-weight="600" text-anchor="middle" fill="#94a3b8">';
g += isGrow ? 'выбери a &gt; 1' : 'выбери 0 &lt; a &lt; 1';
g += '</text>';
g += '</g>';
return g;
}
// асимптота y = 0
g += asymptote('h', 0, ax.toX, ax.toY, xmin, xmax, ymin, ymax, '#94a3b8');
// график y = a^x
g += plotFunc(x => Math.pow(a, x), xmin, xmax, ax.toX, ax.toY, col, 240);
// точка (0; 1) — зелёная
g += pointWithDrop(0, 1, ax.toX, ax.toY, COL_POINT01, '(0; 1)');
// точка (1; a) — красная (если a в пределах ymax)
if(a > 0 && a <= ymax){
g += pointWithDrop(1, a, ax.toX, ax.toY, COL_POINT1A, '(1; '+aPlain(a)+')');
}
g += '</g>';
return g;
}
function drawConstPanel(a){
// обе панели показывают серую линию y = 1
const W = 720, H = 360, pad = 28;
const xmin = -3, xmax = 3, ymin = -0.5, ymax = 8;
let out = '';
// левая
let leftAx;
{
const ax = axes2D(360, H, pad, xmin, xmax, ymin, ymax);
leftAx = ax;
let g = '<g transform="translate(0,0)">' + ax.content;
g += '<text x="180" y="16" font-family="Inter,sans-serif" font-size="13" font-weight="700" text-anchor="middle" fill="'+COL_NEUTRAL+'">a = 1: y = 1 константа</text>';
g += plotFunc(x => 1, xmin, xmax, ax.toX, ax.toY, COL_NEUTRAL, 60);
g += pointWithDrop(0, 1, ax.toX, ax.toY, COL_POINT01, '(0; 1)');
g += '</g>';
out += g;
}
// правая
{
const ax = axes2D(360, H, pad, xmin, xmax, ymin, ymax);
let g = '<g transform="translate(360,0)">' + ax.content;
g += '<text x="180" y="16" font-family="Inter,sans-serif" font-size="13" font-weight="700" text-anchor="middle" fill="'+COL_NEUTRAL+'">a = 1 — не показательная</text>';
g += plotFunc(x => 1, xmin, xmax, ax.toX, ax.toY, COL_NEUTRAL, 60);
g += pointWithDrop(0, 1, ax.toX, ax.toY, COL_POINT01, '(0; 1)');
g += '</g>';
out += g;
}
return out;
}
function describe(a){
if(Math.abs(a - 1) < 0.01){
return '<b>$a = 1$:</b> функция $y = 1^x = 1$ — постоянная, <b>не является</b> показательной (нарушено условие $a \\ne 1$).';
}
if(a > 1){
const aTxt = aLabel(a);
return '<b>$a = '+aTxt+' > 1$ — функция возрастает.</b><br>'
+ '$D = \\mathbb{R}$, $E = (0; +\\infty)$. Проходит через $(0; 1)$ и $(1; '+aPlain(a)+')$.<br>'
+ 'При $x \\to +\\infty$: $y \\to +\\infty$. При $x \\to -\\infty$: $y \\to 0$ (асимптота $y = 0$).';
}
// 0 < a < 1
const aTxt = aLabel(a);
return '<b>$a = '+aTxt+'$, $0 < a < 1$ — функция убывает.</b><br>'
+ '$D = \\mathbb{R}$, $E = (0; +\\infty)$. Проходит через $(0; 1)$ и $(1; '+aPlain(a)+')$.<br>'
+ 'При $x \\to +\\infty$: $y \\to 0$ (асимптота $y = 0$). При $x \\to -\\infty$: $y \\to +\\infty$.';
}
function draw(){
let a = +sa.value;
a = snapToValue(a, SNAP, 0.06);
aL.textContent = aLabel(a);
let svgContent = '';
if(Math.abs(a - 1) < 0.01){
svgContent = drawConstPanel(a);
} else if(a > 1){
svgContent += drawPanel(true, true, a, 0);
svgContent += drawPanel(false, false, a, 360);
} else {
svgContent += drawPanel(false, true, a, 0);
svgContent += drawPanel(true, false, a, 360);
}
// разделительная линия между панелями
svgContent += '<line x1="360" y1="6" x2="360" y2="354" stroke="#e2e8f0" stroke-width="1.5" stroke-dasharray="4 4"/>';
svg.innerHTML = svgContent;
desc.innerHTML = describe(a);
renderMath(desc);
const key = (+a.toFixed(2)).toString();
seen.add(key);
if(!_done && seen.size >= 6){ _done = true; addXp(10, 'p4-iv1'); bumpProgress('p4', 15); }
}
sa.addEventListener('input', draw);
draw();
})();
/* === IV2 — калькулятор y = a^x === */
(function(){
const aI = document.getElementById('p4-iv2-a');
const xI = document.getElementById('p4-iv2-x');
const go = document.getElementById('p4-iv2-go');
const out = document.getElementById('p4-iv2-out');
const fb = document.getElementById('p4-iv2-fb');
const mini = document.getElementById('p4-iv2-mini');
const used = new Set();
let _done = false;
document.querySelectorAll('#p4-iv2 [data-qa]').forEach(b => {
b.addEventListener('click', () => { aI.value = b.dataset.qa; calc(); });
});
function drawMini(a, xPt){
const W = 240, H = 140, pad = 16;
const xmin = -3, xmax = 3;
// подобрать ymax
let ymax = Math.max(1.5, Math.pow(a, 2), Math.pow(a, -2));
ymax = Math.min(ymax, 10);
const ymin = -0.3;
const ax = axes2D(W, H, pad, xmin, xmax, ymin, ymax);
let g = ax.content;
const col = a > 1 ? '#2563eb' : '#ea580c';
g += asymptote('h', 0, ax.toX, ax.toY, xmin, xmax, ymin, ymax, '#94a3b8');
g += plotFunc(x => Math.pow(a, x), xmin, xmax, ax.toX, ax.toY, col, 160);
g += pointWithDrop(0, 1, ax.toX, ax.toY, '#10b981', '');
// точка (x; a^x), если в пределах
const yPt = Math.pow(a, xPt);
if(xPt >= xmin && xPt <= xmax && yPt >= ymin && yPt <= ymax){
g += pointWithDrop(xPt, yPt, ax.toX, ax.toY, '#dc2626', '');
}
mini.innerHTML = g;
}
function calc(){
const a = parseFloat(aI.value);
const x = parseFloat(xI.value);
if(!isFinite(a) || !isFinite(x)){ feedback(fb, false, '&#10007; Введи числа $a$ и $x$.'); return; }
if(a <= 0){ feedback(fb, false, '&#10007; Основание должно быть положительным: $a > 0$.'); return; }
if(Math.abs(a - 1) < 1e-9){ feedback(fb, false, '&#10007; При $a = 1$ функция не является показательной.'); return; }
const y = Math.pow(a, x);
const sign = a > 1 ? 'возрастающая' : 'убывающая';
const signCol = a > 1 ? '#1d4ed8' : '#c2410c';
out.innerHTML = '<div style="font-size:1.05rem;margin-bottom:6px">$y = '+aI.value+'^{'+xI.value+'} = <b style="color:'+signCol+'">'+(+y.toFixed(4))+'</b>$</div>'
+ '<div style="color:'+signCol+';font-weight:700">Функция $y = '+aI.value+'^x$ — '+sign+'</div>';
renderMath(out);
feedback(fb, true, '&#10003; Готово.');
drawMini(a, x);
const key = (+a.toFixed(3))+'_'+(+x.toFixed(3));
used.add(key);
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p4-iv2'); bumpProgress('p4', 15); }
}
go.addEventListener('click', calc);
aI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
xI.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); });
calc();
})();
/* === IV3 — квикфайр «Возрастает или убывает?» === */
(function(){
const Q = [
{ q:'$y = 2^x$', ans:0 },
{ q:'$y = (0{,}5)^x$', ans:1 },
{ q:'$y = \\left(\\dfrac{1}{3}\\right)^x$', ans:1 },
{ q:'$y = 10^x$', ans:0 },
{ q:'$y = e^x$', ans:0 },
{ q:'$y = \\left(\\dfrac{1}{2}\\right)^x$', ans:1 },
{ q:'$y = 5^x$', ans:0 },
{ q:'$y = (0{,}1)^x$', ans:1 },
];
const OPTS = ['Возрастает', 'Убывает'];
let i = 0, score = 0;
const qEl = document.getElementById('p4-iv3-q');
const oEl = document.getElementById('p4-iv3-opts');
const fb = document.getElementById('p4-iv3-fb');
const iEl = document.getElementById('p4-iv3-i');
const sEl = document.getElementById('p4-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p4-iv3'); bumpProgress('p4', 25); }
else if(score >= 5){ addXp(8, 'p4-iv3'); bumpProgress('p4', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = 'Какая монотонность у функции ' + item.q + ' ?';
oEl.innerHTML = OPTS.map((o, k) => '<button class="btn primary" data-k="'+k+'">'+o+'</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
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; Неверно. Правильно: <b>'+OPTS[item.ans]+'</b>. Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1100);
});
});
}
document.getElementById('p4-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* === IV4 — тренажёр значений === */
(function(){
const Q = [
{ q:'$2^3 = \\,?$', ans:8, hint:'$2 \\cdot 2 \\cdot 2 = 8$' },
{ q:'$3^{-2} = \\,?$ (десятичная)', ans:1/9, hint:'$3^{-2} = \\dfrac{1}{9} \\approx 0{,}1111$' },
{ q:'$\\left(\\dfrac{1}{2}\\right)^4 = \\,?$ (десятичная)', ans:0.0625, hint:'$\\dfrac{1}{16} = 0{,}0625$' },
{ q:'$10^{1{,}5} = \\,?$', ans:31.6228, hint:'$10^{3/2} = 10\\sqrt{10} \\approx 31{,}62$' },
{ q:'$4^{0{,}5} = \\,?$', ans:2, hint:'$4^{1/2} = \\sqrt{4} = 2$' },
{ q:'$9^{1{,}5} = \\,?$', ans:27, hint:'$9^{3/2} = (\\sqrt{9})^3 = 3^3 = 27$' },
];
let i = 0, score = 0;
function show(){
const qEl = document.getElementById('p4-iv4-q');
const iEl = document.getElementById('p4-iv4-i');
const sEl = document.getElementById('p4-iv4-s');
const fb = document.getElementById('p4-iv4-fb');
const ansI = document.getElementById('p4-iv4-ans');
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(score === Q.length){ addXp(15, 'p4-iv4'); bumpProgress('p4', 25); }
else if(score >= 4){ addXp(8, 'p4-iv4'); bumpProgress('p4', 15); }
return;
}
iEl.textContent = (i+1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
ansI.value = '';
renderMath(qEl);
fb.style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p4-iv4-fb');
const raw = document.getElementById('p4-iv4-ans').value.replace(',', '.');
const ans = parseFloat(raw);
if(isNaN(ans)){ feedback(fb, false, '&#10007; Введи число.'); return; }
// допуск: либо абсолютный 0.05, либо относительный 1% для крупных значений
const tol = Math.max(0.05, Math.abs(Q[i].ans) * 0.01);
if(Math.abs(ans - Q[i].ans) < tol){
score++;
feedback(fb, true, '&#10003; Верно! '+Q[i].hint+'. Дальше ▶');
} else {
feedback(fb, false, '&#10007; Неверно. Ответ $\\approx '+(+Q[i].ans.toFixed(4))+'$ ('+Q[i].hint+'). Дальше ▶');
}
document.getElementById('p4-iv4-s').textContent = score;
i++;
setTimeout(show, 1400);
}
document.getElementById('p4-iv4-go').addEventListener('click', go);
document.getElementById('p4-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p4-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p4');
}
function buildP5(){
const box = document.getElementById('p5-body');
let html = '';
/* === ТЕОРИЯ === */
html += makeCard('theory', 'Метод приведения к одному основанию', '5.1', `
<p><b>Показательным уравнением</b> называется уравнение, в котором переменная содержится в показателе степени.</p>
<p>Простейший вид: $a^{f(x)} = a^{g(x)}$, где $a > 0$, $a \\ne 1$.</p>
<p>Поскольку показательная функция $y = a^x$ <b>инъективна</b> (взаимно однозначна — каждое значение принимает ровно один раз), из равенства степеней с одинаковым основанием следует равенство показателей:</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$a^{f(x)} = a^{g(x)} \\;\\Leftrightarrow\\; f(x) = g(x)$ &nbsp;(при $a > 0$, $a \\ne 1$).
</p>
<p><b>Алгоритм решения</b>:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li>Привести обе части к степени с <b>одинаковым основанием</b>.</li>
<li>Приравнять показатели: $f(x) = g(x)$.</li>
<li>Решить полученное уравнение и записать корни.</li>
</ol>
<p><b>Пример.</b> $\\left(\\dfrac{1}{2}\\right)^{x-1} = 8$.</p>
<p>Приводим к основанию $2$: $\\left(\\dfrac{1}{2}\\right)^{x-1} = 2^{-(x-1)} = 2^{1-x}$, а $8 = 2^3$.</p>
<p>Получаем $2^{1-x} = 2^3 \\Rightarrow 1 - x = 3 \\Rightarrow x = -2$.</p>
<p><b>Ответ:</b> $x = -2$.</p>`);
html += makeCard('rule', 'Метод замены переменной', '5.2', `
<p>Уравнения вида $A \\cdot a^{2x} + B \\cdot a^x + C = 0$ сводятся к квадратному заменой $t = a^x$.</p>
<p><b>Важное условие:</b> $t = a^x > 0$ при любых $x$. Значит, после нахождения корней $t_1, t_2$ нужно <b>отбросить</b> неположительные значения.</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$A \\cdot a^{2x} + B \\cdot a^x + C = 0 \\;\\xrightarrow{\\;t = a^x > 0\\;}\\; A t^2 + B t + C = 0$
</p>
<p><b>Алгоритм</b>:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li>Заметить структуру: степени образуют пары $a^{2x} = (a^x)^2$.</li>
<li>Ввести замену $t = a^x$, $t > 0$.</li>
<li>Решить квадратное уравнение, проверить условие $t > 0$.</li>
<li>Для каждого подходящего $t$ найти $x$ из $a^x = t$, т.е. $x = \\log_a t$.</li>
</ol>
<p><b>Пример.</b> $9^x - 4 \\cdot 3^x + 3 = 0$.</p>
<p>Замечаем: $9^x = (3^2)^x = (3^x)^2$. Замена $t = 3^x$, $t > 0$.</p>
<p>$t^2 - 4t + 3 = 0 \\Rightarrow t = 1$ или $t = 3$ (оба положительны).</p>
<p>$3^x = 1 \\Rightarrow x = 0$; &nbsp; $3^x = 3 \\Rightarrow x = 1$.</p>
<p><b>Ответ:</b> $x_1 = 0,\\; x_2 = 1$.</p>`);
html += makeCard('example', 'Однородные уравнения и графический метод', '5.3', `
<p><b>Однородное уравнение</b> второй степени относительно $a^x$ и $b^x$ имеет вид</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$A \\cdot a^{2x} + B \\cdot (ab)^x + C \\cdot b^{2x} = 0$.
</p>
<p>Все слагаемые имеют «суммарную степень» $2x$. Делим обе части на $b^{2x}$ (это $> 0$, так что эквивалентность сохраняется):</p>
<p>$A \\cdot \\left(\\dfrac{a}{b}\\right)^{2x} + B \\cdot \\left(\\dfrac{a}{b}\\right)^x + C = 0$.</p>
<p>Замена $t = (a/b)^x$, $t > 0$ — снова получаем квадратное уравнение.</p>
<p><b>Пример.</b> $3 \\cdot 4^x - 7 \\cdot 6^x + 4 \\cdot 9^x = 0$.</p>
<p>Делим на $9^x$: $3 \\cdot (4/9)^x - 7 \\cdot (6/9)^x + 4 = 0$, то есть $3 (2/3)^{2x} - 7 (2/3)^x + 4 = 0$.</p>
<p>Замена $t = (2/3)^x$: $3t^2 - 7t + 4 = 0 \\Rightarrow t = 1$ или $t = 4/3$.</p>
<p>$t = 1 \\Rightarrow x = 0$. $t = 4/3 \\Rightarrow (2/3)^x = 4/3$ — корень $x = -1$ (так как $(2/3)^{-1} = 3/2 \\ne 4/3$… проверь: основание $2/3 < 1$, а $4/3 > 1$, значит $x < 0$).</p>
<hr style="margin:14px 0;border:none;border-top:1px solid var(--border)">
<p><b>Графический метод</b> применяется, когда не удаётся привести к одному основанию и не подходит замена. Решения — <b>абсциссы точек пересечения</b> графиков левой и правой частей.</p>
<p><b>Пример.</b> $2^x = 4 - x$. Строим $y_1 = 2^x$ (возрастает) и $y_2 = 4 - x$ (убывает). Они пересекаются ровно в одной точке (монотонности противоположны).</p>
<p>Подбор: $x = 1 \\Rightarrow 2 \\ne 3$, $x = 1{,}4 \\Rightarrow 2{,}64 \\approx 2{,}6$. Корень $x \\approx 1{,}39$ — только приближённый.</p>
<p><b>Вывод:</b> графический метод даёт лишь приблизительный ответ, но позволяет оценить число корней.</p>`);
/* === ИНТЕРАКТИВЫ === */
/* IV1 — пошаговый решатель */
html += `<div class="wg" id="p5-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Пошаговый решатель уравнений</div></div>
<div class="wg-help">Выбери задачу ползунком и нажимай «Следующий шаг ▶», чтобы открывать решение по одному шагу. Просмотри все 5 задач — получишь XP.</div>
<div class="sliders">
<label>Задача № <b id="p5-iv1-n">1</b> / 5<input type="range" id="p5-iv1-sn" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p5-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.08rem;text-align:center;margin-bottom:10px"></div>
<div id="p5-iv1-steps" style="display:flex;flex-direction:column;gap:8px;margin-bottom:10px"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p5-iv1-next">Следующий шаг ▶</button>
<button class="btn" id="p5-iv1-all">Показать все шаги</button>
<button class="btn" id="p5-iv1-reset">Скрыть шаги</button>
</div>
</div>`;
/* IV2 — калькулятор a^(kx+b) = c */
html += `<div class="wg" id="p5-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $a^{kx+b} = c$</div></div>
<div class="wg-help">Введи $a$, $k$, $b$, $c$ (целые). Калькулятор решит уравнение через логарифмирование и покажет шаги.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:8px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p5-iv2-a" class="tinp" style="width:70px;text-align:center" value="2" step="1">
<span style="font-family:'JetBrains Mono',monospace">$k$ =</span>
<input type="number" id="p5-iv2-k" class="tinp" style="width:70px;text-align:center" value="1" step="1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p5-iv2-b" class="tinp" style="width:70px;text-align:center" value="1" step="1">
<span style="font-family:'JetBrains Mono',monospace">$c$ =</span>
<input type="number" id="p5-iv2-c" class="tinp" style="width:70px;text-align:center" value="16" step="1">
<button class="btn primary" id="p5-iv2-go">Решить</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)">Примеры:</span>
<button class="btn" data-set="2,1,1,16" style="padding:4px 10px;font-size:.82rem">$2^{x+1}=16$</button>
<button class="btn" data-set="3,2,-1,27" style="padding:4px 10px;font-size:.82rem">$3^{2x-1}=27$</button>
<button class="btn" data-set="5,1,0,3" style="padding:4px 10px;font-size:.82rem">$5^x=3$</button>
<button class="btn" data-set="2,1,0,-4" style="padding:4px 10px;font-size:.82rem">$2^x=-4$</button>
</div>
<div id="p5-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;min-height:60px;line-height:1.8"></div>
<div class="feedback" id="p5-iv2-fb"></div>
</div>`;
/* IV3 — какой метод применить? */
html += `<div class="wg" id="p5-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Какой метод применить?</div></div>
<div class="wg-help">Определи подходящий метод решения уравнения. 6 заданий.</div>
<div class="score-display"><span>Задача <b id="p5-iv3-i">1</b> / 6</span><span>Очки: <b id="p5-iv3-s">0</b> / 6</span></div>
<div id="p5-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.12rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p5-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:520px;margin:0 auto"></div>
<div class="feedback" id="p5-iv3-fb"></div>
<div class="actions" style="justify-content:center"><button class="btn" id="p5-iv3-restart">Начать заново</button></div>
</div>`;
/* IV4 — тренажёр уравнений */
html += `<div class="wg" id="p5-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр уравнений</div></div>
<div class="wg-help">Реши уравнение и введи корень. Для уравнений с двумя корнями указано «больший» или «меньший». Допуск $\\pm 0{,}05$. 6 задач.</div>
<div class="score-display"><span>Задача <b id="p5-iv4-i">1</b> / 6</span><span>Очки: <b id="p5-iv4-s">0</b> / 6</span></div>
<div id="p5-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.12rem;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">$x$ =</span>
<input type="number" id="p5-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p5-iv4-go">Проверить</button>
<button class="btn" id="p5-iv4-start">Заново</button>
</div>
<div class="feedback" id="p5-iv4-fb"></div>
</div>`;
html += secNavFor('p5');
html += readButton('p5');
box.innerHTML = html;
renderMath(box);
/* === IV1 — пошаговый решатель === */
(function(){
const TASKS = [
{
q: '$2^{x+1} = 16$',
steps: [
'Приводим к одному основанию: $16 = 2^4$.',
'Получаем $2^{x+1} = 2^4$.',
'Приравниваем показатели: $x + 1 = 4$.',
'<b>Ответ:</b> $x = 3$.'
]
},
{
q: '$\\left(\\dfrac{1}{3}\\right)^{2x-1} = 27$',
steps: [
'Приводим к основанию $3$: $\\left(\\dfrac{1}{3}\\right)^{2x-1} = 3^{-(2x-1)} = 3^{1-2x}$, $27 = 3^3$.',
'Получаем $3^{1-2x} = 3^3$.',
'Приравниваем показатели: $1 - 2x = 3 \\Rightarrow -2x = 2$.',
'<b>Ответ:</b> $x = -1$.'
]
},
{
q: '$4^x = 32$',
steps: [
'Приводим к основанию $2$: $4^x = 2^{2x}$, $32 = 2^5$.',
'Получаем $2^{2x} = 2^5$.',
'Приравниваем показатели: $2x = 5$.',
'<b>Ответ:</b> $x = \\dfrac{5}{2} = 2{,}5$.'
]
},
{
q: '$9^x - 4 \\cdot 3^x + 3 = 0$',
steps: [
'Замечаем $9^x = (3^2)^x = (3^x)^2$. Делаем замену $t = 3^x$, $t > 0$.',
'Получаем квадратное: $t^2 - 4t + 3 = 0$.',
'По теореме Виета: $t_1 = 1$, $t_2 = 3$ — оба положительны.',
'Обратно: $3^x = 1 \\Rightarrow x = 0$; $\\; 3^x = 3 \\Rightarrow x = 1$.',
'<b>Ответ:</b> $x_1 = 0,\\; x_2 = 1$.'
]
},
{
q: '$5^{x+1} + 5^x = 30$',
steps: [
'Выносим $5^x$ за скобку: $5^x \\cdot (5 + 1) = 30$.',
'Получаем $6 \\cdot 5^x = 30 \\Rightarrow 5^x = 5$.',
'Приводим: $5^x = 5^1$, значит $x = 1$.',
'<b>Ответ:</b> $x = 1$.'
]
}
];
const sn = document.getElementById('p5-iv1-sn');
const nL = document.getElementById('p5-iv1-n');
const qEl = document.getElementById('p5-iv1-q');
const stepsEl = document.getElementById('p5-iv1-steps');
const nextBtn = document.getElementById('p5-iv1-next');
const allBtn = document.getElementById('p5-iv1-all');
const resetBtn = document.getElementById('p5-iv1-reset');
const seen = new Set();
let _done = false;
let cur = 0, shown = 0;
function render(){
const t = TASKS[cur];
nL.textContent = (cur + 1);
qEl.innerHTML = t.q;
stepsEl.innerHTML = t.steps.map((s, i) => {
const visible = i < shown;
const isLast = i === t.steps.length - 1 && visible;
const bg = isLast ? '#dcfce7' : 'var(--card)';
const brd = isLast ? '2px solid #16a34a' : '1px solid var(--border)';
return '<div data-i="'+i+'" style="padding:10px 12px;background:'+bg+';border:'+brd+';border-radius:8px;font-size:.95rem;display:'+(visible?'block':'none')+'"><span style="color:var(--muted);font-weight:700;margin-right:6px">Шаг '+(i+1)+':</span>'+s+'</div>';
}).join('');
renderMath(qEl);
renderMath(stepsEl);
// отметить просмотр последнего шага
if(shown >= t.steps.length){
seen.add(cur);
if(!_done && seen.size >= 5){ _done = true; addXp(10, 'p5-iv1'); bumpProgress('p5', 15); }
}
}
function load(n){ cur = Math.max(0, Math.min(TASKS.length - 1, n)); shown = 1; render(); }
sn.addEventListener('input', () => load((+sn.value) - 1));
nextBtn.addEventListener('click', () => {
if(shown < TASKS[cur].steps.length){ shown++; render(); }
});
allBtn.addEventListener('click', () => { shown = TASKS[cur].steps.length; render(); });
resetBtn.addEventListener('click', () => { shown = 1; render(); });
load(0);
})();
/* === IV2 — калькулятор a^(kx+b) = c === */
(function(){
const aI = document.getElementById('p5-iv2-a');
const kI = document.getElementById('p5-iv2-k');
const bI = document.getElementById('p5-iv2-b');
const cI = document.getElementById('p5-iv2-c');
const go = document.getElementById('p5-iv2-go');
const out = document.getElementById('p5-iv2-out');
const fb = document.getElementById('p5-iv2-fb');
const used = new Set();
let _done = false;
document.querySelectorAll('#p5-iv2 [data-set]').forEach(btn => {
btn.addEventListener('click', () => {
const v = btn.dataset.set.split(',');
aI.value = v[0]; kI.value = v[1]; bI.value = v[2]; cI.value = v[3];
calc();
});
});
function fmtFrac(n, d){
const g = gcd(Math.abs(Math.round(n)), Math.abs(Math.round(d)));
const sign = (n * d < 0) ? '-' : '';
const nn = Math.abs(Math.round(n)) / g;
const dd = Math.abs(Math.round(d)) / g;
if(dd === 1) return sign + nn;
return sign + '\\dfrac{' + nn + '}{' + dd + '}';
}
function calc(){
const a = parseFloat(aI.value);
const k = parseFloat(kI.value);
const b = parseFloat(bI.value);
const c = parseFloat(cI.value);
if(![a,k,b,c].every(isFinite)){ feedback(fb, false, '&#10007; Введи все четыре числа.'); return; }
if(a <= 0 || Math.abs(a - 1) < 1e-9){ feedback(fb, false, '&#10007; Основание $a$ должно быть $> 0$ и $\\ne 1$.'); return; }
if(k === 0){ feedback(fb, false, '&#10007; При $k = 0$ показатель не зависит от $x$.'); return; }
const eq = '$' + a + '^{' + (k===1?'':k) + 'x' + (b>0?'+'+b:(b<0?b:'')) + '} = ' + c + '$';
let html = '<div style="font-size:1.04rem;margin-bottom:8px;text-align:center">Уравнение: '+eq+'</div>';
if(c <= 0){
html += '<div style="color:#b91c1c;font-weight:700">Левая часть $a^{kx+b} > 0$ при любых $x$, а правая $= '+c+' \\le 0$.</div>';
html += '<div style="margin-top:6px;color:#b91c1c;font-weight:700">Уравнение <b>не имеет решений</b>.</div>';
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Решено: корней нет.');
used.add(aI.value+','+kI.value+','+bI.value+','+cI.value);
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p5-iv2'); bumpProgress('p5', 15); }
return;
}
// log_a(c) = ln c / ln a
const logAc = Math.log(c) / Math.log(a);
// целое ли logAc? — проверяем, является ли c степенью a
let intLog = null;
for(let p = -10; p <= 12; p++){
if(Math.abs(Math.pow(a, p) - c) < 1e-9){ intLog = p; break; }
}
html += '<div style="margin-bottom:6px">Приводим к одинаковому основанию или логарифмируем по основанию $'+a+'$:</div>';
if(intLog !== null){
html += '<div style="margin-bottom:6px">$'+c+' = '+a+'^{'+intLog+'}$, значит $'+a+'^{'+(k===1?'':k)+'x'+(b>0?'+'+b:(b<0?b:''))+'} = '+a+'^{'+intLog+'}$.</div>';
html += '<div style="margin-bottom:6px">Приравниваем показатели: $'+(k===1?'':k)+'x'+(b>0?'+'+b:(b<0?b:''))+' = '+intLog+'$.</div>';
const xNum = intLog - b;
// x = (intLog - b) / k
const xVal = xNum / k;
let ansTex;
if(Number.isInteger(xVal)){ ansTex = xVal.toString(); }
else { ansTex = fmtFrac(xNum, k); }
html += '<div style="font-weight:700;color:#15803d">$\\Rightarrow x = '+ansTex+' = '+(+xVal.toFixed(4))+'$</div>';
} else {
html += '<div style="margin-bottom:6px">$'+(k===1?'':k)+'x'+(b>0?'+'+b:(b<0?b:''))+' = \\log_{'+a+'} '+c+'$.</div>';
const xVal = (logAc - b) / k;
html += '<div style="margin-bottom:6px">$x = \\dfrac{\\log_{'+a+'} '+c+' - ('+b+')}{'+k+'} \\approx '+(+xVal.toFixed(4))+'$ (приближённо).</div>';
html += '<div style="font-weight:700;color:#1d4ed8">$\\Rightarrow x \\approx '+(+xVal.toFixed(4))+'$</div>';
}
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Решено.');
used.add(aI.value+','+kI.value+','+bI.value+','+cI.value);
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p5-iv2'); bumpProgress('p5', 15); }
}
go.addEventListener('click', calc);
[aI, kI, bI, cI].forEach(i => i.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
calc();
})();
/* === IV3 — какой метод применить? === */
(function(){
const METHODS = ['Одинаковое основание', 'Замена переменной', 'Однородное', 'Графический'];
const Q = [
{ q: '$5^{x-1} = 25$', ans: 0, hint: '$25 = 5^2$ — обе части к одному основанию.' },
{ q: '$4^x - 5 \\cdot 2^x + 4 = 0$', ans: 1, hint: '$4^x = (2^x)^2$ — замена $t = 2^x$.' },
{ q: '$3^x = x + 5$', ans: 3, hint: 'Слева показательная, справа линейная — только графически.' },
{ q: '$2^{x^2 - x} = 1$', ans: 0, hint: '$1 = 2^0$ — степени $2$.' },
{ q: '$3 \\cdot 4^x - 7 \\cdot 6^x + 4 \\cdot 9^x = 0$', ans: 2, hint: '$4 = 2^2$, $9 = 3^2$, $6 = 2 \\cdot 3$ — все слагаемые степени $2x$.' },
{ q: '$25^x + 5 \\cdot 5^x - 6 = 0$', ans: 1, hint: '$25^x = (5^x)^2$ — замена $t = 5^x$.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p5-iv3-q');
const oEl = document.getElementById('p5-iv3-opts');
const fb = document.getElementById('p5-iv3-fb');
const iEl = document.getElementById('p5-iv3-i');
const sEl = document.getElementById('p5-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p5-iv3'); bumpProgress('p5', 25); }
else if(score >= 4){ addXp(8, 'p5-iv3'); bumpProgress('p5', 15); }
return;
}
iEl.textContent = (i + 1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = 'Какой метод подходит для уравнения ' + item.q + ' ?';
oEl.innerHTML = METHODS.map((m, k) => '<button class="btn primary" data-k="'+k+'">'+m+'</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const k = +b.dataset.k;
if(k === item.ans){ score++; feedback(fb, true, '&#10003; Верно! '+item.hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: <b>'+METHODS[item.ans]+'</b>. '+item.hint+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p5-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* === IV4 — тренажёр уравнений === */
(function(){
const Q = [
{ q: '$2^x = 32$', ans: 5, hint: '$32 = 2^5$, поэтому $x = 5$.' },
{ q: '$3^{x-1} = 9$', ans: 3, hint: '$9 = 3^2 \\Rightarrow x - 1 = 2 \\Rightarrow x = 3$.' },
{ q: '$\\left(\\dfrac{1}{2}\\right)^x = 8$', ans: -3, hint: '$\\left(1/2\\right)^x = 2^{-x} = 2^3 \\Rightarrow x = -3$.' },
{ q: '$4^x = 8$', ans: 1.5, hint: '$2^{2x} = 2^3 \\Rightarrow 2x = 3 \\Rightarrow x = 1{,}5$.' },
{ q: '$9^x - 4 \\cdot 3^x + 3 = 0$ — введи <b>больший</b> корень', ans: 1, hint: 'Замена $t = 3^x$: $t = 1, t = 3$. Корни $x = 0$ и $x = 1$.' },
{ q: '$25^x - 6 \\cdot 5^x + 5 = 0$ — введи <b>меньший</b> корень', ans: 0, hint: 'Замена $t = 5^x$: $t = 1, t = 5$. Корни $x = 0$ и $x = 1$.' },
];
let i = 0, score = 0;
function show(){
const qEl = document.getElementById('p5-iv4-q');
const iEl = document.getElementById('p5-iv4-i');
const sEl = document.getElementById('p5-iv4-s');
const fb = document.getElementById('p5-iv4-fb');
const ansI = document.getElementById('p5-iv4-ans');
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(score === Q.length){ addXp(15, 'p5-iv4'); bumpProgress('p5', 25); }
else if(score >= 4){ addXp(8, 'p5-iv4'); bumpProgress('p5', 15); }
return;
}
iEl.textContent = (i + 1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
ansI.value = '';
renderMath(qEl);
fb.style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p5-iv4-fb');
const raw = document.getElementById('p5-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; Неверно. Ответ: $x = '+Q[i].ans+'$. '+Q[i].hint+' Дальше ▶');
}
document.getElementById('p5-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p5-iv4-go').addEventListener('click', go);
document.getElementById('p5-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p5-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p5');
}
function buildP6(){
const box = document.getElementById('p6-body');
let html = '';
/* === ТЕОРИЯ === */
html += makeCard('theory', 'Основное правило', '6.1', `
<p><b>Показательным неравенством</b> называется неравенство вида $a^{f(x)} > a^{g(x)}$ (или $<$, $\\ge$, $\\le$), где $a > 0$, $a \\ne 1$.</p>
<p>Поскольку функция $y = a^x$ <b>монотонна</b> (см. §4), при переходе от степеней к показателям всё зависит от того, возрастает она или убывает:</p>
<p style="text-align:center;padding:12px;background:var(--sec-acc-soft);border-radius:9px;margin:12px 0">
$a^{f(x)} > a^{g(x)} \\;\\Leftrightarrow\\; \\begin{cases} f(x) > g(x), & \\text{если } a > 1, \\\\ f(x) < g(x), & \\text{если } 0 < a < 1. \\end{cases}$
</p>
<p><b>Запомни</b>:</p>
<ul style="margin:6px 0 6px 22px;line-height:1.7">
<li>при $a > 1$ знак неравенства <b>сохраняется</b> (функция возрастает — порядок сохраняется);</li>
<li>при $0 < a < 1$ знак неравенства <b>меняется на противоположный</b> (функция убывает — порядок переворачивается).</li>
</ul>
<p>Это правило — прямое следствие монотонности показательной функции (см. §4).</p>`);
html += makeCard('rule', 'Алгоритм решения', '6.2', `
<p><b>Алгоритм</b> похож на алгоритм для уравнений из §5, но требует постоянного контроля знака неравенства:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li>Привести обе части к степени с <b>одинаковым основанием</b> $a$.</li>
<li>Сравнить значение $a$ с единицей и записать неравенство показателей с правильным знаком.</li>
<li>Решить полученное неравенство для $x$ и записать ответ интервалом.</li>
</ol>
<p><b>Пример&nbsp;1.</b> $2^{x-1} > 8$.</p>
<p>$8 = 2^3$, поэтому $2^{x-1} > 2^3$. Основание $a = 2 > 1$ — знак <b>сохраняется</b>: $x - 1 > 3 \\Rightarrow x > 4$.</p>
<p><b>Ответ:</b> $x \\in (4;\\;+\\infty)$.</p>
<p><b>Пример&nbsp;2.</b> $\\left(\\dfrac{1}{3}\\right)^x \\ge 9$.</p>
<p>$9 = 3^2 = \\left(\\dfrac{1}{3}\\right)^{-2}$. Тогда $\\left(\\dfrac{1}{3}\\right)^x \\ge \\left(\\dfrac{1}{3}\\right)^{-2}$. Основание $a = \\dfrac{1}{3} < 1$ — знак <b>меняется</b>: $x \\le -2$.</p>
<p><b>Ответ:</b> $x \\in (-\\infty;\\;-2]$.</p>`);
html += makeCard('example', 'Метод замены переменной', '6.3', `
<p>Если неравенство содержит $a^{2x}$ и $a^x$, делаем замену $t = a^x$, причём <b>обязательно $t > 0$</b>. Получаем алгебраическое неравенство (чаще квадратное), которое решаем методом интервалов.</p>
<p style="text-align:center;padding:10px;background:var(--sec-acc-soft);border-radius:9px;margin:10px 0">
$A \\cdot a^{2x} + B \\cdot a^x + C \\;\\gtrless\\; 0 \\;\\xrightarrow{\\;t = a^x > 0\\;}\\; A t^2 + B t + C \\;\\gtrless\\; 0$
</p>
<p><b>Алгоритм</b>:</p>
<ol style="margin:8px 0 8px 22px;line-height:1.75">
<li>Сделать замену $t = a^x$, $t > 0$.</li>
<li>Решить полученное неравенство для $t$ методом интервалов.</li>
<li>Пересечь полученное множество с условием $t > 0$.</li>
<li>Вернуться к $x$ из $a^x \\in [\\,t_1;\\,t_2\\,]$ с учётом монотонности.</li>
</ol>
<p><b>Пример.</b> $4^x - 5 \\cdot 2^x + 4 \\le 0$.</p>
<p>Замечаем $4^x = (2^x)^2$. Замена $t = 2^x > 0$: $t^2 - 5t + 4 \\le 0$. Корни $t = 1$, $t = 4$, значит $t \\in [1;\\;4]$ (оба положительны — условие выполнено).</p>
<p>Возвращаемся: $1 \\le 2^x \\le 4 \\Leftrightarrow 2^0 \\le 2^x \\le 2^2$. Основание $2 > 1$ — знак сохраняется: $0 \\le x \\le 2$.</p>
<p><b>Ответ:</b> $x \\in [0;\\;2]$.</p>`);
/* === ИНТЕРАКТИВЫ === */
/* IV1 — решатель показательных неравенств с шагами и числовой прямой */
html += `<div class="wg" id="p6-iv1">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Решатель показательных неравенств</div></div>
<div class="wg-help">Выбери задачу ползунком и нажимай «Следующий шаг ▶». В конце появится ответ на числовой прямой. Просмотри все 5 задач — получишь XP.</div>
<div class="sliders">
<label>Задача № <b id="p6-iv1-n">1</b> / 5<input type="range" id="p6-iv1-sn" min="1" max="5" step="1" value="1"></label>
</div>
<div id="p6-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.08rem;text-align:center;margin-bottom:10px"></div>
<div id="p6-iv1-steps" style="display:flex;flex-direction:column;gap:8px;margin-bottom:10px"></div>
<div id="p6-iv1-line" style="display:none;margin:10px 0;padding:10px;background:var(--card);border-radius:9px;border:1px solid var(--border);text-align:center"></div>
<div class="actions" style="justify-content:center">
<button class="btn primary" id="p6-iv1-next">Следующий шаг ▶</button>
<button class="btn" id="p6-iv1-all">Показать все шаги</button>
<button class="btn" id="p6-iv1-reset">Скрыть шаги</button>
</div>
</div>`;
/* IV2 — калькулятор a^(kx+b) > c */
html += `<div class="wg" id="p6-iv2">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Калькулятор $a^{kx+b} \\gtrless c$</div></div>
<div class="wg-help">Введи $a$, $k$, $b$, $c$ и выбери знак неравенства. Калькулятор подскажет, сохраняется или меняется знак, и выдаст ответ интервалом.</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;margin-bottom:8px">
<span style="font-family:'JetBrains Mono',monospace">$a$ =</span>
<input type="number" id="p6-iv2-a" class="tinp" style="width:70px;text-align:center" value="2" step="1">
<span style="font-family:'JetBrains Mono',monospace">$k$ =</span>
<input type="number" id="p6-iv2-k" class="tinp" style="width:70px;text-align:center" value="1" step="1">
<span style="font-family:'JetBrains Mono',monospace">$b$ =</span>
<input type="number" id="p6-iv2-b" class="tinp" style="width:70px;text-align:center" value="-1" step="1">
<span style="font-family:'JetBrains Mono',monospace">знак</span>
<select id="p6-iv2-sg" class="tinp" style="width:64px;text-align:center">
<option value="gt">&gt;</option>
<option value="ge">&ge;</option>
<option value="lt">&lt;</option>
<option value="le">&le;</option>
</select>
<span style="font-family:'JetBrains Mono',monospace">$c$ =</span>
<input type="number" id="p6-iv2-c" class="tinp" style="width:70px;text-align:center" value="8" step="1">
<button class="btn primary" id="p6-iv2-go">Решить</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)">Примеры:</span>
<button class="btn" data-set="2,1,-1,gt,8" style="padding:4px 10px;font-size:.82rem">$2^{x-1}>8$</button>
<button class="btn" data-set="3,1,1,lt,27" style="padding:4px 10px;font-size:.82rem">$3^{x+1}<27$</button>
<button class="btn" data-set="2,1,0,gt,-5" style="padding:4px 10px;font-size:.82rem">$2^x>-5$</button>
<button class="btn" data-set="2,1,0,lt,-1" style="padding:4px 10px;font-size:.82rem">$2^x<-1$</button>
</div>
<div id="p6-iv2-out" style="padding:12px 14px;background:var(--card);border-radius:9px;font-size:.96rem;min-height:60px;line-height:1.8"></div>
<div class="feedback" id="p6-iv2-fb"></div>
</div>`;
/* IV3 — знак сохраняется или меняется? */
html += `<div class="wg" id="p6-iv3">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 3</span><div class="wg-title">Сохраняется или меняется?</div></div>
<div class="wg-help">Глядя на основание степени, определи, что произойдёт со знаком неравенства при переходе к показателям. 8 заданий.</div>
<div class="score-display"><span>Задача <b id="p6-iv3-i">1</b> / 8</span><span>Очки: <b id="p6-iv3-s">0</b> / 8</span></div>
<div id="p6-iv3-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.12rem;margin-bottom:10px;text-align:center;min-height:54px"></div>
<div id="p6-iv3-opts" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:520px;margin:0 auto"></div>
<div class="feedback" id="p6-iv3-fb"></div>
<div class="actions" style="justify-content:center"><button class="btn" id="p6-iv3-restart">Начать заново</button></div>
</div>`;
/* IV4 — тренажёр неравенств */
html += `<div class="wg" id="p6-iv4">
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 4</span><div class="wg-title">Тренажёр неравенств</div></div>
<div class="wg-help">Реши неравенство и введи <b>границу</b> интервала ответа (например, для $x > 3$ ввести $3$; для $x \\in [0;\\,2]$ ввести любую границу — указано в задании). Допуск $\\pm 0{,}05$. 6 задач.</div>
<div class="score-display"><span>Задача <b id="p6-iv4-i">1</b> / 6</span><span>Очки: <b id="p6-iv4-s">0</b> / 6</span></div>
<div id="p6-iv4-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.12rem;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="p6-iv4-ans" class="tinp" style="width:120px;text-align:center" step="0.01">
<button class="btn primary" id="p6-iv4-go">Проверить</button>
<button class="btn" id="p6-iv4-start">Заново</button>
</div>
<div class="feedback" id="p6-iv4-fb"></div>
</div>`;
html += secNavFor('p6');
html += readButton('p6');
box.innerHTML = html;
renderMath(box);
/* === IV1 — решатель с шагами и числовой прямой === */
(function(){
/* line: {min, max, lo, hi, openLo, openHi, dirLeft, dirRight}
dirLeft/dirRight — стрелка уходит влево/вправо за границу видимой части */
const TASKS = [
{
q: '$2^x > 8$',
steps: [
'Приводим к одному основанию: $8 = 2^3$, поэтому $2^x > 2^3$.',
'Основание $a = 2 > 1$ — знак <b>сохраняется</b>: $x > 3$.',
'<b>Ответ:</b> $x \\in (3;\\;+\\infty)$.'
],
line: { min:-2, max:8, lo:3, hi:null, openLo:true, dirRight:true }
},
{
q: '$\\left(\\dfrac{1}{2}\\right)^x \\ge 4$',
steps: [
'Приводим к одному основанию: $4 = 2^2 = \\left(\\dfrac{1}{2}\\right)^{-2}$.',
'Получаем $\\left(\\dfrac{1}{2}\\right)^x \\ge \\left(\\dfrac{1}{2}\\right)^{-2}$.',
'Основание $a = \\dfrac{1}{2} < 1$ — знак <b>меняется</b>: $x \\le -2$.',
'<b>Ответ:</b> $x \\in (-\\infty;\\;-2]$.'
],
line: { min:-7, max:3, lo:null, hi:-2, openHi:false, dirLeft:true }
},
{
q: '$3^{x+1} < 27$',
steps: [
'Приводим к одному основанию: $27 = 3^3$, поэтому $3^{x+1} < 3^3$.',
'Основание $a = 3 > 1$ — знак <b>сохраняется</b>: $x + 1 < 3$.',
'Откуда $x < 2$.',
'<b>Ответ:</b> $x \\in (-\\infty;\\;2)$.'
],
line: { min:-3, max:7, lo:null, hi:2, openHi:true, dirLeft:true }
},
{
q: '$4^x - 5 \\cdot 2^x + 4 \\le 0$',
steps: [
'Замечаем $4^x = (2^x)^2$. Замена $t = 2^x$, $t > 0$.',
'Получаем квадратное: $t^2 - 5t + 4 \\le 0$. Корни $t = 1$, $t = 4$.',
'Решение по $t$: $t \\in [1;\\;4]$ (оба положительны).',
'Возвращаемся: $1 \\le 2^x \\le 4$, то есть $2^0 \\le 2^x \\le 2^2$. Знак сохраняется: $0 \\le x \\le 2$.',
'<b>Ответ:</b> $x \\in [0;\\;2]$.'
],
line: { min:-2, max:5, lo:0, hi:2, openLo:false, openHi:false }
},
{
q: '$\\left(\\dfrac{1}{3}\\right)^{x-1} > 9$',
steps: [
'Приводим к одному основанию: $9 = 3^2 = \\left(\\dfrac{1}{3}\\right)^{-2}$.',
'Получаем $\\left(\\dfrac{1}{3}\\right)^{x-1} > \\left(\\dfrac{1}{3}\\right)^{-2}$.',
'Основание $a = \\dfrac{1}{3} < 1$ — знак <b>меняется</b>: $x - 1 < -2$.',
'Откуда $x < -1$.',
'<b>Ответ:</b> $x \\in (-\\infty;\\;-1)$.'
],
line: { min:-6, max:4, lo:null, hi:-1, openHi:true, dirLeft:true }
}
];
function drawLine(L){
const W = 420, H = 84, padL = 24, padR = 24;
const y = 44;
const lo = (L.lo!=null) ? L.lo : L.min;
const hi = (L.hi!=null) ? L.hi : L.max;
const span = L.max - L.min;
const toX = v => padL + (v - L.min) * (W - padL - padR) / span;
let s = '<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;max-width:'+W+'px;height:auto">';
// главная ось
s += '<line x1="'+padL+'" y1="'+y+'" x2="'+(W-padR)+'" y2="'+y+'" stroke="#0f172a" stroke-width="1.6"/>';
s += '<polygon points="'+(W-padR)+','+y+' '+(W-padR-8)+','+(y-4)+' '+(W-padR-8)+','+(y+4)+'" fill="#0f172a"/>';
// тики и подписи
for(let v = Math.ceil(L.min); v <= Math.floor(L.max); v++){
const x = toX(v);
s += '<line x1="'+x+'" y1="'+(y-4)+'" x2="'+x+'" y2="'+(y+4)+'" stroke="#64748b" stroke-width="1"/>';
s += '<text x="'+x+'" y="'+(y+18)+'" font-size="11" fill="#64748b" text-anchor="middle">'+v+'</text>';
}
// закрашенная область
const xLo = toX(lo), xHi = toX(hi);
let x1 = xLo, x2 = xHi;
if(L.dirLeft){ x1 = padL + 2; }
if(L.dirRight){ x2 = W - padR - 2; }
s += '<line x1="'+x1+'" y1="'+y+'" x2="'+x2+'" y2="'+y+'" stroke="#7c3aed" stroke-width="5" stroke-linecap="round"/>';
if(L.dirLeft){
s += '<polygon points="'+(padL+2)+','+y+' '+(padL+10)+','+(y-5)+' '+(padL+10)+','+(y+5)+'" fill="#7c3aed"/>';
}
if(L.dirRight){
s += '<polygon points="'+(W-padR-2)+','+y+' '+(W-padR-10)+','+(y-5)+' '+(W-padR-10)+','+(y+5)+'" fill="#7c3aed"/>';
}
// граничные точки
if(L.lo != null){
const fill = L.openLo ? '#fff' : '#7c3aed';
s += '<circle cx="'+xLo+'" cy="'+y+'" r="5.5" fill="'+fill+'" stroke="#7c3aed" stroke-width="2"/>';
s += '<text x="'+xLo+'" y="'+(y-12)+'" font-size="11" fill="#6d28d9" text-anchor="middle" font-weight="700">'+L.lo+'</text>';
}
if(L.hi != null){
const fill = L.openHi ? '#fff' : '#7c3aed';
s += '<circle cx="'+xHi+'" cy="'+y+'" r="5.5" fill="'+fill+'" stroke="#7c3aed" stroke-width="2"/>';
s += '<text x="'+xHi+'" y="'+(y-12)+'" font-size="11" fill="#6d28d9" text-anchor="middle" font-weight="700">'+L.hi+'</text>';
}
s += '</svg>';
return s;
}
const sn = document.getElementById('p6-iv1-sn');
const nL = document.getElementById('p6-iv1-n');
const qEl = document.getElementById('p6-iv1-q');
const stepsEl = document.getElementById('p6-iv1-steps');
const lineEl = document.getElementById('p6-iv1-line');
const nextBtn = document.getElementById('p6-iv1-next');
const allBtn = document.getElementById('p6-iv1-all');
const resetBtn = document.getElementById('p6-iv1-reset');
const seen = new Set();
let _done = false;
let cur = 0, shown = 1;
function render(){
const t = TASKS[cur];
nL.textContent = (cur + 1);
qEl.innerHTML = t.q;
stepsEl.innerHTML = t.steps.map((s, i) => {
const visible = i < shown;
const isLast = i === t.steps.length - 1 && visible;
const bg = isLast ? '#dcfce7' : 'var(--card)';
const brd = isLast ? '2px solid #16a34a' : '1px solid var(--border)';
return '<div data-i="'+i+'" style="padding:10px 12px;background:'+bg+';border:'+brd+';border-radius:8px;font-size:.95rem;display:'+(visible?'block':'none')+'"><span style="color:var(--muted);font-weight:700;margin-right:6px">Шаг '+(i+1)+':</span>'+s+'</div>';
}).join('');
renderMath(qEl);
renderMath(stepsEl);
if(shown >= t.steps.length){
lineEl.innerHTML = '<div style="font-size:.85rem;color:var(--muted);margin-bottom:4px">Ответ на числовой прямой:</div>' + drawLine(t.line);
lineEl.style.display = 'block';
seen.add(cur);
if(!_done && seen.size >= 5){ _done = true; addXp(10, 'p6-iv1'); bumpProgress('p6', 15); }
} else {
lineEl.style.display = 'none';
}
}
function load(n){ cur = Math.max(0, Math.min(TASKS.length - 1, n)); shown = 1; render(); }
sn.addEventListener('input', () => load((+sn.value) - 1));
nextBtn.addEventListener('click', () => {
if(shown < TASKS[cur].steps.length){ shown++; render(); }
});
allBtn.addEventListener('click', () => { shown = TASKS[cur].steps.length; render(); });
resetBtn.addEventListener('click', () => { shown = 1; render(); });
load(0);
})();
/* === IV2 — калькулятор a^(kx+b) > c === */
(function(){
const aI = document.getElementById('p6-iv2-a');
const kI = document.getElementById('p6-iv2-k');
const bI = document.getElementById('p6-iv2-b');
const sgI = document.getElementById('p6-iv2-sg');
const cI = document.getElementById('p6-iv2-c');
const go = document.getElementById('p6-iv2-go');
const out = document.getElementById('p6-iv2-out');
const fb = document.getElementById('p6-iv2-fb');
const used = new Set();
let _done = false;
document.querySelectorAll('#p6-iv2 [data-set]').forEach(btn => {
btn.addEventListener('click', () => {
const v = btn.dataset.set.split(',');
aI.value = v[0]; kI.value = v[1]; bI.value = v[2]; sgI.value = v[3]; cI.value = v[4];
calc();
});
});
const SIGN_TEX = { gt:'>', ge:'\\ge', lt:'<', le:'\\le' };
const SIGN_INT = { gt:'(', ge:'[', lt:')', le:']' };
// переворот знака неравенства при умножении/делении на отрицательное
function flipSign(s){ return ({gt:'lt',ge:'le',lt:'gt',le:'ge'})[s]; }
function calc(){
const a = parseFloat(aI.value);
const k = parseFloat(kI.value);
const b = parseFloat(bI.value);
const c = parseFloat(cI.value);
const sg = sgI.value;
if(![a,k,b,c].every(isFinite)){ feedback(fb, false, '&#10007; Введи все четыре числа.'); return; }
if(a <= 0 || Math.abs(a - 1) < 1e-9){ feedback(fb, false, '&#10007; Основание $a$ должно быть $> 0$ и $\\ne 1$.'); return; }
if(k === 0){ feedback(fb, false, '&#10007; При $k = 0$ показатель не зависит от $x$.'); return; }
const expTex = (k===1?'':k) + 'x' + (b>0?'+'+b:(b<0?b:''));
const ineq = '$' + a + '^{' + expTex + '} ' + SIGN_TEX[sg] + ' ' + c + '$';
let html = '<div style="font-size:1.04rem;margin-bottom:8px;text-align:center">Неравенство: '+ineq+'</div>';
// Случай c <= 0 — левая часть всегда > 0
if(c <= 0){
if(sg === 'gt' || sg === 'ge'){
html += '<div style="color:#15803d;font-weight:700">Левая часть $a^{kx+b} > 0$ при любых $x$, а правая $\\le 0$. Значит неравенство верно <b>при всех</b> $x \\in \\mathbb{R}$.</div>';
} else {
html += '<div style="color:#b91c1c;font-weight:700">Левая часть $a^{kx+b} > 0$, а требуется $\\le c \\le 0$. Решений <b>нет</b>.</div>';
}
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Решено: см. ответ выше.');
used.add(aI.value+','+kI.value+','+bI.value+','+sgI.value+','+cI.value);
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p6-iv2'); bumpProgress('p6', 15); }
return;
}
// Анализ монотонности
let sgEff = sg;
if(a < 1){
html += '<div style="margin-bottom:6px"><b>Основание $a = '+a+' < 1$</b> — функция убывает, знак неравенства <b style="color:#b91c1c">МЕНЯЕТСЯ</b>.</div>';
sgEff = flipSign(sgEff);
} else {
html += '<div style="margin-bottom:6px"><b>Основание $a = '+a+' > 1$</b> — функция возрастает, знак неравенства <b style="color:#15803d">сохраняется</b>.</div>';
}
// Степень
let intLog = null;
for(let p = -10; p <= 12; p++){
if(Math.abs(Math.pow(a, p) - c) < 1e-9){ intLog = p; break; }
}
let rhsTex;
if(intLog !== null){
html += '<div style="margin-bottom:6px">$'+c+' = '+a+'^{'+intLog+'}$, поэтому неравенство показателей: $'+expTex+' '+SIGN_TEX[sgEff]+' '+intLog+'$.</div>';
rhsTex = String(intLog);
} else {
rhsTex = '\\log_{'+a+'} '+c;
html += '<div style="margin-bottom:6px">Неравенство показателей: $'+expTex+' '+SIGN_TEX[sgEff]+' \\log_{'+a+'} '+c+'$.</div>';
}
// Решаем kx + b [sgEff] R, где R = intLog (или log_a c)
// x [?] (R - b) / k. Если k<0, знак ещё раз меняется.
let sgFinal = sgEff;
if(k < 0){
html += '<div style="margin-bottom:6px">Делим на $k = '+k+' < 0$ — знак <b style="color:#b91c1c">снова меняется</b>.</div>';
sgFinal = flipSign(sgFinal);
}
const xNum = (intLog !== null) ? (intLog - b) : (Math.log(c)/Math.log(a) - b);
const xVal = xNum / k;
const xStr = (Math.abs(xVal - Math.round(xVal)) < 1e-9) ? String(Math.round(xVal)) : (+xVal.toFixed(4)).toString();
// Формируем интервал
let interval;
if(sgFinal === 'gt') interval = '('+xStr+';\\,+\\infty)';
else if(sgFinal === 'ge') interval = '['+xStr+';\\,+\\infty)';
else if(sgFinal === 'lt') interval = '(-\\infty;\\,'+xStr+')';
else interval = '(-\\infty;\\,'+xStr+']';
html += '<div style="font-weight:700;color:#15803d">$\\Rightarrow\\; x '+SIGN_TEX[sgFinal]+' '+xStr+',\\quad x \\in '+interval+'$</div>';
out.innerHTML = html;
renderMath(out);
feedback(fb, true, '&#10003; Решено.');
used.add(aI.value+','+kI.value+','+bI.value+','+sgI.value+','+cI.value);
if(!_done && used.size >= 4){ _done = true; addXp(10, 'p6-iv2'); bumpProgress('p6', 15); }
}
go.addEventListener('click', calc);
[aI, kI, bI, cI].forEach(i => i.addEventListener('keydown', e => { if(e.key === 'Enter') calc(); }));
calc();
})();
/* === IV3 — сохраняется или меняется? === */
(function(){
const OPTS = ['Сохраняется', 'Меняется'];
const Q = [
{ q: '$2^{f(x)} > 2^{g(x)}$', ans: 0, hint: 'Основание $2 > 1$ — функция возрастает, знак сохраняется.' },
{ q: '$\\left(\\dfrac{1}{3}\\right)^{f(x)} > \\left(\\dfrac{1}{3}\\right)^{g(x)}$', ans: 1, hint: 'Основание $\\dfrac{1}{3} < 1$ — функция убывает, знак меняется.' },
{ q: '$5^x < 25$', ans: 0, hint: 'Приводим: $5^x < 5^2$. Основание $5 > 1$ — знак сохраняется.' },
{ q: '$\\left(\\dfrac{1}{2}\\right)^x > 8$', ans: 1, hint: 'Приводим: $\\left(1/2\\right)^x > \\left(1/2\\right)^{-3}$. Основание $< 1$ — знак меняется.' },
{ q: '$0{,}1^x \\le 0{,}01$', ans: 1, hint: 'Приводим: $0{,}1^x \\le 0{,}1^2$. Основание $0{,}1 < 1$ — знак меняется.' },
{ q: '$3^x \\ge 27$', ans: 0, hint: 'Приводим: $3^x \\ge 3^3$. Основание $3 > 1$ — знак сохраняется.' },
{ q: '$10^x > 100$', ans: 0, hint: 'Приводим: $10^x > 10^2$. Основание $10 > 1$ — знак сохраняется.' },
{ q: '$\\left(\\dfrac{2}{5}\\right)^x < 1$', ans: 1, hint: 'Приводим: $\\left(2/5\\right)^x < \\left(2/5\\right)^0$. Основание $< 1$ — знак меняется.' },
];
let i = 0, score = 0;
const qEl = document.getElementById('p6-iv3-q');
const oEl = document.getElementById('p6-iv3-opts');
const fb = document.getElementById('p6-iv3-fb');
const iEl = document.getElementById('p6-iv3-i');
const sEl = document.getElementById('p6-iv3-s');
function show(){
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
oEl.innerHTML = '';
if(score === Q.length){ addXp(15, 'p6-iv3'); bumpProgress('p6', 25); }
else if(score >= 6){ addXp(8, 'p6-iv3'); bumpProgress('p6', 15); }
return;
}
iEl.textContent = (i + 1);
sEl.textContent = score;
const item = Q[i];
qEl.innerHTML = 'Знак неравенства при переходе к показателям: ' + item.q;
oEl.innerHTML = OPTS.map((m, k) => '<button class="btn primary" data-k="'+k+'">'+m+'</button>').join('');
fb.style.display = 'none';
renderMath(qEl);
oEl.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
const k = +b.dataset.k;
if(k === item.ans){ score++; feedback(fb, true, '&#10003; Верно! '+item.hint+' Дальше ▶'); }
else feedback(fb, false, '&#10007; Неверно. Правильно: <b>'+OPTS[item.ans]+'</b>. '+item.hint+' Дальше ▶');
sEl.textContent = score;
oEl.querySelectorAll('button').forEach(x => x.disabled = true);
i++;
setTimeout(show, 1500);
});
});
}
document.getElementById('p6-iv3-restart').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
/* === IV4 — тренажёр неравенств === */
(function(){
const Q = [
{ q: '$2^x > 16$ — введи границу', ans: 4, hint: '$2^x > 2^4 \\Rightarrow x > 4$. Граница — $4$.' },
{ q: '$\\left(\\dfrac{1}{3}\\right)^x \\ge 9$ — введи границу', ans: -2, hint: '$\\left(1/3\\right)^x \\ge \\left(1/3\\right)^{-2}$. Знак меняется: $x \\le -2$. Граница — $-2$.' },
{ q: '$5^x < 1$ — введи границу', ans: 0, hint: '$5^x < 5^0$. Знак сохраняется: $x < 0$. Граница — $0$.' },
{ q: '$3^{x-1} \\le 81$ — введи границу', ans: 5, hint: '$3^{x-1} \\le 3^4 \\Rightarrow x - 1 \\le 4 \\Rightarrow x \\le 5$. Граница — $5$.' },
{ q: '$\\left(\\dfrac{1}{2}\\right)^x > 0{,}25$ — введи границу', ans: 2, hint: '$\\left(1/2\\right)^x > \\left(1/2\\right)^2$. Знак меняется: $x < 2$. Граница — $2$.' },
{ q: '$4^x \\ge 8$ — введи границу', ans: 1.5, hint: '$2^{2x} \\ge 2^3 \\Rightarrow 2x \\ge 3 \\Rightarrow x \\ge 1{,}5$. Граница — $1{,}5$.' },
];
let i = 0, score = 0;
function show(){
const qEl = document.getElementById('p6-iv4-q');
const iEl = document.getElementById('p6-iv4-i');
const sEl = document.getElementById('p6-iv4-s');
const fb = document.getElementById('p6-iv4-fb');
const ansI = document.getElementById('p6-iv4-ans');
if(i >= Q.length){
qEl.innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
if(score === Q.length){ addXp(15, 'p6-iv4'); bumpProgress('p6', 25); }
else if(score >= 4){ addXp(8, 'p6-iv4'); bumpProgress('p6', 15); }
return;
}
iEl.textContent = (i + 1);
sEl.textContent = score;
qEl.innerHTML = Q[i].q;
ansI.value = '';
renderMath(qEl);
fb.style.display = 'none';
}
function go(){
if(i >= Q.length) return;
const fb = document.getElementById('p6-iv4-fb');
const raw = document.getElementById('p6-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('p6-iv4-s').textContent = score;
i++;
setTimeout(show, 1500);
}
document.getElementById('p6-iv4-go').addEventListener('click', go);
document.getElementById('p6-iv4-ans').addEventListener('keydown', e => { if(e.key === 'Enter') go(); });
document.getElementById('p6-iv4-start').addEventListener('click', () => { i = 0; score = 0; show(); });
show();
})();
wireReadBtn('p6');
}
function buildFinal2(){
const box = document.getElementById('final2-body');
let html = '';
/* Часть А — Шпаргалка главы (3 mini-карточки) */
html += `<div class="card">
<div class="card-header">
<div class="card-icon theory">${ICONS.theory}</div>
<div class="card-title">Шпаргалка главы 2</div>
<div class="card-num">Итог</div>
</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="#7c3aed" stroke-width="2" style="width:18px;height:18px"><path d="M3 21c4-12 8-18 18-18"/><line x1="3" y1="21" x2="21" y2="21"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 4 · Показ. функция</div>
</div>
<div style="font-size:.95rem">$y = a^x$ ($a > 0$, $a \\ne 1$). $D = \\mathbb{R}$, $E = (0;\\,+\\infty)$. При $a > 1$ — возрастает, при $0 < a < 1$ — убывает. Через $(0;\\,1)$. Асимптота $y = 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="#6d28d9" stroke-width="2" style="width:18px;height:18px"><path d="M5 12h14"/><polyline points="12 5 19 12 12 19"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 5 · Показ. уравнения</div>
</div>
<div style="font-size:.95rem">$a^{f(x)} = a^{g(x)} \\Leftrightarrow f(x) = g(x)$. Четыре метода: одинаковое основание, замена переменной ($t = a^x > 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="#f59e0b" stroke-width="2" style="width:18px;height:18px"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/><polyline points="7 10 3 14 7 18"/></svg>
<div style="font-family:'Unbounded',sans-serif;font-weight:700;color:var(--pri2);font-size:.92rem">§ 6 · Показ. неравенства</div>
</div>
<div style="font-size:.95rem">При $a > 1$ знак <b>сохраняется</b>, при $0 < a < 1$ — <b>меняется</b>. Замена $t = a^x > 0$ — обязательно учитывать положительность.</div>
</div>
</div>
</div>
</div>`;
/* Часть Б — 5 боссов (intro) */
html += `<div class="card">
<div class="card-header">
<div class="card-icon rule">${ICONS.rule}</div>
<div class="card-title">Боссы главы 2</div>
<div class="card-num">5</div>
</div>
<div class="card-body">
<p>5 интегрированных задач по всей главе. За каждого побеждённого босса: <b>+10 XP, +18% к прогрессу</b>. Победишь всех — ачивка <b>«Магистр показательной функции»</b> и <b>+50 XP бонус</b>.</p>
</div>
</div>`;
html += '<div id="ch2-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="ch2-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="ch2-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="ch2-boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#7c3aed,#a78bfa);transition:width .35s"></div>
</div>
<div id="ch2-final-reward" style="margin-top:14px;display:none;padding:14px;background:var(--card);border-radius:11px;border:2px solid #7c3aed">
<div style="font-family:'Unbounded',sans-serif;font-weight:800;color:#6d28d9;font-size:1.05rem;margin-bottom:6px">Магистр показательной функции</div>
<div style="font-size:.92rem;margin-bottom:10px">Глава 2 пройдена! Все 5 боссов повержены. +50 XP бонус.</div>
<a class="btn primary" href="/textbook/algebra-11-ch3" style="text-decoration:none">Дальше: Глава 3 <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>`;
html += secNav('p6', null);
box.innerHTML = html;
renderMath(box);
/* Боссы */
const BOSSES = [
{
n:1, color:'#10b981',
title:'Циклоп Показательной',
tag:'§ 4',
q:'Найдите значение функции $y = 3^x$ при $x = -2$. Введите ответ числом (допуск $\\pm 0{,}05$).',
ans:1/9,
hint:'$3^{-2} = \\dfrac{1}{3^2} = \\dfrac{1}{9} \\approx 0{,}11$.'
},
{
n:2, color:'#0891b2',
title:'Минотавр Уравнений',
tag:'§ 5',
q:'Решите уравнение $5^{2x-1} = 125$. Введите $x$.',
ans:2,
hint:'$125 = 5^3$, поэтому $2x - 1 = 3 \\Rightarrow x = 2$.'
},
{
n:3, color:'#7c3aed',
title:'Гарпия Неравенств',
tag:'§ 6',
q:'Решите $\\left(\\dfrac{1}{2}\\right)^x > 32$. Введите <b>наибольшее целое</b> $x$.',
ans:-6,
hint:'$\\left(1/2\\right)^x > \\left(1/2\\right)^{-5}$. Знак меняется: $x < -5$. Наибольшее целое — $-6$.'
},
{
n:4, color:'#dc2626',
title:'Дракон Замены',
tag:'§ 5 + § 6',
q:'Найдите <b>сумму корней</b> уравнения $9^x - 10 \\cdot 3^x + 9 = 0$.',
ans:2,
hint:'Замена $t = 3^x$: $t^2 - 10t + 9 = 0 \\Rightarrow t = 1$ или $t = 9$. $x = 0$ и $x = 2$. Сумма — $2$.'
},
{
n:5, color:'#f59e0b',
title:'Мастер Показательной',
tag:'§ 4 + § 5 + § 6',
q:'При каком $a$ функция $y = (3a - 5)^x$ убывает на $\\mathbb{R}$? Найдите <b>сумму целых значений</b> $a$ из найденного интервала.',
ans:0,
hint:'Убывает при $0 < 3a - 5 < 1$, то есть $\\dfrac{5}{3} < a < 2$. Целых $a$ в этом интервале нет — сумма равна $0$.'
},
];
const cont = document.getElementById('ch2-bosses-container');
const STATE_KEY = 'algebra11_ch2_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="boss2-'+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="boss2-'+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="boss2-'+b.n+'-ans" class="tinp" style="width:120px;text-align:center" step="0.01" placeholder="число">'
+'<button class="btn primary" id="boss2-'+b.n+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атаковать</button>'
+'<button class="btn" id="boss2-'+b.n+'-hint">Подсказка</button>'
+'</div>'
+'<div class="feedback" id="boss2-'+b.n+'-fb"></div>'
+'</div>';
}).join('');
renderMath(cont);
function refreshOverall(){
const won = BOSS_STATE.filter(s => s.defeated).length;
const txt = document.getElementById('ch2-boss-overall');
const fill = document.getElementById('ch2-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('ch2-final-reward');
if(reward && reward.style.display === 'none'){
reward.style.display = 'block';
if(!STATE.achievements.has('ch2_done')){
achievement('ch2_done','Магистр показательной функции');
addXp(50, 'ch2-bonus');
bumpProgress('final2', 30);
if(window.confetti){ try{ confetti(); }catch(e){} }
}
}
}
}
BOSSES.forEach((b, idx)=>{
const card = document.getElementById('boss2-'+b.n+'-card');
const goBtn = document.getElementById('boss2-'+b.n+'-go');
const hintBtn = document.getElementById('boss2-'+b.n+'-hint');
const ansInp = document.getElementById('boss2-'+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.textContent = '✓ Повержен';
ansInp.disabled = true;
}
goBtn.addEventListener('click', ()=>{
if(BOSS_STATE[idx].defeated) return;
const fb = document.getElementById('boss2-'+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-ch2-'+b.n);
bumpProgress('final2', 18);
goBtn.disabled = true; goBtn.style.opacity = .55; goBtn.textContent = '✓ Повержен';
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('boss2-'+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>