cf88cb88dc
Откатил неверный фикс: добавление width="W" height="H" атрибутов заставило SVG рендериться в intrinsic-размере 180×160 px вместо заполнения родительского контейнера. Из-за этого рисунки выглядели маленькими. Теперь svgBox использует правильную responsive-стратегию: - viewBox="0 0 W H" — определяет систему координат - preserveAspectRatio="xMidYMid meet" — сохраняет пропорции - style="width:100%; max-width:Wpx; height:auto" — растягивает до ширины контейнера, но не больше intrinsic W; height auto держит правильное соотношение сторон через viewBox Cache-bust ?v=5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1655 lines
120 KiB
HTML
1655 lines
120 KiB
HTML
<!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>Геометрия 7 · Глава 1 · Начальные понятия</title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||
onload="renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false})"></script>
|
||
<script src="/js/api.js" defer></script>
|
||
<script src="/js/xp.js" defer></script>
|
||
<script src="/js/geom7_svg.js?v=5" 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; --muted:#64748b;
|
||
--border:#e2e8f0; --sh:0 1px 3px rgba(0,0,0,.06); --sh2:0 4px 14px rgba(0,0,0,.08);
|
||
--pri:#d97706; --pri2:#b45309; --pri-soft:#fef3c7;
|
||
--acc:#f59e0b; --acc2:#d97706; --acc-soft:#fef9c3;
|
||
--ok:#10b981; --ok-bg:#d1fae5; --warn:#f59e0b; --warn-bg:#fef3c7;
|
||
--bad:#ef4444; --fail:#dc2626; --fail-bg:#fee2e2;
|
||
}
|
||
.dark{--bg:#0a0a0e; --card:#13120a; --card-soft:#18160a; --text:#fef9e7; --muted:#a39070; --border:#2a2512}
|
||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||
html,body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.55;font-size:15px}
|
||
button,input,select,textarea{font-family:inherit;font-size:inherit}
|
||
button{cursor:pointer;border:0;background:transparent;color:inherit}
|
||
a{color:inherit;text-decoration:none}
|
||
.ic{width:16px;height:16px;display:inline-block;flex-shrink:0;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle}
|
||
|
||
.hdr{position:relative;background:linear-gradient(110deg,#92400e 0%,#d97706 55%,#fbbf24 100%);color:#fff;padding:46px 22px 30px;overflow:hidden;border-bottom:2px solid rgba(251,191,36,.2);min-height:130px}
|
||
.hdr::before{content:'ГЛАВА 1';position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-family:'Unbounded',sans-serif;font-size:clamp(5rem,15vw,11rem);font-weight:900;letter-spacing:-.04em;color:transparent;-webkit-text-stroke:1.5px rgba(255,235,180,.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:'\25CF';position:absolute;right:-10px;top:-20px;font-size:clamp(2rem,8vw,5.5rem);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(217,119,6,.32)}
|
||
.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(217,119,6,.18);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(217,119,6,.22);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(170px,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(217,119,6,.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,#fff5e1,#fef3c7)}
|
||
|
||
.sec[id="sec-p1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
.sec[id="sec-p2"]{ --sec-acc:#0891b2; --sec-acc-d:#0e7490; --sec-acc-soft:#cffafe; }
|
||
.sec[id="sec-p3"]{ --sec-acc:#059669; --sec-acc-d:#047857; --sec-acc-soft:#d1fae5; }
|
||
.sec[id="sec-p4"]{ --sec-acc:#7c3aed; --sec-acc-d:#6d28d9; --sec-acc-soft:#ede9fe; }
|
||
.sec[id="sec-p5"]{ --sec-acc:#db2777; --sec-acc-d:#9d174d; --sec-acc-soft:#fce7f3; }
|
||
.sec[id="sec-p6"]{ --sec-acc:#dc2626; --sec-acc-d:#b91c1c; --sec-acc-soft:#fee2e2; }
|
||
.sec[id="sec-p7"]{ --sec-acc:#ea580c; --sec-acc-d:#c2410c; --sec-acc-soft:#ffedd5; }
|
||
.sec[id="sec-final1"]{ --sec-acc:#d97706; --sec-acc-d:#b45309; --sec-acc-soft:#fef3c7; }
|
||
|
||
.sec{display:none;position:relative;animation:fadeIn .35s ease}
|
||
.sec.active{display:block}
|
||
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
||
.sec::before{content:attr(data-watermark);position:absolute;right:-20px;top:10%;font-family:'Unbounded',sans-serif;font-size:clamp(6rem,18vw,14rem);font-weight:900;color:transparent;-webkit-text-stroke:1.5px var(--sec-acc-soft,var(--pri-soft));line-height:1;pointer-events:none;user-select:none;z-index:0;opacity:.35}
|
||
.sec-header{margin-bottom:22px;padding-bottom:14px;border-bottom:2px solid var(--sec-acc-soft,var(--pri-soft));position:relative;z-index:1}
|
||
.sec-num{display:inline-block;padding:4px 10px;background:linear-gradient(135deg,var(--sec-acc,var(--pri)),var(--sec-acc-d,var(--pri2)));color:#fff;border-radius:7px;font-family:'Unbounded',sans-serif;font-size:.78rem;font-weight:800;letter-spacing:.04em;margin-bottom:8px}
|
||
.sec-h{font-family:'Unbounded',sans-serif;font-size:1.55rem;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(217,119,6,.06);position:relative;z-index:1;transition:transform .25s cubic-bezier(.16,1,.3,1),box-shadow .25s}
|
||
.card:hover{transform:translateY(-2px)}
|
||
.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.theory{background:#8b5cf6}.card-icon.rule{background:#ec4899}.card-icon.algo{background:#f59e0b}.card-icon.example{background:#10b981}
|
||
.card-icon .ic{width:18px;height:18px}
|
||
.card-title{font-family:'Unbounded',sans-serif;font-size:.82rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);flex:1}
|
||
.card-num{font-size:.74rem;font-weight:700;color:var(--muted);background:var(--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}
|
||
|
||
.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}
|
||
|
||
.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.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))}
|
||
.tinp{padding:8px 12px;border:1.5px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);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))}
|
||
|
||
.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)}
|
||
|
||
.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}
|
||
@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(245,158,11,.15);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}
|
||
|
||
.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}
|
||
|
||
.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,#d97706,#f59e0b);color:#fff;padding:12px 18px;border-radius:11px;font-weight:700;font-size:.9rem;box-shadow:0 8px 28px rgba(217,119,6,.45);z-index:1002;display:none;align-items:center;gap:8px;max-width:340px}
|
||
.ach-popup.show{display:flex}
|
||
|
||
.dnd-pool{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;padding:10px;border:1.5px dashed var(--border);border-radius:10px;min-height:54px}
|
||
.dnd-pool.col{flex-direction:column;align-items:stretch}
|
||
.dnd-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--card);border:1.5px solid var(--border);border-radius:10px;cursor:grab;user-select:none;font-size:.92rem;touch-action:none}
|
||
.dnd-chip:hover{border-color:var(--sec-acc,var(--pri))}
|
||
.dnd-chip.armed{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));box-shadow:0 0 0 3px rgba(217,119,6,.22)}
|
||
.dnd-chip.placed{background:var(--sec-acc-soft,var(--pri-soft));border-color:var(--sec-acc,var(--pri))}
|
||
.dnd-chip .dnd-x{padding:0 5px;color:var(--muted);font-weight:700;cursor:pointer}
|
||
.drop-box{background:var(--card);border:1.5px dashed var(--border);border-radius:10px;padding:10px;min-height:90px}
|
||
.drop-box.over{border-color:var(--sec-acc,var(--pri));background:var(--sec-acc-soft,var(--pri-soft));border-style:solid}
|
||
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--sec-acc-d,var(--pri2));margin-bottom:8px;text-transform:uppercase}
|
||
.drop-items{display:flex;flex-wrap:wrap;gap:6px;min-height:32px}
|
||
.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)}
|
||
.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}
|
||
|
||
.hp-boss{height:14px;background:rgba(220,38,38,.12);border-radius:9px;overflow:hidden;border:1px solid #fecaca;margin:8px 0}
|
||
.hp-boss-fill{height:100%;background:linear-gradient(90deg,#dc2626,#f59e0b);border-radius:9px;transition:width .5s cubic-bezier(.4,0,.2,1)}
|
||
|
||
.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}
|
||
}
|
||
|
||
.boss-card{padding:16px;background:var(--card);border-radius:12px;border:2px solid var(--bad,#dc2626);margin-bottom:14px}
|
||
.boss-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
||
.boss-title{font-family:'Unbounded',sans-serif;font-weight:800;color:#7f1d1d;font-size:1.04rem;flex:1}
|
||
.boss-stage{font-size:.85rem;color:var(--muted)}
|
||
.boss-q{font-size:1rem;line-height:1.55;padding:11px 13px;background:var(--card-soft);border-radius:8px;margin-bottom:9px;border-left:3px solid var(--bad,#dc2626)}
|
||
|
||
.svg-host{display:flex;justify-content:center;margin:12px 0}
|
||
.viz-info{text-align:center;font-size:.92rem;color:var(--muted);margin-top:8px;font-family:'JetBrains Mono',monospace}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="hdr">
|
||
<div class="hdr-row">
|
||
<div>
|
||
<h1>Геометрия 7 · Глава 1</h1>
|
||
<div class="hdr-sub">Начальные понятия геометрии · точка, прямая, луч, отрезок, угол, перпендикуляр</div>
|
||
</div>
|
||
<div class="hdr-side">
|
||
<a href="/textbook/geometry-7" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> К геометрии 7</a>
|
||
<button id="sidebar-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg> Шпаргалка</button>
|
||
<button id="theme-btn" class="hdr-btn"><svg class="ic" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><span id="theme-lab">Тёмная</span></button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="main">
|
||
<div class="col-main">
|
||
<section class="hero">
|
||
<h2>Начала геометрии: всё начинается с точки</h2>
|
||
<p>Здесь мы знакомимся с <b>основными фигурами</b> (точка, прямая, плоскость), учимся различать <b>прямую, луч и отрезок</b>, измерять <b>углы</b>, изучаем <b>смежные и вертикальные углы</b>, <b>окружность и круг</b>, заканчиваем <b>перпендикулярами</b>. Этот фундамент — основа всего курса геометрии.</p>
|
||
<div class="hero-row">
|
||
<button class="btn-primary" onclick="goTo('p1')"><svg class="ic" viewBox="0 0 24 24"><polygon points="6 4 20 12 6 20 6 4" fill="currentColor" stroke="none"/></svg> Начать § 1</button>
|
||
<div class="hero-progress">
|
||
<span class="hp-label">Прогресс по главе</span>
|
||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||
</div>
|
||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="psel">
|
||
<div class="psel-title">Параграфы главы</div>
|
||
<div id="psel-grid" class="psel-grid"></div>
|
||
</section>
|
||
|
||
<section id="sec-p1" class="sec" data-watermark="5-6"><div class="sec-header"><span class="sec-num">§ 1</span><h2 class="sec-h">Повторение материала 5-6 классов</h2></div><div id="p1-body"></div></section>
|
||
<section id="sec-p2" class="sec" data-watermark="●"><div class="sec-header"><span class="sec-num">§ 2</span><h2 class="sec-h">Предмет геометрии</h2></div><div id="p2-body"></div></section>
|
||
<section id="sec-p3" class="sec" data-watermark="—"><div class="sec-header"><span class="sec-num">§ 3</span><h2 class="sec-h">Прямая. Луч. Отрезок. Ломаная</h2></div><div id="p3-body"></div></section>
|
||
<section id="sec-p4" class="sec" data-watermark="○"><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-p7" class="sec" data-watermark="⊥"><div class="sec-header"><span class="sec-num">§ 7</span><h2 class="sec-h">Перпендикулярные прямые</h2></div><div id="p7-body"></div></section>
|
||
<section id="sec-final1" class="sec" data-watermark="★"><div class="sec-header"><span class="sec-num" style="background:linear-gradient(135deg,#d97706,#f59e0b)">Финал главы</span><h2 class="sec-h">Итоги. 5 боссов главы 1</h2></div><div id="final1-body"></div></section>
|
||
</div>
|
||
|
||
<aside class="col-side" id="col-side"><div id="sidebar-content"></div></aside>
|
||
<div class="col-side-backdrop" id="col-side-backdrop"></div>
|
||
</main>
|
||
|
||
<footer class="foot">Интерактивный учебник «Геометрия 7» · Глава 1 · Начальные понятия · LearnSpace</footer>
|
||
|
||
<div id="ach-popup" class="ach-popup"><svg class="ic" viewBox="0 0 24 24" style="width:22px;height:22px"><polygon points="12,2 22,20 2,20"/></svg><span id="ach-text">Достижение!</span></div>
|
||
|
||
<script>
|
||
'use strict';
|
||
|
||
const STATE = { current:'p1', progress:{p1:0,p2:0,p3:0,p4:0,p5:0,p6:0,p7:0,final1:0}, achievements:new Map(), xp:0, level:1 };
|
||
const TOTAL_PARAS = 8;
|
||
const _TB_SLUG = 'geometry-7-ch1';
|
||
|
||
function calcLevel(xp){ return Math.floor(Math.sqrt((xp||0)/100))+1; }
|
||
function _xpForLevel(lv){ return (lv-1)*(lv-1)*100; }
|
||
|
||
const ACH_LABELS = {
|
||
start:'Начало главы 1!',
|
||
p5_done:'Углы изучены!',
|
||
p6_done:'Смежные и вертикальные — освоены!',
|
||
p7_done:'Перпендикуляр — твой инструмент!',
|
||
ch1_done:'Глава 1 пройдена!',
|
||
};
|
||
|
||
function loadProgress(){
|
||
try{
|
||
const s=localStorage.getItem('geometry7_ch1_progress'); if(s) Object.assign(STATE.progress, JSON.parse(s));
|
||
const a=localStorage.getItem('geometry7_ch1_achievements');
|
||
if(a){ const p=JSON.parse(a); if(Array.isArray(p)) p.forEach(id=>STATE.achievements.set(id, ACH_LABELS[id]||id)); else if(p&&typeof p==='object'){ for(const[id,t] of Object.entries(p)) STATE.achievements.set(id,(t&&t!==id)?t:(ACH_LABELS[id]||id)); } }
|
||
STATE.xp=+(localStorage.getItem('geometry7_xp')||0); STATE.level=calcLevel(STATE.xp);
|
||
}catch(e){}
|
||
}
|
||
function saveProgress(){
|
||
try{
|
||
localStorage.setItem('geometry7_ch1_progress', JSON.stringify(STATE.progress));
|
||
localStorage.setItem('geometry7_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
|
||
localStorage.setItem('geometry7_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);
|
||
if(STATE.progress[key]>=100){
|
||
if(key==='p5') achievement('p5_done');
|
||
else if(key==='p6') achievement('p6_done');
|
||
else if(key==='p7') achievement('p7_done');
|
||
else if(key==='final1') achievement('ch1_done');
|
||
}
|
||
}
|
||
|
||
const _markedRead=new Set();
|
||
let _pendingProgressBody=null, _progressTimer=null;
|
||
function _flushProgress(){
|
||
const body=_pendingProgressBody; _pendingProgressBody=null; if(!body) return;
|
||
const tok=(window.LS&&LS.getToken)?LS.getToken():''; if(!tok) return;
|
||
fetch('/api/textbooks/'+_TB_SLUG+'/progress',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+tok},body:JSON.stringify(body),keepalive:true}).catch(()=>{});
|
||
}
|
||
function _queueProgress(patch){ _pendingProgressBody=Object.assign(_pendingProgressBody||{},patch); if(_progressTimer) clearTimeout(_progressTimer); _progressTimer=setTimeout(_flushProgress, 600); }
|
||
function markLastPara(id){ _queueProgress({last_para:id}); }
|
||
function markParaRead(id){ if(_markedRead.has(id)) return; _markedRead.add(id); _queueProgress({mark_read:id}); }
|
||
window.addEventListener('beforeunload', _flushProgress);
|
||
|
||
function addXp(n,src){
|
||
if(!n) return;
|
||
const prev=STATE.level; STATE.xp=Math.max(0,(STATE.xp||0)+n); STATE.level=calcLevel(STATE.xp);
|
||
saveProgress(); refreshProgressUI();
|
||
if(window.LS&&window.LS.xp) window.LS.xp.add(n,'geometry7-ch1-'+(src||'misc'));
|
||
if(STATE.level>prev){
|
||
const pop=document.getElementById('ach-popup');
|
||
if(pop){ document.getElementById('ach-text').textContent='Уровень '+STATE.level+'!'; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),2600); }
|
||
}
|
||
}
|
||
|
||
function refreshProgressUI(){
|
||
const total=Math.round(Object.values(STATE.progress).reduce((a,b)=>a+b,0)/TOTAL_PARAS);
|
||
const f=document.getElementById('hero-hp-fill'); if(f) f.style.width=total+'%';
|
||
const t=document.getElementById('hero-hp-text'); if(t) t.textContent=total+'% пройдено';
|
||
document.querySelectorAll('[data-prog-card]').forEach(el=>{ const k=el.dataset.progCard; const fl=el.querySelector('.psel-prog-fill'); if(fl) fl.style.width=(STATE.progress[k]||0)+'%'; });
|
||
const xpBadge=document.getElementById('hero-xp-badge');
|
||
if(xpBadge){ xpBadge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 22 20 2 20"/></svg> Ур. '+STATE.level+' \xb7 '+(STATE.xp||0)+' XP'; }
|
||
if(STATE.current && document.getElementById('sidebar-content')){ try{ buildSidebar(STATE.current); }catch(e){} }
|
||
}
|
||
|
||
function achievement(id,text){
|
||
if(STATE.achievements.has(id)) return;
|
||
STATE.achievements.set(id, text||ACH_LABELS[id]||id); saveProgress();
|
||
const pop=document.getElementById('ach-popup');
|
||
if(pop){ document.getElementById('ach-text').textContent=text||ACH_LABELS[id]||id; pop.classList.add('show'); setTimeout(()=>pop.classList.remove('show'),3300); }
|
||
addXp(20,'ach-'+id);
|
||
}
|
||
|
||
const PARAS = [
|
||
{ id:'p1', num:'§ 1', name:'Повторение 5-6', sub:'Точки, отрезки, углы' },
|
||
{ id:'p2', num:'§ 2', name:'Предмет геометрии', sub:'Аксиомы и теоремы' },
|
||
{ id:'p3', num:'§ 3', name:'Прямая, луч, отрезок', sub:'+ ломаная и многоугольники' },
|
||
{ id:'p4', num:'§ 4', name:'Окружность и круг', sub:'Центр, радиус' },
|
||
{ id:'p5', num:'§ 5', name:'Угол. Виды углов', sub:'Острый, прямой, тупой' },
|
||
{ id:'p6', num:'§ 6', name:'Смежные и вертикальные', sub:'Сумма = 180°' },
|
||
{ id:'p7', num:'§ 7', name:'Перпендикулярные прямые', sub:'$a \\perp b$' },
|
||
{ id:'final1', num:'★', name:'Финал главы', sub:'Итоги \xB7 5 боссов', final:true },
|
||
];
|
||
|
||
function buildParaSelector(){
|
||
const g=document.getElementById('psel-grid'); g.innerHTML='';
|
||
PARAS.forEach(p=>{
|
||
const card=document.createElement('div');
|
||
card.className='psel-card'+(p.final?' final':'');
|
||
card.dataset.id=p.id; card.dataset.progCard=p.id;
|
||
card.innerHTML='<div class="psel-num">'+p.num+'</div><div class="psel-name">'+p.name+'</div><div class="psel-prog"><div class="psel-prog-fill"></div></div>';
|
||
card.addEventListener('click', ()=>goTo(p.id));
|
||
g.appendChild(card);
|
||
});
|
||
}
|
||
|
||
const BUILT=new Set();
|
||
const BUILDERS = { p1:()=>buildP1(), p2:()=>buildP2(), p3:()=>buildP3(), p4:()=>buildP4(), p5:()=>buildP5(), p6:()=>buildP6(), p7:()=>buildP7(), final1:()=>buildFinal1() };
|
||
function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } }
|
||
function goTo(id){
|
||
STATE.current=id; ensureBuilt(id);
|
||
document.querySelectorAll('.sec').forEach(s=>s.classList.remove('active'));
|
||
const el=document.getElementById('sec-'+id); if(el) el.classList.add('active');
|
||
document.querySelectorAll('.psel-card').forEach(c=>c.classList.toggle('active', c.dataset.id===id));
|
||
buildSidebar(id);
|
||
window.scrollTo({top:0,behavior:'smooth'});
|
||
if((STATE.progress[id]||0)<10) bumpProgress(id, 10);
|
||
if(window.renderMathInElement) setTimeout(()=>renderMath(el), 0);
|
||
markLastPara(id);
|
||
}
|
||
|
||
const SIDEBARS = {
|
||
p1:{title:'Шпаргалка \xA71',rows:[
|
||
['Точка','обозначается прописной латинской буквой $A, B, C$'],
|
||
['Отрезок','часть прямой между двумя точками'],
|
||
['Длина','измеряется в мм, см, дм, м, км'],
|
||
['Сравнение','наложением'],
|
||
]},
|
||
p2:{title:'Шпаргалка \xA72',rows:[
|
||
['3 основные','точка, прямая, плоскость (без определения)'],
|
||
['Планиметрия','свойства плоских фигур'],
|
||
['Стереометрия','свойства фигур в пространстве'],
|
||
['Аксиома','без доказательства'],
|
||
['Теорема','с доказательством'],
|
||
]},
|
||
p3:{title:'Шпаргалка \xA73',rows:[
|
||
['Аксиома прямой','через 2 точки — одна прямая'],
|
||
['Луч','часть прямой с одним концом'],
|
||
['Отрезок','часть прямой с двумя концами'],
|
||
['Аксиома измерения','$AC + CB = AB$'],
|
||
['Ломаная','отрезки соединены концами'],
|
||
]},
|
||
p4:{title:'Шпаргалка \xA74',rows:[
|
||
['Окружность','замкнутая линия, все точки на равном расстоянии от центра'],
|
||
['Круг','часть плоскости, ограниченная окружностью'],
|
||
['Радиус','отрезок центр-окружность'],
|
||
['Диаметр','$d = 2R$'],
|
||
]},
|
||
p5:{title:'Шпаргалка \xA75',rows:[
|
||
['Угол','две стороны (лучи) + вершина'],
|
||
['Острый','$< 90°$'],
|
||
['Прямой','$= 90°$'],
|
||
['Тупой','$90° < \\alpha < 180°$'],
|
||
['Развёрнутый','$= 180°$'],
|
||
['Полный','$= 360°$'],
|
||
['Биссектриса','делит угол пополам'],
|
||
]},
|
||
p6:{title:'Шпаргалка \xA76',rows:[
|
||
['Смежные','общая сторона + дополнительные'],
|
||
['Сумма смежных','$= 180°$'],
|
||
['Вертикальные','стороны $\\to$ дополнительные'],
|
||
['Вертикальные','<b>равны</b>'],
|
||
]},
|
||
p7:{title:'Шпаргалка \xA77',rows:[
|
||
['$a \\perp b$','две прямые, пересекаются под прямым углом'],
|
||
['4 угла','все по 90°'],
|
||
['Через точку','одна перпендикуляр'],
|
||
['$a \\perp c$ и $b \\perp c$','$\\Rightarrow$ $a \\parallel b$'],
|
||
]},
|
||
final1:{title:'Финал главы',rows:[
|
||
['§1–§7','теория главы 1'],
|
||
['Боссов','5'],
|
||
['Награда','+100 XP за полное прохождение'],
|
||
]},
|
||
};
|
||
|
||
const TIPS=[
|
||
{sec:'p1',html:'Длина отрезка <b>положительное</b> число. Равные отрезки $\\Leftrightarrow$ равные длины.'},
|
||
{sec:'p2',html:'<b>Аксиома</b> — фундамент. <b>Теорема</b> — доказывается через аксиомы и уже доказанные теоремы.'},
|
||
{sec:'p3',html:'Через одну точку — <b>бесконечно много</b> прямых. Через две — <b>одна</b>. Через три не на одной прямой — <b>ни одной</b>.'},
|
||
{sec:'p4',html:'Окружность — <b>линия</b>. Круг — <b>часть плоскости</b>. Обруч ↔ окружность, крышка люка ↔ круг.'},
|
||
{sec:'p5',html:'Аксиома измерения углов: если луч проходит внутри угла, он разбивает его на два угла, сумма градусных мер которых равна градусной мере исходного.'},
|
||
{sec:'p6',html:'<b>Смежные</b>: сумма = 180°. <b>Вертикальные</b>: равны между собой. При пересечении двух прямых — 4 угла попарно вертикальны.'},
|
||
{sec:'p7',html:'$a \\perp b$ значит угол между прямыми = 90°. Через любую точку плоскости можно провести <b>только одну</b> прямую, перпендикулярную данной.'},
|
||
{sec:'final1',html:'5 боссов проверяют все темы главы 1.'},
|
||
];
|
||
|
||
function buildSidebar(id){
|
||
const box=document.getElementById('sidebar-content');
|
||
const sb=SIDEBARS[id]||SIDEBARS.p1;
|
||
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"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||
html+='</div>';
|
||
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
|
||
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">Подсказка</h4><div class="sidecard-row" style="font-size:.84rem;line-height:1.55">'+tip.html+'</div></div>';
|
||
if(STATE.achievements.size>0){
|
||
html+='<div class="sidecard"><h4>Достижения</h4>';
|
||
[...STATE.achievements.values()].slice(-4).forEach(text=>{ html+='<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ '+text+'</div>'; });
|
||
html+='</div>';
|
||
}
|
||
box.innerHTML=html;
|
||
if(window.renderMathInElement) try{ renderMath(box); }catch(e){}
|
||
}
|
||
|
||
function initTheme(){
|
||
const t=localStorage.getItem('geometry7_ch1_theme')||'light';
|
||
if(t==='dark') document.documentElement.classList.add('dark');
|
||
document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';
|
||
document.getElementById('theme-btn').addEventListener('click', ()=>{
|
||
document.documentElement.classList.toggle('dark');
|
||
const dark=document.documentElement.classList.contains('dark');
|
||
localStorage.setItem('geometry7_ch1_theme', dark?'dark':'light');
|
||
document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';
|
||
});
|
||
}
|
||
|
||
function renderMath(root){ if(window.renderMathInElement){ try{ renderMathInElement(root, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false}); }catch(e){} } }
|
||
function feedback(elm, ok, text){ if(!elm) return; elm.className='feedback '+(ok?'ok':'fail'); elm.innerHTML=text||(ok?'✓ Верно!':'✗ Неверно'); elm.style.display='block'; try{renderMath(elm);}catch(e){} }
|
||
|
||
const ICONS = {
|
||
theory:'<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
|
||
algo:'<svg class="ic" viewBox="0 0 24 24"><polyline points="17 11 21 7 17 3"/><line x1="21" y1="7" x2="9" y2="7"/><polyline points="7 13 3 17 7 21"/><line x1="3" y1="17" x2="15" y2="17"/></svg>',
|
||
rule:'<svg class="ic" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>',
|
||
example:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 13c1 1 2 2 2 4h4c0-2 1-3 2-4a7 7 0 0 0-4-13z"/></svg>',
|
||
};
|
||
function makeCard(kind, title, num, body){
|
||
const labels={theory:'Теория',algo:'Алгоритм',rule:'Правило',example:'Пример'};
|
||
return '<div class="card"><div class="card-header"><div class="card-icon '+kind+'">'+ICONS[kind]+'</div><div class="card-title">'+(labels[kind]||'')+(title&&title!==labels[kind]?' \xb7 '+title:'')+'</div>'+(num?'<div class="card-num">'+num+'</div>':'')+'</div><div class="card-body">'+body+'</div></div>';
|
||
}
|
||
function secNav(prev, next){
|
||
const NAMES={p1:'\xA71',p2:'\xA72',p3:'\xA73',p4:'\xA74',p5:'\xA75',p6:'\xA76',p7:'\xA77',final1:'Финал'};
|
||
let h='<div class="sec-nav">';
|
||
h+=prev?'<button class="btn" onclick="goTo(\''+prev+'\')"><svg class="ic" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg> '+NAMES[prev]+'</button>':'<span></span>';
|
||
h+=next?'<button class="btn primary" onclick="goTo(\''+next+'\')">'+NAMES[next]+' <svg class="ic" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg></button>':'<span></span>';
|
||
h+='</div>'; return h;
|
||
}
|
||
|
||
function setupSorter(cfg){
|
||
const placed={}; const pool=document.getElementById(cfg.poolId); const scope=document.querySelector(cfg.scopeSelector);
|
||
if(!pool||!scope) return {placed,render:()=>{},reset:()=>{}};
|
||
pool.classList.add('dnd-pool'); if(cfg.columnLayout) pool.classList.add('col');
|
||
let armed=null;
|
||
function buildChip(it,isPlaced){ const e=document.createElement('div'); e.className='dnd-chip'+(isPlaced?' placed':''); e.dataset.id=it.id; e.innerHTML='<span class="dnd-txt">'+it.html+'</span><span class="dnd-x" title="Убрать">\xd7</span>'; attach(e,it.id); return e; }
|
||
function attach(elm,itId){
|
||
elm.addEventListener('click', ev=>{
|
||
if(ev.target.classList && ev.target.classList.contains('dnd-x')){ ev.stopPropagation(); if(placed[itId]){delete placed[itId];render();}else if(armed===itId){armed=null;render();} return; }
|
||
if(placed[itId]){delete placed[itId];armed=null;render();} else {armed=(armed===itId)?null:itId;render();}
|
||
});
|
||
}
|
||
function attachBoxTaps(){ scope.querySelectorAll('.drop-box').forEach(box=>{ box.addEventListener('click',ev=>{ if(!armed)return; if(ev.target.closest('.dnd-chip'))return; const di=box.querySelector('[data-cat]'); if(di){placed[armed]=di.dataset.cat;armed=null;render();} }); }); }
|
||
function render(){ pool.innerHTML=''; cfg.items.forEach(it=>{if(placed[it.id])return;const c=buildChip(it,false);if(armed===it.id)c.classList.add('armed');pool.appendChild(c);}); cfg.cats.forEach(cat=>{const box=scope.querySelector('.drop-items[data-cat="'+cat+'"]');if(!box)return;box.innerHTML='';cfg.items.forEach(it=>{if(placed[it.id]!==cat)return;box.appendChild(buildChip(it,true));});}); if(window.renderMathInElement)try{renderMath(scope);}catch(_){} }
|
||
attachBoxTaps(); render();
|
||
return {placed,render,reset(){ for(const k in placed) delete placed[k]; armed=null; render(); }};
|
||
}
|
||
|
||
function makeTrainer(opts){
|
||
let i=0, score=0;
|
||
const Q=opts.questions;
|
||
const parser = opts.parser || (v => parseFloat(String(v).replace(',','.')));
|
||
function show(){
|
||
if(i >= Q.length){
|
||
document.getElementById(opts.idPrefix+'-q').innerHTML = '<b>Готово!</b> Результат: '+score+' / '+Q.length;
|
||
if(opts.onComplete) opts.onComplete(score, Q.length);
|
||
return;
|
||
}
|
||
document.getElementById(opts.idPrefix+'-i').textContent = (i+1);
|
||
document.getElementById(opts.idPrefix+'-s').textContent = score;
|
||
document.getElementById(opts.idPrefix+'-q').innerHTML = Q[i].q;
|
||
document.getElementById(opts.idPrefix+'-ans').value = '';
|
||
renderMath(document.getElementById(opts.idPrefix+'-q'));
|
||
document.getElementById(opts.idPrefix+'-fb').style.display = 'none';
|
||
}
|
||
function go(){
|
||
if(i >= Q.length) return;
|
||
const fb = document.getElementById(opts.idPrefix+'-fb');
|
||
const raw = document.getElementById(opts.idPrefix+'-ans').value.trim();
|
||
if(raw === ''){ feedback(fb, false, '✗ Введи ответ.'); return; }
|
||
const expected = Q[i].a;
|
||
let ok = false;
|
||
if(typeof expected === 'function') ok = expected(raw);
|
||
else { const got = parser(raw); ok = !isNaN(got) && Math.abs(got - expected) < 1e-6; }
|
||
if(ok){ score++; feedback(fb, true, '✓ Верно! Дальше ▶'); }
|
||
else feedback(fb, false, '✗ Неверно. Правильно: <b>'+(Q[i].show||expected)+'</b>. Дальше ▶');
|
||
document.getElementById(opts.idPrefix+'-s').textContent = score;
|
||
i++; setTimeout(show, 1100);
|
||
}
|
||
document.getElementById(opts.idPrefix+'-go').addEventListener('click', go);
|
||
document.getElementById(opts.idPrefix+'-ans').addEventListener('keydown', e=>{ if(e.key==='Enter') go(); });
|
||
const restart = document.getElementById(opts.idPrefix+'-start');
|
||
if(restart) restart.addEventListener('click', ()=>{ i=0; score=0; show(); });
|
||
show();
|
||
}
|
||
function trainerHTML(idPrefix, total, placeholder){
|
||
return '<div class="score-display"><span>Задача <b id="'+idPrefix+'-i">1</b> / '+total+'</span><span>Очки: <b id="'+idPrefix+'-s">0</b> / '+total+'</span></div>'
|
||
+'<div id="'+idPrefix+'-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;margin-bottom:10px;text-align:center"></div>'
|
||
+'<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center">'
|
||
+'<input type="text" id="'+idPrefix+'-ans" class="tinp" placeholder="'+(placeholder||'Ответ')+'" style="width:140px;text-align:center">'
|
||
+'<button class="btn primary" id="'+idPrefix+'-go">Проверить</button>'
|
||
+'<button class="btn" id="'+idPrefix+'-start">Заново</button>'
|
||
+'</div><div class="feedback" id="'+idPrefix+'-fb"></div>';
|
||
}
|
||
function readButton(paraId){
|
||
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>'
|
||
+' Я прочитал \xA7'+paraId.replace('p','')+' (+10 XP)'
|
||
+'</button></div>';
|
||
}
|
||
function wireReadBtn(paraId){
|
||
document.getElementById(paraId+'-read-btn').addEventListener('click', ()=>{
|
||
addXp(10, paraId+'-read'); bumpProgress(paraId, 30);
|
||
const b=document.getElementById(paraId+'-read-btn'); b.textContent='Прочитано! +10 XP'; b.disabled=true; b.style.opacity=.6;
|
||
});
|
||
}
|
||
|
||
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();
|
||
buildParaSelector(); refreshProgressUI(); goTo('p1');
|
||
setTimeout(()=>achievement('start','Начало главы 1!'), 600);
|
||
}
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 1 — Повторение материала 5-6 классов
|
||
============================================================ */
|
||
function buildP1(){
|
||
const box = document.getElementById('p1-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
/* SVG-иллюстрация: 3 точки на прямой A, C, B */
|
||
let svgLine = '';
|
||
if(G){
|
||
const box1 = G.svgBox(300, 80, { id:'p1-1', cell:20 });
|
||
svgLine = box1.open
|
||
+ G.line({x:30,y:40},{x:270,y:40},{color:'#0891b2',width:2})
|
||
+ G.point(80, 40, 'A', { color:'#dc2626' })
|
||
+ G.point(160, 40, 'C', { color:'#dc2626' })
|
||
+ G.point(230, 40, 'B', { color:'#dc2626' })
|
||
+ box1.close;
|
||
}
|
||
|
||
html += makeCard('theory', 'Геометрия начинается с точки', '1.1', `
|
||
<p>В начальных классах вы уже знакомились с <b>точкой</b>, <b>прямой</b>, <b>лучом</b>, <b>отрезком</b>, <b>углом</b>, <b>окружностью</b>. Теперь — повторим главное.</p>
|
||
<p>Точки обозначают <b>прописными латинскими буквами</b>: $A, B, C, D, \\ldots$. Их рисуют как маленькие круги/жирные точки.</p>
|
||
<p>Через две точки можно провести <b>прямую</b> (бесконечную) или <b>отрезок</b> (с этими двумя точками-концами).</p>
|
||
<div class="svg-host">`+svgLine+`</div>
|
||
<p style="text-align:center;font-size:.85rem;color:var(--muted)">Прямая с тремя точками $A, C, B$. Точка $C$ лежит <b>между</b> $A$ и $B$.</p>`);
|
||
|
||
html += makeCard('rule', 'Длина отрезка', '1.2', `
|
||
<p>Каждый отрезок имеет <b>длину</b>, выраженную <b>положительным числом</b>.</p>
|
||
<p>Единицы длины: мм, см, дм, м, км. Длина $AB$ записывается как <b>$AB = 12$ см</b> или просто <b>$AB = 12$</b> (если единицы не указаны).</p>
|
||
<p>Часто вместо «длина отрезка равна 12 см» говорят «отрезок равен 12 см».</p>
|
||
<p>Свойство: равным отрезкам соответствуют равные длины. И наоборот.</p>
|
||
<p>Чтобы <b>сравнить</b> два отрезка, можно их <b>совместить наложением</b>.</p>`);
|
||
|
||
html += makeCard('example', 'Пример: длина в разных единицах', '1.3', `
|
||
<p>В отрезке $AB$ укладывается 3 отрезка по 1 дм, 5 отрезков по 1 см и 2 отрезка по 1 мм.</p>
|
||
<p>Тогда: $AB = 3$ дм $5$ см $2$ мм $= 352$ мм $= 35{,}2$ см $= 3{,}52$ дм.</p>`);
|
||
|
||
html += '<div class="wg" id="p1-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Перевод единиц длины</div></div>'
|
||
+'<div class="wg-help">Переведи длину в указанные единицы. Ответ — число (десятичная дробь через точку или запятую).</div>'
|
||
+trainerHTML('p1-iv1', 5, 'число')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p1-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Точка между двумя другими</div></div>'
|
||
+'<div class="wg-help">На прямой лежат три точки. Какая лежит <b>между</b> двумя другими? Используй свойство: точка $C$ между $A$ и $B$, если $AC + CB = AB$.</div>'
|
||
+trainerHTML('p1-iv2', 4, 'буква')
|
||
+'</div>';
|
||
|
||
html += secNav(null, 'p2') + readButton('p1');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
makeTrainer({
|
||
idPrefix:'p1-iv1',
|
||
questions:[
|
||
{ q:'Сколько см в 1 дм?', a:10 },
|
||
{ q:'Сколько мм в 25,4 см?', a:254 },
|
||
{ q:'$AB = 2$ м 3 дм. Сколько это см?', a:230 },
|
||
{ q:'$CD = 1500$ мм. Сколько это дм?', a:15 },
|
||
{ q:'$3{,}52$ дм = ? см', a:35.2 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p1-iv1');bumpProgress('p1',25);} else if(s>=3){addXp(6,'p1-iv1');bumpProgress('p1',12);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p1-iv2',
|
||
parser:(v)=>v,
|
||
questions:[
|
||
{ q:'$AC = 5$, $CB = 7$, $AB = 12$. Какая точка между двумя другими?', a:(v)=>String(v).trim().toUpperCase()==='C', show:'C' },
|
||
{ q:'$AB = 4$, $BC = 6$, $AC = 10$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='B', show:'B' },
|
||
{ q:'$MN = 3$, $MK = 8$, $NK = 5$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='N', show:'N' },
|
||
{ q:'$PQ = 9$, $QR = 4$, $PR = 5$. Какая точка между?', a:(v)=>String(v).trim().toUpperCase()==='R', show:'R' },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p1-iv2');bumpProgress('p1',25);} else if(s>=2){addXp(6,'p1-iv2');bumpProgress('p1',12);} }
|
||
});
|
||
|
||
wireReadBtn('p1');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 2 — Предмет геометрии
|
||
============================================================ */
|
||
function buildP2(){
|
||
const box = document.getElementById('p2-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Основные фигуры', '2.1', `
|
||
<p>Три основные геометрические фигуры — <b>точка</b>, <b>прямая</b> и <b>плоскость</b>. Это <b>абстрактные понятия</b>, которые принимаются <b>без определения</b>.</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li><b>Точка</b> — обозначается прописной латинской буквой ($A, B, C$).</li>
|
||
<li><b>Прямая</b> — обозначается двумя прописными буквами ($BC$) или одной малой ($b$).</li>
|
||
<li><b>Плоскость</b> — обозначается одной малой греческой буквой ($\\alpha, \\beta$) или тремя прописными.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory', 'Планиметрия и стереометрия', '2.2', `
|
||
<p>Школьный курс геометрии делится на <b>планиметрию</b> и <b>стереометрию</b>.</p>
|
||
<p>В <b>планиметрии</b> изучаются свойства <b>плоских фигур</b> — тех, которые целиком лежат на плоскости (треугольник, квадрат, окружность).</p>
|
||
<p>В <b>стереометрии</b> рассматриваются свойства <b>пространственных фигур</b> — куб, параллелепипед, пирамида, шар.</p>
|
||
<p>В 7-9 классах мы изучаем <b>планиметрию</b>.</p>`);
|
||
|
||
html += makeCard('rule', 'Определения, аксиомы, теоремы', '2.3', `
|
||
<p>Все геометрические фигуры (кроме точки, прямой и плоскости) имеют <b>определения</b>.</p>
|
||
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px"><b>Определение.</b> <i>Отрезком называется часть прямой, ограниченная двумя точками.</i></p>
|
||
<p>Свойства фигур формулируются как <b>аксиомы</b> и <b>теоремы</b>.</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li><b>Аксиома</b> — утверждение, которое принимается <b>без доказательства</b> (очевидно).</li>
|
||
<li><b>Теорема</b> — утверждение, истинность которого устанавливается <b>доказательством</b>.</li>
|
||
</ul>
|
||
<p>Пример аксиомы: <i>через любые две точки плоскости можно провести прямую, и притом только одну</i>.</p>
|
||
<p>Пример теоремы: <i>сумма углов треугольника равна $180°$</i>.</p>`);
|
||
|
||
html += makeCard('rule', 'Три типа задач', '2.4', `
|
||
<p>В геометрии выделяют три основных типа задач:</p>
|
||
<ol style="padding-left:22px;line-height:1.85">
|
||
<li><b>На доказательство</b> — нужно доказать утверждение.</li>
|
||
<li><b>На вычисление</b> — найти длину, площадь, угол.</li>
|
||
<li><b>На построение</b> — построить фигуру при помощи линейки и циркуля.</li>
|
||
</ol>`);
|
||
|
||
html += '<div class="wg" id="p2-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Аксиома или теорема?</div></div>'
|
||
+'<div class="wg-help">Определи, является ли утверждение аксиомой (без док-ва) или теоремой (с док-вом).</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p2-iv1-i">1</b> / 6</span><span>Очки: <b id="p2-iv1-s">0</b> / 6</span></div>'
|
||
+'<div id="p2-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p2-iv1-ax" style="background:#0891b2;border-color:#0891b2">Аксиома</button><button class="btn primary" id="p2-iv1-th" style="background:#7c3aed;border-color:#7c3aed">Теорема</button></div>'
|
||
+'<div class="feedback" id="p2-iv1-fb"></div></div>';
|
||
|
||
html += secNav('p1', 'p3') + readButton('p2');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'Через любые две точки можно провести прямую, и притом только одну.', isAx:true },
|
||
{ e:'Сумма углов треугольника равна $180°$.', isAx:false },
|
||
{ e:'От любого луча в данную полуплоскость можно отложить угол данной градусной меры.', isAx:true },
|
||
{ e:'Вертикальные углы равны.', isAx:false },
|
||
{ e:'Сумма смежных углов равна $180°$.', isAx:false },
|
||
{ e:'Если на отрезке взять точку, то она разобьёт его на два отрезка, сумма длин которых равна длине исходного.', isAx:true },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p2-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p2-iv1');bumpProgress('p2',30);} else if(score>=4){addXp(6,'p2-iv1');bumpProgress('p2',15);} return; }
|
||
document.getElementById('p2-iv1-i').textContent=(i+1);
|
||
document.getElementById('p2-iv1-s').textContent=score;
|
||
document.getElementById('p2-iv1-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p2-iv1-q'));
|
||
document.getElementById('p2-iv1-fb').style.display='none';
|
||
}
|
||
function ans(isAx){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p2-iv1-fb');
|
||
if(isAx===Q[i].isAx){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else feedback(fb,false,'✗ Это <b>'+(Q[i].isAx?'аксиома':'теорема')+'</b>');
|
||
document.getElementById('p2-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1300);
|
||
}
|
||
document.getElementById('p2-iv1-ax').addEventListener('click',()=>ans(true));
|
||
document.getElementById('p2-iv1-th').addEventListener('click',()=>ans(false));
|
||
show();
|
||
})();
|
||
|
||
wireReadBtn('p2');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 3 — Прямая. Луч. Отрезок. Ломаная
|
||
============================================================ */
|
||
function buildP3(){
|
||
const box = document.getElementById('p3-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
/* SVG: 3 рисунка — прямая, луч, отрезок */
|
||
let svgLine='', svgRay='', svgSeg='';
|
||
if(G){
|
||
const b1=G.svgBox(220,80,{id:'p3-l',cell:20});
|
||
svgLine = b1.open + G.line({x:30,y:40},{x:190,y:40},{color:'#0891b2'}) + G.point(80,40,'A',{color:'#dc2626'}) + G.point(140,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Прямая AB</text>' + b1.close;
|
||
const b2=G.svgBox(220,80,{id:'p3-r',cell:20});
|
||
svgRay = b2.open + G.ray({x:50,y:40},{x:200,y:40},{color:'#059669'}) + G.point(50,40,'A',{color:'#dc2626'}) + G.point(120,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Луч AB</text>' + b2.close;
|
||
const b3=G.svgBox(220,80,{id:'p3-s',cell:20});
|
||
svgSeg = b3.open + G.segment({x:50,y:40},{x:170,y:40},{color:'#7c3aed'}) + G.point(50,40,'A',{color:'#dc2626'}) + G.point(170,40,'B',{color:'#dc2626'}) + '<text x="110" y="20" text-anchor="middle" font-size="12" fill="#475569" font-weight="700" font-family="Unbounded">Отрезок AB</text>' + b3.close;
|
||
}
|
||
|
||
html += makeCard('theory', 'Прямая', '3.1', `
|
||
<p><b>Прямая</b> бесконечна в обе стороны и разбивает плоскость на две <b>полуплоскости</b>.</p>
|
||
<p><b>Аксиома прямой.</b> Через любые две точки плоскости можно провести прямую, и притом только одну.</p>
|
||
<p>Следствие: если две прямые имеют общую точку — это <b>единственная</b> общая точка (иначе через две общие точки прошли бы две разные прямые).</p>
|
||
<p>Через одну точку можно провести <b>бесконечно много</b> прямых. Через три точки — не всегда можно (только если они лежат на одной прямой).</p>
|
||
<div class="svg-host">`+svgLine+`</div>`);
|
||
|
||
html += makeCard('rule', 'Луч', '3.2', `
|
||
<p><b>Лучом</b> называется часть прямой, ограниченная <b>одной точкой</b> (началом луча).</p>
|
||
<p>Луч бесконечен в одну сторону. Обозначается двумя прописными буквами (начало <b>всегда первое</b>): <b>$AB$</b> — луч с началом $A$, проходящий через $B$.</p>
|
||
<p>Два луча с общим началом, лежащие на одной прямой, называются <b>дополнительными</b> (или противоположными).</p>
|
||
<div class="svg-host">`+svgRay+`</div>`);
|
||
|
||
html += makeCard('rule', 'Отрезок', '3.3', `
|
||
<p><b>Отрезком</b> называется часть прямой, ограниченная <b>двумя точками</b> (концами).</p>
|
||
<p>Длина отрезка — положительное число. Два отрезка <b>равны</b>, если их можно совместить наложением.</p>
|
||
<p><b>Аксиома измерения отрезков.</b> Если на отрезке взять точку, она разобьёт его на два отрезка, сумма длин которых равна длине исходного: $AC + CB = AB$.</p>
|
||
<p><b>Аксиома откладывания.</b> На любом луче от его начала можно отложить отрезок данной длины, и притом только один.</p>
|
||
<p><b>Серединой</b> отрезка $EF$ называется точка $M$, делящая его на два равных отрезка: $EM = MF$.</p>
|
||
<div class="svg-host">`+svgSeg+`</div>`);
|
||
|
||
html += makeCard('rule', 'Ломаная и многоугольник', '3.4', `
|
||
<p><b>Ломаной</b> называется фигура из последовательно соединённых отрезков (звеньев), причём никакие два соседних звена не лежат на одной прямой.</p>
|
||
<p>Ломаная <b>замкнутая</b>, если её начало совпадает с концом; иначе — <b>незамкнутая</b>. Ломаная <b>простая</b>, если не имеет самопересечений.</p>
|
||
<p><b>Длиной</b> ломаной называется сумма длин её звеньев.</p>
|
||
<p><b>Простая замкнутая</b> ломаная — это <b>многоугольник</b>. Её звенья — стороны, точки излома — вершины. Сумма длин сторон — <b>периметр</b> $P$.</p>
|
||
<p>Многоугольники: 3 стороны — треугольник, 4 — четырёхугольник, 5 — пятиугольник, ...</p>`);
|
||
|
||
html += '<div class="wg" id="p3-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Прямая, луч или отрезок?</div></div>'
|
||
+'<div class="wg-help">Различай: <b>прямая</b> — бесконечна в обе стороны; <b>луч</b> — в одну; <b>отрезок</b> — ограничен двумя точками.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p3-iv1-i">1</b> / 6</span><span>Очки: <b id="p3-iv1-s">0</b> / 6</span></div>'
|
||
+'<div id="p3-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p3-iv1-line" style="background:#0891b2;border-color:#0891b2">Прямая</button><button class="btn primary" id="p3-iv1-ray" style="background:#059669;border-color:#059669">Луч</button><button class="btn primary" id="p3-iv1-seg" style="background:#7c3aed;border-color:#7c3aed">Отрезок</button></div>'
|
||
+'<div class="feedback" id="p3-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p3-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Длина отрезка</div></div>'
|
||
+'<div class="wg-help">По аксиоме измерения: $AC + CB = AB$. Найди недостающую длину.</div>'
|
||
+trainerHTML('p3-iv2', 5, 'длина')
|
||
+'</div>';
|
||
|
||
html += secNav('p2', 'p4') + readButton('p3');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'Бесконечна в обе стороны', ans:'line' },
|
||
{ e:'Имеет начало, бесконечна в одну сторону', ans:'ray' },
|
||
{ e:'Имеет два конца, ограничена', ans:'seg' },
|
||
{ e:'Можно измерить (имеет длину)', ans:'seg' },
|
||
{ e:'Дополнительные две: общее начало + одна прямая', ans:'ray' },
|
||
{ e:'Через 2 точки — единственная', ans:'line' },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p3-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p3-iv1');bumpProgress('p3',25);} else if(score>=4){addXp(6,'p3-iv1');bumpProgress('p3',12);} return; }
|
||
document.getElementById('p3-iv1-i').textContent=(i+1);
|
||
document.getElementById('p3-iv1-s').textContent=score;
|
||
document.getElementById('p3-iv1-q').innerHTML=Q[i].e;
|
||
document.getElementById('p3-iv1-fb').style.display='none';
|
||
}
|
||
function ans(a){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p3-iv1-fb');
|
||
if(a===Q[i].ans){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else{ const lab={line:'прямая',ray:'луч',seg:'отрезок'}; feedback(fb,false,'✗ Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
|
||
document.getElementById('p3-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p3-iv1-line').addEventListener('click',()=>ans('line'));
|
||
document.getElementById('p3-iv1-ray').addEventListener('click',()=>ans('ray'));
|
||
document.getElementById('p3-iv1-seg').addEventListener('click',()=>ans('seg'));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p3-iv2',
|
||
questions:[
|
||
{ q:'$AC = 5$, $CB = 7$. Найди $AB$.', a:12 },
|
||
{ q:'$AB = 24$, $CB = 9$. Найди $AC$.', a:15 },
|
||
{ q:'$AB = 20$ см. $C$ — середина $AB$. Найди $AC$.', a:10 },
|
||
{ q:'$AB = 24$, $C$ на $AB$, $AC$ на 6 больше $CB$. Найди $AC$.', a:15 },
|
||
{ q:'Периметр квадрата $P = 28$ см. Найди сторону.', a:7 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p3-iv2');bumpProgress('p3',28);} else if(s>=3){addXp(8,'p3-iv2');bumpProgress('p3',14);} }
|
||
});
|
||
|
||
wireReadBtn('p3');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 4 — Окружность и круг
|
||
============================================================ */
|
||
function buildP4(){
|
||
const box = document.getElementById('p4-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
let svgCircle='';
|
||
if(G){
|
||
const b=G.svgBox(280,200,{id:'p4-c',cell:20});
|
||
svgCircle = b.open
|
||
+ G.circle({x:140,y:100}, 70, { color:'#7c3aed', center:true, centerLabel:'O', radius:true, radiusAngle:Math.PI*0.25, radiusLabel:'R' })
|
||
+ G.point(140 + 70*Math.cos(-Math.PI*0.25), 100 - 70*Math.sin(-Math.PI*0.25), 'A', { color:'#dc2626' })
|
||
+ b.close;
|
||
}
|
||
|
||
html += makeCard('theory', 'Окружность и круг — разные понятия', '4.1', `
|
||
<p><b>Окружностью</b> называется замкнутая линия на плоскости, все точки которой находятся на одинаковом расстоянии от одной точки — <b>центра</b> окружности.</p>
|
||
<p><b>Кругом</b> называется внутренняя часть плоскости, ограниченная окружностью.</p>
|
||
<p>Окружность — это <b>линия</b> (граница). Круг — это <b>часть плоскости</b> (поверхность).</p>
|
||
<ul style="padding-left:22px;line-height:1.8">
|
||
<li>Обруч → модель <b>окружности</b></li>
|
||
<li>Крышка люка → модель <b>круга</b></li>
|
||
</ul>`);
|
||
|
||
html += makeCard('rule', 'Радиус и диаметр', '4.2', `
|
||
<p><b>Радиус</b> $R$ — отрезок, соединяющий центр окружности с точкой на окружности.</p>
|
||
<p><b>Диаметр</b> $d$ — отрезок, соединяющий две точки окружности и проходящий через центр. $d = 2R$.</p>
|
||
<p>Длина окружности: $C = 2\\pi R$. Площадь круга: $S = \\pi R^2$ (это вы узнаете подробнее в 9 классе).</p>
|
||
<div class="svg-host">`+svgCircle+`</div>
|
||
<p style="text-align:center;font-size:.85rem;color:var(--muted)">Окружность с центром $O$ и радиусом $OA = R$.</p>`);
|
||
|
||
html += makeCard('rule', 'Положение точки относительно окружности', '4.3', `
|
||
<p>Точка $A$ с расстоянием $OA$ от центра $O$:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>$OA < R$ — точка <b>внутри</b> окружности;</li>
|
||
<li>$OA = R$ — точка <b>на</b> окружности;</li>
|
||
<li>$OA > R$ — точка <b>вне</b> окружности.</li>
|
||
</ul>`);
|
||
|
||
html += '<div class="wg" id="p4-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Точка внутри / на / вне окружности</div></div>'
|
||
+'<div class="wg-help">Дано: радиус $R$ и расстояние $OA$. Где находится точка $A$?</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p4-iv1-i">1</b> / 6</span><span>Очки: <b id="p4-iv1-s">0</b> / 6</span></div>'
|
||
+'<div id="p4-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.1rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p4-iv1-in" style="background:#10b981;border-color:#10b981">Внутри</button><button class="btn primary" id="p4-iv1-on" style="background:#7c3aed;border-color:#7c3aed">На</button><button class="btn primary" id="p4-iv1-out" style="background:#dc2626;border-color:#dc2626">Вне</button></div>'
|
||
+'<div class="feedback" id="p4-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p4-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Радиус и диаметр</div></div>'
|
||
+'<div class="wg-help">$d = 2R$. Найди недостающую величину.</div>'
|
||
+trainerHTML('p4-iv2', 4, 'число')
|
||
+'</div>';
|
||
|
||
html += secNav('p3', 'p5') + readButton('p4');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'$R = 5$ см, $OA = 3$ см', ans:'in' },
|
||
{ e:'$R = 4$ см, $OA = 4$ см', ans:'on' },
|
||
{ e:'$R = 6$ см, $OA = 9$ см', ans:'out' },
|
||
{ e:'$R = 10$ см, $OA = 10$ см', ans:'on' },
|
||
{ e:'$R = 7$ см, $OA = 2$ см', ans:'in' },
|
||
{ e:'$R = 3$ см, $OA = 5$ см', ans:'out' },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p4-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p4-iv1');bumpProgress('p4',28);} else if(score>=4){addXp(6,'p4-iv1');bumpProgress('p4',14);} return; }
|
||
document.getElementById('p4-iv1-i').textContent=(i+1);
|
||
document.getElementById('p4-iv1-s').textContent=score;
|
||
document.getElementById('p4-iv1-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p4-iv1-q'));
|
||
document.getElementById('p4-iv1-fb').style.display='none';
|
||
}
|
||
function ans(a){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p4-iv1-fb');
|
||
if(a===Q[i].ans){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else{ const lab={in:'внутри',on:'на окружности',out:'вне'}; feedback(fb,false,'✗ Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
|
||
document.getElementById('p4-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p4-iv1-in').addEventListener('click',()=>ans('in'));
|
||
document.getElementById('p4-iv1-on').addEventListener('click',()=>ans('on'));
|
||
document.getElementById('p4-iv1-out').addEventListener('click',()=>ans('out'));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p4-iv2',
|
||
questions:[
|
||
{ q:'$R = 8$ см. Найди $d$.', a:16 },
|
||
{ q:'$d = 24$ см. Найди $R$.', a:12 },
|
||
{ q:'$R = 5{,}5$ см. Найди $d$.', a:11 },
|
||
{ q:'$d = 7$ см. Найди $R$.', a:3.5 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(10,'p4-iv2');bumpProgress('p4',22);} else if(s>=2){addXp(5,'p4-iv2');bumpProgress('p4',10);} }
|
||
});
|
||
|
||
wireReadBtn('p4');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 5 — Угол. Виды углов
|
||
============================================================ */
|
||
function buildP5(){
|
||
const box = document.getElementById('p5-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
/* SVG: 4 угла — острый, прямой, тупой, развёрнутый */
|
||
function angleViz(deg, color, label){
|
||
if(!G) return '';
|
||
const b=G.svgBox(180,150,{id:'p5-a-'+deg,cell:20});
|
||
const V={x:40,y:115};
|
||
const A={x:160,y:115};
|
||
const rad=deg*Math.PI/180;
|
||
const Bd = 110;
|
||
const B={x:V.x+Bd*Math.cos(-rad), y:V.y-Bd*Math.sin(rad)};
|
||
let s=b.open;
|
||
/* Заливка-сектор для наглядности */
|
||
if(deg < 180){
|
||
const wR = 36;
|
||
const a1 = 0;
|
||
const a2 = -rad;
|
||
let d=a2-a1; while(d>Math.PI) d-=2*Math.PI; while(d<-Math.PI) d+=2*Math.PI;
|
||
const sweep=d>0?1:0;
|
||
const large=Math.abs(d)>Math.PI?1:0;
|
||
const x1=V.x+wR*Math.cos(a1), y1=V.y+wR*Math.sin(a1);
|
||
const x2=V.x+wR*Math.cos(a2), y2=V.y+wR*Math.sin(a2);
|
||
s += '<path d="M '+V.x+' '+V.y+' L '+x1+' '+y1+' A '+wR+' '+wR+' 0 '+large+' '+sweep+' '+x2+' '+y2+' Z" fill="'+color+'" opacity="0.22"/>';
|
||
} else if(deg === 180){
|
||
/* Развёрнутый — закрашиваем полукруг */
|
||
s += '<path d="M '+(V.x-36)+' '+V.y+' A 36 36 0 0 1 '+(V.x+36)+' '+V.y+' Z" fill="'+color+'" opacity="0.18"/>';
|
||
}
|
||
/* Стороны угла */
|
||
s += G.segment(V,A,{color:'#0891b2',width:2.6});
|
||
s += G.segment(V,B,{color:'#0891b2',width:2.6});
|
||
/* Дуга/метка прямого угла */
|
||
if(deg===90) s += G.rightAngleMark(V,A,B,{color:'#dc2626',size:14});
|
||
else if(deg !== 180) s += G.angle(V,A,B,{color:color,r:26,width:2,label:deg+'°',fontSize:13,labelOffset:14});
|
||
else s += '<text x="'+(V.x+15)+'" y="'+(V.y-8)+'" font-size="13" fill="'+color+'" font-weight="800" font-family="Unbounded">180°</text>';
|
||
/* Вершина */
|
||
s += G.point(V.x,V.y,'',{r:4,color:'#0891b2'});
|
||
/* Подпись */
|
||
s += '<text x="90" y="140" text-anchor="middle" font-size="12" fill="#1e293b" font-weight="800" font-family="Unbounded">'+label+'</text>';
|
||
s += b.close;
|
||
return s;
|
||
}
|
||
const svgAngles =
|
||
'<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center">'
|
||
+ '<div>'+angleViz(45,'#10b981','Острый')+'</div>'
|
||
+ '<div>'+angleViz(90,'#dc2626','Прямой')+'</div>'
|
||
+ '<div>'+angleViz(120,'#f59e0b','Тупой')+'</div>'
|
||
+ '<div>'+angleViz(180,'#7c3aed','Развёрн.')+'</div>'
|
||
+ '</div>';
|
||
|
||
/* SVG: обозначение одного угла 3 разными способами */
|
||
let svgNotation = G ? '' : '<div style="padding:14px;background:#fee2e2;color:#7f1d1d;border-radius:8px;text-align:center;font-size:.85rem">⚠ Библиотека SVG не загружена. Обновите страницу с Ctrl+Shift+R.</div>';
|
||
if(G){
|
||
const notationVariant = function(label, mode){
|
||
const b=G.svgBox(180,160,{id:'p5-not-'+mode,cell:20});
|
||
const V={x:40,y:130}, A={x:160,y:130};
|
||
const B={x:V.x+115*Math.cos(Math.PI/3.5), y:V.y-115*Math.sin(Math.PI/3.5)};
|
||
let s=b.open;
|
||
/* Заливка-сектор */
|
||
const a1=0, a2=-Math.PI/3.5, wR=32;
|
||
const x1=V.x+wR, y1=V.y;
|
||
const x2=V.x+wR*Math.cos(a2), y2=V.y+wR*Math.sin(a2);
|
||
s += '<path d="M '+V.x+' '+V.y+' L '+x1+' '+y1+' A '+wR+' '+wR+' 0 0 0 '+x2+' '+y2+' Z" fill="#f59e0b" opacity="0.20"/>';
|
||
s += G.segment(V,A,{color:'#0891b2',width:2.6});
|
||
s += G.segment(V,B,{color:'#0891b2',width:2.6});
|
||
s += G.angle(V,A,B,{color:'#dc2626',r:22,width:2,label:label,fontSize:14,labelOffset:14});
|
||
s += G.point(V.x,V.y,'',{r:4,color:'#0891b2'});
|
||
/* Подписи вершин по краям */
|
||
if(mode==='full'){
|
||
s += '<text x="'+(V.x-12)+'" y="'+(V.y+18)+'" font-size="15" font-weight="800" font-family="Unbounded" fill="#1e293b">A</text>';
|
||
s += '<text x="'+(B.x+6)+'" y="'+(B.y-4)+'" font-size="15" font-weight="800" font-family="Unbounded" fill="#1e293b">B</text>';
|
||
s += '<text x="'+(A.x+6)+'" y="'+(A.y+6)+'" font-size="15" font-weight="800" font-family="Unbounded" fill="#1e293b">C</text>';
|
||
s += '<text x="90" y="152" text-anchor="middle" font-size="13" fill="#7c3aed" font-weight="800" font-family="Unbounded">∠BAC</text>';
|
||
} else if(mode==='short'){
|
||
s += '<text x="'+(V.x-12)+'" y="'+(V.y+18)+'" font-size="15" font-weight="800" font-family="Unbounded" fill="#1e293b">A</text>';
|
||
s += '<text x="90" y="152" text-anchor="middle" font-size="13" fill="#7c3aed" font-weight="800" font-family="Unbounded">∠A</text>';
|
||
} else {
|
||
s += '<text x="90" y="152" text-anchor="middle" font-size="13" fill="#7c3aed" font-weight="800" font-family="Unbounded">α (альфа)</text>';
|
||
}
|
||
s += b.close;
|
||
return s;
|
||
};
|
||
svgNotation = '<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:8px">'
|
||
+ '<div>'+notationVariant('∠BAC','full')+'</div>'
|
||
+ '<div>'+notationVariant('∠A','short')+'</div>'
|
||
+ '<div>'+notationVariant('α','greek')+'</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
html += makeCard('theory', 'Что такое угол', '5.1', `
|
||
<p>Если из точки провести <b>два луча</b>, получится <b>угол</b>.</p>
|
||
<p>Эти лучи называются <b>сторонами</b> угла, а их общая точка — <b>вершиной</b>.</p>
|
||
<p>Обозначение тремя буквами: $\\angle BAC$ — вершина $A$ записывается <b>посередине</b>. Если по рисунку понятно, какой угол — обозначают одной буквой: $\\angle A$.</p>
|
||
<p>Часто углы обозначают малыми греческими буквами: $\\alpha, \\beta, \\gamma, \\varphi$.</p>
|
||
<p style="font-size:.9rem;color:var(--muted);text-align:center;margin-top:10px"><b>Один и тот же угол можно обозначить тремя способами:</b></p>
|
||
`+svgNotation+``);
|
||
|
||
/* SVG: транспортир с измерением 3 углов */
|
||
let svgProtractor='';
|
||
if(G){
|
||
const b=G.svgBox(320,180,{id:'p5-prot',cell:20});
|
||
const V={x:160,y:160};
|
||
svgProtractor = b.open
|
||
+ G.protractor(V,90,{color:'#0891b2',fill:'#fef3c7'})
|
||
/* Три цветных луча с разными углами */
|
||
+ G.segment(V,{x:V.x+105*Math.cos(-Math.PI*40/180), y:V.y-105*Math.sin(Math.PI*40/180)},{color:'#10b981',width:2.6})
|
||
+ G.segment(V,{x:V.x+105*Math.cos(-Math.PI*90/180), y:V.y-105*Math.sin(Math.PI*90/180)},{color:'#dc2626',width:2.6})
|
||
+ G.segment(V,{x:V.x+105*Math.cos(-Math.PI*140/180), y:V.y-105*Math.sin(Math.PI*140/180)},{color:'#7c3aed',width:2.6})
|
||
/* Подписи углов */
|
||
+ '<text x="'+(V.x+50)+'" y="'+(V.y-30)+'" font-size="13" fill="#047857" font-weight="800" font-family="JetBrains Mono">40°</text>'
|
||
+ '<text x="'+(V.x+8)+'" y="'+(V.y-55)+'" font-size="13" fill="#b91c1c" font-weight="800" font-family="JetBrains Mono">90°</text>'
|
||
+ '<text x="'+(V.x-90)+'" y="'+(V.y-15)+'" font-size="13" fill="#6d28d9" font-weight="800" font-family="JetBrains Mono">140°</text>'
|
||
+ b.close;
|
||
}
|
||
|
||
html += makeCard('rule', 'Измерение углов. Градус', '5.2', `
|
||
<p>Углы измеряют в <b>градусах</b>. Развёрнутый угол (стороны на одной прямой) равен <b>$180°$</b>.</p>
|
||
<p>$1°$ — это $\\dfrac{1}{180}$ часть развёрнутого угла.</p>
|
||
<p>Углы измеряют <b>транспортиром</b>. Транспортир позволяет также построить угол данной градусной меры.</p>
|
||
<div class="svg-host">`+svgProtractor+`</div>
|
||
<p style="text-align:center;font-size:.85rem;color:var(--muted);margin-top:-4px">Три угла (40°, 90°, 140°), отложенные транспортиром от общей стороны.</p>
|
||
<p><b>Аксиома измерения углов.</b> Если внутри угла из его вершины провести луч, он разобьёт угол на два, сумма градусных мер которых равна градусной мере исходного.</p>
|
||
<p><b>Аксиома откладывания углов.</b> От любого луча в данную полуплоскость можно отложить угол данной градусной меры, и притом только один.</p>`);
|
||
|
||
html += makeCard('rule', 'Виды углов', '5.3', `
|
||
<p>В зависимости от градусной меры:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li><b>Острый</b> — меньше $90°$;</li>
|
||
<li><b>Прямой</b> — равен $90°$;</li>
|
||
<li><b>Тупой</b> — больше $90°$, но меньше $180°$;</li>
|
||
<li><b>Развёрнутый</b> — равен $180°$ (стороны — дополнительные лучи);</li>
|
||
<li><b>Полный</b> — равен $360°$ (стороны совпадают).</li>
|
||
</ul>
|
||
<div class="svg-host">`+svgAngles+`</div>`);
|
||
|
||
/* SVG: биссектриса угла — наглядно две равные половинки */
|
||
let svgBisector='';
|
||
if(G){
|
||
const b=G.svgBox(280,180,{id:'p5-bis',cell:20});
|
||
const V={x:60,y:140};
|
||
const A={x:240,y:140};
|
||
const fullAngle=Math.PI/3; /* 60° сверху от горизонтали */
|
||
const halfAngle=fullAngle/2;
|
||
const B={x:V.x+170*Math.cos(fullAngle), y:V.y-170*Math.sin(fullAngle)};
|
||
const D={x:V.x+150*Math.cos(halfAngle), y:V.y-150*Math.sin(halfAngle)};
|
||
/* Две залитые половинки разными цветами */
|
||
function wedgePath(a1,a2,r,fill){
|
||
const x1=V.x+r*Math.cos(a1), y1=V.y-r*Math.sin(a1);
|
||
const x2=V.x+r*Math.cos(a2), y2=V.y-r*Math.sin(a2);
|
||
return '<path d="M '+V.x+' '+V.y+' L '+x1+' '+y1+' A '+r+' '+r+' 0 0 0 '+x2+' '+y2+' Z" fill="'+fill+'" opacity="0.30"/>';
|
||
}
|
||
svgBisector = b.open
|
||
+ wedgePath(0, halfAngle, 42, '#10b981') /* нижняя половинка ∠DAC */
|
||
+ wedgePath(halfAngle, fullAngle, 42, '#7c3aed') /* верхняя половинка ∠BAD */
|
||
+ G.segment(V,A,{color:'#0891b2',width:2.6})
|
||
+ G.segment(V,B,{color:'#0891b2',width:2.6})
|
||
+ G.segment(V,D,{color:'#dc2626',width:2.5,dash:'5 3'})
|
||
/* Дуги равных половинок (одинаковое число штрихов = равные углы) */
|
||
+ '<path d="M '+(V.x+28)+' '+V.y+' A 28 28 0 0 0 '+(V.x+28*Math.cos(halfAngle))+' '+(V.y-28*Math.sin(halfAngle))+'" fill="none" stroke="#dc2626" stroke-width="2"/>'
|
||
+ '<path d="M '+(V.x+28*Math.cos(halfAngle))+' '+(V.y-28*Math.sin(halfAngle))+' A 28 28 0 0 0 '+(V.x+28*Math.cos(fullAngle))+' '+(V.y-28*Math.sin(fullAngle))+'" fill="none" stroke="#dc2626" stroke-width="2"/>'
|
||
+ '<text x="'+(V.x+40)+'" y="'+(V.y-6)+'" font-size="11" fill="#047857" font-weight="700" font-family="JetBrains Mono">35°</text>'
|
||
+ '<text x="'+(V.x+30)+'" y="'+(V.y-32)+'" font-size="11" fill="#6d28d9" font-weight="700" font-family="JetBrains Mono">35°</text>'
|
||
+ G.point(V.x,V.y,'',{r:4,color:'#0891b2'})
|
||
+ '<text x="'+(V.x-14)+'" y="'+(V.y+18)+'" font-size="14" font-weight="800" font-family="Unbounded" fill="#1e293b">A</text>'
|
||
+ '<text x="'+(B.x+6)+'" y="'+(B.y-4)+'" font-size="14" font-weight="800" font-family="Unbounded" fill="#1e293b">B</text>'
|
||
+ '<text x="'+(A.x+6)+'" y="'+(A.y+6)+'" font-size="14" font-weight="800" font-family="Unbounded" fill="#1e293b">C</text>'
|
||
+ '<text x="'+(D.x+6)+'" y="'+(D.y+4)+'" font-size="14" font-weight="800" font-family="Unbounded" fill="#dc2626">D</text>'
|
||
+ '<text x="140" y="170" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">AD — биссектриса ∠BAC = 70°</text>'
|
||
+ b.close;
|
||
}
|
||
|
||
html += makeCard('rule', 'Биссектриса угла', '5.4', `
|
||
<p><b>Биссектрисой</b> угла называется луч, исходящий из его вершины и делящий угол на два <b>равных</b> угла.</p>
|
||
<p>Если $\\angle BAC = 70°$, а $AD$ — биссектриса, то $\\angle BAD = \\angle DAC = 35°$.</p>
|
||
<div class="svg-host">`+svgBisector+`</div>`);
|
||
|
||
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">По градусной мере определи тип угла.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p5-iv1-i">1</b> / 6</span><span>Очки: <b id="p5-iv1-s">0</b> / 6</span></div>'
|
||
+'<div id="p5-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.3rem;text-align:center;margin-bottom:10px;font-family:JetBrains Mono,monospace"></div>'
|
||
+'<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap"><button class="btn primary" id="p5-iv1-acute" style="background:#10b981;border-color:#10b981">Острый</button><button class="btn primary" id="p5-iv1-right" style="background:#dc2626;border-color:#dc2626">Прямой</button><button class="btn primary" id="p5-iv1-obtuse" style="background:#f59e0b;border-color:#f59e0b">Тупой</button><button class="btn primary" id="p5-iv1-straight" style="background:#7c3aed;border-color:#7c3aed">Развёрн.</button></div>'
|
||
+'<div class="feedback" id="p5-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p5-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Аксиома измерения углов</div></div>'
|
||
+'<div class="wg-help">$\\angle BAC + \\angle CAD = \\angle BAD$, где луч $AC$ внутри угла $BAD$. Найди недостающий угол.</div>'
|
||
+trainerHTML('p5-iv2', 5, 'градусы')
|
||
+'</div>';
|
||
|
||
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">Биссектриса делит угол пополам.</div>'
|
||
+trainerHTML('p5-iv3', 4, 'градусы')
|
||
+'</div>';
|
||
|
||
html += secNav('p4', 'p6') + readButton('p5');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'60°', ans:'acute' },
|
||
{ e:'90°', ans:'right' },
|
||
{ e:'120°', ans:'obtuse' },
|
||
{ e:'180°', ans:'straight' },
|
||
{ e:'45°', ans:'acute' },
|
||
{ e:'179°', ans:'obtuse' },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p5-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p5-iv1');bumpProgress('p5',22);} else if(score>=4){addXp(6,'p5-iv1');bumpProgress('p5',12);} return; }
|
||
document.getElementById('p5-iv1-i').textContent=(i+1);
|
||
document.getElementById('p5-iv1-s').textContent=score;
|
||
document.getElementById('p5-iv1-q').innerHTML=Q[i].e;
|
||
document.getElementById('p5-iv1-fb').style.display='none';
|
||
}
|
||
function ans(a){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p5-iv1-fb');
|
||
if(a===Q[i].ans){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else{ const lab={acute:'острый',right:'прямой',obtuse:'тупой',straight:'развёрнутый'}; feedback(fb,false,'✗ Правильно: <b>'+lab[Q[i].ans]+'</b>'); }
|
||
document.getElementById('p5-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1000);
|
||
}
|
||
document.getElementById('p5-iv1-acute').addEventListener('click',()=>ans('acute'));
|
||
document.getElementById('p5-iv1-right').addEventListener('click',()=>ans('right'));
|
||
document.getElementById('p5-iv1-obtuse').addEventListener('click',()=>ans('obtuse'));
|
||
document.getElementById('p5-iv1-straight').addEventListener('click',()=>ans('straight'));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p5-iv2',
|
||
questions:[
|
||
{ q:'$\\angle BAC = 40°$, $\\angle CAD = 25°$. Найди $\\angle BAD$.', a:65 },
|
||
{ q:'$\\angle BAD = 80°$, $\\angle CAD = 30°$. Найди $\\angle BAC$.', a:50 },
|
||
{ q:'$\\angle BAD = 114°$, $\\angle BAE = 2 \\angle EAD$. Найди $\\angle BAE$.', a:76 },
|
||
{ q:'$\\angle ABC = 109°$, $\\angle ABF = 95°$, $\\angle CBD = 54°$ ($BD, BF$ внутри). Найди $\\angle DBF$.', a:40 },
|
||
{ q:'Один из смежных углов равен $40°$. Найди другой.', a:140 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p5-iv2');bumpProgress('p5',28);} else if(s>=3){addXp(8,'p5-iv2');bumpProgress('p5',14);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p5-iv3',
|
||
questions:[
|
||
{ q:'$\\angle BAC = 80°$, $AM$ — биссектриса. Найди $\\angle BAM$.', a:40 },
|
||
{ q:'$\\angle BAC = 68°$, $AM$ — биссектриса $\\angle BAC$, $AK$ — биссектриса $\\angle MAC$. Найди $\\angle BAK$.', a:51 },
|
||
{ q:'Внутри прямого угла $KMN$ проведены лучи $MA$ и $MB$: $\\angle KMB = 72°$, $\\angle AMN = 48°$. Найди $\\angle AMB$.', a:30 },
|
||
{ q:'$\\angle BOC = 70°$, $OM, OK$ — биссектрисы смежных углов $AOC$ и $BOC$. Найди $\\angle MOK$.', a:90 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p5-iv3');bumpProgress('p5',22);} else if(s>=2){addXp(6,'p5-iv3');bumpProgress('p5',10);} }
|
||
});
|
||
|
||
wireReadBtn('p5');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 6 — Смежные углы. Вертикальные углы
|
||
============================================================ */
|
||
function buildP6(){
|
||
const box = document.getElementById('p6-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
/* SVG для смежных углов */
|
||
let svgAdj='', svgVert='';
|
||
if(G){
|
||
const b=G.svgBox(260,160,{id:'p6-adj',cell:20});
|
||
const V={x:130,y:120};
|
||
svgAdj = b.open
|
||
+ G.segment({x:20,y:120},{x:240,y:120},{color:'#0891b2',width:2})
|
||
+ G.segment(V,{x:170,y:50},{color:'#0891b2',width:2})
|
||
+ G.angle(V,{x:20,y:120},{x:170,y:50},{color:'#dc2626',r:30,label:'α',fontSize:13})
|
||
+ G.angle(V,{x:170,y:50},{x:240,y:120},{color:'#0891b2',r:30,label:'β',fontSize:13})
|
||
+ G.point(20,120,'A',{color:'#475569',dx:-12,dy:-6})
|
||
+ G.point(240,120,'B',{color:'#475569',dx:8,dy:-6})
|
||
+ G.point(170,50,'C',{color:'#475569',dx:8,dy:-6})
|
||
+ G.point(V.x,V.y,'O',{color:'#475569',dx:-12,dy:18})
|
||
+ '<text x="130" y="20" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">α + β = 180\xB0</text>'
|
||
+ b.close;
|
||
const b2=G.svgBox(320,230,{id:'p6-vert',cell:20});
|
||
const O={x:160,y:115};
|
||
/* Полупрозрачные заливки секторов (для наглядности 4 углов) */
|
||
function wedge(V, p1, p2, r, fill){
|
||
const a1=Math.atan2(p1.y-V.y, p1.x-V.x);
|
||
const a2=Math.atan2(p2.y-V.y, p2.x-V.x);
|
||
let d=a2-a1; while(d>Math.PI) d-=2*Math.PI; while(d<-Math.PI) d+=2*Math.PI;
|
||
const sweep=d>0?1:0;
|
||
const large=Math.abs(d)>Math.PI?1:0;
|
||
const x1=V.x+r*Math.cos(a1), y1=V.y+r*Math.sin(a1);
|
||
const x2=V.x+r*Math.cos(a2), y2=V.y+r*Math.sin(a2);
|
||
return '<path d="M '+V.x+' '+V.y+' L '+x1+' '+y1+' A '+r+' '+r+' 0 '+large+' '+sweep+' '+x2+' '+y2+' Z" fill="'+fill+'"/>';
|
||
}
|
||
const P_R1={x:295,y:50}, P_R2={x:295,y:180}, P_L1={x:30,y:50}, P_L2={x:30,y:180};
|
||
svgVert = b2.open
|
||
/* 4 цветных сектора как фон */
|
||
+ wedge(O, P_R2, P_R1, 28, 'rgba(220,38,38,.16)') /* ∠1 right */
|
||
+ wedge(O, P_L1, P_L2, 28, 'rgba(220,38,38,.16)') /* ∠2 left */
|
||
+ wedge(O, P_R1, P_L1, 28, 'rgba(234,88,12,.16)') /* ∠3 top */
|
||
+ wedge(O, P_L2, P_R2, 28, 'rgba(234,88,12,.16)') /* ∠4 bottom */
|
||
/* Прямые */
|
||
+ G.line({x:30,y:50},{x:295,y:180},{color:'#0891b2',width:2.5})
|
||
+ G.line({x:30,y:180},{x:295,y:50},{color:'#7c3aed',width:2.5})
|
||
/* Дуги углов */
|
||
+ G.angle(O,P_R2,P_R1,{color:'#dc2626',r:20,width:2.2,label:'∠1',fontSize:14,labelOffset:14})
|
||
+ G.angle(O,P_L1,P_L2,{color:'#dc2626',r:20,width:2.2,label:'∠2',fontSize:14,labelOffset:14})
|
||
+ G.angle(O,P_R1,P_L1,{color:'#ea580c',r:32,width:2.2,label:'∠3',fontSize:14,labelOffset:10})
|
||
+ G.angle(O,P_L2,P_R2,{color:'#ea580c',r:32,width:2.2,label:'∠4',fontSize:14,labelOffset:10})
|
||
/* Вершина O и подпись О (далеко от центра) */
|
||
+ G.point(O.x,O.y,'',{r:4,color:'#1e293b'})
|
||
+ '<text x="'+(O.x-26)+'" y="'+(O.y-22)+'" font-size="15" fill="#1e293b" font-weight="800" font-family="Unbounded">O</text>'
|
||
+ '<line x1="'+(O.x-18)+'" y1="'+(O.y-18)+'" x2="'+(O.x-3)+'" y2="'+(O.y-3)+'" stroke="#94a3b8" stroke-width="1" stroke-dasharray="2 2"/>'
|
||
/* Заголовок-легенда */
|
||
+ '<text x="160" y="220" text-anchor="middle" font-size="12" fill="#1e293b" font-weight="800" font-family="Unbounded">∠1=∠2, ∠3=∠4 — вертикальные</text>'
|
||
+ b2.close;
|
||
}
|
||
|
||
html += makeCard('theory', 'Смежные углы', '6.1', `
|
||
<p>Два угла называются <b>смежными</b>, если у них <b>одна сторона общая</b>, а две другие стороны являются <b>дополнительными лучами</b> (лежат на одной прямой).</p>
|
||
<div class="svg-host">`+svgAdj+`</div>`);
|
||
|
||
html += makeCard('rule', 'Свойство смежных углов', '6.2', `
|
||
<p><b>Теорема.</b> Сумма смежных углов равна $180°$.</p>
|
||
<p style="background:var(--sec-acc-soft);padding:10px 14px;border-radius:8px">Из определения: дополнительные лучи образуют развёрнутый угол $= 180°$. Луч между ними разбивает его на два смежных, сумма градусных мер которых равна $180°$.</p>
|
||
<p>Следствия:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>Если смежные углы <b>равны</b>, то каждый из них <b>прямой</b> ($90°$).</li>
|
||
<li>Если два угла равны, то равны и смежные с ними углы.</li>
|
||
</ul>`);
|
||
|
||
html += makeCard('theory', 'Вертикальные углы', '6.3', `
|
||
<p>Два угла называются <b>вертикальными</b>, если стороны одного угла являются <b>дополнительными лучами</b> к сторонам другого.</p>
|
||
<p>Когда две прямые пересекаются в точке $O$ — образуются 4 угла, попарно вертикальные.</p>
|
||
<div class="svg-host">`+svgVert+`</div>`);
|
||
|
||
html += makeCard('rule', 'Свойство вертикальных углов', '6.4', `
|
||
<p><b>Теорема.</b> Вертикальные углы <b>равны</b>.</p>
|
||
<p><b>Доказательство.</b> Пусть $\\angle 1$ и $\\angle 2$ — вертикальные. Тогда углы 1 и 3 смежные ($\\angle 1 + \\angle 3 = 180°$), а углы 2 и 3 тоже смежные ($\\angle 2 + \\angle 3 = 180°$). Отсюда $\\angle 1 = 180° - \\angle 3 = \\angle 2$. Ч. т. д.</p>
|
||
<p>При пересечении двух прямых образуются 4 угла; если один из них равен $90°$, то и остальные равны $90°$.</p>`);
|
||
|
||
html += '<div class="wg" id="p6-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Сумма смежных = 180°</div></div>'
|
||
+'<div class="wg-help">Найди смежный к данному угол.</div>'
|
||
+trainerHTML('p6-iv1', 5, 'градусы')
|
||
+'</div>';
|
||
|
||
html += '<div class="wg" id="p6-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Углы при пересечении прямых</div></div>'
|
||
+'<div class="wg-help">При пересечении двух прямых известен один угол. Найди указанный.</div>'
|
||
+trainerHTML('p6-iv2', 5, 'градусы')
|
||
+'</div>';
|
||
|
||
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">Применяй и смежные, и вертикальные углы. Иногда — пропорции.</div>'
|
||
+trainerHTML('p6-iv3', 4, 'градусы')
|
||
+'</div>';
|
||
|
||
html += secNav('p5', 'p7') + readButton('p6');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
makeTrainer({
|
||
idPrefix:'p6-iv1',
|
||
questions:[
|
||
{ q:'Один из смежных равен $40°$. Найди другой.', a:140 },
|
||
{ q:'Один из смежных равен $75°$. Найди другой.', a:105 },
|
||
{ q:'Один из смежных равен $90°$. Найди другой.', a:90 },
|
||
{ q:'Угол $\\angle MON = 100°$, $\\angle KON$ — смежный с ним. Найди $\\angle KON$.', a:80 },
|
||
{ q:'$\\angle AOP = 130°$. Смежный с ним?', a:50 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p6-iv1');bumpProgress('p6',22);} else if(s>=3){addXp(6,'p6-iv1');bumpProgress('p6',12);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p6-iv2',
|
||
questions:[
|
||
{ q:'Один из углов при пересечении = $20°$. Найди вертикальный.', a:20 },
|
||
{ q:'Один из углов при пересечении = $20°$. Найди смежный.', a:160 },
|
||
{ q:'$\\angle DOB = 30°$ (при пересечении). Найди $\\angle AOD$.', a:150 },
|
||
{ q:'Один из углов = $110°$. Найди смежный.', a:70 },
|
||
{ q:'Один угол при пересечении = $90°$. Чему равны остальные 3?', a:90 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p6-iv2');bumpProgress('p6',28);} else if(s>=3){addXp(8,'p6-iv2');bumpProgress('p6',14);} }
|
||
});
|
||
|
||
makeTrainer({
|
||
idPrefix:'p6-iv3',
|
||
questions:[
|
||
{ q:'Смежные углы относятся $2 : 3$. Найди меньший.', a:72 },
|
||
{ q:'Смежные углы: один на $40°$ больше другого. Найди меньший.', a:70 },
|
||
{ q:'$\\angle BOC = 70°$, $OM, OK$ — биссектрисы смежных $\\angle AOC, \\angle BOC$. Найди $\\angle MOK$.', a:90 },
|
||
{ q:'Из 4 углов при пересечении: $\\angle 1 - \\angle 2 = 28°$ (смежные). Найди $\\angle 1$.', a:104 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(15,'p6-iv3');bumpProgress('p6',28);} else if(s>=2){addXp(8,'p6-iv3');bumpProgress('p6',14);} }
|
||
});
|
||
|
||
wireReadBtn('p6');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
\xA7 7 — Перпендикулярные прямые
|
||
============================================================ */
|
||
function buildP7(){
|
||
const box = document.getElementById('p7-body');
|
||
const G = window.GEOM7;
|
||
let html = '';
|
||
|
||
let svgPerp='';
|
||
if(G){
|
||
const b=G.svgBox(280,180,{id:'p7-perp',cell:20});
|
||
const O={x:140,y:90};
|
||
svgPerp = b.open
|
||
+ G.line({x:30,y:90},{x:250,y:90},{color:'#0891b2',width:2})
|
||
+ G.line({x:140,y:20},{x:140,y:160},{color:'#7c3aed',width:2})
|
||
+ G.rightAngleMark(O,{x:250,y:90},{x:140,y:20},{color:'#dc2626',size:14})
|
||
+ G.point(O.x,O.y,'O',{color:'#475569',dx:-14,dy:14})
|
||
+ '<text x="250" y="86" font-size="13" fill="#0891b2" font-family="JetBrains Mono" font-weight="700">a</text>'
|
||
+ '<text x="146" y="20" font-size="13" fill="#7c3aed" font-family="JetBrains Mono" font-weight="700">b</text>'
|
||
+ '<text x="140" y="174" text-anchor="middle" font-size="11" fill="#475569" font-weight="700" font-family="Unbounded">a ⊥ b</text>'
|
||
+ b.close;
|
||
}
|
||
|
||
html += makeCard('theory', 'Перпендикулярные прямые', '7.1', `
|
||
<p>Две прямые называются <b>перпендикулярными</b>, если они пересекаются <b>под прямым углом</b>.</p>
|
||
<p>Обозначение: $a \\perp b$ — прямые $a$ и $b$ перпендикулярны.</p>
|
||
<p>При пересечении двух перпендикулярных прямых образуются <b>4 прямых угла</b>.</p>
|
||
<p>Отрезки и лучи называются перпендикулярными, если они лежат на перпендикулярных прямых.</p>
|
||
<div class="svg-host">`+svgPerp+`</div>`);
|
||
|
||
html += makeCard('rule', 'Перпендикуляр', '7.2', `
|
||
<p><b>Перпендикуляром</b> к данной прямой называется <b>отрезок</b>, лежащий на прямой, перпендикулярной данной, один из концов которого (<b>основание перпендикуляра</b>) — точка пересечения этих прямых.</p>
|
||
<p>Из точки $M$, не лежащей на прямой $a$, можно провести <b>перпендикуляр $MK$, опущенный</b> на прямую $a$.</p>
|
||
<p>Из точки $P$ на прямой $a$ можно <b>восстановить</b> перпендикуляр $PE$ к прямой $a$.</p>`);
|
||
|
||
html += makeCard('rule', 'Теоремы о единственности', '7.3', `
|
||
<p><b>Теорема 1.</b> Через точку, лежащую на данной прямой, можно провести прямую, перпендикулярную этой прямой, <b>и только одну</b>.</p>
|
||
<p><b>Теорема 2.</b> Через точку, не лежащую на данной прямой, можно провести прямую, перпендикулярную этой прямой, <b>и притом только одну</b>.</p>
|
||
<p>Следствие: на плоскости через любую точку можно провести <b>ровно одну</b> прямую, перпендикулярную данной.</p>`);
|
||
|
||
html += makeCard('rule', 'Две прямые, перпендикулярные третьей', '7.4', `
|
||
<p><b>Теорема.</b> На плоскости две прямые, перпендикулярные третьей, <b>параллельны</b> между собой.</p>
|
||
<p>Если $a \\perp c$ и $b \\perp c$, то $a \\parallel b$.</p>
|
||
<p><b>Доказательство.</b> Если бы $a$ и $b$ пересекались в точке $M$, то через $M$ прошли бы две прямые, перпендикулярные $c$ — это противоречит единственности перпендикуляра.</p>`);
|
||
|
||
html += '<div class="wg" id="p7-iv1">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">$a \\perp b$ — что это значит?</div></div>'
|
||
+'<div class="wg-help">Проверь утверждение: верно или нет.</div>'
|
||
+'<div class="score-display"><span>Задача <b id="p7-iv1-i">1</b> / 5</span><span>Очки: <b id="p7-iv1-s">0</b> / 5</span></div>'
|
||
+'<div id="p7-iv1-q" style="padding:14px;background:var(--sec-acc-soft);border-radius:10px;font-size:1.05rem;text-align:center;margin-bottom:10px"></div>'
|
||
+'<div style="display:flex;gap:10px;justify-content:center"><button class="btn primary" id="p7-iv1-yes" style="background:#10b981;border-color:#10b981">Верно</button><button class="btn primary" id="p7-iv1-no" style="background:#dc2626;border-color:#dc2626">Неверно</button></div>'
|
||
+'<div class="feedback" id="p7-iv1-fb"></div></div>';
|
||
|
||
html += '<div class="wg" id="p7-iv2">'
|
||
+'<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 2</span><div class="wg-title">Задачи с перпендикулярами</div></div>'
|
||
+'<div class="wg-help">Применяй: $a \\perp b$ → угол $= 90°$; используй смежные/вертикальные.</div>'
|
||
+trainerHTML('p7-iv2', 4, 'градусы')
|
||
+'</div>';
|
||
|
||
html += secNav('p6', 'final1') + readButton('p7');
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
(function(){
|
||
const Q=[
|
||
{ e:'Если $a \\perp b$, то углы между ними равны $90°$.', ok:true },
|
||
{ e:'Через точку, не лежащую на прямой, можно провести 2 перпендикуляра.', ok:false },
|
||
{ e:'Если $a \\perp c$ и $b \\perp c$, то $a \\parallel b$.', ok:true },
|
||
{ e:'При пересечении двух перпендикулярных прямых образуются 4 прямых угла.', ok:true },
|
||
{ e:'Через точку на прямой можно провести два перпендикуляра.', ok:false },
|
||
];
|
||
let i=0,score=0;
|
||
function show(){
|
||
if(i>=Q.length){ document.getElementById('p7-iv1-q').innerHTML='<b>Готово!</b> '+score+' / '+Q.length; if(score===Q.length){addXp(12,'p7-iv1');bumpProgress('p7',28);} else if(score>=3){addXp(6,'p7-iv1');bumpProgress('p7',14);} return; }
|
||
document.getElementById('p7-iv1-i').textContent=(i+1);
|
||
document.getElementById('p7-iv1-s').textContent=score;
|
||
document.getElementById('p7-iv1-q').innerHTML=Q[i].e;
|
||
renderMath(document.getElementById('p7-iv1-q'));
|
||
document.getElementById('p7-iv1-fb').style.display='none';
|
||
}
|
||
function ans(isYes){
|
||
if(i>=Q.length) return;
|
||
const fb=document.getElementById('p7-iv1-fb');
|
||
if(isYes===Q[i].ok){ score++; feedback(fb,true,'✓ Верно!'); }
|
||
else feedback(fb,false,'✗ '+(Q[i].ok?'Это верно':'Это неверно'));
|
||
document.getElementById('p7-iv1-s').textContent=score;
|
||
i++; setTimeout(show,1100);
|
||
}
|
||
document.getElementById('p7-iv1-yes').addEventListener('click',()=>ans(true));
|
||
document.getElementById('p7-iv1-no').addEventListener('click',()=>ans(false));
|
||
show();
|
||
})();
|
||
|
||
makeTrainer({
|
||
idPrefix:'p7-iv2',
|
||
questions:[
|
||
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$. Найди $\\angle COM$.', a:42 },
|
||
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$. Найди $\\angle MOD$.', a:132 },
|
||
{ q:'Прямые $a \\perp b$. Сколько острых углов образуется?', a:0 },
|
||
{ q:'$\\angle BAC = 40°$, $AK \\perp AB$. Найди угол между $AK$ и биссектрисой $\\angle BAC$.', a:70 },
|
||
],
|
||
onComplete:(s,n)=>{ if(s===n){addXp(12,'p7-iv2');bumpProgress('p7',22);} else if(s>=2){addXp(6,'p7-iv2');bumpProgress('p7',10);} }
|
||
});
|
||
|
||
wireReadBtn('p7');
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
FINAL 1 — 5 БОССОВ
|
||
============================================================ */
|
||
const BOSSES = [
|
||
{
|
||
n:1, title:'Босс \xA71-2 — Основные понятия', color:'#d97706',
|
||
steps:[
|
||
{ q:'Через сколько точек на плоскости можно провести одну прямую?', verify:(v)=>+v===2, hint:'Аксиома прямой.' },
|
||
{ q:'$AB = 12$. $C$ между $A$ и $B$, $AC = 5$. Найди $CB$.', verify:(v)=>+v===7, hint:'$AC + CB = AB$.' },
|
||
{ q:'Является ли утверждение «Сумма углов треугольника равна 180°» аксиомой? «да»/«нет»', verify:(v)=>['нет','no','н'].includes(String(v).trim().toLowerCase()), hint:'Это теорема (доказывается).' },
|
||
{ q:'$3$ дм $5$ см $= ?$ см', verify:(v)=>+v===35, hint:'1 дм = 10 см.' },
|
||
{ q:'Сколько прописных букв нужно для обозначения отрезка?', verify:(v)=>+v===2, hint:'Две — на концах.' },
|
||
]
|
||
},
|
||
{
|
||
n:2, title:'Босс \xA73-4 — Прямая, луч, окружность', color:'#059669',
|
||
steps:[
|
||
{ q:'Луч имеет $?$ конца (число)', verify:(v)=>+v===1, hint:'Только начало.' },
|
||
{ q:'Точка $A$ на расстоянии 8 см от центра окружности радиусом 6 см. Внутри (1), на (2) или вне (3)?', verify:(v)=>+v===3, hint:'$OA > R$.' },
|
||
{ q:'Диаметр окружности $= 24$ см. Чему равен радиус?', verify:(v)=>+v===12, hint:'$d = 2R$.' },
|
||
{ q:'Длина замкнутой ломаной из 4 равных звеньев по 3 см?', verify:(v)=>+v===12, hint:'$P = 4 \\cdot 3$.' },
|
||
{ q:'Аксиома измерения отрезков: $AC + CB = ?$', verify:(v)=>{const t=String(v).trim().toUpperCase();return t==='AB';}, hint:'Концы отрезка.' },
|
||
]
|
||
},
|
||
{
|
||
n:3, title:'Босс \xA75 — Углы и их виды', color:'#db2777',
|
||
steps:[
|
||
{ q:'Угол $89°$ — острый, прямой, тупой или развёрнутый? Введи слово.', verify:(v)=>String(v).trim().toLowerCase().startsWith('остр'), hint:'$< 90°$.' },
|
||
{ q:'$\\angle BAC = 100°$, $AM$ — биссектриса. Найди $\\angle BAM$.', verify:(v)=>+v===50, hint:'Биссектриса делит пополам.' },
|
||
{ q:'$\\angle BAD = 80°$, $\\angle BAC = 50°$ ($AC$ внутри $\\angle BAD$). Найди $\\angle CAD$.', verify:(v)=>+v===30, hint:'$\\angle BAC + \\angle CAD = \\angle BAD$.' },
|
||
{ q:'Развёрнутый угол $= ?$ градусов', verify:(v)=>+v===180, hint:'По определению.' },
|
||
{ q:'Полный угол $= ?$ градусов', verify:(v)=>+v===360, hint:'Стороны совпадают.' },
|
||
]
|
||
},
|
||
{
|
||
n:4, title:'Босс \xA76 — Смежные и вертикальные', color:'#dc2626',
|
||
steps:[
|
||
{ q:'Один из смежных углов = $35°$. Найди другой.', verify:(v)=>+v===145, hint:'Сумма = 180°.' },
|
||
{ q:'Один из 4 углов при пересечении прямых = $40°$. Чему равен вертикальный к нему?', verify:(v)=>+v===40, hint:'Вертикальные равны.' },
|
||
{ q:'Смежные углы относятся как $4 : 5$. Найди меньший.', verify:(v)=>+v===80, hint:'$4x + 5x = 180°$.' },
|
||
{ q:'Если смежные углы равны, то каждый из них равен $?$', verify:(v)=>+v===90, hint:'$x + x = 180°$.' },
|
||
{ q:'$\\angle 1 + \\angle 3 = 250°$ (рис. с пересечением). Найди $\\angle 1$.', verify:(v)=>+v===125, hint:'Они вертикальные → равны → $2x = 250°$.' },
|
||
]
|
||
},
|
||
{
|
||
n:5, title:'Финальный босс — Перпендикулярные прямые', color:'#ea580c',
|
||
steps:[
|
||
{ q:'$a \\perp b$. Чему равен угол между ними?', verify:(v)=>+v===90, hint:'По определению.' },
|
||
{ q:'Через точку, не на прямой $a$, можно провести $?$ перпендикуляров к $a$.', verify:(v)=>+v===1, hint:'Только один.' },
|
||
{ q:'Если $a \\perp c$ и $b \\perp c$, то $a \\: ? \\: b$. Введи «параллельны» или «перпендикулярны»', verify:(v)=>String(v).trim().toLowerCase().startsWith('парал'), hint:'Теорема о двух перпендикулярах к третьей.' },
|
||
{ q:'$AB \\perp CD$, $\\angle KOB = 48°$ ($K$ на $CD$). Найди $\\angle KOA$.', verify:(v)=>+v===132, hint:'Смежный к $\\angle KOB$.' },
|
||
{ q:'Сколько острых углов образуется при пересечении двух перпендикулярных прямых?', verify:(v)=>+v===0, hint:'Все 4 угла — прямые.' },
|
||
]
|
||
},
|
||
];
|
||
|
||
function buildFinal1(){
|
||
const box = document.getElementById('final1-body');
|
||
let html = '';
|
||
|
||
html += makeCard('theory', 'Что мы изучили', 'Итог', `
|
||
<p>Глава 1 — фундамент геометрии. Мы:</p>
|
||
<ul style="padding-left:22px;line-height:1.85">
|
||
<li>познакомились с <b>точкой, прямой, плоскостью</b> и узнали, что такое <b>аксиома</b> и <b>теорема</b>;</li>
|
||
<li>различили <b>прямую, луч, отрезок</b> и научились применять аксиому измерения $AC + CB = AB$;</li>
|
||
<li>узнали, что <b>окружность</b> — это линия, а <b>круг</b> — часть плоскости;</li>
|
||
<li>выучили <b>виды углов</b> (острый, прямой, тупой, развёрнутый) и <b>биссектрису</b>;</li>
|
||
<li>доказали свойства <b>смежных</b> ($\\alpha + \\beta = 180°$) и <b>вертикальных</b> углов (равны);</li>
|
||
<li>освоили <b>перпендикулярные прямые</b>: $a \\perp b$, единственность перпендикуляра.</li>
|
||
</ul>
|
||
<p>5 боссов — последнее испытание главы.</p>`);
|
||
|
||
html += '<div id="bosses-container"></div>';
|
||
html += '<div style="margin-top:22px;padding:18px 20px;background:linear-gradient(135deg,var(--pri-soft),var(--acc-soft));border-radius:14px;border:1.5px solid var(--pri);text-align:center">'
|
||
+'<div style="font-family:\'Unbounded\',sans-serif;font-weight:800;color:var(--pri2);font-size:1.1rem;margin-bottom:6px">Прогресс по боссам</div>'
|
||
+'<div id="boss-overall" style="font-size:.95rem;color:var(--text);margin-bottom:10px">0 / 5 боссов побеждено</div>'
|
||
+'<div style="height:14px;background:rgba(217,119,6,.12);border-radius:9px;overflow:hidden"><div id="boss-overall-fill" style="height:100%;width:0%;background:linear-gradient(90deg,#d97706,#f59e0b);transition:width .5s"></div></div>'
|
||
+'</div>';
|
||
html += secNav('p7', null);
|
||
box.innerHTML = html; renderMath(box);
|
||
|
||
const cont = document.getElementById('bosses-container');
|
||
const BOSS_STATE = (function(){
|
||
try{ const s=localStorage.getItem('geometry7_ch1_bosses'); if(s) return JSON.parse(s); }catch(e){}
|
||
return BOSSES.map(()=>({stage:0,defeated:false}));
|
||
})();
|
||
function saveBosses(){ try{ localStorage.setItem('geometry7_ch1_bosses', JSON.stringify(BOSS_STATE)); }catch(e){} }
|
||
function refreshOverall(){
|
||
const won=BOSS_STATE.filter(b=>b.defeated).length;
|
||
const txt=document.getElementById('boss-overall'); if(txt) txt.textContent=won+' / '+BOSSES.length+' боссов побеждено';
|
||
const fill=document.getElementById('boss-overall-fill'); if(fill) fill.style.width=(won*100/BOSSES.length)+'%';
|
||
if(won>=BOSSES.length){ bumpProgress('final1',60); achievement('ch1_done','Глава 1 пройдена!'); }
|
||
}
|
||
cont.innerHTML = BOSSES.map((b,idx)=>{
|
||
return '<div class="boss-card" id="boss-card-'+idx+'" style="border-color:'+b.color+'">'
|
||
+'<div class="boss-head">'
|
||
+'<svg viewBox="0 0 24 24" fill="none" stroke="'+b.color+'" stroke-width="2.2" style="width:28px;height:28px"><polygon points="12,2 22,20 2,20"/></svg>'
|
||
+'<div class="boss-title" style="color:'+b.color+'">'+b.title+'</div>'
|
||
+'<div class="boss-stage" id="boss-'+idx+'-stage">Этап 1 / '+b.steps.length+'</div>'
|
||
+'</div>'
|
||
+'<div class="hp-boss" style="border-color:'+b.color+'66;background:'+b.color+'1a"><div class="hp-boss-fill" id="boss-'+idx+'-fill" style="width:0%;background:linear-gradient(90deg,'+b.color+',#f59e0b)"></div></div>'
|
||
+'<div class="boss-q" id="boss-'+idx+'-q" style="border-color:'+b.color+'"></div>'
|
||
+'<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">'
|
||
+'<input type="text" id="boss-'+idx+'-input" class="tinp" placeholder="Ответ" style="width:160px;text-align:center">'
|
||
+'<button class="btn primary" id="boss-'+idx+'-go" style="background:'+b.color+';border-color:'+b.color+'">Атака</button>'
|
||
+'<button class="btn" id="boss-'+idx+'-hint">Подсказка</button>'
|
||
+'<button class="btn" id="boss-'+idx+'-restart">↻</button>'
|
||
+'</div>'
|
||
+'<div class="feedback" id="boss-'+idx+'-fb"></div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
BOSSES.forEach((b,idx)=>{
|
||
function show(){
|
||
const st=BOSS_STATE[idx];
|
||
const stageEl=document.getElementById('boss-'+idx+'-stage');
|
||
const fill=document.getElementById('boss-'+idx+'-fill');
|
||
const q=document.getElementById('boss-'+idx+'-q');
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
if(st.defeated){
|
||
stageEl.textContent='✓ Побеждён'; fill.style.width='100%';
|
||
q.innerHTML='<b style="color:'+b.color+'">Босс повержен!</b>';
|
||
document.getElementById('boss-'+idx+'-go').disabled=true;
|
||
document.getElementById('boss-'+idx+'-go').style.opacity=.5;
|
||
return;
|
||
}
|
||
stageEl.textContent='Этап '+(st.stage+1)+' / '+b.steps.length;
|
||
fill.style.width=(st.stage*100/b.steps.length)+'%';
|
||
q.innerHTML=b.steps[st.stage].q;
|
||
document.getElementById('boss-'+idx+'-input').value='';
|
||
fb.style.display='none';
|
||
renderMath(q);
|
||
}
|
||
document.getElementById('boss-'+idx+'-go').addEventListener('click',()=>{
|
||
const st=BOSS_STATE[idx]; if(st.defeated) return;
|
||
const step=b.steps[st.stage];
|
||
const val=document.getElementById('boss-'+idx+'-input').value;
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
if(!val.trim()){ feedback(fb,false,'✗ Введи ответ.'); return; }
|
||
if(step.verify(val)){
|
||
st.stage++;
|
||
if(st.stage>=b.steps.length){
|
||
st.defeated=true; saveBosses();
|
||
feedback(fb,true,'✓ Босс '+b.n+' побеждён! +20 XP');
|
||
addXp(20,'boss-'+b.n); bumpProgress('final1',18); refreshOverall();
|
||
setTimeout(show,1400);
|
||
}else{
|
||
saveBosses(); feedback(fb,true,'✓ Верно! +3 XP'); addXp(3,'boss-step'); setTimeout(show,1100);
|
||
}
|
||
}else{ feedback(fb,false,'✗ Промах.'); }
|
||
});
|
||
document.getElementById('boss-'+idx+'-hint').addEventListener('click',()=>{
|
||
const st=BOSS_STATE[idx]; if(st.defeated) return;
|
||
const fb=document.getElementById('boss-'+idx+'-fb');
|
||
fb.className='feedback ok';
|
||
fb.innerHTML='<span style="color:#92400e">\u{1F4A1} Подсказка:</span> '+b.steps[st.stage].hint;
|
||
fb.style.display='block';
|
||
fb.style.background='var(--warn-bg)'; fb.style.color='#92400e'; fb.style.borderLeftColor='var(--warn)';
|
||
renderMath(fb);
|
||
});
|
||
document.getElementById('boss-'+idx+'-restart').addEventListener('click',()=>{
|
||
BOSS_STATE[idx]={stage:0,defeated:false}; saveBosses();
|
||
document.getElementById('boss-'+idx+'-go').disabled=false;
|
||
document.getElementById('boss-'+idx+'-go').style.opacity=1;
|
||
show(); refreshOverall();
|
||
});
|
||
show();
|
||
});
|
||
|
||
refreshOverall();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|