Files
match/index.html
T

930 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family:'Noto Sans SC','Inter',sans-serif;
background:#0a0a14; min-height:100vh; color:#c8c8d0;
overflow-x:hidden;
}
.app { max-width:820px; margin:0 auto; padding:16px; position:relative; min-height:100vh; }
/* HEADER */
.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 */
.pg { display:none; }
.pg.act { display:block; animation:fadeIn 0.3s ease; }
@keyframes fadeIn { from{opacity:0;transform:translateY(10px)} 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 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 slideUp { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }
@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 */
.h-card {
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03);
border-radius:8px; padding:18px; margin-bottom:12px;
}
.h-card .ht { font-size:13px; font-weight:700; color:rgba(255,255,255,0.5); letter-spacing:1px; margin-bottom:6px; }
.streak-bar {
display:flex; gap:6px; margin:8px 0;
}
.streak-bar .d {
width:32px; height:40px; border-radius:4px; display:flex;
align-items:center; justify-content:center; font-size:11px; font-weight:700;
background:rgba(255,255,255,0.03); color:rgba(255,255,255,0.15);
}
.streak-bar .d.on { background:rgba(245,158,11,0.12); color:#f59e0b; border:1px solid rgba(245,158,11,0.15); }
.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; }
.stat-row { display:flex; gap:10px; margin:10px 0; }
.stat-box {
flex:1; background:rgba(255,255,255,0.02); border-radius:6px; padding:12px; text-align:center;
border:1px solid rgba(255,255,255,0.03);
}
.stat-box .n { font-size:24px; font-weight:900; font-family:'Inter',monospace; }
.stat-box .l { font-size:10px; color:rgba(255,255,255,0.3); margin-top:2px; }
.stat-box.g { border-left:2px solid #6366f1; }
.stat-box.y { border-left:2px solid #f59e0b; }
/* UNITS */
.unit-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.u-card {
background:rgba(10,10,26,0.5); border:1px solid rgba(255,255,255,0.03);
border-radius:6px; padding:14px; cursor:pointer; transition:all 0.25s; text-align:center;
}
.u-card:hover { border-color:rgba(99,102,241,0.15); transform:translateY(-2px); }
.u-card .un { font-size:22px; margin-bottom:2px; }
.u-card .ut { font-size:12px; font-weight:700; color:#fff; }
.u-card .ud { font-size:9px; color:rgba(255,255,255,0.25); margin-top:2px; }
.u-card .ub { font-size:8px; color:rgba(255,255,255,0.15); margin-top:4px; }
.u-card .ub .m { color:#6366f1; }
.u-card.done { border-color:rgba(16,185,129,0.1); }
.u-card.done .un { opacity:0.5; }
/* WORD LIST */
.wl-h {
display:flex; align-items:center; gap:8px; margin-bottom:12px;
padding-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.03);
}
.wl-h h2 { font-size:16px; color:#fff; flex:1; }
.wl-h .c { font-size:10px; color:rgba(255,255,255,0.2); font-family:'Inter',monospace; }
.wi {
display:flex; align-items:center; gap:10px; padding:10px 12px;
border-bottom:1px solid rgba(255,255,255,0.02); cursor:pointer;
transition:all 0.2s; border-radius:4px;
}
.wi:hover { background:rgba(255,255,255,0.02); }
.wi .we { font-size:28px; flex-shrink:0; width:40px; text-align:center; }
.wi .we.s { font-size:18px; }
.wi .we.xl { font-size:32px; }
.wi .ww { flex:1; }
.wi .ww .en { font-size:15px; font-weight:700; color:#fff; }
.wi .ww .zh { font-size:12px; color:rgba(255,255,255,0.4); }
.wi .ww .ex { font-size:10px; color:rgba(255,255,255,0.15); margin-top:2px; line-height:1.4; }
.wi .st { flex-shrink:0; }
.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; }
.wi .st .lvl.n { background:rgba(99,102,241,0.1); color:#6366f1; }
.wi .st .lvl.l { background:rgba(16,185,129,0.1); color:#10b981; }
.wi .st .lvl.m { background:rgba(245,158,11,0.1); color:#f59e0b; }
.wi .st .snd { cursor:pointer; font-size:16px; margin-left:4px; opacity:0.3; transition:opacity 0.2s; }
.wi .st .snd:hover { opacity:0.8; }
/* 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>
</head>
<body>
<div class="app">
<!-- HEADER -->
<div class="hd">
<div class="lg"><span class="g">📚</span> 单词<span class="y">乐园</span></div>
<div class="hd-s"></div>
<div class="hd-st"><div class="lb">🔥 连续</div><div class="vl y" id="hStreak">0天</div></div>
<div class="hd-st"><div class="lb">📖 已学</div><div class="vl g" id="hLearned">0</div></div>
</div>
<!-- HOME PAGE -->
<div class="pg act" id="pHome">
<div class="h-card">
<div class="ht">📅 本周打卡</div>
<div class="streak-bar" id="weekBar"></div>
<div id="checkInArea"></div>
</div>
<div class="h-card">
<div class="ht">📊 学习统计</div>
<div class="stat-row">
<div class="stat-box g"><div class="n" id="sLearned">0</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 class="h-card">
<div class="ht">📚 单元列表 · PEP 7上</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>
<!-- UNIT PAGE -->
<div class="pg" id="pUnit">
<button class="btn btn-gh" id="bkHome" style="margin-bottom:10px;">← 返回</button>
<div class="wl-h" id="unitHeader"></div>
<div id="wordList"></div>
<button class="btn btn-p" id="startQuiz" style="width:100%;margin-top:8px;">🎯 开始测这单元</button>
</div>
<!-- QUIZ PAGE -->
<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">
<button class="btn btn-gh" id="bkHomeR" style="margin-bottom:10px;">← 返回</button>
<h2 style="font-size:18px;color:#fff;margin-bottom:10px;">🔄 复习</h2>
<div id="reviewContent"></div>
</div>
<!-- CHECK-IN MODAL -->
<div class="modal" id="ciModal">
<div class="modal-in">
<div class="mi">🌟</div>
<h2>打卡成功!</h2>
<p id="ciMsg">今天又学了新单词!</p>
<button class="btn btn-o" id="ciBtn">继续学习</button>
</div>
</div>
<script>
// ===== DATA: PEP Go for it! 7上 =====
const UNITS = [
{ id:'S1', em:'☀️', title:'Good Morning!', sub:'Starter 1 · 字母 & 问候', words:[
{e:'good',c:'好的',em:'👍',s:'Good morning, class! 早上好,同学们!'},
{e:'morning',c:'早上',em:'🌅',s:'I have breakfast in the morning. 我早上吃早餐。'},
{e:'afternoon',c:'下午',em:'☀️',s:'Good afternoon, Miss Li! 下午好,李老师!'},
{e:'evening',c:'晚上',em:'🌆',s:'Good evening, Mom! 晚上好,妈妈!'},
{e:'fine',c:'好的;健康的',em:'💪',s:'I am fine, thank you. 我很好,谢谢。'},
{e:'OK',c:'好;行',em:'👌',s:'OK, let\'s go! 好的,我们走吧!'},
{e:'hello',c:'你好',em:'👋',s:'Hello, my friend! 你好,我的朋友!'},
{e:'hi',c:'嗨',em:'✋',s:'Hi, how are you? 嗨,你好吗?'},
{e:'thanks',c:'谢谢',em:'🙏',s:'Thanks for your help. 谢谢你的帮助。'},
{e:'how',c:'怎样;怎么',em:'❓',s:'How are you today? 你今天怎么样?'},
{e:'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:[
{e:'what',c:'什么',em:'❓',s:'What is this? 这是什么?'},
{e:'is',c:'是',em:'✅',s:'This is a book. 这是一本书。'},
{e:'this',c:'这个',em:'👆',s:'This is my pen. 这是我的钢笔。'},
{e:'that',c:'那个',em:'👉',s:'That is a ruler. 那是一把尺子。'},
{e:'it',c:'它',em:'🎯',s:'It is a map. 它是一张地图。'},
{e:'a',c:'一个',em:'1️⃣',s:'I have a pen. 我有一支钢笔。'},
{e:'an',c:'一个(元音前)',em:'1️⃣',s:'It is an orange. 它是一个橙子。'},
{e:'map',c:'地图',em:'🗺️',s:'This is a map of China. 这是一张中国地图。'},
{e:'key',c:'钥匙',em:'🔑',s:'Where is my key? 我的钥匙在哪里?'},
{e:'jacket',c:'夹克衫',em:'🧥',s:'My jacket is blue. 我的夹克衫是蓝色的。'},
{e:'cup',c:'杯子',em:'🥤',s:'This cup is red. 这个杯子是红色的。'},
{e:'ruler',c:'尺子',em:'📏',s:'I need a ruler. 我需要一把尺子。'},
{e:'pen',c:'钢笔',em:'🖊️',s:'This pen is black. 这支钢笔是黑色的。'},
{e:'orange',c:'橙子',em:'🍊',s:'The orange is sweet. 这个橙子很甜。'},
{e:'spell',c:'拼写',em:'🔤',s:'How do you spell it? 你怎么拼写它?'},
{e:'please',c:'请',em:'🙏',s:'Sit down, please. 请坐下。'},
]},
{ id:'S3', em:'🌈', title:'What Color?', sub:'Starter 3 · 颜色', words:[
{e:'color',c:'颜色',em:'🎨',s:'What color is it? 它是什么颜色?'},
{e:'red',c:'红色',em:'🔴',s:'The apple is red. 这个苹果是红色的。'},
{e:'yellow',c:'黄色',em:'🟡',s:'The sun is yellow. 太阳是黄色的。'},
{e:'green',c:'绿色',em:'🟢',s:'The tree is green. 树是绿色的。'},
{e:'blue',c:'蓝色',em:'🔵',s:'The sky is blue. 天空是蓝色的。'},
{e:'black',c:'黑色',em:'⚫',s:'My cat is black. 我的猫是黑色的。'},
{e:'white',c:'白色',em:'⚪',s:'Snow is white. 雪是白色的。'},
{e:'purple',c:'紫色',em:'🟣',s:'I like purple flowers. 我喜欢紫色的花。'},
{e:'brown',c:'棕色',em:'🟤',s:'The dog is brown. 这只狗是棕色的。'},
]},
{ id:'U1', em:'👋', title:'My Name\'s Gina', sub:'Unit 1 · 自我介绍 · be动词', words:[
{e:'name',c:'名字',em:'🏷️',s:'My name is Tom. 我的名字是汤姆。'},
{e:'my',c:'我的',em:'👤',s:'This is my book. 这是我的书。'},
{e:'your',c:'你的',em:'👉',s:'What is your name? 你叫什么名字?'},
{e:'his',c:'他的',em:'👦',s:'His name is Mike. 他的名字是迈克。'},
{e:'her',c:'她的',em:'👧',s:'Her bag is red. 她的包是红色的。'},
{e:'he',c:'他',em:'👦',s:'He is my friend. 他是我的朋友。'},
{e:'she',c:'她',em:'👧',s:'She is a teacher. 她是一位老师。'},
{e:'yes',c:'是的',em:'👍',s:'Yes, I can. 是的,我能。'},
{e:'no',c:'不;不是',em:'👎',s:'No, it isn\'t. 不,它不是。'},
{e:'first',c:'第一的;首先',em:'🥇',s:'This is my first book. 这是我的第一本书。'},
{e:'last',c:'最后的',em:'🏁',s:'My last name is Smith. 我姓史密斯。'},
{e:'friend',c:'朋友',em:'🤝',s:'She is my good friend. 她是我的好朋友。'},
{e:'zero',c:'零',em:'0️⃣',s:'My number has a zero. 我的号码里有一个零。'},
{e:'one',c:'一',em:'1️⃣',s:'I have one apple. 我有一个苹果。'},
{e:'two',c:'二',em:'2️⃣',s:'Two cats are playing. 两只猫在玩。'},
{e:'three',c:'三',em:'3️⃣',s:'Three birds 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:[
{e:'sister',c:'姐妹',em:'👧',s:'My sister is kind. 我的姐姐很善良。'},
{e:'brother',c:'兄弟',em:'👦',s:'My brother likes sports. 我的哥哥喜欢运动。'},
{e:'mother',c:'母亲',em:'👩',s:'My mother cooks dinner. 我妈妈做晚饭。'},
{e:'father',c:'父亲',em:'👨',s:'My father is tall. 我爸爸很高。'},
{e:'parent',c:'父亲或母亲',em:'👪',s:'My parents are at home. 我的父母在家。'},
{e:'grandmother',c:'祖母',em:'👵',s:'My grandmother tells stories. 我奶奶讲故事。'},
{e:'grandfather',c:'祖父',em:'👴',s:'My grandfather reads newspaper. 我爷爷看报纸。'},
{e:'family',c:'家庭',em:'👪',s:'My family has four people. 我家有四口人。'},
{e:'those',c:'那些',em:'👉👈',s:'Those are my friends. 那些是我的朋友。'},
{e:'these',c:'这些',em:'👈👈',s:'These are my books. 这些是我的书。'},
{e:'who',c:'谁',em:'❓',s:'Who is she? 她是谁?'},
{e:'aunt',c:'阿姨;姑姑',em:'👩',s:'My aunt is a doctor. 我的阿姨是医生。'},
{e:'uncle',c:'叔叔;舅舅',em:'👨',s:'My uncle drives a car. 我的叔叔开车。'},
{e:'son',c:'儿子',em:'👦',s:'He is her son. 他是她的儿子。'},
{e:'daughter',c:'女儿',em:'👧',s:'She is my daughter. 她是我的女儿。'},
{e:'picture',c:'图片;照片',em:'🖼️',s:'This is a picture of my family. 这是我家的照片。'},
]},
{ id:'U3', em:'🎒', title:'Is This Your Pencil?', sub:'Unit 3 · 学习用品 · 物主代词', words:[
{e:'pencil',c:'铅笔',em:'✏️',s:'Is this your pencil? 这是你的铅笔吗?'},
{e:'book',c:'书',em:'📖',s:'I read a book every day. 我每天读一本书。'},
{e:'eraser',c:'橡皮',em:'🧹',s:'I need an eraser. 我需要一块橡皮。'},
{e:'schoolbag',c:'书包',em:'🎒',s:'My schoolbag is heavy. 我的书包很重。'},
{e:'dictionary',c:'字典',em:'📕',s:'Look it up in the dictionary. 在字典里查一下。'},
{e:'teacher',c:'老师',em:'👩‍🏫',s:'My teacher is nice. 我的老师很好。'},
{e:'student',c:'学生',em:'🧑‍🎓',s:'He is a good student. 他是个好学生。'},
{e:'classroom',c:'教室',em:'🏫',s:'Our classroom is big. 我们的教室很大。'},
{e:'library',c:'图书馆',em:'📚',s:'I like the school library. 我喜欢学校图书馆。'},
{e:'watch',c:'手表',em:'⌚',s:'My watch is new. 我的手表是新的。'},
{e:'ring',c:'戒指',em:'💍',s:'This is a gold ring. 这是一个金戒指。'},
{e:'bag',c:'包',em:'👜',s:'I have a new bag. 我有一个新包。'},
{e:'baseball',c:'棒球',em:'⚾',s:'Let\'s play baseball. 我们打棒球吧。'},
{e:'help',c:'帮助',em:'🤲',s:'Can you help me? 你能帮我吗?'},
{e:'welcome',c:'欢迎',em:'👏',s:'Welcome to our school! 欢迎来到我们学校!'},
]},
{ id:'U4', em:'🛏️', title:'Where\'s My Schoolbag?', sub:'Unit 4 · 方位介词', words:[
{e:'where',c:'在哪里',em:'📍',s:'Where is my pen? 我的钢笔在哪里?'},
{e:'schoolbag',c:'书包',em:'🎒',s:'Where is my schoolbag? 我的书包在哪里?'},
{e:'desk',c:'书桌',em:'🪑',s:'My book is on the desk. 我的书在书桌上。'},
{e:'table',c:'桌子',em:'🪑',s:'The cat is under the table. 猫在桌子下面。'},
{e:'chair',c:'椅子',em:'💺',s:'Sit on the chair, please. 请坐在椅子上。'},
{e:'bookcase',c:'书柜',em:'📚',s:'The books are in the bookcase. 书在书柜里。'},
{e:'bed',c:'床',em:'🛏️',s:'I sleep on the bed. 我在床上睡觉。'},
{e:'sofa',c:'沙发',em:'🛋️',s:'The cat is on the sofa. 猫在沙发上。'},
{e:'dresser',c:'梳妆台',em:'🪞',s:'The comb is on the dresser. 梳子在梳妆台上。'},
{e:'drawer',c:'抽屉',em:'🗄️',s:'It\'s in the drawer. 它在抽屉里。'},
{e:'plant',c:'植物',em:'🌱',s:'The plant is near the window. 植物在窗户旁边。'},
{e:'under',c:'在…下面',em:'⬇️',s:'The ball is under the chair. 球在椅子下面。'},
{e:'on',c:'在…上面',em:'⬆️',s:'The book is on the table. 书在桌子上面。'},
{e:'in',c:'在…里面',em:'📥',s:'The pen is in the bag. 钢笔在包里。'},
{e:'behind',c:'在…后面',em:'⬅️',s:'The dog is behind the door. 狗在门后面。'},
{e:'next to',c:'在…旁边',em:'➡️',s:'The desk is next to the bed. 书桌在床旁边。'},
]},
{ id:'U5', em:'⚽', title:'Do You Have a Soccer Ball?', sub:'Unit 5 · 体育 · 一般现在时(有)', words:[
{e:'have',c:'有',em:'🤲',s:'I have a soccer ball. 我有一个足球。'},
{e:'has',c:'有(三单)',em:'🤲',s:'He has a basketball. 他有一个篮球。'},
{e:'soccer',c:'英式足球',em:'⚽',s:'Let\'s play soccer! 我们踢足球吧!'},
{e:'basketball',c:'篮球',em:'🏀',s:'I like basketball. 我喜欢篮球。'},
{e:'volleyball',c:'排球',em:'🏐',s:'She plays volleyball well. 她排球打得很好。'},
{e:'tennis',c:'网球',em:'🎾',s:'Tennis is fun. 网球很有趣。'},
{e:'baseball',c:'棒球',em:'⚾',s:'Baseball is popular in the US. 棒球在美国很流行。'},
{e:'ball',c:'球',em:'⚪',s:'The ball is red. 这个球是红色的。'},
{e:'sport',c:'运动',em:'🏃',s:'My favorite sport is swimming. 我最喜欢的运动是游泳。'},
{e:'play',c:'玩;打',em:'🎮',s:'I play soccer after school. 我放学后踢足球。'},
{e:'watch',c:'观看',em:'👀',s:'We watch TV at home. 我们在家看电视。'},
{e:'game',c:'比赛;游戏',em:'🎯',s:'The game is exciting. 比赛很激动人心。'},
{e:'fun',c:'有趣的;乐趣',em:'😄',s:'Playing soccer is fun. 踢足球很有趣。'},
{e:'boring',c:'无聊的',em:'😴',s:'This movie is boring. 这部电影很无聊。'},
{e:'difficult',c:'困难的',em:'😰',s:'Math is difficult 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:[
{e:'like',c:'喜欢',em:'❤️',s:'I like apples. 我喜欢苹果。'},
{e:'banana',c:'香蕉',em:'🍌',s:'Bananas are yellow. 香蕉是黄色的。'},
{e:'apple',c:'苹果',em:'🍎',s:'An apple a day keeps the doctor away. 一天一苹果,医生远离我。'},
{e:'orange',c:'橙子',em:'🍊',s:'This orange is sweet. 这个橙子很甜。'},
{e:'pear',c:'梨',em:'🍐',s:'The pear is juicy. 这个梨很多汁。'},
{e:'strawberry',c:'草莓',em:'🍓',s:'I like strawberries. 我喜欢草莓。'},
{e:'hamburger',c:'汉堡包',em:'🍔',s:'Let\'s eat hamburgers. 我们吃汉堡吧。'},
{e:'rice',c:'米饭',em:'🍚',s:'I eat rice for lunch. 我午饭吃米饭。'},
{e:'bread',c:'面包',em:'🍞',s:'Bread is good for breakfast. 面包适合当早餐。'},
{e:'milk',c:'牛奶',em:'🥛',s:'I drink milk every day. 我每天喝牛奶。'},
{e:'water',c:'水',em:'💧',s:'Drink more water. 多喝水。'},
{e:'egg',c:'鸡蛋',em:'🥚',s:'I have an egg for breakfast. 我早餐吃一个鸡蛋。'},
{e:'chicken',c:'鸡肉',em:'🍗',s:'Chicken is delicious. 鸡肉很好吃。'},
{e:'salad',c:'沙拉',em:'🥗',s:'I like fruit salad. 我喜欢水果沙拉。'},
{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:[
{e:'much',c:'多;大量',em:'📊',s:'How much is it? 它多少钱?'},
{e:'how much',c:'多少钱',em:'💰',s:'How much are these shoes? 这些鞋多少钱?'},
{e:'dollar',c:'美元',em:'💵',s:'It is five dollars. 它五美元。'},
{e:'big',c:'大的',em:'🐘',s:'The store is big. 这个商店很大。'},
{e:'small',c:'小的',em:'🐭',s:'I need a small size. 我需要小号。'},
{e:'long',c:'长的',em:'📏',s:'This ruler is long. 这把尺子很长。'},
{e:'short',c:'短的;矮的',em:'📐',s:'The pencil is short. 这铅笔短了。'},
{e:'tall',c:'高的',em:'🗼',s:'My father is tall. 我爸爸很高。'},
{e:'socks',c:'袜子',em:'🧦',s:'These socks are warm. 这些袜子很暖和。'},
{e:'shoes',c:'鞋子',em:'👟',s:'My shoes are new. 我的鞋子是新的。'},
{e:'shirt',c:'衬衫',em:'👔',s:'Your shirt is nice. 你的衬衫很漂亮。'},
{e:'T-shirt',c:'T恤',em:'👕',s:'I like your T-shirt. 我喜欢你的T恤。'},
{e:'sweater',c:'毛衣',em:'🧶',s:'This sweater is soft. 这件毛衣很柔软。'},
{e:'jacket',c:'夹克',em:'🧥',s:'Put on your jacket. 穿上你的夹克。'},
{e:'skirt',c:'裙子',em:'👗',s:'She wears a red skirt. 她穿一条红裙子。'},
{e:'hat',c:'帽子',em:'🎩',s:'This hat is cool. 这顶帽子很酷。'},
{e:'price',c:'价格',em:'🏷️',s:'The price is good. 价格不错。'},
{e:'buy',c:'买',em:'🛒',s:'I want to buy a book. 我想买一本书。'},
{e:'sell',c:'卖',em:'🏪',s:'The store sells shoes. 这家店卖鞋。'},
{e:'cheap',c:'便宜的',em:'💲',s:'This pen is cheap. 这支钢笔很便宜。'},
{e:'expensive',c:'昂贵的',em:'💎',s:'The watch is expensive. 这块手表很贵。'},
]},
{ id:'U8', em:'🎂', title:'When Is Your Birthday?', sub:'Unit 8 · 日期 · 序数词', words:[
{e:'when',c:'什么时候',em:'📅',s:'When is your birthday? 你的生日是什么时候?'},
{e:'birthday',c:'生日',em:'🎂',s:'Happy birthday to you! 祝你生日快乐!'},
{e:'month',c:'月份',em:'📅',s:'There are 12 months in a year. 一年有12个月。'},
{e:'January',c:'一月',em:'🎊',s:'New Year is in January. 新年在一月。'},
{e:'February',c:'二月',em:'💕',s:'February is cold. 二月很冷。'},
{e:'March',c:'三月',em:'🌸',s:'Spring starts in March. 春天从三月开始。'},
{e:'April',c:'四月',em:'🌧️',s:'April showers bring May flowers. 四月雨带来五月花。'},
{e:'May',c:'五月',em:'🌺',s:'May is a beautiful month. 五月是美丽的月份。'},
{e:'June',c:'六月',em:'☀️',s:'School ends in June. 学校六月放假。'},
{e:'July',c:'七月',em:'🏖️',s:'July is very hot. 七月很热。'},
{e:'August',c:'八月',em:'🌞',s:'We go swimming in August. 我们八月去游泳。'},
{e:'September',c:'九月',em:'🍂',s:'School starts in September. 学校九月开学。'},
{e:'October',c:'十月',em:'🎃',s:'Halloween is in October. 万圣节在十月。'},
{e:'November',c:'十一月',em:'🍁',s:'November is cool. 十一月很凉爽。'},
{e:'December',c:'十二月',em:'🎄',s:'Christmas is in December. 圣诞节在十二月。'},
{e:'first',c:'第一',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:[
{e:'favorite',c:'最喜欢的',em:'⭐',s:'My favorite color is blue. 我最喜欢的颜色是蓝色。'},
{e:'subject',c:'科目',em:'📚',s:'My favorite subject is English. 我最喜欢的科目是英语。'},
{e:'math',c:'数学',em:'🔢',s:'Math is interesting. 数学很有趣。'},
{e:'English',c:'英语',em:'🇬🇧',s:'I speak English every day. 我每天说英语。'},
{e:'Chinese',c:'语文;中文',em:'🇨🇳',s:'Chinese is my first language. 中文是我的母语。'},
{e:'science',c:'科学',em:'🔬',s:'Science is fun. 科学很有趣。'},
{e:'history',c:'历史',em:'📜',s:'I like history class. 我喜欢历史课。'},
{e:'music',c:'音乐',em:'🎵',s:'Music makes me happy. 音乐让我快乐。'},
{e:'art',c:'美术',em:'🎨',s:'I draw pictures in art class. 我在美术课上画画。'},
{e:'P.E.',c:'体育',em:'🏃',s:'P.E. is my favorite. 体育是我最喜欢的。'},
{e:'geography',c:'地理',em:'🌍',s:'Geography is about the world. 地理是关于世界的。'},
{e:'biology',c:'生物',em:'🧬',s:'Biology studies living things. 生物学研究生物。'},
{e:'interesting',c:'有趣的',em:'🤔',s:'This book is interesting. 这本书很有趣。'},
{e:'useful',c:'有用的',em:'🛠️',s:'English is useful. 英语很有用。'},
{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 =====
const GRAMMAR = [
{icon:'✅', title:'be动词 (am/is/are)', detail:'be动词是英语中最基本的系动词,意思为"是"。',
examples:['I am a student. (我是学生。)','You are my friend. (你是我的朋友。)','He/She/It is nice. (他/她/它很好。)','We/You/They are happy. (我们/你们/他们很开心。)'],
note:'口诀:I用amyou用areis连着he/she/it;复数都用are。'},
{icon:'❓', title:'一般疑问句 (Yes/No问句)', detail:'把be动词提到主语前,句尾加问号。',
examples:['Are you a student? (你是学生吗?) → Yes, I am. / No, I\'m not.','Is this your pen? (这是你的钢笔吗?) → Yes, it is. / No, it isn\'t.'],
note:'回答用Yes/No + 主语 + be动词的肯定/否定形式。'},
{icon:'🔤', title:'名词复数', detail:'可数名词变复数有规则变化和不规则变化。',
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'],
note:'不可数名词(water, rice, bread等)没有复数形式。'},
{icon:'👤', title:'物主代词 (my/your/his/her)', detail:'物主代词表示"…的",用于名词前。',
examples:['This is my book. (这是我的书。)','Is that your pen? (那是你的钢笔吗?)','Her name is Mary. (她的名字是玛丽。)','Our school is big. (我们的学校很大。)'],
note:'形容词性物主代词:my, your, his, her, its, our, your, their。后面必须跟名词。'},
{icon:'📍', title:'方位介词 (in/on/under/between)', detail:'表示位置关系的词,常和the连用。',
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在旁边。'},
{icon:'🔄', title:'一般现在时 (do/does)', detail:'表示经常发生的动作或存在的状态。主语是三单时动词加s/es。',
examples:['I like apples. (我喜欢苹果。)','He likes bananas. (他喜欢香蕉。)','She goes to school by bus. (她坐公交上学。)','They play soccer on Sunday. (他们周日踢足球。)'],
note:'三单动词规则:一般+s (like→likes)s/x/sh/ch+es (watch→watches);辅音+y→ies (study→studies)。'},
{icon:'❌', title:'否定句 (don\'t/doesn\'t)', detail:'一般现在时的否定:在动词前加don\'t/doesn\'t,动词恢复原形。',
examples:['I don\'t like milk. (我不喜欢牛奶。)','He doesn\'t like sports. (他不喜欢运动。)','She doesn\'t go to school on Sunday. (她周日不上学。)'],
note:'doesn\'t后动词必须用原形!不能说"he doesn\'t likes"。'},
{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.'],
note:'特殊疑问词:What(什么), Where(哪里), Who(谁), When(何时), Why(为什么), How(怎样), How much(多少钱)。'},
{icon:'🧮', title:'序数词 (first/second/third)', detail:'表示顺序的数词,前面通常加the。',
examples:['first (1st) 第一','second (2nd) 第二','third (3rd) 第三','fourth (4th) 第四','fifth (5th) 第五'],
note:'基数词变序数词口诀:一二三特殊记,th要从四加起,ve要用f替,y变ie再加th。'},
{icon:'🤔', title:'情态动词 can', detail:'can表示能力"能、会",后面接动词原形。',
examples:['I can swim. (我会游泳。)','She can speak English. (她会说英语。)','Can you play soccer? (你会踢足球吗?) → Yes, I can. / No, I can\'t.'],
note:'can后面必跟动词原形!否定形式can\'t = cannot。'},
];
// ===== STATE =====
const DAYS = ['日','一','二','三','四','五','六'];
let S = {};
function ld() { try { const d = localStorage.getItem('wordLe'); if (d) { S = JSON.parse(d); } } catch(e) {} }
function sv() { try { localStorage.setItem('wordLe', JSON.stringify(S)); } catch(e) {} }
function initState() {
const def = {
learned: {}, // { "U1_0": { level:0..3, lastReview:0, nextReview:0, correct:0, wrong:0 } }
daily: {}, // { "2026-06-29": { learned:5, quiz:10, correct:8 } }
streak: 0,
lastCheckIn: 0,
totalQuiz: 0,
totalCorrect: 0,
};
if (!S.learned) Object.assign(S, def);
}
// ===== HELPERS =====
function today() { return new Date().toISOString().slice(0,10); }
function wIdx(d) { return d.getDay(); }
function weekDates() {
const d = new Date(); const 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, cb) {
if (!window.speechSynthesis) return;
window.speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(text);
u.lang = 'en-US'; u.rate = 0.9;
if (cb) u.onend = cb;
window.speechSynthesis.speak(u);
}
// ===== RENDER =====
function page(id) {
document.querySelectorAll('.pg').forEach(p=>p.classList.remove('act'));
document.getElementById(id).classList.add('act');
}
function renderHome() {
// Week bar
const wb = document.getElementById('weekBar');
const dates = weekDates();
wb.innerHTML = dates.map(d => {
const key = d.toISOString().slice(0,10);
const isToday = key === today();
const hasData = S.daily[key];
return `<div class="d ${isToday?'td':''} ${hasData?'on':''}">${DAYS[d.getDay()]}</div>`;
}).join('');
// Check-in area
const ci = document.getElementById('checkInArea');
const last = S.lastCheckIn;
const td = parseInt(new Date()/86400000);
const canCheck = last !== td;
ci.innerHTML = canCheck
? `<button class="ci-star" id="doCheckIn"><span class="ci-e">⭐</span><span class="ci-t">今日打卡</span></button>`
: `<div class="ci-star" style="cursor:default;opacity:0.7;"><span class="ci-e">✅</span><span class="ci-t">今日已打卡</span></div>`;
// Stats
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;
// Unit grid
const grid = document.getElementById('unitGrid');
grid.innerHTML = UNITS.map((u, i) => {
const cnt = u.words.length;
const done = u.words.filter((w, wi) => S.learned[uid(i,wi)] && S.learned[uid(i,wi)].level >= 1).length;
return `<div class="u-card ${done===cnt?'done':''}" data-idx="${i}">
<div class="un">${u.em}</div>
<div class="ut">${u.title}</div>
<div class="ud">${u.sub}</div>
<div class="ub">${done}/${cnt} <span class="m">✓</span></div>
</div>`;
}).join('');
// HUD
document.getElementById('hStreak').textContent = S.streak + '天';
document.getElementById('hLearned').textContent = learned;
}
function renderUnit(ui) {
const u = UNITS[ui];
document.getElementById('unitHeader').innerHTML = `<h2>${u.em} ${u.title}</h2><div class="c">${u.sub} · ${u.words.length}词</div>`;
document.getElementById('startQuiz').dataset.ui = ui;
const wl = document.getElementById('wordList');
wl.innerHTML = u.words.map((w, wi) => {
const st = S.learned[uid(ui,wi)];
const lv = st ? st.level : 0;
const lvCls = ['n','l','m','g'][Math.min(lv,3)];
const lvLabel = ['新','认','熟','掌'][Math.min(lv,3)];
return `<div class="wi" data-idx="${wi}">
<div class="we ${w.em.length>2?'s':''}">${w.em}</div>
<div class="ww">
<div class="en">${w.e}</div>
<div class="zh">${w.c}</div>
<div class="ex">${w.s}</div>
</div>
<div class="st">
<span class="lvl ${lvCls}">${lvLabel}</span>
<span class="snd" data-word="${w.e}">🔊</span>
</div>
</div>`;
}).join('');
}
// ===== CHECK-IN =====
function doCheckIn() {
const td = parseInt(new Date()/86400000);
S.lastCheckIn = td;
S.streak = (S.streak || 0) + 1;
if (!S.daily[today()]) S.daily[today()] = { learned:0, quiz:0, correct:0 };
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 =====
function renderGrammar() {
const gl = document.getElementById('grammarList');
gl.innerHTML = GRAMMAR.map(g => `<div class="gr-card">
<div class="gt"><span class="gi">${g.icon}</span>${g.title}</div>
<div class="gd">${g.detail}</div>
<div style="margin:6px 0;">${g.examples.map(e => `<span class="ge">${e}</span>`).join(' ')}</div>
<div class="gr">💡 ${g.note}</div>
</div>`).join('');
}
// ===== REVIEW (SRS-based) =====
function renderReview() {
const now = Date.now();
const due = allWords().filter(w => {
const st = S.learned[uid(w.ui, w.wi)];
return st && st.level < 3 && st.nextReview > 0 && st.nextReview <= now;
});
const rc = document.getElementById('reviewContent');
if (!due.length) {
rc.innerHTML = '<div style="text-align:center;padding:40px 20px;color:rgba(255,255,255,0.15);">🎉 没有待复习的单词!<br>学点新的吧!</div>';
return;
}
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 => {
const st = S.learned[uid(w.ui, w.wi)];
const lv = ['新','生','熟']['' + Math.min(st.level, 2)];
rc.innerHTML += `<div class="wi" style="cursor:default;">
<div class="we ${w.em.length>2?'s':''}">${w.em}</div>
<div class="ww">
<div class="en">${w.e}</div>
<div class="zh">${w.c}</div>
</div>
<div class="st">
<span class="snd" data-word="${w.e}" style="font-size:16px;">🔊</span>
</div>
</div>`;
});
rc.innerHTML += `<button class="btn btn-p" style="width:100%;margin-top:10px;" onclick="startQuiz(S.curUnit !== undefined ? S.curUnit : 0)">🎯 开始复习这些词</button>`;
}
// ===== INIT =====
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>
</body>
</html>