feat: 单词乐园 v2 — 完整初中英语学习系统

This commit is contained in:
NB-076
2026-06-29 15:22:09 +08:00
parent cbffc7c068
commit 4efe3d22ed
+1462 -865
View File
@@ -2,928 +2,1525 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>📚 单词乐园 — PEP 7上</title> <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"> <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">
<style> <style>
* { margin:0; padding:0; box-sizing:border-box; } *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body { body{font-family:'Noto Sans SC','Inter',sans-serif;background:#f5f5fa;min-height:100vh;color:#1a1a2e;overflow-x:hidden}
font-family:'Noto Sans SC','Inter',sans-serif; .app{max-width:480px;margin:0 auto;padding:0;position:relative;min-height:100vh;background:#fff}
background:#0a0a14; min-height:100vh; color:#c8c8d0; /* TABS */
overflow-x:hidden; .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}
.app { max-width:820px; margin:0 auto; padding:16px; position:relative; min-height:100vh; } .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}
/* HEADER */ .tab.act .tl{color:#6366f1}
.hd {
background:rgba(10,10,26,0.85); border:1px solid rgba(99,102,241,0.12);
padding:10px 16px; display:flex; align-items:center; gap:10px; margin-bottom:14px;
backdrop-filter:blur(8px); border-radius:6px;
}
.hd .lg { font-weight:900; font-size:14px; letter-spacing:1px; }
.hd .lg .g { color:#6366f1; }
.hd .lg .y { color:#f59e0b; }
.hd-s { flex:1; }
.hd-st { text-align:center; padding:0 8px; border-right:1px solid rgba(255,255,255,0.03); }
.hd-st:last-child { border-right:none; }
.hd-st .lb { font-size:8px; color:rgba(255,255,255,0.35); letter-spacing:2px; font-weight:700; }
.hd-st .vl { font-size:16px; font-weight:900; font-family:'Inter',monospace; }
.hd-st .vl.g { color:#6366f1; }
.hd-st .vl.y { color:#f59e0b; }
/* PAGES */ /* PAGES */
.pg { display:none; } .pg{display:none;padding:16px 16px 80px}
.pg.act { display:block; animation:fadeIn 0.3s ease; } .pg.act{display:block;animation:fadeUp 0.3s ease}
@keyframes fadeIn { from{opacity:0;transform:translateY(10px)} to{opacity:1;transform:translateY(0)} } @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 pop{0%{opacity:0;transform:scale(0.85)}50%{transform:scale(1.05)}100%{opacity:1;transform:scale(1)}}
@keyframes flipIn { 0%{opacity:0;transform:rotateY(90deg)} 100%{opacity:1;transform:rotateY(0)} } @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 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 slideUp { from{opacity:0;transform:translateY(20px)} 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 pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.05)} }
/* BTN */
.btn {
display:inline-flex; align-items:center; gap:6px; padding:10px 20px;
border:none; border-radius:6px; font-size:13px; font-weight:700;
font-family:inherit; cursor:pointer; transition:all 0.25s;
}
.btn-p { background:#6366f1; color:#fff; }
.btn-p:hover { background:#4f46e5; transform:translateY(-1px); }
.btn-o { background:#f59e0b; color:#0a0a14; }
.btn-o:hover { background:#d97706; transform:translateY(-1px); }
.btn-gh { background:rgba(255,255,255,0.04); color:rgba(255,255,255,0.6); border:1px solid rgba(255,255,255,0.06); }
.btn-gh:hover { background:rgba(255,255,255,0.06); color:rgba(255,255,255,0.8); }
.btn-s { background:rgba(99,102,241,0.1); color:#6366f1; border:1px solid rgba(99,102,241,0.15); }
.btn-s:hover { background:rgba(99,102,241,0.15); }
.btn-g { background:rgba(16,185,129,0.1); color:#10b981; border:1px solid rgba(16,185,129,0.15); }
.btn-g:hover { background:rgba(16,185,129,0.15); }
/* HOME */ /* HOME */
.h-card { .hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03); .hd h1{font-size:22px;font-weight:900;letter-spacing:-0.5px}
border-radius:8px; padding:18px; margin-bottom:12px; .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)}
.h-card .ht { font-size:13px; font-weight:700; color:rgba(255,255,255,0.5); letter-spacing:1px; margin-bottom:6px; } .strk{display:flex;gap:4px;margin:4px 0 12px}
.streak-bar { .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)}
display:flex; gap:6px; margin:8px 0; .strk .d.on{background:#fef3c7;color:#f59e0b}
} .strk .d.td{background:#eee7ff;color:#6366f1;border:1px solid rgba(99,102,241,0.15)}
.streak-bar .d { /* CARDS */
width:32px; height:40px; border-radius:4px; display:flex; .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)}
align-items:center; justify-content:center; font-size:11px; font-weight:700; .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}
background:rgba(255,255,255,0.03); color:rgba(255,255,255,0.15); .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}
.streak-bar .d.on { background:rgba(245,158,11,0.12); color:#f59e0b; border:1px solid rgba(245,158,11,0.15); } /* STAT ROW */
.streak-bar .d.td { background:rgba(99,102,241,0.12); color:#6366f1; border:1px solid rgba(99,102,241,0.15); animation:pulse 1.5s infinite; } .sr{display:flex;gap:8px}
.sb{flex:1;background:#f8f8fc;border-radius:10px;padding:12px;text-align:center}
.stat-row { display:flex; gap:10px; margin:10px 0; } .sb .n{font-size:20px;font-weight:900;font-family:'Inter',monospace}
.stat-box { .sb .l{font-size:9px;color:rgba(26,26,46,0.3);margin-top:2px}
flex:1; background:rgba(255,255,255,0.02); border-radius:6px; padding:12px; text-align:center; /* UNIT LIST */
border:1px solid rgba(255,255,255,0.03); .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}
.stat-box .n { font-size:24px; font-weight:900; font-family:'Inter',monospace; } .ui:active{transform:scale(0.97)}
.stat-box .l { font-size:10px; color:rgba(255,255,255,0.3); margin-top:2px; } .ui .ue{font-size:24px;margin-bottom:2px}
.stat-box.g { border-left:2px solid #6366f1; } .ui .ut{font-size:12px;font-weight:700;color:#1a1a2e}
.stat-box.y { border-left:2px solid #f59e0b; } .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}
/* UNITS */ .ui .ub .m{color:#6366f1}
.unit-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; } .ui.done{background:#f0fdf4}
.u-card { .ui.done .ut{color:#16a34a}
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03); /* BIG BUTTON */
border-radius:6px; padding:14px; cursor:pointer; transition:all 0.25s; text-align:center; .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)}
.u-card:hover { border-color:rgba(99,102,241,0.15); transform:translateY(-2px); } .big-btn.p{background:#6366f1;color:#fff}
.u-card .un { font-size:22px; margin-bottom:2px; } .big-btn.p:hover{background:#4f46e5}
.u-card .ut { font-size:12px; font-weight:700; color:#fff; } .big-btn.o{background:#f59e0b;color:#fff}
.u-card .ud { font-size:9px; color:rgba(255,255,255,0.25); margin-top:2px; } .big-btn.g{background:#f0fdf4;color:#16a34a;border:1px solid rgba(22,163,74,0.1)}
.u-card .ub { font-size:8px; color:rgba(255,255,255,0.15); margin-top:4px; } /* LEARN CARD */
.u-card .ub .m { color:#6366f1; } .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}
.u-card.done { border-color:rgba(16,185,129,0.1); } .lc .le{font-size:64px;margin-bottom:8px;animation:bIn 0.4s ease}
.u-card.done .un { opacity:0.5; } .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 */ /* WORD LIST */
.wl-h { .wi{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,0.03)}
display:flex; align-items:center; gap:8px; margin-bottom:12px; .wi .we{font-size:26px;width:36px;text-align:center}
padding-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.03); .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)}
.wl-h h2 { font-size:16px; color:#fff; flex:1; } .wi .ws{font-size:14px;cursor:pointer;opacity:0.3;transition:opacity 0.2s;padding:4px}
.wl-h .c { font-size:10px; color:rgba(255,255,255,0.2); font-family:'Inter',monospace; } .wi .ws:active{opacity:0.8}
.wi { .wi .wl{font-size:9px;font-weight:700;width:18px;height:18px;line-height:18px;text-align:center;border-radius:50%}
display:flex; align-items:center; gap:10px; padding:10px 12px; .wi .wl.n{background:#f0f0ff;color:#6366f1}
border-bottom:1px solid rgba(255,255,255,0.02); cursor:pointer; .wi .wl.l{background:#f0fdf4;color:#16a34a}
transition:all 0.2s; border-radius:4px; /* CELEBRATION */
} .pyro{position:fixed;inset:0;pointer-events:none;z-index:99;overflow:hidden}
.wi:hover { background:rgba(255,255,255,0.02); } .pyro .sp{position:absolute;width:4px;height:4px;border-radius:2px;animation:spFall linear forwards}
.wi .we { font-size:28px; flex-shrink:0; width:40px; text-align:center; } @keyframes spFall{0%{transform:translateY(-10px) scale(1);opacity:1}100%{transform:translateY(100vh) scale(0);opacity:0}}
.wi .we.s { font-size:18px; } /* MODAL */
.wi .we.xl { font-size:32px; } .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}
.wi .ww { flex:1; } .modal.show{display:flex}
.wi .ww .en { font-size:15px; font-weight:700; color:#fff; } .modal-in{background:#fff;border-radius:20px;padding:32px 24px;text-align:center;max-width:320px;width:100%;animation:pop 0.4s ease}
.wi .ww .zh { font-size:12px; color:rgba(255,255,255,0.4); } .modal-in .mi{font-size:56px;margin-bottom:6px}
.wi .ww .ex { font-size:10px; color:rgba(255,255,255,0.15); margin-top:2px; line-height:1.4; } .modal-in h2{font-size:18px;color:#1a1a2e;margin-bottom:4px}
.wi .st { flex-shrink:0; } .modal-in p{font-size:12px;color:rgba(26,26,46,0.35);margin-bottom:14px}
.wi .st .lvl { display:inline-block; width:18px; height:18px; line-height:18px; text-align:center; border-radius:50%; font-size:9px; font-weight:700; } .modal-in .ci-pct{font-size:40px;font-weight:900;color:#1a1a2e;font-family:'Inter',monospace}
.wi .st .lvl.n { background:rgba(99,102,241,0.1); color:#6366f1; } .modal-in .ci-bar{height:5px;background:#f0f0f5;border-radius:3px;margin:8px 0;overflow:hidden}
.wi .st .lvl.l { background:rgba(16,185,129,0.1); color:#10b981; } .modal-in .ci-bar .ci-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,#6366f1,#f59e0b,#22c55e);transition:width 0.8s ease}
.wi .st .lvl.m { background:rgba(245,158,11,0.1); color:#f59e0b; } .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}
.wi .st .snd { cursor:pointer; font-size:16px; margin-left:4px; opacity:0.3; transition:opacity 0.2s; } .modal-in button.p{background:#6366f1;color:#fff}
.wi .st .snd:hover { opacity:0.8; } @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}
/* QUIZ */
.qz-h { text-align:center; margin-bottom:14px; }
.qz-h .qp { font-size:11px; color:rgba(255,255,255,0.2); font-family:'Inter',monospace; letter-spacing:1px; }
.qz-h .qt { font-size:13px; color:rgba(255,255,255,0.4); margin-top:2px; }
.qz-card {
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03);
border-radius:12px; padding:28px 20px; text-align:center; margin-bottom:14px;
}
.qz-card .q-em { font-size:72px; margin-bottom:10px; animation:bIn 0.4s ease; }
.qz-card .q-en { font-size:32px; font-weight:900; color:#fff; letter-spacing:-0.5px; animation:bIn 0.4s ease; }
.qz-card .q-zh { font-size:18px; color:rgba(255,255,255,0.4); margin-top:4px; }
.qz-card .q-snd { font-size:24px; cursor:pointer; display:inline-block; margin-top:8px; padding:8px; border-radius:50%; background:rgba(99,102,241,0.06); transition:background 0.2s; }
.qz-card .q-snd:hover { background:rgba(99,102,241,0.12); }
.qz-ops { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.qz-op {
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.04);
border-radius:8px; padding:14px; cursor:pointer; text-align:center;
transition:all 0.25s; display:flex; flex-direction:column; align-items:center; gap:6px;
}
.qz-op:hover { border-color:rgba(99,102,241,0.2); background:rgba(99,102,241,0.03); }
.qz-op .oe { font-size:36px; }
.qz-op .ot { font-size:13px; color:#fff; font-weight:600; }
.qz-op.cor { border-color:#10b981; background:rgba(16,185,129,0.06); animation:bIn 0.3s ease; }
.qz-op.wrg { border-color:#ef4444; background:rgba(239,68,68,0.06); animation:shake 0.4s ease; }
.qz-op.disabled { pointer-events:none; opacity:0.5; }
@keyframes shake { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-8px)} 40%{transform:translateX(8px)} 60%{transform:translateX(-5px)} 80%{transform:translateX(5px)} }
.qz-fb {
text-align:center; padding:14px; margin-top:10px; border-radius:6px; display:none;
}
.qz-fb.show { display:block; animation:slideUp 0.3s ease; }
.qz-fb.pass { background:rgba(16,185,129,0.04); border:1px solid rgba(16,185,129,0.08); }
.qz-fb.fail { background:rgba(239,68,68,0.04); border:1px solid rgba(239,68,68,0.08); }
/* GRAMMAR */
.gr-card {
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03);
border-radius:8px; padding:16px; margin-bottom:10px;
}
.gr-card .gt { font-size:15px; font-weight:700; color:#fff; margin-bottom:6px; }
.gr-card .gt .gi { font-size:18px; margin-right:6px; }
.gr-card .gd { font-size:12px; color:rgba(255,255,255,0.5); line-height:1.7; }
.gr-card .ge { display:inline-block; background:rgba(99,102,241,0.06); border:1px solid rgba(99,102,241,0.08); border-radius:3px; padding:2px 8px; font-size:12px; color:#818cf8; font-weight:600; margin:2px; }
.gr-card .gr { margin-top:6px; font-size:11px; color:rgba(255,255,255,0.25); border-top:1px solid rgba(255,255,255,0.02); padding-top:6px; }
/* CHECK-IN DIALOG */
.modal {
display:none; position:fixed; inset:0; background:rgba(5,5,10,0.85);
backdrop-filter:blur(10px); z-index:100; align-items:center; justify-content:center;
}
.modal.show { display:flex; }
.modal-in {
background:#0a0a1a; border:1px solid rgba(99,102,241,0.1); border-radius:12px;
padding:32px 24px; text-align:center; max-width:340px; width:90%;
animation:bIn 0.5s ease;
}
.modal-in .mi { font-size:64px; margin-bottom:8px; }
.modal-in h2 { font-size:20px; color:#fff; margin-bottom:4px; }
.modal-in p { font-size:12px; color:rgba(255,255,255,0.35); margin-bottom:14px; }
/* PROGRESS BAR */
.pb { height:4px; background:rgba(255,255,255,0.03); border-radius:2px; margin:6px 0; overflow:hidden; }
.pb-f { height:100%; border-radius:2px; background:linear-gradient(90deg,#6366f1,#818cf8); transition:width 0.5s ease; }
/* RESPONSIVE */
@media (max-width:600px) {
.unit-grid { grid-template-columns:1fr; }
.qz-ops { grid-template-columns:1fr; }
.stat-row { flex-direction:column; }
.app { padding:10px; }
}
/* CHECK-IN STAR */
.ci-star {
display:inline-flex; align-items:center; gap:4px;
background:rgba(245,158,11,0.06); border:1px solid rgba(245,158,11,0.1);
padding:8px 16px; border-radius:6px; cursor:pointer; transition:all 0.25s;
}
.ci-star:hover { background:rgba(245,158,11,0.1); }
.ci-star .ci-e { font-size:20px; }
.ci-star .ci-t { font-size:12px; font-weight:700; color:#f59e0b; }
</style> </style>
</head> </head>
<body> <body>
<div class="app"> <div class="app">
<div class="pyro" id="pyro"></div>
<!-- HEADER --> <!-- TAB BAR -->
<div class="hd"> <div class="tabs">
<div class="lg"><span class="g">📚</span> 单词<span class="y">乐园</span></div> <div class="tab act" data-tab="home"><span class="ti">🏠</span><span class="tl">学习</span></div>
<div class="hd-s"></div> <div class="tab" data-tab="review"><span class="ti">🔄</span><span class="tl">复习</span></div>
<div class="hd-st"><div class="lb">🔥 连续</div><div class="vl y" id="hStreak">0天</div></div> <div class="tab" data-tab="me"><span class="ti">👤</span><span class="tl">我的</span></div>
<div class="hd-st"><div class="lb">📖 已学</div><div class="vl g" id="hLearned">0</div></div>
</div> </div>
<!-- HOME PAGE --> <!-- HOME -->
<div class="pg act" id="pHome"> <div class="pg act" id="pHome">
<div class="h-card"> <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="ht">📅 本周打卡</div> <div class="strk" id="weekBar"></div>
<div class="streak-bar" id="weekBar"></div> <div id="checkInArea" style="margin-bottom:8px;"></div>
<div id="checkInArea"></div> <!-- Continue learning -->
<div id="continueCard"></div>
<!-- Today's tasks -->
<div class="cd"><div class="ct">📋 今日任务</div>
<div id="taskList"></div>
</div> </div>
<div style="display:flex;gap:6px;margin:8px 0;">
<div class="h-card"> <div class="sb" style="cursor:pointer;flex:1;" onclick="startConj('s')"><div class="n">🔤</div><div class="l">变形</div></div>
<div class="ht">📊 学习统计</div> <div class="sb" style="cursor:pointer;flex:1;" onclick="startGrammarLearn()"><div class="n">📖</div><div class="l">语法</div></div>
<div class="stat-row"> <div class="sb" style="cursor:pointer;flex:1;" onclick="startReading()"><div class="n">📖</div><div class="l">阅读</div></div>
<div class="stat-box g"><div class="n" id="sLearned">0</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 class="stat-box y"><div class="n" id="sMastered">0</div><div class="l">已掌握</div></div>
<div class="stat-box" style="border-left:2px solid rgba(255,255,255,0.06);"><div class="n" id="sDue">0</div><div class="l">待复习</div></div>
</div>
</div> </div>
<div class="sr"><div class="sb"><div class="n" id="sLearned">0</div><div class="l">已学词</div></div>
<div class="h-card"> <div class="sb"><div class="n" id="sMastered">0</div><div class="l">掌握</div></div>
<div class="ht">📚 单元列表 · PEP 7上</div> <div class="sb"><div class="n" id="sDue">0</div><div class="l">待复习</div></div>
<div class="unit-grid" id="unitGrid"></div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="btn btn-gh" id="btnReview" style="flex:1;">🔄 复习</button>
<button class="btn btn-gh" id="btnGrammar" style="flex:1;">📝 语法</button>
<button class="btn btn-gh" id="btnClear" style="flex:1;">🗑️ 清除进度</button>
</div> </div>
</div> </div>
<!-- UNIT PAGE --> <!-- UNITS -->
<div class="pg" id="pUnit"> <div class="pg" id="pUnits">
<button class="btn btn-gh" id="bkHome" style="margin-bottom:10px;">← 返回</button> <h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">📚 单元列表</h2>
<div class="wl-h" id="unitHeader"></div> <div class="ul" id="unitGrid"></div>
<div id="wordList"></div>
<button class="btn btn-p" id="startQuiz" style="width:100%;margin-top:8px;">🎯 开始测这单元</button>
</div> </div>
<!-- QUIZ PAGE --> <!-- REVIEW -->
<div class="pg" id="pQuiz">
<div class="qz-h"><div class="qp" id="qProg"></div><div class="qt" id="qMode"></div></div>
<div class="qz-card" id="qCard"></div>
<div class="qz-ops" id="qOpts"></div>
<div class="qz-fb" id="qFb"></div>
</div>
<!-- GRAMMAR PAGE -->
<div class="pg" id="pGrammar">
<button class="btn btn-gh" id="bkHomeG" style="margin-bottom:10px;">← 返回</button>
<h2 style="font-size:18px;color:#fff;margin-bottom:10px;">📝 语法知识点 · PEP 7上</h2>
<div id="grammarList"></div>
</div>
<!-- REVIEW PAGE -->
<div class="pg" id="pReview"> <div class="pg" id="pReview">
<button class="btn btn-gh" id="bkHomeR" style="margin-bottom:10px;">← 返回</button> <h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">🔄 复习</h2>
<h2 style="font-size:18px;color:#fff;margin-bottom:10px;">🔄 复习</h2>
<div id="reviewContent"></div> <div id="reviewContent"></div>
</div> </div>
<!-- CHECK-IN MODAL --> <!-- EXAM -->
<div class="modal" id="ciModal"> <div class="pg" id="pExamHome">
<div class="modal-in"> <h2 style="font-size:18px;font-weight:900;margin-bottom:12px;">📝 单元考试</h2>
<div class="mi">🌟</div> <div class="ul" id="examUnitGrid"></div>
<h2>打卡成功!</h2> </div>
<p id="ciMsg">今天又学了新单词!</p> <div class="pg" id="pExam">
<button class="btn btn-o" id="ciBtn">继续学习</button> <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>
</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> <script>
// ===== DATA: PEP Go for it! 7上 ===== // ===== DATA =====
const UNITS = [ const UNITS = [
{ id:'S1', em:'☀️', title:'Good Morning!', sub:'Starter 1 · 字母 & 问候', words:[ {id:'S1',em:'☀️',title:'Good Morning!',sub:'问候·字母',gr:'be动词(am/is/are)I用amyou用arehe/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:'good',c:'好的',em:'👍',s:'Good morning, class!'},{e:'morning',c:'早上',em:'🌅',s:'I have breakfast in the morning.'},
{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:'afternoon',c:'下午',em:'☀️',s:'Good afternoon, Miss Li! 下午好,李老师!'}, {e:'fine',c:'好的;健康的',em:'💪',s:'I am fine, thank you.'},{e:'OK',c:'好;行',em:'👌',s:'OK, let us go!'},
{e:'evening',c:'晚上',em:'🌆',s:'Good evening, Mom! 晚上好,妈妈!'}, {e:'hello',c:'你好',em:'👋',s:'Hello, my friend!'},{e:'hi',c:'嗨',em:'✋',s:'Hi, how are you?'},
{e:'fine',c:'好的;健康的',em:'💪',s:'I am fine, thank you. 我很好,谢谢。'}, {e:'thanks',c:'谢谢',em:'🙏',s:'Thanks for your help.'},{e:'how',c:'怎样;怎么',em:'❓',s:'How are you today?'},
{e:'OK',c:'好;行',em:'👌',s:'OK, let\'s go! 好的,我们走吧!'}, {e:'I',c:'',em:'👤',s:'I am twelve years old.'},{e:'you',c:'你;你们',em:'👉',s:'You are nice.'},
{e:'hello',c:'你好',em:'👋',s:'Hello, my friend! 你好,我的朋友!'}, {e:'am',c:'是(I用)',em:'',s:'I am a student.'},{e:'are',c:'是(you/复数用)',em:'✅',s:'You are my friend.'},
{e:'hi',c:'',em:'',s:'Hi, how are you? 嗨,你好吗?'}, {e:'is',c:'是(he/she/it用)',em:'',s:'She is a teacher.'},{e:'please',c:'请',em:'🙏',s:'Sit down, please.'},
{e:'thanks',c:'谢谢',em:'🙏',s:'Thanks for your help. 谢谢你的帮助。'},
{e:'how',c:'怎样;怎么',em:'❓',s:'How are you today? 你今天怎么样?'},
{e:'am',c:'是',em:'✅',s:'I am a student. 我是一个学生。'},
{e:'are',c:'是',em:'✅',s:'You are my friend. 你是我的朋友。'},
]}, ]},
{ id:'S2', em:'✏️', title:'What\'s This?', sub:'Starter 2 · 物品问答', words:[ {id:'S2',em:'✏️',title:"What's This?",sub:'物品·冠词',gr:'冠词a和ana用于辅音音素前( 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? 这是什么?'}, {e:'what',c:'什么',em:'❓',s:'What is this in English?'},{e:'this',c:'这个',em:'👆',s:'This is my pen.'},
{e:'is',c:'',em:'',s:'This is a book. 这是一本书。'}, {e:'that',c:'那个',em:'👉',s:'That is a ruler.'},{e:'it',c:'它',em:'🎯',s:'It is a map.'},
{e:'this',c:'这个',em:'👆',s:'This is my pen. 这是我的钢笔。'}, {e:'a',c:'一个(辅音前)',em:'1️⃣',s:'I have a pen.'},{e:'an',c:'一个(元音前)',em:'1️⃣',s:'It is an orange.'},
{e:'that',c:'那个',em:'👉',s:'That is a ruler. 那是一把尺子。'}, {e:'English',c:'英语',em:'🇬🇧',s:'I like English.'},{e:'map',c:'地图',em:'🗺️',s:'This is a map of China.'},
{e:'it',c:'',em:'🎯',s:'It is a map. 它是一张地图。'}, {e:'key',c:'钥匙',em:'🔑',s:'Where is my key?'},{e:'jacket',c:'夹克衫',em:'🧥',s:'My jacket is blue.'},
{e:'a',c:'一个',em:'1️⃣',s:'I have a pen. 我有一支钢笔。'}, {e:'cup',c:'杯子',em:'🥤',s:'This cup is red.'},{e:'ruler',c:'尺子',em:'📏',s:'I need a ruler.'},
{e:'an',c:'一个(元音前)',em:'1️⃣',s:'It is an orange. 它是一个橙子。'}, {e:'pen',c:'钢笔',em:'🖊️',s:'This pen is black.'},{e:'orange',c:'橙子',em:'🍊',s:'The orange is sweet.'},
{e:'map',c:'地图',em:'🗺️',s:'This is a map of China. 这是一张中国地图。'}, {e:'spell',c:'拼写',em:'🔤',s:'How do you spell it?'},{e:'in',c:'用;在…里',em:'📥',s:'What is this in English?'},
{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:'please',c:'请',em:'🙏',s:'Sit down, please. 请坐下。'},
]}, ]},
{ id:'S3', em:'🌈', title:'What Color?', sub:'Starter 3 · 颜色', words:[ {id:'S3',em:'🌈',title:'What Color?',sub:'颜色',gr:'情态动词cancan + 动词原形,表示"能、会"。否定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:'color',c:'颜色',em:'🎨',s:'What color is it?'},{e:'red',c:'红色',em:'🔴',s:'The apple is red.'},
{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:'yellow',c:'色',em:'🟡',s:'The sun is yellow. 太阳是黄色的。'}, {e:'blue',c:'色',em:'🔵',s:'The sky is blue.'},{e:'black',c:'黑色',em:'⚫',s:'My cat is black.'},
{e:'green',c:'绿色',em:'🟢',s:'The tree is green. 树是绿色的。'}, {e:'white',c:'色',em:'',s:'Snow is white.'},{e:'purple',c:'紫色',em:'🟣',s:'I like purple flowers.'},
{e:'blue',c:'色',em:'🔵',s:'The sky is blue. 天空是蓝色的。'}, {e:'brown',c:'色',em:'🟤',s:'The dog is brown.'},{e:'see',c:'看见',em:'👁️',s:'I see a red flower.'},
{e:'black',c:'黑色',em:'',s:'My cat is black. 我的猫是黑色的。'}, {e:'can',c:'能;会',em:'💪',s:'I can see a bird.'},{e:'say',c:'说',em:'💬',s:'Say it in English.'},
{e:'white',c:'白色',em:'',s:'Snow is white. 雪是白色的。'}, {e:'my',c:'我的',em:'👤',s:'My bag is red.'},{e:'your',c:'你的',em:'👉',s:'Your book is new.'},
{e:'purple',c:'紫色',em:'🟣',s:'I like purple flowers. 我喜欢紫色的花。'},
{e:'brown',c:'棕色',em:'🟤',s:'The dog is brown. 这只狗是棕色的。'},
]}, ]},
{ id:'U1', em:'👋', title:'My Name\'s Gina', sub:'Unit 1 · 自我介绍 · be动词', words:[ {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:'name',c:'名字',em:'🏷️',s:'My name is Tom.'},{e:'nice',c:'令人愉快的',em:'😊',s:'Nice to meet you!'},
{e:'my',c:'我的',em:'👤',s:'This is my book. 这是我的书。'}, {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:'your',c:'你的',em:'👉',s:'What is your name?'},{e:'his',c:'他的',em:'👦',s:'His name is Mike.'},
{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:'her',c:'她的',em:'👧',s:'Her bag is red. 她的包是红色的。'}, {e:'no',c:'不',em:'👎',s:'No, it isn\'t.'},{e:'he',c:'',em:'👦',s:'He is my friend.'},
{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:'she',c:'',em:'👧',s:'She is a teacher. 她是一位老师。'}, {e:'last',c:'最后的',em:'🏁',s:'My last name is Smith.'},{e:'friend',c:'朋友',em:'🤝',s:'She is my friend.'},
{e:'yes',c:'是的',em:'👍',s:'Yes, I can. 是的,我能。'}, {e:'zero',c:'',em:'0️⃣',s:'zero + one = one'},{e:'one',c:'一',em:'1️⃣',s:'I have one apple.'},
{e:'no',c:'不;不是',em:'👎',s:'No, it isn\'t. 不,它不是。'}, {e:'two',c:'',em:'2️⃣',s:'Two cats are playing.'},{e:'three',c:'三',em:'3️⃣',s:'Three birds.'},
{e:'first',c:'第一的;首先',em:'🥇',s:'This is my first book. 这是我的第一本书。'}, {e:'four',c:'',em:'4️⃣',s:'I have four pens.'},{e:'five',c:'五',em:'5️⃣',s:'Five stars for you!'},
{e:'last',c:'最后的',em:'🏁',s:'My last name is Smith. 我姓史密斯。'}, {e:'six',c:'',em:'6️⃣',s:'Six books.'},{e:'seven',c:'七',em:'7️⃣',s:'Seven days.'},
{e:'friend',c:'朋友',em:'🤝',s:'She is my good friend. 她是我的好朋友。'}, {e:'eight',c:'',em:'8️⃣',s:'I am eight.'},{e:'nine',c:'九',em:'9️⃣',s:'nine + one = ten'},
{e:'zero',c:'',em:'0️⃣',s:'My number has a zero. 我的号码里有一个零。'}, {e:'telephone',c:'电话',em:'📞',s:'My telephone number is 123.'},{e:'number',c:'号码;数字',em:'🔢',s:'What is your number?'},
{e:'one',c:'',em:'1️⃣',s:'I have one apple. 我有一个苹果。'}, {e:'school',c:'学校',em:'🏫',s:'I go to school by bus.'},{e:'middle',c:'中间的',em:'🀄',s:'middle school 中学'},
{e:'two',c:'二',em:'2️⃣',s:'Two cats are playing. 两只猫在玩。'},
{e:'three',c:'三',em:'3️⃣',s:'Three birds are singing. 三只鸟在唱歌。'},
{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 on the desk. 六本书在桌子上。'},
{e:'seven',c:'七',em:'7️⃣',s:'Seven days in a week. 一周有七天。'},
{e:'eight',c:'八',em:'8️⃣',s:'I am eight years old. 我八岁了。'},
{e:'nine',c:'九',em:'9️⃣',s:'Nine + one = ten. 九加一等于十。'},
{e:'telephone',c:'电话',em:'📞',s:'My telephone number is 123. 我的电话号码是123。'},
{e:'number',c:'号码;数字',em:'🔢',s:'What is your number? 你的号码是多少?'},
]}, ]},
{ id:'U2', em:'👨‍👩‍👧‍👦', title:'This Is My Sister', sub:'Unit 2 · 家庭成员 · 指示代词', words:[ {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:'sister',c:'姐妹',em:'👧',s:'My sister is kind.'},{e:'brother',c:'兄;弟',em:'👦',s:'My brother likes sports.'},
{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:'mother',c:'母亲',em:'👩',s:'My mother cooks dinner. 我妈妈做晚饭。'}, {e:'parent',c:'父/母亲',em:'👪',s:'My parents are at home.'},{e:'grandma',c:'奶奶;姥姥',em:'👵',s:'Grandma is nice.'},
{e:'father',c:'父亲',em:'👨',s:'My father is tall. 我爸爸很高。'}, {e:'grandpa',c:'爷爷;姥爷',em:'👴',s:'Grandpa walks every day.'},{e:'grandmother',c:'(外)祖母',em:'👵',s:'Grandma tells stories.'},
{e:'parent',c:'父亲或母亲',em:'👪',s:'My parents are at home. 我的父母在家。'}, {e:'grandfather',c:'(外)祖父',em:'👴',s:'Grandpa reads newspaper.'},{e:'family',c:'家庭',em:'👪',s:'My family has 4 people.'},
{e:'grandmother',c:'祖母',em:'👵',s:'My grandmother tells stories. 我奶奶讲故事。'}, {e:'those',c:'那些',em:'👉👈',s:'Those are my friends.'},{e:'these',c:'这些',em:'👈👈',s:'These are my books.'},
{e:'grandfather',c:'祖父',em:'👴',s:'My grandfather reads newspaper. 我爷爷看报纸。'}, {e:'who',c:'谁',em:'❓',s:'Who is she?'},{e:'aunt',c:'阿姨;姑姑',em:'👩',s:'My aunt is a doctor.'},
{e:'family',c:'家庭',em:'👪',s:'My family has four people. 我家有四口人。'}, {e:'uncle',c:'叔叔;舅舅',em:'👨',s:'My uncle drives a car.'},{e:'son',c:'儿子',em:'👦',s:'He is her son.'},
{e:'those',c:'那些',em:'👉👈',s:'Those are my friends. 那些是我的朋友。'}, {e:'daughter',c:'女儿',em:'👧',s:'She is my daughter.'},{e:'cousin',c:'堂/表兄弟姐妹',em:'🧑‍🤝‍🧑',s:'My cousin is my age.'},
{e:'these',c:'这些',em:'👈👈',s:'These are my books. 这些是我的书。'}, {e:'picture',c:'图片;照片',em:'🖼️',s:'This is a family picture.'},{e:'photo',c:'照片',em:'📸',s:'Look at this photo.'},
{e:'who',c:'',em:'',s:'Who is she? 她是谁?'}, {e:'here',c:'这里',em:'📍',s:'Here is my family photo.'},{e:'dog',c:'',em:'🐕',s:'Our dog is friendly.'},
{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:'picture',c:'图片;照片',em:'🖼️',s:'This is a picture of my family. 这是我家的照片。'},
]}, ]},
{ id:'U3', em:'🎒', title:'Is This Your Pencil?', sub:'Unit 3 · 学习用品 · 物主代词', words:[ {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:'pencil',c:'铅笔',em:'✏️',s:'Is this your pencil?'},{e:'book',c:'书',em:'📖',s:'I read a book every day.'},
{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:'eraser',c:'橡皮',em:'🧹',s:'I need an eraser. 我需要一块橡皮。'}, {e:'dictionary',c:'字典',em:'📕',s:'Look it up in the dictionary.'},{e:'teacher',c:'老师',em:'👩‍🏫',s:'My teacher is nice.'},
{e:'schoolbag',c:'书包',em:'🎒',s:'My schoolbag is heavy. 我的书包很重。'}, {e:'student',c:'学生',em:'🧑‍🎓',s:'He is a good student.'},{e:'classroom',c:'教室',em:'🏫',s:'Our classroom is big.'},
{e:'dictionary',c:'字典',em:'📕',s:'Look it up in the dictionary. 在字典里查一下。'}, {e:'library',c:'图书馆',em:'📚',s:'I like the school library.'},{e:'watch',c:'手表',em:'⌚',s:'My watch is new.'},
{e:'teacher',c:'老师',em:'👩‍🏫',s:'My teacher is nice. 我的老师很好。'}, {e:'ring',c:'戒指',em:'💍',s:'This is a gold ring.'},{e:'bag',c:'包',em:'👜',s:'I have a new bag.'},
{e:'student',c:'学生',em:'🧑‍🎓',s:'He is a good student. 他是个好学生。'}, {e:'baseball',c:'棒球',em:'',s:'Let\'s play baseball.'},{e:'help',c:'帮助',em:'🤲',s:'Can you help me?'},
{e:'classroom',c:'教室',em:'🏫',s:'Our classroom is big. 我们的教室很大。'}, {e:'welcome',c:'欢迎',em:'👏',s:'Welcome to our school!'},{e:'excuse',c:'原谅',em:'🙇',s:'Excuse me!'},
{e:'library',c:'图书馆',em:'📚',s:'I like the school library. 我喜欢学校图书馆。'}, {e:'me',c:'我(宾格)',em:'👤',s:'Excuse me! Help me!'},{e:'find',c:'找到',em:'🔍',s:'I can\'t find my pen.'},
{e:'watch',c:'手表',em:'',s:'My watch is new. 我的手表是新的。'}, {e:'lost',c:'丢失的',em:'😰',s:'I lost my keys.'},{e:'call',c:'打电话',em:'📞',s:'Please call me at 123.'},
{e:'ring',c:'戒指',em:'💍',s:'This is a gold ring. 这是一个金戒指。'}, {e:'must',c:'必须',em:'⚠️',s:'You must finish homework.'},{e:'classmate',c:'同班同学',em:'🧑‍🎓',s:'My classmate is friendly.'},
{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! 欢迎来到我们学校!'},
]}, ]},
{ id:'U4', em:'🛏️', title:'Where\'s My Schoolbag?', sub:'Unit 4 · 方位介词', words:[ {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:'where',c:'在哪里',em:'📍',s:'Where is my pen?'},{e:'desk',c:'书桌',em:'🪑',s:'My book is on the desk.'},
{e:'schoolbag',c:'书包',em:'🎒',s:'Where is my schoolbag? 我的书包在哪里?'}, {e:'table',c:'桌子',em:'🪑',s:'The cat is under the table.'},{e:'chair',c:'椅子',em:'💺',s:'Sit on the chair.'},
{e:'desk',c:'书',em:'🪑',s:'My book is on the desk. 我的书在书桌上。'}, {e:'bookcase',c:'书',em:'📚',s:'Books are in the bookcase.'},{e:'bed',c:'床',em:'🛏️',s:'I sleep on the bed.'},
{e:'table',c:'桌子',em:'🪑',s:'The cat is under the table. 猫在桌子下面。'}, {e:'sofa',c:'沙发',em:'🛋️',s:'The cat is on the sofa.'},{e:'drawer',c:'抽屉',em:'🗄️',s:'It\'s in the drawer.'},
{e:'chair',c:'椅子',em:'💺',s:'Sit on the chair, please. 请坐在椅子上。'}, {e:'plant',c:'植物',em:'🌱',s:'The plant is near the window.'},{e:'under',c:'在…下面',em:'⬇️',s:'The ball is under the chair.'},
{e:'bookcase',c:'书柜',em:'📚',s:'The books are in the bookcase. 书在书柜里。'}, {e:'on',c:'在…上面',em:'⬆️',s:'The book is on the table.'},{e:'in',c:'在…里面',em:'📥',s:'The pen is in the bag.'},
{e:'bed',c:'',em:'🛏',s:'I sleep on the bed. 我在床上睡觉。'}, {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:'sofa',c:'沙发',em:'🛋️',s:'The cat is on the sofa. 猫在沙发上。'}, {e:'between',c:'在…之间',em:'🔄',s:'The ball is between the chairs.'},{e:'near',c:'在…附近',em:'📍',s:'The bank is near my school.'},
{e:'dresser',c:'梳妆台',em:'🪞',s:'The comb is on the dresser. 梳子在梳妆台上。'}, {e:'room',c:'房间',em:'🚪',s:'My room is clean.'},{e:'door',c:'门',em:'🚪',s:'Open the door.'},
{e:'drawer',c:'抽屉',em:'🗄️',s:'It\'s in the drawer. 它在抽屉里。'}, {e:'window',c:'窗户',em:'🪟',s:'The window is big.'},{e:'know',c:'知道',em:'🧠',s:'I don\'t know.'},
{e:'plant',c:'植物',em:'🌱',s:'The plant is near the window. 植物在窗户旁边。'}, {e:'think',c:'认为;想',em:'🤔',s:'I think it\'s in your bag.'},
{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. 书桌在床旁边。'},
]}, ]},
{ id:'U5', em:'⚽', title:'Do You Have a Soccer Ball?', sub:'Unit 5 · 体育 · 一般现在时(有)', words:[ {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。疑问用DoesDoes 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:'have',c:'有(非三单)',em:'🤲',s:'I have a soccer ball.'},{e:'has',c:'有(三单)',em:'🤲',s:'He has a basketball.'},
{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:'soccer',c:'英式足球',em:'',s:'Let\'s play soccer! 我们踢足球吧!'}, {e:'volleyball',c:'球',em:'🏐',s:'She plays volleyball well.'},{e:'tennis',c:'网球',em:'🎾',s:'Tennis is fun.'},
{e:'basketball',c:'球',em:'🏀',s:'I like basketball. 我喜欢篮球。'}, {e:'ball',c:'球',em:'',s:'The ball is red.'},{e:'sport',c:'运动',em:'🏃',s:'My favorite sport is swimming.'},
{e:'volleyball',c:'排球',em:'🏐',s:'She plays volleyball well. 她排球打得很好。'}, {e:'play',c:'玩;打(球)',em:'🎮',s:'I play soccer after school.'},{e:'watch',c:'观看',em:'👀',s:'We watch TV at home.'},
{e:'tennis',c:'网球',em:'🎾',s:'Tennis is fun. 网球很有趣。'}, {e:'game',c:'比赛;游戏',em:'🎯',s:'The game is exciting.'},{e:'fun',c:'有趣的',em:'😄',s:'Playing soccer is fun.'},
{e:'baseball',c:'棒球',em:'',s:'Baseball is popular in the US. 棒球在美国很流行。'}, {e:'boring',c:'无聊的',em:'😴',s:'This movie is boring.'},{e:'difficult',c:'困难的',em:'😰',s:'Math is difficult.'},
{e:'ball',c:'',em:'',s:'The ball is red. 这个球是红色的。'}, {e:'easy',c:'容易的',em:'😊',s:'This test is easy.'},{e:'relaxing',c:'放松的',em:'😌',s:'Music is relaxing.'},
{e:'sport',c:'运动',em:'🏃',s:'My favorite sport is swimming. 我最喜欢的运动是游泳。'}, {e:'let',c:'',em:'',s:'Let\'s go!'},{e:'go',c:'去',em:'🚶',s:'Let\'s go to school.'},
{e:'play',c:'玩;打',em:'🎮',s:'I play soccer after school. 我放学后踢足球。'}, {e:'great',c:'太好了',em:'👍',s:'That sounds great!'},{e:'sound',c:'听起来',em:'🔊',s:'That sounds fun.'},
{e:'watch',c:'观看',em:'👀',s:'We watch TV at home. 我们在家看电视。'}, {e:'class',c:'课;班级',em:'📚',s:'We have English class.'},{e:'late',c:'晚的;迟的',em:'⏰',s:'Don\'t be late!'},
{e:'game',c:'比赛;游戏',em:'🎯',s:'The game is exciting. 比赛很激动人心。'}, {e:'only',c:'只;仅仅',em:'🔢',s:'I have only one ball.'},
{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 for me. 数学对我来说很难。'},
{e:'easy',c:'容易的',em:'😊',s:'This test is easy. 这个测试很简单。'},
{e:'relaxing',c:'放松的',em:'😌',s:'Music is relaxing. 音乐让人放松。'},
]}, ]},
{ id:'U6', em:'🍎', title:'Do You Like Bananas?', sub:'Unit 6 · 食物 · 可数/不可数名词', words:[ {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:'like',c:'喜欢',em:'❤️',s:'I like apples.'},{e:'banana',c:'香蕉',em:'🍌',s:'Bananas are yellow.'},
{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:'apple',c:'苹果',em:'🍎',s:'An apple a day keeps the doctor away. 一天一苹果,医生远离我。'}, {e:'pear',c:'',em:'🍐',s:'The pear is juicy.'},{e:'strawberry',c:'草莓',em:'🍓',s:'I like strawberries.'},
{e:'orange',c:'橙子',em:'🍊',s:'This orange is sweet. 这个橙子很甜。'}, {e:'hamburger',c:'汉堡包',em:'🍔',s:'Let\'s eat hamburgers.'},{e:'rice',c:'米饭[U]',em:'🍚',s:'I eat rice for lunch.'},
{e:'pear',c:'',em:'🍐',s:'The pear is juicy. 这个梨很多汁。'}, {e:'bread',c:'面包[U]',em:'🍞',s:'Bread is good for breakfast.'},{e:'milk',c:'牛奶[U]',em:'🥛',s:'I drink milk every day.'},
{e:'strawberry',c:'草莓',em:'🍓',s:'I like strawberries. 我喜欢草莓。'}, {e:'water',c:'水[U]',em:'💧',s:'Drink more water.'},{e:'egg',c:'鸡蛋',em:'🥚',s:'I have an egg.'},
{e:'hamburger',c:'汉堡包',em:'🍔',s:'Let\'s eat hamburgers. 我们吃汉堡吧。'}, {e:'chicken',c:'鸡肉[U]',em:'🍗',s:'Chicken is delicious.'},{e:'salad',c:'沙拉[U]',em:'🥗',s:'I like fruit salad.'},
{e:'rice',c:'米饭',em:'🍚',s:'I eat rice for lunch. 我午饭吃米饭。'}, {e:'ice cream',c:'冰淇淋[U]',em:'🍦',s:'Ice cream is cold and sweet.'},{e:'healthy',c:'健康的',em:'💪',s:'Eating veg is healthy.'},
{e:'bread',c:'面包',em:'🍞',s:'Bread is good for breakfast. 面包适合当早餐。'}, {e:'food',c:'食物',em:'🍽️',s:'Chinese food is delicious.'},{e:'fruit',c:'水果',em:'🍎',s:'Fruit is good for us.'},
{e:'milk',c:'牛奶',em:'🥛',s:'I drink milk every day. 我每天喝牛奶。'}, {e:'vegetable',c:'蔬菜',em:'🥦',s:'Eat more vegetables.'},{e:'breakfast',c:'早餐',em:'🌅',s:'I have breakfast at 7.'},
{e:'water',c:'',em:'💧',s:'Drink more water. 多喝水。'}, {e:'lunch',c:'午餐',em:'☀️',s:'We have lunch at school.'},{e:'dinner',c:'晚餐',em:'🌙',s:'Family dinner is warm.'},
{e:'egg',c:'鸡蛋',em:'🥚',s:'I have an egg for breakfast. 我早餐吃一个鸡蛋。'}, {e:'carrot',c:'胡萝卜',em:'🥕',s:'Carrots are healthy.'},{e:'tomato',c:'番茄',em:'🍅',s:'Tomatoes are red.'},
{e:'chicken',c:'鸡肉',em:'🍗',s:'Chicken is delicious. 鸡肉很好吃。'}, {e:'eat',c:'',em:'🍽️',s:'Let\'s eat together.'},{e:'drink',c:'喝',em:'🥤',s:'Drink some water.'},
{e:'salad',c:'沙拉',em:'🥗',s:'I like fruit salad. 我喜欢水果沙拉。'}, {e:'sure',c:'当然',em:'👍',s:'Sure!'},{e:'well',c:'好地',em:'👌',s:'I eat well every day.'},
{e:'ice cream',c:'冰淇淋',em:'🍦',s:'Ice cream is cold and sweet. 冰淇淋又冷又甜。'},
{e:'healthy',c:'健康的',em:'💪',s:'Eating vegetables 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. 家庭晚餐很温馨。'},
]}, ]},
{ id:'U7', em:'👕', title:'How Much Are These Socks?', sub:'Unit 7 · 购物 · 价格', words:[ {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:'much',c:'多;大量',em:'📊',s:'How much is it? 它多少钱?'}, {e:'how much',c:'多少钱',em:'💰',s:'How much is it?'},{e:'dollar',c:'美元',em:'💵',s:'It is five dollars.'},
{e:'how much',c:'多少钱',em:'💰',s:'How much are these shoes? 这些鞋多少钱?'}, {e:'big',c:'大的',em:'🐘',s:'The store is big.'},{e:'small',c:'小的',em:'🐭',s:'I need a small size.'},
{e:'dollar',c:'美元',em:'💵',s:'It is five dollars. 它五美元。'}, {e:'long',c:'长的',em:'📏',s:'This ruler is long.'},{e:'short',c:'短的;矮的',em:'📐',s:'The pencil is short.'},
{e:'big',c:'的',em:'🐘',s:'The store is big. 这个商店很大。'}, {e:'tall',c:'的',em:'🗼',s:'My father is tall.'},{e:'socks',c:'袜子(pl.)',em:'🧦',s:'These socks are warm.'},
{e:'small',c:'小的',em:'🐭',s:'I need a small size. 我需要小号。'}, {e:'shoes',c:'鞋子(pl.)',em:'👟',s:'My shoes are new.'},{e:'shirt',c:'衬衫',em:'👔',s:'Your shirt is nice.'},
{e:'long',c:'长的',em:'📏',s:'This ruler is long. 这把尺子很长。'}, {e:'T-shirt',c:'T恤',em:'👕',s:'I like your T-shirt.'},{e:'sweater',c:'毛衣',em:'🧶',s:'This sweater is soft.'},
{e:'short',c:'短的;矮的',em:'📐',s:'The pencil is short. 这铅笔短了。'}, {e:'jacket',c:'夹克',em:'🧥',s:'Put on your jacket.'},{e:'skirt',c:'裙子',em:'👗',s:'She wears a red skirt.'},
{e:'tall',c:'高的',em:'🗼',s:'My father is tall. 我爸爸很高。'}, {e:'hat',c:'帽子',em:'🎩',s:'This hat is cool.'},{e:'price',c:'价格',em:'🏷️',s:'The price is good.'},
{e:'socks',c:'袜子',em:'🧦',s:'These socks are warm. 这些袜子很暖和。'}, {e:'buy',c:'买',em:'🛒',s:'I want to buy a book.'},{e:'sell',c:'',em:'🏪',s:'The store sells shoes.'},
{e:'shoes',c:'鞋子',em:'👟',s:'My shoes are new. 我的鞋子是新的。'}, {e:'cheap',c:'便宜的',em:'💲',s:'This pen is cheap.'},{e:'expensive',c:'昂贵的',em:'💎',s:'The watch is expensive.'},
{e:'shirt',c:'衬衫',em:'👔',s:'Your shirt is nice. 你的衬衫很漂亮。'}, {e:'store',c:'商店',em:'🏪',s:'The store sells shoes.'},{e:'want',c:'想要',em:'🫴',s:'I want a new bag.'},
{e:'T-shirt',c:'T恤',em:'👕',s:'I like your T-shirt. 我喜欢你的T恤。'}, {e:'take',c:'拿;买下',em:'🛍️',s:'I\'ll take it.'},{e:'need',c:'需要',em:'📋',s:'I need a pair of shoes.'},
{e:'sweater',c:'毛衣',em:'🧶',s:'This sweater is soft. 这件毛衣很柔软。'}, {e:'look',c:'',em:'👀',s:'Look at this nice hat!'},{e:'pair',c:'一双;一对',em:'👫',s:'a pair of socks'},
{e:'jacket',c:'夹克',em:'🧥',s:'Put on your jacket. 穿上你的夹克。'}, {e:'come',c:'',em:'🚶',s:'Come and buy your clothes!'},
{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. 这块手表很贵。'},
]}, ]},
{ id:'U8', em:'🎂', title:'When Is Your Birthday?', sub:'Unit 8 · 日期 · 序数词', words:[ {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:'when',c:'什么时候',em:'📅',s:'When is your birthday?'},{e:'birthday',c:'生日',em:'🎂',s:'Happy birthday!'},
{e:'birthday',c:'生日',em:'🎂',s:'Happy birthday to you! 祝你生日快乐!'}, {e:'month',c:'月份',em:'📅',s:'12 months in a year.'},{e:'January',c:'一月',em:'🎊',s:'New Year is in January.'},
{e:'month',c:'月',em:'📅',s:'There are 12 months in a year. 一年有12个月。'}, {e:'February',c:'月',em:'💕',s:'February is cold.'},{e:'March',c:'三月',em:'🌸',s:'Spring starts in March.'},
{e:'January',c:'月',em:'🎊',s:'New Year is in January. 新年在一月。'}, {e:'April',c:'月',em:'🌧️',s:'April showers bring May flowers.'},{e:'May',c:'五月',em:'🌺',s:'May is beautiful.'},
{e:'February',c:'月',em:'💕',s:'February is cold. 二月很冷。'}, {e:'June',c:'六月',em:'☀️',s:'School ends in June.'},{e:'July',c:'月',em:'🏖️',s:'July is very hot.'},
{e:'March',c:'月',em:'🌸',s:'Spring starts in March. 春天从三月开始。'}, {e:'August',c:'月',em:'🌞',s:'We go swimming in August.'},{e:'September',c:'九月',em:'🍂',s:'School starts in Sep.'},
{e:'April',c:'月',em:'🌧️',s:'April showers bring May flowers. 四月雨带来五月花。'}, {e:'October',c:'月',em:'🎃',s:'Halloween is in October.'},{e:'November',c:'十一月',em:'🍁',s:'November is cool.'},
{e:'May',c:'月',em:'🌺',s:'May is a beautiful month. 五月是美丽的月份。'}, {e:'December',c:'十二月',em:'🎄',s:'Christmas is in December.'},{e:'first',c:'第一(1st)',em:'🥇',s:'first month is January.'},
{e:'June',c:'六月',em:'☀️',s:'School ends in June. 学校六月放假。'}, {e:'second',c:'第二(2nd)',em:'🥈',s:'second month is February.'},{e:'third',c:'第三(3rd)',em:'🥉',s:'third month is March.'},
{e:'July',c:'七月',em:'🏖️',s:'July is very hot. 七月很热。'}, {e:'fourth',c:'第四(4th)',em:'4️⃣',s:'fourth month is April.'},{e:'fifth',c:'第五(5th)',em:'5️⃣',s:'fifth month is May.'},
{e:'August',c:'八月',em:'🌞',s:'We go swimming in August. 我们八月去游泳。'}, {e:'sixth',c:'第六(6th)',em:'6️⃣',s:'sixth month is June.'},{e:'seventh',c:'第七(7th)',em:'7️⃣',s:'seventh month is July.'},
{e:'September',c:'九月',em:'🍂',s:'School starts in September. 学校九月开学。'}, {e:'eighth',c:'第八(8th)',em:'8️⃣',s:'eighth month is August.'},{e:'party',c:'聚会',em:'🎉',s:'Let\'s have a party!'},
{e:'October',c:'十月',em:'🎃',s:'Halloween is in October. 万圣节在十月。'}, {e:'happy',c:'快乐的',em:'😊',s:'I am happy today.'},{e:'year',c:'',em:'📅',s:'A year has 365 days.'},
{e:'November',c:'十一月',em:'🍁',s:'November is cool. 十一月很凉爽。'}, {e:'day',c:'天;日',em:'☀️',s:'7 days in a week.'},{e:'date',c:'日期',em:'📅',s:'What\'s the date today?'},
{e:'December',c:'十二月',em:'🎄',s:'Christmas is in December. 圣诞节在十二月。'}, {e:'trip',c:'旅行',em:'🚗',s:'We have a school trip.'},{e:'test',c:'测试',em:'📝',s:'We have a test today.'},
{e:'first',c:'第一',em:'🥇',s:'I am the first in the race. 我在比赛中得第一。'},
{e:'second',c:'第二',em:'🥈',s:'February is the second month. 二月是第二个月。'},
{e:'third',c:'第三',em:'🥉',s:'He got third place. 他得了第三名。'},
{e:'party',c:'聚会',em:'🎉',s:'Let\'s have a birthday party! 我们开生日派对吧!'},
{e:'happy',c:'快乐的',em:'😊',s:'I am happy today. 我今天很开心。'},
{e:'old',c:'老的;…岁的',em:'🎂',s:'I am twelve years old. 我十二岁了。'},
{e:'age',c:'年龄',em:'🎂',s:'What is your age? 你多大了?'},
]}, ]},
{ id:'U9', em:'📚', title:'My Favorite Subject', sub:'Unit 9 · 科目 · 特殊疑问句', words:[ {id:'U9',em:'📚',title:'My Favorite Subject',sub:'科目·特殊疑问句',gr:'特殊疑问词:What(什么) Where(哪里) Who(谁) When(何时) Why(为什么) How(怎样)。Why用Because回答。星期前用onon 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:'favorite',c:'最喜欢的',em:'⭐',s:'My favorite color is blue.'},{e:'subject',c:'科目',em:'📚',s:'My favorite subject is English.'},
{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:'math',c:'数学',em:'🔢',s:'Math is interesting. 数学很有趣。'}, {e:'Chinese',c:'语文;中文',em:'🇨🇳',s:'Chinese is my native language.'},{e:'science',c:'科学',em:'🔬',s:'Science is fun.'},
{e:'English',c:'英语',em:'🇬🇧',s:'I speak English every day. 我每天说英语。'}, {e:'history',c:'历史',em:'📜',s:'I like history class.'},{e:'music',c:'音乐',em:'🎵',s:'Music makes me happy.'},
{e:'Chinese',c:'语文;中文',em:'🇨🇳',s:'Chinese is my first language. 中文是我的母语。'}, {e:'art',c:'美术',em:'🎨',s:'I draw in art class.'},{e:'P.E.',c:'体育',em:'🏃',s:'P.E. is my favorite.'},
{e:'science',c:'科学',em:'🔬',s:'Science is fun. 科学很有趣。'}, {e:'because',c:'因为',em:'🤷',s:'I like it because it\'s fun.'},{e:'why',c:'为什么',em:'❓',s:'Why do you like English?'},
{e:'history',c:'历史',em:'📜',s:'I like history class. 我喜欢历史课。'}, {e:'lesson',c:'课',em:'📖',s:'We have six lessons a day.'},{e:'finish',c:'完成',em:'',s:'I finish my homework.'},
{e:'music',c:'音乐',em:'🎵',s:'Music makes me happy. 音乐让我快乐。'}, {e:'from',c:'从…',em:'🔄',s:'I am from China.'},{e:'about',c:'关于',em:'📋',s:'This book is about science.'},
{e:'art',c:'美术',em:'🎨',s:'I draw pictures in art class. 我在美术课上画画。'}, {e:'talk',c:'谈话',em:'💬',s:'Let\'s talk about sports.'},{e:'free',c:'空闲的',em:'🆓',s:'I\'m free on Sunday.'},
{e:'P.E.',c:'体育',em:'🏃',s:'P.E. is my favorite. 体育是我最喜欢的。'}, {e:'cool',c:'酷的',em:'😎',s:'That\'s a cool T-shirt!'},{e:'Monday',c:'星期一',em:'1️⃣',s:'Monday is the first day.'},
{e:'geography',c:'地理',em:'🌍',s:'Geography is about the world. 地理是关于世界的。'}, {e:'Tuesday',c:'星期二',em:'2️⃣',s:'Tuesday follows Monday.'},{e:'Wednesday',c:'星期三',em:'3️⃣',s:'Wednesday is mid-week.'},
{e:'biology',c:'生物',em:'🧬',s:'Biology studies living things. 生物学研究生物。'}, {e:'Thursday',c:'星期四',em:'4️⃣',s:'Thursday comes after Wednesday.'},{e:'Friday',c:'星期五',em:'5️⃣',s:'I love Friday!'},
{e:'interesting',c:'有趣的',em:'🤔',s:'This book is interesting. 这本书很有趣。'}, {e:'Saturday',c:'星期六',em:'6️⃣',s:'No school on Saturday!'},{e:'Sunday',c:'星期日',em:'7️⃣',s:'Sunday is for rest.'},
{e:'useful',c:'有用的',em:'🛠️',s:'English is useful. 英语很有用。'}, {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. 比赛很激动人心。'}, {e:'exciting',c:'令人兴奋的',em:'🎉',s:'The game is exciting.'},
{e:'because',c:'因为',em:'🤷',s:'I like it because it\'s fun. 我喜欢它因为它有趣。'},
{e:'why',c:'为什么',em:'❓',s:'Why do you like English? 你为什么喜欢英语?'},
{e:'teacher',c:'老师',em:'👩‍🏫',s:'My teacher is very kind. 我的老师很和蔼。'},
{e:'lesson',c:'课',em:'📖',s:'We have six lessons a day. 我们一天上六节课。'},
{e:'finish',c:'完成',em:'✅',s:'I finish my homework. 我完成了作业。'},
]}, ]},
]; ];
// ===== GRAMMAR DATA ===== // ===== EXAMS (真题风格:阅读+完形+语法+翻译+对话) =====
const GRAMMAR = [ const EXAMS = {
{icon:'✅', title:'be动词 (am/is/are)', detail:'be动词是英语中最基本的系动词,意思为"是"。', S1:[
examples:['I am a student. (我是学生。)','You are my friend. (你是我的朋友。)','He/She/It is nice. (他/她/它很好。)','We/You/They are happy. (我们/你们/他们很开心。)'], {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最贴切。'},
note:'口诀:I用amyou用areis连着he/she/it;复数都用are。'}, {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? = 你呢?'},
{icon:'', title:'一般疑问句 (Yes/No问句)', detail:'把be动词提到主语前,句尾加问号。', {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动词搭配。'},
examples:['Are you a student? (你是学生吗?) → Yes, I am. / No, I\'m not.','Is this your pen? (这是你的钢笔吗?) → Yes, it is. / No, it isn\'t.'], {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:[
note:'回答用Yes/No + 主语 + be动词的肯定/否定形式。'}, {q:'How old is Li Ming?',a:['11','12','13','14'],c:1},
{icon:'🔤', title:'名词复数', detail:'可数名词变复数有规则变化和不规则变化。', {q:'Li Ming\'s English teacher is ______.',a:['Mr. Wang','Ms. Wang','Mr. Li','Ms. Li'],c:1},
examples:['一般+s: book→books, pen→pens','以s/x/sh/ch结尾+es: bus→buses, box→boxes','以辅音+y变y为i+es: family→families','不规则: child→children, man→men, woman→women'], {q:'Li Ming is in ______.',a:['Class 7, Grade 3','Class 3, Grade 7','Grade 7','Class 3'],c:1},
note:'不可数名词(water, rice, bread等)没有复数形式。'}, ]},
{icon:'👤', title:'物主代词 (my/your/his/her)', detail:'物主代词表示"…的",用于名词前。', {t:'mc',q:'I ______ a student. My parents ______ teachers.',o:['am;are','is;are','am;is','are;am'],a:0,e:'I用amparents复数用are。语法点:be动词与主语一致。'},
examples:['This is my book. (这是我的书。)','Is that your pen? (那是你的钢笔吗?)','Her name is Mary. (她的名字是玛丽。)','Our school is big. (我们的学校很大。)'], {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."她是我的表姐。"语法点:否定句+人称代词。'},
note:'形容词性物主代词:my, your, his, her, its, our, your, their。后面必须跟名词。'}, {t:'trans',q:'我是中国人。你叫什么名字?',a:'I am Chinese. What\'s your name?',e:'Chinese: 中国人。What\'s your name? 你叫什么名字?'},
{icon:'📍', title:'方位介词 (in/on/under/between)', detail:'表示位置关系的词,常和the连用。', {t:'trans',q:'她是我的好朋友。我们都在七年级。',a:'She is my good friend. We are in Grade 7.',e:'good friend: 好朋友。Grade 7: 七年级。'},
examples:['The book is on the desk. (书在书桌上。)','The cat is under the table. (猫在桌子下面。)','The pen is in the bag. (钢笔在包里。)','The ball is behind the door. (球在门后面。)'], ],
note:'口诀:on在上面,in在里面,under在下面,behind在后面,next to在旁边。'}, S2:[
{icon:'🔄', title:'一般现在时 (do/does)', detail:'表示经常发生的动作或存在的状态。主语是三单时动词加s/es。', {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。'},
examples:['I like apples. (我喜欢苹果。)','He likes bananas. (他喜欢香蕉。)','She goes to school by bus. (她坐公交上学。)','They play soccer on Sunday. (他们周日踢足球。)'], {t:'mc',q:'— ______ do you ______ "pen"? — P-E-N.',o:['How;spell','What;say','Where;write','Who;read'],a:0,e:'问"怎么拼写"用How。spell: 拼写。语法点:特殊疑问词how的用法。'},
note:'三单动词规则:一般+s (like→likes)s/x/sh/ch+es (watch→watches);辅音+y→ies (study→studies)。'}, {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的用法。'},
{icon:'', title:'否定句 (don\'t/doesn\'t)', detail:'一般现在时的否定:在动词前加don\'t/doesn\'t,动词恢复原形。', {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他的。语法点:形容词性+名词性物主代词。'},
examples:['I don\'t like milk. (我不喜欢牛奶。)','He doesn\'t like sports. (他不喜欢运动。)','She doesn\'t go to school on Sunday. (她周日不上学。)'], {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。'},
note:'doesn\'t后动词必须用原形!不能说"he doesn\'t likes"。'}, {t:'trans',q:'请拼写"钥匙"。那是一张地图。',a:'Please spell "key". That is a map.',e:'please请。spell拼写。that那个(远处)。'},
{icon:'🎯', title:'特殊疑问句 (What/Where/Who/When/Why)', detail:'用疑问词开头的问句,回答不能只用Yes/No。', ],
examples:['What is your name? (你叫什么?) → My name is Tom.','Where is my bag? (我的包在哪?) → It\'s on the desk.','Who is she? (她是谁?) → She is my sister.','When is your birthday? (你的生日在哪天?) → It\'s in May.','Why do you like English? (你为什么喜欢英语?) → Because it\'s fun.'], S3:[
note:'特殊疑问词:What(什么), Where(哪里), Who(谁), When(何时), Why(为什么), How(怎样), How much(多少钱)。'}, {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特殊疑问句。'},
{icon:'🧮', title:'序数词 (first/second/third)', detail:'表示顺序的数词,前面通常加the。', {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。'},
examples:['first (1st) 第一','second (2nd) 第二','third (3rd) 第三','fourth (4th) 第四','fifth (5th) 第五'], {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我们的夹克。语法点:形容词性物主代词。'},
note:'基数词变序数词口诀:一二三特殊记,th要从四加起,ve要用f替,y变ie再加th。'}, {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:[
{icon:'🤔', title:'情态动词 can', detail:'can表示能力"能、会",后面接动词原形。', {q:'What color is the schoolbag?',a:['Blue','White','Blue and white','Black'],c:2},
examples:['I can swim. (我会游泳。)','She can speak English. (她会说英语。)','Can you play soccer? (你会踢足球吗?) → Yes, I can. / No, I can\'t.'], {q:'What is in the schoolbag?',a:['A pen, a ruler and an eraser','Only a book','A cat','Many pencils'],c:0},
note:'can后面必跟动词原形!否定形式can\'t = cannot。'}, {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必须出现。'},
// ===== STATE ===== {t:'trans',q:'我的书包是蓝色的。我能看见一只红色的鸟。',a:'My schoolbag is blue. I can see a red bird.',e:'注意中文没有"的"→英文用my。can see: 能看见。'},
const DAYS = ['日','一','二','三','四','五','六']; ],
let S = {}; U1:[
function ld() { try { const d = localStorage.getItem('wordLe'); if (d) { S = JSON.parse(d); } } catch(e) {} } {t:'mc',q:'— Nice ______ meet you! — Nice to meet you, ______.',o:['to;too','too;to','to;to','too;too'],a:0,e:'to是不定式符号,too表示"也"。语法点:不定式与副词的区分。'},
function sv() { try { localStorage.setItem('wordLe', JSON.stringify(S)); } catch(e) {} } {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物主代词。'},
function initState() { {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动词否定+家庭词汇。'},
const def = { {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问年龄。'},
learned: {}, // { "U1_0": { level:0..3, lastReview:0, nextReview:0, correct:0, wrong:0 } } {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:[
daily: {}, // { "2026-06-29": { learned:5, quiz:10, correct:8 } } {q:'How old is Mike?',a:['12','13','14','11'],c:1},
streak: 0, {q:'Where is Alice from?',a:['England','China','America','Canada'],c:1},
lastCheckIn: 0, {q:'Mike\'s telephone number is ______.',a:['234-5678','324-5678','234-8765','543-5678'],c:0},
totalQuiz: 0, ]},
totalCorrect: 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动词+特殊疑问词。'},
if (!S.learned) Object.assign(S, def); {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来自英国。'},
],
// ===== HELPERS ===== U2:[
function today() { return new Date().toISOString().slice(0,10); } {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指父母两人。语法点:人称代词主格。'},
function wIdx(d) { return d.getDay(); } {t:'mc',q:'— ______ is the girl in the photo? — She\'s my cousin, Lily.',o:['Who','What','Where','How'],a:0,e:'Who问人。语法点:who特殊疑问句。'},
function weekDates() { {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这个(近处)。语法点:指示代词。'},
const d = new Date(); const arr = []; {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:[
for (let i = 0; i < 7; i++) { const c = new Date(d); c.setDate(d.getDate() - d.getDay() + i); arr.push(c); } {q:'How many people are in Lily\'s family?',a:['4','5','6','7'],c:2},
return arr; {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},
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; } {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。'},
function allWords() { const r=[]; UNITS.forEach((u,ui)=>u.words.forEach((w,wi)=>r.push({...w,ui,wi}))); return r; } {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看报纸。语法点:代词+动宾搭配。'},
function speak(text, cb) { {t:'trans',q:'这张照片里有我的父母和我的妹妹。',a:'In this photo are my parents and my sister.',e:'注意英语中先说in the photo再说人。'},
if (!window.speechSynthesis) return; {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医院。'},
window.speechSynthesis.cancel(); ],
const u = new SpeechSynthesisUtterance(text); U3:[
u.lang = 'en-US'; u.rate = 0.9; {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她的。语法点:名词性物主代词。'},
if (cb) u.onend = cb; {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.不客气。'},
window.speechSynthesis.speak(u); {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:[
// ===== RENDER ===== {q:'Where did the student lose his schoolbag?',a:['In the classroom','In the library','In the dining hall','On the playground'],c:1},
function page(id) { {q:'What is NOT in the schoolbag?',a:['Books','A pencil box','An ID card','A cellphone'],c:3},
document.querySelectorAll('.pg').forEach(p=>p.classList.remove('act')); {q:'What\'s the student\'s name?',a:['Tom','Jack','Mike','Alice'],c:1},
document.getElementById(id).classList.add('act'); ]},
} {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找不到。'},
function renderHome() { {t:'trans',q:'请拨打110找警察。',a:'Please call 110 to find the police.',e:'call + 号码 + to find... 打电话找……。'},
// Week bar ],
const wb = document.getElementById('weekBar'); U4:[
const dates = weekDates(); {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动词复数+代词复数。'},
wb.innerHTML = dates.map(d => { {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在…旁边。语法点:方位介词。'},
const key = d.toISOString().slice(0,10); {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在上面。语法点:介词对比例句。'},
const isToday = key === today(); {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表观点。'},
const hasData = S.daily[key]; {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:[
return `<div class="d ${isToday?'td':''} ${hasData?'on':''}">${DAYS[d.getDay()]}</div>`; {q:'Where is the bed?',a:['Next to the desk','Near the window','Under the desk','Behind the door'],c:1},
}).join(''); {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},
// Check-in area ]},
const ci = document.getElementById('checkInArea'); {t:'trans',q:'我的书在书桌里吗?不,它们在书架上。',a:'Are my books in the desk? No, they\'re in the bookcase.',e:'bookcase书架。注意books复数用are。'},
const last = S.lastCheckIn; {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。'},
const td = parseInt(new Date()/86400000); ],
const canCheck = last !== td; U5:[
ci.innerHTML = canCheck {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。语法点:一般现在时疑问/否定。'},
? `<button class="ci-star" id="doCheckIn"><span class="ci-e">⭐</span><span class="ci-t">今日打卡</span></button>` {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。语法点:第三人称单数。'},
: `<div class="ci-star" style="cursor:default;opacity:0.7;"><span class="ci-e">✅</span><span class="ci-t">今日已打卡</span></div>`; {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+动词原形。'},
// Stats {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+形容词。'},
const all = allWords(); {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:[
const learned = Object.keys(S.learned).length; {q:'How many volleyballs does the writer have?',a:['One','Two','Three','Four'],c:1},
const mastered = Object.values(S.learned).filter(v => v.level >= 2).length; {q:'What sport do Tom and the writer NOT have?',a:['Basketball','Volleyball','Soccer','Baseball'],c:3},
const now = Date.now(); {q:'They play basketball ______.',o:['every morning','after school','on weekends','in the evening'],c:1},
const due = Object.values(S.learned).filter(v => v.level < 3 && v.nextReview > 0 && v.nextReview <= now).length; ]},
document.getElementById('sLearned').textContent = learned; {t:'trans',q:'我哥哥有5个篮球。他每天都打球。',a:'My brother has five basketballs. He plays ball every day.',e:'brother三单用has。every day每天。'},
document.getElementById('sMastered').textContent = mastered; {t:'trans',q:'我们不喜欢看电视。它很无聊。',a:'We don\'t like watching TV. It\'s boring.',e:'don\'t like不喜欢。watching TV看电视。boring无聊的。'},
document.getElementById('sDue').textContent = due; ],
U6:[
// Unit grid {t:'mc',q:'— Do you like ______? — Yes, I do. They\'re sweet.',o:['strawberries','strawberrys','strawberry','strawbery'],a:0,e:'strawberry→strawberries(辅音y→ies)。语法点:名词复数变化规则。'},
const grid = document.getElementById('unitGrid'); {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。语法点:一般现在时三单。'},
grid.innerHTML = UNITS.map((u, i) => { {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。语法点:可数/不可数名词。'},
const cnt = u.words.length; {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健康的。语法点:一般疑问句+形容词。'},
const done = u.words.filter((w, wi) => S.learned[uid(i,wi)] && S.learned[uid(i,wi)].level >= 1).length; {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:[
return `<div class="u-card ${done===cnt?'done':''}" data-idx="${i}"> {q:'What does Lucy have for breakfast?',a:['Rice and chicken','An egg, bread and milk','Salad and fruit','Hamburgers'],c:1},
<div class="un">${u.em}</div> {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},
<div class="ut">${u.title}</div> {q:'Lucy has good ______.',o:['eating habits','study habits','sports habits','sleeping habits'],c:0},
<div class="ud">${u.sub}</div> ]},
<div class="ub">${done}/${cnt} <span class="m">✓</span></div> {t:'trans',q:'我妈妈喜欢苹果和香蕉,但她不喜欢冰淇淋。',a:'My mother likes apples and bananas, but she doesn\'t like ice cream.',e:'三单likes。but但是。doesn\'t like不喜欢。'},
</div>`; {t:'trans',q:'胡萝卜对我们的健康有好处。',a:'Carrots are good for our health.',e:'be good for对…有好处。health健康。'},
}).join(''); ],
U7:[
// HUD {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动词复数。'},
document.getElementById('hStreak').textContent = S.streak + '天'; {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+名词。'},
document.getElementById('hLearned').textContent = learned; {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:[
function renderUnit(ui) { {q:'How much are the T-shirts?',a:['$12','$15','$18','$20'],c:1},
const u = UNITS[ui]; {q:'What is on sale for boys?',a:['T-shirts','Shorts','Skirts','Sweaters'],c:1},
document.getElementById('unitHeader').innerHTML = `<h2>${u.em} ${u.title}</h2><div class="c">${u.sub} · ${u.words.length}词</div>`; {q:'The word "sale" in this passage means ______.',o:['销售','促销','拍卖','甩卖'],c:1},
document.getElementById('startQuiz').dataset.ui = ui; ]},
const wl = document.getElementById('wordList'); {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.我买了。'},
wl.innerHTML = u.words.map((w, wi) => { {t:'trans',q:'我们店里所有衣服都在大减价!',a:'All the clothes are on sale in our store!',e:'on sale促销。all所有的。clothes衣服(复数)。'},
const st = S.learned[uid(ui,wi)]; ],
const lv = st ? st.level : 0; U8:[
const lvCls = ['n','l','m','g'][Math.min(lv,3)]; {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区别。'},
const lvLabel = ['新','认','熟','掌'][Math.min(lv,3)]; {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)。语法点:序数词。'},
return `<div class="wi" data-idx="${wi}"> {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。'},
<div class="we ${w.em.length>2?'s':''}">${w.em}</div> {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:[
<div class="ww"> {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},
<div class="en">${w.e}</div> {q:'What is Mary\'s gift for Linda?',a:['A cake','A book','A CD','A doll'],c:1},
<div class="zh">${w.c}</div> {q:'The party is probably on ______.',o:['June 15th','June 5th','July 15th','May 15th'],c:0},
<div class="ex">${w.s}</div> ]},
</div> {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。'},
<div class="st"> {t:'trans',q:'我的生日是3月12日。一年中的第三个月是三月。',a:'My birthday is on March 12th. The third month of the year is March.',e:'on+具体日期。third第三。March三月。'},
<span class="lvl ${lvCls}">${lvLabel}</span> ],
<span class="snd" data-word="${w.e}">🔊</span> U9:[
</div> {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因果句。'},
</div>`; {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表示转折。'},
}).join(''); {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},
// ===== CHECK-IN ===== {q:'Why doesn\'t Tom like math?',a:['It\'s boring','It\'s difficult','The teacher is strict','It\'s too easy'],c:1},
function doCheckIn() { {q:'Which subject begins at 11:00?',o:['History','Science','P.E.','Chinese'],c:2},
const td = parseInt(new Date()/86400000); ]},
S.lastCheckIn = td; {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疑问链。'},
S.streak = (S.streak || 0) + 1; {t:'trans',q:'我最喜欢的科目是历史,因为它很有趣。',a:'My favorite subject is history because it\'s very interesting.',e:'favorite subject最喜欢的科目。because因为。'},
if (!S.daily[today()]) S.daily[today()] = { learned:0, quiz:0, correct:0 }; {t:'trans',q:'我们的语文老师非常和蔼。我们都喜欢她。',a:'Our Chinese teacher is very kind. We all like her.',e:'kind和蔼的。We all like her.我们都喜欢她。'},
S.daily[today()].learned = (S.daily[today()].learned || 0) + 1; ],
sv(); renderHome(); };
// Show modal
const days = S.streak;
document.getElementById('ciMsg').textContent = days >= 7 ? `🔥 连续 ${days} 天!太棒了!` : `已连续打卡 ${days} 天,继续加油!`;
document.getElementById('ciModal').classList.add('show');
}
// ===== EVENT DELEGATION =====
document.addEventListener('click', function(e) {
const t = e.target;
// Unit card
const uc = t.closest('.u-card');
if (uc) { S.curUnit = parseInt(uc.dataset.idx); page('pUnit'); renderUnit(S.curUnit); return; }
// Back
if (t.closest('#bkHome') || t.closest('#bkHomeG') || t.closest('#bkHomeR')) { page('pHome'); renderHome(); return; }
// Speak
const snd = t.closest('.snd');
if (snd) { const w = snd.dataset.word; if (w) speak(w); return; }
// Start quiz
if (t.closest('#startQuiz')) { startQuiz(parseInt(t.closest('#startQuiz').dataset.ui)); return; }
// Quiz option
const qo = t.closest('.qz-op');
if (qo && !qo.classList.contains('disabled')) { pickQuiz(parseInt(qo.dataset.idx)); return; }
// Check-in
if (t.closest('#doCheckIn')) { doCheckIn(); return; }
if (t.closest('#ciBtn')) { document.getElementById('ciModal').classList.remove('show'); return; }
// Grammar
if (t.closest('#btnGrammar')) { page('pGrammar'); renderGrammar(); return; }
// Review
if (t.closest('#btnReview')) { page('pReview'); renderReview(); return; }
// Word in list → quiz single
const wi = t.closest('.wi');
if (wi && !snd) {
const idx = parseInt(wi.dataset.idx);
if (S.curUnit !== undefined) { startQuiz(S.curUnit, idx); }
return;
}
// Clear
if (t.closest('#btnClear')) {
if (confirm('清除所有学习进度?')) { localStorage.removeItem('wordLe'); S={}; initState(); sv(); renderHome(); }
return;
}
});
// ===== QUIZ ENGINE =====
let Q = { pool:[], idx:0, correct:0, total:0, mode:'', ui:-1, done:false };
function startQuiz(ui, startIdx) {
const u = UNITS[ui];
const pool = u.words.map((w, wi) => ({...w, ui, wi}));
Q = { pool: shuffle(pool), idx:0, correct:0, total:pool.length, mode:'en2em', ui, done:false };
if (startIdx !== undefined) {
const si = Q.pool.findIndex(p => p.wi === startIdx);
if (si >= 0) Q.idx = si;
}
page('pQuiz');
renderQuiz();
}
function renderQuiz() {
if (Q.done || Q.idx >= Q.pool.length) { finishQuiz(); return; }
const q = Q.pool[Q.idx];
const prog = `${Q.idx+1}/${Q.total}`;
document.getElementById('qProg').textContent = prog + ' · ✅ '+Q.correct;
document.getElementById('qMode').textContent = UNITS[Q.ui].em + ' ' + UNITS[Q.ui].title;
// Generate options: 3 random wrong + 1 correct
const u = UNITS[Q.ui];
let opts = [q];
const pool = u.words.filter(w => w.e !== q.e);
shuffle(pool);
for (let i = 0; i < 3 && i < pool.length; i++) opts.push(pool[i]);
shuffle(opts);
// Card
const card = document.getElementById('qCard');
card.innerHTML = `<div class="q-zh">选择对应的图片</div><div class="q-en" style="font-size:40px;margin-top:8px;">${q.e}</div>`;
card.innerHTML += `<div class="q-snd" data-word="${q.e}">🔊</div>`;
// Options (emojis only)
const odiv = document.getElementById('qOpts');
odiv.innerHTML = opts.map((o, i) =>
`<div class="qz-op" data-idx="${i}" data-correct="${o.e===q.e?'1':'0'}">
<div class="oe">${o.em}</div>
<div class="ot">${o.c}</div>
</div>`
).join('');
document.getElementById('qFb').className = 'qz-fb';
document.getElementById('qFb').innerHTML = '';
// Auto-speak
setTimeout(() => speak(q.e), 300);
}
function pickQuiz(idx) {
const opts = document.querySelectorAll('.qz-op');
const selected = opts[idx];
const isCorrect = selected.dataset.correct === '1';
const q = Q.pool[Q.idx];
const id = uid(q.ui, q.wi);
// Disable all
opts.forEach(o => o.classList.add('disabled'));
if (isCorrect) {
selected.classList.add('cor');
Q.correct++;
// Update SRS
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();
if (st.correct >= 3) st.level = Math.min(3, st.level + 1);
else if (st.correct >= 1) st.level = Math.max(1, st.level);
// Next review: 1h → 6h → 24h → 7d
const intervals = [0, 3600000, 21600000, 86400000, 604800000];
st.nextReview = Date.now() + intervals[Math.min(st.level + 1, intervals.length - 1)];
// Daily
if (!S.daily[today()]) S.daily[today()] = { learned:0, quiz:0, correct:0 };
S.daily[today()].quiz = (S.daily[today()].quiz || 0) + 1;
S.daily[today()].correct = (S.daily[today()].correct || 0) + 1;
S.totalQuiz++; S.totalCorrect++;
sv();
// Show correct mark on card
const fb = document.getElementById('qFb');
fb.className = 'qz-fb show pass';
fb.innerHTML = `✅ <strong>${q.e}</strong> = ${q.c} ${q.em}<br><span style="font-size:11px;color:rgba(255,255,255,0.3);">${q.s}</span>`;
speak(q.e);
setTimeout(() => { Q.idx++; renderQuiz(); }, 1200);
} else {
selected.classList.add('wrg');
// Highlight correct
opts.forEach(o => { if (o.dataset.correct === '1') o.classList.add('cor'); });
// Update SRS
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; // Retry in 1min
if (!S.daily[today()]) S.daily[today()] = { learned:0, quiz:0, correct:0 };
S.daily[today()].quiz = (S.daily[today()].quiz || 0) + 1;
S.totalQuiz++;
sv();
const fb = document.getElementById('qFb');
fb.className = 'qz-fb show fail';
fb.innerHTML = `❌ 正确答案:<strong>${q.e}</strong> ${q.em} = ${q.c}<br><span style="font-size:11px;color:rgba(255,255,255,0.3);">${q.s}</span>`;
speak(q.e);
setTimeout(() => { Q.idx++; renderQuiz(); }, 2000);
}
}
function finishQuiz() {
const pct = Q.total ? Math.round(Q.correct / Q.total * 100) : 0;
const msg = pct >= 90 ? '🌟 完美!全部掌握!' : pct >= 70 ? '👍 不错,继续加油!' : '💪 多练几次就会了!';
const fb = document.getElementById('qFb');
fb.className = 'qz-fb show ' + (pct >= 70 ? 'pass' : 'fail');
fb.innerHTML = `<div style="font-size:28px;font-weight:900;color:#fff;">${Q.correct}/${Q.total}</div>
<div style="font-size:13px;color:rgba(255,255,255,0.4);margin:4px 0;">正确率 ${pct}%</div>
<div style="font-size:12px;margin-bottom:10px;">${msg}</div>
<button class="btn btn-p" onclick="page('pHome');renderHome();">🏠 返回</button>
<button class="btn btn-s" onclick="startQuiz(${Q.ui})" style="margin-left:6px;">🔄 再来一次</button>`;
document.getElementById('qCard').innerHTML = '';
document.getElementById('qOpts').innerHTML = '';
document.getElementById('qProg').textContent = '';
document.getElementById('qMode').textContent = '';
Q.done = true;
}
// ===== GRAMMAR ===== // ===== GRAMMAR =====
function renderGrammar() { const GRAMMAR = [
const gl = document.getElementById('grammarList'); {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.']},
gl.innerHTML = GRAMMAR.map(g => `<div class="gr-card"> {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.']},
<div class="gt"><span class="gi">${g.icon}</span>${g.title}</div> {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']},
<div class="gd">${g.detail}</div> {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.']},
<div style="margin:6px 0;">${g.examples.map(e => `<span class="ge">${e}</span>`).join(' ')}</div> {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.']},
<div class="gr">💡 ${g.note}</div> {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.']},
</div>`).join(''); {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();
} }
// ===== REVIEW (SRS-based) ===== function renderConj() {
function renderReview() { if (C.idx >= C.total) { finishConj(); return; }
const now = Date.now(); const v = C.pool[C.idx];
const due = allWords().filter(w => { const modeLabels = {s:'三单形式', ed:'过去式', pp:'过去分词'};
const st = S.learned[uid(w.ui, w.wi)]; const ansKey = {s:'s', ed:'ed', pp:'pp'}[C.mode];
return st && st.level < 3 && st.nextReview > 0 && st.nextReview <= now; const correctAns = v[ansKey];
}); const hint = C.mode === 's' ? `He/She/It + ${v.b}` : C.mode === 'ed' ? 'yesterday/last...' : 'have/has + ?';
const rc = document.getElementById('reviewContent'); const cc = document.getElementById('conjContent');
if (!due.length) { cc.innerHTML = `<div class="cd" style="text-align:center;padding:24px;">
rc.innerHTML = '<div style="text-align:center;padding:40px 20px;color:rgba(255,255,255,0.15);">🎉 没有待复习的单词!<br>学点新的吧!</div>'; <div style="font-size:11px;color:rgba(26,26,46,0.3);margin-bottom:4px;">写出下列动词的 <strong style="color:#6366f1;">${modeLabels[C.mode]}</strong></div>
return; <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>
</div>`;
setTimeout(()=>{const si=document.getElementById('conjInput');if(si)si.focus();},100);
document.getElementById('conjScore').textContent = C.correct+'/'+C.total;
}
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);
}
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'); }
}
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 =====
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){}
}
function sparks(n) {
const c=document.getElementById('pyro');if(!c)return;
const cs=['#6366f1','#f59e0b','#22c55e','#fff','#f59e0b','#6366f1','#ef4444'];
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();
}
// ===== 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 = '';
}
} }
rc.innerHTML = `<div style="text-align:center;margin-bottom:10px;color:rgba(255,255,255,0.3);font-size:12px;">📋 有 <span style="color:#f59e0b;">${due.length}</span> 个单词待复习</div>`;
due.slice(0, 20).forEach(w => { // Task list: focused, 3-4 items max
const st = S.learned[uid(w.ui, w.wi)]; const gDone = S._grammarProg ? Object.keys(S._grammarProg).length : 0;
const lv = ['新','生','熟']['' + Math.min(st.level, 2)]; const wrongCount = Object.values(S.learned).filter(st=>(st.wrong||0)>(st.correct||0)).length;
rc.innerHTML += `<div class="wi" style="cursor:default;"> const tasks = [];
<div class="we ${w.em.length>2?'s':''}">${w.em}</div> if (gDone < gTotal) tasks.push({icon:'📖',text:'语法学习 ('+gDone+'/'+gTotal+')',action:'startGrammarLearn()'});
<div class="ww"> if (learned < all.length) tasks.push({icon:'📚',text:'学新词 ('+learned+'/'+all.length+')',action:'startLearn('+(S.lastUnit||0)+')'});
<div class="en">${w.e}</div> if (due > 0) tasks.push({icon:'🔄',text:'复习 '+due+' 个到期词',action:"switchTab('review')"});
<div class="zh">${w.c}</div> 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>
<div class="st"> <div style="background:#fefce8;border-radius:6px;padding:8px;text-align:center;">
<span class="snd" data-word="${w.e}" style="font-size:16px;">🔊</span> <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>
</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>`;
}); });
rc.innerHTML += `<button class="btn btn-p" style="width:100%;margin-top:10px;" onclick="startQuiz(S.curUnit !== undefined ? S.curUnit : 0)">🎯 开始复习这些词</button>`; 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 ===== // ===== INIT =====
ld(); initState(); sv(); renderHome(); ld(); initState(); sv(); renderHome();
// Listen for speech button clicks (for q-snd)
document.addEventListener('click', function(e) {
const qs = e.target.closest('.q-snd');
if (qs) {
const txt = qs.dataset.word;
if (txt) { speak(txt); return; }
}
});
</script> </script>
</body> </body>
</html> </html>