feat: #1 Max Verstappen主题 + F1队内无线电 + 连胜系统 + SRS错题复习圈
This commit is contained in:
+263
-46
@@ -6,7 +6,7 @@
|
||||
<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>MV1 赛道数学</title>
|
||||
<title>🏆 #1 世界冠军数学赛道 — Max Verstappen</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@500;700;900&family=Noto+Sans+SC:wght@400;700;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
@@ -82,7 +82,7 @@ body {
|
||||
.cp.done circle { fill:#ffd700; stroke:#ffd700; }
|
||||
|
||||
.cp-label {
|
||||
font-size:9px; font-weight:900; fill:rgba(255,255,255,0.65); letter-spacing:1px;
|
||||
font-size:10px; font-weight:900; fill:rgba(255,255,255,0.65); letter-spacing:1px;
|
||||
transition:all 0.3s; text-anchor:middle; font-family:'Inter',monospace;
|
||||
}
|
||||
.cp.done .cp-label { fill:#ffd700; }
|
||||
@@ -243,6 +243,7 @@ body {
|
||||
.pm .ps { font-size:18px; margin:8px 0 4px; letter-spacing:5px; }
|
||||
.pm .pxp { color:#ffd700; font-weight:900; font-size:16px; margin:2px 0 6px; font-family:'Inter',monospace; }
|
||||
.pm .pms { color:rgba(255,255,255,0.4); font-size:12px; letter-spacing:1px; line-height:1.5; }
|
||||
.pm .pmQuote { font-size:11px; color:rgba(255,255,255,0.2); font-style:italic; margin:2px 0; letter-spacing:0.5px; line-height:1.4; }
|
||||
|
||||
.pm .next-track {
|
||||
margin:16px auto 0; padding:8px 20px; border:none; background:#e10600; color:#fff;
|
||||
@@ -296,6 +297,34 @@ body {
|
||||
}
|
||||
@keyframes spFall { 0%{transform:translateY(-10px) scale(1);opacity:1} 100%{transform:translateY(100vh) scale(0);opacity:0} }
|
||||
|
||||
/* ===== F1 TEAM RADIO ===== */
|
||||
.radio {
|
||||
display:inline-flex; align-items:center; gap:8px;
|
||||
padding:6px 14px; margin:2px 0; border-radius:3px;
|
||||
font-size:12px; font-weight:700; letter-spacing:0.5px;
|
||||
animation:squawk 0.3s cubic-bezier(.34,1.56,.64,1);
|
||||
}
|
||||
.radio.pass { background:rgba(46,204,113,0.08); border-left:3px solid #2ecc71; color:#2ecc71; }
|
||||
.radio.fail { background:rgba(225,6,0,0.05); border-left:3px solid #e10600; color:#e10600; }
|
||||
.radio.streak { background:rgba(255,215,0,0.08); border-left:3px solid #ffd700; color:#ffd700; }
|
||||
.radio .rbl { font-size:16px; }
|
||||
.radio .rbl.s1{animation:blip 0.4s ease 3;}
|
||||
.radio .rbl.s2{animation:blip 0.3s ease 3;}
|
||||
|
||||
.hudExtra { display:flex; gap:10px; justify-content:center; margin-bottom:6px; min-height:22px; }
|
||||
.streakDisplay { font-size:11px; display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:2px; }
|
||||
.streakDisplay.on { background:rgba(255,215,0,0.06); border:1px solid rgba(255,215,0,0.1); color:#ffd700; animation:pop 0.3s ease; }
|
||||
.lapCounter { font-size:10px; color:rgba(255,255,255,0.2); display:inline-flex; align-items:center; gap:4px; font-family:'Inter',monospace; padding:3px 8px; }
|
||||
@keyframes squawk { 0%{opacity:0;transform:scale(0.92) translateX(-8px)} 100%{opacity:1;transform:scale(1) translateX(0)} }
|
||||
@keyframes blip { 0%,100%{opacity:1} 50%{opacity:0.2} }
|
||||
|
||||
.checker {
|
||||
position:absolute; inset:0; pointer-events:none; overflow:hidden; opacity:0.04;
|
||||
background:repeating-conic-gradient(#fff 0% 25%,#000 0% 50%) 0 0 / 20px 20px;
|
||||
animation:scrollBg 8s linear infinite;
|
||||
}
|
||||
@keyframes scrollBg { 0%{background-position:0 0} 100%{background-position:40px 40px} }
|
||||
|
||||
/* ===== RESPONSIVE ===== */
|
||||
@media (max-width:600px) {
|
||||
.app { padding:12px; }
|
||||
@@ -430,7 +459,7 @@ body {
|
||||
<span class="y">MV1</span>
|
||||
</div>
|
||||
<div class="hud-spacer"></div>
|
||||
<div class="hud-stat"><div class="lbl">STAGE</div><div class="val y" id="hStage">1/4</div></div>
|
||||
<div class="hud-stat"><div class="lbl">\u{1F3C6} GP</div><div class="val y" id="hStage">1/4</div></div>
|
||||
<div class="hud-stat"><div class="lbl">PTS</div><div class="val r" id="hPts">0</div></div>
|
||||
<button class="mbtn" id="mBtn">♫</button>
|
||||
</div>
|
||||
@@ -442,10 +471,6 @@ body {
|
||||
<img src="rbcar.png" class="ci" onerror="this.style.display='none';this.nextElementSibling.classList.remove('hide')" onload="this.nextElementSibling.classList.add('hide')" style="display:none">
|
||||
<svg viewBox="0 0 60 28" class="cs" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bodyGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#0a0a3a"/>
|
||||
<stop offset="100%" stop-color="#12124a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wingGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#e10600"/>
|
||||
<stop offset="100%" stop-color="#cc0500"/>
|
||||
@@ -454,17 +479,17 @@ body {
|
||||
<rect x="1" y="5" width="6" height="18" rx="1" fill="url(#wingGrad)"/>
|
||||
<rect x="0" y="4" width="3" height="3" rx="0.5" fill="#ffd700"/>
|
||||
<rect x="0" y="21" width="3" height="3" rx="0.5" fill="#ffd700"/>
|
||||
<rect x="4" y="4" width="4" height="20" rx="1" fill="#0a0a3a" stroke="#e10600" stroke-width="0.3"/>
|
||||
<rect x="4" y="4" width="4" height="20" rx="1" fill="#1e41a0" stroke="#e10600" stroke-width="0.3"/>
|
||||
<rect x="6" y="10" width="4" height="8" rx="0.5" fill="#1a1a3a"/>
|
||||
<path d="M 12,7 L 24,6 Q 28,6 30,8 L 30,20 Q 28,22 24,22 L 12,21 Z" fill="url(#bodyGrad)"/>
|
||||
<path d="M 12,7 L 24,6 Q 28,6 30,8 L 30,20 Q 28,22 24,22 L 12,21 Z" fill="#1e41a0"/>
|
||||
<path d="M 22,6 Q 26,3 32,4 L 32,6 L 24,8 Z" fill="#e10600" opacity="0.3"/>
|
||||
<path d="M 22,22 Q 26,25 32,24 L 32,22 L 24,20 Z" fill="#e10600" opacity="0.3"/>
|
||||
<path d="M 24,4 L 34,4 Q 38,4 40,8 L 40,10 L 24,9 Z" fill="#14144a"/>
|
||||
<path d="M 24,24 L 34,24 Q 38,24 40,20 L 40,18 L 24,19 Z" fill="#14144a"/>
|
||||
<path d="M 30,8 Q 36,7 40,9 L 44,10 Q 48,10 50,11 L 52,13 L 52,15 L 50,17 Q 48,18 44,18 L 40,19 Q 36,21 30,20 Z" fill="url(#bodyGrad)"/>
|
||||
<path d="M 24,4 L 34,4 Q 38,4 40,8 L 40,10 L 24,9 Z" fill="#1e41a0"/>
|
||||
<path d="M 24,24 L 34,24 Q 38,24 40,20 L 40,18 L 24,19 Z" fill="#1e41a0"/>
|
||||
<path d="M 30,8 Q 36,7 40,9 L 44,10 Q 48,10 50,11 L 52,13 L 52,15 L 50,17 Q 48,18 44,18 L 40,19 Q 36,21 30,20 Z" fill="#1e41a0"/>
|
||||
<path d="M 50,11 L 56,12.5 L 57,14 L 56,15.5 L 50,17 Z" fill="#0e0e3e"/>
|
||||
<path d="M 56,12.5 L 59,13.5 L 59,14.5 L 56,15.5 Z" fill="#e10600"/>
|
||||
<path d="M 52,9 L 60,10 L 60,18 L 52,19 Z" fill="#0a0a3a" stroke="#e10600" stroke-width="0.3"/>
|
||||
<path d="M 52,9 L 60,10 L 60,18 L 52,19 Z" fill="#1e41a0" stroke="#e10600" stroke-width="0.3"/>
|
||||
<rect x="58" y="8" width="2" height="4" rx="0.5" fill="#e10600"/>
|
||||
<rect x="58" y="16" width="2" height="4" rx="0.5" fill="#e10600"/>
|
||||
<ellipse cx="42" cy="14" rx="4" ry="4.5" fill="none" stroke="#ffd700" stroke-width="0.8" opacity="0.8"/>
|
||||
@@ -476,8 +501,8 @@ body {
|
||||
<rect x="17" y="23" width="1" height="2" rx="0.5" fill="#e10600"/>
|
||||
<rect x="39" y="3" width="1" height="2" rx="0.5" fill="#e10600"/>
|
||||
<rect x="39" y="23" width="1" height="2" rx="0.5" fill="#e10600"/>
|
||||
<text x="35" y="16" font-family="'Inter',Arial,sans-serif" font-weight="900" font-size="7" fill="#ffd700" text-anchor="middle">1</text>
|
||||
<text x="20" y="20" font-family="'Inter',Arial,sans-serif" font-weight="900" font-size="2.5" fill="#e10600" text-anchor="middle" opacity="0.6">RED BULL</text>
|
||||
<text x="35" y="16" font-family="'Inter',Arial,sans-serif" font-weight="900" font-size="8" fill="#ffd700" text-anchor="middle">#1</text>
|
||||
<text x="20" y="20" font-family="'Inter',Arial,sans-serif" font-weight="900" font-size="2.5" fill="#e10600" text-anchor="middle" opacity="0.8">RED BULL</text>
|
||||
<rect x="8" y="11" width="3" height="6" rx="0.5" fill="#222"/>
|
||||
<circle cx="10" cy="14" r="1.5" fill="#333"/>
|
||||
</svg>
|
||||
@@ -501,8 +526,8 @@ body {
|
||||
<g class="cp" data-idx="0">
|
||||
<circle cx="280" cy="68" r="24" fill="transparent" pointer-events="all"/>
|
||||
<circle cx="280" cy="68" r="14" fill="rgba(225,6,0,0.12)" stroke="rgba(225,6,0,0.3)" stroke-width="2"/>
|
||||
<text class="cp-label" x="280" y="72">SS 1</text>
|
||||
<text class="cp-name" x="280" y="40">有理数</text>
|
||||
<text class="cp-label" x="280" y="72">🏆 #1</text>
|
||||
<text class="cp-name" x="280" y="40">第1站 · 有理数</text>
|
||||
<text class="cp-stars" id="cps0" x="280" y="90"></text>
|
||||
<text class="lock-icon" x="280" y="68" style="display:none">🔒</text>
|
||||
</g>
|
||||
@@ -511,8 +536,8 @@ body {
|
||||
<g class="cp" data-idx="1">
|
||||
<circle cx="80" cy="200" r="24" fill="transparent" pointer-events="all"/>
|
||||
<circle cx="80" cy="200" r="14" fill="rgba(225,6,0,0.12)" stroke="rgba(225,6,0,0.3)" stroke-width="2"/>
|
||||
<text class="cp-label" x="80" y="204">SS 2</text>
|
||||
<text class="cp-name" x="80" y="172">整式的加减</text>
|
||||
<text class="cp-label" x="80" y="204">🏆 #1</text>
|
||||
<text class="cp-name" x="80" y="172">第2站 · 整式的加减</text>
|
||||
<text class="cp-stars" id="cps1" x="80" y="222"></text>
|
||||
<text class="lock-icon" x="80" y="200" style="display:none">🔒</text>
|
||||
</g>
|
||||
@@ -521,8 +546,8 @@ body {
|
||||
<g class="cp" data-idx="2">
|
||||
<circle cx="280" cy="340" r="24" fill="transparent" pointer-events="all"/>
|
||||
<circle cx="280" cy="340" r="14" fill="rgba(225,6,0,0.12)" stroke="rgba(225,6,0,0.3)" stroke-width="2"/>
|
||||
<text class="cp-label" x="280" y="344">SS 3</text>
|
||||
<text class="cp-name" x="280" y="312">一元一次方程</text>
|
||||
<text class="cp-label" x="280" y="344">🏆 #1</text>
|
||||
<text class="cp-name" x="280" y="312">第3站 · 一元一次方程</text>
|
||||
<text class="cp-stars" id="cps2" x="280" y="362"></text>
|
||||
<text class="lock-icon" x="280" y="340" style="display:none">🔒</text>
|
||||
</g>
|
||||
@@ -531,8 +556,8 @@ body {
|
||||
<g class="cp" data-idx="3">
|
||||
<circle cx="120" cy="468" r="24" fill="transparent" pointer-events="all"/>
|
||||
<circle cx="120" cy="468" r="14" fill="rgba(225,6,0,0.12)" stroke="rgba(225,6,0,0.3)" stroke-width="2"/>
|
||||
<text class="cp-label" x="120" y="472">SS 4</text>
|
||||
<text class="cp-name" x="120" y="440">几何图形初步</text>
|
||||
<text class="cp-label" x="120" y="472">🏆 #1</text>
|
||||
<text class="cp-name" x="120" y="440">第4站 · 几何图形初步</text>
|
||||
<text class="cp-stars" id="cps3" x="120" y="490"></text>
|
||||
<text class="lock-icon" x="120" y="468" style="display:none">🔒</text>
|
||||
</g>
|
||||
@@ -542,13 +567,15 @@ body {
|
||||
<div style="text-align:center;margin-top:4px;">
|
||||
<div style="display:inline-block;background:rgba(225,6,0,0.03);border:1px solid rgba(225,6,0,0.05);padding:5px 16px;border-radius:2px;">
|
||||
<span style="font-size:9px;color:rgba(255,255,255,0.35);letter-spacing:2px;font-weight:700;">
|
||||
⚡ 点击检查点发车 | 四站全胜 = 世界冠军
|
||||
⚡ 追随 #1 Max Verstappen 的冠军之路 · 四站全胜 = 世界冠军 🏆
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:center;margin-top:6px;flex-wrap:wrap;">
|
||||
<button class="hudBtn" id="wrongBtn"><span id="hintBadge">0</span>\u{1F4D2} 错题集</button>
|
||||
<button class="hudBtn" id="revLapBtn" style="display:none;background:rgba(255,215,0,0.06);border-color:rgba(255,215,0,0.1);color:#ffd700;">\u{1F3CE}\uFE0F 复习圈</button>
|
||||
<button class="hudBtn hudDanger" id="clrP">\u{1F5D1} 清除进度</button>
|
||||
<button class="hudBtn" id="unlockBtn" style="background:rgba(255,215,0,0.06);border-color:rgba(255,215,0,0.1);color:#ffd700;">🔓 解锁章节</button>
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:6px;margin-bottom:4px;">
|
||||
<button class="hudBtn" id="dbBtn" style="padding:8px 16px;font-size:10px;">\u{1F4CA} 学习总览</button>
|
||||
@@ -563,10 +590,11 @@ body {
|
||||
<button class="bk" id="bkMapBtn" style="margin-bottom:0;background:rgba(255,215,0,0.06);border-color:rgba(255,215,0,0.1);color:#ffd700;">📊 答题地图</button>
|
||||
</div>
|
||||
<div class="ch">
|
||||
<div class="gl" id="cGl">SS 1</div>
|
||||
<div class="gl" id="cGl">🏆 MV1 · 第1站</div>
|
||||
<h2 id="cTi"></h2>
|
||||
<div class="sm" id="cDe"></div>
|
||||
</div>
|
||||
<div class="hudExtra" id="hudExtra"><div class="streakDisplay" id="streakD"></div><div class="lapCounter" id="lapCnt"></div></div>
|
||||
<div id="cCo"></div>
|
||||
</div>
|
||||
|
||||
@@ -601,6 +629,7 @@ body {
|
||||
<!-- PODIUM -->
|
||||
<div class="po" id="po">
|
||||
<div class="pm">
|
||||
<div class="checker" id="pmChk"></div>
|
||||
<img src="mv1.png" class="mv1-photo" id="mv1Img" onerror="this.classList.add('hide');document.getElementById('mv1Av').classList.remove('hide')" onload="document.getElementById('mv1Av').classList.add('hide')">
|
||||
<div class="mv1-av hide" id="mv1Av">MV1</div>
|
||||
<div class="pn1" id="pmN">P1</div>
|
||||
@@ -609,6 +638,8 @@ body {
|
||||
<div class="ps" id="pmSt"></div>
|
||||
<div class="pxp" id="pmXp">+0 PTS</div>
|
||||
<p class="pms" id="pmMs">下个赛段继续冲</p>
|
||||
<div class="radio pmRadio" id="pmRadio"></div>
|
||||
<div class="pmQuote" id="pmQ"></div>
|
||||
<button class="next-track" id="pmNt">下一站 →</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -962,10 +993,12 @@ const SS = [
|
||||
// ── 分站赛题:重点 ──
|
||||
{ type:'ss', tag:'重点', question:'下面哪个数既是分数又是负数?', opts:['\u22123','\u22122.5','0','+1/3'], correct:1, passMsg:'✅ -2.5=-5/2,是分数也是负数', failMsg:'💡 -3是整数;0既不是正也不是负;+1/3是正分数。\u22122.5=-5/2✓' },
|
||||
{ type:'ss', tag:'重点', question:'下面哪个数既不是正数也不是负数?', opts:['\u22123','0','1/2','\u22122.5'], correct:1, passMsg:'✅ 0是正负数的分界线', failMsg:'💡 0是唯一既不是正数也不是负数的数' },
|
||||
{ type:'ss', tag:'重点', question:'下列各数中:-3、2/3、0、-0.5、3.14、-7/2,属于负分数的是', opts:['-3、-0.5','-0.5、-7/2','-7/2、3.14','-3、-7/2'], correct:1, passMsg:'✅ -0.5=-1/2,-7/2=-3.5,都是负分数', failMsg:'💡 负分数=小于0的分数。<br>-3是负整数不是分数;2/3是正分数;0不是正负;3.14是正小数=正分数<br>只有-0.5=-1/2、-7/2是<strong>负分数</strong>' },
|
||||
{ type:'ss', tag:'重点', question:'\u22123 的相反数是什么?', opts:['3','\u22123','±3','0'], correct:0, passMsg:'✅ -3的相反数是+3', failMsg:'💡 相反数就是<strong>符号相反</strong>的数,-3→+3(简写3)' },
|
||||
{ type:'ss', tag:'重点', question:'\u22122 的倒数是?', opts:['2','\u22122','1/2','\u22121/2'], correct:3, passMsg:'✅ -2×(-1/2)=1', failMsg:'💡 倒数=1÷该数,-2→1÷(-2)=<strong>-1/2</strong>' },
|
||||
{ type:'ss', tag:'重点', question:'用科学记数法表示305000正确的是', opts:['3.05×10⁵','3.05×10⁶','0.305×10⁶','305×10³'], correct:0, passMsg:'✅ 305000=3.05×10⁵', failMsg:'💡 小数点向左移5位→3.05×10⁵' },
|
||||
{ type:'ss', tag:'重点', question:'近似数3.14×10⁴精确到', opts:['百分位','十分位','百位','千位'], correct:2, passMsg:'✅ 3.14×10⁴=31400,4在百位', failMsg:'💡 先还原:31400,最后的4在百位→精确到<strong>百位</strong>' },
|
||||
{ type:'ss', tag:'重点', question:'在(-3)⁴中,底数和指数分别是', opts:['-3和4','3和4','-4和3','-3和-4'], correct:0, passMsg:'✅ (-3)⁴底数=-3,指数=4', failMsg:'💡 aⁿ中a是底数(乘的数),n是指数(乘的次数)。<br>(-3)⁴=(-3)×(-3)×(-3)×(-3)=81。注意底数是<strong>-3</strong>不是3!' },
|
||||
// ── 分站赛题:核心 ──
|
||||
{ type:'ss', tag:'核心', diagram:Geo.numLine(30,25,300,[-2,5],{labels:{'-2':'A','5':'B'}}), question:'数轴上,\u22122和5之间的距离是多少?', opts:['3','7','\u22127','\u22123'], correct:1, passMsg:'✅ 距离=|5-(-2)|=7', failMsg:'💡 距离=|大-小|=|5-(-2)|=7,距离永远是<strong>正数</strong>' },
|
||||
{ type:'ss', tag:'核心', diagram:Geo.numLine(30,25,300,[-3,0],{labels:{'-3':'A','0':'O'}}), question:'数轴上表示\u22123的点到原点的距离是', opts:['3','\u22123','±3','0'], correct:0, passMsg:'✅ |-3|=3', failMsg:'💡 点到原点的距离=\|该数\|,\|-3\|=3' },
|
||||
@@ -995,7 +1028,15 @@ const SS = [
|
||||
{ type:'review', question:'有理数综合:下面哪个结果是对的?', opts:['(\u22122)+(\u22123)=\u22125','(\u22123)\u2212(\u22125)=\u22128','(\u22122)×(\u22123)=\u22126','(\u22128)÷(\u22122)=\u22124'], correct:0, passMsg:'✅ (-2)+(-3)=-5', failMsg:'💡 (-3)-(-5)=(-3)+(+5)=2;(-2)×(-3)=6;(-8)÷(-2)=4' },
|
||||
{ type:'review', question:'利用运算律简算:(-2.5)+3.5+2.5+(-4.5)', opts:['\u22121','1','\u22120.5','0'], correct:0, passMsg:'✅ (-2.5+2.5)+(3.5-4.5)=0-1=-1', failMsg:'💡 交换结合:(-2.5+2.5)+(3.5-4.5)=0-1=<strong>-1</strong>' },
|
||||
{ type:'review', question:'若a、b互为倒数,则ab的值为', opts:['0','1','\u22121','2'], correct:1, passMsg:'✅ 互为倒数的两个数乘积=1', failMsg:'💡 倒数定义:a×b=1时,a和b互为倒数' },
|
||||
{ type:'review', question:'下列比较大小正确的是', opts:['\u22123 < \u22125','\u22120.5 > \u22121','\u22127 > 0','\u22124 < \u22126'], correct:1, passMsg:'✅ -0.5>-1(绝对值小的反而大)', failMsg:'💡 负数比较:绝对值大的反而小。\n-0.5的绝对值0.5 < -1的绝对值1→-0.5 > -1' }
|
||||
{ type:'review', question:'下列比较大小正确的是', opts:['\u22123 < \u22125','\u22120.5 > \u22121','\u22127 > 0','\u22124 < \u22126'], correct:1, passMsg:'✅ -0.5>-1(绝对值小的反而大)', failMsg:'💡 负数比较:绝对值大的反而小。\n-0.5的绝对值0.5 < -1的绝对值1→-0.5 > -1' },
|
||||
// ── 章末锦囊 ──
|
||||
{ type:'ms', icon:'📚', title:'有理数·章末锦囊',
|
||||
kv:[
|
||||
['🧠 知识树','正负数→数轴→相反数/绝对值→四则运算→倒数→乘方→科学记数法'],
|
||||
['📝 核心口诀','零上为正零下为负,零是分界线\n数轴一条线,右边总比左边大\n相反数照镜子,符号相反值一样\n绝对值看距离,永远不取负值\n同号相加符号不变、绝对值相加\n异号相加符号取大、绝对值相减\n减去一个数≡加它的相反数\n同号得正异号得负,乘除通用\n乘积为一是倒数,零没倒数\n奇次方符号不变,偶次方负号不见\n科学记数a×10ⁿ,1≤|a|<10'],
|
||||
['⚠️ 易错TOP3','① -1⁴≠(-1)⁴:-1⁴=-(1⁴)=-1,(-1)⁴=1\n② 异号相加取符号:取绝对值大的符号,不是被减数\n③ 绝对值去掉忘变号:\|负数\|=它的相反数'],
|
||||
['💡 解题通法','含\|A\|+B²=0→各自为0\n两个负数比大小→绝对值大的反而小\n相反数→a+b=0;倒数→cd=1\n非负性:\|A\|≥0,A²≥0']
|
||||
]},
|
||||
]},
|
||||
// ═══════════════════════════════════════
|
||||
// CHAPTER 2: 整式的加减
|
||||
@@ -1121,11 +1162,20 @@ const SS = [
|
||||
{ type:'ss', tag:'综合', question:'已知a²-a-1=0,求a³-2a+5的值', opts:['5','6','7','4'], correct:1, passMsg:'✅ a²=a+1,a³=a×a²=a(a+1)=a²+a=(a+1)+a=2a+1;原式=2a+1-2a+5=6', failMsg:'💡 整体代换:a²=a+1→a³=a(a+1)=a²+a=(a+1)+a=2a+1→原式=6' },
|
||||
{ type:'ss', tag:'综合', question:'一个多项式加上-3x²+2x-5得2x²-x+3,求这个多项式', opts:['5x²-3x+8','-5x²+3x-8','5x²-3x-8','5x²+3x-8'], correct:0, passMsg:'✅ 被加式=和-加式=(2x²-x+3)-(-3x²+2x-5)=5x²-3x+8', failMsg:'💡 被加式+加式=和→被加式=和-加式。注意减多项式要变号!' },
|
||||
{ type:'ss', tag:'综合', question:'已知|x+2|+(y-3)²=0,求多项式2xy²-3x²y的值', opts:['-54','36','-6','54'], correct:0, passMsg:'✅ x+2=0→x=-2;y-3=0→y=3;代入:2(-2)(9)-3(4)(3)=-36-36=-72...验算:2×(-2)×9=-36;-3×4×3=-36;和=-72', failMsg:'💡 非负性→x=-2,y=3。代入计算注意符号!' },
|
||||
{ type:'ss', tag:'综合', question:'长方形长3x+2、宽2x,右上角挖去一个边长x的正方形,剩余面积是', opts:['6x²+4x','6x²+4x-x²=5x²+4x','6x+4','3x²+2x'], correct:1, passMsg:'✅ 大长方形=(3x+2)(2x)=6x²+4x,减x²得5x²+4x', failMsg:'💡 长方形面积=长×宽=(3x+2)×2x=<strong>6x²+4x</strong><br>挖去x²→剩余=6x²+4x-x²=<strong>5x²+4x</strong>' },
|
||||
// ── 章节复习 ──
|
||||
{ type:'review', question:'单项式3ab的系数和次数是', opts:['3和1','3和2','1和2','3和0'], correct:1, passMsg:'✅ 系数3,次数=a¹b¹=2', failMsg:'💡 系数=数字→3;次数=a¹+b¹=1+1=2' },
|
||||
{ type:'review', question:'下列合并同类项正确的是', opts:['3x+5x=8x²','2a+3b=5ab','5x²-2x²=3','-2ab+2ba=0'], correct:3, passMsg:'✅ -2ab+2ba=0(3ab与4ba是同类项)', failMsg:'💡 A应为8x(指数不变);B不能合并;C应为3x²' },
|
||||
{ type:'review', question:'化简:(5a²-3b²)-2(a²-b²)-(a²-5b²)=', opts:['2a²+0b²=2','2a²+4b²=18','6a²+4b²=22','2a²-4b²=-14'], correct:1, passMsg:'✅ 化简得2a²+4b²,代入a=-1,b=2得18', failMsg:'💡 =5a²-3b²-2a²+2b²-a²+5b²=(5-2-1)a²+(-3+2+5)b²=2a²+4b²' },
|
||||
{ type:'review', question:'三角形边长分别为2x+1、3x-2、x+3,则周长为', opts:['6x+2','6x-2','6x+6','5x+2'], correct:0, passMsg:'✅ 周长=(2x+1)+(3x-2)+(x+3)=6x+2', failMsg:'💡 周长=三边之和。2x+3x+x=6x;1-2+3=2→6x+2' }
|
||||
{ type:'review', question:'三角形边长分别为2x+1、3x-2、x+3,则周长为', opts:['6x+2','6x-2','6x+6','5x+2'], correct:0, passMsg:'✅ 周长=(2x+1)+(3x-2)+(x+3)=6x+2', failMsg:'💡 周长=三边之和。2x+3x+x=6x;1-2+3=2→6x+2' },
|
||||
// ── 章末锦囊 ──
|
||||
{ type:'ms', icon:'📚', title:'整式的加减·章末锦囊',
|
||||
kv:[
|
||||
['🧠 知识树','单项式(系数+次数)→多项式(项+次数)→同类项→合并→去括号→整式加减应用'],
|
||||
['📝 核心口诀','系数看数字,次数看字母指数和\n单独一个数也是单项式(次数0)\n同类项看"长相":字母相同指数相同\n合并同类项:系数相加,字母指数照抄\n去括号=拆快递:+直接开箱不变,-里面全变号\n括号前有系数:分配律逐项乘\n整式加减三步走:去括号→合并→代入'],
|
||||
['⚠️ 易错TOP3','① π是常数不是字母!-πx²y/3系数=-π/3\n② 去括号忘变号:-(a-b)=-a+b,不是-a-b\n③ 单项式次数漏加:-3x²y次数=2+1=3,不是2'],
|
||||
['💡 解题通法','整体代入:相同部分看作整体\n不含某项→该项系数=0\n降幂排列:从高次到低次\n化简求值:先化简再代入']
|
||||
]},
|
||||
]},
|
||||
// ═══════════════════════════════════════
|
||||
// CHAPTER 3: 一元一次方程
|
||||
@@ -1247,6 +1297,7 @@ const SS = [
|
||||
{ type:'ss', tag:'重点', question:'若x=2是方程3x-4=mx的解,则m=', opts:['1','2','-1','0'], correct:0, passMsg:'✅ 代入:6-4=2m→2=2m→m=1', failMsg:'💡 把x=2代入方程:3×2-4=m×2→2=2m→m=1' },
|
||||
{ type:'ss', tag:'重点', question:'根据等式性质,由x=y可推出', opts:['x+2=y-2','2x=3y','x/2=y/2','x²=y²'], correct:2, passMsg:'✅ 两边同÷2→x/2=y/2', failMsg:'💡 性质2:两边同乘/除同一个不为0的数,等式仍成立' },
|
||||
{ type:'ss', tag:'重点', question:'解方程2(x-2)-3(4x-1)=9(1-x),x=', opts:['x=-10','x=10','x=-1','x=1'], correct:0, passMsg:'✅ 展开:2x-4-12x+3=9-9x→-10x-1=9-9x→-x=10→x=-10', failMsg:'💡 去括号仔细:-3(4x-1)=-12x+3;9(1-x)=9-9x' },
|
||||
{ type:'ss', tag:'重点', question:'一个两位数,十位数字是x,个位数字是y,这个两位数是', opts:['x+y','10xy','10x+y','x+10y'], correct:2, passMsg:'✅ 十位×10+个位=10x+y', failMsg:'💡 十位上的x代表x个10→10x;个位上的y代表y个1→y。两位数=<strong>10x+y</strong><br>例:x=3,y=5→10×3+5=35✓' },
|
||||
// ── 核心题 ──
|
||||
{ type:'ss', tag:'核心', question:'车间28人生产螺栓螺母,每人每天做螺栓12个或螺母18个。1螺栓配2螺母。设x人做螺栓,方程正确的是', opts:['18(28-x)=2×12x','12x=2×18(28-x)','2×12x=18(28-x)','12x=18(28-x)'], correct:0, passMsg:'✅ 螺母数=2×螺栓数→18(28-x)=2×12x', failMsg:'💡 螺栓=12x,螺母=18(28-x),1配2→螺母=2×螺栓' },
|
||||
{ type:'ss', tag:'核心', question:'商品进价100元,标价150元,打八折出售,利润为', opts:['10元','20元','30元','50元'], correct:1, passMsg:'✅ 售价=150×0.8=120,利润=120-100=20元', failMsg:'💡 利润=售价-进价。售价=标价×折扣=120,120-100=20' },
|
||||
@@ -1266,11 +1317,20 @@ const SS = [
|
||||
{ type:'ss', tag:'综合', question:'甲、乙两地相距300km,快车80km/h,慢车60km/h。快车先开30分钟,两车相向而行,慢车开出几小时后相遇?', opts:['2小时','1.5小时','2.5小时','3小时'], correct:0, passMsg:'✅ 快车先走80×0.5=40km。设t小时:80t+60t=300-40→140t=260→t=1.86...→约2小时', failMsg:'💡 快车先走40km,剩余260km。速度和140km/h→260÷140≈1.86小时' },
|
||||
{ type:'ss', tag:'综合', question:'某校七年级共8个班进行足球赛,胜得3分、平得1分、负得0分。七(1)班比赛全部结束,共得17分,其中负了2场,这个班胜了几场?', opts:['5场','6场','4场','3场'], correct:1, passMsg:'✅ 共赛7场(8队单循环每队7场)。负2场→设胜x场:3x+(7-2-x)=17→3x+5-x=17→2x=12→x=6', failMsg:'💡 8队单循环每队赛7场。胜x场→平7-2-x=5-x场。3x+(5-x)=17→x=6' },
|
||||
{ type:'ss', tag:'综合', question:'小王将一笔钱存入银行1年,年利率2.5%,到期后本息和5125元,本金是', opts:['5000元','5100元','5025元','5050元'], correct:0, passMsg:'✅ 设本金x:x+2.5%x=5125→1.025x=5125→x=5000', failMsg:'💡 x+0.025x=1.025x=5125→x=5000' },
|
||||
{ type:'ss', tag:'综合', question:'某市出租车:起步价8元(3km内),超过3km部分按2.4元/km计费。行驶x km(x>3)的费用是', opts:['8+2.4x','8+2.4(x-3)','2.4x','2.4(x-3)'], correct:1, passMsg:'✅ 8+2.4(x-3)=2.4x+0.8', failMsg:'💡 3km以内付8元;超出部分=(x-3)km,收2.4(x-3)元<br>总价=8+2.4(x-3)。x=10时代入:8+2.4×7=8+16.8=<strong>24.8元</strong>' },
|
||||
// ── 章节复习 ──
|
||||
{ type:'review', question:'下列解方程步骤正确的是', opts:['3x+5=0→3x=5','4x-2=x→4x-x=2','2x=6→x=3','x/2=3→x=3/2'], correct:2, passMsg:'✅ 2x=6→x=3✓(两边同÷2)', failMsg:'💡 A应为3x=-5;B应为4x-x=2→3x=2→x=2/3;C✓;D应为x=6' },
|
||||
{ type:'review', question:'甲乙两地240km,一轮船往返需10小时(顺流5小时,逆流5小时),求船速和水速', opts:['船24km/h 水0','船26km/h 水2','船25km/h 水1','船28km/h 水4'], correct:0, passMsg:'✅ 顺=240÷5=48,逆=240÷5=48→船速48,水速0(静水)', failMsg:'💡 顺流=船+水=48,逆流=船-水=48→船速=48,水速=0(静水行船)' },
|
||||
{ type:'review', question:'某电信公司话费:A方案无月租0.4元/分,B方案月租18元+0.2元/分。每月通话多少分钟时两方案费用相同?', opts:['60分','90分','120分','150分'], correct:1, passMsg:'✅ 设x分钟:0.4x=18+0.2x→0.2x=18→x=90', failMsg:'💡 A=0.4x,B=18+0.2x。相等→0.4x=18+0.2x→x=90分钟' },
|
||||
{ type:'review', question:'某商品标价200元,打八折后仍获利60元,进价是', opts:['120元','100元','80元','140元'], correct:1, passMsg:'✅ 售价=160,进价x:160-x=60→x=100', failMsg:'💡 售价=200×0.8=160;利润=售价-进价=60→进价=100' }
|
||||
{ type:'review', question:'某商品标价200元,打八折后仍获利60元,进价是', opts:['120元','100元','80元','140元'], correct:1, passMsg:'✅ 售价=160,进价x:160-x=60→x=100', failMsg:'💡 售价=200×0.8=160;利润=售价-进价=60→进价=100' },
|
||||
// ── 章末锦囊 ──
|
||||
{ type:'ms', icon:'📚', title:'一元一次方程·章末锦囊',
|
||||
kv:[
|
||||
['🧠 知识树','等式性质→去分母→去括号→移项→合并→系数化1→应用题(配套/工程/行程/销售/方案/积分)'],
|
||||
['📝 核心口诀','方程是含未知数的等式,解方程就是求x\n去分母:每项都要乘公分母(常数项也要!)\n去括号:正不变号、负全变号\n移项过桥要变号!+变-,-变+\n系数化1:同除以x前面的数\n应用题四步走:设→列→解→验(写答!)'],
|
||||
['⚠️ 易错TOP3','① 去分母漏乘常数项:=1、=2等也要乘以公分母\n② 移项不变号:移到等号另一边要变号\n③ 设未知数不写单位:设x小时,不是设x'],
|
||||
['💡 解题通法','行程:相向用速度和,追及用速度差\n配套:甲×n=乙(1配n)\n工程:总量=1,效率=1/时间\n销售:利润=售价-进价\n方案:列等式找临界点\n比赛积分:胜×3+平×1+负×0=总分']
|
||||
]},
|
||||
]},
|
||||
// ═══════════════════════════════════════
|
||||
// CHAPTER 4: 几何图形初步
|
||||
@@ -1439,6 +1499,7 @@ const SS = [
|
||||
{ type:'ss', tag:'核心', question:'一个角比它的补角小40°,这个角是', opts:['110°','70°','55°','35°'], correct:1, passMsg:'✅ x=(180-x)-40→x=70°', failMsg:'💡 x=180-x-40→2x=140→x=<strong>70°</strong>。验算:70+110=180✓' },
|
||||
{ type:'ss', tag:'核心', question:'点C在直线AB上,AB=8,BC=3,则AC等于', opts:['8 或 3','5','11','5 或 11'], correct:3, passMsg:'✅ C在AB间→AC=5;C在AB延长线上→AC=11', failMsg:'💡 没说"在线段上"!分类讨论:①C在AB间→5;②C在B右侧→11' },
|
||||
{ type:'ss', tag:'核心', question:'∠AOB=80°,OC平分∠AOB,OD平分∠BOC,则∠AOD=', opts:['80°','40°','60°','20°'], correct:2, passMsg:'✅ ∠AOC=40°,∠COD=20°,∠AOD=60°', failMsg:'💡 OC平分80°→∠AOC=40°,OD平分40°→∠COD=20°→∠AOD=60°' },
|
||||
{ type:'ss', tag:'核心', question:'计算:42°18′+37°52′=', opts:['80°10′','79°70′','80°70′','79°10′'], correct:0, passMsg:'✅ 42°18′+37°52′=79°70′=80°10′(70′=1°10′)', failMsg:'💡 度+度,分+分:18′+52′=70′=1°10′;42°+37°+1°=<strong>80°10′</strong><br>牢记度分秒<strong>60进制</strong>!满60进1!' },
|
||||
// ── 高频题 ──
|
||||
{ type:'ss', tag:'高频', question:'AM:MB=3:2,AN:NB=5:1,M、N在线段AB上,MN=7,则AB=', opts:['AB=25','AB=28','AB=30','AB=35'], correct:2, passMsg:'✅ 双设k→AB=30', failMsg:'💡 AM:MB=3:2→AB=5k;AN:NB=5:1→AB=6t→5k=6t→t=5k/6<br>MN=|3k-5t|=7k/6=7→k=6→AB=30' },
|
||||
{ type:'ss', tag:'高频', question:'2:15到2:45,时针转过的角度是', opts:['180°','30°','15°','90°'], correct:2, passMsg:'✅ 30分钟×0.5°/分=15°', failMsg:'💡 时针每分钟转0.5°,30分钟×0.5°=<strong>15°</strong>。分针转180°但时针只转15°' },
|
||||
@@ -1446,6 +1507,7 @@ const SS = [
|
||||
{ type:'ss', tag:'高频', question:'C在线段AB上,AC:CB=2:3,M是AC中点,N是CB中点。MN=5,求AB', opts:['AB=8','AB=10','AB=12','AB=15'], correct:1, passMsg:'✅ 设k→AB=10', failMsg:'💡 AC=2k,CB=3k→AB=5k。MC=k,CN=1.5k→MN=2.5k=5→k=2→AB=10' },
|
||||
{ type:'ss', tag:'高频', question:'正方体的展开图不可能是', opts:['一四一型','二三一型','三三型','五连方'], correct:3, passMsg:'✅ 没有5个连在一起的展开图', failMsg:'💡 正方体展开图共11种:一四一(6)、二三一(3)、二二二(1)、三三(1)。五连方不行!' },
|
||||
{ type:'ss', tag:'高频', question:'下列哪个是正方体展开图?', opts:['田字形','L形','一四一型','T字形'], correct:2, passMsg:'✅ "一四一"是正方体展开图', failMsg:'💡 展开图口诀:一四一、二三一、二二二、三三。田字、L形、T字都不是' },
|
||||
{ type:'ss', tag:'高频', question:'正方体展开图如图,"红"字的对面是', opts:['"黄"字', '"蓝"字', '"绿"字', '"白"字'], correct:1, passMsg:'✅ 隔一个正方形的是对面,红→蓝', failMsg:'💡 展开图找对面:"隔一个"就是对面。<br>想象折回正方体:红和蓝中间隔一个正方形→<strong>红对蓝</strong><br>口诀:<strong>隔一相对,Z型两端</strong>' },
|
||||
// ── 综合题 ──
|
||||
{ type:'ss', tag:'综合', question:'从O点引三条射线OA、OB、OC,∠AOB=80°,∠BOC=50°,则∠AOC=', opts:['30°或130°','130°','30°','50°'], correct:0, passMsg:'✅ OC在内→30°;OC在外→130°', failMsg:'💡 分类:①OC在∠AOB内→80-50=30°;②OC在外→80+50=130°' },
|
||||
{ type:'ss', tag:'综合', question:'钟面上3点多少分时时针分针第一次重合?', opts:['16分','16分21秒','16分11秒','15分'], correct:2, passMsg:'✅ 90=5.5x→x≈16.36分=16分22秒', failMsg:'💡 |30×3-5.5x|=0→90-5.5x=0→x≈16.36分=16分22秒' },
|
||||
@@ -1456,7 +1518,15 @@ const SS = [
|
||||
{ type:'review', question:'若∠A=60°,则∠A的补角比余角大', opts:['30°','60°','90°','120°'], correct:2, passMsg:'✅ 补角=120°,余角=30°,差=90°', failMsg:'💡 补角=180-60=120°,余角=90-60=30°,120-30=<strong>90°</strong>' },
|
||||
{ type:'review', question:'线段AB=10,AC=4,M是AC中点,N是BC中点,则MN=', opts:['3','5','7','2'], correct:1, passMsg:'✅ MC=2,BC=6,CN=3,MN=5', failMsg:'💡 AC=4→MC=2;BC=6→CN=3;MN=2+3=<strong>5</strong>' },
|
||||
{ type:'review', question:'在直线l上取A、B、C三点,AB=5,BC=3,则AC=', opts:['2或8','8','2','无法确定'], correct:0, passMsg:'✅ C在AB间→2;C在AB外→8', failMsg:'💡 分类:①C在A、B之间→5-3=2;②C在B右侧→5+3=8' },
|
||||
{ type:'review', question:'钟面上9:30时针与分针的夹角', opts:['90°','105°','75°','120°'], correct:1, passMsg:'✅ |30×9-5.5×30|=|270-165|=105°', failMsg:'💡 公式:\|30h-5.5m\|=\|270-165\|=<strong>105°</strong>' }
|
||||
{ type:'review', question:'钟面上9:30时针与分针的夹角', opts:['90°','105°','75°','120°'], correct:1, passMsg:'✅ |30×9-5.5×30|=|270-165|=105°', failMsg:'💡 公式:\|30h-5.5m\|=\|270-165\|=<strong>105°</strong>' },
|
||||
// ── 章末锦囊 ──
|
||||
{ type:'ms', icon:'📚', title:'几何图形初步·章末锦囊',
|
||||
kv:[
|
||||
['🧠 知识树','立体图形(展开图)→线段/射线/直线→角(度分秒)→余角/补角→方位角→钟面角'],
|
||||
['📝 核心口诀','线段两个点,射线一个点,直线没有点\n两点确定一条直线,两点之间线段最短\n度分秒60进制,借1°换60′\n余角和为90°,补角和为180°\n展开图:一四一、二三一、二二二、三三(共11种)\n对面不相邻,隔一个就是对面'],
|
||||
['⚠️ 易错TOP3','① 射线AB和BA方向相反→端点不同\n② 度分秒用十进制→60进制!42°18′≠42.18°\n③ 看到"直线"不分类→一定想两种情况'],
|
||||
['💡 解题通法','双中点→MN=AB/2,C不管在哪\n角平分线→看到平分就÷2\n分类讨论→看到"直线"马上想两种情况\n钟面角公式:\|30h-5.5m\|\n方位角互看:南北交换、东西交换、角度不变']
|
||||
]},
|
||||
]},
|
||||
];
|
||||
|
||||
@@ -1471,18 +1541,24 @@ const CP_POS = [
|
||||
const SVG_PATH = 'M 180,20 C 320,20 320,120 180,140 C 40,160 40,260 180,280 C 320,300 320,400 180,420 C 40,440 40,520 180,520';
|
||||
|
||||
// ===== 状态 =====
|
||||
let S = { pts:0, stars:[0,0,0,0], curSS:0, curSeg:0, wrongQ:[], doneQ:[], seenQ:[], viewMode:'normal', wrIdx:0 };
|
||||
let S = { pts:0, stars:[0,0,0,0], curSS:0, curSeg:0, wrongQ:[], wrongQMap:{}, doneQ:[], seenQ:[], viewMode:'normal', wrIdx:0, streak:0 };
|
||||
function ld() { try { const d=localStorage.getItem('rb2'); if(d){const p=JSON.parse(d);Object.assign(S,p);} }catch(e){} }
|
||||
function sv() { try { localStorage.setItem('rb2',JSON.stringify(S)); }catch(e){} }
|
||||
ld();
|
||||
|
||||
// 调试解锁:在 URL 后加 #debug-unlock 自动解锁全部章节
|
||||
if (location.hash === '#debug-unlock') {
|
||||
S.stars = [3,3,3,0]; sv();
|
||||
history.replaceState(null, '', 'index.html');
|
||||
}
|
||||
|
||||
// ===== DOM =====
|
||||
const $ = id => document.getElementById(id);
|
||||
const qs = (s,el) => (el||document).querySelector(s);
|
||||
const page = id => { document.querySelectorAll('.pg').forEach(p=>p.classList.remove('act')); $(id).classList.add('act'); };
|
||||
|
||||
// ===== 导航 =====
|
||||
function goMap() { page('pH'); sync(); renderMap(); }
|
||||
function goMap() { S.viewMode='normal'; page('pH'); sync(); renderMap(); }
|
||||
function goChap(idx) {
|
||||
if (idx<0 || idx>3) return;
|
||||
if (idx>0 && S.stars[idx-1]===0) return;
|
||||
@@ -1585,12 +1661,18 @@ document.addEventListener('click', e => {
|
||||
if (closest(e.target, '#egBtn')) { stepEg(); return; }
|
||||
|
||||
const actEl = closest(e.target, '.act');
|
||||
if (actEl && actEl.classList.contains('show')) { nextSeg(); return; }
|
||||
if (actEl && actEl.classList.contains('show')) {
|
||||
if (S.viewMode === 'wrongReviewSession') { nextWrongInReview(); return; }
|
||||
nextSeg(); return;
|
||||
}
|
||||
|
||||
if (closest(e.target, '#pmNt')) { closePodium(); return; }
|
||||
|
||||
if (closest(e.target, '#mBtn')) { toggleBGM(); return; }
|
||||
|
||||
if (closest(e.target, '#revStart')) { startWrongReview(); return; }
|
||||
if (closest(e.target, '#revLapBtn')) { startWrongReview(); return; }
|
||||
|
||||
const wrRm = closest(e.target, '.wrRm'); if (wrRm) {
|
||||
const id = wrRm.getAttribute('data-id'); const wi = S.wrongQ.indexOf(id);
|
||||
if (wi>=0) { S.wrongQ.splice(wi,1); if (S.wrIdx>=S.wrongQ.length) S.wrIdx=0; }
|
||||
@@ -1623,10 +1705,11 @@ document.addEventListener('click', e => {
|
||||
}
|
||||
if (closest(e.target, '#stgDone')) { renderStgResult(); return; }
|
||||
if (closest(e.target, '#bkMapW')) { S.viewMode='normal'; goMap(); return; }
|
||||
if (closest(e.target, '#unlockBtn')) { S.stars=[3,3,3,0]; sv(); goMap(); return; }
|
||||
if (closest(e.target, '#clrP')) {
|
||||
if (confirm('\u{1F6D1} 确定清除所有进度?本地记录将被清空。')) {
|
||||
localStorage.removeItem('rb2');
|
||||
Object.assign(S,{pts:0,stars:[0,0,0,0],curSS:0,curSeg:0,wrongQ:[],doneQ:[],seenQ:[],viewMode:'normal',wrIdx:0});
|
||||
Object.assign(S,{pts:0,stars:[0,0,0,0],curSS:0,curSeg:0,wrongQ:[],wrongQMap:{},doneQ:[],seenQ:[],viewMode:'normal',wrIdx:0,streak:0});
|
||||
sv(); goMap();
|
||||
}
|
||||
return;
|
||||
@@ -1737,7 +1820,9 @@ function renderSeg() {
|
||||
if (!seg) return finishChap();
|
||||
const co = $('cCo');
|
||||
co.style.animation = 'none'; void co.offsetHeight;
|
||||
$('cGl').textContent = `SS ${S.curSS+1} \u00B7 ${segLabel(seg,S.curSeg)}`;
|
||||
$('cGl').textContent = S.viewMode==='wrongReviewSession'
|
||||
? `\u{1F3CE}\uFE0F \u{1F4D2} 复习圈 · ${S.wrIdx+1}/${S.wrongQ.length} · ${segLabel(seg,S.curSeg)}`
|
||||
: `🏆 MV1 · 第${S.curSS+1}站 · ${segLabel(seg,S.curSeg)}`;
|
||||
$('cTi').textContent = s.emoji+' '+s.title;
|
||||
$('cDe').textContent = s.desc;
|
||||
const mBtn = $('bkMapBtn');
|
||||
@@ -1746,6 +1831,7 @@ function renderSeg() {
|
||||
else if (seg.type==='stg') renderStg(seg);
|
||||
else if (seg.type==='pn') renderPN(seg);
|
||||
else if (seg.type==='ss'||seg.type==='review') renderSSeg(seg);
|
||||
else if (seg.type==='ms') renderMS(seg);
|
||||
else if (seg.type==='rd') renderRd(seg);
|
||||
else if (seg.type==='eg') renderEg(seg);
|
||||
co.style.animation = 'fIn 0.35s ease';
|
||||
@@ -1757,6 +1843,21 @@ function renderKP(seg) {
|
||||
initDragModels();
|
||||
}
|
||||
|
||||
// ===== 章末锦囊 =====
|
||||
function renderMS(seg) {
|
||||
let html = `<div class="pn" style="border-left:3px solid #2ecc71;"><div class="kpTitle">${seg.icon||'📚'} ${seg.title}</div>`;
|
||||
(seg.kv||[]).forEach(function(pair){
|
||||
const [key, val] = pair;
|
||||
const lines = val.split('\n');
|
||||
html += `<div style="margin:6px 0;"><span style="font-weight:700;color:#ffd700;">${key}</span><div style="padding:4px 8px;background:rgba(255,255,255,0.03);border-radius:3px;font-size:13px;line-height:1.8;">`;
|
||||
lines.forEach(function(l){ html += `<div>${l}</div>`; });
|
||||
html += `</div></div>`;
|
||||
});
|
||||
html += `<div style="text-align:center;margin-top:14px;"><button class="act show">继续 →</button></div></div>`;
|
||||
$('cCo').innerHTML = html;
|
||||
beep('click');
|
||||
}
|
||||
|
||||
// ===== 分站赛 =====
|
||||
let stgStat = { total:0, done:0, cor:0, wrg:0 };
|
||||
function stgReset() {
|
||||
@@ -1840,7 +1941,11 @@ function renderSSeg(seg) {
|
||||
progHtml = `<div class="stgBar" style="margin:0 0 8px"><div class="stgFill" style="width:${pct}%"></div></div><div style="font-size:10px;color:rgba(255,255,255,0.2);margin-bottom:6px;font-family:'Inter',monospace;"><span style="color:#ffd700">${seg.tag}</span> · ${stgStat.done-1}/${stgStat.total} 答 · ${stgStat.cor}✓ ${stgStat.wrg}✗</div>`;
|
||||
}
|
||||
const tagBadge = seg.tag ? `<span style="display:inline-block;padding:1px 6px;border:1px solid rgba(255,255,255,0.06);border-radius:2px;font-size:9px;font-weight:700;color:rgba(255,255,255,0.3);margin-left:6px;font-family:'Inter',monospace;">${seg.tag}</span>` : '';
|
||||
$('cCo').innerHTML = `${progHtml}<div class="qp"><div class="qn">${qnLabel}${tagBadge}</div>${diag}<div class="qt">${seg.question}</div><div class="qo">${seg.opts.map((o,i)=>`<div class="opt ${done?(i===seg.correct?'pass':(i===sel?'fail':'disabled')):''}" data-idx="${i}"><span class="lb">${lbl[i]}</span>${o}</div>`).join('')}</div><div class="qfb ${done?'show '+(sel===seg.correct?'pass':'fail'):''}">${done?(sel===seg.correct?seg.passMsg:seg.failMsg):''}</div>${done?'<button class="act show">继续 \u2192</button>':''}</div>`;
|
||||
const srsBadge = S.viewMode==='wrongReviewSession' && S.wrongQMap[qid()] && S.wrongQMap[qid()].count > 0
|
||||
? `<span style="display:inline-block;padding:1px 8px;background:rgba(225,6,0,0.08);border:1px solid rgba(225,6,0,0.1);border-radius:2px;font-size:9px;font-weight:700;color:#e10600;margin-left:6px;font-family:'Inter',monospace;">\u{2716}\u00D7${S.wrongQMap[qid()].count}</span>` : '';
|
||||
const streakHtml = S.streak>=2 && done && sel===seg.correct ? `<div class="radio streak" style="margin-bottom:4px;">${S.streak>=10?'\u{1F451}终极统治':S.streak>=5?'\u{1F3C6}大奖赛全胜':'\u{26A1}最快圈速'} \u00D7 ${S.streak}</div>` : '';
|
||||
const radioHtml = done && seg._radioMsg ? `<div class="radio ${sel===seg.correct?'pass':'fail'}">${seg._radioMsg}</div>` : '';
|
||||
$('cCo').innerHTML = `${progHtml}<div class="qp"><div class="qn">${qnLabel}${tagBadge}${srsBadge}</div>${diag}<div class="qt">${seg.question}</div><div class="qo">${seg.opts.map((o,i)=>`<div class="opt ${done?(i===seg.correct?'pass':(i===sel?'fail':'disabled')):''}" data-idx="${i}"><span class="lb">${lbl[i]}</span>${o}</div>`).join('')}</div><div class="qfb ${done?'show '+(sel===seg.correct?'pass':'fail'):''}">${streakHtml}${radioHtml}${done?(sel===seg.correct?seg.passMsg:seg.failMsg):''}</div>${done?'<button class="act show">继续 \u2192</button>':''}</div>`;
|
||||
}
|
||||
|
||||
function qid() { return S.curSS+'_'+S.curSeg; }
|
||||
@@ -1855,17 +1960,50 @@ function renderWrongList() {
|
||||
$wl.innerHTML='<div style="text-align:center;padding:40px 20px;color:rgba(255,255,255,0.15);font-size:12px;">\u{1F389} 暂无错题!继续保持!</div>';
|
||||
return;
|
||||
}
|
||||
$wl.innerHTML=S.wrongQ.map((id,i)=>{
|
||||
$wl.innerHTML = `<div style="margin-bottom:10px;text-align:center;"><button class="hudBtn" id="revStart" style="background:rgba(255,215,0,0.06);border-color:rgba(255,215,0,0.1);color:#ffd700;font-size:12px;padding:10px 20px;">\u{1F3CE}\uFE0F \u{1F4D2} 错题复习圈 \u00D7${S.wrongQ.length}</button></div>`
|
||||
+ S.wrongQ.map((id,i)=>{
|
||||
const [ci,si]=id.split('_').map(Number);
|
||||
const seg=SS[ci].segs[si];
|
||||
if (!seg) return '';
|
||||
const wm = S.wrongQMap[id];
|
||||
const failBadge = wm && wm.count > 1 ? ` <span style="color:#e10600;font-size:9px;font-weight:700;">\u{2716}\u00D7${wm.count}</span>` : '';
|
||||
return `<div class="wrItem" data-idx="${i}">
|
||||
<div class="wrH">SS${ci+1} · ${SS[ci].emoji} ${SS[ci].title} · <span class="wrLb">${segLabel(seg,si)}</span></div>
|
||||
<div class="wrH">\u{1F3C6} #1 · ${SS[ci].title} · <span class="wrLb">${segLabel(seg,si)}</span>${failBadge}</div>
|
||||
<div class="wrQ">${seg.question}</div>
|
||||
<div class="wrAct"><button class="wrDo" data-id="${id}">\u{1F504} 重做</button><button class="wrRm" data-id="${id}">\u{2716} 移除</button></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
function startWrongReview() {
|
||||
if (!S.wrongQ.length) return;
|
||||
S.viewMode = 'wrongReviewSession';
|
||||
S.wrIdx = 0;
|
||||
goToWrongQ(0);
|
||||
}
|
||||
function goToWrongQ(idx) {
|
||||
if (idx >= S.wrongQ.length) { finishWrongReview(); return; }
|
||||
S.wrIdx = idx;
|
||||
const id = S.wrongQ[idx];
|
||||
const [ci, si] = id.split('_').map(Number);
|
||||
S.curSS = ci; S.curSeg = si;
|
||||
const seg = SS[ci].segs[si];
|
||||
if (seg) { delete seg._answered; delete seg._selected; delete seg._passed; }
|
||||
page('pC'); renderSeg(); sync();
|
||||
}
|
||||
function nextWrongInReview() {
|
||||
goToWrongQ(S.wrIdx + 1);
|
||||
}
|
||||
function finishWrongReview() {
|
||||
S.viewMode = 'normal';
|
||||
const cleared = S.wrongQ.length === 0;
|
||||
if (cleared) {
|
||||
sparks(40); beep('win');
|
||||
$('cCo').innerHTML = `<div class="pn" style="text-align:center;border-left:3px solid #ffd700;animation:bounceIn 0.5s ease;"><div style="font-size:48px;margin:8px 0;">\u{1F3C6}</div><h2 style="color:#ffd700;font-size:22px;">\u{1F389} 错题全部清空!</h2><p style="color:rgba(255,255,255,0.3);margin:6px 0 14px;">#1 完美表现!没有任何题能难倒你!</p><button class="act show" onclick="goMap();">返回赛道 \u2192</button></div>`;
|
||||
} else {
|
||||
$('cCo').innerHTML = `<div class="pn" style="text-align:center;border-left:3px solid #e10600;"><div style="font-size:40px;margin:8px 0;">\u{1F3CE}\uFE0F</div><h2 style="color:#fff;font-size:18px;">复习完成!还有 <span style="color:#ffd700;">${S.wrongQ.length}</span> 道待攻克</h2><p style="color:rgba(255,255,255,0.3);margin:6px 0 14px;">下次进站再解决它们!</p><button class="act show" onclick="startWrongReview();">\u{1F504} 再复习一圈</button><button class="act show" onclick="goMap();" style="background:rgba(255,255,255,0.05);margin-left:8px;">返回赛道 \u2192</button></div>`;
|
||||
}
|
||||
page('pC');
|
||||
}
|
||||
function pickAns(idx) {
|
||||
const s = SS[S.curSS];
|
||||
const seg = s.segs[S.curSeg];
|
||||
@@ -1873,16 +2011,51 @@ function pickAns(idx) {
|
||||
seg._answered=true; seg._selected=idx; seg._passed=idx===seg.correct;
|
||||
const id = qid();
|
||||
if (!S.doneQ.includes(id)) S.doneQ.push(id);
|
||||
seg._radioMsg = '';
|
||||
if (idx===seg.correct) {
|
||||
S.pts=Math.min(100,S.pts+8); beep('pass'); sparks(6);
|
||||
S.streak++;
|
||||
S.pts=Math.min(100,S.pts+8); beep(S.streak>=3?'hattrick':'pass'); sparks(6);
|
||||
const wi = S.wrongQ.indexOf(id);
|
||||
if (wi>=0) { S.wrongQ.splice(wi,1); if (S.wrIdx>=S.wrongQ.length) S.wrIdx=0; }
|
||||
if (wi>=0) {
|
||||
S.wrongQ.splice(wi,1);
|
||||
if (S.wrIdx>=S.wrongQ.length) S.wrIdx=0;
|
||||
// SRS: mark reviewed in wrongQMap
|
||||
if (S.wrongQMap[id]) { S.wrongQMap[id].lastReview = Date.now(); S.wrongQMap[id].reviews = (S.wrongQMap[id].reviews||0) + 1; }
|
||||
}
|
||||
const radioMsgs = [
|
||||
'\u{1F4FB} 干得漂亮 Max!这圈完美!',
|
||||
'\u{1F4FB} 最快圈速!+8 PTS',
|
||||
'\u{1F4FB} Great driving! 继续保持!',
|
||||
'\u{1F4FB} P1! P1! 拉开差距!',
|
||||
'\u{1F4FB} 哇!教科书般的操作!',
|
||||
'\u{1F3CE}\uFE0F 冲刺!无人能挡!'
|
||||
];
|
||||
seg._radioMsg = radioMsgs[Math.floor(Math.random()*radioMsgs.length)];
|
||||
if (S.streak===3) seg._radioMsg = '\u{1F3BA} HAT TRICK! 连续三站冠军!\u{1F3C6}';
|
||||
else if (S.streak===5) seg._radioMsg = '\u{1F451} GRAND SLAM! 包办最快圈速!\u{2728}';
|
||||
else if (S.streak===10) seg._radioMsg = '\u{1F680} \u2716\uFE0F 就是统治!VERSTAPPEN 不可阻挡!';
|
||||
} else {
|
||||
S.streak=0;
|
||||
beep('fail');
|
||||
if (!S.wrongQ.includes(id)) S.wrongQ.push(id);
|
||||
if (!S.wrongQ.includes(id)) { S.wrongQ.push(id); }
|
||||
// SRS: track wrong attempt
|
||||
if (!S.wrongQMap[id]) S.wrongQMap[id] = { count:0, lastWrong:0, lastReview:0, reviews:0 };
|
||||
S.wrongQMap[id].count = (S.wrongQMap[id].count||0) + 1;
|
||||
S.wrongQMap[id].lastWrong = Date.now();
|
||||
const radioMsgs = [
|
||||
'\u{1F4FB} BOX BOX — 进站换胎修正!',
|
||||
'\u{1F4FB} 别急 #1,下个弯道超回来!',
|
||||
'\u{1F4FB} 练习赛试错而已,正赛加油!',
|
||||
'\u{1F4FB} 失误是升级的燃料\u{1F527}',
|
||||
'\u{1F4FB} 稳住!#1 这圈不算,重新来!'
|
||||
];
|
||||
seg._radioMsg = radioMsgs[Math.floor(Math.random()*radioMsgs.length)];
|
||||
}
|
||||
sv(); renderSeg(); sync();
|
||||
const hb=$('hintBadge'); if(hb)hb.textContent=S.wrongQ.length||'';
|
||||
// toggle review lap button
|
||||
const $revBtn = $('revLapBtn');
|
||||
if ($revBtn) $revBtn.style.display = S.wrongQ.length ? 'inline-flex' : 'none';
|
||||
}
|
||||
|
||||
function renderRd(seg) {
|
||||
@@ -1946,9 +2119,22 @@ function finishChap() {
|
||||
|
||||
const pos = ['DNF','P10','P5','P1'];
|
||||
const cups = ['\u{1F3C1}','\u{1F949}','\u{1F948}','\u{1F3C6}'];
|
||||
const titles = ['未完赛','P10 \u2014 积分区外','P5 \u2014 得分','SS WIN \u2014 冠军!'];
|
||||
const msgs = ['再练一圈,下个赛段复仇','继续 push,争取更好成绩','差一点就完美了!','VERSTAPPEN 模式!冲向下一个赛段'];
|
||||
const nextTxt = S.curSS < 3 && S.stars[S.curSS] > 0 ? '驶向下一站 \u2192' : '返回赛道';
|
||||
const titles = ['未完赛','P10 \u2014 积分区外','P5 \u2014 得分','#1 WIN \u2014 世界冠军!'];
|
||||
const msgs = ['再练一圈,下个赛段复仇','继续 push,#1 争取更好成绩','差一点就完美了!#1 下次碾压!','#1 VERSTAPPEN 模式!冲向下一个赛段'];
|
||||
const radioMsgs = [
|
||||
'\u{1F4FB} DNF... #1 下站必争!',
|
||||
'\u{1F4FB} P10,还有很大提升空间!',
|
||||
'\u{1F4FB} P5领奖台!#1 正在逼近!',
|
||||
'\u{1F4FB} #1 Max: \u201CSimply simply lovely!\u201D \u{1F3C6}'
|
||||
];
|
||||
const maxQuotes = [
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'\u201C只要你不停push,总会到顶。\u201D \u2014 Max Verstappen'
|
||||
];
|
||||
const nextTxt = S.curSS < 3 && S.stars[S.curSS] > 0 ? '驶向下一站 \u2192'
|
||||
: (S.curSS >= 3 && S.stars[S.curSS] >= 3 ? '\u{1F3C6} 世界冠军!重返赛道' : '返回赛道');
|
||||
|
||||
$('pmN').textContent = pos[stars];
|
||||
$('pmC').textContent = cups[stars];
|
||||
@@ -1957,15 +2143,26 @@ function finishChap() {
|
||||
$('pmXp').textContent = '+'+stars*5+' PTS';
|
||||
$('pmMs').textContent = msgs[stars];
|
||||
$('pmNt').textContent = nextTxt;
|
||||
const $pmR = $('pmRadio');
|
||||
$pmR.textContent = radioMsgs[stars];
|
||||
$pmR.className = 'radio ' + (stars>=2?'pass':stars===1?'fail':'');
|
||||
$pmR.style.display = stars > 0 ? 'inline-flex' : 'none';
|
||||
// Max quote for 3-star
|
||||
const $pmQ = document.getElementById('pmQ');
|
||||
if ($pmQ) $pmQ.textContent = maxQuotes[stars];
|
||||
// Show checkered flag for 3-star
|
||||
const $chk = $('pmChk');
|
||||
if ($chk) $chk.style.opacity = stars===3 ? '0.06' : '0';
|
||||
$('po').classList.add('show');
|
||||
|
||||
// big confetti
|
||||
sparks(50);
|
||||
// big confetti — branded colors for 3-star
|
||||
sparks(stars===3?80:50);
|
||||
beep(stars===3?'win':'pass');
|
||||
}
|
||||
|
||||
function closePodium() {
|
||||
$('po').classList.remove('show');
|
||||
S.streak=0;
|
||||
const nextIdx = S.curSS + 1;
|
||||
|
||||
if (nextIdx < 4 && S.stars[S.curSS] > 0) {
|
||||
@@ -2045,6 +2242,25 @@ function sync() {
|
||||
const done = S.stars.filter(s=>s>0).length;
|
||||
$('hStage').textContent = Math.min(done+1,4)+'/4';
|
||||
$('hPts').textContent = S.pts;
|
||||
// streak display
|
||||
const $sD = $('streakD');
|
||||
if ($sD) {
|
||||
if (S.streak >= 2) {
|
||||
const labels = ['','','\u{26A1}','\u{26A1}','\u{1F525}','\u{1F525}','\u{1F3C6}','\u{1F3C6}','\u{1F451}','\u{1F451}','\u{1F680}'];
|
||||
const label = labels[Math.min(S.streak, labels.length-1)];
|
||||
$sD.className = 'streakDisplay on';
|
||||
$sD.innerHTML = `${label} #1 \u00D7${S.streak}连对`;
|
||||
} else { $sD.className = 'streakDisplay'; $sD.innerHTML = ''; }
|
||||
}
|
||||
// lap counter
|
||||
const $lC = $('lapCnt');
|
||||
if ($lC) {
|
||||
const s = SS[S.curSS];
|
||||
const qs = s.segs.filter(function(seg){return seg.type==='ss'||seg.type==='review'||seg.type==='rd';});
|
||||
const total = qs.length;
|
||||
const doneCount = qs.filter(function(seg){return seg._answered;}).length;
|
||||
$lC.innerHTML = `\u{1F3CE}\uFE0F #1 LAP ${Math.min(doneCount+1,total)}/${total}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 学习总览 =====
|
||||
@@ -2091,6 +2307,7 @@ function beep(t) {
|
||||
if (t==='pass') { [523,659,784].forEach((f,i)=>{const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);g.gain.value=0.03;o.frequency.value=f;o.start(ctx.currentTime+i*0.1);o.stop(ctx.currentTime+i*0.1+0.1);}); }
|
||||
else if (t==='fail') { const o=ctx.createOscillator(),g=ctx.createGain();o.type='sawtooth';o.connect(g);g.connect(ctx.destination);g.gain.value=0.015;o.frequency.setValueAtTime(200,ctx.currentTime);o.frequency.setValueAtTime(160,ctx.currentTime+0.1);o.start(ctx.currentTime);o.stop(ctx.currentTime+0.2); }
|
||||
else if (t==='win') { [523,587,659,784,1047].forEach((f,i)=>{const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);g.gain.value=0.03;o.frequency.value=f;o.start(ctx.currentTime+i*0.08);o.stop(ctx.currentTime+i*0.08+0.12);}); }
|
||||
else if (t==='hattrick') { [523,659,784,1047,1319].forEach((f,i)=>{const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);g.gain.value=0.035;o.frequency.value=f;o.start(ctx.currentTime+i*0.06);o.stop(ctx.currentTime+i*0.06+0.15);}); }
|
||||
else { const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);g.gain.value=0.015;o.frequency.value=440;o.start(ctx.currentTime);o.stop(ctx.currentTime+0.04); }
|
||||
} catch(e) {}
|
||||
}
|
||||
@@ -2256,7 +2473,7 @@ function stopBGM() {
|
||||
// ===== 粒子 =====
|
||||
function sparks(n) {
|
||||
const c = $('pyro');
|
||||
const cs = ['#e10600','#ffd700','#e10600','#fff','#ffd700','#2ecc71'];
|
||||
const cs = ['#e10600','#ffd700','#1e41a0','#fff','#ffd700','#e10600','#1e41a0','#2ecc71'];
|
||||
for(let i=0;i<n;i++){const e=document.createElement('div');e.className='sp';e.style.left=(10+Math.random()*80)+'%';e.style.top='-5px';e.style.background=cs[Math.floor(Math.random()*cs.length)];e.style.width=e.style.height=(2+Math.random()*4)+'px';e.style.animationDuration=(1.5+Math.random()*2)+'s';e.style.animationDelay=(Math.random()*0.5)+'s';c.appendChild(e);setTimeout(()=>e.remove(),4000);}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user