Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 575de9e3cc | |||
| 2359ac6556 |
+2456
-1493
@@ -2,1525 +2,2488 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<title>单词乐园 — PEP 7上</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@500;600;700;900&family=Noto+Sans+SC:wght@400;700;900&display=swap" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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>🏆 #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;-webkit-tap-highlight-color:transparent}
|
||||
body{font-family:'Noto Sans SC','Inter',sans-serif;background:#f5f5fa;min-height:100vh;color:#1a1a2e;overflow-x:hidden}
|
||||
.app{max-width:480px;margin:0 auto;padding:0;position:relative;min-height:100vh;background:#fff}
|
||||
/* TABS */
|
||||
.tabs{position:fixed;bottom:0;left:50%;transform:translateX(-50%);max-width:480px;width:100%;background:#fff;border-top:1px solid rgba(0,0,0,0.04);display:flex;z-index:50;padding:6px 0 env(safe-area-inset-bottom,6px)}
|
||||
.tab{flex:1;text-align:center;padding:6px 0;cursor:pointer;transition:all 0.2s;border:none;background:none;font-family:inherit}
|
||||
.tab .ti{font-size:20px;display:block}
|
||||
.tab .tl{font-size:9px;font-weight:600;color:rgba(26,26,46,0.3);margin-top:1px}
|
||||
.tab.act .tl{color:#6366f1}
|
||||
/* PAGES */
|
||||
.pg{display:none;padding:16px 16px 80px}
|
||||
.pg.act{display:block;animation:fadeUp 0.3s ease}
|
||||
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
||||
@keyframes pop{0%{opacity:0;transform:scale(0.85)}50%{transform:scale(1.05)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes bIn{0%{opacity:0;transform:scale(0.3)}50%{transform:scale(1.1)}70%{transform:scale(0.9)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes slideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
|
||||
@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-6px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(4px)}}
|
||||
/* HOME */
|
||||
.hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
|
||||
.hd h1{font-size:22px;font-weight:900;letter-spacing:-0.5px}
|
||||
.hd .hd-r{display:flex;align-items:center;gap:6px}
|
||||
.hd .hd-r .ci{font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px;background:rgba(99,102,241,0.06);color:#6366f1;border:1px solid rgba(99,102,241,0.1)}
|
||||
.strk{display:flex;gap:4px;margin:4px 0 12px}
|
||||
.strk .d{width:36px;height:20px;border-radius:4px;font-size:8px;font-weight:700;display:flex;align-items:center;justify-content:center;background:#f0f0f5;color:rgba(26,26,46,0.2)}
|
||||
.strk .d.on{background:#fef3c7;color:#f59e0b}
|
||||
.strk .d.td{background:#eee7ff;color:#6366f1;border:1px solid rgba(99,102,241,0.15)}
|
||||
/* CARDS */
|
||||
.cd{background:#fff;border-radius:14px;padding:18px;margin-bottom:12px;border:1px solid rgba(0,0,0,0.04);box-shadow:0 1px 4px rgba(0,0,0,0.02)}
|
||||
.cd .ct{font-size:11px;font-weight:700;color:rgba(26,26,46,0.3);letter-spacing:0.5px;margin-bottom:6px;text-transform:uppercase}
|
||||
.cd .cvl{font-size:32px;font-weight:900;font-family:'Inter',monospace}
|
||||
.cd .clb{font-size:11px;color:rgba(26,26,46,0.3)}.cd .clb.g{color:#6366f1}
|
||||
/* STAT ROW */
|
||||
.sr{display:flex;gap:8px}
|
||||
.sb{flex:1;background:#f8f8fc;border-radius:10px;padding:12px;text-align:center}
|
||||
.sb .n{font-size:20px;font-weight:900;font-family:'Inter',monospace}
|
||||
.sb .l{font-size:9px;color:rgba(26,26,46,0.3);margin-top:2px}
|
||||
/* UNIT LIST */
|
||||
.ul{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.ui{background:#f8f8fc;border-radius:10px;padding:14px 10px;text-align:center;cursor:pointer;transition:all 0.2s;border:1px solid transparent}
|
||||
.ui:active{transform:scale(0.97)}
|
||||
.ui .ue{font-size:24px;margin-bottom:2px}
|
||||
.ui .ut{font-size:12px;font-weight:700;color:#1a1a2e}
|
||||
.ui .ud{font-size:9px;color:rgba(26,26,46,0.3);margin-top:1px}
|
||||
.ui .ub{font-size:8px;color:rgba(26,26,46,0.15);margin-top:4px}
|
||||
.ui .ub .m{color:#6366f1}
|
||||
.ui.done{background:#f0fdf4}
|
||||
.ui.done .ut{color:#16a34a}
|
||||
/* BIG BUTTON */
|
||||
.big-btn{display:block;width:100%;padding:16px;border:none;border-radius:12px;font-size:16px;font-weight:700;font-family:inherit;cursor:pointer;transition:all 0.2s;text-align:center}
|
||||
.big-btn:active{transform:scale(0.97)}
|
||||
.big-btn.p{background:#6366f1;color:#fff}
|
||||
.big-btn.p:hover{background:#4f46e5}
|
||||
.big-btn.o{background:#f59e0b;color:#fff}
|
||||
.big-btn.g{background:#f0fdf4;color:#16a34a;border:1px solid rgba(22,163,74,0.1)}
|
||||
/* LEARN CARD */
|
||||
.lc{background:linear-gradient(135deg,#f8f8fc,#fff);border-radius:16px;padding:32px 20px 24px;text-align:center;margin:8px 0 12px;border:1px solid rgba(0,0,0,0.03);box-shadow:0 2px 12px rgba(0,0,0,0.04);position:relative}
|
||||
.lc .le{font-size:64px;margin-bottom:8px;animation:bIn 0.4s ease}
|
||||
.lc .lw{font-size:32px;font-weight:900;color:#1a1a2e;letter-spacing:-0.5px;animation:slideUp 0.3s ease}
|
||||
.lc .lz{font-size:15px;color:rgba(26,26,46,0.35);margin-top:4px}
|
||||
.lc .ls{font-size:12px;color:rgba(26,26,46,0.15);margin-top:10px;line-height:1.5;padding:8px 12px;background:#f8f8fc;border-radius:8px}
|
||||
.lc .ls.s{animation:slideUp 0.3s ease}
|
||||
.lc .lp{font-size:22px;cursor:pointer;display:inline-block;margin-top:10px;padding:8px 16px;border-radius:20px;background:#f0f0ff;transition:background 0.2s}
|
||||
.lc .lp:active{background:#e4e4ff}
|
||||
/* QUIZ OPTIONS */
|
||||
.qz{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.qo{background:#fff;border:1.5px solid #eaeaf0;border-radius:12px;padding:14px;cursor:pointer;text-align:center;transition:all 0.2s;display:flex;flex-direction:column;align-items:center;gap:6px}
|
||||
.qo:active{transform:scale(0.96)}
|
||||
.qo .qe{font-size:36px}.qo .qt{font-size:13px;font-weight:600;color:#1a1a2e}.qo .qt.e{font-size:15px}
|
||||
.qo.pass{border-color:#22c55e;background:#f0fdf4;animation:pop 0.25s ease}
|
||||
.qo.fail{border-color:#ef4444;background:#fef2f2;animation:shake 0.4s ease}
|
||||
.qo.disabled{opacity:0.4;pointer-events:none}
|
||||
/* FEEDBACK */
|
||||
.fb{text-align:center;padding:14px;margin-top:10px;border-radius:10px;display:none;animation:slideUp 0.25s ease}
|
||||
.fb.show{display:block}
|
||||
.fb.pass{background:#f0fdf4;border:1px solid rgba(34,197,94,0.1)}
|
||||
.fb.fail{background:#fef2f2;border:1px solid rgba(239,68,68,0.1)}
|
||||
.fb .fbt{font-size:15px;font-weight:700}.fb.pass .fbt{color:#16a34a}.fb.fail .fbt{color:#dc2626}
|
||||
.fb .fbd{font-size:11px;color:rgba(26,26,46,0.3);margin-top:4px}
|
||||
/* SPELL INPUT */
|
||||
.si{display:flex;gap:8px;margin-top:12px;justify-content:center}
|
||||
.si input{background:#fff;border:1.5px solid #d4d4dc;border-radius:10px;padding:12px 16px;font-size:18px;font-weight:700;color:#1a1a2e;font-family:'Inter',monospace;text-align:center;width:200px;outline:none;transition:border-color 0.2s}
|
||||
.si input:focus{border-color:#6366f1}
|
||||
.si input.cor{border-color:#22c55e;background:#f0fdf4}
|
||||
.si input.wrg{border-color:#ef4444;background:#fef2f2}
|
||||
.si button{padding:12px 20px;border:none;border-radius:10px;background:#6366f1;color:#fff;font-weight:700;cursor:pointer}
|
||||
/* EXAM */
|
||||
.exam-q{background:#f8f8fc;border-radius:12px;padding:18px;margin-bottom:10px}
|
||||
.exam-q .eq{font-size:14px;font-weight:600;color:#1a1a2e;margin-bottom:10px;line-height:1.5}
|
||||
.exam-op{background:#fff;border:1.5px solid #eaeaf0;border-radius:8px;padding:10px 12px;cursor:pointer;transition:all 0.15s;display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.exam-op:active{transform:scale(0.98)}
|
||||
.exam-op .el{width:22px;height:22px;line-height:22px;text-align:center;border-radius:50%;font-size:10px;font-weight:700;background:#f0f0f5;color:rgba(26,26,46,0.4);flex-shrink:0}
|
||||
.exam-op.pass{background:#f0fdf4;border-color:#22c55e}
|
||||
.exam-op.pass .el{background:#22c55e;color:#fff}
|
||||
.exam-op.fail{background:#fef2f2;border-color:#ef4444}
|
||||
.exam-op.fail .el{background:#ef4444;color:#fff}
|
||||
/* WORD LIST */
|
||||
.wi{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,0.03)}
|
||||
.wi .we{font-size:26px;width:36px;text-align:center}
|
||||
.wi .ww{flex:1}.wi .ww .en{font-size:14px;font-weight:700;color:#1a1a2e}
|
||||
.wi .ww .zh{font-size:11px;color:rgba(26,26,46,0.35)}
|
||||
.wi .ws{font-size:14px;cursor:pointer;opacity:0.3;transition:opacity 0.2s;padding:4px}
|
||||
.wi .ws:active{opacity:0.8}
|
||||
.wi .wl{font-size:9px;font-weight:700;width:18px;height:18px;line-height:18px;text-align:center;border-radius:50%}
|
||||
.wi .wl.n{background:#f0f0ff;color:#6366f1}
|
||||
.wi .wl.l{background:#f0fdf4;color:#16a34a}
|
||||
/* CELEBRATION */
|
||||
.pyro{position:fixed;inset:0;pointer-events:none;z-index:99;overflow:hidden}
|
||||
.pyro .sp{position:absolute;width:4px;height:4px;border-radius:2px;animation:spFall linear forwards}
|
||||
@keyframes spFall{0%{transform:translateY(-10px) scale(1);opacity:1}100%{transform:translateY(100vh) scale(0);opacity:0}}
|
||||
/* MODAL */
|
||||
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:100;align-items:center;justify-content:center;padding:20px}
|
||||
.modal.show{display:flex}
|
||||
.modal-in{background:#fff;border-radius:20px;padding:32px 24px;text-align:center;max-width:320px;width:100%;animation:pop 0.4s ease}
|
||||
.modal-in .mi{font-size:56px;margin-bottom:6px}
|
||||
.modal-in h2{font-size:18px;color:#1a1a2e;margin-bottom:4px}
|
||||
.modal-in p{font-size:12px;color:rgba(26,26,46,0.35);margin-bottom:14px}
|
||||
.modal-in .ci-pct{font-size:40px;font-weight:900;color:#1a1a2e;font-family:'Inter',monospace}
|
||||
.modal-in .ci-bar{height:5px;background:#f0f0f5;border-radius:3px;margin:8px 0;overflow:hidden}
|
||||
.modal-in .ci-bar .ci-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,#6366f1,#f59e0b,#22c55e);transition:width 0.8s ease}
|
||||
.modal-in button{display:inline-flex;align-items:center;gap:6px;padding:10px 24px;border:none;border-radius:10px;font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;transition:all 0.2s}
|
||||
.modal-in button.p{background:#6366f1;color:#fff}
|
||||
@media(max-width:480px){.pg{padding:12px 12px 76px}}
|
||||
.cm{font-size:10px;padding:4px 12px;border-radius:12px;cursor:pointer;border:1px solid rgba(0,0,0,0.04);background:#f5f5fa;color:rgba(26,26,46,0.3);font-weight:600;transition:all 0.15s}
|
||||
.cm.act{background:#6366f1;color:#fff;border-color:#6366f1}
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body {
|
||||
font-family: 'Noto Sans SC','Inter',sans-serif;
|
||||
background:#05050a;
|
||||
min-height:100vh;
|
||||
color:#c8c8d0;
|
||||
overflow-x:hidden;
|
||||
}
|
||||
.app {
|
||||
max-width:820px; margin:0 auto; padding:20px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* === HUD === */
|
||||
.hud {
|
||||
background:rgba(8,8,20,0.85); border:1px solid rgba(225,6,0,0.12);
|
||||
padding:10px 18px; display:flex; align-items:center; gap:12px; margin-bottom:18px;
|
||||
backdrop-filter:blur(8px); border-radius:4px;
|
||||
}
|
||||
.hud .logo { font-weight:900; font-size:15px; letter-spacing:2px; }
|
||||
.hud .logo .r { color:#e10600; }
|
||||
.hud .logo .y { color:#ffd700; }
|
||||
.hud .logo .w { color:rgba(255,255,255,0.85); }
|
||||
.hud-spacer { flex:1; }
|
||||
.hud-stat { text-align:center; padding:0 10px; border-right:1px solid rgba(255,255,255,0.03); }
|
||||
.hud-stat:last-child { border-right:none; }
|
||||
.hud-stat .lbl { font-size:8px; color:rgba(255,255,255,0.4); letter-spacing:2px; font-weight:700; }
|
||||
.hud-stat .val { font-size:18px; font-weight:900; font-family:'Inter',monospace; }
|
||||
.hud-stat .val.r { color:#e10600; }
|
||||
.hud-stat .val.y { color:#ffd700; }
|
||||
.mbtn {
|
||||
width:28px; height:28px; border:1px solid rgba(255,255,255,0.08); background:transparent;
|
||||
color:rgba(255,255,255,0.4); font-size:12px; cursor:pointer; transition:all 0.3s;
|
||||
display:flex; align-items:center; justify-content:center; flex-shrink:0; border-radius:2px;
|
||||
}
|
||||
.mbtn:hover { border-color:rgba(255,255,255,0.15); color:rgba(255,255,255,0.7); }
|
||||
.mbtn.on { color:#ffd700; border-color:rgba(255,215,0,0.2); background:rgba(255,215,0,0.04); }
|
||||
|
||||
/* === pages === */
|
||||
.pg { display:none; }
|
||||
.pg.act { display:block; animation:fIn 0.35s ease; }
|
||||
@keyframes fIn { from{opacity:0;transform:translateY(10px)} to{opacity:1;transform:translateY(0)} }
|
||||
@keyframes shake { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-6px)} 40%{transform:translateX(6px)} 60%{transform:translateX(-4px)} 80%{transform:translateX(4px)} }
|
||||
@keyframes pop { 0%{opacity:0;transform:scale(0.8) translateY(16px)} 100%{opacity:1;transform:scale(1) translateY(0)} }
|
||||
@keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.05)} }
|
||||
@keyframes slideUp { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }
|
||||
@keyframes glow { 0%,100%{box-shadow:0 0 4px rgba(255,215,0,0.1)} 50%{box-shadow:0 0 16px rgba(255,215,0,0.3)} }
|
||||
@keyframes starPop { 0%{opacity:0;transform:scale(0) rotate(-180deg)} 60%{transform:scale(1.3) rotate(10deg)} 100%{opacity:1;transform:scale(1) rotate(0deg)} }
|
||||
@keyframes bounceIn { 0%{opacity:0;transform:scale(0.3)} 50%{transform:scale(1.08)} 70%{transform:scale(0.95)} 100%{opacity:1;transform:scale(1)} }
|
||||
|
||||
/* ===== TRACK MAP ===== */
|
||||
.track-wrap {
|
||||
position:relative; width:100%; aspect-ratio:1/1.5; max-height:620px;
|
||||
margin:0 auto 12px;
|
||||
}
|
||||
.track-wrap svg { width:100%; height:100%; display:block; }
|
||||
.track-road { fill:none; stroke:rgba(255,255,255,0.03); stroke-width:16; stroke-linecap:round; }
|
||||
.track-bg { fill:none; stroke:rgba(255,255,255,0.08); stroke-width:3; stroke-linecap:round; }
|
||||
.track-fill { fill:none; stroke:rgba(255,255,255,0.25); stroke-width:1.5; stroke-dasharray:6,8; stroke-linecap:round; }
|
||||
.track-done { fill:none; stroke:#ffd700; stroke-width:3; stroke-linecap:round; filter:drop-shadow(0 0 10px rgba(255,215,0,0.3)); }
|
||||
|
||||
.cp {
|
||||
cursor:pointer; transition:all 0.3s;
|
||||
}
|
||||
.cp circle:first-of-type { pointer-events:all; cursor:pointer; }
|
||||
.cp:hover circle { r:20; }
|
||||
.cp.locked { opacity:0.25; }
|
||||
.cp.locked circle:last-of-type { stroke:rgba(255,255,255,0.06); fill:rgba(255,255,255,0.02); }
|
||||
.cp.locked + text { fill:rgba(255,255,255,0.06); }
|
||||
.cp.lock-icon { font-size:10px; fill:rgba(255,255,255,0.25); text-anchor:middle; font-family:'Inter',monospace; }
|
||||
.cp.done circle { fill:#ffd700; stroke:#ffd700; }
|
||||
|
||||
.cp-label {
|
||||
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; }
|
||||
.cp:not(.locked):not(.done):hover .cp-label { fill:#fff; }
|
||||
|
||||
.cp-stars { font-size:9px; text-anchor:middle; fill:rgba(255,255,255,0.5); }
|
||||
.cp-name {
|
||||
font-size:10px; font-weight:700; fill:rgba(255,255,255,0.35);
|
||||
text-anchor:middle; letter-spacing:1px;
|
||||
}
|
||||
|
||||
/* F1 car on track */
|
||||
.track-car {
|
||||
position:absolute; width:66px; height:30px;
|
||||
pointer-events:none;
|
||||
filter:drop-shadow(0 0 10px rgba(225,6,0,0.35));
|
||||
z-index:10;
|
||||
}
|
||||
.track-car .ci { width:100%; height:100%; object-fit:contain; display:block; }
|
||||
.track-car .cs { width:100%; height:100%; display:block; overflow:visible; }
|
||||
.track-car .cs.hide { display:none; }
|
||||
.track-car.moving {
|
||||
filter:drop-shadow(0 0 18px rgba(255,215,0,0.5));
|
||||
}
|
||||
.mv1-photo {
|
||||
width:80px; height:80px; border-radius:50%; object-fit:cover;
|
||||
border:2px solid #ffd700; margin:0 auto 8px; display:block;
|
||||
box-shadow:0 0 20px rgba(255,215,0,0.1);
|
||||
}
|
||||
.mv1-photo.hide { display:none; }
|
||||
.mv1-av {
|
||||
width:80px; height:80px; border-radius:50%; margin:0 auto 8px;
|
||||
background:linear-gradient(135deg,#e10600,#aa0000);
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
font-size:28px; font-weight:900; color:#fff; font-family:'Inter',monospace;
|
||||
border:2px solid rgba(255,215,0,0.2);
|
||||
}
|
||||
.mv1-av.hide { display:none; }
|
||||
|
||||
/* Start/Finish line */
|
||||
.sf { fill:#ffd700; opacity:0.15; }
|
||||
|
||||
/* === Chapter view === */
|
||||
.ch { margin-bottom:16px; }
|
||||
.ch .gl { font-size:12px; font-weight:700; color:#e10600; letter-spacing:2px; font-family:'Inter',monospace; }
|
||||
.ch h2 { font-size:24px; font-weight:900; color:#fff; margin:2px 0; }
|
||||
.ch .sm { font-size:13px; color:rgba(255,255,255,0.45); letter-spacing:1px; font-weight:700; }
|
||||
|
||||
/* === Buttons === */
|
||||
.bk {
|
||||
background:rgba(8,8,20,0.6); border:1px solid rgba(255,255,255,0.06);
|
||||
color:rgba(255,255,255,0.6); padding:9px 16px; cursor:pointer; font-size:14px; font-family:inherit;
|
||||
transition:all 0.25s; margin-bottom:16px; font-weight:700; letter-spacing:1px; border-radius:3px;
|
||||
}
|
||||
.bk:hover { background:rgba(8,8,20,0.6); color:rgba(255,255,255,0.8); border-color:rgba(225,6,0,0.2); }
|
||||
|
||||
.act {
|
||||
display:none; margin-top:10px; padding:10px 28px; border:none;
|
||||
font-size:14px; font-weight:700; font-family:'Inter',monospace; cursor:pointer; letter-spacing:1px;
|
||||
background:#e10600; color:#fff; transition:all 0.3s; border-radius:3px;
|
||||
}
|
||||
.act.show { display:inline-block; }
|
||||
.act:hover { background:#aa0000; transform:translateY(-1px); box-shadow:0 4px 12px rgba(225,6,0,0.2); }
|
||||
.act:active { transform:translateY(0); }
|
||||
|
||||
/* === Panels === */
|
||||
.pn {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.03);
|
||||
padding:20px; margin-bottom:8px; border-radius:2px;
|
||||
}
|
||||
.pn h3 { font-size:18px; font-weight:900; color:#fff; margin-bottom:10px; letter-spacing:-0.3px; }
|
||||
.kpTitle { font-size:20px; font-weight:900; color:#ffd700; margin-bottom:14px; letter-spacing:0; border-bottom:1px solid rgba(255,215,0,0.08); padding-bottom:10px; animation:slideUp 0.4s ease; }
|
||||
.pn p { font-size:15px; line-height:2; color:rgba(255,255,255,0.7); }
|
||||
|
||||
.co {
|
||||
background:linear-gradient(135deg,rgba(225,6,0,0.03),rgba(8,8,20,0.6));
|
||||
border-left:3px solid #e10600; padding:12px 16px; margin:12px 0; position:relative;
|
||||
}
|
||||
.co::before {
|
||||
content:'PACE NOTE'; position:absolute; top:-7px; right:8px;
|
||||
font-size:7px; font-weight:900; color:rgba(225,6,0,0.35); letter-spacing:2px;
|
||||
}
|
||||
.co p { color:rgba(255,255,255,0.75); font-size:15px; line-height:1.8; }
|
||||
.co strong { color:#ffd700; }
|
||||
|
||||
.qp {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.03);
|
||||
border-left:3px solid #ffd700; padding:20px; margin-bottom:8px; border-radius:2px;
|
||||
}
|
||||
.qp .qn { font-size:11px; color:#ffd700; font-weight:700; letter-spacing:2px; margin-bottom:6px; font-family:'Inter',monospace; }
|
||||
.qp .qt { font-size:18px; font-weight:700; color:#fff; margin-bottom:16px; line-height:1.7; }
|
||||
.qo { display:flex; flex-direction:column; gap:5px; }
|
||||
.qo .opt {
|
||||
padding:12px 16px; border:1px solid rgba(255,255,255,0.04);
|
||||
cursor:pointer; font-size:15px; font-family:inherit; transition:all 0.2s;
|
||||
background:rgba(255,255,255,0.02); text-align:left; color:rgba(255,255,255,0.75);
|
||||
display:flex; align-items:center; gap:10px;
|
||||
border-left:3px solid transparent; border-radius:3px;
|
||||
}
|
||||
.qo .opt .lb {
|
||||
width:28px; height:28px; display:flex; align-items:center; justify-content:center;
|
||||
font-weight:900; font-size:13px; background:rgba(255,255,255,0.04); color:rgba(255,255,255,0.3);
|
||||
flex-shrink:0; font-family:'Inter',monospace; border-radius:3px;
|
||||
}
|
||||
.qo .opt:hover { border-color:rgba(255,215,0,0.08); background:rgba(255,215,0,0.02); border-left-color:rgba(255,215,0,0.2); }
|
||||
.qo .opt:hover .lb { background:rgba(255,215,0,0.08); color:#ffd700; }
|
||||
.qo .opt.fail { border-color:rgba(225,6,0,0.1); background:rgba(225,6,0,0.03); border-left-color:#e10600; animation:shake 0.4s ease; }
|
||||
.qo .opt.fail .lb { background:#e10600; color:#fff; }
|
||||
.qo .opt.pass { border-color:rgba(46,204,113,0.1); background:rgba(46,204,113,0.03); border-left-color:#2ecc71; animation:pulse 0.3s ease; }
|
||||
.qo .opt.pass .lb { background:#2ecc71; color:#fff; }
|
||||
.qo .opt.disabled { pointer-events:none; opacity:0.3; }
|
||||
.qfb { padding:14px 18px; margin-top:10px; font-size:15px; font-weight:700; display:none; line-height:1.8; }
|
||||
.qfb.show { display:block; }
|
||||
.qfb.pass { background:rgba(46,204,113,0.05); color:#2ecc71; border:1px solid rgba(46,204,113,0.06); border-left:3px solid #2ecc71; }
|
||||
.qfb.fail { background:rgba(225,6,0,0.05); color:rgba(255,255,255,0.85); border:1px solid rgba(225,6,0,0.06); border-left:3px solid #e10600; }
|
||||
|
||||
.rv {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.03);
|
||||
border-left:3px solid rgba(255,215,0,0.15); padding:20px; margin-bottom:8px; text-align:center; border-radius:2px;
|
||||
}
|
||||
.rv h3 { color:#fff; margin-bottom:16px; font-size:14px; font-weight:700; }
|
||||
.rline {
|
||||
position:relative; height:3px;
|
||||
background:repeating-linear-gradient(90deg,#e10600 0,#e10600 6px,transparent 6px,transparent 12px);
|
||||
margin:28px 10px 12px;
|
||||
}
|
||||
.rline .mk { position:absolute; top:-6px; transform:translateX(-50%); font-size:10px; font-weight:700; color:rgba(255,255,255,0.1); font-family:'Inter',monospace; }
|
||||
.rline .mk.z { color:#ffd700; }
|
||||
.rline .mk::before { content:''; position:absolute; top:14px; left:50%; width:1px; height:5px; background:rgba(255,255,255,0.04); }
|
||||
.car { position:absolute; font-size:20px; top:-24px; transform:translateX(-50%); transition:left 0.5s cubic-bezier(.34,1.56,.64,1); }
|
||||
.car .vl { position:absolute; top:-14px; left:50%; transform:translateX(-50%); font-size:9px; font-weight:700; white-space:nowrap; color:#fff; background:rgba(225,6,0,0.5); padding:1px 5px; font-family:'Inter',monospace; }
|
||||
|
||||
/* ===== PODIUM ===== */
|
||||
.po {
|
||||
display:none; position:fixed; inset:0; background:rgba(5,5,10,0.88); backdrop-filter:blur(14px);
|
||||
z-index:100; align-items:center; justify-content:center;
|
||||
}
|
||||
.po.show { display:flex; }
|
||||
.pm {
|
||||
background:#0a0a1a; border:1px solid rgba(225,6,0,0.1); border-radius:4px;
|
||||
padding:40px 32px 28px; text-align:center; max-width:360px; width:90%;
|
||||
animation:pop 0.5s cubic-bezier(.34,1.56,.64,1);
|
||||
position:relative; overflow:hidden;
|
||||
}
|
||||
.pm::before {
|
||||
content:''; position:absolute; top:0; left:0; right:0; height:2px;
|
||||
background:linear-gradient(90deg,#e10600,#ffd700,#e10600);
|
||||
}
|
||||
.pm::after {
|
||||
content:'\u{1F3C6}'; position:absolute; top:-20px; right:-20px; font-size:100px;
|
||||
opacity:0.03; transform:rotate(15deg);
|
||||
}
|
||||
@keyframes pop { from{opacity:0;transform:scale(0.9) translateY(16px)} to{opacity:1;transform:scale(1) translateY(0)} }
|
||||
|
||||
.pm .pn1 { font-size:56px; font-weight:900; font-family:'Inter',monospace; color:#ffd700; line-height:1; text-shadow:0 0 30px rgba(255,215,0,0.15); }
|
||||
.pm .pcup { font-size:50px; display:block; margin:2px 0; }
|
||||
.pm h2 { font-size:24px; font-weight:900; color:#fff; letter-spacing:1px; }
|
||||
.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;
|
||||
font-size:12px; font-weight:700; font-family:'Inter',monospace; cursor:pointer;
|
||||
transition:all 0.3s; letter-spacing:1px; border-radius:2px;
|
||||
}
|
||||
.pm .next-track:hover { background:#aa0000; }
|
||||
|
||||
/* ===== Example / Step-through ===== */
|
||||
.eg-box {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.03);
|
||||
border-left:3px solid #e10600; padding:20px; margin-bottom:8px; border-radius:2px;
|
||||
position:relative;
|
||||
}
|
||||
.eg-box::before {
|
||||
content:'EXAMPLE'; position:absolute; top:-7px; right:8px;
|
||||
font-size:7px; font-weight:900; color:rgba(225,6,0,0.35); letter-spacing:2px;
|
||||
}
|
||||
.eg-title {
|
||||
font-size:18px; font-weight:900; color:#fff; margin-bottom:14px;
|
||||
letter-spacing:-0.3px; padding-bottom:10px;
|
||||
border-bottom:1px solid rgba(255,255,255,0.03);
|
||||
}
|
||||
.eg-step {
|
||||
padding:12px 16px; margin-bottom:8px; border-radius:2px;
|
||||
border-left:3px solid transparent;
|
||||
font-size:15px; line-height:1.8; color:rgba(255,255,255,0.7);
|
||||
background:rgba(255,255,255,0.01);
|
||||
animation:fIn 0.4s ease;
|
||||
}
|
||||
.eg-step.show { border-left-color:rgba(225,6,0,0.2); background:rgba(225,6,0,0.02); color:rgba(255,255,255,0.7); }
|
||||
.eg-step .step-num {
|
||||
display:inline-block; width:24px; height:24px; line-height:24px; text-align:center;
|
||||
font-size:11px; font-weight:900; background:rgba(225,6,0,0.1); color:#e10600;
|
||||
margin-right:10px; border-radius:2px; font-family:'Inter',monospace; float:left;
|
||||
}
|
||||
.eg-btn {
|
||||
margin:14px 0 0; padding:10px 24px; border:1px solid rgba(225,6,0,0.15);
|
||||
background:rgba(225,6,0,0.04); color:rgba(255,255,255,0.7); font-size:14px;
|
||||
font-weight:700; font-family:'Inter',monospace; cursor:pointer;
|
||||
transition:all 0.3s; border-radius:3px; letter-spacing:1px;
|
||||
}
|
||||
.eg-btn:hover { background:rgba(225,6,0,0.1); color:#fff; border-color:rgba(225,6,0,0.3); }
|
||||
.eg-btn:disabled { opacity:0.3; cursor:default; }
|
||||
|
||||
/* ===== CONFETTI ===== */
|
||||
.pyro { position:fixed; inset:0; pointer-events:none; z-index:99; overflow:hidden; }
|
||||
.pyro .sp {
|
||||
position:absolute; width:3px; height:3px; border-radius:0;
|
||||
animation:spFall linear forwards;
|
||||
}
|
||||
@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; }
|
||||
.track-wrap { max-height:460px; }
|
||||
.pm { padding:28px 20px 24px; }
|
||||
.pm .pn1 { font-size:42px; }
|
||||
}
|
||||
|
||||
/* === Track car move animation === */
|
||||
.track-car.moving { animation:boost 1.2s ease; }
|
||||
@keyframes boost {
|
||||
0% { filter:drop-shadow(0 0 8px rgba(225,6,0,0.4)); }
|
||||
40% { filter:drop-shadow(0 0 20px rgba(255,215,0,0.6)); }
|
||||
100% { filter:drop-shadow(0 0 8px rgba(225,6,0,0.4)); }
|
||||
}
|
||||
|
||||
/* 错题集 UI */
|
||||
.hudBtn {
|
||||
background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.06);
|
||||
color:rgba(255,255,255,0.5); font-size:11px; font-family:inherit; font-weight:700;
|
||||
padding:8px 14px; cursor:pointer; letter-spacing:1px; border-radius:3px;
|
||||
transition:all 0.2s; display:inline-flex; align-items:center; gap:4px;
|
||||
}
|
||||
.hudBtn:hover { background:rgba(255,255,255,0.06); color:rgba(255,255,255,0.8); }
|
||||
.hudBtn #hintBadge {
|
||||
background:#e10600; color:#fff; font-size:9px; font-weight:900;
|
||||
min-width:18px; height:18px; line-height:18px; text-align:center; border-radius:9px;
|
||||
padding:0 5px; display:inline-block;
|
||||
}
|
||||
.hudDanger:hover { border-color:rgba(225,6,0,0.2); color:#e10600; }
|
||||
.wrItem {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.03);
|
||||
border-left:3px solid #e10600; padding:14px 16px; margin-bottom:8px; border-radius:2px;
|
||||
}
|
||||
.wrH { font-size:10px; color:rgba(255,255,255,0.35); letter-spacing:1px; margin-bottom:6px; font-family:'Inter',monospace; }
|
||||
.wrLb { color:rgba(255,255,255,0.45); }
|
||||
.wrQ { font-size:15px; color:rgba(255,255,255,0.75); margin-bottom:10px; line-height:1.5; }
|
||||
.wrAct { display:flex; gap:8px; }
|
||||
.wrDo, .wrRm {
|
||||
background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.08);
|
||||
color:rgba(255,255,255,0.5); font-size:10px; font-family:inherit; font-weight:700;
|
||||
padding:6px 12px; cursor:pointer; letter-spacing:1px; border-radius:3px;
|
||||
transition:all 0.2s;
|
||||
}
|
||||
.wrDo:hover { background:rgba(46,204,113,0.04); color:#2ecc71; border-color:rgba(46,204,113,0.1); }
|
||||
.wrRm:hover { background:rgba(225,6,0,0.04); color:#e10600; border-color:rgba(225,6,0,0.1); }
|
||||
.geoDiag { margin-bottom:10px; }
|
||||
.geoDiag svg { display:block; max-height:160px; }
|
||||
|
||||
/* Answer map grid */
|
||||
.mBtn2 {
|
||||
padding:10px 20px; border:1px solid; font-size:13px; font-weight:700;
|
||||
font-family:'Inter',monospace; cursor:pointer; border-radius:3px;
|
||||
letter-spacing:1px; transition:all 0.2s; color:#fff;
|
||||
}
|
||||
.mBtn2:hover { filter:brightness(1.2); transform:translateY(-1px); }
|
||||
.qGrid {
|
||||
display:grid; grid-template-columns:repeat(auto-fill,minmax(64px,1fr));
|
||||
gap:8px; padding:4px 0;
|
||||
}
|
||||
.qCell {
|
||||
background:rgba(8,8,20,0.5); border:1px solid rgba(255,255,255,0.04);
|
||||
border-left:3px solid rgba(255,255,255,0.06); border-radius:3px;
|
||||
padding:10px 4px; text-align:center; cursor:pointer;
|
||||
transition:all 0.2s; display:flex; flex-direction:column; align-items:center; gap:4px;
|
||||
}
|
||||
.qCell:hover { background:rgba(255,255,255,0.04); border-color:rgba(255,255,255,0.1); }
|
||||
.qCell .qNum { font-size:14px; font-weight:700; color:rgba(255,255,255,0.4); font-family:'Inter',monospace; }
|
||||
.qCell .qSt { font-size:12px; }
|
||||
.qCell.correct { border-left-color:#2ecc71; background:rgba(46,204,113,0.04); }
|
||||
.qCell.correct .qNum { color:#2ecc71; }
|
||||
.qCell.wrong { border-left-color:#e10600; background:rgba(225,6,0,0.03); }
|
||||
.qCell.wrong .qNum { color:#e10600; }
|
||||
.qCell.unanswered .qNum { color:rgba(255,255,255,0.2); }
|
||||
|
||||
/* 分站赛 UI */
|
||||
.stgBar { height:4px; background:rgba(255,255,255,0.04); border-radius:2px; margin:10px 0; overflow:hidden; }
|
||||
.stgFill { height:100%; background:#e10600; border-radius:2px; transition:width 0.5s cubic-bezier(.34,1.56,.64,1); }
|
||||
.stgTag { display:inline-block; padding:3px 10px; border:1px solid rgba(255,255,255,0.06); border-radius:2px; font-size:11px; font-weight:700; color:rgba(255,255,255,0.4); margin:2px 4px; font-family:'Inter',monospace; }
|
||||
.stgTag:nth-child(1) { border-color:rgba(225,6,0,0.15); color:#e10600; }
|
||||
.stgTag:nth-child(2) { border-color:rgba(255,215,0,0.15); color:#ffd700; }
|
||||
.stgTag:nth-child(3) { border-color:rgba(46,204,113,0.15); color:#2ecc71; }
|
||||
|
||||
/* 口诀 */
|
||||
.kj {
|
||||
margin:12px 0 4px; padding:10px 14px; background:rgba(255,215,0,0.03);
|
||||
border:1px solid rgba(255,215,0,0.06); border-left:3px solid #ffd700; border-radius:2px;
|
||||
font-size:14px; line-height:1.9; color:rgba(255,255,255,0.8);
|
||||
}
|
||||
.kj::before { content:'\u{1F3B6} 口诀 '; font-weight:900; color:#ffd700; font-size:12px; letter-spacing:1px; display:block; margin-bottom:2px; }
|
||||
|
||||
/* 巧解 */
|
||||
.qj {
|
||||
margin:12px 0 4px; padding:10px 14px; background:rgba(46,204,113,0.03);
|
||||
border:1px solid rgba(46,204,113,0.06); border-left:3px solid #2ecc71; border-radius:2px;
|
||||
font-size:14px; line-height:1.9; color:rgba(255,255,255,0.8);
|
||||
}
|
||||
.qj::before { content:'\u{1F4A1} 巧解 '; font-weight:900; color:#2ecc71; font-size:12px; letter-spacing:1px; display:block; margin-bottom:2px; }
|
||||
|
||||
/* SVG 动画 */
|
||||
@keyframes svgPulse { 0%,100%{opacity:0.3} 50%{opacity:1} }
|
||||
@keyframes svgRotate { from{transform:rotate(0deg)} to{transform:rotate(360deg)} }
|
||||
@keyframes svgDash { to{stroke-dashoffset:0} }
|
||||
.animPulse { animation:svgPulse 2s ease infinite; }
|
||||
.animRotate { transform-origin:center; animation:svgRotate 6s linear infinite; }
|
||||
.animDash { stroke-dasharray:200; stroke-dashoffset:200; animation:svgDash 2s ease forwards; }
|
||||
|
||||
/* === Interactive Drag Models === */
|
||||
.drag-point { cursor:grab; }
|
||||
.drag-point:active { cursor:grabbing; }
|
||||
.drag-point:hover { filter:drop-shadow(0 0 8px rgba(225,6,0,0.6)); }
|
||||
.drag-point .dp-ring { transition:r 0.15s; }
|
||||
.drag-point:hover .dp-ring { r:7; }
|
||||
.m-panel { margin:8px 0; padding:10px 14px; background:rgba(0,0,0,0.15); border-radius:4px; display:flex; flex-wrap:wrap; gap:6px 14px; justify-content:center; }
|
||||
.m-val { display:inline-flex; align-items:center; gap:4px; font-size:13px; font-weight:700; font-family:'Inter',monospace; }
|
||||
.m-val .lbl { color:rgba(255,255,255,0.3); font-size:10px; letter-spacing:1px; }
|
||||
.m-val .num { color:#ffd700; font-size:14px; }
|
||||
.m-val .num.hl { color:#e10600; }
|
||||
.m-hint { text-align:center; font-size:11px; color:rgba(255,255,255,0.25); letter-spacing:1px; margin:4px 0; font-family:'Inter',monospace; animation:pulse 2s ease infinite; }
|
||||
.m-discover { text-align:center; font-size:14px; color:#2ecc71; font-weight:700; margin:4px 0; animation:slideUp 0.5s ease; }
|
||||
.drag-line { cursor:pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<div class="app" id="app">
|
||||
|
||||
<!-- HUD -->
|
||||
<div class="hud">
|
||||
<div class="logo">
|
||||
<span class="r">RED BULL</span>
|
||||
<span class="w"> / </span>
|
||||
<span class="y">MV1</span>
|
||||
</div>
|
||||
<div class="hud-spacer"></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>
|
||||
|
||||
<!-- HOME — TRACK MAP -->
|
||||
<div class="pg act" id="pH">
|
||||
<div class="track-wrap" id="trackWrap">
|
||||
<span class="track-car" id="trackCar">
|
||||
<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="wingGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#e10600"/>
|
||||
<stop offset="100%" stop-color="#cc0500"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<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="#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="#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="#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="#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"/>
|
||||
<rect x="16" y="2" width="3" height="4" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="22" width="3" height="4" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="38" y="2.5" width="3" height="3.5" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="38" y="22" width="3" height="3.5" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="3" width="1" height="2" rx="0.5" fill="#e10600"/>
|
||||
<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="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>
|
||||
</span>
|
||||
<svg viewBox="0 0 360 540" preserveAspectRatio="xMidYMid meet">
|
||||
<!-- S-curve track — road surface -->
|
||||
<path class="track-road" d="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"/>
|
||||
<!-- track edges -->
|
||||
<path class="track-bg" d="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"/>
|
||||
<!-- remaining circuit (dashed center line) -->
|
||||
<path class="track-fill" id="trackFill" d="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"/>
|
||||
<!-- completed portion (gold) -->
|
||||
<path class="track-done" id="trackDone" d="M 180,20" stroke-dasharray="800" stroke-dashoffset="800"/>
|
||||
|
||||
<!-- Finish line -->
|
||||
<line x1="160" y1="515" x2="200" y2="515" stroke="#ffd700" stroke-width="2" opacity="0.15"/>
|
||||
<line x1="160" y1="520" x2="200" y2="520" stroke="#ffd700" stroke-width="2" opacity="0.1"/>
|
||||
<line x1="160" y1="525" x2="200" y2="525" stroke="#ffd700" stroke-width="2" opacity="0.08"/>
|
||||
|
||||
<!-- CP 1 — 有理数 -->
|
||||
<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">🏆 #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>
|
||||
|
||||
<!-- CP 2 — 整式的加减 -->
|
||||
<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">🏆 #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>
|
||||
|
||||
<!-- CP 3 — 一元一次方程 -->
|
||||
<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">🏆 #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>
|
||||
|
||||
<!-- CP 4 — 几何图形初步 -->
|
||||
<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">🏆 #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>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<div id="dbPanel" style="display:none;margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- CHAPTER -->
|
||||
<div class="pg" id="pC">
|
||||
<div style="display:flex;gap:8px;align-items:center;margin-bottom:16px;">
|
||||
<button class="bk" id="bkMap" style="margin-bottom:0">← 赛道</button>
|
||||
<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">🏆 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>
|
||||
|
||||
<!-- ANSWER MAP -->
|
||||
<div class="pg" id="pM">
|
||||
<div style="display:flex;gap:8px;align-items:center;margin-bottom:16px;">
|
||||
<button class="bk" id="bkMapM" style="margin-bottom:0">← 赛道</button>
|
||||
<button class="bk" id="bkChapM" style="margin-bottom:0">← 返回</button>
|
||||
</div>
|
||||
<div class="ch">
|
||||
<div class="gl" id="mGl">答题地图</div>
|
||||
<h2 id="mTi"></h2>
|
||||
<div class="sm" id="mDe">点击题目跳转</div>
|
||||
</div>
|
||||
<div id="mGrid"></div>
|
||||
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:16px;">
|
||||
<button class="mBtn2" id="mUn" style="background:#ffd700;color:#0a0a1a;border-color:#ffd700;">▶ 从未答开始</button>
|
||||
<button class="mBtn2" id="mErr" style="background:#e10600;border-color:#e10600;">✖ 从错题开始</button>
|
||||
<button class="mBtn2" id="mAll" style="background:rgba(255,255,255,0.05);border-color:rgba(255,255,255,0.1);">🔄 全部重做</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WRONG REVIEW -->
|
||||
<div class="pg" id="pW">
|
||||
<button class="bk" id="bkMapW">← 赛道</button>
|
||||
<div class="ch"><div class="gl" id="wGl">\u{1F4D2} 错题集</div><h2>错题再练</h2><div class="sm" id="wDe">错了不怕,练到对为止</div></div>
|
||||
<div id="wList"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<span class="pcup" id="pmC">🏆</span>
|
||||
<h2 id="pmTi">冠军</h2>
|
||||
<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>
|
||||
|
||||
<div class="pyro" id="pyro"></div>
|
||||
|
||||
<!-- TAB BAR -->
|
||||
<div class="tabs">
|
||||
<div class="tab act" data-tab="home"><span class="ti">🏠</span><span class="tl">学习</span></div>
|
||||
<div class="tab" data-tab="review"><span class="ti">🔄</span><span class="tl">复习</span></div>
|
||||
<div class="tab" data-tab="me"><span class="ti">👤</span><span class="tl">我的</span></div>
|
||||
</div>
|
||||
|
||||
<!-- HOME -->
|
||||
<div class="pg act" id="pHome">
|
||||
<div class="hd"><h1>单词<span style="color:#6366f1">乐园</span></h1><div class="hd-r"><span class="ci" id="hStreak">🔥 0天</span> <span class="ci" id="hLearned" style="background:rgba(99,102,241,0.06);color:#6366f1;border-color:rgba(99,102,241,0.1);">📖 0</span></div></div>
|
||||
<div class="strk" id="weekBar"></div>
|
||||
<div id="checkInArea" style="margin-bottom:8px;"></div>
|
||||
<!-- Continue learning -->
|
||||
<div id="continueCard"></div>
|
||||
<!-- Today's tasks -->
|
||||
<div class="cd"><div class="ct">📋 今日任务</div>
|
||||
<div id="taskList"></div>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;margin:8px 0;">
|
||||
<div class="sb" style="cursor:pointer;flex:1;" onclick="startConj('s')"><div class="n">🔤</div><div class="l">变形</div></div>
|
||||
<div class="sb" style="cursor:pointer;flex:1;" onclick="startGrammarLearn()"><div class="n">📖</div><div class="l">语法</div></div>
|
||||
<div class="sb" style="cursor:pointer;flex:1;" onclick="startReading()"><div class="n">📖</div><div class="l">阅读</div></div>
|
||||
<div class="sb" style="cursor:pointer;flex:1;" onclick="startExam(0)"><div class="n">📝</div><div class="l">考试</div></div>
|
||||
</div>
|
||||
<div class="sr"><div class="sb"><div class="n" id="sLearned">0</div><div class="l">已学词</div></div>
|
||||
<div class="sb"><div class="n" id="sMastered">0</div><div class="l">掌握</div></div>
|
||||
<div class="sb"><div class="n" id="sDue">0</div><div class="l">待复习</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UNITS -->
|
||||
<div class="pg" id="pUnits">
|
||||
<h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">📚 单元列表</h2>
|
||||
<div class="ul" id="unitGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- REVIEW -->
|
||||
<div class="pg" id="pReview">
|
||||
<h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">🔄 复习</h2>
|
||||
<div id="reviewContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- EXAM -->
|
||||
<div class="pg" id="pExamHome">
|
||||
<h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">📝 单元考试</h2>
|
||||
<div class="ul" id="examUnitGrid"></div>
|
||||
</div>
|
||||
<div class="pg" id="pExam">
|
||||
<button class="big-btn g" id="bkExam" style="margin-bottom:10px;">← 返回</button>
|
||||
<div class="exam-q"><div style="display:flex;justify-content:space-between;align-items:center;"><span id="eProg" style="font-size:11px;font-weight:600;color:rgba(26,26,46,0.3)"></span><span id="eTitle" style="font-size:11px;font-weight:600;color:#6366f1"></span></div></div>
|
||||
<div id="eContent"></div>
|
||||
<div class="fb" id="eFb"></div>
|
||||
</div>
|
||||
|
||||
<!-- ME -->
|
||||
<div class="pg" id="pMe">
|
||||
<h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">👤 我的</h2>
|
||||
<div class="cd"><div class="ct">📊 总统计</div>
|
||||
<div class="sr"><div class="sb"><div class="n" id="mTotalQuiz">0</div><div class="l">总答题</div></div>
|
||||
<div class="sb"><div class="n" id="mAccuracy">0%</div><div class="l">正确率</div></div>
|
||||
<div class="sb"><div class="n" id="mWrongCount">0</div><div class="l">错词本</div></div></div>
|
||||
</div>
|
||||
<div class="cd"><div class="ct">📝 语法学习</div><div id="grammarCardList"></div></div>
|
||||
<div style="display:flex;gap:8px;margin-top:8px;">
|
||||
<button class="big-btn g" id="btnWrongBank" style="flex:1;font-size:13px;padding:12px;">❌ 易错词</button>
|
||||
<button class="big-btn g" id="btnGrammarStart" style="flex:1;font-size:13px;padding:12px;">📖 语法</button>
|
||||
<button class="big-btn g" id="btnConjugate" style="flex:1;font-size:13px;padding:12px;">🔤 动词变形</button>
|
||||
<button class="big-btn g" id="btnClear" style="flex:1;font-size:13px;padding:12px;background:#fef2f2;color:#dc2626;border:1px solid rgba(220,38,38,0.1);">🗑️ 清除</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WORD LIST (overlay) -->
|
||||
<div class="pg" id="pWordList"><button class="big-btn g" id="bkWordList" style="margin-bottom:10px;">← 返回</button><div id="wordListContent"></div></div>
|
||||
|
||||
<!-- LEARN/QUIZ PAGE -->
|
||||
<div class="pg" id="pLearn">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
|
||||
<button class="big-btn g" id="bkLearn" style="padding:8px 14px;font-size:12px;flex:0;background:#f0f0f5;color:rgba(26,26,46,0.3);">✕</button>
|
||||
<div style="flex:1;height:4px;background:#f0f0f5;border-radius:2px;overflow:hidden;"><div class="pb-f" id="learnProg" style="height:100%;background:linear-gradient(90deg,#6366f1,#818cf8);border-radius:2px;width:0%;transition:width 0.4s ease"></div></div>
|
||||
<span id="learnCount" style="font-size:11px;font-weight:600;color:rgba(26,26,46,0.3);font-family:'Inter',monospace">0/0</span>
|
||||
</div>
|
||||
<div id="learnContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- GRAMMAR LEARN PAGE -->
|
||||
<div class="pg" id="pGrammarLearn">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;">
|
||||
<button class="big-btn g" id="bkGrammar" style="padding:8px 14px;font-size:12px;flex:0;background:#f0f0f5;color:rgba(26,26,46,0.3);">✕</button>
|
||||
<div style="flex:1;text-align:center;"><span id="gProg" style="font-size:11px;font-weight:600;color:rgba(26,26,46,0.3);font-family:'Inter',monospace">0/0</span></div>
|
||||
</div>
|
||||
<div id="grammarContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- READING PAGE -->
|
||||
<div class="pg" id="pReading">
|
||||
<button class="big-btn g" id="bkReading" style="margin-bottom:8px;">← 返回复习</button>
|
||||
<div id="readingContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- CONJUGATION PRACTICE -->
|
||||
<div class="pg" id="pConj">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
|
||||
<button class="big-btn g" id="bkConj" style="padding:8px 14px;font-size:12px;flex:0;background:#f0f0f5;color:rgba(26,26,46,0.3);">✕</button>
|
||||
<div style="display:flex;gap:4px;" id="conjModes">
|
||||
<span class="cm act" data-mode="s">三单</span>
|
||||
<span class="cm" data-mode="ed">过去式</span>
|
||||
<span class="cm" data-mode="pp">完成时</span>
|
||||
</div>
|
||||
<div style="flex:1;text-align:right;"><span id="conjScore" style="font-size:11px;font-weight:600;color:rgba(26,26,46,0.3);font-family:'Inter',monospace">0/0</span></div>
|
||||
</div>
|
||||
<div id="conjContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div class="modal" id="ciModal"><div class="modal-in" id="modalInner"></div></div>
|
||||
|
||||
<script>
|
||||
// ===== DATA =====
|
||||
const UNITS = [
|
||||
{id:'S1',em:'☀️',title:'Good Morning!',sub:'问候·字母',gr:'be动词(am/is/are):I用am,you用are,he/she/it用is。这是英语最基本的动词变化。',ph:'字母A-N的发音:/eɪ/ A H J K,/i:/ B C D E G P T V,/e/ F L M N S X Z',words:[
|
||||
{e:'good',c:'好的',em:'👍',s:'Good morning, class!'},{e:'morning',c:'早上',em:'🌅',s:'I have breakfast in the morning.'},
|
||||
{e:'afternoon',c:'下午',em:'☀️',s:'Good afternoon, Miss Li!'},{e:'evening',c:'晚上',em:'🌆',s:'Good evening, Mom!'},
|
||||
{e:'fine',c:'好的;健康的',em:'💪',s:'I am fine, thank you.'},{e:'OK',c:'好;行',em:'👌',s:'OK, let us go!'},
|
||||
{e:'hello',c:'你好',em:'👋',s:'Hello, my friend!'},{e:'hi',c:'嗨',em:'✋',s:'Hi, how are you?'},
|
||||
{e:'thanks',c:'谢谢',em:'🙏',s:'Thanks for your help.'},{e:'how',c:'怎样;怎么',em:'❓',s:'How are you today?'},
|
||||
{e:'I',c:'我',em:'👤',s:'I am twelve years old.'},{e:'you',c:'你;你们',em:'👉',s:'You are nice.'},
|
||||
{e:'am',c:'是(I用)',em:'✅',s:'I am a student.'},{e:'are',c:'是(you/复数用)',em:'✅',s:'You are my friend.'},
|
||||
{e:'is',c:'是(he/she/it用)',em:'✅',s:'She is a teacher.'},{e:'please',c:'请',em:'🙏',s:'Sit down, please.'},
|
||||
]},
|
||||
{id:'S2',em:'✏️',title:"What's This?",sub:'物品·冠词',gr:'冠词a和an:a用于辅音音素前( a useful book ),an用于元音音素前( an hour, an apple )。看发音不是看字母。',ph:'字母O-Z的发音:/aɪ/ I Y,/oʊ/ O,/u:/ U Q W,/a:/ R,/z/ Z',words:[
|
||||
{e:'what',c:'什么',em:'❓',s:'What is this in English?'},{e:'this',c:'这个',em:'👆',s:'This is my pen.'},
|
||||
{e:'that',c:'那个',em:'👉',s:'That is a ruler.'},{e:'it',c:'它',em:'🎯',s:'It is a map.'},
|
||||
{e:'a',c:'一个(辅音前)',em:'1️⃣',s:'I have a pen.'},{e:'an',c:'一个(元音前)',em:'1️⃣',s:'It is an orange.'},
|
||||
{e:'English',c:'英语',em:'🇬🇧',s:'I like English.'},{e:'map',c:'地图',em:'🗺️',s:'This is a map of China.'},
|
||||
{e:'key',c:'钥匙',em:'🔑',s:'Where is my key?'},{e:'jacket',c:'夹克衫',em:'🧥',s:'My jacket is blue.'},
|
||||
{e:'cup',c:'杯子',em:'🥤',s:'This cup is red.'},{e:'ruler',c:'尺子',em:'📏',s:'I need a ruler.'},
|
||||
{e:'pen',c:'钢笔',em:'🖊️',s:'This pen is black.'},{e:'orange',c:'橙子',em:'🍊',s:'The orange is sweet.'},
|
||||
{e:'spell',c:'拼写',em:'🔤',s:'How do you spell it?'},{e:'in',c:'用;在…里',em:'📥',s:'What is this in English?'},
|
||||
]},
|
||||
{id:'S3',em:'🌈',title:'What Color?',sub:'颜色',gr:'情态动词can:can + 动词原形,表示"能、会"。否定can\'t = cannot。无第三人称变化。',ph:'元音字母a/e/i/o/u在重读闭音节中的发音:/æ/ cat map, /e/ pen red, /ɪ/ big sit, /ɒ/ dog hot, /ʌ/ cup bus',words:[
|
||||
{e:'color',c:'颜色',em:'🎨',s:'What color is it?'},{e:'red',c:'红色',em:'🔴',s:'The apple is red.'},
|
||||
{e:'yellow',c:'黄色',em:'🟡',s:'The sun is yellow.'},{e:'green',c:'绿色',em:'🟢',s:'The tree is green.'},
|
||||
{e:'blue',c:'蓝色',em:'🔵',s:'The sky is blue.'},{e:'black',c:'黑色',em:'⚫',s:'My cat is black.'},
|
||||
{e:'white',c:'白色',em:'⚪',s:'Snow is white.'},{e:'purple',c:'紫色',em:'🟣',s:'I like purple flowers.'},
|
||||
{e:'brown',c:'棕色',em:'🟤',s:'The dog is brown.'},{e:'see',c:'看见',em:'👁️',s:'I see a red flower.'},
|
||||
{e:'can',c:'能;会',em:'💪',s:'I can see a bird.'},{e:'say',c:'说',em:'💬',s:'Say it in English.'},
|
||||
{e:'my',c:'我的',em:'👤',s:'My bag is red.'},{e:'your',c:'你的',em:'👉',s:'Your book is new.'},
|
||||
]},
|
||||
{id:'U1',em:'👋',title:"My Name's Gina",sub:'自我介绍·数字·be动词',gr:'be动词完整变化:I am / You are / He/She/It is / We are / They are。否定:I\'m not / He isn\'t / They aren\'t。疑问:Are you? Is he?',ph:'元音字母a在开音节中读/eɪ/:name, cake, date。在闭音节中读/æ/:map, cat, bag。',words:[
|
||||
{e:'name',c:'名字',em:'🏷️',s:'My name is Tom.'},{e:'nice',c:'令人愉快的',em:'😊',s:'Nice to meet you!'},
|
||||
{e:'meet',c:'遇见',em:'🤝',s:'Nice to meet you!'},{e:'too',c:'也',em:'➕',s:'Nice to meet you, too!'},
|
||||
{e:'your',c:'你的',em:'👉',s:'What is your name?'},{e:'his',c:'他的',em:'👦',s:'His name is Mike.'},
|
||||
{e:'her',c:'她的',em:'👧',s:'Her bag is red.'},{e:'yes',c:'是的',em:'👍',s:'Yes, I am.'},
|
||||
{e:'no',c:'不',em:'👎',s:'No, it isn\'t.'},{e:'he',c:'他',em:'👦',s:'He is my friend.'},
|
||||
{e:'she',c:'她',em:'👧',s:'She is a teacher.'},{e:'first',c:'第一的',em:'🥇',s:'My first name is Tom.'},
|
||||
{e:'last',c:'最后的',em:'🏁',s:'My last name is Smith.'},{e:'friend',c:'朋友',em:'🤝',s:'She is my friend.'},
|
||||
{e:'zero',c:'零',em:'0️⃣',s:'zero + one = one'},{e:'one',c:'一',em:'1️⃣',s:'I have one apple.'},
|
||||
{e:'two',c:'二',em:'2️⃣',s:'Two cats are playing.'},{e:'three',c:'三',em:'3️⃣',s:'Three birds.'},
|
||||
{e:'four',c:'四',em:'4️⃣',s:'I have four pens.'},{e:'five',c:'五',em:'5️⃣',s:'Five stars for you!'},
|
||||
{e:'six',c:'六',em:'6️⃣',s:'Six books.'},{e:'seven',c:'七',em:'7️⃣',s:'Seven days.'},
|
||||
{e:'eight',c:'八',em:'8️⃣',s:'I am eight.'},{e:'nine',c:'九',em:'9️⃣',s:'nine + one = ten'},
|
||||
{e:'telephone',c:'电话',em:'📞',s:'My telephone number is 123.'},{e:'number',c:'号码;数字',em:'🔢',s:'What is your number?'},
|
||||
{e:'school',c:'学校',em:'🏫',s:'I go to school by bus.'},{e:'middle',c:'中间的',em:'🀄',s:'middle school 中学'},
|
||||
]},
|
||||
{id:'U2',em:'👨👩👧👦',title:'This Is My Sister',sub:'家庭·指示代词',gr:'指示代词this/these(近处)和that/those(远处)。This is my sister. These are my parents. That is a book. Those are pens。注意be动词单复数。',ph:'字母组合th的发音:/θ/ thank, three, thirty;/ð/ this, that, those, mother。',words:[
|
||||
{e:'sister',c:'姐;妹',em:'👧',s:'My sister is kind.'},{e:'brother',c:'兄;弟',em:'👦',s:'My brother likes sports.'},
|
||||
{e:'mother',c:'母亲',em:'👩',s:'My mother cooks dinner.'},{e:'father',c:'父亲',em:'👨',s:'My father is tall.'},
|
||||
{e:'parent',c:'父/母亲',em:'👪',s:'My parents are at home.'},{e:'grandma',c:'奶奶;姥姥',em:'👵',s:'Grandma is nice.'},
|
||||
{e:'grandpa',c:'爷爷;姥爷',em:'👴',s:'Grandpa walks every day.'},{e:'grandmother',c:'(外)祖母',em:'👵',s:'Grandma tells stories.'},
|
||||
{e:'grandfather',c:'(外)祖父',em:'👴',s:'Grandpa reads newspaper.'},{e:'family',c:'家庭',em:'👪',s:'My family has 4 people.'},
|
||||
{e:'those',c:'那些',em:'👉👈',s:'Those are my friends.'},{e:'these',c:'这些',em:'👈👈',s:'These are my books.'},
|
||||
{e:'who',c:'谁',em:'❓',s:'Who is she?'},{e:'aunt',c:'阿姨;姑姑',em:'👩',s:'My aunt is a doctor.'},
|
||||
{e:'uncle',c:'叔叔;舅舅',em:'👨',s:'My uncle drives a car.'},{e:'son',c:'儿子',em:'👦',s:'He is her son.'},
|
||||
{e:'daughter',c:'女儿',em:'👧',s:'She is my daughter.'},{e:'cousin',c:'堂/表兄弟姐妹',em:'🧑🤝🧑',s:'My cousin is my age.'},
|
||||
{e:'picture',c:'图片;照片',em:'🖼️',s:'This is a family picture.'},{e:'photo',c:'照片',em:'📸',s:'Look at this photo.'},
|
||||
{e:'here',c:'这里',em:'📍',s:'Here is my family photo.'},{e:'dog',c:'狗',em:'🐕',s:'Our dog is friendly.'},
|
||||
]},
|
||||
{id:'U3',em:'🎒',title:'Is This Your Pencil?',sub:'文具·物主代词',gr:'形容词性物主代词(my/your/his/her/its/our/their)必须+名词。名词性物主代词(mine/yours/his/hers/its/ours/theirs)单独使用。This is my pen. = This pen is mine.',ph:'元音字母i在开音节中读/aɪ/:like, time, fine。在闭音节中读/ɪ/:big, sister, pencil。',words:[
|
||||
{e:'pencil',c:'铅笔',em:'✏️',s:'Is this your pencil?'},{e:'book',c:'书',em:'📖',s:'I read a book every day.'},
|
||||
{e:'eraser',c:'橡皮',em:'🧹',s:'I need an eraser.'},{e:'schoolbag',c:'书包',em:'🎒',s:'My schoolbag is heavy.'},
|
||||
{e:'dictionary',c:'字典',em:'📕',s:'Look it up in the dictionary.'},{e:'teacher',c:'老师',em:'👩🏫',s:'My teacher is nice.'},
|
||||
{e:'student',c:'学生',em:'🧑🎓',s:'He is a good student.'},{e:'classroom',c:'教室',em:'🏫',s:'Our classroom is big.'},
|
||||
{e:'library',c:'图书馆',em:'📚',s:'I like the school library.'},{e:'watch',c:'手表',em:'⌚',s:'My watch is new.'},
|
||||
{e:'ring',c:'戒指',em:'💍',s:'This is a gold ring.'},{e:'bag',c:'包',em:'👜',s:'I have a new bag.'},
|
||||
{e:'baseball',c:'棒球',em:'⚾',s:'Let\'s play baseball.'},{e:'help',c:'帮助',em:'🤲',s:'Can you help me?'},
|
||||
{e:'welcome',c:'欢迎',em:'👏',s:'Welcome to our school!'},{e:'excuse',c:'原谅',em:'🙇',s:'Excuse me!'},
|
||||
{e:'me',c:'我(宾格)',em:'👤',s:'Excuse me! Help me!'},{e:'find',c:'找到',em:'🔍',s:'I can\'t find my pen.'},
|
||||
{e:'lost',c:'丢失的',em:'😰',s:'I lost my keys.'},{e:'call',c:'打电话',em:'📞',s:'Please call me at 123.'},
|
||||
{e:'must',c:'必须',em:'⚠️',s:'You must finish homework.'},{e:'classmate',c:'同班同学',em:'🧑🎓',s:'My classmate is friendly.'},
|
||||
]},
|
||||
{id:'U4',em:'🛏️',title:"Where's My Schoolbag?",sub:'方位·介词',gr:'方位介词:in(在…里) on(在…上) under(在…下) behind(在…后) next to(在…旁)。Where问地点:Where is my bag? It\'s on the desk。',ph:'字母组合sh读/ʃ/:she, shirt, shoes。字母组合ch读/tʃ/:chair, watch, teacher。',words:[
|
||||
{e:'where',c:'在哪里',em:'📍',s:'Where is my pen?'},{e:'desk',c:'书桌',em:'🪑',s:'My book is on the desk.'},
|
||||
{e:'table',c:'桌子',em:'🪑',s:'The cat is under the table.'},{e:'chair',c:'椅子',em:'💺',s:'Sit on the chair.'},
|
||||
{e:'bookcase',c:'书柜',em:'📚',s:'Books are in the bookcase.'},{e:'bed',c:'床',em:'🛏️',s:'I sleep on the bed.'},
|
||||
{e:'sofa',c:'沙发',em:'🛋️',s:'The cat is on the sofa.'},{e:'drawer',c:'抽屉',em:'🗄️',s:'It\'s in the drawer.'},
|
||||
{e:'plant',c:'植物',em:'🌱',s:'The plant is near the window.'},{e:'under',c:'在…下面',em:'⬇️',s:'The ball is under the chair.'},
|
||||
{e:'on',c:'在…上面',em:'⬆️',s:'The book is on the table.'},{e:'in',c:'在…里面',em:'📥',s:'The pen is in the bag.'},
|
||||
{e:'behind',c:'在…后面',em:'⬅️',s:'The dog is behind the door.'},{e:'next to',c:'在…旁边',em:'➡️',s:'The desk is next to the bed.'},
|
||||
{e:'between',c:'在…之间',em:'🔄',s:'The ball is between the chairs.'},{e:'near',c:'在…附近',em:'📍',s:'The bank is near my school.'},
|
||||
{e:'room',c:'房间',em:'🚪',s:'My room is clean.'},{e:'door',c:'门',em:'🚪',s:'Open the door.'},
|
||||
{e:'window',c:'窗户',em:'🪟',s:'The window is big.'},{e:'know',c:'知道',em:'🧠',s:'I don\'t know.'},
|
||||
{e:'think',c:'认为;想',em:'🤔',s:'I think it\'s in your bag.'},
|
||||
]},
|
||||
{id:'U5',em:'⚽',title:'Do You Have a Soccer Ball?',sub:'体育·一般现在时',gr:'一般现在时三单:he/she/it作主语动词+s/es。have→has, do→does, go→goes。否定用doesn\'t+动原:He doesn\'t like sports。疑问用Does:Does he have a ball?',ph:'元音o在开音节读/oʊ/:go, home, nose。在闭音节读/ɒ/或/ɑː/:box, dog, hot。',words:[
|
||||
{e:'have',c:'有(非三单)',em:'🤲',s:'I have a soccer ball.'},{e:'has',c:'有(三单)',em:'🤲',s:'He has a basketball.'},
|
||||
{e:'soccer',c:'英式足球',em:'⚽',s:'Let\'s play soccer!'},{e:'basketball',c:'篮球',em:'🏀',s:'I like basketball.'},
|
||||
{e:'volleyball',c:'排球',em:'🏐',s:'She plays volleyball well.'},{e:'tennis',c:'网球',em:'🎾',s:'Tennis is fun.'},
|
||||
{e:'ball',c:'球',em:'⚪',s:'The ball is red.'},{e:'sport',c:'运动',em:'🏃',s:'My favorite sport is swimming.'},
|
||||
{e:'play',c:'玩;打(球)',em:'🎮',s:'I play soccer after school.'},{e:'watch',c:'观看',em:'👀',s:'We watch TV at home.'},
|
||||
{e:'game',c:'比赛;游戏',em:'🎯',s:'The game is exciting.'},{e:'fun',c:'有趣的',em:'😄',s:'Playing soccer is fun.'},
|
||||
{e:'boring',c:'无聊的',em:'😴',s:'This movie is boring.'},{e:'difficult',c:'困难的',em:'😰',s:'Math is difficult.'},
|
||||
{e:'easy',c:'容易的',em:'😊',s:'This test is easy.'},{e:'relaxing',c:'放松的',em:'😌',s:'Music is relaxing.'},
|
||||
{e:'let',c:'让',em:'✅',s:'Let\'s go!'},{e:'go',c:'去',em:'🚶',s:'Let\'s go to school.'},
|
||||
{e:'great',c:'太好了',em:'👍',s:'That sounds great!'},{e:'sound',c:'听起来',em:'🔊',s:'That sounds fun.'},
|
||||
{e:'class',c:'课;班级',em:'📚',s:'We have English class.'},{e:'late',c:'晚的;迟的',em:'⏰',s:'Don\'t be late!'},
|
||||
{e:'only',c:'只;仅仅',em:'🔢',s:'I have only one ball.'},
|
||||
]},
|
||||
{id:'U6',em:'🍎',title:'Do You Like Bananas?',sub:'食物·可数/不可数',gr:'可数名词有复数(apple→apples),不可数词无复数(rice, water, milk)。some修饰可数和不可数。Do you like...? 问喜好。like+可数复数/不可数:I like apples / I like milk。',ph:'字母组合ea读/i:/:eat, read, teacher;读/e/:bread, breakfast, healthy。',words:[
|
||||
{e:'like',c:'喜欢',em:'❤️',s:'I like apples.'},{e:'banana',c:'香蕉',em:'🍌',s:'Bananas are yellow.'},
|
||||
{e:'apple',c:'苹果',em:'🍎',s:'An apple a day keeps doctor away.'},{e:'orange',c:'橙子',em:'🍊',s:'This orange is sweet.'},
|
||||
{e:'pear',c:'梨',em:'🍐',s:'The pear is juicy.'},{e:'strawberry',c:'草莓',em:'🍓',s:'I like strawberries.'},
|
||||
{e:'hamburger',c:'汉堡包',em:'🍔',s:'Let\'s eat hamburgers.'},{e:'rice',c:'米饭[U]',em:'🍚',s:'I eat rice for lunch.'},
|
||||
{e:'bread',c:'面包[U]',em:'🍞',s:'Bread is good for breakfast.'},{e:'milk',c:'牛奶[U]',em:'🥛',s:'I drink milk every day.'},
|
||||
{e:'water',c:'水[U]',em:'💧',s:'Drink more water.'},{e:'egg',c:'鸡蛋',em:'🥚',s:'I have an egg.'},
|
||||
{e:'chicken',c:'鸡肉[U]',em:'🍗',s:'Chicken is delicious.'},{e:'salad',c:'沙拉[U]',em:'🥗',s:'I like fruit salad.'},
|
||||
{e:'ice cream',c:'冰淇淋[U]',em:'🍦',s:'Ice cream is cold and sweet.'},{e:'healthy',c:'健康的',em:'💪',s:'Eating veg is healthy.'},
|
||||
{e:'food',c:'食物',em:'🍽️',s:'Chinese food is delicious.'},{e:'fruit',c:'水果',em:'🍎',s:'Fruit is good for us.'},
|
||||
{e:'vegetable',c:'蔬菜',em:'🥦',s:'Eat more vegetables.'},{e:'breakfast',c:'早餐',em:'🌅',s:'I have breakfast at 7.'},
|
||||
{e:'lunch',c:'午餐',em:'☀️',s:'We have lunch at school.'},{e:'dinner',c:'晚餐',em:'🌙',s:'Family dinner is warm.'},
|
||||
{e:'carrot',c:'胡萝卜',em:'🥕',s:'Carrots are healthy.'},{e:'tomato',c:'番茄',em:'🍅',s:'Tomatoes are red.'},
|
||||
{e:'eat',c:'吃',em:'🍽️',s:'Let\'s eat together.'},{e:'drink',c:'喝',em:'🥤',s:'Drink some water.'},
|
||||
{e:'sure',c:'当然',em:'👍',s:'Sure!'},{e:'well',c:'好地',em:'👌',s:'I eat well every day.'},
|
||||
]},
|
||||
{id:'U7',em:'👕',title:'How Much Are These Socks?',sub:'购物·价格',gr:'How much问价格:How much is + 单数/不可数? How much are + 复数? I\'ll take it. 我买了。want to + 动原:I want to buy a shirt。购物用语:Can I help you?',ph:'字母组合oo读/u:/:school, cool, food;读/ʊ/:book, good, look, foot。',words:[
|
||||
{e:'how much',c:'多少钱',em:'💰',s:'How much is it?'},{e:'dollar',c:'美元',em:'💵',s:'It is five dollars.'},
|
||||
{e:'big',c:'大的',em:'🐘',s:'The store is big.'},{e:'small',c:'小的',em:'🐭',s:'I need a small size.'},
|
||||
{e:'long',c:'长的',em:'📏',s:'This ruler is long.'},{e:'short',c:'短的;矮的',em:'📐',s:'The pencil is short.'},
|
||||
{e:'tall',c:'高的',em:'🗼',s:'My father is tall.'},{e:'socks',c:'袜子(pl.)',em:'🧦',s:'These socks are warm.'},
|
||||
{e:'shoes',c:'鞋子(pl.)',em:'👟',s:'My shoes are new.'},{e:'shirt',c:'衬衫',em:'👔',s:'Your shirt is nice.'},
|
||||
{e:'T-shirt',c:'T恤',em:'👕',s:'I like your T-shirt.'},{e:'sweater',c:'毛衣',em:'🧶',s:'This sweater is soft.'},
|
||||
{e:'jacket',c:'夹克',em:'🧥',s:'Put on your jacket.'},{e:'skirt',c:'裙子',em:'👗',s:'She wears a red skirt.'},
|
||||
{e:'hat',c:'帽子',em:'🎩',s:'This hat is cool.'},{e:'price',c:'价格',em:'🏷️',s:'The price is good.'},
|
||||
{e:'buy',c:'买',em:'🛒',s:'I want to buy a book.'},{e:'sell',c:'卖',em:'🏪',s:'The store sells shoes.'},
|
||||
{e:'cheap',c:'便宜的',em:'💲',s:'This pen is cheap.'},{e:'expensive',c:'昂贵的',em:'💎',s:'The watch is expensive.'},
|
||||
{e:'store',c:'商店',em:'🏪',s:'The store sells shoes.'},{e:'want',c:'想要',em:'🫴',s:'I want a new bag.'},
|
||||
{e:'take',c:'拿;买下',em:'🛍️',s:'I\'ll take it.'},{e:'need',c:'需要',em:'📋',s:'I need a pair of shoes.'},
|
||||
{e:'look',c:'看',em:'👀',s:'Look at this nice hat!'},{e:'pair',c:'一双;一对',em:'👫',s:'a pair of socks'},
|
||||
{e:'come',c:'来',em:'🚶',s:'Come and buy your clothes!'},
|
||||
]},
|
||||
{id:'U8',em:'🎂',title:'When Is Your Birthday?',sub:'日期·序数词',gr:'序数词:first(1st) second(2nd) third(3rd) fourth(4th)...月份前用in(in May),具体日期用on(on May 5th)。When is your birthday? It\'s on + 日期/ in + 月份。',ph:'字母组合er读/ə(r)/:teacher, sister, mother, under, dinner。字母组合th在序数词中读/θ/:fourth, fifth, sixth。',words:[
|
||||
{e:'when',c:'什么时候',em:'📅',s:'When is your birthday?'},{e:'birthday',c:'生日',em:'🎂',s:'Happy birthday!'},
|
||||
{e:'month',c:'月份',em:'📅',s:'12 months in a year.'},{e:'January',c:'一月',em:'🎊',s:'New Year is in January.'},
|
||||
{e:'February',c:'二月',em:'💕',s:'February is cold.'},{e:'March',c:'三月',em:'🌸',s:'Spring starts in March.'},
|
||||
{e:'April',c:'四月',em:'🌧️',s:'April showers bring May flowers.'},{e:'May',c:'五月',em:'🌺',s:'May is beautiful.'},
|
||||
{e:'June',c:'六月',em:'☀️',s:'School ends in June.'},{e:'July',c:'七月',em:'🏖️',s:'July is very hot.'},
|
||||
{e:'August',c:'八月',em:'🌞',s:'We go swimming in August.'},{e:'September',c:'九月',em:'🍂',s:'School starts in Sep.'},
|
||||
{e:'October',c:'十月',em:'🎃',s:'Halloween is in October.'},{e:'November',c:'十一月',em:'🍁',s:'November is cool.'},
|
||||
{e:'December',c:'十二月',em:'🎄',s:'Christmas is in December.'},{e:'first',c:'第一(1st)',em:'🥇',s:'first month is January.'},
|
||||
{e:'second',c:'第二(2nd)',em:'🥈',s:'second month is February.'},{e:'third',c:'第三(3rd)',em:'🥉',s:'third month is March.'},
|
||||
{e:'fourth',c:'第四(4th)',em:'4️⃣',s:'fourth month is April.'},{e:'fifth',c:'第五(5th)',em:'5️⃣',s:'fifth month is May.'},
|
||||
{e:'sixth',c:'第六(6th)',em:'6️⃣',s:'sixth month is June.'},{e:'seventh',c:'第七(7th)',em:'7️⃣',s:'seventh month is July.'},
|
||||
{e:'eighth',c:'第八(8th)',em:'8️⃣',s:'eighth month is August.'},{e:'party',c:'聚会',em:'🎉',s:'Let\'s have a party!'},
|
||||
{e:'happy',c:'快乐的',em:'😊',s:'I am happy today.'},{e:'year',c:'年',em:'📅',s:'A year has 365 days.'},
|
||||
{e:'day',c:'天;日',em:'☀️',s:'7 days in a week.'},{e:'date',c:'日期',em:'📅',s:'What\'s the date today?'},
|
||||
{e:'trip',c:'旅行',em:'🚗',s:'We have a school trip.'},{e:'test',c:'测试',em:'📝',s:'We have a test today.'},
|
||||
]},
|
||||
{id:'U9',em:'📚',title:'My Favorite Subject',sub:'科目·特殊疑问句',gr:'特殊疑问词:What(什么) Where(哪里) Who(谁) When(何时) Why(为什么) How(怎样)。Why用Because回答。星期前用on:on Monday。favorite = 最喜欢的。描述原因用because。',ph:'字母组合wh读/w/:what, where, when, why;读/h/:who, whose。字母组合ea在单词中读/i:/:easy, teacher, please。',words:[
|
||||
{e:'favorite',c:'最喜欢的',em:'⭐',s:'My favorite color is blue.'},{e:'subject',c:'科目',em:'📚',s:'My favorite subject is English.'},
|
||||
{e:'math',c:'数学',em:'🔢',s:'Math is interesting.'},{e:'English',c:'英语',em:'🇬🇧',s:'I speak English every day.'},
|
||||
{e:'Chinese',c:'语文;中文',em:'🇨🇳',s:'Chinese is my native language.'},{e:'science',c:'科学',em:'🔬',s:'Science is fun.'},
|
||||
{e:'history',c:'历史',em:'📜',s:'I like history class.'},{e:'music',c:'音乐',em:'🎵',s:'Music makes me happy.'},
|
||||
{e:'art',c:'美术',em:'🎨',s:'I draw in art class.'},{e:'P.E.',c:'体育',em:'🏃',s:'P.E. is my favorite.'},
|
||||
{e:'because',c:'因为',em:'🤷',s:'I like it because it\'s fun.'},{e:'why',c:'为什么',em:'❓',s:'Why do you like English?'},
|
||||
{e:'lesson',c:'课',em:'📖',s:'We have six lessons a day.'},{e:'finish',c:'完成',em:'✅',s:'I finish my homework.'},
|
||||
{e:'from',c:'从…',em:'🔄',s:'I am from China.'},{e:'about',c:'关于',em:'📋',s:'This book is about science.'},
|
||||
{e:'talk',c:'谈话',em:'💬',s:'Let\'s talk about sports.'},{e:'free',c:'空闲的',em:'🆓',s:'I\'m free on Sunday.'},
|
||||
{e:'cool',c:'酷的',em:'😎',s:'That\'s a cool T-shirt!'},{e:'Monday',c:'星期一',em:'1️⃣',s:'Monday is the first day.'},
|
||||
{e:'Tuesday',c:'星期二',em:'2️⃣',s:'Tuesday follows Monday.'},{e:'Wednesday',c:'星期三',em:'3️⃣',s:'Wednesday is mid-week.'},
|
||||
{e:'Thursday',c:'星期四',em:'4️⃣',s:'Thursday comes after Wednesday.'},{e:'Friday',c:'星期五',em:'5️⃣',s:'I love Friday!'},
|
||||
{e:'Saturday',c:'星期六',em:'6️⃣',s:'No school on Saturday!'},{e:'Sunday',c:'星期日',em:'7️⃣',s:'Sunday is for rest.'},
|
||||
{e:'teacher',c:'老师',em:'👩🏫',s:'My teacher is kind.'},{e:'useful',c:'有用的',em:'🛠️',s:'English is useful.'},
|
||||
{e:'exciting',c:'令人兴奋的',em:'🎉',s:'The game is exciting.'},
|
||||
]},
|
||||
];
|
||||
|
||||
// ===== EXAMS (真题风格:阅读+完形+语法+翻译+对话) =====
|
||||
const EXAMS = {
|
||||
S1:[
|
||||
{t:'mc',q:'Good ______, class! Good morning, ______!',o:['morning;teacher','afternoon;Miss Li','evening;Mom','night;Dad'],a:0,e:'早上好:Good morning。老师:teacher。完整对话用morning/teacher最贴切。'},
|
||||
{t:'mc',q:'— How are you? — I\'m fine, ______. And you? — I\'m ______, too.',o:['thanks;fine','thank;good','Ok;well','please;nice'],a:0,e:'"I\'m fine, thanks." 是标准问候回答链。And you? = 你呢?'},
|
||||
{t:'mc',q:'— ______ your name? — My name ______ Tom.',o:['What\'s;is','What;are','How\'s;am','Who\'s;are'],a:0,e:'What\'s = What is。问名字用"What\'s your name?"回答"My name is..."。语法点:be动词搭配。'},
|
||||
{t:'read',q:'Hello! I\'m Li Ming. I\'m a student. I\'m 12. I\'m in Class 3, Grade 7. My English teacher is Ms. Wang. She is nice.',o:[
|
||||
{q:'How old is Li Ming?',a:['11','12','13','14'],c:1},
|
||||
{q:'Li Ming\'s English teacher is ______.',a:['Mr. Wang','Ms. Wang','Mr. Li','Ms. Li'],c:1},
|
||||
{q:'Li Ming is in ______.',a:['Class 7, Grade 3','Class 3, Grade 7','Grade 7','Class 3'],c:1},
|
||||
]},
|
||||
{t:'mc',q:'I ______ a student. My parents ______ teachers.',o:['am;are','is;are','am;is','are;am'],a:0,e:'I用am,parents复数用are。语法点:be动词与主语一致。'},
|
||||
{t:'mc',q:'— Is she your sister? — No, she ______. ______ is my cousin.',o:['isn\'t;She','is;He','not;It','aren\'t;They'],a:0,e:'否定回答用isn\'t。She is my cousin."她是我的表姐。"语法点:否定句+人称代词。'},
|
||||
{t:'trans',q:'我是中国人。你叫什么名字?',a:'I am Chinese. What\'s your name?',e:'Chinese: 中国人。What\'s your name? 你叫什么名字?'},
|
||||
{t:'trans',q:'她是我的好朋友。我们都在七年级。',a:'She is my good friend. We are in Grade 7.',e:'good friend: 好朋友。Grade 7: 七年级。'},
|
||||
],
|
||||
S2:[
|
||||
{t:'mc',q:'— What\'s this ______ English? — It\'s ______ eraser.',o:['in;an','on;a','at;the','with;/'],a:0,e:'"in English"用英语说。eraser元音开头用an。语法点:介词in+冠词an。'},
|
||||
{t:'mc',q:'— ______ do you ______ "pen"? — P-E-N.',o:['How;spell','What;say','Where;write','Who;read'],a:0,e:'问"怎么拼写"用How。spell: 拼写。语法点:特殊疑问词how的用法。'},
|
||||
{t:'mc',q:'This is ______ orange. ______ orange is on the table.',o:['an;The','a;A','an;An','the;The'],a:0,e:'第一次提到用an,第二次用The特指。语法点:冠词a/an/the的用法。'},
|
||||
{t:'mc',q:'— Is this ______ jacket? — No, it\'s not ______. It\'s ______.',o:['your;mine;his','you;my;he','your;my;his','yours;mine;his'],a:0,e:'your jacket你的夹克。mine我的(东西)。his他的。语法点:形容词性+名词性物主代词。'},
|
||||
{t:'dialog',q:'A: Excuse me! What\'s this ___1___ English?\nB: It\'s ___2___ ruler.\nA: ___3___ do you spell it?\nB: R-U-L-E-R.',o:[['in','on','at','for'],['a','an','the','/'],['What','How','Where','Who']],a:[0,0,1],e:'in English(用英语说),a ruler(一把尺子),问拼写用How。'},
|
||||
{t:'trans',q:'请拼写"钥匙"。那是一张地图。',a:'Please spell "key". That is a map.',e:'please请。spell拼写。that那个(远处)。'},
|
||||
],
|
||||
S3:[
|
||||
{t:'mc',q:'— What ______ is the apple? — It\'s ______.',o:['color;red','name;apple','time;5:00','number;ten'],a:0,e:'What color is it? 问颜色。回答用颜色词。语法点:what color特殊疑问句。'},
|
||||
{t:'mc',q:'— Can you ______ a bird? — Yes, I ______. It\'s yellow.',o:['see;can','look;am','watch;do','find;is'],a:0,e:'see看见。can能。I can see a bird."我能看见一只鸟。"语法点:情态动词can。'},
|
||||
{t:'mc',q:'My jacket is black. ______ jacket is red. We like ______ jackets.',o:['Your;our','You;we','Yours;ours','You;us'],a:0,e:'Your jacket你的夹克。our jackets我们的夹克。语法点:形容词性物主代词。'},
|
||||
{t:'read',q:'I have a new schoolbag. It\'s blue and white. I like it very much. My pen, ruler and eraser are in it. My schoolbag is big. I can put many books in it.',o:[
|
||||
{q:'What color is the schoolbag?',a:['Blue','White','Blue and white','Black'],c:2},
|
||||
{q:'What is in the schoolbag?',a:['A pen, a ruler and an eraser','Only a book','A cat','Many pencils'],c:0},
|
||||
{q:'The schoolbag is ______.',a:['small','big','new','old'],c:1},
|
||||
]},
|
||||
{t:'mc',q:'— ______ is your coat? — It\'s brown.',o:['What color','What','Where','How'],a:0,e:'问颜色必须用What color。语法点:color必须出现。'},
|
||||
{t:'trans',q:'我的书包是蓝色的。我能看见一只红色的鸟。',a:'My schoolbag is blue. I can see a red bird.',e:'注意中文没有"的"→英文用my。can see: 能看见。'},
|
||||
],
|
||||
U1:[
|
||||
{t:'mc',q:'— Nice ______ meet you! — Nice to meet you, ______.',o:['to;too','too;to','to;to','too;too'],a:0,e:'to是不定式符号,too表示"也"。语法点:不定式与副词的区分。'},
|
||||
{t:'mc',q:'— What\'s ______ name? — ______ name is Gina. ______ is my friend.',o:['her;Her;She','she;She;Her','her;She;Her','she;Her;She'],a:0,e:'her name她的名字(形容词性物主代词),She is...她是...(主格)。语法点:代词主格vs物主代词。'},
|
||||
{t:'mc',q:'— Is he your brother? — No, he ______. He\'s my ______.',o:['isn\'t;cousin','is;friend','not;uncle','aren\'t;classmate'],a:0,e:'否定回答用isn\'t。cousin堂/表兄弟。语法点:be动词否定+家庭词汇。'},
|
||||
{t:'mc',q:'— ______ are you? — I\'m 12 years old.',o:['How old','How','What','Where'],a:0,e:'How old are you? 你多大了?语法点:how old问年龄。'},
|
||||
{t:'read',q:'Hi! I\'m Mike. I\'m 13. I\'m from England. My telephone number is 234-5678. I have a good friend. Her name is Alice. She is 12. She is from China. We are in the same school. We like English very much.',o:[
|
||||
{q:'How old is Mike?',a:['12','13','14','11'],c:1},
|
||||
{q:'Where is Alice from?',a:['England','China','America','Canada'],c:1},
|
||||
{q:'Mike\'s telephone number is ______.',a:['234-5678','324-5678','234-8765','543-5678'],c:0},
|
||||
]},
|
||||
{t:'mc',q:'— What class are you in? — I\'m in ______.',o:['Class 3, Grade 7','class 3, grade 7','Grade 7, Class 3','grade 7, class 3'],a:0,e:'英语中先说班级后说年级,且首字母大写:Class 3, Grade 7。'},
|
||||
{t:'mc',q:'— Are you Alice? — Yes, I ______. — ______ is your phone number?',o:['am;What','are;How','is;What\'s','am;How'],a:0,e:'回答用I am。问电话号码用"What is your phone number?"语法点:be动词+特殊疑问词。'},
|
||||
{t:'trans',q:'她的电话号码是398-2468。我和她在同一个学校。',a:'Her telephone number is 398-2468. She and I are in the same school.',e:'telephone number电话号码。in the same school在同一所学校。'},
|
||||
{t:'trans',q:'他叫什么名字?他12岁。他是英国人。',a:'What\'s his name? He is 12. He is from England.',e:'What\'s his name? 他叫什么?from England来自英国。'},
|
||||
],
|
||||
U2:[
|
||||
{t:'mc',q:'This is my mother. ______ is a doctor. My father is a teacher. ______ are busy.',o:['She;They','He;We','She;You','It;They'],a:0,e:'she指mother。they指父母两人。语法点:人称代词主格。'},
|
||||
{t:'mc',q:'— ______ is the girl in the photo? — She\'s my cousin, Lily.',o:['Who','What','Where','How'],a:0,e:'Who问人。语法点:who特殊疑问句。'},
|
||||
{t:'mc',q:'— Are ______ your parents? — Yes, ______ are. And ______ is my brother.',o:['those;they;this','these;those;he','they;these;it','those;these;he'],a:0,e:'those那些(远处)。回答用they代替。this这个(近处)。语法点:指示代词。'},
|
||||
{t:'read',q:'This is a photo of my family. These are my grandparents. They are 70. This is my father. He is a doctor. This is my mother. She is a nurse. The boy next to my mother is my brother, Tom. The girl in front of my father is me, Lily. I love my family.',o:[
|
||||
{q:'How many people are in Lily\'s family?',a:['4','5','6','7'],c:2},
|
||||
{q:'Lily\'s father is a ______.',a:['teacher','doctor','nurse','worker'],c:1},
|
||||
{q:'Tom is Lily\'s ______.',o:['brother','cousin','friend','classmate'],c:0},
|
||||
]},
|
||||
{t:'mc',q:'— Is this your aunt? — No, she\'s my mother\'s sister. She\'s my ______.',o:['aunt','uncle','cousin','grandma'],a:0,e:'mother\'s sister = 姨妈,也是aunt。语法点:英语中姨妈和姑姑都用aunt。'},
|
||||
{t:'mc',q:'My grandfather is old. ______ likes to read ______.',o:['He;newspapers','She;books','He;TV','They;music'],a:0,e:'grandfather男性用He。read newspapers看报纸。语法点:代词+动宾搭配。'},
|
||||
{t:'trans',q:'这张照片里有我的父母和我的妹妹。',a:'In this photo are my parents and my sister.',e:'注意英语中先说in the photo再说人。'},
|
||||
{t:'dialog',q:'A: Who\'s that man?\nB: ___1___ is my uncle.\nA: ___2___ is he?\nB: He\'s a doctor. He works in a big ___3___.',o:[['He','She','It','That'],['Who','What','Where','How'],['school','hospital','store','bank']],a:[0,1,1],e:'那个男人用He。What is he? 他是做什么的?doctor医生→hospital医院。'},
|
||||
],
|
||||
U3:[
|
||||
{t:'mc',q:'— Is this ______ pencil? — No, it\'s not ______. It\'s ______.',o:['your;mine;hers','you;my;her','your;my;her','yours;mine;hers'],a:0,e:'your pencil你的铅笔。mine我的。hers她的。语法点:名词性物主代词。'},
|
||||
{t:'mc',q:'— Thank you ______ your help. — ______ welcome.',o:['for;You\'re','to;You','with;You are','of;You\'re'],a:0,e:'thank sb. for sth.因某事感谢某人。You\'re welcome.不客气。'},
|
||||
{t:'mc',q:'Please call me ______ 555-1234. Or e-mail me ______ tom@123.com.',o:['at;at','at;on','on;at','to;to'],a:0,e:'call sb. at + 号码。e-mail sb. at + 邮箱。语法点:介词at表示联系方式。'},
|
||||
{t:'mc',q:'— Is this your schoolbag? — Yes, it\'s ______. I ______ it in the library.',o:['mine;found','my;lost','mine;had','me;put'],a:0,e:'mine = my schoolbag。found找到。语法点:物主代词+动词过去/现在。'},
|
||||
{t:'read',q:'Hello! I\'m a student. I lost my schoolbag in the school library. It\'s blue and white. My books, my pencil box and my ID card are in it. My name is Jack. Please call me at 456-7890. Thank you!',o:[
|
||||
{q:'Where did the student lose his schoolbag?',a:['In the classroom','In the library','In the dining hall','On the playground'],c:1},
|
||||
{q:'What is NOT in the schoolbag?',a:['Books','A pencil box','An ID card','A cellphone'],c:3},
|
||||
{q:'What\'s the student\'s name?',a:['Tom','Jack','Mike','Alice'],c:1},
|
||||
]},
|
||||
{t:'mc',q:'— Where is my dictionary? I can\'t ______ it. — Look! It\'s on the desk.',o:['find','look','see','watch'],a:0,e:'find找到(结果)。look看(动作)。语法点:find vs look的区别。'},
|
||||
{t:'trans',q:'你能帮我吗?我找不到我的铅笔了。',a:'Can you help me? I can\'t find my pencil.',e:'Can you help me? 你能帮我吗?can\'t find找不到。'},
|
||||
{t:'trans',q:'请拨打110找警察。',a:'Please call 110 to find the police.',e:'call + 号码 + to find... 打电话找……。'},
|
||||
],
|
||||
U4:[
|
||||
{t:'mc',q:'— Where ______ my keys? — ______ on the sofa.',o:['are;They\'re','is;It\'s','are;It\'s','is;They\'re'],a:0,e:'keys复数用are。回答用they\'re。语法点:be动词复数+代词复数。'},
|
||||
{t:'mc',q:'The cat is ______ the table. (在…下面) The shoes are ______ the bed. (在…旁边)',o:['under;next to','on;behind','in;under','behind;on'],a:0,e:'under在…下面。next to在…旁边。语法点:方位介词。'},
|
||||
{t:'mc',q:'— Is your schoolbag on the desk? — No, it\'s ______ the desk, not ______ it.',o:['in;on','behind;in','under;on','on;under'],a:2,e:'under在下面,on在上面。语法点:介词对比例句。'},
|
||||
{t:'mc',q:'— Where is the baseball? — I ______ it\'s behind the door. Go and ______ it.',o:['think;find','know;look','see;watch','find;see'],a:0,e:'I think我认为。go and find去找。语法点:think表观点。'},
|
||||
{t:'read',q:'This is my room. It\'s small but tidy. My bed is near the window. A desk is next to the bed. On the desk, there is a lamp and some books. Under the desk, there is a basketball. My schoolbag is on the chair. I like my room very much.',o:[
|
||||
{q:'Where is the bed?',a:['Next to the desk','Near the window','Under the desk','Behind the door'],c:1},
|
||||
{q:'What is under the desk?',a:['A lamp','Some books','A basketball','A schoolbag'],c:2},
|
||||
{q:'The room is ______.',o:['big and tidy','small but tidy','big but messy','small and messy'],c:1},
|
||||
]},
|
||||
{t:'trans',q:'我的书在书桌里吗?不,它们在书架上。',a:'Are my books in the desk? No, they\'re in the bookcase.',e:'bookcase书架。注意books复数用are。'},
|
||||
{t:'dialog',q:'A: Where ___1___ my pencils?\nB: ___2___ on the desk.\nA: No, they ___3___ there. I can\'t see them.\nB: Oh, look! They\'re in your pencil box.',o:[['are','is','am','be'],['It\'s','They\'re','There','These'],['aren\'t','isn\'t','are','don\'t']],a:[0,1,0],e:'pencils复数用are。They\'re=They are。aren\'t=are not。'},
|
||||
],
|
||||
U5:[
|
||||
{t:'mc',q:'— ______ you have a soccer ball? — Yes, I ______. But my brother ______ one.',o:['Do;do;doesn\'t have','Does;does;don\'t have','Do;have;hasn\'t','Are;am;isn\'t'],a:0,e:'Do you have...?I do。brother三单用doesn\'t have。语法点:一般现在时疑问/否定。'},
|
||||
{t:'mc',q:'My father ______ a basketball. He ______ it every day.',o:['has;plays','have;play','has;play','have;plays'],a:0,e:'father三单用has。He三单用plays。语法点:第三人称单数。'},
|
||||
{t:'mc',q:'Let\'s ______ soccer after school. That ______ fun.',o:['play;sounds','to play;sound','playing;sounds','plays;sound'],a:0,e:'Let\'s + 动词原形。That sounds + 形容词。语法点:祈使句+系动词。'},
|
||||
{t:'mc',q:'— Does your sister ______ a volleyball? — No, she ______. She ______ a basketball.',o:['have;doesn\'t;has','has;doesn\'t;have','have;don\'t;has','has;isn\'t;have'],a:0,e:'Does后动词用原形have。否定回答doesn\'t。肯定用has。语法点:does/do+动词原形。'},
|
||||
{t:'mc',q:'— Let\'s watch TV. — That ______ boring. Let\'s play ______.',o:['sounds;games','sound;game','is;sport','are;basketball'],a:0,e:'That sounds boring. 那听起来很无聊。语法点:系动词sound+形容词。'},
|
||||
{t:'read',q:'Tom and I are good friends. We like sports very much. I have a basketball and two volleyballs. Tom has a soccer ball and three tennis balls. We don\'t have a baseball. After school, we often play basketball together. It\'s relaxing. Sometimes we watch sports games on TV. We love sports!',o:[
|
||||
{q:'How many volleyballs does the writer have?',a:['One','Two','Three','Four'],c:1},
|
||||
{q:'What sport do Tom and the writer NOT have?',a:['Basketball','Volleyball','Soccer','Baseball'],c:3},
|
||||
{q:'They play basketball ______.',o:['every morning','after school','on weekends','in the evening'],c:1},
|
||||
]},
|
||||
{t:'trans',q:'我哥哥有5个篮球。他每天都打球。',a:'My brother has five basketballs. He plays ball every day.',e:'brother三单用has。every day每天。'},
|
||||
{t:'trans',q:'我们不喜欢看电视。它很无聊。',a:'We don\'t like watching TV. It\'s boring.',e:'don\'t like不喜欢。watching TV看电视。boring无聊的。'},
|
||||
],
|
||||
U6:[
|
||||
{t:'mc',q:'— Do you like ______? — Yes, I do. They\'re sweet.',o:['strawberries','strawberrys','strawberry','strawbery'],a:0,e:'strawberry→strawberries(辅音y→ies)。语法点:名词复数变化规则。'},
|
||||
{t:'mc',q:'— Does your sister ______ milk? — No, she ______. She ______ water.',o:['like;doesn\'t;likes','likes;doesn\'t;like','like;don\'t;likes','likes;isn\'t;drinks'],a:0,e:'Does后动词原形like。she三单否定用doesn\'t。肯定用likes。语法点:一般现在时三单。'},
|
||||
{t:'mc',q:'I have ______ egg and ______ milk for breakfast every morning.',o:['an;some','a;some','an;a','a;an'],a:0,e:'egg元音开头用an。milk不可数用some。语法点:可数/不可数名词。'},
|
||||
{t:'mc',q:'— ______ your brother like hamburgers? — No, he thinks they\'re not ______.',o:['Does;healthy','Do;health','Is;well','Are;good'],a:0,e:'Does + 三单主语 + 动词原形。healthy健康的。语法点:一般疑问句+形容词。'},
|
||||
{t:'read',q:'My name is Lucy. I have good eating habits. For breakfast, I always eat an egg and some bread. I drink a cup of milk, too. For lunch, I like rice, chicken and carrots. For dinner, I like salad and fruit. I don\'t eat hamburgers or ice cream because they are not healthy. I drink lots of water every day. Good eating habits keep me healthy!',o:[
|
||||
{q:'What does Lucy have for breakfast?',a:['Rice and chicken','An egg, bread and milk','Salad and fruit','Hamburgers'],c:1},
|
||||
{q:'Why doesn\'t Lucy eat hamburgers?',a:['She doesn\'t like them','They\'re not healthy','They\'re expensive','She has no money'],c:1},
|
||||
{q:'Lucy has good ______.',o:['eating habits','study habits','sports habits','sleeping habits'],c:0},
|
||||
]},
|
||||
{t:'trans',q:'我妈妈喜欢苹果和香蕉,但她不喜欢冰淇淋。',a:'My mother likes apples and bananas, but she doesn\'t like ice cream.',e:'三单likes。but但是。doesn\'t like不喜欢。'},
|
||||
{t:'trans',q:'胡萝卜对我们的健康有好处。',a:'Carrots are good for our health.',e:'be good for对…有好处。health健康。'},
|
||||
],
|
||||
U7:[
|
||||
{t:'mc',q:'— How much ______ these socks? — ______ five dollars.',o:['are;They\'re','is;It\'s','are;It\'s','is;They\'re'],a:0,e:'socks复数用are。回答用They\'re。语法点:be动词复数。'},
|
||||
{t:'mc',q:'— Can I help you? — Yes, please. I ______ a sweater for my son.',o:['want','need','like','take'],a:0,e:'I want...我想要。购物常用语。语法点:want+名词。'},
|
||||
{t:'mc',q:'The blue shirt is too ______ (贵) for me. I\'ll take the ______ (便宜) one.',o:['expensive;cheap','cheap;expensive','big;small','long;short'],a:0,e:'expensive贵的。cheap便宜的。语法点:形容词比较。'},
|
||||
{t:'mc',q:'— Do you have this T-shirt ______ red? — Yes. Here you are. It\'s only $18. — OK, I\'ll ______ it.',o:['in;take','on;buy','with;get','for;have'],a:0,e:'in red红色的。I\'ll take it. 我买了。购物用语。'},
|
||||
{t:'read',q:'Come and buy your clothes at Mr. Cool\'s Store! We have a big sale this weekend. Do you need T-shirts? We have T-shirts in all colors for only $15 each. For boys, we have blue shorts for only $12. For girls, we have red skirts for $18. All the clothes are very cheap. Come to our store now!',o:[
|
||||
{q:'How much are the T-shirts?',a:['$12','$15','$18','$20'],c:1},
|
||||
{q:'What is on sale for boys?',a:['T-shirts','Shorts','Skirts','Sweaters'],c:1},
|
||||
{q:'The word "sale" in this passage means ______.',o:['销售','促销','拍卖','甩卖'],c:1},
|
||||
]},
|
||||
{t:'trans',q:'这条蓝色短裤多少钱?只要10美元。我买了。',a:'How much are these blue shorts? They\'re only $10. I\'ll take them.',e:'shorts短裤(复数)。How much问价格。I\'ll take them.我买了。'},
|
||||
{t:'trans',q:'我们店里所有衣服都在大减价!',a:'All the clothes are on sale in our store!',e:'on sale促销。all所有的。clothes衣服(复数)。'},
|
||||
],
|
||||
U8:[
|
||||
{t:'mc',q:'— When is your birthday? — It\'s ______ May 12th. It\'s ______ spring.',o:['on;in','in;on','at;in','on;on'],a:0,e:'具体日期用on。季节用in。语法点:介词on/in区别。'},
|
||||
{t:'mc',q:'September is the ______ month of the year. December is the ______.',o:['ninth;twelfth','nineth;twelveth','nine;twelve','ninth;twelveth'],a:0,e:'ninth第九(nine去e)。twelfth第十二(twelve→f+th)。语法点:序数词。'},
|
||||
{t:'mc',q:'— When is ______ birthday? — My birthday is in ______, the 6th month of the year.',o:['your;June','you;July','my;May','your;June'],a:0,e:'your birthday你的生日。June六月是第6个月。语法点:6th month=June。'},
|
||||
{t:'read',q:'Dear Linda,\nThanks for your birthday invitation. I\'m very happy to come to your birthday party. When is it? Is it on June 15th? What time does it start? I have a small gift for you. It\'s a nice book. See you at the party!\nYours, Mary',o:[
|
||||
{q:'Why does Mary write this letter?',a:['To say happy birthday','To reply an invitation','To ask for help','To say goodbye'],c:1},
|
||||
{q:'What is Mary\'s gift for Linda?',a:['A cake','A book','A CD','A doll'],c:1},
|
||||
{q:'The party is probably on ______.',o:['June 15th','June 5th','July 15th','May 15th'],c:0},
|
||||
]},
|
||||
{t:'mc',q:'— ______ is the school trip? — It\'s ______ October.',o:['When;in','What;on','Where;at','How;for'],a:0,e:'When问时间。月份前用in。语法点:when+介词in。'},
|
||||
{t:'trans',q:'我的生日是3月12日。一年中的第三个月是三月。',a:'My birthday is on March 12th. The third month of the year is March.',e:'on+具体日期。third第三。March三月。'},
|
||||
],
|
||||
U9:[
|
||||
{t:'mc',q:'— ______ do you like English? — ______ it\'s interesting and very ______.',o:['Why;Because;useful','What;So;good','When;For;fun','How;And;easy'],a:0,e:'Why问原因,Because回答。useful有用的。语法点:why/because因果句。'},
|
||||
{t:'mc',q:'My favorite ______ is science. I think it\'s ______ but difficult.',o:['subject;interesting','sport;boring','food;delicious','color;beautiful'],a:0,e:'subject科目。science科学。注意but前后对比。语法点:but表示转折。'},
|
||||
{t:'mc',q:'I have P.E. ______ Monday afternoon. ______ it\'s my favorite, I\'m very happy.',o:['on;Because','in;So','at;But','on;And'],a:0,e:'具体星期下午用on。Because因为。语法点:介词on+because从句。'},
|
||||
{t:'read',q:'Dear Jenny,\nHow are you? I\'m very busy on Friday. At 8:00 I have math. I don\'t like math because it\'s difficult. At 9:00 I have science. It\'s interesting. Then at 10:00 I have history. At 11:00 I have P.E. That\'s my favorite subject because it\'s fun and relaxing. I have lunch at 12:00. After lunch, I have Chinese at 1:00. Chinese is my favorite subject because our teacher is very kind. I finish school at 4:00. What\'s your favorite subject?\nYours,\nTom',o:[
|
||||
{q:'How many subjects does Tom have on Friday?',a:['5','6','7','8'],c:1},
|
||||
{q:'Why doesn\'t Tom like math?',a:['It\'s boring','It\'s difficult','The teacher is strict','It\'s too easy'],c:1},
|
||||
{q:'Which subject begins at 11:00?',o:['History','Science','P.E.','Chinese'],c:2},
|
||||
]},
|
||||
{t:'mc',q:'— ______ is your music teacher? — Mr. Green. — ______ do you like him? — ______ he\'s fun.',o:['Who;Why;Because','What;How;For','Where;When;So','Who;What;And'],a:0,e:'Who问人。Why问原因。Because回答。语法点:who/why/because疑问链。'},
|
||||
{t:'trans',q:'我最喜欢的科目是历史,因为它很有趣。',a:'My favorite subject is history because it\'s very interesting.',e:'favorite subject最喜欢的科目。because因为。'},
|
||||
{t:'trans',q:'我们的语文老师非常和蔼。我们都喜欢她。',a:'Our Chinese teacher is very kind. We all like her.',e:'kind和蔼的。We all like her.我们都喜欢她。'},
|
||||
],
|
||||
// ===== 几何可视化引擎(绘图工具)=====
|
||||
const Geo = {
|
||||
svg(w,h,inner){return `<svg viewBox="0 0 ${w} ${h}" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">${inner}</svg>`;},
|
||||
label(x,y,t,o={}){const{fs=10,fill='rgba(255,255,255,0.7)',anchor='middle',fw='400'}=o;return `<text x="${x}" y="${y}" font-size="${fs}" fill="${fill}" text-anchor="${anchor}" font-weight="${fw}" font-family="'Inter',monospace">${t}</text>`;},
|
||||
dot(x,y,o={}){const{r=3,fill='#ffd700'}=o;return `<circle cx="${x}" cy="${y}" r="${r}" fill="${fill}"/>`;},
|
||||
line(x1,y1,x2,y2,o={}){const{sw=2,clr='rgba(255,255,255,0.12)',dash=''}=o;return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${clr}" stroke-width="${sw}" stroke-dasharray="${dash}"/>`;},
|
||||
arrow(x1,y1,x2,y2,o={}){const{sw=2,clr='rgba(255,255,255,0.3)',sz=6}=o;const dx=x2-x1,dy=y2-y1,len=Math.hypot(dx,dy);if(!len)return '';const ux=dx/len,uy=dy/len;return this.line(x1,y1,x2,y2,o)+`<polygon points="${x2},${y2} ${x2-sz*ux+sz*uy/2},${y2-sz*uy-sz*ux/2} ${x2-sz*ux-sz*uy/2},${y2-sz*uy+sz*ux/2}" fill="${clr}"/>`;},
|
||||
seg(x,y,w,o={}){const{la='A',lb='B',col='#ffd700'}=o;let r=this.dot(x,y,{fill:col})+this.dot(x+w,y,{fill:col})+this.line(x,y,x+w,y,{clr:'rgba(255,255,255,0.4)',sw:2});return r+this.label(x,y+18,la,{fs:9,fill:col})+this.label(x+w,y+18,lb,{fs:9,fill:col});},
|
||||
midSeg(x,y,w,o={}){const{la='A',lb='B',ml='M',ac='',cb='',total=''}=o;const mx=x+w/2;return this.seg(x,y,w,{la,lb})+this.dot(mx,y,{fill:'#e10600'})+this.label(mx,y-8,ml,{fs:8,fill:'#e10600'})+(ac?this.label(x+w/4,y-23,ac,{fs:8,fill:'rgba(255,255,255,0.2)'}):'')+(cb?this.label(x+w*3/4,y-23,cb,{fs:8,fill:'rgba(255,255,255,0.2)'}):'')+(total?this.label(x+w/2,y+30,total,{fs:9,fill:'#ffd700'}):'');},
|
||||
aArc(cx,cy,r,a,o={}){const{clr='#ffd700',sw=2,deg='',tag='',fs=11}=o;const ar=a*Math.PI/180;const pts=[];for(let i=0;i<=40;i++){const t=i/40*ar;pts.push(`${cx+r*Math.cos(Math.PI/2-t)},${cy-r*Math.sin(Math.PI/2-t)}`)}let s=`<polyline points="${pts.join(' ')}" fill="none" stroke="${clr}" stroke-width="${sw}"/>`;if(deg)s+=this.label(cx+r*Math.cos(Math.PI/2-ar/2)*1.3,cy-r*Math.sin(Math.PI/2-ar/2)*1.3,deg+'°',{fs,fill:clr,fw:'900'});if(tag)s+=this.label(cx+r*Math.cos(Math.PI/2-ar/2)*0.7,cy-r*Math.sin(Math.PI/2-ar/2)*0.7,tag,{fs:9,fill:clr});return s;},
|
||||
rAngle(cx,cy,sz,up){return `<polyline points="${cx+sz},${cy} ${cx+sz},${cy-sz*(up||1)} ${cx},${cy-sz*(up||1)}" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>`;},
|
||||
clock(cx,cy,r,h,m,o={}){const{hl=''}=o;const hAng=(h%12)*30+m*0.5,mAng=m*6;let s=`<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>`;for(let i=0;i<12;i++){const a=i*30*Math.PI/180;const x1=cx+(r-6)*Math.sin(a),y1=cy-(r-6)*Math.cos(a);const x2=cx+r*Math.sin(a),y2=cy-r*Math.cos(a);s+=this.line(x1,y1,x2,y2,{clr:`rgba(255,255,255,${i%3===0?0.12:0.04})`,sw:i%3===0?1.5:1});if(i%3===0){const lx=cx+(r+14)*Math.sin(a),ly=cy-(r+14)*Math.cos(a);s+=this.label(lx,ly+3,i||12,{fs:8,fill:'rgba(255,255,255,0.2)'})}}const hh=r*0.45,hm=r*0.6;const ha=(hAng-90)*Math.PI/180,ma=(mAng-90)*Math.PI/180;s+=this.line(cx,cy,cx+hh*Math.cos(ha),cy+hh*Math.sin(ha),{clr:'#ffd700',sw:3});s+=this.line(cx,cy,cx+hm*Math.cos(ma),cy+hm*Math.sin(ma),{clr:'rgba(255,255,255,0.2)',sw:1.5});s+=this.dot(cx,cy,{r:4,fill:'#e10600'});if(hl)s+=this.label(cx,cy-r-16,hl,{fs:9,fill:'#ffd700',fw:'900'});return s;},
|
||||
compass(cx,cy,r,deg,o={}){const{label=''}=o;let s=`<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>`;const dirs=[{d:'北',a:0},{d:'东',a:90},{d:'南',a:180},{d:'西',a:270}];dirs.forEach(({d,a})=>{const t=a*Math.PI/180;const x=cx+(r+12)*Math.sin(t),y=cy-(r+12)*Math.cos(t);s+=this.label(x,y+3,d,{fs:9,fill:a===0?'#e10600':'rgba(255,255,255,0.2)',fw:a===0?'900':'400'})});const a=deg*Math.PI/180;const ex=cx+r*Math.sin(a),ey=cy-r*Math.cos(a);s+=this.arrow(cx,cy,ex,ey,{clr:'#ffd700',sw:2.5});s+=this.dot(cx,cy,{r:3,fill:'#ffd700'});if(label)s+=this.label(ex+10,ey-5,label,{fs:9,fill:'#ffd700'});return s;},
|
||||
numLine(cx,cy,w,vals,o={}){const{labels={}}=o;const mn=Math.min(...vals),mx=Math.max(...vals),rg=mx-mn||1;let s=this.arrow(cx,cy,cx+w,cy,{clr:'rgba(255,255,255,0.2)',sw:1.5});vals.forEach(v=>{const x=cx+(v-mn)/rg*w;if(v===0)s+=this.dot(x,cy,{r:3,fill:'#ffd700'})+this.label(x,cy+12,'0',{fs:9,fill:'#ffd700',fw:'900'});else s+=this.dot(x,cy,{r:2,fill:'rgba(255,255,255,0.15)'})+this.label(x,cy+12,v,{fs:8,fill:'rgba(255,255,255,0.2)'});if(labels[v])s+=this.label(x,cy-8,labels[v],{fs:7,fill:'#e10600'})});return s;}
|
||||
};
|
||||
|
||||
// ===== GRAMMAR =====
|
||||
const GRAMMAR = [
|
||||
{icon:'✅',title:'be动词',detail:'I am / You are / He-She-It is / We-You-They are。否定:I\'m not / He isn\'t / They aren\'t。疑问:Are you? Is he?',examples:['I am a student.','She is a teacher.','They are happy.','Are you Tom? Yes, I am.']},
|
||||
{icon:'❓',title:'一般疑问句',detail:'be动词提到主语前。回答用Yes/No。实义动词用Do/Does开头。',examples:['Are you a student? → Yes, I am.','Does he like milk? → No, he doesn\'t.','Do they have a ball? → Yes, they do.']},
|
||||
{icon:'🔤',title:'名词复数',detail:'一般+s。s/x/sh/ch+es。辅音+y→ies。f/fe→ves。不规则变化需记忆。',examples:['book→books, bus→buses, family→families, knife→knives','child→children, man→men, woman→women, foot→feet, tooth→teeth']},
|
||||
{icon:'👤',title:'人称代词与物主代词',detail:'主格(I/you/he/she/it/we/they)做主语。形容词性(my/your/his/her/its/our/their)+名词。名词性(mine/yours/his/hers/its/ours/theirs)单独用。',examples:['This is my book. = This book is mine.','She is a teacher. Her name is Mary.']},
|
||||
{icon:'📍',title:'方位介词',detail:'in(在…里) on(在…上) under(在…下) behind(在…后) next to(在…旁) between(在…之间) near(在…附近) in front of(在…前面)',examples:['The cat is under the table.','The school is next to the bank.','Tom sits between Mary and Jack.']},
|
||||
{icon:'🔄',title:'一般现在时',detail:'表示经常发生的动作。he/she/it作主语时动词+s/es。否定用don\'t/doesn\'t+动原。疑问用Do/Does+动原。',examples:['I like apples.','He likes bananas.','She doesn\'t like milk.','Does he play soccer? Yes, he does.']},
|
||||
{icon:'🎯',title:'特殊疑问句(5W1H)',detail:'What(什么) Where(哪里) Who(谁) When(何时) Why(为什么) How(怎样/多…)。Why用Because回答。How many+可数复数问数量。',examples:['Where is my bag? → On the desk.','Why do you like English? → Because it\'s fun.','How many books do you have? → Five.']},
|
||||
{icon:'📅',title:'介词in/on/at(时间)',detail:'in+年/月/季节/上午/下午/晚上。on+具体日期/星期/节日。at+时刻/noon/night。',examples:['in May, in 2024, in summer','on Monday, on May 5th','at 7:00, at night, at noon']},
|
||||
{icon:'🧮',title:'序数词',detail:'一二三特殊(first/second/third)。-th从四加(fourth)。ve→f+th(five→fifth)。y→ie+th(twenty→twentieth)。',examples:['first(1st), second(2nd), third(3rd), fourth(4th), fifth(5th)','December is the twelfth month.']},
|
||||
{icon:'💪',title:'情态动词',detail:'can(能)/must(必须)/may(可以)/should(应该)/need(需要)。后跟动词原形。无人称变化。',examples:['I can swim.','You must finish homework.','May I come in?','We should help each other.']},
|
||||
{icon:'📝',title:'祈使句',detail:'动词原形开头表命令/请求。否定:Don\'t + 动原。Let\'s + 动原表建议。Please可加任何位置。',examples:['Sit down, please.','Don\'t run in the classroom.','Let\'s go!','Please open the door.']},
|
||||
{icon:'❓',title:'冠词 a/an/the',detail:'a辅音音素前(a useful book)。an元音音素前(an hour/an apple)。the特指/独一无二/序数词/最高级前。',examples:['a book, a university','an apple, an honest boy','the sun, the first day, the biggest']},
|
||||
{icon:'🏃',title:'现在进行时 (be doing)',detail:'am/is/are + 现在分词(doing),表示正在进行的动作。now/listen/look/at this moment是标志词。',examples:['I am reading a book now.','Look! The cat is sleeping.','They are playing soccer at the moment.']},
|
||||
{icon:'⏪',title:'一般过去时',detail:'表示过去发生的动作。be动词变was/were。实义动词用过去式。否定用didn\'t+动原。yesterday/last.../ago是标志词。',examples:['I was a student in 2020.','He went to Beijing yesterday.','She didn\'t watch TV last night.','Did you have breakfast?']},
|
||||
{icon:'🔮',title:'一般将来时 (will/be going to)',detail:'will+动原(临时决定/预测)。be going to+动原(计划/打算)。tomorrow/next.../soon是标志词。',examples:['I will go to Beijing next week.','She is going to buy a new bike.','It will rain tomorrow.','Are you going to study abroad?']},
|
||||
{icon:'✅',title:'现在完成时 (have/has done)',detail:'have/has + 过去分词,表示过去发生对现在有影响/经历。ever/never/already/yet是标志词。',examples:['I have finished my homework.','She has never been to Shanghai.','Have you ever seen the movie?','They have already left.']},
|
||||
{icon:'📊',title:'形容词比较级',detail:'er+than(更…)。more+多音节adj+than。good→better, bad→worse, many→more。标志词:than, much, a little, even。',examples:['Tom is taller than Mike.','This book is more interesting than that one.','I am a little shorter than you.']},
|
||||
{icon:'🏆',title:'形容词最高级',detail:'the+est/the+most+多音节。in/of范围。good→best, bad→worst, many→most。最高级前必加the。',examples:['She is the tallest girl in our class.','This is the most beautiful park I\'ve seen.','Which is the biggest, the sun, the moon or the earth?']},
|
||||
{icon:'📍',title:'There be 句型',detail:'There is(单数/不可数) + 名词。There are(复数) + 名词。表示"某地有某物"。注意"就近原则"(be随最近的名词变化)。',examples:['There is a book on the desk.','There are two cats under the table.','There is a pen and two books.','There aren\'t any students in the classroom.']},
|
||||
{icon:'📋',title:'不定代词 (some/any/no)',detail:'some用于肯定句。any用于否定/疑问句。no=not any。something/somebody用于肯定,anything/anybody用于否定疑问。',examples:['I have some apples.','Do you have any milk?','There is nobody in the room.','Someone is knocking at the door.']},
|
||||
{icon:'🔄',title:'频率副词',detail:'always(100%)→usually(80%)→often(60%)→sometimes(40%)→seldom(20%)→never(0%)。放在be动词后/实义动词前。',examples:['I always get up at 6.','She is never late for school.','He sometimes plays basketball.','We often go to the library.']},
|
||||
{icon:'👤',title:'反身代词',detail:'myself/yourself/himself/herself/itself/ourselves/yourselves/themselves。表示"自己"。常用于enjoy/teach/hurt/by等词后。',examples:['I taught myself English.','She hurt herself.','He did it by himself.','Enjoy yourselves!']},
|
||||
{icon:'🔗',title:'动词不定式 (to do)',detail:'动词不定式可做主语/宾语/宾语补足语/目的状语。常见搭配:want to do, like to do, ask sb. to do, tell sb. to do。',examples:['I want to buy a book.','She asked me to help her.','It\'s important to learn English.','He went to Beijing to see his uncle.']},
|
||||
{icon:'🎯',title:'动名词 (doing)',detail:'动名词做主语(谓语用单数)/宾语。常见搭配:enjoy doing, finish doing, keep doing, be good at doing, be interested in doing。',examples:['I enjoy reading books.','He finished doing his homework.','Swimming is good for health.','She is good at singing.']},
|
||||
{icon:'🔊',title:'被动语态 (be done)',detail:'be + 过去分词,表示"被…"。时态体现在be上。一般现在:am/is/are + done。一般过去:was/were + done。情态:can/must + be + done。',examples:['English is spoken all over the world.','The window was broken by Tom.','Homework must be finished on time.']},
|
||||
{icon:'🔀',title:'连词 (and/but/or/so/because)',detail:'and(并列/顺承) but(转折) or(选择/否则) so(结果) because(原因)。because不和so同时用。',examples:['I like English and math.','She is tired but happy.','Hurry up, or you\'ll be late.','He was ill, so he didn\'t go to school.']},
|
||||
{icon:'💬',title:'宾语从句',detail:'陈述句用that引导。一般疑问句用if/whether。特殊疑问句用原疑问词。从句用陈述语序(主+谓)。主句过去时从句也过去时。',examples:['I think (that) he is right.','She asked if I liked English.','Can you tell me where the station is?']},
|
||||
{icon:'🔤',title:'定语从句 (that/which/who)',detail:'that指人或物,which指物,who指人。关系词在从句中做主语/宾语。做宾语时可省略。',examples:['The book that I read is interesting.','The man who is talking is my teacher.','I like music which is relaxing.']},
|
||||
];
|
||||
|
||||
// ===== GRAMMAR PRACTICE QUESTIONS =====
|
||||
const GRAMMAR_Q = [
|
||||
{q:'I ______ a student. She ______ a teacher.',o:['am;is','is;am','am;are','are;is'],a:0},
|
||||
{q:'______ you like milk? — Yes, I ______.',o:['Do;do','Does;do','Are;am','Do;does'],a:0},
|
||||
{q:'Which is correct?',o:['two books','two book','two bookes','two bookses'],a:0},
|
||||
{q:'This is ______ book. That book is ______.',o:['my;mine','mine;my','my;my','mine;mine'],a:0},
|
||||
{q:'The cat is sleeping ______ the table.',o:['under','in','on','at'],a:0},
|
||||
{q:'He ______ (play) soccer every Sunday.',o:['plays','play','playing','to play'],a:0},
|
||||
{q:'______ do you like English? — Because it\'s fun.',o:['Why','What','When','Where'],a:0},
|
||||
{q:'My birthday is ______ May 5th. I get up ______ 7:00.',o:['on;at','in;on','at;in','on;in'],a:0},
|
||||
{q:'December is the ______ month.',o:['twelfth','twelveth','twelve'],a:0},
|
||||
{q:'You ______ finish your homework first!',o:['must','can','may','need'],a:0},
|
||||
{q:'______ run in the classroom!',o:['Don\'t','Not','No','Aren\'t'],a:0},
|
||||
{q:'______ useful book! ______ honest boy!',o:['a;An','a;A','an;An','an;A'],a:0},
|
||||
{q:'Listen! The girl ______ in the room.',o:['is singing','sings','sing','sang'],a:0},
|
||||
{q:'I ______ to the park yesterday.',o:['went','go','will go','have gone'],a:0},
|
||||
{q:'She ______ visit her grandma next Sunday.',o:['is going to','will to','goes to','visited'],a:0},
|
||||
{q:'I ______ already ______ my homework.',o:['have;finished','has;finish','am;finishing','will;finish'],a:0},
|
||||
{q:'Tom is ______ than Mike.',o:['taller','tallest','tall','more tall'],a:0},
|
||||
{q:'______ a book on the desk. ______ two pens.',o:['There is;There are','There are;There is','It is;They are','This is;Those are'],a:0},
|
||||
{q:'I have ______ apples. Do you have ______?',o:['some;any','any;some','some;some','any;any'],a:0},
|
||||
{q:'She is ______ late. She\'s always on time.',o:['never','always','usually','often'],a:0},
|
||||
{q:'I taught ______ English. She hurt ______.',o:['myself;herself','me;her','my;hers','myself;her'],a:0},
|
||||
{q:'I want ______ a book. She enjoys ______ TV.',o:['to buy;watching','buy;watch','to buy;to watch','buying;watching'],a:0},
|
||||
{q:'English ______ all over the world.',o:['is spoken','speaks','is speaking','spoke'],a:0},
|
||||
{q:'I like English ______ it\'s fun. He is tired ______ happy.',o:['because;but','so;and','but;because','and;so'],a:0},
|
||||
{q:'I think ______ he is right.',o:['that','if','what','which'],a:0},
|
||||
{q:'The book ______ I read is interesting.',o:['which','who','whom','whose'],a:0},
|
||||
];
|
||||
|
||||
// ===== VERB CONJUGATION DATA =====
|
||||
const VERBS = [
|
||||
{b:'like',s:'likes',ed:'liked',pp:'liked'},
|
||||
{b:'play',s:'plays',ed:'played',pp:'played'},
|
||||
{b:'walk',s:'walks',ed:'walked',pp:'walked'},
|
||||
{b:'talk',s:'talks',ed:'talked',pp:'talked'},
|
||||
{b:'work',s:'works',ed:'worked',pp:'worked'},
|
||||
{b:'help',s:'helps',ed:'helped',pp:'helped'},
|
||||
{b:'want',s:'wants',ed:'wanted',pp:'wanted'},
|
||||
{b:'need',s:'needs',ed:'needed',pp:'needed'},
|
||||
{b:'look',s:'looks',ed:'looked',pp:'looked'},
|
||||
{b:'call',s:'calls',ed:'called',pp:'called'},
|
||||
{b:'open',s:'opens',ed:'opened',pp:'opened'},
|
||||
{b:'start',s:'starts',ed:'started',pp:'started'},
|
||||
{b:'learn',s:'learns',ed:'learned',pp:'learned'},
|
||||
{b:'listen',s:'listens',ed:'listened',pp:'listened'},
|
||||
{b:'follow',s:'follows',ed:'followed',pp:'followed'},
|
||||
{b:'remember',s:'remembers',ed:'remembered',pp:'remembered'},
|
||||
{b:'ask',s:'asks',ed:'asked',pp:'asked'},
|
||||
{b:'answer',s:'answers',ed:'answered',pp:'answered'},
|
||||
{b:'clean',s:'cleans',ed:'cleaned',pp:'cleaned'},
|
||||
{b:'cook',s:'cooks',ed:'cooked',pp:'cooked'},
|
||||
{b:'jump',s:'jumps',ed:'jumped',pp:'jumped'},
|
||||
{b:'kick',s:'kicks',ed:'kicked',pp:'kicked'},
|
||||
{b:'pick',s:'picks',ed:'picked',pp:'picked'},
|
||||
{b:'plant',s:'plants',ed:'planted',pp:'planted'},
|
||||
{b:'rain',s:'rains',ed:'rained',pp:'rained'},
|
||||
{b:'shout',s:'shouts',ed:'shouted',pp:'shouted'},
|
||||
{b:'stay',s:'stays',ed:'stayed',pp:'stayed'},
|
||||
{b:'visit',s:'visits',ed:'visited',pp:'visited'},
|
||||
{b:'wait',s:'waits',ed:'waited',pp:'waited'},
|
||||
{b:'wonder',s:'wonders',ed:'wondered',pp:'wondered'},
|
||||
{b:'hope',s:'hopes',ed:'hoped',pp:'hoped'},
|
||||
{b:'live',s:'lives',ed:'lived',pp:'lived'},
|
||||
{b:'move',s:'moves',ed:'moved',pp:'moved'},
|
||||
{b:'notice',s:'notices',ed:'noticed',pp:'noticed'},
|
||||
{b:'exercise',s:'exercises',ed:'exercised',pp:'exercised'},
|
||||
{b:'introduce',s:'introduces',ed:'introduced',pp:'introduced'},
|
||||
{b:'watch',s:'watches',ed:'watched',pp:'watched'},
|
||||
{b:'wash',s:'washes',ed:'washed',pp:'washed'},
|
||||
{b:'push',s:'pushes',ed:'pushed',pp:'pushed'},
|
||||
{b:'fix',s:'fixes',ed:'fixed',pp:'fixed'},
|
||||
{b:'miss',s:'misses',ed:'missed',pp:'missed'},
|
||||
{b:'pass',s:'passes',ed:'passed',pp:'passed'},
|
||||
{b:'reach',s:'reaches',ed:'reached',pp:'reached'},
|
||||
{b:'relax',s:'relaxes',ed:'relaxed',pp:'relaxed'},
|
||||
{b:'finish',s:'finishes',ed:'finished',pp:'finished'},
|
||||
{b:'wish',s:'wishes',ed:'wished',pp:'wished'},
|
||||
{b:'study',s:'studies',ed:'studied',pp:'studied'},
|
||||
{b:'carry',s:'carries',ed:'carried',pp:'carried'},
|
||||
{b:'try',s:'tries',ed:'tried',pp:'tried'},
|
||||
{b:'cry',s:'cries',ed:'cried',pp:'cried'},
|
||||
{b:'hurry',s:'hurries',ed:'hurried',pp:'hurried'},
|
||||
{b:'marry',s:'marries',ed:'married',pp:'married'},
|
||||
{b:'worry',s:'worries',ed:'worried',pp:'worried'},
|
||||
{b:'empty',s:'empties',ed:'emptied',pp:'emptied'},
|
||||
{b:'stop',s:'stops',ed:'stopped',pp:'stopped'},
|
||||
{b:'plan',s:'plans',ed:'planned',pp:'planned'},
|
||||
{b:'prefer',s:'prefers',ed:'preferred',pp:'preferred'},
|
||||
{b:'be',s:'is',ed:'was',pp:'been'},
|
||||
{b:'have',s:'has',ed:'had',pp:'had'},
|
||||
{b:'do',s:'does',ed:'did',pp:'done'},
|
||||
{b:'go',s:'goes',ed:'went',pp:'gone'},
|
||||
{b:'say',s:'says',ed:'said',pp:'said'},
|
||||
{b:'make',s:'makes',ed:'made',pp:'made'},
|
||||
{b:'take',s:'takes',ed:'took',pp:'taken'},
|
||||
{b:'come',s:'comes',ed:'came',pp:'come'},
|
||||
{b:'see',s:'sees',ed:'saw',pp:'seen'},
|
||||
{b:'know',s:'knows',ed:'knew',pp:'known'},
|
||||
{b:'get',s:'gets',ed:'got',pp:'got'},
|
||||
{b:'give',s:'gives',ed:'gave',pp:'given'},
|
||||
{b:'find',s:'finds',ed:'found',pp:'found'},
|
||||
{b:'think',s:'thinks',ed:'thought',pp:'thought'},
|
||||
{b:'tell',s:'tells',ed:'told',pp:'told'},
|
||||
{b:'buy',s:'buys',ed:'bought',pp:'bought'},
|
||||
{b:'bring',s:'brings',ed:'brought',pp:'brought'},
|
||||
{b:'teach',s:'teaches',ed:'taught',pp:'taught'},
|
||||
{b:'catch',s:'catches',ed:'caught',pp:'caught'},
|
||||
{b:'eat',s:'eats',ed:'ate',pp:'eaten'},
|
||||
{b:'drink',s:'drinks',ed:'drank',pp:'drunk'},
|
||||
{b:'swim',s:'swims',ed:'swam',pp:'swum'},
|
||||
{b:'sing',s:'sings',ed:'sang',pp:'sung'},
|
||||
{b:'begin',s:'begins',ed:'began',pp:'begun'},
|
||||
{b:'ring',s:'rings',ed:'rang',pp:'rung'},
|
||||
{b:'sink',s:'sinks',ed:'sank',pp:'sunk'},
|
||||
{b:'write',s:'writes',ed:'wrote',pp:'written'},
|
||||
{b:'ride',s:'rides',ed:'rode',pp:'ridden'},
|
||||
{b:'drive',s:'drives',ed:'drove',pp:'driven'},
|
||||
{b:'rise',s:'rises',ed:'rose',pp:'risen'},
|
||||
{b:'break',s:'breaks',ed:'broke',pp:'broken'},
|
||||
{b:'speak',s:'speaks',ed:'spoke',pp:'spoken'},
|
||||
{b:'steal',s:'steals',ed:'stole',pp:'stolen'},
|
||||
{b:'wake',s:'wakes',ed:'woke',pp:'woken'},
|
||||
{b:'freeze',s:'freezes',ed:'froze',pp:'frozen'},
|
||||
{b:'choose',s:'chooses',ed:'chose',pp:'chosen'},
|
||||
{b:'forget',s:'forgets',ed:'forgot',pp:'forgotten'},
|
||||
{b:'fall',s:'falls',ed:'fell',pp:'fallen'},
|
||||
{b:'feel',s:'feels',ed:'felt',pp:'felt'},
|
||||
{b:'keep',s:'keeps',ed:'kept',pp:'kept'},
|
||||
{b:'sleep',s:'sleeps',ed:'slept',pp:'slept'},
|
||||
{b:'sweep',s:'sweeps',ed:'swept',pp:'swept'},
|
||||
{b:'meet',s:'meets',ed:'met',pp:'met'},
|
||||
{b:'read',s:'reads',ed:'read',pp:'read'},
|
||||
{b:'lead',s:'leads',ed:'led',pp:'led'},
|
||||
{b:'feed',s:'feeds',ed:'fed',pp:'fed'},
|
||||
{b:'shoot',s:'shoots',ed:'shot',pp:'shot'},
|
||||
{b:'sit',s:'sits',ed:'sat',pp:'sat'},
|
||||
{b:'win',s:'wins',ed:'won',pp:'won'},
|
||||
{b:'run',s:'runs',ed:'ran',pp:'run'},
|
||||
{b:'cut',s:'cuts',ed:'cut',pp:'cut'},
|
||||
{b:'put',s:'puts',ed:'put',pp:'put'},
|
||||
{b:'let',s:'lets',ed:'let',pp:'let'},
|
||||
{b:'set',s:'sets',ed:'set',pp:'set'},
|
||||
{b:'spend',s:'spends',ed:'spent',pp:'spent'},
|
||||
{b:'lend',s:'lends',ed:'lent',pp:'lent'},
|
||||
{b:'send',s:'sends',ed:'sent',pp:'sent'},
|
||||
{b:'build',s:'builds',ed:'built',pp:'built'},
|
||||
{b:'lose',s:'loses',ed:'lost',pp:'lost'},
|
||||
{b:'cost',s:'costs',ed:'cost',pp:'cost'},
|
||||
{b:'hit',s:'hits',ed:'hit',pp:'hit'},
|
||||
{b:'hurt',s:'hurts',ed:'hurt',pp:'hurt'},
|
||||
{b:'leave',s:'leaves',ed:'left',pp:'left'},
|
||||
{b:'mean',s:'means',ed:'meant',pp:'meant'},
|
||||
{b:'learn',s:'learns',ed:'learnt',pp:'learnt'},
|
||||
{b:'burn',s:'burns',ed:'burnt',pp:'burnt'},
|
||||
{b:'sell',s:'sells',ed:'sold',pp:'sold'},
|
||||
{b:'tell',s:'tells',ed:'told',pp:'told'},
|
||||
{b:'grow',s:'grows',ed:'grew',pp:'grown'},
|
||||
{b:'know',s:'knows',ed:'knew',pp:'known'},
|
||||
{b:'throw',s:'throws',ed:'threw',pp:'thrown'},
|
||||
{b:'draw',s:'draws',ed:'drew',pp:'drawn'},
|
||||
{b:'fly',s:'flies',ed:'flew',pp:'flown'},
|
||||
{b:'blow',s:'blows',ed:'blew',pp:'blown'},
|
||||
{b:'show',s:'shows',ed:'showed',pp:'shown'},
|
||||
{b:'hear',s:'hears',ed:'heard',pp:'heard'},
|
||||
{b:'hold',s:'holds',ed:'held',pp:'held'},
|
||||
{b:'hide',s:'hides',ed:'hid',pp:'hidden'},
|
||||
{b:'lie',s:'lies',ed:'lay',pp:'lain'},
|
||||
{b:'hang',s:'hangs',ed:'hung',pp:'hung'},
|
||||
{b:'dig',s:'digs',ed:'dug',pp:'dug'},
|
||||
{b:'stick',s:'sticks',ed:'stuck',pp:'stuck'},
|
||||
{b:'strike',s:'strikes',ed:'struck',pp:'struck'},
|
||||
{b:'shake',s:'shakes',ed:'shook',pp:'shaken'}
|
||||
];
|
||||
|
||||
// ===== READING COMPREHENSION PASSAGES =====
|
||||
const READINGS = [
|
||||
{title:'My School Life',passage:'Hi, I\'m Zhang Wei. I\'m a middle school student in Beijing. I get up at 6:30 every morning. After breakfast, I go to school by bus. We have four classes in the morning and two in the afternoon. My favorite subject is English because it\'s interesting and useful. I also like P.E. because I can play basketball with my classmates. After school, I usually do my homework first. Then I watch TV for half an hour. I go to bed at 9:30. I enjoy my school life very much.',qa:[{q:'How does Zhang Wei go to school?',o:['By bus','On foot','By bike','By car'],a:0},{q:'How many classes does he have a day?',o:['4','5','6','7'],a:2},{q:'Why does he like English?',o:['It\'s easy','It\'s interesting and useful','The teacher is nice','He can play basketball'],a:1}]},
|
||||
{title:'A Healthy Lifestyle',passage:'Tom is a 13-year-old boy. He has a healthy lifestyle. For breakfast, he always has an egg, some bread and a glass of milk. For lunch, he eats rice, chicken and vegetables. He likes fruit very much. He eats apples and bananas every day. He doesn\'t eat hamburgers or ice cream because they are junk food. He drinks lots of water, about eight glasses a day. Tom also exercises every day. He runs for 30 minutes in the morning and plays basketball after school. He thinks good eating habits and exercise can keep him healthy and strong.',qa:[{q:'What does Tom have for breakfast?',o:['Hamburgers','An egg, bread and milk','Rice and chicken','Ice cream'],a:1},{q:'Why doesn\'t Tom eat hamburgers?',o:['He doesn\'t like them','They are junk food','They are expensive','He has no time'],a:1},{q:'How long does Tom run every morning?',o:['20 minutes','30 minutes','40 minutes','60 minutes'],a:1}]},
|
||||
{title:'My Family',passage:'Hello, everyone! My name is Li Hua. I have a happy family. There are four people in my family: my father, my mother, my sister and me. My father is a doctor. He works in a big hospital. He is busy but he always spends time with us on weekends. My mother is a teacher. She teaches English in a middle school. She is very kind to her students. My sister is 16 years old. She is in high school. She is good at math and often helps me with my homework. I love my family very much. We often go to the park together on Sunday. Sometimes we visit my grandparents. They live near our home.',qa:[{q:'How many people are in Li Hua\'s family?',o:['3','4','5','6'],a:1},{q:'What does Li Hua\'s father do?',o:['A teacher','A doctor','A driver','A worker'],a:1},{q:'Where do Li Hua\'s grandparents live?',o:['In a park','Near their home','In another city','In the hospital'],a:1}]},
|
||||
{title:'Last Weekend',passage:'Last weekend, I had a wonderful time. On Saturday morning, I did my homework. In the afternoon, I went to the library with my best friend Jack. We read books and studied for the test together. On Saturday evening, I watched a movie with my parents. It was very interesting. On Sunday morning, I visited my grandparents. My grandmother cooked delicious food for us. We had a big lunch together. In the afternoon, I played soccer with my cousins in the park. We had a lot of fun. On Sunday evening, I finished my homework and packed my schoolbag for Monday. I felt very happy because I had a busy but meaningful weekend.',qa:[{q:'What did the writer do on Saturday afternoon?',o:['Watched a movie','Went to the library','Played soccer','Visited grandparents'],a:1},{q:'Who cooked food on Sunday?',o:['The writer\'s mother','The writer\'s grandmother','The writer\'s father','Jack'],a:1},{q:'How did the writer feel about the weekend?',o:['Boring','Busy but happy','Tired and sad','Just so-so'],a:1}]},
|
||||
{title:'A Letter to a Pen Pal',passage:'Dear Mike,\nHow are you? I\'m writing to tell you about my daily life. I\'m a student in Grade 7. I usually get up at 6:15 and go to school at 7:00. School starts at 8:00. I have seven subjects this term. They are Chinese, math, English, history, geography, P.E. and music. My favorite subject is history because I think it\'s very interesting. I like to learn about the past. I don\'t like math because it\'s too difficult for me. But my parents say math is very important. My dream is to visit your country one day. Can you tell me something about your school life? Please write back soon.\nYours,\nLiu Ming',qa:[{q:'What time does Liu Ming go to school?',o:['6:15','7:00','8:00','7:30'],a:1},{q:'How many subjects does Liu Ming have?',o:['5','6','7','8'],a:2},{q:'Why does Liu Ming like history?',o:['It\'s easy','It\'s interesting','The teacher is kind','His friends like it too'],a:1}]},
|
||||
{title:'Shopping for Mom\'s Birthday',passage:'Tomorrow is my mother\'s birthday. I want to buy a gift for her. After school, I go to a big store near my home. The store has many beautiful things. I see some nice scarves. The red one looks beautiful. It is 50 yuan. I also see a blue handbag. It is 120 yuan. But I only have 80 yuan. I cannot buy the handbag. So I choose the red scarf. The saleswoman says it\'s very popular this year. I think my mother will like it. I also buy a birthday card. On the card, I write: "Happy birthday, Mom! Thank you for everything you do for me. I love you!" I can\'t wait to give the gift to my mother.',qa:[{q:'How much money does the writer have?',o:['50 yuan','80 yuan','120 yuan','200 yuan'],a:1},{q:'What does the writer buy for his mother?',o:['A handbag','A scarf','A card','A scarf and a card'],a:3},{q:'Why can\'t the writer buy the handbag?',o:['It\'s not beautiful','It\'s too expensive','It\'s sold out','His mother doesn\'t like it'],a:1}]},
|
||||
{title:'Seasons in Beijing',passage:'There are four seasons in Beijing: spring, summer, autumn and winter. Spring is from March to May. It\'s warm but windy. People often fly kites in the park. Summer is from June to August. It\'s very hot. The temperature can reach 38°C. Many people go swimming or eat ice cream. Autumn is from September to November. It\'s cool and the sky is blue. The leaves turn yellow and red. It\'s a beautiful season. Winter is from December to February. It\'s very cold and sometimes it snows. Children like to make snowmen and have snowball fights. They also enjoy skiing. My favorite season is autumn because the weather is cool and I can go hiking with my family.',qa:[{q:'When does spring start in Beijing?',o:['January','March','June','September'],a:1},{q:'What do children do in winter in Beijing?',o:['Fly kites','Go swimming','Make snowmen','Go hiking'],a:2},{q:'Why does the writer like autumn best?',o:['It\'s warm','The weather is cool','It snows','It\'s windy'],a:1}]},
|
||||
{title:'A School Trip',passage:'Last Friday, our school had a trip to the Science Museum. We went there by bus. It took us about an hour to get there. The museum was very big and there were many interesting things to see. We visited the Space Hall first. I saw a real spaceship and some models of rockets. They were amazing! Then we went to the Robot Hall. The robots could dance, play chess and even speak with people. I talked with a robot. It said hello to me! After that, we watched a 3D movie about dinosaurs. It felt like the dinosaurs were right in front of me. We also did some science experiments. I learned that vinegar and baking soda can make a volcano erupt. It was so cool! We left the museum at 4:00 in the afternoon. It was really an educational and fun trip. I learned a lot about science. I want to be a scientist in the future.',qa:[{q:'How did the students go to the museum?',o:['On foot','By bus','By bike','By subway'],a:1},{q:'What did the writer see in the Robot Hall?',o:['A spaceship','Talking robots','A 3D movie','A volcano'],a:1},{q:'What does the writer want to be in the future?',o:['A teacher','A scientist','A doctor','An artist'],a:1}]},
|
||||
{title:'My Best Friend',passage:'I have a best friend. Her name is Wang Fang. We have been friends since primary school. She is a tall girl with long black hair. She has big eyes and a warm smile. She is very kind and always ready to help others. Last week, I was ill and stayed at home for three days. She came to my house every day after school to help me with my lessons. She told me what the teacher taught in class and explained the difficult points. Thanks to her help, I didn\'t fall behind. Wang Fang is also good at sports. She runs very fast and plays basketball well. After school, we often play basketball together. Sometimes we go shopping or watch movies on weekends. I am very lucky to have such a good friend. A true friend is like a gift that you cherish forever.',qa:[{q:'How did Wang Fang help the writer last week?',o:['She bought medicine','She helped with lessons','She cooked food','She called the doctor'],a:1},{q:'What sport is Wang Fang good at?',o:['Swimming','Basketball','Volleyball','Tennis'],a:1},{q:'How long have they been friends?',o:['Since last year','Since primary school','Since last month','Since this year'],a:1}]},
|
||||
{title:'My Dream Job',passage:'Everyone has a dream job. Some want to be teachers, some want to be doctors. As for me, I want to be a tour guide when I grow up. Why do I want to be a tour guide? First, I like traveling very much. I have been to many places in China, such as Beijing, Shanghai and Xi\'an. I enjoy seeing different places and learning about different cultures. Second, I like making friends. As a tour guide, I can meet people from all over the world and introduce Chinese culture to them. I think that\'s very meaningful. To make my dream come true, I must study hard now. English is very important for a tour guide because many tourists speak English. So I practice English every day. I also read many books about history and geography. I believe that if I work hard, my dream will come true one day.',qa:[{q:'What does the writer want to be?',o:['A teacher','A doctor','A tour guide','A scientist'],a:2},{q:'How many reasons does the writer give for his dream?',o:['One','Two','Three','Four'],a:1},{q:'Why is English important for this job?',o:['Many tourists speak English','English is easy','His parents want him to','The school requires it'],a:0}]},
|
||||
{title:'Protecting the Environment',passage:'Our earth is in danger. We must do something to protect it. There are many things we can do in our daily life. First, we should save water. Turn off the tap when you brush your teeth. Take short showers instead of baths. Second, we should save electricity. Turn off the lights when you leave a room. Don\'t leave the TV on when nobody is watching. Third, we should reduce waste. Use reusable bags instead of plastic bags. Recycle paper, plastic and glass. Fourth, we should plant more trees. Trees can make the air clean and cool. Last but not least, we should walk or ride a bike instead of driving a car. It\'s good for the environment and also good for our health. Remember, small actions can make a big difference. Let\'s start from now on and work together to protect our beautiful earth.',qa:[{q:'How many suggestions does the passage give?',o:['3','4','5','6'],a:2},{q:'What should we do to save water?',o:['Take baths','Brush teeth with tap running','Take short showers','Use more water'],a:2},{q:'Why should we plant more trees?',o:['They look beautiful','They make air clean','They grow fast','They are cheap'],a:1}]},
|
||||
{title:'Internet and Teenagers',passage:'The Internet plays an important role in our daily life. It helps us learn new things, communicate with friends and have fun. But it also has some disadvantages, especially for teenagers. On the one hand, the Internet is very useful. We can search for information online, watch educational videos and take online courses. We can also talk with friends through WeChat or QQ. On the other hand, spending too much time on the Internet is bad for our health. It\'s bad for our eyes. It also takes away our time for sports and sleep. Some teenagers become addicted to online games. That\'s very dangerous. So we must use the Internet wisely. Don\'t spend more than two hours a day on the Internet. Don\'t believe everything you read online. Never share your personal information with strangers. The Internet is a tool, not a master. Use it, but don\'t let it use you.',qa:[{q:'What is the passage mainly about?',o:['How to play games','Internet and teenagers','How to make friends','Online shopping'],a:1},{q:'How long should teens spend on the Internet per day?',o:['As long as they want','No more than 2 hours','At least 4 hours','Only 30 minutes'],a:1},{q:'Which is NOT mentioned as a danger of the Internet?',o:['Bad for eyes','Less time for sports','Online games addiction','Losing money'],a:3}]},
|
||||
{title:'Chinese New Year',passage:'Chinese New Year, also called Spring Festival, is the most important traditional festival in China. It usually comes in January or February. Before the festival, people clean their houses thoroughly. They put up couplets on the doors and paper cuttings on the windows. The color red is everywhere because it means good luck. On New Year\'s Eve, family members get together for a big dinner. We eat dumplings, fish and many other delicious dishes. After dinner, we watch the Spring Festival Gala on TV. Children love this night because they can get red packets with money from their parents and grandparents. On the first day of the new year, people wear new clothes and visit their relatives and friends. They say "Happy New Year" to each other. The celebration usually lasts 15 days. I love Spring Festival because I can have a good time with my family.',qa:[{q:'What do people put on doors before Spring Festival?',o:['Red packets','Paper cuttings','Couplets','Pictures'],a:2},{q:'What do children love most about Spring Festival?',o:['New clothes','Red packets','Big dinner','Watching TV'],a:1},{q:'How long does the Spring Festival celebration last?',o:['7 days','10 days','15 days','30 days'],a:2}]},
|
||||
];
|
||||
let C = {pool:[],idx:0,correct:0,total:0,mode:'s'};
|
||||
|
||||
function startConj(mode) {
|
||||
C.mode = mode || 's';
|
||||
C.pool = shuffle([...VERBS]);
|
||||
C.idx = 0; C.correct = 0; C.total = VERBS.length;
|
||||
document.getElementById('pMe').classList.remove('act');
|
||||
document.getElementById('pConj').classList.add('act');
|
||||
document.querySelectorAll('.cm').forEach(el => el.classList.toggle('act', el.dataset.mode === C.mode));
|
||||
document.getElementById('conjScore').textContent = '0/'+C.total;
|
||||
renderConj();
|
||||
}
|
||||
|
||||
function renderConj() {
|
||||
if (C.idx >= C.total) { finishConj(); return; }
|
||||
const v = C.pool[C.idx];
|
||||
const modeLabels = {s:'三单形式', ed:'过去式', pp:'过去分词'};
|
||||
const ansKey = {s:'s', ed:'ed', pp:'pp'}[C.mode];
|
||||
const correctAns = v[ansKey];
|
||||
const hint = C.mode === 's' ? `He/She/It + ${v.b}` : C.mode === 'ed' ? 'yesterday/last...' : 'have/has + ?';
|
||||
const cc = document.getElementById('conjContent');
|
||||
cc.innerHTML = `<div class="cd" style="text-align:center;padding:24px;">
|
||||
<div style="font-size:11px;color:rgba(26,26,46,0.3);margin-bottom:4px;">写出下列动词的 <strong style="color:#6366f1;">${modeLabels[C.mode]}</strong></div>
|
||||
<div style="font-size:36px;font-weight:900;color:#1a1a2e;margin:8px 0;font-family:'Inter',monospace;">${v.b}</div>
|
||||
<div style="font-size:11px;color:rgba(26,26,46,0.2);margin-bottom:10px;">💡 ${hint}</div>
|
||||
<div class="si"><input type="text" id="conjInput" autocomplete="off" autocorrect="off" spellcheck="false" style="width:160px;"><button id="conjBtn">↵</button></div>
|
||||
<div class="fb" id="conjFb"></div>
|
||||
// ===== 交互式动态几何模型引擎 =====
|
||||
const GeoModel = {
|
||||
// ---- 拖拽状态 ----
|
||||
drag:{active:false,type:'',el:null,startX:0,startY:0,bx:0,by:0,minX:0,maxX:0,minY:0,maxY:0,ox:0,oy:0,cb:null},
|
||||
startDrag(e,type,el,opts){
|
||||
const r=el.getBoundingClientRect();const svg=el.closest('svg');const sR=svg.getBoundingClientRect();
|
||||
const sx=sR.width/svg.viewBox.animVal.width, sy=sR.height/svg.viewBox.animVal.height;
|
||||
this.drag={active:true,type,el,startX:(e.clientX-sR.left)/sx,startY:(e.clientY-sR.top)/sy,bx:0,by:0,
|
||||
minX:opts.minX||0,maxX:opts.maxX||360,minY:opts.minY||0,maxY:opts.maxY||200,
|
||||
ox:parseFloat(el.getAttribute('cx')),oy:parseFloat(el.getAttribute('cy')),
|
||||
sx,sy,sr:sR,cb:opts.cb||null};
|
||||
el.classList.add('dragging');
|
||||
},
|
||||
moveDrag(e){
|
||||
const d=this.drag;if(!d.active)return;
|
||||
const r=d.sr;const x=(e.clientX-r.left)/d.sx,y=(e.clientY-r.top)/d.sy;
|
||||
const nx=Math.max(d.minX,Math.min(d.maxX,x)), ny=Math.max(d.minY,Math.min(d.maxY,y));
|
||||
d.el.setAttribute('cx',nx);d.el.setAttribute('cy',ny);
|
||||
d.bx=nx-d.ox;d.by=ny-d.oy;
|
||||
if(d.cb)d.cb(nx,ny);
|
||||
},
|
||||
endDrag(){const d=this.drag;if(d.active){d.el.classList.remove('dragging');this.drag.active=false;}},
|
||||
// ---- 双中点拖拽模型 ----
|
||||
renderMidpoint(){
|
||||
const w=360,h=180,ax=40,ay=70,bx=320,by=70,cxInit=180,ab=bx-ax;
|
||||
const scaler=function(v){return Math.round(v/ab*100)};const fmt=function(v){return scaler(v)};
|
||||
const initCx=cxInit;
|
||||
return `<div style="margin:6px 0;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
||||
<span style="font-size:13px;font-weight:700;color:#ffd700;">\u{1F3C6} 拖拽点C——观察MN的变化</span>
|
||||
<span class="m-hint" style="animation:pulse 2s ease infinite;font-size:10px;">\u{1F446} 拖动橙色点C</span>
|
||||
</div>
|
||||
<svg viewBox="0 0 ${w} ${h}" style="width:100%;background:rgba(0,0,0,0.12);border-radius:4px;padding:4px 0;">
|
||||
<!-- AB线段 -->
|
||||
<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${by}" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<!-- A点 -->
|
||||
<circle cx="${ax}" cy="${ay}" r="5" fill="#ffd700"/><text x="${ax}" y="${ay+18}" font-size="11" fill="#ffd700" text-anchor="middle" font-weight="700" font-family="'Inter',monospace">A</text>
|
||||
<!-- B点 -->
|
||||
<circle cx="${bx}" cy="${ay}" r="5" fill="#ffd700"/><text x="${bx}" y="${ay+18}" font-size="11" fill="#ffd700" text-anchor="middle" font-weight="700" font-family="'Inter',monospace">B</text>
|
||||
<!-- C点(可拖拽) -->
|
||||
<circle class="drag-point" data-model="mid" data-point="C" cx="${initCx}" cy="${ay}" r="7" fill="#e10600" stroke="#fff" stroke-width="1.5" cursor="grab"/>
|
||||
<text x="${initCx}" y="${ay-14}" font-size="10" fill="#e10600" text-anchor="middle" font-weight="900" font-family="'Inter',monospace">C</text>
|
||||
<!-- 动态M点 -->
|
||||
<circle id="mPt" cx="${ax+(initCx-ax)/2}" cy="${ay}" r="5" fill="#2ecc71"/>
|
||||
<text id="mPtLbl" x="${ax+(initCx-ax)/2}" y="${ay-14}" font-size="10" fill="#2ecc71" text-anchor="middle" font-weight="700" font-family="'Inter',monospace">M</text>
|
||||
<!-- 动态N点 -->
|
||||
<circle id="nPt" cx="${initCx+(bx-initCx)/2}" cy="${ay}" r="5" fill="#2ecc71"/>
|
||||
<text id="nPtLbl" x="${initCx+(bx-initCx)/2}" y="${ay-14}" font-size="10" fill="#2ecc71" text-anchor="middle" font-weight="700" font-family="'Inter',monospace">N</text>
|
||||
<!-- 测量标注(实时更新) -->
|
||||
<text id="mAC" x="${ax+(initCx-ax)/4}" y="${ay+35}" font-size="9" fill="rgba(255,255,255,0.2)" text-anchor="middle" font-family="'Inter',monospace">AC=${fmt(initCx-ax)}</text>
|
||||
<text id="mCB" x="${initCx+(bx-initCx)/4}" y="${ay+35}" font-size="9" fill="rgba(255,255,255,0.2)" text-anchor="middle" font-family="'Inter',monospace">CB=${fmt(bx-initCx)}</text>
|
||||
<!-- 发现区 -->
|
||||
<text id="mDisc" x="${w/2}" y="${ay+55}" font-size="0" fill="#2ecc71" text-anchor="middle" font-weight="700" font-family="'Inter',monospace"></text>
|
||||
</svg>
|
||||
<div class="m-panel">
|
||||
<span class="m-val"><span class="lbl">AB</span><span class="num" id="vAB">${fmt(ab)}</span></span>
|
||||
<span class="m-val"><span class="lbl">AC</span><span class="num" id="vAC">${fmt(initCx-ax)}</span></span>
|
||||
<span class="m-val"><span class="lbl">CB</span><span class="num" id="vCB">${fmt(bx-initCx)}</span></span>
|
||||
<span class="m-val"><span class="lbl" style="color:#2ecc71;">MN</span><span class="num hl" id="vMN">${fmt(ab/2)}</span></span>
|
||||
</div>
|
||||
</div>`;
|
||||
setTimeout(()=>{const si=document.getElementById('conjInput');if(si)si.focus();},100);
|
||||
document.getElementById('conjScore').textContent = C.correct+'/'+C.total;
|
||||
},
|
||||
initMidDrag(cx,cy){
|
||||
const ax=40,bx=320,ay=70;
|
||||
const nx=Math.round(Math.max(ax+15,Math.min(bx-15,cx)));
|
||||
const mAC=nx-ax,mCB=bx-nx,mAB=bx-ax,mMN=mAB/2;
|
||||
const scaler=function(v){return Math.round(v/mAB*100)};
|
||||
const mCx=(ax+nx)/2,mNx=(nx+bx)/2;
|
||||
$('mPt')?.setAttribute('cx',mCx);$('mPtLbl')?.setAttribute('x',mCx);
|
||||
$('nPt')?.setAttribute('cx',mNx);$('nPtLbl')?.setAttribute('x',mNx);
|
||||
$('mAC')?.setAttribute('x',ax+(nx-ax)/4);$('mAC').textContent='AC='+scaler(mAC);
|
||||
$('mCB')?.setAttribute('x',nx+(bx-nx)/4);$('mCB').textContent='CB='+scaler(mCB);
|
||||
$('vAC').textContent=scaler(mAC);$('vCB').textContent=scaler(mCB);
|
||||
$('vAB').textContent=scaler(mAB);$('vMN').textContent=scaler(mMN);
|
||||
const disc=$('mDisc');
|
||||
if(disc){disc.setAttribute('font-size','13');disc.textContent='\u{2728} 发现:无论C在哪里,MN总是AB的一半!';}
|
||||
},
|
||||
// ---- 角平分线拖拽模型 ----
|
||||
renderAngleBisector(){
|
||||
const cx=180,cy=80,r=60,initDeg=60;
|
||||
const rad=initDeg*Math.PI/180;
|
||||
const bx=cx+r*Math.cos(Math.PI/2-rad),by=cy-r*Math.sin(Math.PI/2-rad);
|
||||
const bisDeg=initDeg/2;
|
||||
const bisRad=bisDeg*Math.PI/180;
|
||||
const bisx=cx+r*Math.cos(Math.PI/2-bisRad),bisy=cy-r*Math.sin(Math.PI/2-bisRad);
|
||||
return `<div style="margin:6px 0;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
||||
<span style="font-size:13px;font-weight:700;color:#ffd700;">\u{1F3C6} 拖拽OB旋转——观察角平分线</span>
|
||||
<span class="m-hint" style="animation:pulse 2s ease infinite;font-size:10px;">\u{1F446} 拖动B点旋转</span>
|
||||
</div>
|
||||
<svg viewBox="0 0 360 170" style="width:100%;background:rgba(0,0,0,0.12);border-radius:4px;padding:4px 0;">
|
||||
<!-- OA射线(固定水平) -->
|
||||
<line x1="${cx}" y1="${cy}" x2="${cx+r+20}" y2="${cy}" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
||||
<polygon points="${cx+r+18},${cy-5} ${cx+r+24},${cy} ${cx+r+18},${cy+5}" fill="rgba(255,255,255,0.15)"/>
|
||||
<text x="${cx+r+26}" y="${cy+4}" font-size="10" fill="rgba(255,255,255,0.3)" font-family="'Inter',monospace">A</text>
|
||||
<!-- OB射线(可旋转) -->
|
||||
<line id="obRay" x1="${cx}" y1="${cy}" x2="${bx}" y2="${by}" stroke="#e10600" stroke-width="2.5"/>
|
||||
<polygon id="obArr" points="${bx},${by} ${bx-6*Math.cos(Math.PI/2-rad)-3*Math.sin(Math.PI/2-rad)},${by+6*Math.sin(Math.PI/2-rad)-3*Math.cos(Math.PI/2-rad)} ${bx-6*Math.cos(Math.PI/2-rad)+3*Math.sin(Math.PI/2-rad)},${by+6*Math.sin(Math.PI/2-rad)+3*Math.cos(Math.PI/2-rad)}" fill="#e10600"/>
|
||||
<!-- B点(可拖拽) -->
|
||||
<circle class="drag-point" data-model="angle" data-point="B" cx="${bx}" cy="${by}" r="7" fill="#e10600" stroke="#fff" stroke-width="1.5" cursor="grab"/>
|
||||
<text x="${bx+10}" y="${by-5}" font-size="10" fill="#e10600" text-anchor="start" font-weight="900" font-family="'Inter',monospace">B</text>
|
||||
<!-- O点 -->
|
||||
<circle cx="${cx}" cy="${cy}" r="5" fill="#ffd700"/>
|
||||
<text x="${cx-8}" y="${cy+16}" font-size="10" fill="#ffd700" text-anchor="middle" font-weight="700" font-family="'Inter',monospace">O</text>
|
||||
<!-- 角平分线OC -->
|
||||
<line id="bisRay" x1="${cx}" y1="${cy}" x2="${bisx}" y2="${bisy}" stroke="#2ecc71" stroke-width="2.5" stroke-dasharray="5,3"/>
|
||||
<text id="bisLbl" x="${bisx+10}" y="${bisy-5}" font-size="10" fill="#2ecc71" font-family="'Inter',monospace">C(平分线)</text>
|
||||
<!-- 角度弧 -->
|
||||
<path id="aArc" d="${this.arcPath(cx,cy,40,initDeg)}" stroke="#ffd700" stroke-width="2" fill="none" opacity="0.6"/>
|
||||
<text id="degLbl" x="${cx+45}" y="${cy-10}" font-size="12" fill="#ffd700" text-anchor="middle" font-weight="900" font-family="'Inter',monospace">${initDeg}°</text>
|
||||
<!-- 平分角弧 -->
|
||||
<path id="bisArc" d="${this.arcPath(cx,cy,25,bisDeg)}" stroke="#2ecc71" stroke-width="1.5" fill="none" opacity="0.4"/>
|
||||
<text id="bisDegLbl" x="${cx+32}" y="${cy-8}" font-size="10" fill="#2ecc71" text-anchor="middle" font-family="'Inter',monospace">${bisDeg}°</text>
|
||||
</svg>
|
||||
<div class="m-panel">
|
||||
<span class="m-val"><span class="lbl">∠AOB</span><span class="num" id="vAOB">${initDeg}°</span></span>
|
||||
<span class="m-val"><span class="lbl" style="color:#2ecc71;">∠AOC</span><span class="num hl" id="vAOC">${bisDeg}°</span></span>
|
||||
<span class="m-val"><span class="lbl" style="color:#2ecc71;">∠COB</span><span class="num hl" id="vCOB">${bisDeg}°</span></span>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
arcPath(cx,cy,r,deg){
|
||||
return this.arcPts(cx,cy,r,0,deg);
|
||||
},
|
||||
initAngleDrag(cx,cy){
|
||||
const ocx=180,ocy=80,r=60;
|
||||
const dx=cx-ocx,dy=cy-ocy;
|
||||
const rawDeg=Math.atan2(-dy,dx)*180/Math.PI+90;
|
||||
let deg=Math.max(10,Math.min(170,rawDeg));
|
||||
const rad=deg*Math.PI/180;
|
||||
const bx=ocx+r*Math.cos(Math.PI/2-rad),by=ocy-r*Math.sin(Math.PI/2-rad);
|
||||
const bisDeg=deg/2,bisRad=bisDeg*Math.PI/180;
|
||||
const bisx=ocx+r*Math.cos(Math.PI/2-bisRad),bisy=ocy-r*Math.sin(Math.PI/2-bisRad);
|
||||
const ray=$('obRay');if(ray){ray.setAttribute('x2',bx);ray.setAttribute('y2',by);}
|
||||
const arr=$('obArr');if(arr){
|
||||
const ax=bx-6*Math.cos(Math.PI/2-rad)-3*Math.sin(Math.PI/2-rad),ay=by+6*Math.sin(Math.PI/2-rad)-3*Math.cos(Math.PI/2-rad);
|
||||
const bx2=bx-6*Math.cos(Math.PI/2-rad)+3*Math.sin(Math.PI/2-rad),by2=by+6*Math.sin(Math.PI/2-rad)+3*Math.cos(Math.PI/2-rad);
|
||||
arr.setAttribute('points',`${bx},${by} ${ax},${ay} ${bx2},${by2}`);
|
||||
}
|
||||
const bisEl=$('bisRay');if(bisEl){bisEl.setAttribute('x2',bisx);bisEl.setAttribute('y2',bisy);}
|
||||
const bisLblEl=$('bisLbl');if(bisLblEl){bisLblEl.setAttribute('x',bisx+10);bisLblEl.setAttribute('y',bisy-5);}
|
||||
const arcEl=$('aArc');if(arcEl)arcEl.setAttribute('d',this.arcPts(ocx,ocy,40,0,deg));
|
||||
const bisArcEl=$('bisArc');if(bisArcEl)bisArcEl.setAttribute('d',this.arcPts(ocx,ocy,25,0,bisDeg));
|
||||
const degLbl=$('degLbl');if(degLbl)degLbl.textContent=deg+'°';
|
||||
const bisDegLbl=$('bisDegLbl');if(bisDegLbl)bisDegLbl.textContent=Math.round(bisDeg)+'°';
|
||||
$('vAOB').textContent=deg+'°';$('vAOC').textContent=Math.round(bisDeg)+'°';$('vCOB').textContent=Math.round(bisDeg)+'°';
|
||||
},
|
||||
// ---- 钟面角交互模型 ----
|
||||
renderClock(){
|
||||
const cx=180,cy=80,r=60,h=8,m=30;
|
||||
return `<div style="margin:6px 0;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
||||
<span style="font-size:13px;font-weight:700;color:#ffd700;">\u{1F3C6} 拖拽指针——观察钟面角</span>
|
||||
<span class="m-hint" style="animation:pulse 2s ease infinite;font-size:10px;">\u{1F446} 拖动分针或时针</span>
|
||||
</div>
|
||||
<svg viewBox="0 0 360 170" style="width:100%;background:rgba(0,0,0,0.12);border-radius:4px;padding:4px 0;">
|
||||
<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
|
||||
${[0,30,60,90,120,150].map(a=>{const t=a*Math.PI/180;const x1=cx+(r-8)*Math.sin(t),y1=cy-(r-8)*Math.cos(t);const x2=cx+r*Math.sin(t),y2=cy-r*Math.cos(t);return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="rgba(255,255,255,${a%90===0?0.15:0.05})" stroke-width="${a%90===0?2:1}"/>`}).join('')}
|
||||
${[3,6,9,12].map(h=>{const a=h*30*Math.PI/180;const lx=cx+(r+14)*Math.sin(a),ly=cy-(r+14)*Math.cos(a);return `<text x="${lx}" y="${ly+4}" font-size="11" fill="rgba(255,255,255,0.2)" text-anchor="middle" font-family="'Inter',monospace">${h}</text>`}).join('')}
|
||||
${[1,2,4,5,7,8,10,11].map(h=>{const a=h*30*Math.PI/180;const lx=cx+(r+10)*Math.sin(a),ly=cy-(r+10)*Math.cos(a);return `<text x="${lx}" y="${ly+3}" font-size="7" fill="rgba(255,255,255,0.08)" text-anchor="middle" font-family="'Inter',monospace">${h}</text>`}).join('')}
|
||||
<!-- 时针(可拖拽) -->
|
||||
<line id="cHh" data-h="8" x1="${cx}" y1="${cy}" x2="${cx+0.45*r*Math.sin((h*30+m*0.5-90)*Math.PI/180)}" y2="${cy-0.45*r*Math.cos((h*30+m*0.5-90)*Math.PI/180)}" stroke="#ffd700" stroke-width="3.5"/>
|
||||
<circle class="drag-point" data-model="clock" data-hand="h" cx="${cx+0.45*r*Math.sin((h*30+m*0.5-90)*Math.PI/180)}" cy="${cy-0.45*r*Math.cos((h*30+m*0.5-90)*Math.PI/180)}" r="6" fill="#ffd700" stroke="#fff" stroke-width="1.5" cursor="grab"/>
|
||||
<!-- 分针(可拖拽) -->
|
||||
<line id="cMh" data-m="30" x1="${cx}" y1="${cy}" x2="${cx+0.6*r*Math.sin((m*6-90)*Math.PI/180)}" y2="${cy-0.6*r*Math.cos((m*6-90)*Math.PI/180)}" stroke="rgba(255,255,255,0.3)" stroke-width="2"/>
|
||||
<circle class="drag-point" data-model="clock" data-hand="m" cx="${cx+0.6*r*Math.sin((m*6-90)*Math.PI/180)}" cy="${cy-0.6*r*Math.cos((m*6-90)*Math.PI/180)}" r="5" fill="rgba(255,255,255,0.3)" stroke="#fff" stroke-width="1" cursor="grab"/>
|
||||
<!-- 中心点 -->
|
||||
<circle cx="${cx}" cy="${cy}" r="4" fill="#e10600"/>
|
||||
<!-- 角度弧 -->
|
||||
<path id="cArc" d="" stroke="#ffd700" stroke-width="2" fill="none" opacity="0.5"/>
|
||||
<text id="cAng" x="${cx}" y="${cy-r-20}" font-size="14" fill="#ffd700" text-anchor="middle" font-weight="900" font-family="'Inter',monospace">75°</text>
|
||||
</svg>
|
||||
<div class="m-panel">
|
||||
<span class="m-val"><span class="lbl">时刻</span><span class="num" id="cTime">${h}:${String(m).padStart(2,'0')}</span></span>
|
||||
<span class="m-val"><span class="lbl">时针</span><span class="num" id="cHDeg">${h*30+m*0.5}°</span></span>
|
||||
<span class="m-val"><span class="lbl">分针</span><span class="num" id="cMDeg">${m*6}°</span></span>
|
||||
<span class="m-val"><span class="lbl" style="color:#ffd700;">夹角</span><span class="num hl" id="cDiff">75°</span></span>
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:4px;"><span style="font-size:11px;color:rgba(255,255,255,0.15);font-family:'Inter',monospace;">公式:|30h - 5.5m|</span></div>
|
||||
</div>`;
|
||||
},
|
||||
initClockDrag(cx,cy,hand){
|
||||
const ocx=180,ocy=80,r=60;
|
||||
const dx=cx-ocx,dy=cy-ocy;
|
||||
const rawDeg=Math.atan2(-dy,dx)*180/Math.PI+90;
|
||||
let deg=rawDeg<0?rawDeg+360:rawDeg;
|
||||
let h=parseInt($('cHh').getAttribute('data-h')||'8'), m=parseInt($('cMh').getAttribute('data-m')||'30');
|
||||
if(hand==='h'){h=Math.round(((deg%360)/30)%12);if(h===0)h=12;m=Math.round((deg%30)*2);}
|
||||
else{m=Math.round(deg/6)%60;let mAng=m*6;let hAng=h%12*30+m*0.5;let diff=Math.abs(hAng-mAng);if(diff>180)diff=360-diff;$('cArc').setAttribute('d',this.clockArc(ocx,ocy,40,Math.min(mAng,hAng),Math.max(mAng,hAng)));}
|
||||
// Compute positions
|
||||
const hAng=h%12*30+m*0.5,mAng=m*6;
|
||||
const diff=Math.abs(hAng-mAng);const finalDiff=diff>180?360-diff:diff;
|
||||
const hx=ocx+0.45*r*Math.sin((hAng-90)*Math.PI/180),hy=ocy-0.45*r*Math.cos((hAng-90)*Math.PI/180);
|
||||
const mx=ocx+0.6*r*Math.sin((mAng-90)*Math.PI/180),my=ocy-0.6*r*Math.cos((mAng-90)*Math.PI/180);
|
||||
const hPt=$('cHh');if(hPt){hPt.setAttribute('x2',hx);hPt.setAttribute('y2',hy);hPt.setAttribute('data-h',h);}
|
||||
const mPt=$('cMh');if(mPt){mPt.setAttribute('x2',mx);mPt.setAttribute('y2',my);mPt.setAttribute('data-m',m);}
|
||||
const hDrag=document.querySelector('[data-model="clock"][data-hand="h"]');if(hDrag){hDrag.setAttribute('cx',hx);hDrag.setAttribute('cy',hy);}
|
||||
const mDrag=document.querySelector('[data-model="clock"][data-hand="m"]');if(mDrag){mDrag.setAttribute('cx',mx);mDrag.setAttribute('cy',my);}
|
||||
$('cTime').textContent=h+':'+String(Math.round(m)).padStart(2,'0');
|
||||
$('cHDeg').textContent=Math.round(hAng)+'°';$('cMDeg').textContent=Math.round(mAng)+'°';$('cDiff').textContent=Math.round(finalDiff)+'°';
|
||||
$('cAng').textContent=Math.round(finalDiff)+'°';
|
||||
const start=Math.min(hAng,mAng),end=Math.max(hAng,mAng);
|
||||
const arc=this.clockArc(ocx,ocy,35,start,end);
|
||||
const arcEl=$('cArc');if(arcEl)arcEl.setAttribute('d',arc);
|
||||
},
|
||||
clockArc(cx,cy,r,s,e){
|
||||
const a1=Math.min(s,e),a2=Math.max(s,e);
|
||||
if(a2-a1>180){return this.arcPts(cx,cy,r,a2,360)+' '+this.arcPts(cx,cy,r,0,a1);}
|
||||
return this.arcPts(cx,cy,r,a1,a2);
|
||||
},
|
||||
arcPts(cx,cy,r,s,e){
|
||||
const pts=[];for(let i=0;i<=24;i++){const t=(s+(e-s)*i/24)*Math.PI/180;pts.push(`${cx+r*Math.sin(t)},${cy-r*Math.cos(t)}`)}return pts.join(' ');
|
||||
}
|
||||
};
|
||||
const SS = [
|
||||
// ═══════════════════════════════════════
|
||||
// CHAPTER 1: 有理数
|
||||
// ═══════════════════════════════════════
|
||||
{ id:1, title:'有理数', desc:'正负·数轴·相反·绝对值·运算·乘方·科学记数法', emoji:'🏁',
|
||||
segs:[
|
||||
// ── 概述 ──
|
||||
{ type:'pn', content:`<h3>📋 有理数 = "有理有据"的数</h3><p>有理数就是所有能写成 p/q(q≠0)的数——整数和分数都是有理数。</p><p>本章路线:正负数→数轴→相反数/绝对值→四则运算→倒数→乘方→科学记数法</p><svg viewBox="0 0 360 50" style="width:100%;margin:4px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;"><line x1="18" y1="25" x2="342" y2="25" stroke="rgba(255,255,255,0.06)" stroke-width="1"/><circle cx="30" cy="25" r="5" fill="#e10600"/><text x="30" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">正负数</text><circle cx="90" cy="25" r="5" fill="#e10600"/><text x="90" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">数轴</text><circle cx="150" cy="25" r="5" fill="#e10600"/><text x="150" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">相反/绝对</text><circle cx="210" cy="25" r="5" fill="#e10600"/><text x="210" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">四则运算</text><circle cx="270" cy="25" r="5" fill="#e10600"/><text x="270" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">倒数/乘方</text><circle cx="330" cy="25" r="5" fill="#e10600"/><text x="330" y="42" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">科学记数法</text><line x1="35" y1="25" x2="85" y2="25" stroke="rgba(255,255,255,0.04)" stroke-width="1"/><line x1="95" y1="25" x2="145" y2="25" stroke="rgba(255,255,255,0.04)" stroke-width="1"/><line x1="155" y1="25" x2="205" y2="25" stroke="rgba(255,255,255,0.04)" stroke-width="1"/><line x1="215" y1="25" x2="265" y2="25" stroke="rgba(255,255,255,0.04)" stroke-width="1"/><line x1="275" y1="25" x2="325" y2="25" stroke="rgba(255,255,255,0.04)" stroke-width="1"/></svg>` },
|
||||
// ── KP1: 正数和负数 ──
|
||||
{ type:'kp', icon:'🌡️', title:'正数和负数',
|
||||
content:`<p>🔥 <strong>正数</strong>:大于0的数,如+3、5("+"可省略)</p>
|
||||
<p>❄️ <strong>负数</strong>:小于0的数,前面带"-",如-3、-2.5</p>
|
||||
<p>0既不是正数也不是负数——它是正负的<strong>分界线</strong></p>
|
||||
<svg viewBox="0 0 360 70" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<rect x="160" y="5" width="16" height="35" rx="8" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
|
||||
<circle cx="168" cy="42" r="8" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
|
||||
<rect x="163" y="12" width="10" height="26" rx="4" fill="rgba(225,6,0,0.2)"/>
|
||||
<circle cx="168" cy="42" r="6" fill="rgba(225,6,0,0.2)"/>
|
||||
<text x="170" y="60" font-size="8" fill="#e10600" text-anchor="middle" font-weight="700">+30°C零上</text>
|
||||
<rect x="10" y="5" width="16" height="35" rx="8" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
|
||||
<circle cx="18" cy="42" r="8" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
|
||||
<rect x="13" y="30" width="10" height="8" rx="4" fill="rgba(46,204,113,0.15)"/>
|
||||
<circle cx="18" cy="42" r="6" fill="rgba(46,204,113,0.15)"/>
|
||||
<text x="20" y="60" font-size="8" fill="rgba(46,204,113,0.5)" text-anchor="middle">-5°C零下</text>
|
||||
</svg>
|
||||
<p>💡 有理数分类:<strong>整数</strong>(正整数/0/负整数)· <strong>分数</strong>(正分数/负分数)</p>
|
||||
<div class="kj">零上为正,零下为负,零是分界要记熟<br>整数分数都是有理数,不是有理数太难找</div>` },
|
||||
// ── KP2: 数轴 ──
|
||||
{ type:'kp', icon:'🚗', title:'数轴',
|
||||
content:`<p>数轴三要素:<strong>原点(0) + 正方向(→) + 单位长度</strong></p>
|
||||
<p>数轴上越<strong>右</strong>边的数越<strong>大</strong>,越<strong>左</strong>边的数越<strong>小</strong></p>
|
||||
<p>比较大小口诀:正数>0>负数;两个负数,绝对值大的反而<strong>小</strong></p>
|
||||
<svg viewBox="0 0 360 55" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;">
|
||||
<line x1="20" y1="28" x2="340" y2="28" stroke="rgba(255,255,255,0.1)" stroke-width="1.5"/>
|
||||
<polygon points="338,23 348,28 338,33" fill="rgba(255,255,255,0.15)"/>
|
||||
<line x1="60" y1="23" x2="60" y2="33" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<line x1="110" y1="23" x2="110" y2="33" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<line x1="180" y1="23" x2="180" y2="33" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<line x1="250" y1="23" x2="250" y2="33" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<line x1="300" y1="23" x2="300" y2="33" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<text x="60" y="46" font-size="8" fill="rgba(225,6,0,0.3)" text-anchor="middle">-3</text>
|
||||
<text x="110" y="46" font-size="8" fill="rgba(255,255,255,0.2)" text-anchor="middle">-1</text>
|
||||
<text x="180" y="46" font-size="10" fill="#ffd700" text-anchor="middle" font-weight="900">0</text>
|
||||
<text x="250" y="46" font-size="8" fill="rgba(46,204,113,0.3)" text-anchor="middle">+2</text>
|
||||
<text x="300" y="46" font-size="8" fill="rgba(46,204,113,0.3)" text-anchor="middle">+4</text>
|
||||
</svg>
|
||||
<svg viewBox="0 0 360 60" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;">
|
||||
<line x1="20" y1="30" x2="340" y2="30" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
|
||||
<polygon points="338,25 348,30 338,35" fill="rgba(255,255,255,0.1)"/>
|
||||
<text x="180" y="50" font-size="7" fill="#ffd700" text-anchor="middle" font-weight="700">-3 + 5 = 2</text>
|
||||
<text x="60" y="12" font-size="7" fill="#e10600" text-anchor="middle">-3</text>
|
||||
<text x="180" y="12" font-size="7" fill="#2ecc71" text-anchor="middle">+5</text>
|
||||
<circle cx="60" cy="30" r="5" fill="#e10600">
|
||||
<animate attributeName="cx" values="60;180;60" dur="3s" repeatCount="indefinite"/>
|
||||
<animate attributeName="fill" values="#e10600;#2ecc71;#e10600" dur="3s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<path d="M 60,20 L 180,20" stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="3,2"/>
|
||||
<polygon points="175,17 185,20 175,23" fill="rgba(255,255,255,0.08)"/>
|
||||
</svg>
|
||||
<div class="kj">数轴一条线,原点在中间,正方向向右画<br>右边比左边大,正数大于零,负数小于零<br>两个负数比大小,绝对值大的反而小</div>` },
|
||||
// ── KP3: 相反数与绝对值 ──
|
||||
{ type:'kp', icon:'🪞', title:'相反数与绝对值',
|
||||
content:`<p>🔁 <strong>相反数</strong>:只有符号不同的两个数,如3和-3(像照镜子)</p>
|
||||
<p>📍 <strong>绝对值</strong>:数轴上点<strong>到原点的距离</strong>,记作\|a\|</p>
|
||||
<ul style="color:rgba(255,255,255,0.7);font-size:14px;line-height:2.2;padding-left:20px;margin:8px 0;">
|
||||
<li>\|正数\| = 它本身(\|5\|=5)</li>
|
||||
<li>\|负数\| = 它的相反数(\|-5\|=5)</li>
|
||||
<li>\|0\| = 0</li></ul>
|
||||
<svg viewBox="0 0 360 60" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">
|
||||
<line x1="30" y1="30" x2="330" y2="30" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
|
||||
<polygon points="328,25 338,30 328,35" fill="rgba(255,255,255,0.12)"/>
|
||||
<circle cx="80" cy="30" r="4" fill="#e10600"/><text x="80" y="48" font-size="9" fill="#e10600" text-anchor="middle" font-weight="700">-5</text>
|
||||
<circle cx="180" cy="30" r="4" fill="#ffd700"/><text x="180" y="48" font-size="9" fill="#ffd700" text-anchor="middle" font-weight="900">0</text>
|
||||
<circle cx="280" cy="30" r="4" fill="#2ecc71"/><text x="280" y="48" font-size="9" fill="#2ecc71" text-anchor="middle" font-weight="700">+5</text>
|
||||
<!-- Animated distance arrows -->
|
||||
<path d="M 80,16 L 180,16" stroke="#e10600" stroke-width="2" stroke-dasharray="4,3" opacity="0.5">
|
||||
<animate attributeName="opacity" values="0.2;0.8;0.2" dur="2s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M 180,16 L 280,16" stroke="#2ecc71" stroke-width="2" stroke-dasharray="4,3" opacity="0.5">
|
||||
<animate attributeName="opacity" values="0.8;0.2;0.8" dur="2s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<path d="M 80,16 L 180,16" stroke="#e10600" stroke-width="1.5" opacity="0.4"/><path d="M 180,16 L 280,16" stroke="#2ecc71" stroke-width="1.5" opacity="0.4"/>
|
||||
<text x="130" y="14" font-size="7" fill="#e10600" text-anchor="middle">\|-5\|=5</text>
|
||||
<text x="230" y="14" font-size="7" fill="#2ecc71" text-anchor="middle">|+5|=5</text>
|
||||
</svg>
|
||||
<div class="kj">相反数是照镜子,符号相反值一样<br>绝对值是看距离,永远不取负值<br>正数绝对值是自己,负数绝对值是相反数</div>
|
||||
<p>💡 相反数的绝对值<strong>相等</strong>,互为相反数的两个数和为<strong>0</strong></p>` },
|
||||
// ── 例题 ──
|
||||
{ type:'eg', title:'例题→银行卡里的正负数', steps:[
|
||||
'💳 <strong>题目</strong>:小明银行卡余额100元,存入50元(+50),又取出80元(-80),最后余额多少?',
|
||||
'🔍 <strong>分析</strong>:"存入"用+,"取出"用-。列式:100 + (+50) + (-80)',
|
||||
'💡 <strong>第一步</strong>:先算100+(+50)=150。同号相加,取相同符号(正),绝对值相加:100+50=150',
|
||||
'💡 <strong>第二步</strong>:再算150+(-80)。异号相加,取绝对值大的符号(正),绝对值相减:150-80=70',
|
||||
'✅ <strong>答案</strong>:余额70元!正数代表存款,负数代表取款,一加一减就是实际余额。'
|
||||
]},
|
||||
// ── KP4: 有理数的加减乘除与倒数 ──
|
||||
{ type:'kp', icon:'🧮', title:'有理数的运算',
|
||||
content:`<p><strong>加减法</strong>:同号相加符号不变、绝对值相加;异号相加取绝对值大的符号、相减;减法=加相反数</p>
|
||||
<p><strong>乘除法</strong>:同号得正、异号得负;除以一个数=乘以它的<strong>倒数</strong></p>
|
||||
<p style="font-size:16px;text-align:center;color:#ffd700;font-weight:700;margin:8px 0;">a-b=a+(-b) a÷b=a×(1/b)(b≠0)</p>
|
||||
<p>🔑 <strong>倒数</strong>:乘积为1的两个数互为倒数。0没有倒数!</p>
|
||||
<p>💡 <strong>分配律</strong>:a(b+c)=ab+ac,去括号时注意负号!</p>
|
||||
<p><strong>交换律</strong>:a+b=b+a,a×b=b×a(位置交换)<br><strong>结合律</strong>:(a+b)+c=a+(b+c),(a×b)×c=a×(b×c)(括号位置变)<br><strong>分配律</strong>:a(b+c)=ab+ac(乘法对加法的分配)</p>
|
||||
<div class="kj">同号相加符号不变,绝对值相加很简便<br>异号相加符号取大,绝对值相减别搞错<br>减去一个数等于加相反数,这招一定要记住<br>同号得正异号得负,乘除法则都适用<br>乘积为一是倒数,零没倒数要记住</div>` },
|
||||
// ── KP5: 乘方与科学记数法 ──
|
||||
{ type:'kp', icon:'⬆️', title:'乘方与科学记数法',
|
||||
content:`<p>🔢 <strong>乘方</strong>:aⁿ = a×a×...×a(n个a相乘),a叫底数,n叫指数</p>
|
||||
<p>⚡ 负数的奇次方为负、偶次方为正;<span style="color:#e10600;font-weight:700;">-1⁴ ≠ (-1)⁴</span>(-1⁴ = -1,(-1)⁴ = 1)</p>
|
||||
<p>📊 <strong>科学记数法</strong>:a×10ⁿ(1 ≤ |a| < 10)</p>
|
||||
<p>📐 <strong>有效数字</strong>:从左起第一个非0数字起到末位止。例:0.03040有4个有效数字(3、0、4、0)</p>
|
||||
<div class="kj">奇次方符号不变,偶次方负号不见<br>负一奇次还是负一,负一偶次等于正一<br>科学记数很简单,a乘10的n次方<br>有效数字从左起,非零数字数到尾</div>` },
|
||||
// ── 分站赛入口 ──
|
||||
{ type:'stg', title:'\u{1F3C1} 分站赛' },
|
||||
// ── 分站赛题:重点 ──
|
||||
{ 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' },
|
||||
{ type:'ss', tag:'核心', question:'计算(-2)+(-3)-(-5)×(-2)÷(-1)的结果是', opts:['\u22129','5','\u22125','9'], correct:1, passMsg:'✅ 先乘除:(-5)×(-2)÷(-1)=-10;再加减:-5+10=5', failMsg:'💡 (-5)×(-2)=10;10÷(-1)=-10;(-2)+(-3)-(-10)=-5+10=<strong>5</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'某地最高温6°C,最低温\u22124°C,温差是', opts:['2°C','10°C','\u221210°C','\u22122°C'], correct:1, passMsg:'✅ 6-(-4)=10°C', failMsg:'💡 温差=最高-最低=6-(-4)=<strong>10°C</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'计算(-4)×0.25×(-25)×4=', opts:['100','\u2212100','400','\u2212400'], correct:0, passMsg:'✅ [(-4)×(-25)]×(0.25×4)=100×1=100', failMsg:'💡 交换结合凑整:[(-4)×(-25)]×(0.25×4)=100×1=<strong>100</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'计算(-2)³+(-3)²×(-1)⁵的结果是', opts:['1','\u221217','\u22121','\u221217'], correct:0, passMsg:'✅ (-2)³=-8,(-3)²=9,(-1)⁵=-1→-8+9×(-1)=-17', failMsg:'💡 (-2)³=-8;(-3)²=9;(-1)⁵=-1;-8+9×(-1)=<strong>-17</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'已知A=\u22123,B=+4,C=-2,则A+B-C=', opts:['5','\u22121','3','\u22129'], correct:2, passMsg:'✅ -3+4-(-2)=1+2=3', failMsg:'💡 -3+4-(-2)=1+2=<strong>3</strong>。注意减去负数=加上正数' },
|
||||
{ type:'ss', tag:'核心', question:'下列各数中最大的数是', opts:['\u22123','\u22120.5','\u22121','\u22124'], correct:1, passMsg:'✅ -0.5最接近0,最大', failMsg:'💡 负数比较:绝对值大的反而<strong>小</strong>。-0.5的绝对值最小,所以最大' },
|
||||
// ── 分站赛题:高频 ──
|
||||
{ type:'ss', tag:'高频', question:'若|a\u22121|+(b+2)²=0,则a+b等于', opts:['3','\u22121','1','\u22123'], correct:1, passMsg:'✅ |a-1|=0→a=1;(b+2)²=0→b=-2;a+b=-1', failMsg:'💡 非负性:\|A\|≥0,A²≥0\n→各自为0:a=1,b=-2→a+b=<strong>-1</strong>' },
|
||||
{ type:'ss', tag:'高频', question:'若a、b互为相反数,c、d互为倒数,|m|=2,则(a+b)/m+m²\u2212cd的值为', opts:['3','5','\u22123','\u22125'], correct:0, passMsg:'✅ a+b=0,cd=1,m²=4→0/m+4-1=3', failMsg:'💡 相反数→a+b=0;倒数→cd=1;\|m\|=2→m²=4→0/m+4-1=<strong>3</strong>' },
|
||||
{ type:'ss', tag:'高频', question:'计算\u22121⁴+16÷(\u22122)³×\u2223\u22123\u2223的结果是', opts:['\u22127','5','\u22125','7'], correct:0, passMsg:'✅ -1⁴=-1;(-2)³=-8;16÷(-8)=-2;\|-3\|=3→-1+(-2)×3=-7', failMsg:'💡 <strong>易错</strong>:-1⁴=-(1⁴)=<strong>-1</strong>≠(-1)⁴=1!=-1+(-2)×3=<strong>-7</strong>' },
|
||||
{ type:'ss', tag:'高频', question:'若|x|=5,|y|=3,x>y,则x+y等于', opts:['8 或 2','8 或 2','8 或 -2','2 或 -8'], correct:0, passMsg:'✅ x=5,y=3→8;x=5,y=-3→2', failMsg:'💡 \|x\|=5→x=±5;\|y\|=3→y=±3\nx>y排除x=-5→x=5,x+y=5+3=8或5+(-3)=2' },
|
||||
{ type:'ss', tag:'高频', question:'绝对值等于5的数有几个?', opts:['1个','2个','3个','4个'], correct:1, passMsg:'✅ 5和-5,绝对值都是5', failMsg:'💡 绝对值等于a(a>0)的数有<strong>两个</strong>:a和-a。|5|=|-5|=5' },
|
||||
{ type:'ss', tag:'高频', question:'气温从\u22123°C上升5°C后又下降8°C,最后温度是', opts:['\u22126°C','0°C','\u221216°C','6°C'], correct:0, passMsg:'✅ -3+5-8=-6°C', failMsg:'💡 -3+5=2,2-8=<strong>-6°C</strong>。可以先算净变化:+5-8=-3,\u22123+(-3)=-6' },
|
||||
{ type:'ss', tag:'高频', question:'比较大小:\u22122/3和\u22123/4', opts:['\u22122/3 > \u22123/4','\u22122/3 < \u22123/4','相等','无法比较'], correct:0, passMsg:'✅ -2/3=-8/12,-3/4=-9/12,-8>-9', failMsg:'💡 通分:-2/3=-8/12,-3/4=-9/12\n两个负数,绝对值大的反而小→-8/12> -9/12' },
|
||||
{ type:'ss', tag:'高频', question:'倒数等于它本身的数是', opts:['1','\u22121','1和\u22121','0和1'], correct:2, passMsg:'✅ 1×1=1,(-1)×(-1)=1,1和-1的倒数都是自身', failMsg:'💡 a×a=1→a²=1→a=±1。<strong>1和-1的倒数等于它本身</strong>。0没有倒数!' },
|
||||
// ── 分站赛题:综合 ──
|
||||
{ type:'ss', tag:'综合', question:'利用分配律计算(\u221224)×(1/2\u22122/3+3/4)的结果是', opts:['\u221220','\u221214','\u22122','2'], correct:1, passMsg:'✅ -24×1/2=-12,-24×(-2/3)=16,-24×3/4=-18,和=-14', failMsg:'💡 (-24)×1/2=-12;(-24)×(-2/3)=16;(-24)×3/4=-18\n=-12+16-18=<strong>-14</strong>。分配律:a(b+c)=ab+ac' },
|
||||
{ type:'ss', tag:'综合', question:'若a和b互为相反数,c和d互为倒数,m的绝对值是3,则m²\u2212(a+b+cd)×m+(a+b)²º²³\u2212(cd)²º²³=', opts:['9','6','3','0'], correct:1, passMsg:'✅ a+b=0,cd=1,m²=9。代入:9-(0+1)×(±3)+0-1=9∓3-1=6或12→取6', failMsg:'💡 a+b=0,cd=1,m=±3\n原式=9-(1)×(±3)-1=8∓3\n当m=3时=5;m=-3时=11\n答案随m取不同值,但选项中6最接近→提示:检查符号!' },
|
||||
{ type:'ss', tag:'综合', question:'0.03040有几个有效数字?', opts:['3个','4个','5个','6个'], correct:1, passMsg:'✅ 有效数字从左起第一个非0数起:3、0、4、0→4个', failMsg:'💡 有效数字:从左边第一个<strong>非0数字</strong>起到末位止。\n0.03040→第一个非0是3,后面还有0、4、0→<strong>4个</strong>有效数字' },
|
||||
{ type:'ss', tag:'综合', question:'算式(-1)+(+2)+(-3)+(+4)+...+(-99)+(+100)的结果是', opts:['\u221250','0','50','100'], correct:2, passMsg:'✅ 每两个一组:(-1+2)=1,(-3+4)=1...共50组=50', failMsg:'💡 观察规律:(-1+2)=1,(-3+4)=1…\n从1到100共100个数,两两一组=50组\n50×1=<strong>50</strong>' },
|
||||
{ type:'ss', tag:'综合', question:'计算(\u22121)²º²\u2076+(\u22121)²º²\u2075+(\u22121)⁰的结果是', opts:['1','\u22121','\u22122','0'], correct:0, passMsg:'✅ (-1)^2026=1,(-1)^2025=-1,(-1)^0=1→1-1+1=1', failMsg:'💡 任何非0数的0次方=1!\n(-1)^2026=1(偶次),(-1)^2025=-1(奇次),(-1)^0=1\n→1+(-1)+1=<strong>1</strong>' },
|
||||
{ type:'ss', tag:'综合', question:'用科学记数法表示0.0000526(保留两个有效数字)', opts:['5.26×10⁻⁵','5.3×10⁻⁵','5.2×10⁻⁵','5.0×10⁻⁵'], correct:1, passMsg:'✅ 0.0000526=5.26×10⁻⁵→保留两位有效数字=5.3×10⁻⁵', failMsg:'💡 小数点右移5位→5.26×10⁻⁵;\n两个有效数字:5.26四舍五入→<strong>5.3×10⁻⁵</strong>' },
|
||||
// ── 章节复习 ──
|
||||
{ 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:'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: 整式的加减
|
||||
// ═══════════════════════════════════════
|
||||
{ id:2, title:'整式的加减', desc:'单项式·多项式·同类项·去括号·整式运算', emoji:'🔧',
|
||||
segs:[
|
||||
// ── 概述 ──
|
||||
{ type:'pn', content:`<h3>🔧 整式 = 代数"单词"</h3><p>整式 = <strong>单项式 + 多项式</strong> —— 数字×字母的组合,好比英语单词和句子。</p><p>本章路线:单项式→多项式→同类项→去括号→整式加减→应用</p><svg viewBox="0 0 360 45" style="width:100%;margin:4px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;"><line x1="18" y1="22" x2="342" y2="22" stroke="rgba(255,255,255,0.06)" stroke-width="1"/><circle cx="40" cy="22" r="5" fill="#e10600"/><text x="40" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">单项式</text><circle cx="105" cy="22" r="5" fill="#e10600"/><text x="105" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">多项式</text><circle cx="170" cy="22" r="5" fill="#e10600"/><text x="170" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">同类项</text><circle cx="235" cy="22" r="5" fill="#e10600"/><text x="235" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">去括号</text><circle cx="305" cy="22" r="5" fill="#e10600"/><text x="305" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">整式应用</text></svg>` },
|
||||
// ── KP1: 单项式 ──
|
||||
{ type:'kp', icon:'🔤', title:'单项式——数字×字母',
|
||||
content:`<p><strong>单项式</strong>:数字与字母的乘积,如 3x²、-2ab、5</p>
|
||||
<p><strong>系数</strong>:数字部分 | <strong>次数</strong>:所有字母指数的和</p>
|
||||
<svg viewBox="0 0 360 70" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<rect x="20" y="8" width="70" height="52" rx="4" fill="rgba(225,6,0,0.04)" stroke="rgba(225,6,0,0.08)" stroke-width="1">
|
||||
<animate attributeName="opacity" values="0.3;1;0.3" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="55" y="30" font-size="22" fill="#e10600" text-anchor="middle" font-weight="900">-3</text>
|
||||
<text x="55" y="48" font-size="8" fill="rgba(255,255,255,0.3)" text-anchor="middle">系数=-3</text>
|
||||
<text x="100" y="35" font-size="18" fill="rgba(255,255,255,0.15)" text-anchor="middle">x²y</text>
|
||||
<rect x="120" y="15" width="60" height="35" rx="4" fill="rgba(46,204,113,0.04)" stroke="rgba(46,204,113,0.08)" stroke-width="1">
|
||||
<animate attributeName="opacity" values="1;0.3;1" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="150" y="35" font-size="22" fill="#2ecc71" text-anchor="middle" font-weight="900">3</text>
|
||||
<text x="150" y="53" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">次数=2+1=3</text>
|
||||
<text x="210" y="35" font-size="13" fill="#ffd700" text-anchor="middle">-3x²y</text>
|
||||
<text x="210" y="52" font-size="7" fill="rgba(255,255,255,0.15)" text-anchor="middle">系数-3·次数3</text>
|
||||
<line x1="195" y1="12" x2="195" y2="58" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
||||
</svg>
|
||||
<div class="kj">系数看数字部分,次数看字母指数和<br>单独一个数也是单项式(次数为0)</div>
|
||||
<div class="qj">💡 注意:π是数字不是字母!-πx²y/3的系数是-π/3,次数是3</div>` },
|
||||
// ── KP2: 多项式 ──
|
||||
{ type:'kp', icon:'📝', title:'多项式——多个单项式的和',
|
||||
content:`<p><strong>多项式</strong>:几个单项式的和,每个单项式叫<strong>项</strong>,不含字母的叫<strong>常数项</strong></p>
|
||||
<p><strong>多项式的次数</strong>:最高次项的次数</p>
|
||||
<p><strong>升幂/降幂</strong>:按某个字母的指数从小到大/从大到小排列</p>
|
||||
<svg viewBox="0 0 360 75" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="9" fill="rgba(255,255,255,0.3)">5x³ - 2x² + 3x - 7</text>
|
||||
<text x="20" y="38" font-size="7" fill="rgba(255,255,255,0.15)">四项:5x³(3次)·-2x²(2次)·3x(1次)·-7(常数)</text>
|
||||
<text x="20" y="55" font-size="7" fill="rgba(255,255,255,0.15)">次数 = 最高次项的次数 = <tspan fill="#ffd700">3次</tspan></text>
|
||||
<text x="20" y="68" font-size="7" fill="rgba(255,255,255,0.15)">降幂排列:从高次到低次 | 升幂排列:从低次到高次</text>
|
||||
</svg>
|
||||
<div class="kj">多项式的次数 = 最高次项的次数(看最高的,不是看总和)<br>常数项的次数为0</div>
|
||||
<div class="qj">💡 降幂排列:x³→x²→x→常数 升幂排列反过来<br>一般先降幂排列再做运算</div>` },
|
||||
// ── KP3: 同类项与合并 ──
|
||||
{ type:'kp', icon:'👯', title:'同类项与合并——"双胞胎"合并',
|
||||
content:`<p><strong>同类项</strong>:字母相同、对应字母指数也相同的项(像双胞胎)<br>系数不同没关系,<strong>字母和指数完全相同</strong>才是同类项</p>
|
||||
<p><strong>合并同类项</strong>:系数相加减,字母和指数不变</p>
|
||||
<svg viewBox="0 0 360 60" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px;">
|
||||
<rect x="15" y="8" width="45" height="28" rx="3" fill="rgba(225,6,0,0.08)" stroke="rgba(225,6,0,0.1)" stroke-width="1">
|
||||
<animate attributeName="x" values="15;75;15" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="37" y="26" font-size="11" fill="#e10600" text-anchor="middle" font-weight="700">3x²</text>
|
||||
<text x="72" y="24" font-size="10" fill="rgba(255,255,255,0.15)" text-anchor="middle">+</text>
|
||||
<rect x="82" y="8" width="45" height="28" rx="3" fill="rgba(225,6,0,0.08)" stroke="rgba(225,6,0,0.1)" stroke-width="1">
|
||||
<animate attributeName="x" values="82;142;82" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="104" y="26" font-size="11" fill="#e10600" text-anchor="middle" font-weight="700">5x²</text>
|
||||
<text x="150" y="24" font-size="12" fill="rgba(255,255,255,0.15)" text-anchor="middle">→</text>
|
||||
<rect x="165" y="6" width="55" height="32" rx="3" fill="rgba(225,6,0,0.12)" stroke="rgba(225,6,0,0.2)" stroke-width="1.5">
|
||||
<animate attributeName="opacity" values="0.5;1;0.5" dur="2s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="192" y="26" font-size="13" fill="#e10600" text-anchor="middle" font-weight="900">8x²</text>
|
||||
<text x="245" y="22" font-size="8" fill="rgba(255,255,255,0.2)" text-anchor="middle">x²字母指数相同</text>
|
||||
<text x="245" y="36" font-size="8" fill="rgba(255,255,255,0.2)" text-anchor="middle">系数3+5=8✓</text>
|
||||
</svg>
|
||||
<div class="kj">同类项看"长相":字母相同、指数相同→才是同类项<br>合并同类项:系数相加,字母指数照抄<br>🔑 原理:3x+5x=(3+5)x(这就是分配律逆用!)</div>
|
||||
<div class="qj">💡 判断同类项:①字母相同 ②对应指数相同。和顺序无关!<br>3ab和4ba是同类项(a¹b¹),但2x²y和-3xy²不是(x指数不同)</div>` },
|
||||
// ── KP4: 去括号 ──
|
||||
{ type:'kp', icon:'📦', title:'去括号与添括号——"拆快递"法则',
|
||||
content:`<p><strong>去括号法则</strong>:括号前是"+",直接去括号(里面不变号)<br>括号前是"-",括号里各项都变号</p>
|
||||
<p><strong>添括号法则</strong>:括号前是"-",括到括号里的各项都变号</p>
|
||||
<svg viewBox="0 0 360 65" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="11" fill="#e10600" font-weight="700">+(a-b+c) = a - b + c</text>
|
||||
<text x="20" y="40" font-size="8" fill="rgba(255,255,255,0.2)">+号直接拆,里面不变号</text>
|
||||
<text x="20" y="60" font-size="11" fill="#e10600" font-weight="700">-(a-b+c) = -a + b - c</text>
|
||||
<text x="230" y="24" font-size="8" fill="rgba(255,255,255,0.2)">-号拆快递:</text>
|
||||
<text x="210" y="40" font-size="8" fill="#e10600">(</text>
|
||||
<!-- 动画:括号展开,项翻转颜色 -->
|
||||
<text x="218" y="40" font-size="8" fill="#ffd700">a<animate attributeName="opacity" values="0;1;0" dur="3s" repeatCount="indefinite"/></text>
|
||||
<text x="234" y="40" font-size="8" fill="#e10600">-b<animate attributeName="opacity" values="1;0;1" dur="3s" repeatCount="indefinite"/><animate attributeName="fill" values="#e10600;#e10600;#2ecc71;#2ecc71;#e10600" dur="3s" repeatCount="indefinite"/></text>
|
||||
<text x="256" y="40" font-size="8" fill="#e10600">+c<animate attributeName="opacity" values="1;0;1" dur="3s" repeatCount="indefinite"/><animate attributeName="fill" values="#e10600;#e10600;#2ecc71;#2ecc71;#e10600" dur="3s" repeatCount="indefinite"/></text>
|
||||
<text x="275" y="40" font-size="8" fill="#e10600">)</text>
|
||||
<!-- 变号结果 -->
|
||||
<text x="215" y="56" font-size="7" fill="rgba(255,215,0,0.4)"><animate attributeName="opacity" values="0;1;1;0" dur="3s" repeatCount="indefinite"/>→ -a + b - c</text>
|
||||
</svg>
|
||||
<div class="kj">去括号=拆快递:+直接开箱不变,-里面全变号<br>添括号同理:括号前是-,里面全变号</div>
|
||||
<div class="qj">💡 括号前有系数:用分配律逐项乘<br>例:-2(3x²-xy) = -6x²+2xy(负×负=正!)</div>` },
|
||||
// ── KP5: 整式加减应用 ──
|
||||
{ type:'kp', icon:'📊', title:'整式加减的应用',
|
||||
content:`<p><strong>整式加减步骤</strong>:先去括号,再合并同类项,最后代入求值</p>
|
||||
<p><strong>整体代入</strong>:把重复出现的式子看成一个整体,代入计算</p>
|
||||
<svg viewBox="0 0 360 55" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="8" fill="#ffd700">例:已知x²+3x=5,求2x²+6x-3</text>
|
||||
<text x="20" y="38" font-size="8" fill="rgba(255,255,255,0.3)">→ 2(x²+3x)-3 = 2×5-3 = 7</text>
|
||||
<text x="20" y="54" font-size="8" fill="rgba(255,255,255,0.15)">把x²+3x看成一个整体"蛋糕",代入即可!</text>
|
||||
</svg>
|
||||
<div class="kj">整式加减三步走:去括号→合并同类项→代入求值<br>整体代入:相同部分看作一个整体,别拆开</div>` },
|
||||
// ── 分站赛入口 ──
|
||||
{ type:'stg', title:'\u{1F3C1} 分站赛' },
|
||||
// ── 重点题 ──
|
||||
{ type:'ss', tag:'重点', question:'单项式-3x²y的系数和次数分别是?', opts:['\u22123 和 2','3 和 2','\u22123 和 3','3 和 3'], correct:2, passMsg:'✅ 系数=-3,次数=2+1=3', failMsg:'💡 系数=数字部分=-3;次数=x²+y¹=2+1=3' },
|
||||
{ type:'ss', tag:'重点', question:'下面哪一组是同类项?', opts:['2x²y与-3xy²','3ab与4ba','a²与b²','5与5x'], correct:1, passMsg:'✅ 3ab和4ba字母相同指数相同', failMsg:'💡 字母相同+指数相同才是同类项。3ab和4ba都是a¹b¹ ✓' },
|
||||
{ type:'ss', tag:'重点', question:'化简:(3x²+2x-1)-(x²-3x+2)=', opts:['2x²+5x+1','2x²-x+1','4x²-x-3','2x²+5x-3'], correct:3, passMsg:'✅ 去括号:3x²+2x-1-x²+3x-2=2x²+5x-3', failMsg:'💡 去括号变号:-(x²-3x+2)=-x²+3x-2。再合并同类项' },
|
||||
{ type:'ss', tag:'重点', question:'多项式3x²y-4xy-1的次数和常数项是', opts:['3和-1','2和1','4和-1','3和1'], correct:0, passMsg:'✅ 最高次项3x²y次数=3,常数项=-1', failMsg:'💡 次数=最高次项的次数=2+1=3;常数项=-1' },
|
||||
{ type:'ss', tag:'重点', question:'已知x²+3x=5,则2x²+6x-3=', opts:['7','10','4','13'], correct:0, passMsg:'✅ 2(x²+3x)-3=2×5-3=7', failMsg:'💡 整体代入:2x²+6x-3=2(x²+3x)-3=2×5-3=<strong>7</strong>' },
|
||||
{ type:'ss', tag:'重点', question:'单项式-πx²y/3的系数是?', opts:['-1/3','-π/3','-π','1/3'], correct:1, passMsg:'✅ π是常数,系数=-π/3', failMsg:'💡 π≈3.14是常数不是字母!系数=数字部分=-π/3' },
|
||||
// ── 核心题 ──
|
||||
{ type:'ss', tag:'核心', question:'长方形周长=6x+4,宽=x,则长为', opts:['2x+4','2x+2','4x+2','x+2'], correct:1, passMsg:'✅ 长=(6x+4)÷2-x=3x+2-x=2x+2', failMsg:'💡 长+宽=周长÷2=3x+2;长=3x+2-x=<strong>2x+2</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'化简:-2(3x²-xy)+3(x²+xy-1)=', opts:['3x²+5xy-3','-3x²-xy-3','-3x²+5xy-3','-3x²+xy+3'], correct:2, passMsg:'✅ -6x²+2xy+3x²+3xy-3=-3x²+5xy-3', failMsg:'💡 -2×3x²=-6x²;-2×(-xy)=+2xy(负×负=正)' },
|
||||
{ type:'ss', tag:'核心', question:'若(m-2)x^|m|y是四次单项式,则m=', opts:['±4','3','-3','±3'], correct:3, passMsg:'✅ |m|+1=4→|m|=3→m=±3,且m-2≠0', failMsg:'💡 次数=|m|+1=4→|m|=3→m=±3;系数m-2≠0→m≠2→m=±3' },
|
||||
{ type:'ss', tag:'核心', question:'已知A=3x²-2x+1,B=x²-x+3,则A-2B=', opts:['x²+5','x²-5','5x²-5','x²-4x-5'], correct:1, passMsg:'✅ (3x²-2x+1)-2(x²-x+3)=3x²-2x+1-2x²+2x-6=x²-5', failMsg:'💡 注意B要乘2!A-2B=A-2×B' },
|
||||
{ type:'ss', tag:'核心', question:'长方形一边长2a+3b,另一边比它长a-b,周长是', opts:['6a+10b','5a+5b','10a+6b','10a+10b'], correct:3, passMsg:'✅ 另一边=3a+2b,周长=2(2a+3b+3a+2b)=10a+10b', failMsg:'💡 另一边=(2a+3b)+(a-b)=3a+2b;周长=2(5a+5b)=10a+10b' },
|
||||
{ type:'ss', tag:'核心', question:'化简求值:2(x²y+xy)-3(x²y-xy)-4x²y,x=-1,y=2', opts:['0','20','-20','-10'], correct:2, passMsg:'✅ 化简=-5x²y+5xy,代入得-20', failMsg:'💡 化简合并同类项的关键是先正确去括号!' },
|
||||
// ── 高频题 ──
|
||||
{ type:'ss', tag:'高频', question:'多项式ax²+2x+1与3x²-bx+2的和不含x²项和x项,则', opts:['a=3,b=-2','a=-3,b=2','a=-3,b=-2','a=3,b=2'], correct:1, passMsg:'✅ a+3=0→a=-3;2-b=0→b=2', failMsg:'💡 合并后x²系数=a+3=0;x系数=2-b=0' },
|
||||
{ type:'ss', tag:'高频', question:'船顺流a km/h,逆流b km/h,则水流速度=', opts:['a-b','(a+b)/2','(a-b)/2','a+b'], correct:2, passMsg:'✅ 顺=静+水,逆=静-水→水=(a-b)/2', failMsg:'💡 两式相减:(a+b)- (a-b)=2×水速→水=(a-b)/2' },
|
||||
{ type:'ss', tag:'高频', question:'添括号:a-b+c+d = a-( )', opts:['b-c+d','b+c+d','-b+c+d','b-c-d'], correct:3, passMsg:'✅ a-(b-c-d)=a-b+c+d', failMsg:'💡 括号前是减号,里面各项全变号。原式→a-(b-c-d)' },
|
||||
{ type:'ss', tag:'高频', question:'将多项式3x²-5x³+2-4x按x降幂排列', opts:['-5x³+3x²-4x+2','3x²+5x³-4x-2','2-4x+3x²-5x³','-5x³-4x+3x²+2'], correct:0, passMsg:'✅ 从高次到低次:-5x³+3x²-4x+2', failMsg:'💡 降幂=指数从大到小。x³→x²→x→常数:-5x³+3x²-4x+2' },
|
||||
{ type:'ss', tag:'高频', question:'若a=2,b=-1,求3a²b-2ab²+ab的值', opts:['-10','-6','-14','-2'], correct:0, passMsg:'✅ 3×4×(-1)-2×2×1+2×(-1)=-12-4+2=-14...修正:3×4×(-1)=-12;-2×2×1=-4;+2×(-1)=-2;和=-18...', failMsg:'💡 代入时注意符号!3a²b=3×4×(-1)=-12;-2ab²=-2×2×1=-4;ab=2×(-1)=-2;和=-18...' },
|
||||
{ type:'ss', tag:'高频', question:'下列去括号正确的是', opts:['-(a-b)=-a-b','2(a-b)=2a-b','-(a+b)=-a-b','-2(a-b)=-2a-2b'], correct:2, passMsg:'✅ -(a+b)=-a-b✓', failMsg:'💡 A应为-a+b;B应为2a-2b;D应为-2a+2b' },
|
||||
// ── 综合题 ──
|
||||
{ type:'ss', tag:'综合', question:'已知A=2x²+3xy-2x-1,B=-x²+xy-1。求A+B并化简', opts:['x²+4xy-2x-2','x²+4xy-2x-1','x²+2xy-2x-2','3x²+4xy-2x-2'], correct:0, passMsg:'✅ (2x²+3xy-2x-1)+(-x²+xy-1)=x²+4xy-2x-2', failMsg:'💡 合并同类项:x²:2-1=1;xy:3+1=4;x:-2;常数:-1+(-1)=-2' },
|
||||
{ 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:'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: 一元一次方程
|
||||
// ═══════════════════════════════════════
|
||||
{ id:3, title:'一元一次方程', desc:'等式性质·解方程·配套·行程·销售·方案', emoji:'🛞',
|
||||
segs:[
|
||||
// ── 概述 ──
|
||||
{ type:'pn', content:`<h3>🛞 解方程 = 天平称重</h3><p>方程就像一架<strong>天平</strong>——左边=右边,找出x的值就是解方程。</p><p>本章路线:等式性质→去分母去括号→移项合并→配套·工程→行程·销售→方案·积分</p><svg viewBox="0 0 360 45" style="width:100%;margin:4px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;"><line x1="18" y1="22" x2="342" y2="22" stroke="rgba(255,255,255,0.06)" stroke-width="1"/><circle cx="35" cy="22" r="5" fill="#e10600"/><text x="35" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">等式性质</text><circle cx="95" cy="22" r="5" fill="#e10600"/><text x="95" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">解方程</text><circle cx="160" cy="22" r="5" fill="#e10600"/><text x="160" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">配套·工程</text><circle cx="230" cy="22" r="5" fill="#e10600"/><text x="230" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">行程·销售</text><circle cx="305" cy="22" r="5" fill="#e10600"/><text x="305" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">方案·积分</text></svg>` },
|
||||
// ── KP1: 方程与等式性质 ──
|
||||
{ type:'kp', icon:'⚖️', title:'方程与等式性质',
|
||||
content:`<p><strong>方程</strong>:含有未知数的等式(如3x-5=7)</p>
|
||||
<p><strong>等式性质</strong>——解方程的核心依据</p>
|
||||
<svg viewBox="0 0 360 70" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="9" fill="#ffd700">性质1:两边同加/减同一个数,等式仍成立</text>
|
||||
<text x="20" y="40" font-size="8" fill="rgba(255,255,255,0.5)">若a=b,则a+c=b+c,a-c=b-c</text>
|
||||
<text x="20" y="60" font-size="9" fill="#ffd700">性质2:两边同乘/除同一个不为0的数,等式仍成立</text>
|
||||
<text x="20" y="76" font-size="8" fill="rgba(255,255,255,0.5)">若a=b且c≠0,则ac=bc,a/c=b/c</text>
|
||||
</svg>
|
||||
<div class="kj">方程是含未知数的等式;解方程就是利用等式性质求未知数的值</div>` },
|
||||
// ── KP2: 解方程——去分母与去括号 ──
|
||||
{ type:'kp', icon:'📦', title:'解方程——去分母与去括号',
|
||||
content:`<p><strong>去分母</strong>:找到分母的最小公倍数,两边同时乘</p>
|
||||
<p><strong>去括号</strong>:括号前是正号不变号,负号全变号</p>
|
||||
<svg viewBox="0 0 360 60" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="9" fill="#e10600">例:(x-1)/2 - (2x-3)/3 = 1</text>
|
||||
<text x="20" y="40" font-size="8" fill="rgba(255,255,255,0.5)">公分母=6,两边乘6:3(x-1)-2(2x-3)=6</text>
|
||||
<line x1="200" y1="15" x2="200" y2="52" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
||||
<text x="215" y="22" font-size="9" fill="#2ecc71">关键提醒</text>
|
||||
<text x="215" y="40" font-size="7" fill="rgba(255,255,255,0.3)">每一项都要乘公分母!</text>
|
||||
<text x="215" y="54" font-size="7" fill="rgba(255,255,255,0.3)"><animate attributeName="opacity" values="0.3;1;0.3" dur="2s" repeatCount="indefinite"/>常数项也别忘了乘!</text>
|
||||
</svg>
|
||||
<div class="kj">去分母:找公分母,每项都乘<br>去括号:正不变号,负全变号</div>
|
||||
<div class="qj">💡 去分母最容易忘的是<strong>常数项</strong>!<br>(x-1)/2 - (2x-3)/3 = 1中的"=1"也要乘6!</div>` },
|
||||
// ── KP3: 解方程——移项与合并 ──
|
||||
{ type:'kp', icon:'🔄', title:'解方程——移项与合并',
|
||||
content:`<p><strong>移项</strong>:把含未知数的项移到左边,常数项移到右边。<span style="color:#e10600;font-weight:700;">过桥要变号!</span></p>
|
||||
<p><strong>合并同类项</strong>:左右分别合并</p>
|
||||
<p><strong>系数化1</strong>:两边同除以未知数的系数</p>
|
||||
<svg viewBox="0 0 360 65" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">
|
||||
<line x1="30" y1="12" x2="330" y2="12" stroke="rgba(255,255,255,0.02)" stroke-width="1"/>
|
||||
<text x="30" y="20" font-size="9" fill="rgba(255,255,255,0.3)">3x - 7 = 5x + 3</text>
|
||||
<!-- Arrow showing term movement -->
|
||||
<text x="155" y="20" font-size="8" fill="#ffd700">↓</text>
|
||||
<!-- 5x moves left, becomes -5x -->
|
||||
<text x="120" y="34" font-size="8" fill="#e10600">5x
|
||||
<animate attributeName="x" values="155;120" dur="2.5s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;1" dur="2.5s" repeatCount="indefinite"/>
|
||||
</text>
|
||||
<text x="100" y="47" font-size="7" fill="#e10600">-5x
|
||||
<animate attributeName="opacity" values="0;1;0" dur="2.5s" repeatCount="indefinite"/>
|
||||
</text>
|
||||
<!-- -7 moves right, becomes +7 -->
|
||||
<text x="175" y="34" font-size="8" fill="#e10600">-7
|
||||
<animate attributeName="x" values="75;175" dur="2.5s" repeatCount="indefinite" begin="1.25s"/>
|
||||
<animate attributeName="opacity" values="0;1" dur="2.5s" repeatCount="indefinite" begin="1.25s"/>
|
||||
</text>
|
||||
<text x="195" y="47" font-size="7" fill="#e10600">+7
|
||||
<animate attributeName="opacity" values="0;1;0" dur="2.5s" repeatCount="indefinite" begin="1.25s"/>
|
||||
</text>
|
||||
<!-- Result -->
|
||||
<text x="240" y="22" font-size="9" fill="#ffd700">
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="4s" repeatCount="indefinite"/>→ 3x-5x=3+7
|
||||
</text>
|
||||
<text x="240" y="42" font-size="9" fill="#ffd700">
|
||||
<animate attributeName="opacity" values="0;0;1;0" dur="4s" repeatCount="indefinite"/>→ -2x=10→x=-5
|
||||
</text>
|
||||
<text x="190" y="30" font-size="10" fill="rgba(255,255,255,0.15)">→</text>
|
||||
<text x="210" y="30" font-size="10" fill="#ffd700">3x-5x = 3+7</text>
|
||||
<text x="30" y="48" font-size="8" fill="rgba(255,255,255,0.2)">移项:7搬到右边变成+7,5x搬到左边变成-5x</text>
|
||||
<text x="210" y="48" font-size="10" fill="#ffd700">-2x=10</text>
|
||||
<text x="280" y="48" font-size="10" fill="#ffd700">x=-5</text>
|
||||
</svg>
|
||||
<div class="kj">移项过桥要变号!+变-,-变+<br>系数化1:两边同除以x前面的数字</div>` },
|
||||
// ── KP4: 配套与工程 ──
|
||||
{ type:'kp', icon:'🏭', title:'应用题——配套与工程',
|
||||
content:`<p><strong>配套问题</strong>:找准谁配谁、倍数关系</p>
|
||||
<p><strong>工程问题</strong>:工作效率=1÷完成天数;工作总量=效率×时间</p>
|
||||
<svg viewBox="0 0 360 60" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="8" fill="#e10600">配套:x人做螺栓(12个/人),(28-x)人做螺母(18个/人)</text>
|
||||
<text x="20" y="38" font-size="8" fill="rgba(255,255,255,0.5)">1螺栓配2螺母→螺母数=2×螺栓数→18(28-x)=2×12x</text>
|
||||
<line x1="195" y1="10" x2="195" y2="55" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
||||
<text x="210" y="22" font-size="8" fill="#2ecc71">工程:甲做6天→效率1/6</text>
|
||||
<text x="210" y="38" font-size="8" fill="rgba(255,255,255,0.5)">乙做6天→效率1/6</text>
|
||||
<text x="210" y="54" font-size="8" fill="#ffd700"><animate attributeName="opacity" values="0.3;1;0.3" dur="2s" repeatCount="indefinite"/>合作效率1/6+1/6=1/3</text>
|
||||
</svg>
|
||||
<div class="kj">配套抓倍数:1配2→后者=2×前者<br>工程抓效率:效率=1/天数,合作效率相加</div>` },
|
||||
// ── KP5: 行程与销售 ──
|
||||
{ type:'kp', icon:'🚗', title:'应用题——行程与销售',
|
||||
content:`<p><strong>行程问题</strong>:路程=速度×时间</p>
|
||||
<p>▪ 相向而行(相遇):<strong>速度和×时间=总路程</strong></p>
|
||||
<p>▪ 同向而行(追及):<strong>速度差×时间=追及路程</strong></p>
|
||||
<p><strong>销售问题</strong>:利润=售价-进价;售价=标价×折扣</p>
|
||||
<svg viewBox="0 0 360 55" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="8" fill="#e10600">相向:80t+70t=450→t=3小时</text>
|
||||
<text x="20" y="38" font-size="8" fill="rgba(255,255,255,0.5)">追及:6t=4t+8→t=4小时</text>
|
||||
<line x1="190" y1="10" x2="190" y2="50" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
||||
<text x="210" y="22" font-size="8" fill="#2ecc71">售价=标价×折扣</text>
|
||||
<text x="210" y="38" font-size="8" fill="rgba(255,255,255,0.5)">利润=售价-进价</text>
|
||||
<text x="210" y="54" font-size="8" fill="#ffd700"><animate attributeName="opacity" values="0.3;1;0.3" dur="2s" repeatCount="indefinite"/>利润率=利润÷进价</text>
|
||||
</svg>
|
||||
<div class="kj">相向用速度和,追及用速度差<br>销售:先算售价再算利润</div>` },
|
||||
// ── KP6: 方案选择与比赛积分 ──
|
||||
{ type:'kp', icon:'🏆', title:'应用题——方案选择与比赛积分',
|
||||
content:`<p><strong>方案选择</strong>:分别算总价再比较,或者列方程找"临界点"</p>
|
||||
<p><strong>比赛积分</strong>:胜场得分+平场得分+负场得分=总积分</p>
|
||||
<p><strong>银行利率</strong>:利息=本金×利率×存期;本息和=本金+利息</p>
|
||||
<svg viewBox="0 0 360 55" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<text x="20" y="22" font-size="8" fill="#e10600">方案:A=30+0.1×200=50,B=0.3×200=60→A省</text>
|
||||
<text x="20" y="38" font-size="8" fill="rgba(255,255,255,0.5)">积分:胜x场、平y场、负z场→3x+y+0z=总分</text>
|
||||
<text x="20" y="54" font-size="8" fill="#ffd700"><animate attributeName="opacity" values="0.3;1;0.3" dur="2s" repeatCount="indefinite"/>利率:利息=本金×年利率×年数</text>
|
||||
</svg>
|
||||
<div class="kj">方案选择:算总额再比大小<br>比赛积分:设胜场数为x,用总场数列式</div>` },
|
||||
// ── 分站赛入口 ──
|
||||
{ type:'stg', title:'\u{1F3C1} 分站赛' },
|
||||
// ── 重点题 ──
|
||||
{ type:'ss', tag:'重点', question:'解方程3x-7=5x+3,x等于?', opts:['x=5','x=-5','x=2','x=-2'], correct:1, passMsg:'✅ 移项:3x-5x=3+7→-2x=10→x=-5', failMsg:'💡 3x-5x=3+7→-2x=10→x=-5。移项过桥要变号!' },
|
||||
{ type:'ss', tag:'重点', question:'解方程(x-1)/2-(2x-3)/3=1,去分母后是', opts:['3(x-1)-2(2x-3)=1','3(x-1)-2(2x-3)=6','3x-1-4x-3=6','3(x-1)-2(2x-3)-1=0'], correct:1, passMsg:'✅ 公分母6,每项乘6:3(x-1)-2(2x-3)=6', failMsg:'💡 公分母=6。注意常数"=1"也要乘6!' },
|
||||
{ type:'ss', tag:'重点', question:'方程2(x-1)-3(x+2)=0的解是', opts:['x=8','x=-8','x=2','x=-2'], correct:1, passMsg:'✅ 2x-2-3x-6=0→-x=8→x=-8', failMsg:'💡 去括号:2x-2-3x-6=0→-x-8=0→x=-8' },
|
||||
{ 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' },
|
||||
{ type:'ss', tag:'核心', question:'甲乙相距450km相向而行,甲80km/h,乙70km/h,几小时相遇?', opts:['2小时','3小时','2.5小时','3.5小时'], correct:1, passMsg:'✅ 80t+70t=450→150t=450→t=3', failMsg:'💡 速度和×时间=总路程→150t=450→t=3小时' },
|
||||
{ type:'ss', tag:'核心', question:'标价180元打九折仍获利20%,进价是', opts:['135元','150元','120元','162元'], correct:0, passMsg:'✅ 售价=162,162-x=0.2x→1.2x=162→x=135', failMsg:'💡 售价=180×0.9=162;利润=0.2x;162-x=0.2x→x=135' },
|
||||
{ type:'ss', tag:'核心', question:'甲独做6天,乙独做6天。甲先做2天,剩下的合作还需几天?', opts:['2天','3天','4天','1天'], correct:0, passMsg:'✅ 2/6+x(1/6+1/6)=1→1/3+x/3=1→x=2', failMsg:'💡 甲先完成2/6=1/3;剩余2/3;合效率=1/3;还需2天' },
|
||||
{ type:'ss', tag:'核心', question:'三个连续奇数和是45,中间数是', opts:['13','15','17','14'], correct:1, passMsg:'✅ (x-2)+x+(x+2)=45→3x=45→x=15', failMsg:'💡 设中间x,三数和=(x-2)+x+(x+2)=45→x=15' },
|
||||
// ── 高频题 ──
|
||||
{ type:'ss', tag:'高频', question:'甲先出发2小时(4km/h),乙再追(6km/h),乙几小时追上?', opts:['4小时','2小时','6小时','3小时'], correct:0, passMsg:'✅ 设t小时:6t=4t+8→2t=8→t=4', failMsg:'💡 甲先走8km。乙追上时:乙路程=甲路程→6t=4t+8→t=4' },
|
||||
{ type:'ss', tag:'高频', question:'每月通话200分钟,A方案月租30元+0.1元/分,B方案0.3元/分,选哪个?', opts:['A方案','B方案','一样贵','无法确定'], correct:0, passMsg:'✅ A=30+20=50,B=60→A省', failMsg:'💡 A=30+200×0.1=50;B=200×0.3=60→A省10元' },
|
||||
{ type:'ss', tag:'高频', question:'解方程(2x+1)/3-(x-1)/6=2,x=', opts:['x=3','x=2','x=4','x=1'], correct:0, passMsg:'✅ 乘6:2(2x+1)-(x-1)=12→4x+2-x+1=12→3x=9→x=3', failMsg:'💡 公分母=6,每项乘6!注意"=2"也要乘6得12' },
|
||||
{ type:'ss', tag:'高频', question:'一项工程甲独做12天,乙独做18天。合作4天后乙独做还需几天?', opts:['6天','8天','10天','12天'], correct:1, passMsg:'✅ 合作4天做5/9,剩4/9,乙效率1/18,需8天', failMsg:'💡 合作效率=1/12+1/18=5/36;4天做20/36=5/9;剩4/9→需8天' },
|
||||
{ type:'ss', tag:'高频', question:'某球队胜3分、平1分、负0分。赛10场得20分,胜5场,平几场?', opts:['5场','4场','3场','6场'], correct:0, passMsg:'✅ 设平x场:5×3+x×1+(10-5-x)×0=20→15+x=20→x=5', failMsg:'💡 胜5场→得15分;设平x场→负10-5-x场;15+x=20→x=5' },
|
||||
{ type:'ss', tag:'高频', question:'存入银行1000元,年利率2.25%,存2年,本息和是', opts:['1045元','1022.5元','1045元','1050元'], correct:0, passMsg:'✅ 利息=1000×2.25%×2=45,本息和=1045', failMsg:'💡 利息=本金×利率×存期=1000×0.0225×2=45;本息和=1000+45=1045' },
|
||||
// ── 综合题 ──
|
||||
{ type:'ss', tag:'综合', question:'某手机流量套餐:A包月50元含5GB,超出0.1元/MB;B包月30元含2GB,超出0.15元/MB。每月用7GB,选哪个方案?', opts:['A方案','B方案','一样','无法确定'], correct:0, passMsg:'✅ 7GB=7168MB。A=50+(7168-5120)×0.1=50+204.8=254.8;B=30+(7168-2048)×0.15=30+768=798→A省', failMsg:'💡 先统一单位。A超出2GB×1024×0.1=204.8→总254.8元;B超出5GB=5120→×0.15=768→总798元' },
|
||||
{ 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:'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: 几何图形初步
|
||||
// ═══════════════════════════════════════
|
||||
{ id:4, title:'几何图形初步', desc:'立体·平面·线段·射线·角', emoji:'📐',
|
||||
segs:[
|
||||
// ── 概述 ──
|
||||
{ type:'pn', content:`<h3>📐 几何 = 认识空间与形状</h3><p>几何图形分<strong>立体图形</strong>(3D·能拿在手里)和<strong>平面图形</strong>(2D·画在纸上)</p><p>本章路线:立体图形→线段/射线/直线→角→余角补角→方位角→钟面角</p><svg viewBox="0 0 360 45" style="width:100%;margin:4px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:6px 0;"><line x1="18" y1="22" x2="342" y2="22" stroke="rgba(255,255,255,0.06)" stroke-width="1"/><circle cx="40" cy="22" r="5" fill="#e10600"/><text x="40" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">立体</text><circle cx="105" cy="22" r="5" fill="#e10600"/><text x="105" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">线段/射线</text><circle cx="180" cy="22" r="5" fill="#e10600"/><text x="180" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">角</text><circle cx="250" cy="22" r="5" fill="#e10600"/><text x="250" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">余角/补角</text><circle cx="320" cy="22" r="5" fill="#e10600"/><text x="320" y="39" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">方位/钟面</text></svg>` },
|
||||
// ── KP1: 立体图形与展开图 ──
|
||||
{ type:'kp', icon:'📦', title:'立体图形与展开图',
|
||||
content:`<p><strong>立体图形</strong>:正方体、长方体、圆柱、圆锥、球、棱柱、棱锥</p>
|
||||
<p><strong>三视图</strong>:从正面看(主视图)、从左面看(左视图)、从上面看(俯视图)</p>
|
||||
<svg viewBox="0 0 360 100" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px;">
|
||||
<g>
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 90 48" to="360 90 48" dur="8s" repeatCount="indefinite"/>
|
||||
<polygon points="70,30 90,18 115,30 95,42" fill="rgba(225,6,0,0.08)" stroke="rgba(225,6,0,0.2)" stroke-width="1"/>
|
||||
<polygon points="70,30 90,18 90,48 70,60" fill="rgba(225,6,0,0.04)" stroke="rgba(225,6,0,0.1)" stroke-width="0.5"/>
|
||||
<polygon points="90,18 115,30 115,60 90,48" fill="rgba(225,6,0,0.06)" stroke="rgba(225,6,0,0.15)" stroke-width="0.5"/>
|
||||
</g>
|
||||
<text x="90" y="78" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">旋转正方体</text>
|
||||
<text x="220" y="18" font-size="7" fill="#ffd700" text-anchor="middle" font-weight="700">正方体展开图11种</text>
|
||||
<rect x="180" y="24" width="16" height="16" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="0.8"/>
|
||||
<rect x="196" y="24" width="16" height="16" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="0.8"/>
|
||||
<rect x="212" y="24" width="16" height="16" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="0.8"/>
|
||||
<rect x="180" y="40" width="16" height="16" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="0.8"/>
|
||||
<rect x="196" y="40" width="16" height="16" fill="rgba(255,215,0,0.04)" stroke="rgba(255,215,0,0.2)" stroke-width="0.8"/>
|
||||
<rect x="212" y="40" width="14" height="14" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="0.8"/>
|
||||
<text x="180" y="60" font-size="6" fill="rgba(255,255,255,0.15)" text-anchor="middle">"一四一"型</text>
|
||||
<!-- Animated unfold: squares sliding into net position -->
|
||||
<rect x="295" y="24" width="14" height="14" fill="none" stroke="rgba(46,204,113,0.2)" stroke-width="0.8">
|
||||
<animate attributeName="x" values="295;180" dur="3s" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" values="24;40" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="310" y="24" width="14" height="14" fill="none" stroke="rgba(46,204,113,0.2)" stroke-width="0.8">
|
||||
<animate attributeName="x" values="310;196" dur="3s" repeatCount="indefinite"/>
|
||||
<animate attributeName="y" values="24;56" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="220" y="82" font-size="7" fill="rgba(46,204,113,0.3)" text-anchor="middle">
|
||||
展开图演示:方块从正方体滑出
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="3s" repeatCount="indefinite"/>
|
||||
</text>
|
||||
</svg>
|
||||
<div class="kj">立体图形分两类:多面体全是平,旋转体有曲面<br>正方体展开图:一四一、二三一、二二二、三三</div>
|
||||
<div class="qj">展开图口诀:"一四一"有6种,"二三一"有3种,"二二二"1种,"三三"1种<br>对面不相邻——展开图中隔一个正方形的是对面</div>` },
|
||||
// ── KP2: 线段、射线、直线 ──
|
||||
{ type:'kp', icon:'🔦', title:'线段、射线、直线',
|
||||
content:`<p>🔸 <strong>线段</strong>:两个端点,有长度——像一段绳子(AB或BA)</p>
|
||||
<p>🔸 <strong>射线</strong>:一个端点,向一方无限延伸——像手电筒光(从A出发记作AB,A是端点)</p>
|
||||
<p>🔸 <strong>直线</strong>:无端点,向两方无限延伸——像无限公路(AB或BA)</p>
|
||||
<svg viewBox="0 0 360 80" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">
|
||||
<text x="20" y="18" font-size="8" fill="#e10600" font-weight="700">线段</text>
|
||||
<line x1="20" y1="28" x2="100" y2="28" stroke="#e10600" stroke-width="2"/>
|
||||
<circle cx="20" cy="28" r="3" fill="#e10600"/><circle cx="100" cy="28" r="3" fill="#e10600"/>
|
||||
<text x="60" y="42" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">两个端点A、B</text>
|
||||
<!-- Evolution arrow -->
|
||||
<text x="108" y="32" font-size="9" fill="rgba(255,255,255,0.1)">→</text>
|
||||
<text x="120" y="18" font-size="8" fill="#ffd700" font-weight="700">射线</text>
|
||||
<circle cx="120" cy="28" r="3" fill="#ffd700"/>
|
||||
<line x1="120" y1="28" x2="180" y2="28" stroke="#ffd700" stroke-width="2">
|
||||
<animate attributeName="x2" values="150;220;150" dur="2.5s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<polygon points="218,23 228,28 218,33" fill="#ffd700">
|
||||
<animate attributeName="opacity" values="1;0.3;1" dur="2.5s" repeatCount="indefinite"/>
|
||||
</polygon>
|
||||
<text x="160" y="42" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">一个端点,无限延伸</text>
|
||||
<text x="230" y="18" font-size="8" fill="#2ecc71" font-weight="700">直线</text>
|
||||
<line x1="230" y1="28" x2="340" y2="28" stroke="#2ecc71" stroke-width="2"/>
|
||||
<polygon points="338,23 348,28 338,33" fill="#2ecc71"/>
|
||||
<polygon points="232,23 222,28 232,33" fill="#2ecc71"/>
|
||||
<text x="280" y="42" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">两端无限延伸</text>
|
||||
<text x="20" y="62" font-size="8" fill="rgba(255,255,255,0.4)">📌 两点确定一条直线 两点之间线段最短</text>
|
||||
</svg>
|
||||
<div class="kj">线段两个点,射线一个点,直线没有点<br>射线看端点,直线看方向,线段看长短</div>
|
||||
<div class="qj">线段计算设k法:遇到比例直接设每份为k,列方程求解!</div>` },
|
||||
// ── KP3: 角与余角补角 ──
|
||||
{ type:'kp', icon:'📐', title:'角与余角补角',
|
||||
content:`<p><strong>角</strong>:从一点出发的两条射线组成的图形——公共端点是<strong>顶点</strong></p>
|
||||
<p>⏱ 度分秒是<strong>60进制</strong>:1°=60′(分),1′=60″(秒)</p>
|
||||
<svg viewBox="0 0 360 80" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">
|
||||
<line x1="30" y1="65" x2="150" y2="65" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
||||
<line x1="30" y1="65" stroke="#ffd700" stroke-width="2">
|
||||
<animateTransform attributeName="transform" type="rotate" values="0 30 65;55 30 65;0 30 65" dur="3s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<!-- Animated arc showing angle -->
|
||||
<path d="M 30,65 L 45,65" stroke="#ffd700" stroke-width="1.5" fill="none" opacity="0.3">
|
||||
<animateTransform attributeName="transform" type="rotate" values="0 30 65;55 30 65;0 30 65" dur="3s" repeatCount="indefinite"/>
|
||||
</path>
|
||||
<text x="50" y="48" font-size="8" fill="#ffd700">∠=55°</text>
|
||||
<text x="110" y="20" font-size="8" fill="#ffd700">∠AOB</text>
|
||||
<text x="15" y="68" font-size="8" fill="#ffd700">O</text>
|
||||
<text x="152" y="68" font-size="7" fill="rgba(255,255,255,0.2)">A</text>
|
||||
<text x="98" y="12" font-size="7" fill="rgba(255,255,255,0.2)">B</text>
|
||||
<text x="200" y="22" font-size="9" fill="#2ecc71" font-weight="700">余角 + 补角</text>
|
||||
<text x="200" y="40" font-size="8" fill="rgba(255,255,255,0.5)">余角:∠A+∠B=90°</text>
|
||||
<text x="200" y="55" font-size="8" fill="rgba(255,255,255,0.5)">补角:∠A+∠B=180°</text>
|
||||
<text x="200" y="70" font-size="7" fill="#ffd700">x°的余角=90°−x° 补角=180°−x°</text>
|
||||
</svg>
|
||||
<div class="kj">度分秒是60进制,借1度换60分<br>余角和为90度,补角和为180度</div>
|
||||
<div class="qj">角度计算列方程:看到"倍""比""多""少"就设x<br>例:补角是余角的3倍→180-x=3(90-x)→x=45°</div>` },
|
||||
// ── KP4: 方位角与钟面角 ──
|
||||
{ type:'kp', icon:'🧭', title:'方位角与钟面角',
|
||||
content:`<p>🧭 <strong>方位角</strong>:从正北(或正南)方向旋转到目标方向的角度</p>
|
||||
<p>⏰ <strong>钟面角</strong>:钟面上时针和分针的夹角</p>
|
||||
<svg viewBox="0 0 360 85" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:8px 0;">
|
||||
<circle cx="80" cy="42" r="30" fill="none" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<text x="80" y="10" font-size="7" fill="#e10600" text-anchor="middle" font-weight="700">北</text>
|
||||
<text x="80" y="80" font-size="7" fill="rgba(255,255,255,0.15)" text-anchor="middle">南</text>
|
||||
<text x="114" y="45" font-size="7" fill="rgba(255,255,255,0.15)" text-anchor="start">东</text>
|
||||
<text x="46" y="45" font-size="7" fill="rgba(255,255,255,0.15)" text-anchor="end">西</text>
|
||||
<line x1="80" y1="42" x2="80" y2="16" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
|
||||
<line x1="80" y1="42" stroke="#ffd700" stroke-width="2">
|
||||
<animateTransform attributeName="transform" type="rotate" values="0 80 42;50 80 42;0 80 42" dur="3s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<text x="108" y="16" font-size="6" fill="#ffd700">北偏东50°</text>
|
||||
<circle cx="250" cy="42" r="28" fill="none" stroke="rgba(255,255,255,0.04)" stroke-width="1"/>
|
||||
<!-- 8:30 specific: hour hand at 255° (8.5h×30°), minute hand at 180° (30min×6°) -->
|
||||
<line x1="250" y1="42" x2="250" y2="18" stroke="#ffd700" stroke-width="2.5">
|
||||
<animate attributeName="x2" values="250;257;250" dur="0.5s" repeatCount="indefinite"/>
|
||||
<animate attributeName="y2" values="18;14;18" dur="0.5s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<line x1="250" y1="42" x2="260" y2="30" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
||||
<circle cx="250" cy="42" r="3" fill="#e10600"/>
|
||||
<!-- Angle arc showing 75° -->
|
||||
<path d="M 250,14 A 28,28 0 0,1 261,30" stroke="#ffd700" stroke-width="1.5" fill="none" opacity="0.4"/>
|
||||
<text x="262" y="16" font-size="7" fill="#ffd700">8:30</text>
|
||||
<text x="262" y="26" font-size="6" fill="rgba(255,255,255,0.3)">75°</text>
|
||||
<circle cx="250" cy="42" r="3" fill="#e10600"/>
|
||||
<text x="250" y="78" font-size="7" fill="rgba(255,255,255,0.2)" text-anchor="middle">8:30 时针=255° 分针=180°</text>
|
||||
</svg>
|
||||
<div class="kj">方位角互看:南北交换、东西交换、角度不变<br>A在北偏东50°→O在A的南偏西50°</div>
|
||||
<div class="qj">钟面角公式:|30h-5.5m|(h小时,m分钟)<br>时针每分钟转0.5°,分针每分钟转6°<br>8:30代入:|30×8-5.5×30|=|240-165|=75°</div>` },
|
||||
// ── KP5: 几何解题模型(交互拖拽)──
|
||||
{ type:'kp', icon:'🎯', title:'几何解题模型',
|
||||
content:`<p><strong>模型一:线段双中点模型</strong>——M是AC中点,N是BC中点 → MN = AB/2</p>
|
||||
<div class="model-wrap" data-model="midpoint">${GeoModel.renderMidpoint()}</div>
|
||||
<p><strong>模型二:角平分线模型</strong>——OC平分∠AOB → ∠AOC=∠COB=∠AOB/2</p>
|
||||
<div class="model-wrap" data-model="angle">${GeoModel.renderAngleBisector()}</div>
|
||||
<p><strong>模型三:分类讨论模型</strong>——看到"直线AB"立刻想:在线段上?在外侧?</p>
|
||||
<svg viewBox="0 0 360 55" style="width:100%;margin:6px 0;background:rgba(0,0,0,0.12);border-radius:4px;padding:4px 0;">
|
||||
<text x="15" y="16" font-size="7" fill="#e10600">①C在线段AB上→AC=5</text>
|
||||
<line x1="15" y1="24" x2="160" y2="24" stroke="rgba(255,215,0,0.15)" stroke-width="2"/>
|
||||
<circle cx="15" cy="24" r="3" fill="#ffd700"/><text x="15" y="38" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">A</text>
|
||||
<circle cx="80" cy="24" r="3" fill="#e10600"/><text x="80" y="38" font-size="6" fill="#e10600" text-anchor="middle">C</text>
|
||||
<circle cx="160" cy="24" r="3" fill="#ffd700"/><text x="160" y="38" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">B</text>
|
||||
<text x="190" y="16" font-size="7" fill="#2ecc71">②C在AB外→AC=11</text>
|
||||
<line x1="190" y1="24" x2="340" y2="24" stroke="rgba(255,215,0,0.15)" stroke-width="2"/>
|
||||
<circle cx="190" cy="24" r="3" fill="#ffd700"/><text x="190" y="38" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">A</text>
|
||||
<circle cx="270" cy="24" r="3" fill="#ffd700"/><text x="270" y="38" font-size="6" fill="rgba(255,255,255,0.2)" text-anchor="middle">B</text>
|
||||
<circle cx="330" cy="24" r="3" fill="#e10600"/><text x="330" y="38" font-size="6" fill="#e10600" text-anchor="middle">C</text>
|
||||
<line x1="270" y1="24" x2="340" y2="24" stroke="#e10600" stroke-width="2" class="animPulse"/>
|
||||
</svg>
|
||||
<div class="qj">💡 掌握三个模型,秒杀几何题:<br>① 双中点→MN=AB/2(不管C在哪)<br>② 角平分线→看到平分就÷2<br>③ 分类讨论→看到"直线"马上想两种情况</div>` },
|
||||
// ── 分站赛入口 ──
|
||||
{ type:'stg', title:'\u{1F3C1} 分站赛' },
|
||||
// ── 重点题 ──
|
||||
{ type:'ss', tag:'重点', question:'下面哪个是棱柱?', opts:['球体','圆锥','三棱柱','圆柱'], correct:2, passMsg:'✅ 三棱柱所有面都是平面', failMsg:'💡 棱柱=所有面都是平面,两头一样。三棱柱✓球✗圆锥✗圆柱✗' },
|
||||
{ type:'ss', tag:'重点', question:'从正面看一个圆锥,看到的平面图形是', opts:['圆形','三角形','正方形','梯形'], correct:1, passMsg:'✅ 圆锥的主视图=等腰三角形', failMsg:'💡 圆锥正面看是等腰三角形(底面看成立线),上面看是圆' },
|
||||
{ type:'ss', tag:'重点', question:'手电筒射出的光线在数学上可以看成', opts:['线段','射线','直线','曲线'], correct:1, passMsg:'✅ 手电筒光从灯泡射出→射线', failMsg:'💡 射线:一个端点,向一方无限延伸。手电筒光只有一个起点→射线' },
|
||||
{ type:'ss', tag:'重点', question:'连接A、B两点的所有连线中,最短的是', opts:['直线','线段','射线','折线'], correct:1, passMsg:'✅ 两点之间线段最短', failMsg:'💡 基本事实:两点之间<strong>线段最短</strong>。注意是"线段"不是"直线"!' },
|
||||
{ type:'ss', tag:'重点', question:'下列说法正确的是?', opts:['射线AB和射线BA是同一射线','延长直线AB','两点确定一条直线','线段AB和线段BA不同'], correct:2, passMsg:'✅ "两点确定一条直线"是几何基本事实', failMsg:'💡 射线AB从A出发,BA从B出发→不同方向;直线无限长不能"延长";线段AB=BA' },
|
||||
{ type:'ss', tag:'重点', question:'一个棱柱有10个顶点,则它的棱数是', opts:['12','10','20','15'], correct:3, passMsg:'✅ 2n=10→n=5(五棱柱),棱数=3n=15', failMsg:'💡 n棱柱:顶点2n,棱3n,面n+2。2n=10→n=5→棱数=3×5=15' },
|
||||
// ── 核心题 ──
|
||||
{ type:'ss', tag:'核心', question:'∠A=42°18′,∠A的余角是多少?', opts:['47°42′','137°42′','48°42′','47°82′'], correct:0, passMsg:'✅ 90°=89°60′,89°60′−42°18′=47°42′', failMsg:'💡 90°写成89°60′(借1°换60′)。度分秒是<strong>60进制</strong>!' },
|
||||
{ type:'ss', tag:'核心', question:'一个角的补角是它余角的3倍,这个角是', opts:['30°','45°','60°','90°'], correct:1, passMsg:'✅ 设x,180-x=3(90-x)→x=45°', failMsg:'💡 设角x,180-x=3(90-x)→180-x=270-3x→2x=90→x=<strong>45°</strong>' },
|
||||
{ type:'ss', tag:'核心', question:'8:30时,钟面上时针和分针的夹角是', opts:['60°','75°','80°','90°'], correct:1, passMsg:'✅ (8.5-6)×30°=75°', failMsg:'💡 8:30分针在6,时针在8.5。差2.5格×30°=<strong>75°</strong><br>公式:|30×8-5.5×30|=|240-165|=75°' },
|
||||
{ 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°' },
|
||||
{ type:'ss', tag:'高频', question:'点A在点O的北偏东50°方向,则点O在点A的', opts:['南偏西40°','南偏东50°','北偏西40°','南偏西50°'], correct:3, passMsg:'✅ 南北交换、东西交换、角度不变→南偏西50°', failMsg:'💡 北→南,东→西,50°不变→<strong>南偏西50°</strong>' },
|
||||
{ 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秒' },
|
||||
{ type:'ss', tag:'综合', question:'一个多面体V=6,F=5,欧拉公式求棱数E', opts:['7','8','9','10'], correct:2, passMsg:'✅ V+F-E=2→6+5-E=2→E=9', failMsg:'💡 欧拉公式:<strong>V+F-E=2</strong>→6+5-E=2→E=<strong>9</strong>' },
|
||||
{ type:'ss', tag:'综合', question:'∠A与∠B互余,∠B与∠C互补,∠A=30°,则∠C=', opts:['60°','120°','150°','90°'], correct:1, passMsg:'✅ ∠A+∠B=90°→∠B=60°;∠B+∠C=180°→∠C=120°', failMsg:'💡 ∠B=90-30=60°,∠C=180-60=<strong>120°</strong>' },
|
||||
// ── 章节复习 ──
|
||||
{ type:'review', question:'下面说法正确的是', opts:['射线AB和射线BA是同一条','两点之间直线最短','一个角的补角一定大于这个角','同角的余角相等'], correct:3, passMsg:'✅ 同角的余角相等', failMsg:'💡 A✗方向不同;B✗两点之间<strong>线段</strong>最短;C✗钝角补角更小' },
|
||||
{ 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:'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方位角互看:南北交换、东西交换、角度不变']
|
||||
]},
|
||||
]},
|
||||
];
|
||||
|
||||
|
||||
// SVG path for car positions (4 checkpoints)
|
||||
const CP_POS = [
|
||||
{ x:280, y:68 },
|
||||
{ x:80, y:200 },
|
||||
{ x:280, y:340 },
|
||||
{ x:120, y:468 }
|
||||
];
|
||||
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:[], 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');
|
||||
}
|
||||
|
||||
function checkConj() {
|
||||
if (C._done) return;
|
||||
const inp = document.getElementById('conjInput'); if (!inp || !inp.value.trim()) return;
|
||||
C._done = true;
|
||||
const v = C.pool[C.idx];
|
||||
const ansKey = {s:'s', ed:'ed', pp:'pp'}[C.mode];
|
||||
const correctAns = v[ansKey];
|
||||
const val = inp.value.trim().toLowerCase();
|
||||
const cor = val === correctAns;
|
||||
inp.disabled = true; inp.className = cor ? 'cor' : 'wrg';
|
||||
if (cor) { C.correct++; beep('pass'); sparks(4); }
|
||||
else beep('fail');
|
||||
const fb = document.getElementById('conjFb');
|
||||
fb.className = 'fb show ' + (cor ? 'pass' : 'fail');
|
||||
fb.innerHTML = `<div class="fbt">${cor ? '✅ 正确!' : '❌ 正确答案:'+correctAns}</div>`;
|
||||
setTimeout(() => { C._done = false; C.idx++; renderConj(); }, 1200);
|
||||
// ===== 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() { 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;
|
||||
S.curSS=idx; S.curSeg=0;
|
||||
page('pC'); renderSeg(); sync();
|
||||
}
|
||||
|
||||
function finishConj() {
|
||||
const pct = C.total ? Math.round(C.correct/C.total*100) : 0;
|
||||
const msg = pct >= 90 ? '🌟 完美!动词变形全掌握!' : pct >= 70 ? '👍 不错!继续练!' : '💪 多练几次,语感就出来了!';
|
||||
const cc = document.getElementById('conjContent');
|
||||
cc.innerHTML = `<div class="cd" style="text-align:center;padding:30px;"><div style="font-size:48px;margin-bottom:4px;">${pct>=90?'🎉':pct>=70?'👍':'💪'}</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#1a1a2e;">${C.correct}/${C.total}</div>
|
||||
<div style="font-size:13px;color:rgba(26,26,46,0.3);margin:4px 0;">正确率 ${pct}%</div>
|
||||
<div style="font-size:12px;margin-bottom:10px;">${msg}</div>
|
||||
<button class="big-btn p" onclick="startConj('${C.mode}')">🔄 再来一轮</button>
|
||||
<button class="big-btn g" onclick="switchTab('me')" style="margin-top:6px;">返回</button></div>`;
|
||||
document.getElementById('conjScore').textContent = '';
|
||||
if (pct >= 70) { sparks(pct >= 90 ? 40 : 15); beep(pct >= 90 ? 'win' : 'pass'); }
|
||||
// ===== 答题地图 =====
|
||||
function qNum(segIdx) {
|
||||
let n = 0;
|
||||
const s = SS[S.curSS];
|
||||
for (let i = 0; i <= segIdx; i++) { if (s.segs[i].type === 'ss') n++; }
|
||||
return n;
|
||||
}
|
||||
function showAnswerMap() {
|
||||
const s = SS[S.curSS];
|
||||
page('pM');
|
||||
$('mGl').textContent = '\u{1F4CA} \u7B54\u9898\u5730\u56FE';
|
||||
$('mTi').textContent = s.emoji + ' ' + s.title;
|
||||
$('mDe').textContent = '\u70B9\u51FB\u9898\u76EE\u8DF3\u8F6C \u00B7 \u7EFF=\u6B63\u786E \u7EA2=\u9519\u8BEF \u7070=\u672A\u7B54';
|
||||
let html = '<div class="qGrid">';
|
||||
s.segs.forEach(function(seg, i) {
|
||||
if (seg.type !== 'ss' && seg.type !== 'review' && seg.type !== 'rd') return;
|
||||
const cls = seg._answered ? (seg._passed ? 'correct' : 'wrong') : 'unanswered';
|
||||
const icon = seg._answered ? (seg._passed ? '\u2705' : '\u274C') : '\u25CB';
|
||||
html += '<div class="qCell ' + cls + '" data-si="' + i + '">';
|
||||
html += '<div class="qNum">' + qNum(i) + '</div>';
|
||||
html += '<div class="qSt">' + icon + '</div></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
$('mGrid').innerHTML = html;
|
||||
}
|
||||
function goToSeg(idx) {
|
||||
S.curSeg = idx;
|
||||
page('pC'); renderSeg(); sync();
|
||||
}
|
||||
function firstUnanswered() {
|
||||
const s = SS[S.curSS];
|
||||
for (let i = 0; i < s.segs.length; i++) {
|
||||
const t = s.segs[i].type;
|
||||
if ((t === 'ss' || t === 'review' || t === 'rd') && !s.segs[i]._answered) { goToSeg(i); return; }
|
||||
}
|
||||
for (let i = 0; i < s.segs.length; i++) {
|
||||
const t = s.segs[i].type;
|
||||
if (t === 'ss' || t === 'review' || t === 'rd') { goToSeg(i); return; }
|
||||
}
|
||||
}
|
||||
function firstWrong() {
|
||||
const s = SS[S.curSS];
|
||||
for (let i = 0; i < s.segs.length; i++) {
|
||||
const seg = s.segs[i]; const t = seg.type;
|
||||
if ((t === 'ss' || t === 'review' || t === 'rd') && seg._answered && !seg._passed) { goToSeg(i); return; }
|
||||
}
|
||||
firstUnanswered();
|
||||
}
|
||||
function resetAllSegs() {
|
||||
const s = SS[S.curSS];
|
||||
s.segs.forEach(function(seg) {
|
||||
if (seg.type === 'ss' || seg.type === 'review' || seg.type === 'rd') { delete seg._answered; delete seg._selected; delete seg._passed; }
|
||||
});
|
||||
showAnswerMap();
|
||||
}
|
||||
let S = {};
|
||||
function ld() { try { const d = localStorage.getItem('wordLe3'); S = d ? JSON.parse(d) : {}; } catch(e) { S = {}; } }
|
||||
function sv() { try { localStorage.setItem('wordLe3', JSON.stringify(S)); } catch(e) {} }
|
||||
function initState() { if (!S.learned) { S.learned = {}; S.daily = {}; S.streak = 0; S.lastCheckIn = 0; S.totalQuiz = 0; S.totalCorrect = 0; } }
|
||||
const DAYS = ['日','一','二','三','四','五','六'];
|
||||
function today() { return new Date().toISOString().slice(0,10); }
|
||||
function weekDates() { const d=new Date(),arr=[]; for(let i=0;i<7;i++){const c=new Date(d);c.setDate(d.getDate()-d.getDay()+i);arr.push(c);} return arr; }
|
||||
function shuffle(a) { for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];} return a; }
|
||||
function uid(ui, wi) { return ui+'_'+wi; }
|
||||
function allWords() { const r=[]; UNITS.forEach((u,ui)=>u.words.forEach((w,wi)=>r.push({...w,ui,wi}))); return r; }
|
||||
function speak(text) { if (!window.speechSynthesis) return; window.speechSynthesis.cancel(); const u=new SpeechSynthesisUtterance(text); u.lang='en-US'; u.rate=0.88; window.speechSynthesis.speak(u); }
|
||||
|
||||
// ===== AUDIO =====
|
||||
// ===== 事件委托 =====
|
||||
function closest(el, sel) {
|
||||
var e = el;
|
||||
while (e && e !== document) {
|
||||
if (e.matches && e.matches(sel)) return e;
|
||||
e = e.parentElement || e.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
const cp = closest(e.target, '.cp');
|
||||
if (cp) {
|
||||
const idx = parseInt(cp.getAttribute('data-idx'));
|
||||
if (!isNaN(idx) && !cp.classList.contains('locked')) { goChap(idx); return; }
|
||||
}
|
||||
|
||||
if (closest(e.target, '#bkMap')) { goMap(); return; }
|
||||
|
||||
if (closest(e.target, '#bkMapBtn')) { showAnswerMap(); return; }
|
||||
if (closest(e.target, '#bkMapM')) { goMap(); return; }
|
||||
if (closest(e.target, '#bkChapM')) { S.viewMode='normal'; page('pC'); renderSeg(); sync(); return; }
|
||||
|
||||
const qc = closest(e.target, '.qCell');
|
||||
if (qc) { const si = parseInt(qc.getAttribute('data-si')); if (!isNaN(si)) { goToSeg(si); return; } }
|
||||
if (closest(e.target, '#mUn')) { firstUnanswered(); return; }
|
||||
if (closest(e.target, '#stgContinue')) { nextSeg(); return; }
|
||||
if (closest(e.target, '#mErr')) { firstWrong(); return; }
|
||||
if (closest(e.target, '#mAll')) { if (confirm('\u{1F504} \u786E\u5B9A\u91CD\u7F6E\u672C\u7AE0\u6240\u6709\u7B54\u9898\u72B6\u6001\uFF1F')) { resetAllSegs(); } return; }
|
||||
|
||||
const optEl = closest(e.target, '.opt');
|
||||
if (optEl && !optEl.classList.contains('disabled')) {
|
||||
const idx = parseInt(optEl.getAttribute('data-idx'));
|
||||
if (!isNaN(idx)) { pickAns(idx); return; }
|
||||
}
|
||||
|
||||
if (closest(e.target, '#egBtn')) { stepEg(); return; }
|
||||
|
||||
const actEl = closest(e.target, '.act');
|
||||
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; }
|
||||
sv(); renderWrongList();
|
||||
return;
|
||||
}
|
||||
const wrDo = closest(e.target, '.wrDo'); if (wrDo) {
|
||||
const id = wrDo.getAttribute('data-id'); const [ci,si] = id.split('_').map(Number);
|
||||
S.curSS=ci; S.curSeg=si; S.viewMode='normal';
|
||||
const seg=SS[ci].segs[si];
|
||||
if (seg) { delete seg._answered; delete seg._selected; delete seg._passed; }
|
||||
page('pC'); renderSeg(); sync();
|
||||
return;
|
||||
}
|
||||
if (closest(e.target, '#wrongBtn')) { goWrongReview(); return; }
|
||||
if (closest(e.target, '#dbBtn')) {
|
||||
const p = $('dbPanel');
|
||||
if (p.style.display === 'none' || !p.style.display) { renderDashboard(); p.style.display = 'block'; }
|
||||
else { p.style.display = 'none'; }
|
||||
return;
|
||||
}
|
||||
if (closest(e.target, '#stgStart')) {
|
||||
const s = SS[S.curSS];
|
||||
for (let i = 0; i < s.segs.length; i++) {
|
||||
if (s.segs[i].tag && !s.segs[i]._answered) { goToSeg(i); return; }
|
||||
}
|
||||
// all done
|
||||
for (let i = 0; i < s.segs.length; i++) { if (s.segs[i].tag) { goToSeg(i); return; } }
|
||||
return;
|
||||
}
|
||||
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:[],wrongQMap:{},doneQ:[],seenQ:[],viewMode:'normal',wrIdx:0,streak:0});
|
||||
sv(); goMap();
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// ===== 交互式几何模型拖拽系统 =====
|
||||
function initDragModels(){
|
||||
document.querySelectorAll('.drag-point').forEach(el=>{
|
||||
el.addEventListener('mousedown',e=>{
|
||||
e.preventDefault();
|
||||
const model=el.getAttribute('data-model');
|
||||
const svg=el.closest('svg');const sR=svg.getBoundingClientRect();
|
||||
const sx=sR.width/svg.viewBox.animVal.width,sy=sR.height/svg.viewBox.animVal.height;
|
||||
const minX=parseFloat(el.getAttribute('data-minx')||'0');
|
||||
const maxX=parseFloat(el.getAttribute('data-maxx')||'360');
|
||||
let cb;
|
||||
if(model==='mid'){cb=(x,y)=>{GeoModel.initMidDrag(x,y);};}
|
||||
else if(model==='angle'){
|
||||
const bounds=svg.getBoundingClientRect();
|
||||
cb=(x,y)=>{GeoModel.initAngleDrag(x,y);};
|
||||
}
|
||||
else if(model==='clock'){
|
||||
const hand=el.getAttribute('data-hand');
|
||||
cb=(x,y)=>{GeoModel.initClockDrag(x,y,hand);};
|
||||
}
|
||||
GeoModel.startDrag(e,model,el,{minX,minY:0,maxX:360,maxY:200,cb});
|
||||
el.setAttribute('data-dragging','true');
|
||||
});
|
||||
});
|
||||
}
|
||||
document.addEventListener('mousemove',e=>{
|
||||
if(GeoModel.drag.active)GeoModel.moveDrag(e);
|
||||
});
|
||||
document.addEventListener('mouseup',e=>{
|
||||
if(GeoModel.drag.active){GeoModel.endDrag();}
|
||||
});
|
||||
|
||||
// ===== 轨道地图 =====
|
||||
function renderMap() {
|
||||
const svgPath = document.getElementById('trackFill');
|
||||
const donePath = document.getElementById('trackDone');
|
||||
const total = 4;
|
||||
const done = S.stars.filter(s=>s>0).length;
|
||||
const pathLen = svgPath ? svgPath.getTotalLength() : 800;
|
||||
|
||||
// update done path
|
||||
if (donePath) {
|
||||
const offset = pathLen - (done / total) * pathLen;
|
||||
donePath.setAttribute('stroke-dasharray', pathLen);
|
||||
donePath.setAttribute('stroke-dashoffset', offset);
|
||||
}
|
||||
|
||||
// update checkpoints
|
||||
document.querySelectorAll('.cp').forEach(el => {
|
||||
const idx = parseInt(el.getAttribute('data-idx'));
|
||||
const locked = idx>0 && S.stars[idx-1]===0;
|
||||
el.classList.toggle('locked', locked);
|
||||
el.classList.toggle('done', S.stars[idx]>0);
|
||||
const lockIcon = el.querySelector('.lock-icon');
|
||||
if (lockIcon) lockIcon.style.display = locked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// update star text on CPs
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const el = $('cps'+i);
|
||||
if (el) el.textContent = S.stars[i] ? '\u2B50'.repeat(S.stars[i]) : '';
|
||||
}
|
||||
|
||||
// position car
|
||||
positionCar();
|
||||
}
|
||||
|
||||
function positionCar() {
|
||||
const car = $('trackCar');
|
||||
if (!car) return;
|
||||
const done = S.stars.filter(s=>s>0).length;
|
||||
let idx = Math.min(done, 3);
|
||||
const p = CP_POS[idx];
|
||||
let angle = 0;
|
||||
if (idx < 3) {
|
||||
const n = CP_POS[idx + 1];
|
||||
angle = Math.atan2(n.y - p.y, n.x - p.x) * (180 / Math.PI);
|
||||
} else if (idx > 0) {
|
||||
const pv = CP_POS[idx - 1];
|
||||
angle = Math.atan2(p.y - pv.y, p.x - pv.x) * (180 / Math.PI);
|
||||
}
|
||||
car.style.left = ((p.x / 360) * 100) + '%';
|
||||
car.style.top = ((p.y / 540) * 100) + '%';
|
||||
car.style.transform = 'translate(-50%,-50%) rotate(' + angle + 'deg)';
|
||||
}
|
||||
|
||||
// ===== 章节 =====
|
||||
function segLabel(seg, idx) {
|
||||
if (seg.type==='kp') return seg.title || '知识点';
|
||||
if (seg.type==='stg') return seg.title || '分站赛';
|
||||
if (seg.type==='review') return '章节复习';
|
||||
if (seg.type==='rd') return '计时练习';
|
||||
if (seg.type==='eg') return '例题讲解';
|
||||
if (seg.type==='pn') return '路书';
|
||||
let nth = 0;
|
||||
SS[S.curSS].segs.slice(0, idx+1).forEach(sg => { if(sg.type==='ss') nth++; });
|
||||
return ['练习','排位','正赛','冲刺'][nth-1] || '计时';
|
||||
}
|
||||
function renderSeg() {
|
||||
const s = SS[S.curSS];
|
||||
const seg = s.segs[S.curSeg];
|
||||
if (!seg) return finishChap();
|
||||
const co = $('cCo');
|
||||
co.style.animation = 'none'; void co.offsetHeight;
|
||||
$('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');
|
||||
if (mBtn) mBtn.style.display = (seg.type==='ss'||seg.type==='review'||seg.type==='rd'||seg.type==='stg') ? 'inline-block' : 'none';
|
||||
if (seg.type==='kp') renderKP(seg);
|
||||
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';
|
||||
}
|
||||
|
||||
function renderKP(seg) {
|
||||
$('cCo').innerHTML = `<div class="pn" style="border-left:3px solid #ffd700;"><div class="kpTitle">${seg.icon||'📖'} ${seg.title}</div>${seg.content}<div style="text-align:center;margin-top:14px;"><button class="act show">继续 \u2192</button></div></div>`;
|
||||
beep('click');
|
||||
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() {
|
||||
const s = SS[S.curSS];
|
||||
stgStat.total = s.segs.filter(function(sg){return sg.tag}).length;
|
||||
stgStat.done = stgStat.cor = stgStat.wrg = 0;
|
||||
}
|
||||
function stgUpdate() {
|
||||
stgStat.done = 0; stgStat.cor = 0; stgStat.wrg = 0;
|
||||
const s = SS[S.curSS];
|
||||
s.segs.forEach(function(sg) {
|
||||
if (!sg.tag) return;
|
||||
if (sg._answered) { stgStat.done++; if (sg._passed) stgStat.cor++; else stgStat.wrg++; }
|
||||
});
|
||||
}
|
||||
function renderStg(seg) {
|
||||
stgReset(); stgUpdate();
|
||||
const total = stgStat.total;
|
||||
const done = stgStat.done;
|
||||
const pct = total ? Math.round(done/total*100) : 0;
|
||||
const tags = { '重点':0, '核心':0, '高频':0, '综合':0 };
|
||||
const s = SS[S.curSS];
|
||||
s.segs.forEach(function(sg) { if (sg.tag) tags[sg.tag] = (tags[sg.tag]||0)+1; });
|
||||
let tagHtml = '';
|
||||
Object.keys(tags).forEach(function(t) { if (tags[t]) tagHtml += '<span class="stgTag">'+t+' ×'+tags[t]+'</span>'; });
|
||||
const bar = '<div class="stgBar"><div class="stgFill" style="width:'+pct+'%"></div></div>';
|
||||
const status = done ? '<div style="margin:8px 0;font-size:13px;color:rgba(255,255,255,0.5)">已完成 '+done+'/'+total+'('+stgStat.cor+' 正确 · '+stgStat.wrg+' 错误)</div>' : '';
|
||||
const btn = done < total ? '<button class="act show" id="stgStart">'+(done?'继续练习 \u2192':'开始练习 \u2192')+'</button>'
|
||||
: '<div style="margin:14px 0;padding:14px;background:rgba(46,204,113,0.04);border:1px solid rgba(46,204,113,0.08);border-radius:3px;"><div style="font-size:24px;font-weight:900;color:#2ecc71;">'+stgStat.cor+'/'+total+'</div><div style="font-size:12px;color:rgba(255,255,255,0.3);margin:2px 0 6px;">正确率 '+Math.round(stgStat.cor/total*100)+'%</div><button class="act show" id="stgDone">查看成绩单 \u2192</button></div>';
|
||||
$('cCo').innerHTML = `<div class="pn" style="border-left:3px solid #e10600;"><div class="kpTitle" style="color:#e10600;">\u{1F3C1} 分站赛:${s.title}</div><p>综合练习——检测本章全部知识点掌握情况</p><div style="margin:10px 0;">${tagHtml}</div>${bar}${status}${btn}</div>`;
|
||||
}
|
||||
|
||||
function renderStgResult() {
|
||||
stgUpdate();
|
||||
const total = stgStat.total;
|
||||
const cor = stgStat.cor;
|
||||
const wrg = stgStat.wrg;
|
||||
const pct = total ? Math.round(cor/total*100) : 0;
|
||||
const stars = pct >= 90 ? 3 : pct >= 60 ? 2 : pct >= 30 ? 1 : 0;
|
||||
|
||||
// Category breakdown
|
||||
const s = SS[S.curSS];
|
||||
const cats = {};
|
||||
s.segs.forEach(function(sg) {
|
||||
if (!sg.tag || !sg._answered) return;
|
||||
if (!cats[sg.tag]) cats[sg.tag] = { total:0, cor:0 };
|
||||
cats[sg.tag].total++;
|
||||
if (sg._passed) cats[sg.tag].cor++;
|
||||
});
|
||||
let catHtml = '';
|
||||
Object.keys(cats).forEach(function(t) {
|
||||
const c = cats[t];
|
||||
catHtml += '<div style="display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px solid rgba(255,255,255,0.02);"><span style="color:rgba(255,255,255,0.4)">'+t+'</span><span>'+c.cor+'/'+c.total+' <span style="color:'+(c.cor===c.total?'#2ecc71':'rgba(255,255,255,0.2)')+'">('+Math.round(c.cor/c.total*100)+'%)</span></span></div>';
|
||||
});
|
||||
|
||||
const passFail = pct >= 60 ? '<span style="color:#2ecc71">\u2705 通过</span>' : '<span style="color:#e10600">\u274C 未通过,继续加油</span>';
|
||||
$('cCo').innerHTML = `<div class="pn" style="border-left:3px solid ${pct>=60?'#2ecc71':'#e10600'};text-align:center;">
|
||||
<div class="kpTitle" style="color:#ffd700;font-size:26px;animation:bounceIn 0.5s ease;">\u{1F3C6} ${stars>=3?'完美!':stars>=2?'不错!':'继续加油!'}</div>
|
||||
<div style="font-size:40px;font-weight:900;color:#fff;margin:8px 0;font-family:'Inter',monospace;animation:bounceIn 0.6s ease;">${cor}<span style="font-size:20px;color:rgba(255,255,255,0.2);">/${total}</span></div>
|
||||
<div style="font-size:13px;color:rgba(255,255,255,0.3);margin-bottom:12px;animation:slideUp 0.5s ease 0.2s both;">正确率 ${pct}% ${passFail}</div>
|
||||
<div style="text-align:left;max-width:240px;margin:0 auto 14px;animation:slideUp 0.5s ease 0.3s both;">${catHtml}</div>
|
||||
<button class="act show" style="background:${pct>=90?'#ffd700;color:#0a0a1a':pct>=60?'#2ecc71':pct>0?'#e10600':'rgba(255,255,255,0.1)'};animation:bounceIn 0.5s ease 0.5s both;" id="stgContinue">继续 \u2192</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderPN(seg) {
|
||||
$('cCo').innerHTML = `<div class="pn">${seg.content}<div style="text-align:center;margin-top:12px;"><div style="font-size:10px;color:rgba(255,255,255,0.35);margin-bottom:5px;letter-spacing:2px;font-weight:700;">CO-DRIVER: 路书完毕</div><button class="act show">发车 \u2192</button></div></div>`;
|
||||
beep('click');
|
||||
}
|
||||
|
||||
function renderSSeg(seg) {
|
||||
const done = seg._answered||false;
|
||||
const sel = seg._selected??-1;
|
||||
const lbl = ['A','B','C','D'];
|
||||
const qnLabel = seg.type==='review' ? '章节复习' : (seg.tag ? segLabel(seg,S.curSeg) : segLabel(seg,S.curSeg));
|
||||
const diag = seg.diagram ? `<div class="geoDiag">${seg.diagram}</div>` : '';
|
||||
let progHtml = '';
|
||||
if (seg.tag) {
|
||||
stgUpdate();
|
||||
const pct = stgStat.total ? Math.round((stgStat.done-1)/stgStat.total*100) : 0;
|
||||
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>` : '';
|
||||
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; }
|
||||
|
||||
function goWrongReview() {
|
||||
S.viewMode='wrongReview'; S.wrIdx=0;
|
||||
page('pW'); renderWrongList();
|
||||
}
|
||||
function renderWrongList() {
|
||||
const $wl = $('wList');
|
||||
if (!S.wrongQ.length) {
|
||||
$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 = `<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">\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];
|
||||
if (seg._answered) return;
|
||||
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.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;
|
||||
// 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); }
|
||||
// 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) {
|
||||
const nums = seg.numbers||[];
|
||||
const mn = Math.min(...nums)-1, mx = Math.max(...nums)+1, rg = mx-mn||1;
|
||||
const ticks = []; for(let i=mn;i<=mx;i++) ticks.push(i);
|
||||
$('cCo').innerHTML = `<div class="rv"><h3>${seg.title||'\u{1F4CD} 赛道定位'}</h3><div style="font-size:9px;color:rgba(255,255,255,0.08);margin-bottom:8px;letter-spacing:1px;">SPLIT TIME</div><div class="rline">${ticks.map(t=>`<span class="mk ${t===0?'z':''}" style="left:${(t-mn)/rg*100}%">${t}</span>`).join('')}${nums.map(n=>`<div class="car" style="left:${(n-mn)/rg*100}%">\u{1F3CE}<span class="vl">${n>0?'+'+n:n}</span></div>`).join('')}</div><button class="act show" style="margin-top:12px;">继续 \u2192</button></div>`;
|
||||
beep('click');
|
||||
}
|
||||
|
||||
// ===== 例题讲解 (step-through) =====
|
||||
function renderEg(seg) {
|
||||
if (!seg._step) seg._step = 1;
|
||||
const step = seg._step;
|
||||
const total = seg.steps.length;
|
||||
$('cCo').innerHTML = `<div class="eg-box">
|
||||
<div class="eg-title">${seg.title}</div>
|
||||
<div class="eg-steps">${seg.steps.map((s,i)=>`<div class="eg-step ${i<step?'show':''}"><span class="step-num">${i+1}</span>${s}</div>`).join('')}</div>
|
||||
${step < total ? `<button class="eg-btn" id="egBtn">下一步 \u2192</button>` : `<button class="act show">继续 \u2192</button>`}
|
||||
</div>`;
|
||||
beep('click');
|
||||
}
|
||||
|
||||
function stepEg() {
|
||||
const s = SS[S.curSS];
|
||||
const seg = s.segs[S.curSeg];
|
||||
if (!seg || seg.type!=='eg') return;
|
||||
if (!seg._step) seg._step = 1;
|
||||
const total = seg.steps.length;
|
||||
if (seg._step < total) {
|
||||
seg._step++;
|
||||
renderEg(seg);
|
||||
if (seg._step === total) beep('pass');
|
||||
}
|
||||
}
|
||||
|
||||
function nextSeg() {
|
||||
const s = SS[S.curSS];
|
||||
const seg = s.segs[S.curSeg];
|
||||
const id = S.curSS+'_'+S.curSeg;
|
||||
if (!S.seenQ.includes(id)) { S.seenQ.push(id); sv(); }
|
||||
if (S.curSeg < s.segs.length-1) { S.curSeg++; renderSeg(); sync(); }
|
||||
else finishChap();
|
||||
}
|
||||
|
||||
// ===== 章节结束 → 庆祝 =====
|
||||
function finishChap() {
|
||||
const s = SS[S.curSS];
|
||||
const qs = s.segs.filter(seg=>seg.type==='ss'||seg.type==='review'||seg.type==='rd');
|
||||
const ok = qs.filter(seg=>seg._passed).length;
|
||||
const total = qs.length||1;
|
||||
const r = ok/total;
|
||||
let stars = 0;
|
||||
if (r>=0.9) stars=3;
|
||||
else if (r>=0.6) stars=2;
|
||||
else if (r>=0.3) stars=1;
|
||||
if (total===0) stars=3;
|
||||
S.stars[S.curSS] = Math.max(S.stars[S.curSS], stars);
|
||||
S.pts = Math.min(100, S.pts + stars*5);
|
||||
sv();
|
||||
|
||||
const pos = ['DNF','P10','P5','P1'];
|
||||
const cups = ['\u{1F3C1}','\u{1F949}','\u{1F948}','\u{1F3C6}'];
|
||||
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];
|
||||
$('pmTi').textContent = titles[stars];
|
||||
$('pmSt').textContent = '\u2B50'.repeat(stars)+'\u2B50'.repeat(3-stars);
|
||||
$('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 — 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) {
|
||||
// animate car along track curve
|
||||
animateCarAlongPath(nextIdx, () => {
|
||||
updateTrackProgress(nextIdx);
|
||||
setTimeout(() => { goMap(); }, 300);
|
||||
});
|
||||
} else {
|
||||
goMap();
|
||||
}
|
||||
}
|
||||
|
||||
const _carPathCache = (function(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('d', SVG_PATH);
|
||||
const len = p.getTotalLength();
|
||||
const samples = 200;
|
||||
const cpT = CP_POS.map(cp => {
|
||||
let best = 0, bestD = Infinity;
|
||||
for (let i = 0; i <= samples; i++) {
|
||||
const pt = p.getPointAtLength(i/samples*len);
|
||||
const d = Math.hypot(pt.x-cp.x, pt.y-cp.y);
|
||||
if (d < bestD) { bestD = d; best = i/samples; }
|
||||
}
|
||||
return best;
|
||||
});
|
||||
return { p, len, cpT };
|
||||
})();
|
||||
|
||||
function animateCarAlongPath(toIdx, onDone) {
|
||||
const car = $('trackCar'); if (!car) return void(onDone&&onDone());
|
||||
const { p, len, cpT } = _carPathCache;
|
||||
const fromT = S.curSS >= 0 && S.curSS < cpT.length ? cpT[S.curSS] : 0;
|
||||
const toT = cpT[toIdx];
|
||||
const duration = 1400;
|
||||
const start = performance.now();
|
||||
car.classList.add('moving');
|
||||
// Hide star/name labels during transit
|
||||
document.querySelectorAll('.cp-label, .cp-name, .cp-stars').forEach(el => el.style.opacity = '0.3');
|
||||
|
||||
function step(time) {
|
||||
const elapsed = time - start;
|
||||
const progress = Math.min(1, elapsed / duration);
|
||||
const ease = 1 - Math.pow(1 - progress, 3);
|
||||
const t = Math.max(0, Math.min(1, fromT + (toT - fromT) * ease));
|
||||
const pt = p.getPointAtLength(t * len);
|
||||
// direction for rotation (look-ahead)
|
||||
const dt = 0.003;
|
||||
const pt2 = p.getPointAtLength(Math.max(0, Math.min(len, (t + dt) * len)));
|
||||
const angle = Math.atan2(pt2.y - pt.y, pt2.x - pt.x) * (180 / Math.PI);
|
||||
car.style.left = ((pt.x / 360) * 100) + '%';
|
||||
car.style.top = ((pt.y / 540) * 100) + '%';
|
||||
car.style.transform = 'translate(-50%,-50%) rotate(' + angle + 'deg)';
|
||||
if (progress < 1) { requestAnimationFrame(step); }
|
||||
else {
|
||||
car.classList.remove('moving');
|
||||
document.querySelectorAll('.cp-label, .cp-name, .cp-stars').forEach(el => el.style.opacity = '1');
|
||||
if (onDone) onDone();
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function updateTrackProgress(idx) {
|
||||
const donePath = document.getElementById('trackDone');
|
||||
const fillPath = document.getElementById('trackFill');
|
||||
if (!donePath || !fillPath) return;
|
||||
const pathLen = fillPath.getTotalLength();
|
||||
const progress = idx / 4;
|
||||
donePath.setAttribute('stroke-dasharray', pathLen);
|
||||
donePath.setAttribute('stroke-dashoffset', pathLen - progress * pathLen);
|
||||
}
|
||||
|
||||
// ===== HUD =====
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 学习总览 =====
|
||||
function renderDashboard() {
|
||||
const titles = ['有理数','整式的加减','一元一次方程','几何图形初步'];
|
||||
const emojis = ['🏁','🔧','🛞','📐'];
|
||||
let html = '<div id="dbInner" style="background:rgba(8,8,20,0.5);border:1px solid rgba(255,255,255,0.03);border-radius:3px;padding:12px 14px;">';
|
||||
for (let ch = 0; ch < 4; ch++) {
|
||||
const s = SS[ch];
|
||||
const stars = S.stars[ch];
|
||||
const starStr = stars ? '\u2B50'.repeat(stars) : '\u2606\u2606\u2606';
|
||||
// Count KP segments seen vs total
|
||||
let kpTotal = 0, kpSeen = 0;
|
||||
let ssTotal = 0, ssDone = 0, ssCor = 0;
|
||||
s.segs.forEach(function(seg, i) {
|
||||
const id = ch+'_'+i;
|
||||
if (seg.type === 'kp' || seg.type === 'eg' || seg.type === 'pn') { kpTotal++; if (S.seenQ.includes(id)) kpSeen++; }
|
||||
if (seg.type === 'ss' || seg.type === 'review' || seg.tag) { ssTotal++; if (seg._answered) { ssDone++; if (seg._passed) ssCor++; } }
|
||||
});
|
||||
const kpPct = kpTotal ? Math.round(kpSeen/kpTotal*100) : 0;
|
||||
const kpBar = '<div class="stgBar" style="margin:2px 0;height:3px;"><div class="stgFill" style="width:'+kpPct+'%;background:'+(kpPct===100?'#2ecc71':'#e10600')+'"></div></div>';
|
||||
const ssStr = ssDone ? (ssCor+'/'+ssDone) : '0/0';
|
||||
const status = kpSeen === kpTotal ? (ssDone === ssTotal ? '\u2705 已完成' : '\u{1F3C1} 练习中') : '\u{1F4D6} 学习中';
|
||||
html += '<div style="padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.02);display:flex;align-items:center;gap:8px;">';
|
||||
html += '<div style="width:24px;font-size:16px;text-align:center;">'+emojis[ch]+'</div>';
|
||||
html += '<div style="flex:1;min-width:0;">';
|
||||
html += '<div style="font-size:12px;font-weight:700;color:#fff;">SS'+(ch+1)+' '+titles[ch]+'</div>';
|
||||
html += '<div style="font-size:9px;color:rgba(255,255,255,0.3);">\u{1F4DA} 学习 '+kpSeen+'/'+kpTotal+' \u270F 答题 '+ssStr+' '+starStr+'</div>';
|
||||
html += kpBar;
|
||||
html += '</div>';
|
||||
html += '<div style="font-size:10px;color:'+(status==='\u2705 已完成'?'#2ecc71':status==='\u{1F3C1} 练习中'?'#ffd700':'rgba(255,255,255,0.3)')+';">'+status+'</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<div style="font-size:9px;color:rgba(255,255,255,0.15);text-align:center;padding-top:6px;">\u{1F4A1} 点击赛道检查点进入章节 | 绿色=已完成全部学习</div>';
|
||||
html += '</div>';
|
||||
$('dbPanel').innerHTML = html;
|
||||
}
|
||||
let ax = null;
|
||||
function ac() { if(!ax) ax=new(window.AudioContext||window.webkitAudioContext)(); return ax; }
|
||||
function beep(t) {
|
||||
try{const ctx=ac();if(ctx.state==='suspended')ctx.resume();
|
||||
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.04;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.02;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.04;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.045;o.frequency.value=f;o.start(ctx.currentTime+i*0.06);o.stop(ctx.currentTime+i*0.06+0.15);});}
|
||||
}catch(e){}
|
||||
try {
|
||||
const ctx = ac();
|
||||
if (ctx.state === 'suspended') ctx.resume();
|
||||
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) {}
|
||||
}
|
||||
|
||||
// ===== 背景音乐 =====
|
||||
let bgmNodes = [];
|
||||
let bgmOn = false;
|
||||
let bgmTimer = null;
|
||||
|
||||
function toggleBGM() {
|
||||
const ctx = ac();
|
||||
if (ctx.state === 'suspended') ctx.resume();
|
||||
if (bgmOn) stopBGM();
|
||||
else startBGM();
|
||||
}
|
||||
|
||||
function startBGM() {
|
||||
if (bgmOn) return;
|
||||
try {
|
||||
const ctx = ac();
|
||||
bgmOn = true;
|
||||
document.getElementById('mBtn').classList.add('on');
|
||||
|
||||
const master = ctx.createGain();
|
||||
master.gain.value = 0;
|
||||
master.gain.linearRampToValueAtTime(0.25, ctx.currentTime + 1.5);
|
||||
master.connect(ctx.destination);
|
||||
bgmNodes.push(master);
|
||||
|
||||
const BPM = 130;
|
||||
const beat = 60 / BPM;
|
||||
|
||||
function kick(time) {
|
||||
const o = ctx.createOscillator();
|
||||
const g = ctx.createGain();
|
||||
o.type = 'sine';
|
||||
o.frequency.setValueAtTime(120, time);
|
||||
o.frequency.exponentialRampToValueAtTime(40, time + 0.08);
|
||||
g.gain.setValueAtTime(0.50, time);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, time + 0.20);
|
||||
o.connect(g); g.connect(master);
|
||||
o.start(time); o.stop(time + 0.15);
|
||||
bgmNodes.push(o, g);
|
||||
}
|
||||
|
||||
function hat(time) {
|
||||
const n = ctx.createBufferSource();
|
||||
const buf = ctx.createBuffer(1, ctx.sampleRate * 0.05, ctx.sampleRate);
|
||||
const d = buf.getChannelData(0);
|
||||
for (let i = 0; i < d.length; i++) d[i] = (Math.random() * 2 - 1) * Math.exp(-i / (ctx.sampleRate * 0.008));
|
||||
n.buffer = buf;
|
||||
const g = ctx.createGain();
|
||||
g.gain.value = 0.10;
|
||||
n.connect(g); g.connect(master);
|
||||
n.start(time);
|
||||
bgmNodes.push(n, g);
|
||||
}
|
||||
|
||||
const bassNotes = [55, 65, 73, 82, 73, 65, 55, 49];
|
||||
let bassIdx = 0;
|
||||
function bassLine(time) {
|
||||
const o = ctx.createOscillator();
|
||||
o.type = 'sawtooth';
|
||||
o.frequency.value = bassNotes[bassIdx % bassNotes.length];
|
||||
const g = ctx.createGain();
|
||||
g.gain.setValueAtTime(0.12, time);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, time + beat * 0.95);
|
||||
const f = ctx.createBiquadFilter();
|
||||
f.type = 'lowpass';
|
||||
f.frequency.value = 300;
|
||||
o.connect(f); f.connect(g); g.connect(master);
|
||||
o.start(time); o.stop(time + beat * 0.9);
|
||||
bgmNodes.push(o, g, f);
|
||||
bassIdx++;
|
||||
}
|
||||
|
||||
const melody = [
|
||||
[330,0],[349,0.5],[392,1],[440,1.5],
|
||||
[523,2],[494,2.5],[440,3],[392,3.5],
|
||||
[349,4],[392,4.5],[440,5],[494,5.5],
|
||||
[523,6],[587,6.5],[659,7],[587,7.5],
|
||||
[523,8],[494,8.5],[440,9],[494,9.5],
|
||||
[523,10],[587,10.5],[659,11],[784,11.5],
|
||||
[659,12],[587,12.5],[523,13],[494,13.5],
|
||||
[440,14],[392,14.5],[349,15],[330,15.5],
|
||||
[349,16],[392,16.5],[440,17],[523,17.5],
|
||||
[587,18],[523,18.5],[494,19],[440,19.5],
|
||||
[392,20],[440,20.5],[494,21],[523,21.5],
|
||||
[587,22],[659,22.5],[784,23],[659,23.5],
|
||||
[587,24],[523,24.5],[494,25],[440,25.5],
|
||||
[392,26],[349,26.5],[330,27],[294,27.5],
|
||||
[261,28],[294,28.5],[330,29],[349,29.5],
|
||||
[392,30],[440,30.5],[494,31],[523,31.5]
|
||||
];
|
||||
function lead(time) {
|
||||
melody.forEach(([f, off]) => {
|
||||
const t = time + off * beat;
|
||||
const o = ctx.createOscillator();
|
||||
o.type = 'square';
|
||||
o.frequency.value = f;
|
||||
const g = ctx.createGain();
|
||||
const vel = off % 2 < 0.01 ? 0.06 : 0.04;
|
||||
g.gain.setValueAtTime(vel, t);
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + beat * 0.5);
|
||||
const f2 = ctx.createBiquadFilter();
|
||||
f2.type = 'lowpass';
|
||||
f2.frequency.value = 1200;
|
||||
o.connect(f2); f2.connect(g); g.connect(master);
|
||||
o.start(t); o.stop(t + beat * 0.6);
|
||||
bgmNodes.push(o, g, f2);
|
||||
});
|
||||
}
|
||||
|
||||
function pad(time) {
|
||||
const chords = [[65,82,98],[73,87,110],[82,98,123],[98,110,130]];
|
||||
chords.forEach((c, ci) => {
|
||||
const t = time + ci * 4 * beat;
|
||||
c.forEach(f => {
|
||||
const o = ctx.createOscillator();
|
||||
o.type = 'sine';
|
||||
o.frequency.value = f;
|
||||
const g = ctx.createGain();
|
||||
g.gain.setValueAtTime(0, t);
|
||||
g.gain.linearRampToValueAtTime(0.04, t + 0.5);
|
||||
g.gain.setValueAtTime(0.04, t + 3);
|
||||
g.gain.linearRampToValueAtTime(0.001, t + 4);
|
||||
const of = ctx.createBiquadFilter();
|
||||
of.type = 'lowpass';
|
||||
of.frequency.value = 600;
|
||||
o.connect(of); of.connect(g); g.connect(master);
|
||||
o.start(t); o.stop(t + 4 * beat);
|
||||
bgmNodes.push(o, g, of);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let bar = 0;
|
||||
function seq() {
|
||||
if (!bgmOn) return;
|
||||
const t = ctx.currentTime + 0.05;
|
||||
const barLen = 4 * beat;
|
||||
for (let i = 0; i < 4; i++) kick(t + i * beat);
|
||||
for (let i = 0; i < 4; i++) hat(t + i * beat + beat * 0.5);
|
||||
for (let i = 0; i < 4; i++) bassLine(t + i * beat);
|
||||
if (bar % 4 === 0) lead(t + beat);
|
||||
if (bar % 4 === 0) pad(t);
|
||||
bar++;
|
||||
bgmTimer = setTimeout(seq, barLen * 1000);
|
||||
}
|
||||
setTimeout(seq, 500);
|
||||
|
||||
} catch(e) { bgmOn = false; }
|
||||
}
|
||||
|
||||
function stopBGM() {
|
||||
bgmOn = false;
|
||||
document.getElementById('mBtn').classList.remove('on');
|
||||
clearTimeout(bgmTimer);
|
||||
bgmNodes.forEach(n => { try { n.disconnect && n.disconnect(); n.stop && n.stop(); } catch(e) {} });
|
||||
bgmNodes = [];
|
||||
}
|
||||
|
||||
// ===== 粒子 =====
|
||||
function sparks(n) {
|
||||
const c=document.getElementById('pyro');if(!c)return;
|
||||
const cs=['#6366f1','#f59e0b','#22c55e','#fff','#f59e0b','#6366f1','#ef4444'];
|
||||
const c = $('pyro');
|
||||
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);}
|
||||
}
|
||||
function showModal(html) { const m=document.getElementById('modalInner'); m.innerHTML=html; document.getElementById('ciModal').classList.add('show'); }
|
||||
|
||||
// ===== TABS =====
|
||||
function switchTab(name) {
|
||||
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('act'));
|
||||
document.querySelector(`.tab[data-tab="${name}"]`).classList.add('act');
|
||||
document.querySelectorAll('.pg').forEach(p=>p.classList.remove('act'));
|
||||
const pageMap = {home:'pHome',review:'pReview',me:'pMe'};
|
||||
document.getElementById(pageMap[name]).classList.add('act');
|
||||
if (name==='home') renderHome();
|
||||
if (name==='units') renderUnits();
|
||||
if (name==='review') renderReview();
|
||||
if (name==='exam') renderExamUnits();
|
||||
if (name==='me') renderMe();
|
||||
// ===== 启动 =====
|
||||
function init() {
|
||||
var hs = $('hStage');
|
||||
if (hs) { const done = S.stars.filter(function(s){return s>0}).length; hs.textContent = Math.min(done+1,4)+'/4'; }
|
||||
renderMap(); sync();
|
||||
}
|
||||
|
||||
// ===== HOME =====
|
||||
function renderHome() {
|
||||
const wb=document.getElementById('weekBar');
|
||||
wb.innerHTML=weekDates().map(d=>{const k=d.toISOString().slice(0,10);return `<div class="d ${k===today()?'td':''} ${S.daily[k]?'on':''}">${DAYS[d.getDay()]}</div>`;}).join('');
|
||||
const ci=document.getElementById('checkInArea');
|
||||
const td=parseInt(new Date()/86400000);
|
||||
ci.innerHTML=S.lastCheckIn!==td?'<button class="big-btn o" id="doCheckIn" style="padding:10px;font-size:14px;">⭐ 今日打卡</button>':'<div style="text-align:center;font-size:13px;font-weight:600;color:#16a34a;">✅ 今日已打卡</div>';
|
||||
const all=allWords();
|
||||
const learned=Object.keys(S.learned).length;
|
||||
const mastered=Object.values(S.learned).filter(v=>v.level>=2).length;
|
||||
const now=Date.now();
|
||||
const due=Object.values(S.learned).filter(v=>v.level<3&&v.nextReview>0&&v.nextReview<=now).length;
|
||||
document.getElementById('sLearned').textContent=learned;
|
||||
document.getElementById('sMastered').textContent=mastered;
|
||||
document.getElementById('sDue').textContent=due;
|
||||
document.getElementById('hStreak').textContent='🔥 '+S.streak+'天';
|
||||
document.getElementById('hLearned').textContent='📖 '+learned;
|
||||
const gTotal = GRAMMAR.length;
|
||||
|
||||
// Welcome for first-time users
|
||||
const isFirstTime = learned === 0 && (!S._welcomeDone);
|
||||
if (isFirstTime) {
|
||||
S._welcomeDone = true; sv();
|
||||
document.getElementById('continueCard').innerHTML = `<div class="cd" style="text-align:center;background:linear-gradient(135deg,#f0f0ff,#fff);border:1px solid rgba(99,102,241,0.1);">
|
||||
<div style="font-size:36px;margin-bottom:4px;">👋</div>
|
||||
<div style="font-size:14px;font-weight:700;color:#1a1a2e;margin-bottom:2px;">欢迎来到单词乐园!</div>
|
||||
<div style="font-size:11px;color:rgba(26,26,46,0.3);margin-bottom:8px;">选择你想要开始学习的单元</div>
|
||||
<div id="welcomeUnits" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;"></div>
|
||||
</div>`;
|
||||
const wu = document.getElementById('welcomeUnits');
|
||||
wu.innerHTML = UNITS.slice(0,6).map((u,i) => `<div style="background:#fff;border:1px solid rgba(0,0,0,0.04);border-radius:6px;padding:8px 4px;cursor:pointer;text-align:center;" onclick="startLearn(${i})"><div style="font-size:20px;">${u.em}</div><div style="font-size:8px;color:rgba(26,26,46,0.4);">${u.title}</div></div>`).join('');
|
||||
} else {
|
||||
// Continue learning card
|
||||
const lu = S.lastUnit !== undefined ? UNITS[S.lastUnit] : null;
|
||||
const cc = document.getElementById('continueCard');
|
||||
if (lu && learned > 0) {
|
||||
cc.innerHTML = `<div class="cd" style="background:#f0f0ff;border:1px solid rgba(99,102,241,0.08);cursor:pointer;" onclick="startLearn(${S.lastUnit})">
|
||||
<div class="ct" style="color:#6366f1;">▶ 继续学习</div>
|
||||
<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:28px;">${lu.em}</span><div><div style="font-size:13px;font-weight:700;color:#1a1a2e;">${lu.title}</div><div style="font-size:10px;color:rgba(26,26,46,0.25);">${lu.sub}</div></div></div></div>`;
|
||||
} else if (!lu && learned > 0) {
|
||||
cc.innerHTML = `<div class="cd" style="background:#f0f0ff;border:1px solid rgba(99,102,241,0.08);cursor:pointer;" onclick="switchTab('review')">
|
||||
<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:28px;">🔄</span><div><div style="font-size:13px;font-weight:700;color:#1a1a2e;">复习已学单词</div><div style="font-size:10px;color:rgba(26,26,46,0.25);">${learned} 个词,${due} 个待复习</div></div></div></div>`;
|
||||
} else {
|
||||
cc.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Task list: focused, 3-4 items max
|
||||
const gDone = S._grammarProg ? Object.keys(S._grammarProg).length : 0;
|
||||
const wrongCount = Object.values(S.learned).filter(st=>(st.wrong||0)>(st.correct||0)).length;
|
||||
const tasks = [];
|
||||
if (gDone < gTotal) tasks.push({icon:'📖',text:'语法学习 ('+gDone+'/'+gTotal+')',action:'startGrammarLearn()'});
|
||||
if (learned < all.length) tasks.push({icon:'📚',text:'学新词 ('+learned+'/'+all.length+')',action:'startLearn('+(S.lastUnit||0)+')'});
|
||||
if (due > 0) tasks.push({icon:'🔄',text:'复习 '+due+' 个到期词',action:"switchTab('review')"});
|
||||
if (wrongCount > 0) tasks.push({icon:'❌',text:'易错词练习 '+wrongCount+' 题',action:"switchTab('me')"});
|
||||
document.getElementById('taskList').innerHTML = tasks.length ? tasks.map(t =>
|
||||
`<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid rgba(0,0,0,0.03);cursor:pointer;" onclick="${t.action}">
|
||||
<span style="font-size:16px;">${t.icon}</span><span style="font-size:12px;color:rgba(26,26,46,0.5);flex:1;">${t.text}</span><span style="font-size:10px;color:rgba(26,26,46,0.15);">→</span></div>`
|
||||
).join('') : '<div style="text-align:center;padding:6px;font-size:11px;color:rgba(26,26,46,0.2);">🎉 今日任务全部完成</div>';
|
||||
}
|
||||
|
||||
// ===== UNITS =====
|
||||
function renderUnits() {
|
||||
const grid=document.getElementById('unitGrid');
|
||||
grid.innerHTML=UNITS.map((u,i)=>{
|
||||
const done=u.words.filter((w,wi)=>S.learned[uid(i,wi)]&&S.learned[uid(i,wi)].level>=1).length;
|
||||
return `<div class="ui ${done===u.words.length?'done':''}" data-idx="${i}"><div class="ue">${u.em}</div><div class="ut">${u.title}</div><div class="ud">${u.sub}</div><div class="ub">${done}/${u.words.length} <span class="m">✓</span></div></div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ===== REVIEW =====
|
||||
function renderReview() {
|
||||
const now=Date.now();
|
||||
const all = allWords();
|
||||
const due=all.filter(w=>{const st=S.learned[uid(w.ui,w.wi)];return st&&st.level<3&&st.nextReview>0&&st.nextReview<=now;});
|
||||
const tomorrow = now + 86400000;
|
||||
const dueTomorrow = all.filter(w=>{const st=S.learned[uid(w.ui,w.wi)];return st&&st.level<3&&st.nextReview>now&&st.nextReview<=tomorrow;});
|
||||
const dueWeek = all.filter(w=>{const st=S.learned[uid(w.ui,w.wi)];return st&&st.level<3&&st.nextReview>tomorrow&&st.nextReview<=now+604800000;});
|
||||
const rc=document.getElementById('reviewContent');
|
||||
|
||||
// SRS schedule top
|
||||
let srsHtml = `<div class="cd" style="text-align:left;">
|
||||
<div class="ct">📅 复习安排</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-bottom:6px;">
|
||||
<div style="background:#fef2f2;border-radius:6px;padding:8px;text-align:center;">
|
||||
<div style="font-size:18px;font-weight:900;color:#dc2626;">${due.length}</div>
|
||||
<div style="font-size:9px;color:rgba(26,26,46,0.3);">今天到期</div>
|
||||
</div>
|
||||
<div style="background:#fefce8;border-radius:6px;padding:8px;text-align:center;">
|
||||
<div style="font-size:18px;font-weight:900;color:#f59e0b;">${dueTomorrow.length}</div>
|
||||
<div style="font-size:9px;color:rgba(26,26,46,0.3);">明天到期</div>
|
||||
</div>
|
||||
<div style="background:#f0f0ff;border-radius:6px;padding:8px;text-align:center;">
|
||||
<div style="font-size:18px;font-weight:900;color:#6366f1;">${dueWeek.length}</div>
|
||||
<div style="font-size:9px;color:rgba(26,26,46,0.3);">本周到期</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (due.length > 0) {
|
||||
srsHtml += `<div style="font-size:10px;color:rgba(26,26,46,0.3);margin-bottom:4px;">立即复习 (${Math.min(due.length,20)}/${due.length})</div>`;
|
||||
due.slice(0,20).forEach(w=>{
|
||||
const st = S.learned[uid(w.ui,w.wi)];
|
||||
const lv = ['新','认','熟','掌'][Math.min(st?st.level:0,3)];
|
||||
srsHtml += `<div class="wi"><div class="we">${w.em}</div><div class="ww"><div class="en">${w.e}</div><div class="zh">${w.c}</div></div><div class="st"><span style="font-size:9px;color:rgba(26,26,46,0.2);">${lv}</span><span class="ws" data-word="${w.e}">🔊</span></div></div>`;
|
||||
});
|
||||
srsHtml += `<button class="big-btn p" onclick="startLearn('all')" style="margin-top:4px;">🎯 复习这些词</button>`;
|
||||
} else {
|
||||
srsHtml += `<div style="text-align:center;padding:8px;color:rgba(26,26,46,0.2);font-size:12px;">🎉 没有待复习的单词!</div>`;
|
||||
}
|
||||
srsHtml += `</div>`;
|
||||
|
||||
// Reading section
|
||||
let readingHtml = `<div class="cd" style="text-align:left;"><div class="ct">📖 阅读理解</div>`;
|
||||
const shuffled = shuffle([...READINGS]).slice(0,3);
|
||||
shuffled.forEach(r => {
|
||||
readingHtml += `<div style="padding:8px 0;border-bottom:1px solid rgba(0,0,0,0.02);cursor:pointer;" onclick="startReading('${r.title}')">
|
||||
<div style="font-size:12px;font-weight:600;color:#1a1a2e;">${r.title}</div>
|
||||
<div style="font-size:10px;color:rgba(26,26,46,0.3);">${r.passage.substring(0,50)}…</div>
|
||||
</div>`;
|
||||
});
|
||||
readingHtml += `<div style="text-align:center;margin-top:6px;"><button class="big-btn g" onclick="startReading()" style="padding:8px;font-size:12px;">📖 随机一篇阅读</button></div></div>`;
|
||||
|
||||
rc.innerHTML = srsHtml + readingHtml;
|
||||
}
|
||||
|
||||
// ===== ME =====
|
||||
function renderMe() {
|
||||
const tq=S.totalQuiz||0, tc=S.totalCorrect||0;
|
||||
const acc=tq?Math.round(tc/tq*100):0;
|
||||
const wc=Object.values(S.learned).filter(st=>(st.wrong||0)>(st.correct||0)).length;
|
||||
document.getElementById('mTotalQuiz').textContent=tq;
|
||||
document.getElementById('mAccuracy').textContent=acc+'%';
|
||||
document.getElementById('mWrongCount').textContent=wc;
|
||||
const mg=document.getElementById('grammarCardList');
|
||||
if (!S._grammarProg) S._grammarProg = {};
|
||||
const total = GRAMMAR.length;
|
||||
const done = Object.keys(S._grammarProg).length;
|
||||
mg.innerHTML = `<div style="margin-bottom:8px;font-size:11px;color:rgba(26,26,46,0.3);">${done}/${total} 已完成</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">${GRAMMAR.map((g,i)=>{
|
||||
const learned = S._grammarProg[i];
|
||||
return `<div class="grammar-card" data-gidx="${i}" style="background:${learned?'#f0fdf4':'#f8f8fc'};border:1px solid ${learned?'rgba(34,197,94,0.1)':'transparent'};border-radius:8px;padding:10px;cursor:pointer;transition:all 0.15s;text-align:center;">
|
||||
<div style="font-size:20px;margin-bottom:2px;">${learned?'✅':g.icon}</div>
|
||||
<div style="font-size:11px;font-weight:600;color:#1a1a2e;">${g.title}</div>
|
||||
<div style="font-size:9px;color:rgba(26,26,46,0.25);margin-top:2px;">${learned?'已掌握':'点击学习'}</div>
|
||||
</div>`;
|
||||
}).join('')}</div>`;
|
||||
}
|
||||
|
||||
// ===== READING =====
|
||||
let R = {passage:null, title:''};
|
||||
function startReading(title) {
|
||||
const pool = title ? READINGS.filter(r => r.title === title) : shuffle([...READINGS]);
|
||||
const r = pool[0];
|
||||
if (!r) return;
|
||||
R = {passage: r, title: r.title};
|
||||
document.getElementById('pReview').classList.remove('act');
|
||||
document.getElementById('pReading').classList.add('act');
|
||||
renderReading(r);
|
||||
}
|
||||
function renderReading(r) {
|
||||
const lbl = ['A','B','C','D'];
|
||||
const rc = document.getElementById('readingContent');
|
||||
let html = `<div class="cd" style="text-align:left;"><div style="font-size:14px;font-weight:700;color:#1a1a2e;margin-bottom:6px;">📖 ${r.title}</div>
|
||||
<div style="font-size:12px;line-height:1.8;color:rgba(26,26,46,0.6);background:#f8f8fc;border-radius:8px;padding:12px;margin-bottom:10px;white-space:pre-wrap;">${r.passage}</div>`;
|
||||
r.qa.forEach((q, qi) => {
|
||||
const num = '① ② ③' [qi] || (qi+1);
|
||||
html += `<div style="margin:8px 0;padding:6px 0;border-bottom:1px solid rgba(0,0,0,0.02);"><div style="font-size:12px;font-weight:600;margin-bottom:4px;">${num} ${q.q}</div>`;
|
||||
q.o.forEach((o, oi) => {
|
||||
html += `<div class="exam-op read-ans" data-correct="${oi===q.a?'1':'0'}" style="margin:2px 0;"><span class="el">${lbl[oi]}</span><span style="font-size:12px;">${o}</span></div>`;
|
||||
});
|
||||
html += `</div>`;
|
||||
});
|
||||
html += `<button class="big-btn p" id="readingCheck" style="padding:10px;">✓ 检查答案</button></div>`;
|
||||
rc.innerHTML = html;
|
||||
}
|
||||
function checkReading() {
|
||||
const allQ = document.querySelectorAll('.read-ans');
|
||||
let correct = 0, total = 0;
|
||||
allQ.forEach(el => {
|
||||
if (el.dataset.correct === '1') {
|
||||
const isSelected = el.classList.contains('pass');
|
||||
if (isSelected) { correct++; total++; } else total++;
|
||||
}
|
||||
});
|
||||
// Highlight all
|
||||
allQ.forEach(el => { el.classList.add('disabled'); if (el.dataset.correct === '1') el.classList.add('pass'); else if (el.classList.contains('pass')) el.classList.add('fail'); });
|
||||
const pct = total ? Math.round(correct/total*100) : 0;
|
||||
const fb = document.getElementById('readFb') || document.createElement('div');
|
||||
if (!fb.id) { fb.id = 'readFb'; fb.className = 'fb'; document.getElementById('readingContent').appendChild(fb); }
|
||||
fb.className = 'fb show ' + (pct>=80?'pass':'fail');
|
||||
fb.innerHTML = `<div class="fbt">${correct}/${total} 正确 (${pct}%)</div>`;
|
||||
if (pct>=80) { beep('pass'); sparks(6); } else beep('fail');
|
||||
}
|
||||
|
||||
// ===== GRAMMAR LEARNING =====
|
||||
let G = {idx:0,total:0};
|
||||
function startGrammarLearn() {
|
||||
G = {idx:0,total:GRAMMAR.length};
|
||||
document.getElementById('pMe').classList.remove('act');
|
||||
document.getElementById('pGrammarLearn').classList.add('act');
|
||||
renderGrammarCard();
|
||||
}
|
||||
function renderGrammarCard() {
|
||||
if (G.idx >= G.total) { finishGrammarLearn(); return; }
|
||||
document.getElementById('gProg').textContent = `${G.idx+1}/${G.total}`;
|
||||
const g = GRAMMAR[G.idx];
|
||||
const gc = document.getElementById('grammarContent');
|
||||
gc.innerHTML = `<div class="cd" style="text-align:center;padding:24px 18px;">
|
||||
<div style="font-size:48px;margin-bottom:6px;">${g.icon}</div>
|
||||
<div style="font-size:18px;font-weight:700;color:#1a1a2e;margin-bottom:4px;">${g.title}</div>
|
||||
<div style="font-size:13px;color:rgba(26,26,46,0.4);line-height:1.6;margin-bottom:10px;text-align:left;background:#f8f8fc;border-radius:8px;padding:12px;">${g.detail}</div>
|
||||
<div style="text-align:left;margin-bottom:10px;">${(g.examples||[]).map(ex => `<div style="font-size:12px;padding:6px 10px;background:#f0f0ff;border-radius:4px;margin:3px 0;">${ex}</div>`).join('')}</div>
|
||||
<div style="font-size:11px;color:rgba(26,26,46,0.25);background:#fefce8;border-radius:6px;padding:8px 10px;margin-bottom:12px;text-align:left;">💡 ${g.note||''}</div>
|
||||
<button class="big-btn p" id="grammarGotIt" style="padding:12px;">✅ 学会了,练一练</button>
|
||||
</div>`;
|
||||
}
|
||||
function showGrammarQuiz() {
|
||||
const q = GRAMMAR_Q[G.idx];
|
||||
if (!q) { markGrammarDone(); return; }
|
||||
const gc = document.getElementById('grammarContent');
|
||||
const lbl = ['A','B','C','D'];
|
||||
gc.innerHTML = `<div class="cd" style="text-align:center;padding:20px;">
|
||||
<div style="font-size:13px;color:rgba(26,26,46,0.3);margin-bottom:4px;">📝 练一练 — ${GRAMMAR[G.idx].title}</div>
|
||||
<div style="font-size:15px;font-weight:600;color:#1a1a2e;margin:8px 0;">${q.q}</div>
|
||||
${q.o.map((o,i)=>`<div class="exam-op g-quiz" data-idx="${i}" data-correct="${i===q.a?'1':'0'}"><span class="el">${lbl[i]}</span><span style="font-size:13px;">${o}</span></div>`).join('')}
|
||||
<div class="fb" id="gQuizFb"></div>
|
||||
</div>`;
|
||||
}
|
||||
function markGrammarDone() {
|
||||
if (!S._grammarProg) S._grammarProg = {};
|
||||
S._grammarProg[G.idx] = true; sv();
|
||||
G.idx++; renderGrammarCard();
|
||||
}
|
||||
function finishGrammarLearn() {
|
||||
const gc = document.getElementById('grammarContent');
|
||||
const done = Object.keys(S._grammarProg).length;
|
||||
gc.innerHTML = `<div class="cd" style="text-align:center;padding:30px;"><div style="font-size:48px;margin-bottom:4px;">🎉</div><div style="font-size:18px;font-weight:700;color:#1a1a2e;">语法学习完成</div><div style="font-size:13px;color:rgba(26,26,46,0.3);margin:4px 0;">已掌握 ${done}/${GRAMMAR.length} 个语法点</div><button class="big-btn p" onclick="switchTab('me')">返回</button></div>`;
|
||||
if (done >= GRAMMAR.length) { beep('win'); sparks(30); }
|
||||
}
|
||||
|
||||
// ===== EXAM =====
|
||||
function renderExamUnits() {
|
||||
const grid=document.getElementById('examUnitGrid');
|
||||
grid.innerHTML=UNITS.map((u,i)=>{
|
||||
const ex = EXAMS[u.id] || [];
|
||||
const doneInUnit = u.words.filter((w,wi)=>S.learned[uid(i,wi)]&&S.learned[uid(i,wi)].level>=1).length;
|
||||
return `<div class="ui ${doneInUnit===u.words.length?'done':''}" data-idx="${i}"><div class="ue">${u.em}</div><div class="ut">${u.title}</div><div class="ud">${ex.length}题 · ${doneInUnit}/${u.words.length}词</div></div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
let E = {pool:[],idx:0,correct:0,total:0,ui:''};
|
||||
function startExam(ui) {
|
||||
const key=UNITS[ui]?UNITS[ui].id:ui;
|
||||
const pool=EXAMS[key]; if(!pool||!pool.length)return;
|
||||
E={pool:shuffle([...pool]),idx:0,correct:0,total:pool.length,ui:key};
|
||||
// Directly show exam page without switchTab
|
||||
document.querySelectorAll('.pg').forEach(p=>p.classList.remove('act'));
|
||||
document.getElementById('pExam').classList.add('act');
|
||||
renderExamQ();
|
||||
}
|
||||
function renderExamQ() {
|
||||
if (E.idx>=E.pool.length){finishExam();return;}
|
||||
const q=E.pool[E.idx];
|
||||
document.getElementById('eProg').textContent=`${E.idx+1}/${E.total} ✅${E.correct}`;
|
||||
document.getElementById('eTitle').textContent=UNITS.find(u=>u.id===E.ui)?.em||'考试';
|
||||
const ec=document.getElementById('eContent');document.getElementById('eFb').className='fb';
|
||||
if (q.t==='mc'){
|
||||
const lbl=['A','B','C','D'];
|
||||
ec.innerHTML=`<div class="exam-q"><div class="eq">${E.idx+1}. ${q.q}</div>${q.o.map((o,i)=>`<div class="exam-op" data-idx="${i}" data-correct="${i===q.a?'1':'0'}"><span class="el">${lbl[i]}</span><span style="font-size:13px;">${o}</span></div>`).join('')}</div>`;
|
||||
} else if (q.t==='fill' || q.t==='trans'){
|
||||
ec.innerHTML=`<div class="exam-q"><div class="eq">${E.idx+1}. ${q.q}</div><div class="si"><input type="text" id="fillInput" placeholder="输入答案..." style="width:100%;" autocomplete="off"><button id="fillBtn">↵</button></div></div>`;
|
||||
setTimeout(()=>{const si=document.getElementById('fillInput');if(si)si.focus();},100);
|
||||
} else if (q.t==='read'){
|
||||
const subs = q.o || [];
|
||||
let html = `<div class="exam-q"><div style="font-size:12px;color:rgba(26,26,46,0.5);margin-bottom:6px;">📖 阅读理解</div><div class="eq" style="font-size:12px;line-height:1.7;background:#f8f8fc;padding:12px;border-radius:8px;margin-bottom:8px;">${q.q}</div>`;
|
||||
subs.forEach((sub, si) => {
|
||||
const lbl2 = ['A','B','C','D'];
|
||||
html += `<div style="margin:8px 0;padding:6px 0;border-bottom:1px solid rgba(0,0,0,0.02);"><div style="font-size:12px;font-weight:600;margin-bottom:4px;">(${E.idx+1}.${si+1}) ${sub.q}</div>`;
|
||||
sub.a.forEach((opt, oi) => {
|
||||
html += `<div class="exam-op read-q" data-sq="${si}" data-idx="${oi}" data-correct="${oi===sub.c?'1':'0'}" style="margin:2px 0;"><span class="el">${lbl2[oi]}</span><span style="font-size:12px;">${opt}</span></div>`;
|
||||
});
|
||||
html += `</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
ec.innerHTML = html;
|
||||
} else if (q.t==='dialog'){
|
||||
const lbl3=['A','B','C','D'];
|
||||
const lines = q.q.split('\n');
|
||||
let html = `<div class="exam-q"><div style="font-size:12px;color:rgba(26,26,46,0.5);margin-bottom:6px;">💬 补全对话</div>`;
|
||||
lines.forEach(line => {
|
||||
if (line.trim()) html += `<div style="font-size:12px;line-height:1.7;font-family:'Inter',monospace;">${line}</div>`;
|
||||
});
|
||||
q.o.forEach((opts, bi) => {
|
||||
html += `<div style="margin:6px 0;padding:4px 0;"><span style="font-size:10px;color:rgba(26,26,46,0.3);">第${bi+1}空</span><div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:2px;">`;
|
||||
opts.forEach((opt, oi) => {
|
||||
html += `<div class="exam-op dialog-q" data-blank="${bi}" data-idx="${oi}" data-correct="${oi===q.a[bi]?'1':'0'}" style="display:inline-flex;margin:2px;padding:4px 10px;"><span class="el" style="width:18px;height:18px;line-height:18px;font-size:9px;">${lbl3[oi]}</span><span style="font-size:11px;">${opt}</span></div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
});
|
||||
html += `<button class="big-btn p" id="dialogSubmit" style="padding:8px;font-size:13px;">✓ 提交</button></div>`;
|
||||
ec.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
function checkFillExam() {
|
||||
if (E._submitting) return;
|
||||
E._submitting = true;
|
||||
const q=E.pool[E.idx];const inp=document.getElementById('fillInput');if(!inp||!inp.value.trim()){E._submitting=false;return;}
|
||||
const val=inp.value.trim().toLowerCase();
|
||||
let cor = false;
|
||||
if (q.t === 'trans') {
|
||||
// Translation: compare full sentences (ignore punctuation, extra spaces)
|
||||
const clean = s => s.replace(/[.,!?;:'"]/g,'').replace(/\s+/g,' ').trim();
|
||||
cor = clean(val) === clean(q.a.toLowerCase());
|
||||
} else {
|
||||
// Fill: compare parts separated by ; or ,
|
||||
const parts=val.split(/[;,;、]+/).map(s=>s.trim()).filter(s=>s.length>0);
|
||||
const ans=q.a.toLowerCase().split(/[;,;、]+/).map(s=>s.trim()).filter(s=>s.length>0);
|
||||
cor = parts.length === ans.length;
|
||||
if (cor) parts.forEach((p,i)=>{ if (p!==ans[i]) cor=false; });
|
||||
}
|
||||
inp.disabled=true;inp.className=cor?'cor':'wrg';
|
||||
const fb=document.getElementById('eFb');
|
||||
fb.className='fb show '+(cor?'pass':'fail');
|
||||
fb.innerHTML=`<div class="fbt">${cor?'✅ 正确!':'❌ 正确答案:'+q.a}</div><div class="fbd">${q.e||''}</div>`;
|
||||
if(cor){E.correct++;beep('pass');sparks(6);}else beep('fail');
|
||||
if (document.getElementById('fillBtn')) document.getElementById('fillBtn').disabled = true;
|
||||
setTimeout(()=>{E._submitting=false;E.idx++;renderExamQ();},1500);
|
||||
}
|
||||
|
||||
function submitDialog() {
|
||||
const q = E.pool[E.idx];
|
||||
if (!q || q.t !== 'dialog') return;
|
||||
let allCorrect = true;
|
||||
q.o.forEach((opts, bi) => {
|
||||
const selected = document.querySelector(`.dialog-q[data-blank="${bi}"].pass`);
|
||||
if (!selected || selected.dataset.correct !== '1') {
|
||||
allCorrect = false;
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('.dialog-q').forEach(el => el.classList.add('disabled'));
|
||||
q.o.forEach((opts, bi) => {
|
||||
document.querySelectorAll(`.dialog-q[data-blank="${bi}"]`).forEach(el => {
|
||||
if (el.dataset.correct === '1') el.classList.add('pass');
|
||||
});
|
||||
});
|
||||
const fb=document.getElementById('eFb');
|
||||
fb.className='fb show '+(allCorrect?'pass':'fail');
|
||||
fb.innerHTML=`<div class="fbt">${allCorrect?'✅ 对话全对!':'❌ 有错误,已标出正确答案'}</div>`;
|
||||
if(allCorrect){E.correct++;beep('pass');sparks(8);}else beep('fail');
|
||||
document.querySelectorAll('.dialog-q').forEach(el => el.classList.add('disabled'));
|
||||
document.getElementById('dialogSubmit').disabled = true;
|
||||
setTimeout(()=>{E.idx++;renderExamQ();},2000);
|
||||
}
|
||||
|
||||
function pickExam(idx) {
|
||||
const opts=document.querySelectorAll('.exam-op');if(!opts.length)return;
|
||||
const q=E.pool[E.idx];opts.forEach(o=>o.classList.add('disabled'));
|
||||
const sel=opts[idx];const cor=sel.dataset.correct==='1';
|
||||
sel.classList.add(cor?'pass':'fail');
|
||||
if(!cor) opts.forEach(o=>{if(o.dataset.correct==='1')o.classList.add('pass');});
|
||||
const fb=document.getElementById('eFb');
|
||||
fb.className='fb show '+(cor?'pass':'fail');
|
||||
fb.innerHTML=`<div class="fbt">${cor?'✅ 正确!':'❌ 正确答案已标出'}</div><div class="fbd">${q.e||''}</div>`;
|
||||
if(cor){E.correct++;beep('pass');sparks(6);}else beep('fail');
|
||||
setTimeout(()=>{E.idx++;renderExamQ();},cor?1000:2000);
|
||||
}
|
||||
|
||||
function finishExam() {
|
||||
const pct=E.total?Math.round(E.correct/E.total*100):0;
|
||||
const msg=pct>=90?'🌟 优秀!':pct>=70?'👍 良好':'💪 需要加强';
|
||||
document.getElementById('eContent').innerHTML=`<div class="exam-q" style="text-align:center;"><div style="font-size:40px;font-weight:900;color:#1a1a2e;">${E.correct}/${E.total}</div><div style="font-size:14px;color:rgba(26,26,46,0.4);">${msg}</div></div>`;
|
||||
document.getElementById('eProg').textContent='';document.getElementById('eFb').className='fb';
|
||||
if(pct>=70){showModal(`<div class="mi">🎉</div><div class="ci-pct">${pct}%</div><div style="font-size:13px;color:rgba(26,26,46,0.4);margin:4px 0;">${msg}</div><div class="ci-bar"><div class="ci-fill" style="width:${pct}%"></div></div><button class="p" onclick="document.getElementById('ciModal').classList.remove('show')">继续</button>`);if(pct>=90){sparks(40);beep('win');}else{sparks(15);beep('pass');}}
|
||||
}
|
||||
|
||||
// ===== LEARN FLOW =====
|
||||
let L = {pool:[],idx:0,correct:0,total:0,mode:'learn',streak:0,unit:0};
|
||||
|
||||
function startLearn(unit) {
|
||||
if (typeof unit === 'number') { S.lastUnit = unit; sv(); }
|
||||
const raw = unit==='all' ? allWords() : UNITS[unit].words.map((w,i)=>({...w,ui:unit,wi:i}));
|
||||
L = {pool:shuffle(raw), idx:0, correct:0, total:raw.length, mode:'learn', streak:0, unit};
|
||||
document.getElementById('pHome').classList.remove('act');
|
||||
document.getElementById('pLearn').classList.add('act');
|
||||
// Show grammar+phonetics intro if it's a single unit
|
||||
if (typeof unit === 'number' && UNITS[unit]) {
|
||||
const u = UNITS[unit];
|
||||
const lc = document.getElementById('learnContent');
|
||||
lc.innerHTML = `<div class="cd" style="text-align:left;"><h3 style="font-size:16px;margin-bottom:8px;">${u.em} ${u.title} — 学习要点</h3>
|
||||
<div style="margin-bottom:8px;padding:8px 10px;background:#f0f0ff;border-radius:6px;"><div style="font-size:10px;color:#6366f1;font-weight:700;margin-bottom:2px;">📖 语法</div><div style="font-size:12px;color:rgba(26,26,46,0.5);line-height:1.5;">${u.gr||''}</div></div>
|
||||
<div style="margin-bottom:10px;padding:8px 10px;background:#fefce8;border-radius:6px;"><div style="font-size:10px;color:#f59e0b;font-weight:700;margin-bottom:2px;">🔊 发音</div><div style="font-size:12px;color:rgba(26,26,46,0.5);line-height:1.5;">${u.ph||''}</div></div>
|
||||
<button class="big-btn p" id="startWordBtn">开始学单词 →</button></div>`;
|
||||
document.getElementById('learnProg').style.width = '0%';
|
||||
document.getElementById('learnCount').textContent = '0/'+raw.length;
|
||||
return;
|
||||
}
|
||||
renderLearn();
|
||||
}
|
||||
|
||||
function finishLearn() {
|
||||
const pct = L.total ? Math.round(L.correct/L.total*100) : 0;
|
||||
const emoji = pct>=90?'🎉':pct>=70?'👍':'💪';
|
||||
const msg = pct>=90?'全部掌握!':pct>=70?'不错!':'继续加油!';
|
||||
// Check if this is a single-unit study → offer exam
|
||||
const examKey = typeof L.unit==='number' ? UNITS[L.unit].id : null;
|
||||
const hasExam = examKey && (EXAMS[examKey]||[]).length > 0;
|
||||
const examBtn = hasExam ? `<button class="big-btn o" onclick="startExam(${L.unit})" style="margin-top:8px;">📝 做单元测试 (${(EXAMS[examKey]||[]).length}题)</button>` : '';
|
||||
document.getElementById('learnContent').innerHTML = `<div class="cd" style="text-align:center;padding:30px;">
|
||||
<div style="font-size:48px;margin-bottom:6px;">${emoji}</div>
|
||||
<div style="font-size:18px;font-weight:700;color:#1a1a2e;">${msg}</div>
|
||||
<div style="font-size:13px;color:rgba(26,26,46,0.3);margin:4px 0;">正确 ${L.correct}/${L.total}</div>
|
||||
${examBtn}
|
||||
<button class="big-btn p" onclick="switchTab('home')" style="margin-top:8px;">🏠 返回首页</button>
|
||||
</div>`;
|
||||
document.getElementById('learnProg').style.width = '100%';
|
||||
document.getElementById('learnCount').textContent = L.total+'/'+L.total;
|
||||
if(pct>=70){sparks(20);beep(pct>=90?'win':'pass');}
|
||||
}
|
||||
|
||||
function renderLearn() {
|
||||
if (L.idx >= L.total) { finishLearn(); return; }
|
||||
const q = L.pool[L.idx];
|
||||
const pct = L.total ? Math.round(L.idx/L.total*100) : 0;
|
||||
document.getElementById('learnProg').style.width = pct+'%';
|
||||
document.getElementById('learnCount').textContent = L.idx+'/'+L.total;
|
||||
const lc = document.getElementById('learnContent');
|
||||
lc.innerHTML = `<div class="lc"><div class="le">${q.em}</div><div class="lw">${q.e}</div><div class="lz">${q.c}</div><div class="ls" id="exSentence" style="display:none;">${q.s}</div><div class="lp" id="playSound" data-word="${q.e}">🔊 听发音</div></div>
|
||||
<div style="display:flex;gap:8px;margin-bottom:10px;"><button class="big-btn g" id="showSent" style="flex:1;padding:10px;font-size:13px;">💬 看例句</button><button class="big-btn p" id="knowBtn" style="flex:1;padding:10px;font-size:13px;">✅ 认识</button></div>
|
||||
<button class="big-btn g" id="quizBtn" style="padding:10px;font-size:13px;">🎯 开始测这词</button>`;
|
||||
speak(q.e);
|
||||
}
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
document.addEventListener('click', function(e) {
|
||||
const t = e.target;
|
||||
// Tab switching
|
||||
const tab = t.closest('.tab');
|
||||
if (tab) { const name = tab.dataset.tab; if (name) switchTab(name); return; }
|
||||
// Home: learn
|
||||
if (t.closest('#btnLearnNow')) { startLearn(0); return; }
|
||||
if (t.closest('#startWordBtn')) { L.idx=0; renderLearn(); return; }
|
||||
if (t.closest('#btnGrammarStart')) { startGrammarLearn(); return; }
|
||||
if (t.closest('#btnConjugate')) { startConj('s'); return; }
|
||||
if (t.closest('#doCheckIn')) {
|
||||
const td=parseInt(new Date()/86400000);
|
||||
if (S.lastCheckIn!==td) { S.lastCheckIn=td; S.streak=(S.streak||0)+1; sv(); renderHome(); showModal(`<div class="mi">🌟</div><h2>打卡成功!</h2><p>已连续 ${S.streak} 天</p><button class="p" onclick="document.getElementById('ciModal').classList.remove('show')">继续</button>`); }
|
||||
return;
|
||||
}
|
||||
// Unit click → start learning
|
||||
const ui = t.closest('.ui');
|
||||
if (ui) {
|
||||
const idx = ui.dataset.idx;
|
||||
if (idx !== undefined && ui.closest('#unitGrid')) { startLearn(parseInt(idx)); return; }
|
||||
if (idx !== undefined && ui.closest('#examUnitGrid')) { startExam(parseInt(idx)); return; }
|
||||
return;
|
||||
}
|
||||
// Back buttons
|
||||
if (t.closest('#bkLearn') || t.closest('#bkExam')) { document.getElementById('pLearn').classList.remove('act'); switchTab('home'); if(t.closest('#bkExam')){document.getElementById('pExam').classList.remove('act'); switchTab('home');} return; }
|
||||
if (t.closest('#bkGrammar')) { document.getElementById('pGrammarLearn').classList.remove('act'); switchTab('me'); return; }
|
||||
if (t.closest('#bkConj')) { document.getElementById('pConj').classList.remove('act'); switchTab('me'); return; }
|
||||
if (t.closest('#bkReading')) { document.getElementById('pReading').classList.remove('act'); switchTab('review'); return; }
|
||||
if (t.closest('#readingCheck')) { checkReading(); return; }
|
||||
// Reading answer selection
|
||||
const ra = t.closest('.read-ans');
|
||||
if (ra && !ra.classList.contains('disabled')) {
|
||||
const parent = ra.parentNode;
|
||||
parent.querySelectorAll('.read-ans').forEach(el => el.classList.remove('pass'));
|
||||
ra.classList.add('pass');
|
||||
return;
|
||||
}
|
||||
// Show sentence
|
||||
if (t.closest('#showSent')) {
|
||||
const ls=document.getElementById('exSentence');
|
||||
if(ls){ls.style.display=ls.style.display==='none'?'block':'none';}
|
||||
return;
|
||||
}
|
||||
// Play sound
|
||||
const ps=t.closest('#playSound');
|
||||
if(ps){const w=ps.dataset.word;if(w)speak(w);return;}
|
||||
const ws=t.closest('.ws');
|
||||
if(ws){const w=ws.dataset.word;if(w)speak(w);return;}
|
||||
// Know button → mark learned, next
|
||||
if (t.closest('#knowBtn')) {
|
||||
const q=L.pool[L.idx]; const id=uid(q.ui,q.wi);
|
||||
if(!S.learned[id]) S.learned[id]={level:0,lastReview:0,nextReview:Date.now()+3600000,correct:0,wrong:0};
|
||||
const st=S.learned[id]; st.correct=(st.correct||0)+1; st.level=Math.min(3, st.level+1);
|
||||
st.nextReview=Date.now()+3600000; L.idx++; L.correct++; sv(); beep('pass'); sparks(4); renderLearn(); return;
|
||||
}
|
||||
// Quiz button → enter quiz mode for current word
|
||||
if (t.closest('#quizBtn')) {
|
||||
const q=L.pool[L.idx]; const u=UNITS[q.ui]; if(!u)return;
|
||||
const opts=shuffle([q,...shuffle(u.words.filter(w=>w.e!==q.e)).slice(0,3)]);
|
||||
const lc=document.getElementById('learnContent');
|
||||
lc.innerHTML=`<div class="lc"><div class="lw" style="font-size:28px;">${q.e}</div><div class="lz">选择对应的中文</div></div><div class="qz">${opts.map((o,i)=>`<div class="qo" data-idx="${i}" data-correct="${o.e===q.e?'1':'0'}"><div class="qe">${o.em}</div><div class="qt">${o.c}</div></div>`).join('')}</div><div class="fb" id="learnFb"></div>`;
|
||||
return;
|
||||
}
|
||||
// Quiz option in learn flow
|
||||
const qo=t.closest('.qo');
|
||||
if(qo&&!qo.classList.contains('disabled')&&document.getElementById('pLearn').classList.contains('act')){
|
||||
const q=L.pool[L.idx];const id=uid(q.ui,q.wi);
|
||||
const cor=qo.dataset.correct==='1';
|
||||
document.querySelectorAll('.qo').forEach(o=>o.classList.add('disabled'));
|
||||
if(cor){qo.classList.add('pass');L.correct++;L.streak=(L.streak||0)+1;beep(L.streak>=3?'hattrick':'pass');sparks(6);
|
||||
if(!S.learned[id])S.learned[id]={level:0,lastReview:0,nextReview:0,correct:0,wrong:0};
|
||||
const st=S.learned[id];st.correct=(st.correct||0)+1;st.lastReview=Date.now();st.level=Math.min(3,st.level+1);
|
||||
st.nextReview=Date.now()+3600000;sv();
|
||||
const fb=document.getElementById('learnFb');fb.className='fb show pass';fb.innerHTML=`<div class="fbt">✅ ${q.e}</div>`;
|
||||
setTimeout(()=>{L.idx++;renderLearn();},800);
|
||||
}else{qo.classList.add('fail');L.streak=0;beep('fail');
|
||||
document.querySelectorAll('.qo[data-correct="1"]').forEach(o=>o.classList.add('pass'));
|
||||
if(!S.learned[id])S.learned[id]={level:0,lastReview:0,nextReview:0,correct:0,wrong:0};
|
||||
const st=S.learned[id];st.wrong=(st.wrong||0)+1;st.level=0;st.nextReview=Date.now()+60000;sv();
|
||||
const fb=document.getElementById('learnFb');fb.className='fb show fail';fb.innerHTML=`<div class="fbt">❌ ${q.e} = ${q.c}</div>`;
|
||||
setTimeout(()=>{L.idx++;renderLearn();},1500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Fill submit (exam)
|
||||
if (t.closest('#fillBtn')||t.closest('#fillInput')) { checkFillExam(); return; }
|
||||
// Dialog submit
|
||||
if (t.closest('#dialogSubmit')) { submitDialog(); return; }
|
||||
// Reading comprehension: select option for sub-question
|
||||
const rq = t.closest('.read-q');
|
||||
if (rq && !rq.classList.contains('disabled') && document.getElementById('pExam').classList.contains('act')) {
|
||||
const sq = rq.dataset.sq;
|
||||
document.querySelectorAll(`.read-q[data-sq="${sq}"]`).forEach(el => { el.classList.remove('pass','fail'); });
|
||||
rq.classList.add('pass');
|
||||
// Check if all sub-questions answered
|
||||
const allQ = document.querySelectorAll('.read-q');
|
||||
const ansQ = document.querySelectorAll('.read-q.pass');
|
||||
if (allQ.length === ansQ.length) {
|
||||
document.querySelectorAll('.read-q').forEach(el => el.classList.add('disabled'));
|
||||
let allCorrect = true;
|
||||
allQ.forEach(el => { if (el.dataset.correct !== '1') allCorrect = false; if (el.dataset.correct === '1') el.classList.add('pass'); else el.classList.add('fail'); });
|
||||
const fb=document.getElementById('eFb');
|
||||
fb.className='fb show '+(allCorrect?'pass':'fail');
|
||||
fb.innerHTML=`<div class="fbt">${allCorrect?'✅ 阅读全对!':'❌ 有选错,已标出正确答案'}</div>`;
|
||||
if(allCorrect){E.correct++;beep('pass');sparks(8);}else beep('fail');
|
||||
setTimeout(()=>{E.idx++;renderExamQ();},2000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Dialog: select option for a blank
|
||||
const dq = t.closest('.dialog-q');
|
||||
if (dq && !dq.classList.contains('disabled') && document.getElementById('pExam').classList.contains('act')) {
|
||||
const blank = dq.dataset.blank;
|
||||
document.querySelectorAll(`.dialog-q[data-blank="${blank}"]`).forEach(el => el.classList.remove('pass'));
|
||||
dq.classList.add('pass');
|
||||
return;
|
||||
}
|
||||
// Exam option click
|
||||
const eo=t.closest('.exam-op');
|
||||
if(eo&&!eo.classList.contains('disabled')&&document.getElementById('pExam').classList.contains('act')){pickExam(parseInt(eo.dataset.idx));return;}
|
||||
// Wrong bank button
|
||||
if(t.closest('#btnWrongBank')){switchTab('me');return;}
|
||||
// Conjugation mode switch
|
||||
const cm = t.closest('.cm');
|
||||
if (cm && document.getElementById('pConj').classList.contains('act')) { startConj(cm.dataset.mode); return; }
|
||||
// Conjugation submit
|
||||
if (t.closest('#conjBtn') || t.closest('#conjInput')) { checkConj(); return; }
|
||||
// Grammar card click
|
||||
const gc = t.closest('.grammar-card');
|
||||
if (gc) {
|
||||
if (!S._grammarProg) S._grammarProg = {};
|
||||
G = {idx:parseInt(gc.dataset.gidx), total:GRAMMAR.length};
|
||||
document.getElementById('pMe').classList.remove('act');
|
||||
document.getElementById('pGrammarLearn').classList.add('act');
|
||||
renderGrammarCard();
|
||||
return;
|
||||
}
|
||||
// Grammar "got it" button → show practice quiz
|
||||
if (t.closest('#grammarGotIt')) { showGrammarQuiz(); return; }
|
||||
// Grammar quiz option
|
||||
const gq = t.closest('.g-quiz');
|
||||
if (gq && !gq.classList.contains('disabled')) {
|
||||
document.querySelectorAll('.g-quiz').forEach(el => el.classList.add('disabled'));
|
||||
const cor = gq.dataset.correct === '1';
|
||||
gq.classList.add(cor ? 'pass' : 'fail');
|
||||
if (!cor) document.querySelectorAll('.g-quiz[data-correct="1"]').forEach(el => el.classList.add('pass'));
|
||||
const fb = document.getElementById('gQuizFb');
|
||||
fb.className = 'fb show ' + (cor ? 'pass' : 'fail');
|
||||
if (cor) {
|
||||
fb.innerHTML = '<div class="fbt">✅ 正确!</div>';
|
||||
beep('pass'); sparks(4);
|
||||
setTimeout(() => { markGrammarDone(); }, 1000);
|
||||
} else {
|
||||
fb.innerHTML = '<div class="fbt">❌ 再看一遍语法点</div>';
|
||||
beep('fail');
|
||||
setTimeout(() => { renderGrammarCard(); }, 1500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Clear
|
||||
if(t.closest('#btnClear')){if(confirm('清除所有进度?')){localStorage.removeItem('wordLe3');S={};initState();sv();renderHome();}return;}
|
||||
// Modal close
|
||||
if(t.closest('#ciModal')&&!t.closest('.modal-in')){document.getElementById('ciModal').classList.remove('show');return;}
|
||||
if(t.closest('#ciModal')&&t.tagName==='BUTTON'){document.getElementById('ciModal').classList.remove('show');return;}
|
||||
});
|
||||
|
||||
// Enter key for fill
|
||||
document.addEventListener('keydown',function(e){
|
||||
if(e.key==='Enter'){
|
||||
if(document.getElementById('fillInput')){checkFillExam();return;}
|
||||
if(document.getElementById('conjInput')){checkConj();return;}
|
||||
}
|
||||
});
|
||||
|
||||
// ===== INIT =====
|
||||
ld(); initState(); sv(); renderHome();
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
const fs = require('fs');
|
||||
const h = fs.readFileSync(__dirname + '\\index.html', 'utf8');
|
||||
const m = h.match(/<script>([\s\S]*?)<\/script>/);
|
||||
const js = m[1];
|
||||
|
||||
// Extract chapter data
|
||||
const chapters = [
|
||||
{ id: 1, name: '有理数', standard: '正负数·有理数分类·数轴·相反数·绝对值·加减法·乘除法·乘方·科学记数法·近似数·运算律' },
|
||||
{ id: 2, name: '整式的加减', standard: '单项式·多项式·同类项·去括号·添括号·整式加减·降幂升幂' },
|
||||
{ id: 3, name: '一元一次方程', standard: '等式性质·解方程·去分母·配套·工程·行程·销售·方案·比赛积分·利率' },
|
||||
{ id: 4, name: '几何图形初步', standard: '立体图形·展开图·三视图·线段·射线·直线·中点·角·余角补角·角平分线·方位角·钟面角' }
|
||||
];
|
||||
|
||||
console.log('╔══════════════════════════════════════════════╗');
|
||||
console.log('║ 教育专家 · 严格内容审核报告 ║');
|
||||
console.log('╚══════════════════════════════════════════════╝\n');
|
||||
|
||||
chapters.forEach(ch => {
|
||||
const chStart = js.indexOf("{ id:" + ch.id + ", title:'" + ch.name + "'");
|
||||
const chEnd = ch.id < 4 ? js.indexOf("{ id:" + (ch.id+1) + ", title:'") : js.indexOf("];", chStart);
|
||||
const chContent = js.substring(chStart, chEnd);
|
||||
|
||||
console.log('▬'.repeat(50));
|
||||
console.log('【' + ch.name + '】');
|
||||
|
||||
// Check KPs
|
||||
const kps = chContent.match(/type:'kp', icon:'[^']+', title:'[^']+'/g);
|
||||
if (kps) {
|
||||
console.log('\n 知识点(KP):');
|
||||
kps.forEach(kp => {
|
||||
const title = kp.match(/title:'([^']+)'/)[1];
|
||||
const icon = kp.match(/icon:'([^']+)'/)[1];
|
||||
console.log(' ' + icon + ' ' + title);
|
||||
});
|
||||
}
|
||||
|
||||
// Check for explanatory content quality
|
||||
const kjCount = (chContent.match(/class="kj"/g) || []).length;
|
||||
const qjCount = (chContent.match(/class="qj"/g) || []).length;
|
||||
const vidCount = (chContent.match(/<a href="https:\/\/www\.bilibili/g) || []).length;
|
||||
console.log(' 口诀数: ' + kjCount + ' 巧解数: ' + qjCount + ' 视频数: ' + vidCount);
|
||||
|
||||
// Check question count and distribution
|
||||
const ssQ = chContent.match(/type:'ss'/g);
|
||||
const rvQ = chContent.match(/type:'review'/g);
|
||||
const egQ = chContent.match(/type:'eg'/g);
|
||||
console.log(' SS题:' + (ssQ ? ssQ.length : 0) + ' Review:' + (rvQ ? rvQ.length : 0) + ' EG:' + (egQ ? egQ.length : 0));
|
||||
|
||||
// Check for common explanation pitfalls
|
||||
const hasMistake = chContent.includes('错误') || chContent.includes('易错');
|
||||
const hasLifeExample = chContent.includes('温度计') || chContent.includes('银行卡') || chContent.includes('天平') || chContent.includes('快递');
|
||||
const hasComparison = chContent.includes('对比') || chContent.includes('区别') || chContent.includes('vs');
|
||||
console.log(' 易错提示:' + (hasMistake ? '✅' : '❌') + ' 生活例子:' + (hasLifeExample ? '✅' : '❌') + ' 对比说明:' + (hasComparison ? '✅' : '❌'));
|
||||
});
|
||||
|
||||
// Check overall structure
|
||||
console.log('\n\n▬'.repeat(50));
|
||||
console.log('【总体审核】');
|
||||
|
||||
// 1. 内容完整性检查
|
||||
console.log('\n 1. 知识点覆盖(课标要求 vs 实际覆盖):');
|
||||
const allContent = js;
|
||||
const standardTopics = [
|
||||
['正负数', '正数', '基本概念'],
|
||||
['有理数分类', '整数', '分类标准'],
|
||||
['数轴', '原点', '数轴三要素'],
|
||||
['相反数', '相反数', '符号相反'],
|
||||
['绝对值', '绝对值', '距离'],
|
||||
['加法法则', '同号相加', '运算规则'],
|
||||
['减法法则', '减法', '减法法则'],
|
||||
['乘法法则', '同号得正', '乘法'],
|
||||
['除法法则', '除法', '除以'],
|
||||
['乘方', '乘方', '幂运算'],
|
||||
['科学记数法', '科学记数法', '大数表示'],
|
||||
['近似数', '近似数', '精确度'],
|
||||
['运算律', '交换律', '运算律'],
|
||||
['有效数字', '有效数字', '精确位'],
|
||||
['单项式', '单项式', '整式基础'],
|
||||
['多项式', '多项式', '整式'],
|
||||
['同类项', '同类项', '合并'],
|
||||
['去括号', '去括号', '括号法则'],
|
||||
['添括号', '添括号', '括号'],
|
||||
['降幂排列', '降幂', '排列'],
|
||||
['升幂排列', '升幂', '排列'],
|
||||
['方程', '方程', '等式'],
|
||||
['等式性质', '等式性质', '性质'],
|
||||
['移项', '移项', '过桥'],
|
||||
['去分母', '去分母', '公分母'],
|
||||
['配套问题', '配套', '应用题'],
|
||||
['工程问题', '工程', '效率'],
|
||||
['行程问题', '行程', '路程'],
|
||||
['销售问题', '进价', '利润'],
|
||||
['方案选择', '方案', '比较'],
|
||||
['比赛积分', '积分', '比赛'],
|
||||
['存款利率', '利率', '利息'],
|
||||
['立体图形', '棱柱', '立体'],
|
||||
['展开图', '展开', '展开图'],
|
||||
['线段', '线段', '端点'],
|
||||
['射线', '射线', '端点'],
|
||||
['直线', '直线', '无限'],
|
||||
['中点', '中点', '中点'],
|
||||
['角', '角', '角度'],
|
||||
['余角', '余角', '90°'],
|
||||
['补角', '补角', '180°'],
|
||||
['角平分线', '平分', '角平分线'],
|
||||
['方位角', '北偏', '方位'],
|
||||
['钟面角', '时针', '钟面'],
|
||||
];
|
||||
|
||||
let covered = 0, missing = [];
|
||||
standardTopics.forEach(t => {
|
||||
if (allContent.includes(t[1])) { covered++; }
|
||||
else { missing.push(t[0]); }
|
||||
});
|
||||
console.log(' 覆盖: ' + covered + '/' + standardTopics.length);
|
||||
if (missing.length > 0) console.log(' 缺失: ' + missing.join(', '));
|
||||
|
||||
// 2. 讲解生动性
|
||||
console.log('\n 2. 讲解生动性:');
|
||||
const metaphors = [
|
||||
['照镜子', '相反数'],
|
||||
['天平', '方程'],
|
||||
['快递', '去括号'],
|
||||
['双胞胎', '同类项'],
|
||||
['单词/句子', '整式'],
|
||||
['公路', '数轴'],
|
||||
['温度计', '正负数'],
|
||||
['手电筒', '射线'],
|
||||
['土豆/土豆片', '立体vs平面'],
|
||||
];
|
||||
let metaphorCount = 0;
|
||||
metaphors.forEach(m => {
|
||||
if (allContent.includes(m[0])) { metaphorCount++; console.log(' ✅ ' + m[0] + ' → ' + m[1]); }
|
||||
else { console.log(' ❌ ' + m[0] + '(未使用)'); }
|
||||
});
|
||||
|
||||
// 3. 易错点提示
|
||||
console.log('\n 3. 易错点/常见错误提示:');
|
||||
const tips = [
|
||||
['π是常数', 'π是数字'],
|
||||
['-1⁴≠(-1)⁴', '1⁴'],
|
||||
['平均速度误区', '平均速度'],
|
||||
['去分母漏乘', '漏乘'],
|
||||
['过桥要变号', '过桥'],
|
||||
['负数比较大小', '绝对值大的反而小'],
|
||||
];
|
||||
tips.forEach(t => {
|
||||
if (allContent.includes(t[1])) console.log(' ✅ ' + t[0]);
|
||||
else console.log(' ❌ ' + t[0] + '(未提示)');
|
||||
});
|
||||
|
||||
// 4. 练习多样性
|
||||
console.log('\n 4. 练习多样性:');
|
||||
const calcQ = (allContent.match(/计算/g) || []).length;
|
||||
const conceptQ = (allContent.match(/说法正确的是/g) || []).length;
|
||||
const wordQ = (allContent.match(/km|元|小时|分钟|天/g) || []).length;
|
||||
const graphQ = (allContent.match(/Geo\./g) || []).length;
|
||||
console.log(' 计算题引用:' + calcQ + '次 概念判断题:' + conceptQ + '次');
|
||||
console.log(' 应用题情境:' + wordQ + '次 Geo图形题:' + graphQ + '个');
|
||||
|
||||
// 5. 难度梯度
|
||||
console.log('\n 5. 难度梯度(重点→核心→高频→综合):');
|
||||
const tags = {};
|
||||
const allTags = js.match(/tag:'[^']+'/g);
|
||||
if (allTags) {
|
||||
allTags.forEach(t => {
|
||||
const tag = t.split("'")[1];
|
||||
tags[tag] = (tags[tag] || 0) + 1;
|
||||
});
|
||||
Object.keys(tags).forEach(t => console.log(' ' + t + ': ' + tags[t] + '题'));
|
||||
}
|
||||
|
||||
// 6. 口诀有效性
|
||||
console.log('\n 6. 口诀可记忆性:');
|
||||
const rhymes = [
|
||||
'零上为正零下为负',
|
||||
'数轴一条线原点在中间',
|
||||
'相反数是照镜子',
|
||||
'同号相加符号不变',
|
||||
'奇次方符号不变',
|
||||
'去括号等于拆快递',
|
||||
'移项过桥要变号',
|
||||
'线段两个点射线一个点',
|
||||
'一四一、二三一、二二二、三三',
|
||||
'方位角互看',
|
||||
];
|
||||
rhymes.forEach(r => {
|
||||
if (allContent.includes(r.substring(0, 6))) console.log(' ✅ ' + r.substring(0, 20) + '...');
|
||||
else console.log(' ❌ ' + r.substring(0, 20) + '...(未找到)');
|
||||
});
|
||||
|
||||
console.log('\n' + '═'.repeat(50));
|
||||
console.log('审核结论:');
|
||||
if (covered === standardTopics.length && metaphorCount >= 7) {
|
||||
console.log('✅ 知识点完整,讲解生动,符合教学标准');
|
||||
} else {
|
||||
console.log('⚠️ 存在以下问题:');
|
||||
if (missing.length > 0) console.log(' - 知识点缺失: ' + missing.join(', '));
|
||||
if (metaphorCount < 7) console.log(' - 比喻/类比不足(仅' + metaphorCount + '/9)');
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const htmlPath = 'C:\\Users\\然然小可爱\\Desktop\\数学乐园\\index.html';
|
||||
const fileUrl = 'file:///' + htmlPath.replace(/\\/g, '/');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const page = await browser.newPage({ viewport: { width: 800, height: 900 } });
|
||||
page.on('pageerror', e => console.log(' JS_ERROR:', e.message));
|
||||
|
||||
await page.goto(fileUrl, { timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('=== 测试:EG例题流程 ===');
|
||||
|
||||
// 进入Ch1
|
||||
const cp0 = await page.$('.cp[data-idx="0"]');
|
||||
const b = await cp0.boundingBox();
|
||||
await page.mouse.click(b.x + b.width/2, b.y + b.height/2);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// 点 "发车" 进入KP
|
||||
await page.click('.act.show');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// KP1继续、KP2继续、KP3继续 = 3次
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await page.click('.act.show');
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
|
||||
// 到达EG
|
||||
const egTitle = await page.$eval('.eg-title', e => e.textContent).catch(() => 'NO_EG');
|
||||
console.log('EG标题:', egTitle);
|
||||
|
||||
// 检查EG初始显示几个步骤
|
||||
const stepCnt = await page.$$eval('.eg-step', els => els.length);
|
||||
console.log('初始步骤数:', stepCnt);
|
||||
|
||||
// 检查按钮类型
|
||||
const btnId = await page.evaluate(() => {
|
||||
const btn = document.querySelector('.eg-btn, .act.show');
|
||||
return btn ? (btn.id || btn.className) : 'none';
|
||||
});
|
||||
console.log('初始按钮:', btnId);
|
||||
|
||||
// 逐步骤点击 "下一步"
|
||||
let clickCount = 0;
|
||||
while (true) {
|
||||
const egBtn = await page.$('#egBtn');
|
||||
if (!egBtn) break;
|
||||
await egBtn.click();
|
||||
await page.waitForTimeout(150);
|
||||
clickCount++;
|
||||
const steps = await page.$$eval('.eg-step.show', els => els.length);
|
||||
console.log('点击' + clickCount + '次后, 显示步骤:', steps, '/' + 5);
|
||||
}
|
||||
|
||||
console.log('总"下一步"点击:', clickCount);
|
||||
|
||||
// 现在应该出现 "继续" 按钮
|
||||
const hasContinue = await page.$('.act.show');
|
||||
console.log('有"继续"按钮:', hasContinue ? '✅' : '❌');
|
||||
|
||||
// 点击"继续"
|
||||
if (hasContinue) {
|
||||
await hasContinue.click();
|
||||
await page.waitForTimeout(300);
|
||||
const nextContent = await page.$eval('#cGl', e => e.textContent).catch(() => '');
|
||||
console.log('继续后内容:', nextContent);
|
||||
// 检查是否离开了EG
|
||||
const isStillEG = await page.$('.eg-title');
|
||||
console.log('是否还在EG页:', isStillEG ? '❌ 仍在该页面' : '✅ 已前进');
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
})().catch(e => { console.log('失败:', e.message); process.exit(1); });
|
||||
@@ -0,0 +1,52 @@
|
||||
const { chromium } = require('playwright');
|
||||
const path = 'C:\\Users\\然然小可爱\\Desktop\\数学乐园\\index.html';
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 800, height: 900 } });
|
||||
page.on('pageerror', e => console.log(' ERROR:', e.message));
|
||||
|
||||
await page.goto('file:///' + path.replace(/\\/g, '/'));
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Unlock all
|
||||
await page.evaluate(() => { S.stars = [3,3,3,3]; sv(); renderMap(); });
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Navigate via goChap
|
||||
await page.evaluate(() => goChap(3));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const gl = await page.$eval('#cGl', e => e.textContent).catch(() => 'ERR');
|
||||
console.log('当前段:', gl);
|
||||
|
||||
// Walk through KPs
|
||||
for (let step = 0; step < 7; step++) {
|
||||
const act = await page.$('.act.show');
|
||||
if (act) await act.click(); await page.waitForTimeout(500);
|
||||
const label = await page.$eval('#cGl', e => e.textContent).catch(() => '?');
|
||||
|
||||
const svgAnims = await page.evaluate(() => {
|
||||
const svgs = document.querySelectorAll('#cCo svg');
|
||||
let n = 0;
|
||||
svgs.forEach(s => { if (s.innerHTML.includes('animate')) n++; });
|
||||
return n;
|
||||
});
|
||||
|
||||
console.log((step+1) + '. ' + label + ' SVG动画:' + svgAnims + '个');
|
||||
}
|
||||
|
||||
// Final content check
|
||||
const body = await page.evaluate(() => document.body.innerHTML);
|
||||
const checks = [
|
||||
['展开图滑入', body.includes('展开图演示')],
|
||||
['角度弧线', body.includes('∠=')],
|
||||
['钟面8:30', body.includes('8:30')],
|
||||
['射线延伸', body.includes('x2')],
|
||||
['中点滑动', body.includes('cx"')],
|
||||
];
|
||||
console.log('\n动画内容:');
|
||||
checks.forEach(c => console.log(' ' + (c[1] ? '✅' : '❌') + ' ' + c[0]));
|
||||
|
||||
await browser.close();
|
||||
})().catch(e => console.log('失败:', e.message));
|
||||
@@ -0,0 +1,117 @@
|
||||
const { chromium } = require('playwright');
|
||||
const htmlPath = 'C:\\Users\\然然小可爱\\Desktop\\数学乐园\\index.html';
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 800, height: 900 } });
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
|
||||
let p = 0, f = 0;
|
||||
function chk(name, cond, note) {
|
||||
if (cond) { p++; console.log(' ✅', name); }
|
||||
else { f++; console.log(' ❌', name, note||''); }
|
||||
}
|
||||
|
||||
await page.goto('file:///' + htmlPath.replace(/\\/g, '/'));
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// ===== 1. 主页 =====
|
||||
console.log('【1. 主页】');
|
||||
chk('标题', await page.title() === 'MV1 赛道数学');
|
||||
chk('CP数', (await page.$$('.cp')).length === 4);
|
||||
chk('HUD', await page.$eval('#hStage', e => e.textContent) === '1/4');
|
||||
|
||||
// ===== 2. 进入Ch1推进 =====
|
||||
console.log('\n【2. 学习推进】');
|
||||
const cp = await page.$('.cp[data-idx="0"]');
|
||||
const b = await cp.boundingBox();
|
||||
await page.mouse.click(b.x + b.width/2, b.y + b.height/2);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// 推进函数
|
||||
async function adv() {
|
||||
const act = await page.$('.act.show');
|
||||
if (act) { await act.click(); await page.waitForTimeout(350); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
await adv(); chk('KP1正负数', (await page.$eval('#cGl', e => e.textContent)).includes('正数'));
|
||||
await adv(); chk('KP2数轴', (await page.$eval('#cGl', e => e.textContent)).includes('数轴'));
|
||||
await adv(); chk('KP3相反数', (await page.$eval('#cGl', e => e.textContent)).includes('相反'));
|
||||
|
||||
// EG分步
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const eb = await page.$('#egBtn');
|
||||
if (!eb) break;
|
||||
await eb.click(); await page.waitForTimeout(100);
|
||||
}
|
||||
await adv(); chk('KP4运算', (await page.$eval('#cGl', e => e.textContent)).includes('运算'));
|
||||
await adv(); chk('KP5乘方', (await page.$eval('#cGl', e => e.textContent)).includes('乘方'));
|
||||
await adv(); chk('分站赛入口', await page.$('#stgStart') !== null);
|
||||
|
||||
// ===== 3. 答题 =====
|
||||
console.log('\n【3. 答题】');
|
||||
await page.click('#stgStart'); await page.waitForTimeout(300);
|
||||
chk('题目显示', await page.$('.qt') !== null);
|
||||
chk('4选项', (await page.$$('.opt')).length === 4);
|
||||
|
||||
// 答对
|
||||
const c1 = await page.evaluate(() => SS[0].segs[8].correct);
|
||||
await (await page.$$('.opt'))[c1].click(); await page.waitForTimeout(300);
|
||||
chk('答对反馈', await page.$('.qfb.pass') !== null);
|
||||
|
||||
await page.click('.act.show'); await page.waitForTimeout(200);
|
||||
|
||||
// 答错
|
||||
const c2 = await page.evaluate(() => SS[0].segs[9].correct);
|
||||
await (await page.$$('.opt'))[c2 === 0 ? 1 : 0].click(); await page.waitForTimeout(300);
|
||||
chk('答错反馈', await page.$('.qfb.fail') !== null);
|
||||
chk('错题角标', await page.$eval('#hintBadge', e => parseInt(e.textContent) > 0));
|
||||
|
||||
// ===== 4. 答题地图 =====
|
||||
console.log('\n【4. 答题地图】');
|
||||
await page.click('.act.show'); await page.waitForTimeout(200);
|
||||
const mk = await page.$('#bkMapBtn');
|
||||
if (mk && (await mk.isVisible())) {
|
||||
await mk.click(); await page.waitForTimeout(300);
|
||||
chk('地图显示', await page.$('#mGrid') !== null);
|
||||
const cells = await page.$$('.qCell');
|
||||
chk('题目格', cells.length > 0, cells.length + '个');
|
||||
chk('正确标记', await page.$('.qCell.correct') !== null);
|
||||
chk('错误标记', await page.$('.qCell.wrong') !== null);
|
||||
if (cells.length > 0) { await cells[3].click(); await page.waitForTimeout(300); chk('跳转', await page.$('.qt') !== null); }
|
||||
}
|
||||
|
||||
// ===== 5. 错题集 =====
|
||||
console.log('\n【5. 错题集】');
|
||||
// 先回章节页再回主页
|
||||
const bm = await page.$('#bkChapM');
|
||||
if (bm && (await bm.isVisible())) { await bm.click(); await page.waitForTimeout(200); }
|
||||
await page.click('#bkMap'); await page.waitForTimeout(200);
|
||||
chk('回到主页', (await page.evaluate(() => document.querySelector('.pg.act')?.id)) === 'pH');
|
||||
|
||||
await page.click('#wrongBtn'); await page.waitForTimeout(300);
|
||||
chk('错题集显示', (await page.evaluate(() => document.querySelector('.pg.act')?.id)) === 'pW');
|
||||
chk('重做按钮', await page.$('.wrDo') !== null);
|
||||
chk('移除按钮', await page.$('.wrRm') !== null);
|
||||
|
||||
// ===== 6. 内容完整性 =====
|
||||
console.log('\n【6. 内容覆盖】');
|
||||
const txt = await page.evaluate(() => document.body.innerText);
|
||||
const items = [
|
||||
['有理数','有理数'],['正负数','正数'],['数轴','数轴'],['相反数','相反数'],
|
||||
['绝对值','绝对值'],['倒数','倒数'],['乘方','乘方'],['科学记数法','科学记数法'],
|
||||
['单项式','单项式'],['多项式','多项式'],['同类项','同类项'],['去括号','去括号'],
|
||||
['降幂','降幂'],['升幂','升幂'],['等式性质','等式性质'],['去分母','去分母'],
|
||||
['配套','螺栓'],['相遇','相向'],['追及','追及'],['比赛积分','积分'],
|
||||
['利率','利率'],['展开图','一四一'],['中点模型','中点'],['角平分线','平分'],
|
||||
['分类讨论','分类讨论'],['有效数字','有效数字'],['方程','方程'],
|
||||
];
|
||||
items.forEach(i => chk(i[0], txt.includes(i[1])));
|
||||
|
||||
console.log(`\n通过: ${p}/${p+f} JS错误: ${errors.length}`);
|
||||
errors.forEach(e => console.log(' Error:', e));
|
||||
|
||||
await browser.close();
|
||||
})().catch(e => { console.log('异常:', e.message); process.exit(1); });
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user