1974 lines
169 KiB
HTML
1974 lines
169 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>打字练习</title>
|
||
<style>
|
||
:root {
|
||
--bg: #8ee3ff;
|
||
--card: #ffffff;
|
||
--line: #2d3a4a;
|
||
--text: #243044;
|
||
--muted: #5c6d82;
|
||
--accent: #ff6fa3;
|
||
--ok: #22c55e;
|
||
--bad: #ff4d6d;
|
||
--orange: #ff9f43;
|
||
--yellow: #ffe14d;
|
||
--green: #7be36a;
|
||
--blue: #5ab8ff;
|
||
--shadow: 4px 6px 0 #2d3a4a;
|
||
--radius: 22px;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html, body { height: 100%; }
|
||
html.modal-lock, html.modal-lock body {
|
||
overflow: hidden !important;
|
||
overscroll-behavior: none;
|
||
}
|
||
body.modal-lock {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
width: 100%;
|
||
}
|
||
body {
|
||
margin: 0;
|
||
color: var(--text);
|
||
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||
background:
|
||
radial-gradient(circle at 8% 16%, #fff 0 16px, transparent 17px),
|
||
radial-gradient(circle at 92% 10%, #ffe14d 0 22px, transparent 23px),
|
||
radial-gradient(circle at 86% 82%, #ff9ec8 0 26px, transparent 27px),
|
||
radial-gradient(circle at 10% 84%, #7be36a 0 18px, transparent 19px),
|
||
linear-gradient(180deg, #7ed7ff 0%, #b8edff 42%, #fff4c8 100%);
|
||
}
|
||
button { font-family: inherit; cursor: pointer; }
|
||
.hidden { display: none !important; }
|
||
|
||
#view-select {
|
||
min-height: 100%;
|
||
max-width: 1040px;
|
||
margin: 0 auto;
|
||
padding: 36px 24px 64px;
|
||
}
|
||
.brand { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
|
||
.brand h1 { margin: 0; font-size: 28px; }
|
||
.brand a, .brand span, .brand button {
|
||
color: #35506c;
|
||
font-size: 13px;
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 999px;
|
||
padding: 2px 10px;
|
||
text-decoration: none;
|
||
font-weight: 700;
|
||
box-shadow: 2px 3px 0 var(--line);
|
||
}
|
||
.brand button { padding: 4px 12px; }
|
||
.stats-panel {
|
||
width: min(560px, 100%);
|
||
max-height: min(78vh, 720px);
|
||
overflow: auto;
|
||
overscroll-behavior: contain;
|
||
-webkit-overflow-scrolling: touch;
|
||
background: #fffdf3;
|
||
border: 3px solid var(--line);
|
||
border-radius: 22px;
|
||
padding: 24px 22px 18px;
|
||
box-shadow: 6px 8px 0 var(--line);
|
||
}
|
||
.stats-panel h3 { margin: 0 0 6px; font-size: 22px; }
|
||
.stats-panel .msg { margin: 0 0 16px; }
|
||
.stats-empty {
|
||
padding: 18px 14px;
|
||
background: #fff;
|
||
border: 3px dashed var(--line);
|
||
border-radius: 14px;
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
}
|
||
.stats-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
|
||
.stats-day {
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 16px;
|
||
padding: 12px 14px;
|
||
box-shadow: 2px 3px 0 var(--line);
|
||
}
|
||
.stats-day h4 { margin: 0 0 8px; font-size: 16px; }
|
||
.stats-day-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
}
|
||
.stats-day-grid .k { color: var(--muted); font-size: 12px; font-weight: 700; }
|
||
.stats-day-grid .v { margin-top: 2px; font-size: 16px; font-family: "SF Mono", Menlo, Consolas, monospace; font-weight: 700; }
|
||
.words-panel { width: min(620px, 100%); }
|
||
.rules {
|
||
margin: 0 0 16px;
|
||
padding: 12px 14px;
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 14px;
|
||
color: #35506c;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
line-height: 1.7;
|
||
}
|
||
.words-warn {
|
||
margin: 0 0 14px;
|
||
padding: 10px 12px;
|
||
background: #ffe3e8;
|
||
border: 3px solid var(--line);
|
||
border-radius: 12px;
|
||
color: #b42318;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
}
|
||
.book-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.book-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 12px;
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 14px;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
box-shadow: 2px 3px 0 var(--line);
|
||
}
|
||
.book-item input { width: 16px; height: 16px; accent-color: #ff6fa3; }
|
||
.word-meaning {
|
||
margin: 0;
|
||
font-size: 24px;
|
||
font-weight: 800;
|
||
color: #1d4ed8;
|
||
}
|
||
.intro { margin: 0 0 22px; color: #35506c; font-weight: 700; }
|
||
|
||
.chapter-block { margin-bottom: 28px; }
|
||
.chapter-block h2 {
|
||
margin: 0 0 12px;
|
||
font-size: 18px;
|
||
}
|
||
.chapter-block p {
|
||
margin: -6px 0 12px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
.level-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 12px;
|
||
}
|
||
.level-card {
|
||
text-align: left;
|
||
padding: 16px 14px 14px;
|
||
border: 3px solid var(--line);
|
||
border-radius: 18px;
|
||
color: var(--text);
|
||
box-shadow: var(--shadow);
|
||
transition: transform .15s ease;
|
||
}
|
||
.ch-1 .level-card { background: #fff3d6; }
|
||
.ch-2 .level-card { background: #dff7c9; }
|
||
.ch-3 .level-card { background: #d6f0ff; }
|
||
.ch-4 .level-card { background: #ffd9ec; }
|
||
.ch-5 .level-card { background: #efe0ff; }
|
||
.ch-6 .level-card { background: #ffe4c4; }
|
||
.level-card:hover:not(:disabled) {
|
||
transform: translate(-2px, -2px);
|
||
box-shadow: 6px 8px 0 var(--line);
|
||
}
|
||
.level-card:disabled {
|
||
cursor: not-allowed;
|
||
opacity: .5;
|
||
filter: grayscale(.35);
|
||
}
|
||
.level-id {
|
||
font-size: 11px;
|
||
color: #fff;
|
||
background: var(--accent);
|
||
border: 2px solid var(--line);
|
||
border-radius: 999px;
|
||
padding: 1px 8px;
|
||
display: inline-block;
|
||
}
|
||
.level-card h3 { margin: 8px 0 4px; font-size: 16px; }
|
||
.level-card .sub { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.4; }
|
||
.level-meta { margin-top: 10px; font-size: 12px; }
|
||
.badge {
|
||
display: inline-flex;
|
||
padding: 3px 8px;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
border: 2px solid var(--line);
|
||
}
|
||
.badge.ok { color: #15803d; background: #bbf7d0; }
|
||
|
||
#view-practice {
|
||
min-height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 20px 24px 32px;
|
||
}
|
||
.topbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
max-width: 1100px;
|
||
width: 100%;
|
||
margin: 0 auto 18px;
|
||
}
|
||
.topbar h2 { margin: 0; font-size: 18px; }
|
||
.topbar-actions { display: flex; align-items: center; gap: 8px; }
|
||
.btn-speak {
|
||
width: 40px;
|
||
height: 40px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 14px;
|
||
box-sizing: border-box;
|
||
}
|
||
.btn-speak svg { width: 28px; height: 28px; display: block; flex: none; }
|
||
.btn-speak.is-playing { background: #ffe14d; }
|
||
.voice-label { margin: 0 0 6px; color: #35506c; font-size: 13px; font-weight: 800; }
|
||
.voice-hint { margin: 0 0 10px; color: var(--muted); font-size: 12px; font-weight: 700; line-height: 1.45; }
|
||
.voice-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
margin: 0 0 16px;
|
||
}
|
||
.voice-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 10px;
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 14px;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
box-shadow: 2px 3px 0 var(--line);
|
||
}
|
||
.voice-item input { accent-color: #ff6fa3; }
|
||
.voice-item.is-missing { opacity: 0.55; cursor: not-allowed; }
|
||
.voice-item.is-missing input { pointer-events: none; }
|
||
.voice-item span { display: flex; flex-direction: column; gap: 2px; }
|
||
.voice-sub { font-size: 11px; font-weight: 700; color: var(--muted); }
|
||
.btn-ghost {
|
||
box-sizing: border-box;
|
||
border: 3px solid var(--line);
|
||
background: #fff;
|
||
color: var(--text);
|
||
border-radius: 14px;
|
||
padding: 8px 14px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
box-shadow: 3px 4px 0 var(--line);
|
||
}
|
||
.hud {
|
||
max-width: 1100px;
|
||
width: 100%;
|
||
margin: 0 auto 18px;
|
||
padding: 14px 18px 16px;
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 18px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.hud-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 12px; }
|
||
.stat .label { font-size: 12px; color: var(--muted); }
|
||
.stat .value {
|
||
margin-top: 4px;
|
||
font-size: 22px;
|
||
font-variant-numeric: tabular-nums;
|
||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||
}
|
||
.bar {
|
||
height: 10px;
|
||
background: #e8eef6;
|
||
border: 2px solid var(--line);
|
||
border-radius: 99px;
|
||
overflow: hidden;
|
||
}
|
||
.bar > i {
|
||
display: block;
|
||
height: 100%;
|
||
width: 0;
|
||
background: linear-gradient(90deg, #ff9f43, #ffe14d, #7be36a, #5ab8ff);
|
||
transition: width .12s linear;
|
||
}
|
||
.ime-banner {
|
||
max-width: 1100px;
|
||
width: 100%;
|
||
margin: 0 auto 14px;
|
||
padding: 10px 14px;
|
||
border-radius: 12px;
|
||
background: #ffe3e8;
|
||
color: #b42318;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
border: 3px solid var(--line);
|
||
}
|
||
.practice-main {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
}
|
||
.finger-banner {
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
padding: 10px 28px;
|
||
border: 3px solid var(--line);
|
||
border-radius: 999px;
|
||
box-shadow: 4px 5px 0 var(--line);
|
||
}
|
||
.finger-banner.orange { background: #ffb347; }
|
||
.finger-banner.yellow { background: #ffe14d; }
|
||
.finger-banner.green { background: #8ee87a; }
|
||
.finger-banner.blue { background: #7ec8ff; }
|
||
.finger-banner.purple { background: #ddd6fe; }
|
||
.hint {
|
||
margin: 0;
|
||
color: #35506c;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
.text-board {
|
||
width: min(1100px, 100%);
|
||
padding: 28px 20px;
|
||
background: #fffdf3;
|
||
border: 3px solid var(--line);
|
||
border-radius: 22px;
|
||
min-height: 140px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 14px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.text-line { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 7px; }
|
||
.glyph {
|
||
width: 1.55em;
|
||
height: 1.7em;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||
font-size: 28px;
|
||
color: #8a96a8;
|
||
background: #fff;
|
||
border: 3px solid #d7e0ea;
|
||
border-radius: 12px;
|
||
}
|
||
.glyph.ch-a, .glyph.ch-semi, .glyph.ch-q, .glyph.ch-p { border-color: #ff9f43; }
|
||
.glyph.ch-s, .glyph.ch-l, .glyph.ch-w, .glyph.ch-o { border-color: #e6c200; }
|
||
.glyph.ch-d, .glyph.ch-k, .glyph.ch-e, .glyph.ch-i { border-color: #3db54a; }
|
||
.glyph.ch-f, .glyph.ch-j, .glyph.ch-r, .glyph.ch-t, .glyph.ch-y, .glyph.ch-u { border-color: #2f8fe0; }
|
||
.glyph.ch-space { width: 2.4em; border-color: #c4b5fd; }
|
||
.glyph.done { color: #fff; background: #22c55e; border-color: #15803d; }
|
||
.glyph.current {
|
||
text-decoration: underline;
|
||
text-underline-offset: 6px;
|
||
transform: scale(1.18);
|
||
box-shadow: 0 4px 0 var(--line);
|
||
}
|
||
.glyph.current.ch-a, .glyph.current.ch-semi, .glyph.current.ch-q, .glyph.current.ch-p { background: #ffb347; color: #5a2b00; }
|
||
.glyph.current.ch-s, .glyph.current.ch-l, .glyph.current.ch-w, .glyph.current.ch-o { background: #ffe14d; color: #5a4300; }
|
||
.glyph.current.ch-d, .glyph.current.ch-k, .glyph.current.ch-e, .glyph.current.ch-i { background: #8ee87a; color: #145214; }
|
||
.glyph.current.ch-f, .glyph.current.ch-j, .glyph.current.ch-r, .glyph.current.ch-t, .glyph.current.ch-y, .glyph.current.ch-u { background: #7ec8ff; color: #0a3a66; }
|
||
.glyph.current.ch-space { background: #ddd6fe; color: #5b21b6; }
|
||
.glyph.current.is-wrong {
|
||
background: #ff4d6d !important;
|
||
color: #fff !important;
|
||
border-color: #9f1239;
|
||
animation: shake .22s ease;
|
||
}
|
||
@keyframes shake {
|
||
0%, 100% { transform: scale(1.18) translateX(0); }
|
||
25% { transform: scale(1.18) translateX(-4px); }
|
||
75% { transform: scale(1.18) translateX(4px); }
|
||
}
|
||
.keyboard-wrap { width: min(980px, 100%); }
|
||
.keyboard {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 7px;
|
||
padding: 16px;
|
||
background: #fff7d6;
|
||
border: 3px solid var(--line);
|
||
border-radius: 20px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.kb-row { display: flex; justify-content: center; gap: 6px; }
|
||
.key {
|
||
--w: 46px;
|
||
height: 46px;
|
||
width: var(--w);
|
||
flex: 0 0 var(--w);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 12px;
|
||
border: 3px solid var(--line);
|
||
background: #eef3f8;
|
||
color: #8a96a8;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
font-family: "SF Mono", Menlo, Consolas, monospace;
|
||
opacity: .38;
|
||
box-shadow: 0 3px 0 #b8c2d0;
|
||
}
|
||
.key.enabled { opacity: 1; color: #243044; box-shadow: 0 3px 0 var(--line); }
|
||
.key.finger-orange { background: #ffb347; }
|
||
.key.finger-yellow { background: #ffe14d; }
|
||
.key.finger-green { background: #8ee87a; }
|
||
.key.finger-blue { background: #7ec8ff; }
|
||
.key.finger-purple { background: #ddd6fe; }
|
||
.key.target {
|
||
transform: scale(1.08);
|
||
box-shadow: 0 0 0 3px #fff, 0 5px 0 var(--line);
|
||
animation: pulse 1.1s ease-in-out infinite;
|
||
}
|
||
.key.flash-ok { background: #22c55e !important; color: #fff; }
|
||
.key.flash-bad { background: #ff4d6d !important; color: #fff; }
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1.08); }
|
||
50% { transform: scale(1.14); }
|
||
}
|
||
.finger-legend {
|
||
margin: 12px 0 0;
|
||
text-align: center;
|
||
color: #35506c;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
.c-orange { color: #d97706; }
|
||
.c-yellow { color: #a16207; }
|
||
.c-green { color: #15803d; }
|
||
.c-blue { color: #1d4ed8; }
|
||
|
||
.modal-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(80, 150, 200, .35);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
z-index: 20;
|
||
overflow: hidden;
|
||
overscroll-behavior: none;
|
||
touch-action: none;
|
||
}
|
||
.modal-mask .stats-panel,
|
||
.modal-mask .modal {
|
||
touch-action: pan-y;
|
||
}
|
||
#stats-mask { z-index: 30; }
|
||
#words-mask { z-index: 31; }
|
||
#books-mask { z-index: 40; }
|
||
.modal {
|
||
width: min(460px, 100%);
|
||
background: #fffdf3;
|
||
border: 3px solid var(--line);
|
||
border-radius: 22px;
|
||
padding: 28px 26px 22px;
|
||
box-shadow: 6px 8px 0 var(--line);
|
||
}
|
||
.modal h3 { margin: 0 0 8px; font-size: 24px; }
|
||
.modal.pass h3 { color: var(--ok); }
|
||
.modal.fail h3 { color: var(--bad); }
|
||
.modal .msg { margin: 0 0 18px; color: var(--muted); font-size: 14px; }
|
||
.result-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px; }
|
||
.result-item {
|
||
background: #fff;
|
||
border: 3px solid var(--line);
|
||
border-radius: 12px;
|
||
padding: 10px 12px;
|
||
}
|
||
.result-item .k { color: var(--muted); font-size: 12px; }
|
||
.result-item .v { margin-top: 4px; font-size: 18px; font-family: "SF Mono", Menlo, Consolas, monospace; }
|
||
.unlock-line { margin: 0 0 16px; color: #1d4ed8; font-size: 13px; font-weight: 700; }
|
||
.modal-actions { display: flex; gap: 10px; }
|
||
.btn {
|
||
flex: 1;
|
||
border: 3px solid var(--line);
|
||
border-radius: 14px;
|
||
padding: 11px 14px;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
box-shadow: 3px 4px 0 var(--line);
|
||
}
|
||
.btn-primary { background: #ffe14d; color: #243044; }
|
||
.btn-secondary { background: #fff; color: var(--text); }
|
||
.toast {
|
||
position: fixed;
|
||
left: 50%;
|
||
bottom: 28px;
|
||
transform: translateX(-50%);
|
||
background: #ffe14d;
|
||
color: var(--text);
|
||
padding: 10px 16px;
|
||
border-radius: 999px;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
z-index: 30;
|
||
border: 3px solid var(--line);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
@media (max-width: 900px) {
|
||
.level-grid { grid-template-columns: repeat(2, 1fr); }
|
||
}
|
||
@media (max-width: 720px) {
|
||
.hud-row, .result-grid, .stats-day-grid, .book-grid, .voice-grid { grid-template-columns: 1fr; }
|
||
.glyph { font-size: 22px; }
|
||
.key { --w: 28px; height: 32px; font-size: 10px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="view-select">
|
||
<div class="brand">
|
||
<h1>打字练习</h1>
|
||
<span>基准键到上排键</span>
|
||
<button type="button" id="btn-stats">练习记录</button>
|
||
<button type="button" id="btn-words">背单词</button>
|
||
</div>
|
||
<p class="intro">从单个字母练起,再慢慢连起来;上排键每练一个,就和 ASDF JKL; 乱序合打一遍。</p>
|
||
<div id="chapter-list"></div>
|
||
</div>
|
||
|
||
<div id="view-practice" class="hidden">
|
||
<div class="topbar">
|
||
<h2 id="practice-title">练习</h2>
|
||
<div class="topbar-actions">
|
||
<button class="btn-ghost" id="btn-back" type="button">返回选关</button>
|
||
<button class="btn-ghost btn-speak hidden" id="btn-speak" type="button" title="再听一遍" aria-label="再听一遍">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path fill="currentColor" d="M3 8.6v6.8h4.2L14 21V3L7.2 8.6H3z"></path>
|
||
<path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" d="M16.7 8.3a4.7 4.7 0 0 1 0 7.4"></path>
|
||
<path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" d="M19.6 6a8.2 8.2 0 0 1 0 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="hud">
|
||
<div class="hud-row">
|
||
<div class="stat"><div class="label">进度</div><div class="value" id="stat-progress">0 / 10</div></div>
|
||
<div class="stat"><div class="label">正确率</div><div class="value" id="stat-accuracy">--</div></div>
|
||
<div class="stat"><div class="label">速度</div><div class="value" id="stat-kpm">--</div></div>
|
||
<div class="stat"><div class="label">用时</div><div class="value" id="stat-time">00:00</div></div>
|
||
</div>
|
||
<div class="bar"><i id="stat-bar"></i></div>
|
||
</div>
|
||
<div id="ime-banner" class="ime-banner hidden">请切换到英文输入法后再开始。</div>
|
||
<div class="practice-main">
|
||
<div class="finger-banner yellow" id="finger-banner">左手小指 · A</div>
|
||
<p class="hint" id="practice-hint"></p>
|
||
<div class="text-board">
|
||
<div class="text-line" id="text-line"></div>
|
||
<p class="word-meaning hidden" id="word-meaning"></p>
|
||
</div>
|
||
<div class="keyboard-wrap">
|
||
<div class="keyboard" id="keyboard"></div>
|
||
<p class="finger-legend" id="finger-legend"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="modal-mask" class="modal-mask hidden">
|
||
<div class="modal" id="result-modal">
|
||
<h3 id="result-title">通关</h3>
|
||
<p class="msg" id="result-msg"></p>
|
||
<div class="result-grid">
|
||
<div class="result-item"><div class="k" id="result-acc-label">正确率</div><div class="v" id="result-accuracy"></div></div>
|
||
<div class="result-item"><div class="k" id="result-kpm-label">速度</div><div class="v" id="result-kpm"></div></div>
|
||
<div class="result-item"><div class="k">用时</div><div class="v" id="result-time"></div></div>
|
||
<div class="result-item"><div class="k">击键 正确 / 错误 / 总计</div><div class="v" id="result-hits"></div></div>
|
||
</div>
|
||
<p class="unlock-line hidden" id="unlock-line"></p>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-primary hidden" id="btn-next" type="button">下一关</button>
|
||
<button class="btn btn-primary" id="btn-retry" type="button">再打一次</button>
|
||
<button class="btn btn-secondary" id="btn-to-select" type="button">返回选关</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="stats-mask" class="modal-mask hidden">
|
||
<div class="stats-panel" id="stats-panel">
|
||
<h3>练习记录</h3>
|
||
<p class="msg">按天查看练习时长、敲击次数和正确率。</p>
|
||
<div id="stats-list" class="stats-list"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-primary" id="btn-stats-close" type="button">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="words-mask" class="modal-mask hidden">
|
||
<div class="stats-panel words-panel">
|
||
<h3>背单词模式</h3>
|
||
<p class="msg">是否开始练习?</p>
|
||
<div class="rules">
|
||
规则:只打你已经通关关卡里出现过的字母。单词来自你勾选的词书,多选时在这些词书的合集里抽。每次屏幕上只出现一个单词和它的中文意思,打完这个词立刻消失,下一个词马上顶上。不点「返回选关」就不会停。如果已学按键只有 1 到 8 个字母,还不能开始,需要继续新学。
|
||
</div>
|
||
<p class="words-warn hidden" id="words-warn">学习的按键字母过少,请继续新学。</p>
|
||
<p class="voice-label">选择朗读人声</p>
|
||
<p class="voice-hint">不提供童声。只使用系统公开标注的英语人声,例如 Grandma 老年女声、Karen 澳洲女声、Lee 澳洲男声。名字或地区标签对不上的不录用,也不会拿别的声音顶替。</p>
|
||
<div id="voice-list" class="voice-grid"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-primary" id="btn-words-start" type="button">开始练习</button>
|
||
<button class="btn btn-secondary" id="btn-words-back" type="button">返回选关</button>
|
||
<button class="btn btn-secondary" id="btn-words-books" type="button">选定范围</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="books-mask" class="modal-mask hidden">
|
||
<div class="stats-panel words-panel">
|
||
<h3>选定词书范围</h3>
|
||
<p class="msg">可以多选。同时勾选时,单词只从这些词书里抽。</p>
|
||
<div id="book-list" class="book-grid"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-primary" id="btn-books-save" type="button">确定</button>
|
||
<button class="btn btn-secondary" id="btn-books-cancel" type="button">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="toast" class="toast hidden"></div>
|
||
|
||
<script>
|
||
window.WORD_BOOKS = [{"id":"primary-core","name":"小学核心词汇","words":[["apple","苹果"],["bag","包"],["ball","球"],["bed","床"],["big","大的"],["bird","鸟"],["blue","蓝色"],["book","书"],["box","盒子"],["boy","男孩"],["bus","公共汽车"],["cake","蛋糕"],["cat","猫"],["chair","椅子"],["class","班级"],["clock","时钟"],["coat","外套"],["color","颜色"],["come","来"],["cup","杯子"],["dad","爸爸"],["day","天"],["desk","课桌"],["dog","狗"],["door","门"],["egg","鸡蛋"],["face","脸"],["family","家庭"],["father","父亲"],["fish","鱼"],["floor","地板"],["flower","花"],["food","食物"],["friend","朋友"],["game","游戏"],["girl","女孩"],["good","好的"],["green","绿色"],["hand","手"],["happy","高兴的"],["hat","帽子"],["head","头"],["help","帮助"],["home","家"],["house","房子"],["jump","跳"],["kid","小孩"],["kind","友善的"],["lamp","灯"],["leg","腿"],["light","光"],["like","喜欢"],["look","看"],["make","制作"],["man","男人"],["map","地图"],["milk","牛奶"],["mom","妈妈"],["moon","月亮"],["mother","母亲"],["name","名字"],["nice","美好的"],["nose","鼻子"],["open","打开"],["orange","橙子"],["pen","钢笔"],["pencil","铅笔"],["play","玩"],["red","红色"],["room","房间"],["run","跑"],["sad","难过的"],["school","学校"],["see","看见"],["sit","坐"],["sun","太阳"],["table","桌子"],["teacher","老师"],["tree","树"],["water","水"],["white","白色"],["window","窗户"],["yellow","黄色"],["yes","是的"],["you","你"],["zoo","动物园"],["ant","蚂蚁"],["arm","手臂"],["ask","问"],["baby","婴儿"],["back","后面"],["banana","香蕉"],["bear","熊"],["bee","蜜蜂"],["bike","自行车"],["black","黑色"],["bread","面包"],["brother","兄弟"],["car","汽车"],["card","卡片"],["clean","干净的"],["cold","冷的"],["cow","奶牛"],["dance","跳舞"],["draw","画"],["duck","鸭子"],["ear","耳朵"],["eat","吃"],["eye","眼睛"],["farm","农场"],["fast","快的"],["foot","脚"],["fox","狐狸"],["fun","有趣"],["gift","礼物"],["goat","山羊"],["hair","头发"],["hello","你好"],["hot","热的"],["ice","冰"],["job","工作"],["key","钥匙"],["king","国王"],["kite","风筝"],["leaf","叶子"],["lion","狮子"],["long","长的"],["love","爱"],["meat","肉"],["mouse","老鼠"],["new","新的"],["old","老的"],["park","公园"],["pig","猪"],["pink","粉色"],["rain","雨"],["rice","米饭"],["river","河"],["ship","船"],["shoe","鞋子"],["sing","唱歌"],["sister","姐妹"],["small","小的"],["smile","微笑"],["snow","雪"],["song","歌曲"],["star","星星"],["stop","停止"],["story","故事"],["swim","游泳"],["tall","高的"],["tea","茶"],["time","时间"],["toy","玩具"],["train","火车"],["walk","走"],["warm","温暖的"],["watch","手表"],["week","星期"],["wind","风"],["woman","女人"],["work","工作"],["year","年"],["young","年轻的"]]},{"id":"primary-all","name":"小学单词大全","words":[["after","在……之后"],["again","再次"],["always","总是"],["animal","动物"],["answer","回答"],["any","任何"],["around","在周围"],["away","离开"],["because","因为"],["before","在……之前"],["best","最好的"],["better","更好的"],["body","身体"],["both","两者"],["bring","带来"],["busy","忙碌的"],["call","打电话"],["care","关心"],["catch","抓住"],["city","城市"],["close","关闭"],["cloud","云"],["cook","烹饪"],["country","国家"],["cut","切"],["dark","黑暗的"],["dinner","晚餐"],["drink","喝"],["drive","开车"],["early","早的"],["easy","容易的"],["every","每一个"],["fall","落下"],["feel","感觉"],["find","找到"],["fine","好的"],["first","第一"],["fly","飞"],["forget","忘记"],["from","从"],["full","满的"],["give","给"],["great","很棒的"],["group","小组"],["grow","生长"],["hard","困难的"],["hear","听见"],["high","高的"],["hold","握住"],["hope","希望"],["hour","小时"],["hurt","受伤"],["idea","想法"],["keep","保持"],["know","知道"],["large","大的"],["last","最后的"],["late","晚的"],["learn","学习"],["leave","离开"],["left","左边"],["letter","信"],["line","线"],["little","小的"],["live","居住"],["many","许多"],["maybe","也许"],["meet","遇见"],["middle","中间"],["minute","分钟"],["more","更多"],["morning","早晨"],["move","移动"],["much","许多"],["must","必须"],["near","靠近"],["need","需要"],["never","从不"],["next","下一个"],["night","夜晚"],["number","数字"],["often","经常"],["once","一次"],["only","仅仅"],["other","其他的"],["over","在……上面"],["own","自己的"],["paper","纸"],["part","部分"],["people","人们"],["place","地方"],["plant","植物"],["please","请"],["point","指向"],["pretty","漂亮的"],["pull","拉"],["push","推"],["put","放"],["quick","快的"],["quiet","安静的"],["ready","准备好的"],["really","真正地"],["right","右边"],["same","相同的"],["second","第二"],["send","发送"],["shall","将"],["should","应该"],["show","展示"],["side","旁边"],["sleep","睡觉"],["slow","慢的"],["soon","很快"],["sound","声音"],["speak","说"],["start","开始"],["still","仍然"],["study","学习"],["such","这样的"],["sure","确定的"],["talk","谈话"],["tell","告诉"],["than","比"],["thank","谢谢"],["their","他们的"],["them","他们"],["then","然后"],["there","那里"],["these","这些"],["they","他们"],["thing","东西"],["think","思考"],["those","那些"],["together","一起"],["too","也"],["try","尝试"],["turn","转动"],["under","在……下面"],["until","直到"],["upon","在……上"],["use","使用"],["very","非常"],["wait","等待"],["want","想要"],["well","好地"],["were","是"],["what","什么"],["when","什么时候"],["where","哪里"],["which","哪一个"],["while","当……时"],["who","谁"],["why","为什么"],["will","将要"],["with","和"],["word","单词"],["would","将会"],["write","写"],["your","你的"]]},{"id":"junior","name":"初中单词","words":[["able","能够"],["about","关于"],["above","在上面"],["across","穿过"],["afraid","害怕的"],["afternoon","下午"],["age","年龄"],["ago","以前"],["agree","同意"],["air","空气"],["almost","几乎"],["alone","独自"],["along","沿着"],["already","已经"],["also","也"],["although","虽然"],["among","在……之中"],["angry","生气的"],["another","另一个"],["anyone","任何人"],["anything","任何事物"],["appear","出现"],["area","地区"],["arrive","到达"],["art","艺术"],["article","文章"],["become","变成"],["begin","开始"],["believe","相信"],["below","在下面"],["between","在……之间"],["birthday","生日"],["borrow","借"],["bottle","瓶子"],["break","打破"],["bridge","桥"],["bright","明亮的"],["build","建造"],["building","建筑物"],["business","生意"],["buy","买"],["camera","相机"],["camp","营地"],["capital","首都"],["careful","小心的"],["carry","携带"],["center","中心"],["change","改变"],["cheap","便宜的"],["child","孩子"],["choose","选择"],["cinema","电影院"],["circle","圆"],["clean","打扫"],["clear","清楚的"],["clever","聪明的"],["climb","爬"],["clothes","衣服"],["cloudy","多云的"],["club","俱乐部"],["collect","收集"],["college","学院"],["common","普通的"],["complete","完成"],["computer","电脑"],["continue","继续"],["control","控制"],["conversation","对话"],["cook","厨师"],["copy","复制"],["correct","正确的"],["cost","花费"],["could","能够"],["count","数"],["course","课程"],["cover","覆盖"],["cross","穿过"],["crowd","人群"],["cry","哭"],["culture","文化"],["danger","危险"],["dangerous","危险的"],["date","日期"],["decide","决定"],["deep","深的"],["describe","描述"],["diary","日记"],["dictionary","词典"],["different","不同的"],["difficult","困难的"],["direction","方向"],["discover","发现"],["discuss","讨论"],["dish","盘子"],["during","在……期间"],["each","每个"],["earth","地球"],["east","东"],["education","教育"],["either","也"],["else","其他"],["empty","空的"],["end","结束"],["energy","能量"],["enjoy","享受"],["enough","足够的"],["enter","进入"],["even","甚至"],["evening","晚上"],["ever","曾经"],["everyone","每个人"],["everything","一切"],["example","例子"],["except","除了"],["excuse","原谅"],["exercise","练习"],["expect","期待"],["expensive","昂贵的"],["experience","经验"],["explain","解释"],["factory","工厂"],["fail","失败"],["famous","著名的"],["far","远的"],["farm","农场"],["favorite","最喜欢的"],["few","很少"],["field","田野"],["fight","打架"],["fill","装满"],["finally","最后"],["finger","手指"],["finish","完成"],["fire","火"],["follow","跟随"],["foreign","外国的"],["forest","森林"],["form","表格"],["free","免费的"],["fresh","新鲜的"],["front","前面"],["future","未来"],["garden","花园"],["geography","地理"],["get","得到"],["glass","玻璃"],["grade","年级"],["ground","地面"],["guess","猜测"],["guide","向导"],["habit","习惯"],["half","一半"],["happen","发生"],["health","健康"],["healthy","健康的"],["heart","心脏"],["heavy","重的"],["history","历史"],["holiday","假日"],["hospital","医院"],["hotel","酒店"],["however","然而"],["huge","巨大的"],["human","人类"],["hungry","饥饿的"],["important","重要的"],["improve","提高"],["include","包括"],["inside","在里面"],["instead","代替"],["interest","兴趣"],["interesting","有趣的"],["internet","互联网"],["introduce","介绍"],["invite","邀请"],["island","岛屿"],["join","加入"],["joke","笑话"],["journey","旅程"],["just","只是"],["kitchen","厨房"],["knife","刀"],["lake","湖"],["language","语言"],["later","后来"],["laugh","笑"],["law","法律"],["lazy","懒惰的"],["least","最少"],["lesson","课"],["library","图书馆"],["life","生活"],["listen","听"],["lonely","孤独的"],["lose","失去"],["loud","大声的"],["low","低的"],["lucky","幸运的"],["machine","机器"],["main","主要的"],["market","市场"],["match","比赛"],["matter","事情"],["meal","一餐"],["mean","意思是"],["medicine","药"],["member","成员"],["message","信息"],["method","方法"],["mind","头脑"],["miss","想念"],["modern","现代的"],["moment","片刻"],["money","钱"],["month","月"],["mountain","山"],["movie","电影"],["music","音乐"],["nature","自然"],["nearly","几乎"],["necessary","必要的"],["neighbour","邻居"],["news","新闻"],["nobody","没有人"],["noise","噪音"],["none","没有一个"],["north","北"],["nothing","没有东西"],["notice","注意"],["object","物体"],["ocean","海洋"],["off","离开"],["office","办公室"],["oil","油"],["once","曾经"],["online","在线的"],["order","命令"],["outside","在外面"],["page","页"],["pair","一双"],["parent","父母"],["pass","通过"],["past","过去"],["patient","病人"],["pay","支付"],["peace","和平"],["person","人"],["photo","照片"],["piece","一块"],["plan","计划"],["player","选手"],["police","警察"],["polite","有礼貌的"],["poor","贫穷的"],["popular","受欢迎的"],["possible","可能的"],["practice","练习"],["prefer","更喜欢"],["prepare","准备"],["present","礼物"],["price","价格"],["problem","问题"],["produce","生产"],["product","产品"],["program","节目"],["progress","进步"],["project","项目"],["promise","承诺"],["protect","保护"],["proud","骄傲的"],["provide","提供"],["public","公共的"],["purpose","目的"],["question","问题"],["quite","相当"],["radio","收音机"],["raise","举起"],["reach","到达"],["read","阅读"],["real","真实的"],["reason","原因"],["receive","收到"],["remember","记得"],["report","报告"],["rest","休息"],["result","结果"],["return","返回"],["rich","富有的"],["ride","骑"],["rise","上升"],["road","路"],["rock","岩石"],["rule","规则"],["safe","安全的"],["salt","盐"],["save","拯救"],["science","科学"],["search","搜索"],["season","季节"],["secret","秘密"],["seem","似乎"],["sell","卖"],["send","寄"],["sentence","句子"],["serious","严肃的"],["service","服务"],["several","几个"],["share","分享"],["shop","商店"],["short","短的"],["shout","喊"],["sick","生病的"],["sign","标志"],["simple","简单的"],["since","自从"],["size","尺寸"],["skill","技能"],["sky","天空"],["smart","聪明的"],["smoke","烟"],["society","社会"],["soft","软的"],["soldier","士兵"],["someone","某人"],["something","某事"],["sometimes","有时"],["south","南"],["space","空间"],["special","特别的"],["spend","花费"],["sport","运动"],["spring","春天"],["stand","站立"],["state","州"],["station","车站"],["stay","停留"],["steal","偷"],["step","步骤"],["stone","石头"],["store","商店"],["storm","暴风雨"],["straight","直的"],["strange","奇怪的"],["street","街道"],["strong","强壮的"],["student","学生"],["subject","科目"],["success","成功"],["sudden","突然的"],["suggest","建议"],["summer","夏天"],["sunny","晴朗的"],["support","支持"],["surprise","使吃惊"],["sweet","甜的"],["system","系统"],["take","拿"],["taste","味道"],["team","队伍"],["technology","技术"],["telephone","电话"],["temperature","温度"],["test","测试"],["text","课文"],["thin","瘦的"],["third","第三"],["thirsty","口渴的"],["though","虽然"],["through","穿过"],["throw","扔"],["ticket","票"],["tired","疲倦的"],["today","今天"],["tomorrow","明天"],["tonight","今晚"],["top","顶部"],["total","总计"],["touch","触摸"],["town","城镇"],["travel","旅行"],["trip","旅行"],["true","真实的"],["trust","信任"],["truth","真相"],["twice","两次"],["understand","理解"],["university","大学"],["useful","有用的"],["usual","通常的"],["vacation","假期"],["value","价值"],["village","村庄"],["visit","参观"],["voice","声音"],["wait","等待"],["wake","醒来"],["wall","墙"],["war","战争"],["wash","洗"],["waste","浪费"],["way","道路"],["weak","弱的"],["wear","穿"],["weather","天气"],["website","网站"],["west","西"],["whether","是否"],["whole","全部的"],["wide","宽的"],["win","赢"],["winter","冬天"],["wish","希望"],["without","没有"],["wonderful","精彩的"],["wood","木头"],["world","世界"],["worry","担心"],["worst","最坏的"],["worth","值得"],["wrong","错误的"],["yesterday","昨天"],["yet","还"]]},{"id":"zhongkao","name":"中考词汇","words":[["achieve","实现"],["action","行动"],["active","积极的"],["activity","活动"],["actually","实际上"],["advantage","优点"],["advice","建议"],["affect","影响"],["against","反对"],["allow","允许"],["amazing","令人惊讶的"],["ancient","古老的"],["announce","宣布"],["anyway","不管怎样"],["anywhere","任何地方"],["argue","争论"],["attention","注意"],["attitude","态度"],["avoid","避免"],["basic","基本的"],["beauty","美丽"],["behave","表现"],["behavior","行为"],["besides","此外"],["billion","十亿"],["biology","生物"],["bit","一点"],["blind","盲的"],["blood","血"],["board","木板"],["bomb","炸弹"],["border","边界"],["bored","无聊的"],["boring","乏味的"],["bottom","底部"],["brave","勇敢的"],["breathe","呼吸"],["broad","宽阔的"],["burn","燃烧"],["button","按钮"],["calm","平静的"],["cancer","癌症"],["candidate","候选人"],["carbon","碳"],["career","职业"],["cause","导致"],["celebrate","庆祝"],["century","世纪"],["certain","确定的"],["challenge","挑战"],["chance","机会"],["character","性格"],["charge","收费"],["check","检查"],["chemical","化学的"],["chemistry","化学"],["choice","选择"],["claim","声称"],["climate","气候"],["coach","教练"],["coast","海岸"],["coin","硬币"],["comfortable","舒适的"],["comment","评论"],["community","社区"],["compare","比较"],["competition","比赛"],["complain","抱怨"],["condition","条件"],["confident","自信的"],["consider","考虑"],["contain","包含"],["content","内容"],["convenient","方便的"],["corner","角落"],["create","创造"],["creative","有创造力的"],["credit","信用"],["crime","犯罪"],["cultural","文化的"],["customer","顾客"],["damage","损害"],["deal","处理"],["degree","程度"],["demand","要求"],["depend","依靠"],["design","设计"],["detail","细节"],["develop","发展"],["development","发展"],["device","装置"],["difference","差别"],["difficulty","困难"],["directly","直接地"],["disabled","残疾的"],["disappear","消失"],["disaster","灾难"],["discount","折扣"],["disease","疾病"],["divide","分开"],["double","双倍的"],["doubt","怀疑"],["downstairs","楼下"],["dream","梦想"],["drop","落下"],["drug","药"],["duty","职责"],["earthquake","地震"],["effect","效果"],["effort","努力"],["electric","电的"],["else","别的"],["emotion","情感"],["encourage","鼓励"],["engine","引擎"],["environment","环境"],["equal","平等的"],["especially","尤其"],["event","事件"],["everyday","日常的"],["exactly","确切地"],["exam","考试"],["excellent","优秀的"],["exchange","交换"],["exist","存在"],["exit","出口"],["express","表达"],["extra","额外的"],["fair","公平的"],["fan","粉丝"],["fear","害怕"],["feature","特征"],["festival","节日"],["figure","数字"],["final","最后的"],["fit","适合"],["fix","修理"],["flag","旗帜"],["flat","公寓"],["focus","集中"],["force","力量"],["forever","永远"],["forgive","原谅"],["former","以前的"],["forward","向前"],["freedom","自由"],["friendly","友好的"],["function","功能"],["further","进一步"],["gain","获得"],["general","一般的"],["generation","一代"],["gentle","温柔的"],["gesture","手势"],["global","全球的"],["goal","目标"],["government","政府"],["gradually","逐渐地"],["graduate","毕业"],["guard","守卫"],["guest","客人"],["hardly","几乎不"],["harm","伤害"],["heat","热"],["height","高度"],["hide","隐藏"],["highly","高度地"],["honest","诚实的"],["honor","荣誉"],["huge","巨大的"],["humor","幽默"],["immediately","立刻"],["increase","增加"],["independent","独立的"],["industry","工业"],["influence","影响"],["information","信息"],["insect","昆虫"],["insist","坚持"],["instance","例子"],["instrument","乐器"],["international","国际的"],["interview","面试"],["invent","发明"],["item","项目"],["jazz","爵士乐"],["joy","欢乐"],["judge","判断"],["junior","初级的"],["junk","垃圾"],["justice","正义"],["kick","踢"],["kilometer","千米"],["knowledge","知识"],["lack","缺乏"],["land","陆地"],["latest","最新的"],["lead","带领"],["leader","领导"],["level","水平"],["lie","躺"],["lift","举起"],["limit","限制"],["list","清单"],["local","当地的"],["lock","锁"],["lonely","孤独的"],["loss","损失"],["lovely","可爱的"],["mail","邮件"],["manage","管理"],["mark","标记"],["material","材料"],["math","数学"],["meaning","意思"],["measure","测量"],["medical","医疗的"],["memory","记忆"],["mention","提到"],["metal","金属"],["million","百万"],["mirror","镜子"],["mistake","错误"],["mix","混合"],["model","模型"],["monitor","班长"],["mostly","主要地"],["museum","博物馆"],["nation","国家"],["national","国家的"],["natural","自然的"],["negative","消极的"],["neither","两者都不"],["nervous","紧张的"],["network","网络"],["normal","正常的"],["note","笔记"],["novel","小说"],["nowhere","无处"],["nurse","护士"],["occur","发生"],["offer","提供"],["official","官方的"],["operate","操作"],["opinion","意见"],["opportunity","机会"],["opposite","相反的"],["option","选择"],["ordinary","普通的"],["organize","组织"],["origin","起源"],["otherwise","否则"],["ought","应该"],["ourselves","我们自己"],["owner","主人"],["pack","打包"],["pain","疼痛"],["paint","油漆"],["palace","宫殿"],["pardon","原谅"],["park","停车"],["particular","特定的"],["partner","伙伴"],["passenger","乘客"],["path","小路"],["percent","百分之"],["perfect","完美的"],["perform","表演"],["period","时期"],["permit","允许"],["personal","个人的"],["physics","物理"],["piano","钢琴"],["pilot","飞行员"],["planet","行星"],["plastic","塑料"],["pleasure","愉快"],["plenty","大量"],["plus","加"],["poem","诗"],["pollution","污染"],["population","人口"],["position","位置"],["positive","积极的"],["postcard","明信片"],["poster","海报"],["pot","锅"],["power","力量"],["powerful","强大的"],["praise","表扬"],["prevent","防止"],["pride","自豪"],["print","打印"],["private","私人的"],["prize","奖品"],["process","过程"],["profession","职业"],["professor","教授"],["profit","利润"],["pronounce","发音"],["proper","适当的"],["prove","证明"],["publish","出版"],["punish","惩罚"],["quality","质量"],["quarter","四分之一"],["race","比赛"],["railway","铁路"],["rapid","迅速的"],["rare","稀有的"],["rather","宁愿"],["realize","意识到"],["recent","最近的"],["record","记录"],["recycle","回收"],["reduce","减少"],["refer","提到"],["regard","认为"],["region","地区"],["regular","定期的"],["relation","关系"],["relative","亲戚"],["relax","放松"],["remain","保持"],["remind","提醒"],["remove","移除"],["repair","修理"],["repeat","重复"],["replace","替换"],["reply","回复"],["require","需要"],["research","研究"],["respect","尊重"],["responsible","负责的"],["review","复习"],["reward","奖励"],["role","角色"],["round","圆形的"],["row","一排"],["rubber","橡皮"],["rush","冲"],["safety","安全"],["sail","航行"],["sale","销售"],["sample","样品"],["satisfy","使满意"],["scene","场景"],["schedule","日程"],["score","分数"],["screen","屏幕"],["sea","海"],["search","搜寻"],["seat","座位"],["secret","秘密"],["section","部分"],["secure","安全的"],["seed","种子"],["seek","寻找"],["select","选择"],["self","自我"],["sense","感觉"],["separate","分开的"],["series","系列"],["serve","服务"],["set","一套"],["settle","解决"],["shake","摇"],["shape","形状"],["share","份额"],["sharp","锋利的"],["sheet","一张"],["shelf","架子"],["shock","震惊"],["shoot","射击"],["shut","关闭"],["sight","视力"],["silence","沉默"],["silent","安静的"],["silver","银"],["similar","相似的"],["single","单个的"],["sink","下沉"],["situation","情况"],["skill","技能"],["skin","皮肤"],["slave","奴隶"],["slide","滑动"],["slight","轻微的"],["smooth","光滑的"],["social","社会的"],["soil","土壤"],["solid","固体的"],["solve","解决"],["sort","种类"],["soul","灵魂"],["source","来源"],["spare","空闲的"],["specific","具体的"],["speech","演讲"],["speed","速度"],["spirit","精神"],["split","分裂"],["spot","地点"],["spread","传播"],["square","广场"],["stage","舞台"],["standard","标准"],["statement","声明"],["statue","雕像"],["steel","钢"],["stick","粘贴"],["stock","库存"],["stomach","胃"],["strategy","策略"],["stress","压力"],["strict","严格的"],["strike","罢工"],["structure","结构"],["struggle","奋斗"],["style","风格"],["succeed","成功"],["sudden","突然"],["suffer","遭受"],["suit","适合"],["super","超级的"],["supply","供应"],["surface","表面"],["survive","生存"],["symbol","象征"],["talent","才能"],["task","任务"],["tax","税"],["technique","技术"],["teen","青少年"],["term","学期"],["theory","理论"],["therefore","因此"],["thick","厚的"],["thought","思想"],["threat","威胁"],["throughout","遍及"],["thus","因此"],["title","标题"],["tool","工具"],["topic","话题"],["tour","旅游"],["trade","贸易"],["tradition","传统"],["traffic","交通"],["translate","翻译"],["transport","运输"],["treat","对待"],["trend","趋势"],["trial","试验"],["type","类型"],["typical","典型的"],["ugly","丑陋的"],["unable","不能的"],["union","联盟"],["unique","独特的"],["unit","单元"],["unless","除非"],["update","更新"],["upper","上面的"],["upstairs","楼上"],["urban","城市的"],["user","用户"],["various","各种各样的"],["vehicle","车辆"],["version","版本"],["view","观点"],["violence","暴力"],["virus","病毒"],["visitor","访客"],["vital","至关重要的"],["volume","音量"],["volunteer","志愿者"],["vote","投票"],["warn","警告"],["wave","挥手"],["wealth","财富"],["weapon","武器"],["weight","重量"],["welcome","欢迎"],["wheel","轮子"],["whenever","无论何时"],["whisper","低语"],["whose","谁的"],["wild","野生的"],["willing","愿意的"],["wine","葡萄酒"],["wing","翅膀"],["winner","获胜者"],["wire","电线"],["wise","明智的"],["within","在……之内"],["witness","证人"],["wonder","想知道"],["worker","工人"],["wound","伤口"],["youth","青年"]]},{"id":"senior","name":"高中单词","words":[["abandon","放弃"],["absolute","绝对的"],["abstract","抽象的"],["academic","学术的"],["access","进入"],["accompany","陪伴"],["accomplish","完成"],["account","账户"],["accurate","准确的"],["accuse","指责"],["acknowledge","承认"],["acquire","获得"],["adapt","适应"],["adequate","足够的"],["adjust","调整"],["admire","钦佩"],["admit","承认"],["adopt","采用"],["advance","前进"],["affair","事务"],["agency","机构"],["agenda","议程"],["aggressive","好斗的"],["agriculture","农业"],["aid","援助"],["airline","航空公司"],["alert","警觉的"],["alien","外星人"],["alive","活着的"],["allocate","分配"],["alter","改变"],["alternative","可供选择的"],["amaze","使惊叹"],["ambition","野心"],["amount","数量"],["amuse","逗乐"],["analyse","分析"],["ancestor","祖先"],["angle","角度"],["anniversary","周年纪念"],["annual","每年的"],["anxiety","焦虑"],["apart","分开"],["apology","道歉"],["apparent","明显的"],["appeal","呼吁"],["appendix","附录"],["appetite","食欲"],["applicant","申请人"],["application","申请"],["apply","申请"],["appoint","任命"],["appreciate","感激"],["approach","接近"],["appropriate","适当的"],["approve","批准"],["approximate","大约的"],["architect","建筑师"],["architecture","建筑"],["arise","出现"],["arouse","引起"],["arrange","安排"],["arrest","逮捕"],["arrow","箭"],["artificial","人造的"],["ashamed","羞愧的"],["aspect","方面"],["assault","攻击"],["assemble","组装"],["assess","评估"],["asset","资产"],["assign","分配"],["assist","帮助"],["associate","联系"],["assume","假定"],["assure","保证"],["astonish","使惊讶"],["athlete","运动员"],["atmosphere","气氛"],["attach","附上"],["attack","攻击"],["attain","获得"],["attempt","尝试"],["attend","参加"],["attract","吸引"],["attribute","属性"],["audience","观众"],["author","作者"],["authority","权威"],["automatic","自动的"],["available","可用的"],["average","平均的"],["award","奖"],["aware","意识到的"],["awful","糟糕的"],["background","背景"],["balance","平衡"],["ban","禁止"],["band","乐队"],["bar","酒吧"],["bargain","便宜货"],["barrier","障碍"],["base","基础"],["basis","基础"],["battle","战斗"],["beach","海滩"],["beam","光束"],["bear","忍受"],["beat","打败"],["behalf","代表"],["benefit","益处"],["bias","偏见"],["bid","出价"],["bind","捆"],["biological","生物的"],["blame","责备"],["blank","空白的"],["blast","爆炸"],["bleed","流血"],["blend","混合"],["block","街区"],["bloom","开花"],["boast","吹嘘"],["bold","大胆的"],["bond","纽带"],["boom","繁荣"],["boost","促进"],["bother","打扰"],["bound","一定的"],["bow","鞠躬"],["branch","分支"],["brand","品牌"],["brief","简短的"],["brilliant","杰出的"],["budget","预算"],["bulk","大量"],["bump","碰撞"],["bunch","一束"],["burden","负担"],["burst","爆发"],["calculate","计算"],["campaign","运动"],["cancel","取消"],["capability","能力"],["capable","有能力的"],["capacity","容量"],["capture","捕捉"],["cast","投"],["casual","随便的"],["category","类别"],["cease","停止"],["ceremony","仪式"],["certificate","证书"],["chain","链条"],["chairman","主席"],["chamber","房间"],["champion","冠军"],["chaos","混乱"],["characteristic","特征"],["chart","图表"],["chase","追逐"],["chat","聊天"],["cheat","欺骗"],["chief","主要的"],["chip","芯片"],["circuit","电路"],["circumstance","情况"],["cite","引用"],["civil","公民的"],["clarify","澄清"],["classic","经典的"],["classify","分类"],["clause","条款"],["client","客户"],["clinic","诊所"],["code","代码"],["collapse","倒塌"],["colleague","同事"],["column","专栏"],["combat","战斗"],["combine","结合"],["comedy","喜剧"],["command","命令"],["commence","开始"],["comment","评论"],["commission","委员会"],["commit","承诺"],["committee","委员会"],["communicate","交流"],["companion","同伴"],["company","公司"],["compel","强迫"],["compensate","补偿"],["compete","竞争"],["compile","汇编"],["complex","复杂的"],["component","组成部分"],["compose","组成"],["compound","化合物"],["comprehensive","全面的"],["comprise","包含"],["compromise","妥协"],["compute","计算"],["conceal","隐藏"],["concede","承认"],["concentrate","集中"],["concept","概念"],["concern","关心"],["conclude","得出结论"],["concrete","具体的"],["condemn","谴责"],["conduct","进行"],["confer","商议"],["confess","坦白"],["confidence","信心"],["confine","限制"],["confirm","确认"],["conflict","冲突"],["conform","遵从"],["confront","面对"],["confuse","使困惑"],["congratulate","祝贺"],["congress","国会"],["connect","连接"],["conquer","征服"],["conscience","良心"],["conscious","有意识的"],["consensus","共识"],["consent","同意"],["consequence","后果"],["conservative","保守的"],["considerable","相当大的"],["consist","由……组成"],["consistent","一致的"],["constant","不断的"],["constitute","构成"],["construct","建造"],["consult","咨询"],["consume","消耗"],["contact","联系"],["contemporary","当代的"],["contest","比赛"],["context","语境"],["continent","大陆"],["contract","合同"],["contradict","反驳"],["contrary","相反的"],["contrast","对比"],["contribute","贡献"],["controversy","争议"],["convenience","便利"],["convention","惯例"],["convert","转变"],["convey","传达"],["convince","说服"],["cooperate","合作"],["coordinate","协调"],["cope","应付"],["core","核心"],["corporate","公司的"],["correspond","通信"],["corrupt","腐败的"],["council","委员会"],["counsel","建议"],["counter","柜台"],["county","郡"],["couple","一对"],["courage","勇气"],["court","法院"],["crack","裂缝"],["craft","工艺"],["crash","撞击"],["crawl","爬行"],["creature","生物"],["credit","学分"],["crew","全体人员"],["crisis","危机"],["criterion","标准"],["critic","批评家"],["critical","关键的"],["criticize","批评"],["crop","庄稼"],["crucial","至关重要的"],["cruel","残酷的"],["crush","压碎"],["crystal","水晶"],["cultivate","培养"],["cure","治愈"],["curious","好奇的"],["current","当前的"],["curve","曲线"],["custom","习俗"],["cycle","循环"]]},{"id":"gaokao","name":"高考词汇","words":[["debate","辩论"],["decade","十年"],["decline","下降"],["decorate","装饰"],["decrease","减少"],["defeat","击败"],["defence","防御"],["deficit","赤字"],["define","定义"],["definite","明确的"],["delegate","代表"],["delete","删除"],["deliberate","故意的"],["delicate","精致的"],["delight","高兴"],["deliver","递送"],["democracy","民主"],["demonstrate","证明"],["dense","密集的"],["deny","否认"],["depart","离开"],["department","部门"],["departure","离开"],["depend","依赖"],["depict","描绘"],["deposit","存款"],["depress","使沮丧"],["derive","源自"],["descend","下降"],["deserve","应得"],["desire","渴望"],["desperate","绝望的"],["destroy","破坏"],["detect","发现"],["determine","决定"],["devote","奉献"],["diagnose","诊断"],["diagram","图表"],["dialect","方言"],["dictate","口述"],["differ","不同"],["digest","消化"],["digital","数字的"],["dimension","维度"],["diminish","减少"],["dip","浸"],["diplomatic","外交的"],["direct","直接的"],["disable","使残疾"],["discard","丢弃"],["discharge","排放"],["discipline","纪律"],["disclose","透露"],["discount","打折"],["discourage","使气馁"],["discourse","话语"],["discriminate","歧视"],["disgust","厌恶"],["dismiss","解雇"],["disorder","混乱"],["display","展示"],["dispose","处理"],["dispute","争论"],["disrupt","扰乱"],["dissolve","溶解"],["distinct","明显的"],["distinguish","区分"],["distort","扭曲"],["distract","分心"],["distribute","分发"],["district","地区"],["disturb","打扰"],["diverse","多样的"],["divide","划分"],["divine","神圣的"],["doctrine","教义"],["document","文件"],["domain","领域"],["domestic","国内的"],["dominant","占优势的"],["donate","捐赠"],["dose","剂量"],["draft","草稿"],["drag","拖"],["drain","排水"],["drama","戏剧"],["dramatic","戏剧性的"],["drift","漂流"],["drill","钻孔"],["drought","干旱"],["drown","淹死"],["due","到期的"],["dull","乏味的"],["dump","倾倒"],["durable","耐用的"],["duration","持续时间"],["dust","灰尘"],["dynamic","动态的"],["eager","渴望的"],["earn","赚得"],["ease","轻松"],["echo","回声"],["economic","经济的"],["economy","经济"],["edge","边缘"],["edit","编辑"],["edition","版本"],["editor","编辑"],["educate","教育"],["effective","有效的"],["efficient","高效的"],["elaborate","详尽的"],["elastic","有弹性的"],["elect","选举"],["elegant","优雅的"],["element","元素"],["elementary","初级的"],["eliminate","消除"],["elite","精英"],["embark","着手"],["embarrass","使尴尬"],["embrace","拥抱"],["emerge","出现"],["emergency","紧急情况"],["emit","发出"],["emotion","情绪"],["emphasis","强调"],["empire","帝国"],["empirical","经验的"],["employ","雇用"],["enable","使能够"],["enclose","围住"],["encounter","遭遇"],["endless","无尽的"],["endorse","支持"],["endure","忍受"],["enforce","实施"],["engage","从事"],["enhance","增强"],["enormous","巨大的"],["enrich","丰富"],["ensure","确保"],["entertain","娱乐"],["enthusiasm","热情"],["entitle","给……权利"],["entry","进入"],["envelope","信封"],["environment","环境"],["episode","一集"],["equal","相等的"],["equip","装备"],["equivalent","相等的"],["era","时代"],["erect","直立的"],["error","错误"],["escape","逃跑"],["essay","文章"],["essential","必要的"],["establish","建立"],["estate","地产"],["estimate","估计"],["ethic","伦理"],["evaluate","评价"],["even","平坦的"],["evidence","证据"],["evident","明显的"],["evolve","进化"],["exact","精确的"],["exaggerate","夸张"],["exceed","超过"],["exception","例外"],["excess","过量"],["exchange","交换"],["excite","使兴奋"],["exclude","排除"],["exclusive","独家的"],["execute","执行"],["exercise","锻炼"],["exhaust","耗尽"],["exhibit","展览"],["exist","存在"],["expand","扩大"],["expansion","扩张"],["expect","预料"],["expense","费用"],["experiment","实验"],["expert","专家"],["expire","到期"],["explain","解释"],["explicit","明确的"],["explode","爆炸"],["exploit","利用"],["explore","探索"],["export","出口"],["expose","暴露"],["express","快递"],["extend","延伸"],["extension","延长"],["extensive","广泛的"],["extent","程度"],["external","外部的"],["extract","提取"],["extreme","极端的"]]},{"id":"cet4","name":"四级词汇","words":[["ability","能力"],["abroad","在国外"],["absence","缺席"],["absolute","绝对的"],["absorb","吸收"],["abstract","摘要"],["abundant","丰富的"],["abuse","滥用"],["academic","学院的"],["accelerate","加速"],["accent","口音"],["acceptance","接受"],["access","通道"],["accident","事故"],["accommodation","住宿"],["accompany","伴随"],["accomplish","达成"],["according","根据"],["account","叙述"],["accumulate","积累"],["accurate","精确的"],["accuse","控告"],["accustomed","习惯的"],["achieve","达到"],["acid","酸"],["acknowledge","致谢"],["acquaintance","熟人"],["acquire","获得"],["action","行动"],["active","活跃的"],["activity","活动"],["actor","男演员"],["actual","实际的"],["adapt","改编"],["addition","增加"],["address","地址"],["adequate","适当的"],["adjust","校准"],["administration","管理"],["admire","羡慕"],["admission","准许进入"],["admit","准许进入"],["adopt","收养"],["adult","成年人"],["advance","预先"],["advantage","优势"],["adventure","冒险"],["advertise","登广告"],["advice","劝告"],["advisable","明智的"],["affair","风流韵事"],["affect","打动"],["afford","负担得起"],["afraid","担心的"],["afterwards","后来"],["agency","代理"],["agenda","待议事项"],["agent","代理人"],["aggressive","侵略的"],["agreement","协议"],["agriculture","农学"],["ahead","向前"],["aid","助手"],["aim","目标"],["aircraft","飞机"],["airline","航线"],["airport","机场"],["alarm","警报"],["album","相册"],["alcohol","酒精"],["alert","警报"],["alike","相像的"],["alive","有活力的"],["alliance","联盟"],["allowance","津贴"],["ally","同盟国"],["almost","差不多"],["alone","单独"],["alongside","在旁边"],["aloud","大声地"],["alphabet","字母表"],["alter","更改"],["alternative","替代物"],["altitude","海拔"],["altogether","总共"],["amateur","业余爱好者"],["amaze","使惊奇"],["ambassador","大使"],["ambition","抱负"],["ambulance","救护车"],["amend","修改"],["amid","在……中"],["amount","总额"],["amuse","使娱乐"],["analyse","剖析"],["analysis","分析"],["ancestor","祖宗"],["anchor","锚"],["ancient","古代的"],["anger","怒火"],["angle","角"],["angry","发怒的"],["ankle","脚踝"],["anniversary","周年"],["announce","通告"],["annoy","使恼怒"],["annual","一年一次的"],["anonymous","匿名的"],["anxiety","忧虑"],["anxious","焦虑的"],["anyhow","无论如何"],["anyway","总之"],["apart","相距"],["apartment","公寓"],["apologize","道歉"],["apology","歉意"],["apparent","表面上的"],["appeal","上诉"],["appear","显得"],["appearance","外貌"],["appendix","阑尾"],["appetite","胃口"],["applaud","鼓掌"],["apple","苹果"],["appliance","器具"],["applicable","适用的"],["applicant","求职者"],["application","应用"],["apply","应用"],["appoint","约定"],["appointment","约会"],["appreciate","欣赏"],["approach","方法"],["appropriate","恰当的"],["approval","批准"],["approve","赞成"],["approximate","近似的"],["arbitrary","任意的"],["architect","设计师"],["architecture","建筑学"],["archive","档案"],["area","面积"],["argue","争吵"],["argument","论据"],["arise","产生"],["arithmetic","算术"],["arm","武器"],["army","军队"],["around","大约"],["arouse","唤醒"],["arrange","整理"],["arrangement","布置"],["arrest","拘留"],["arrival","到达"],["arrive","抵达"],["arrow","箭头"],["article","物品"],["artificial","假的"],["artist","艺术家"],["artistic","艺术的"],["ash","灰烬"],["ashamed","惭愧的"],["aside","到旁边"],["ask","请求"],["asleep","睡着的"],["aspect","外观"],["assemble","集合"],["assembly","集会"],["assess","估价"],["asset","财产"],["assign","指派"],["assignment","作业"],["assist","协助"],["assistance","援助"],["assistant","助手"],["associate","同事"],["association","协会"],["assume","承担"],["assumption","假设"],["assure","确保"],["astonish","使吃惊"],["athlete","田径运动员"],["atmosphere","大气"],["atom","原子"],["attach","贴上"],["attack","抨击"],["attain","达到"],["attempt","企图"],["attend","照料"],["attention","注意力"],["attitude","看法"],["attract","引起"],["attraction","吸引力"],["attractive","有吸引力的"],["attribute","把……归因于"],["audience","听众"],["audio","音频"],["audit","审计"],["august","威严的"],["author","著者"],["authority","当局"],["auto","汽车"],["automatic","自动化的"],["autonomy","自治"],["autumn","秋天"],["available","有空的"],["avenue","大街"],["average","普通的"],["avoid","躲开"],["await","等候"],["awake","醒着的"],["award","授予"],["aware","知道的"],["away","远离"],["awful","可怕的"],["awkward","尴尬的"],["axis","轴"]]},{"id":"cet6","name":"六级词汇","words":[["abolish","废除"],["abrupt","突然的"],["absurd","荒谬的"],["abundance","充裕"],["academy","研究院"],["accessory","附件"],["accommodate","容纳"],["accord","符合"],["accountant","会计"],["accumulate","积聚"],["accurate","精密的"],["accustom","使习惯"],["acid","尖刻的"],["acknowledge","承认"],["acquaint","使认识"],["activate","激活"],["acute","敏锐的"],["adapt","使适应"],["addict","使上瘾"],["adhere","粘附"],["adjacent","邻近的"],["adjoin","毗连"],["administer","管理"],["adolescent","青少年"],["adore","崇拜"],["adverse","不利的"],["advocate","提倡"],["aerial","航空的"],["aesthetic","审美的"],["affiliate","使隶属"],["affirm","断言"],["aggravate","加重"],["agitate","煽动"],["agony","极度痛苦"],["agreeable","令人愉快的"],["air","使通风"],["aisle","过道"],["album","唱片集"],["alert","机警的"],["alien","外国的"],["allege","声称"],["alleviate","减轻"],["allocate","分派"],["alloy","合金"],["ally","结盟"],["alter","改变"],["alternate","轮流"],["amateur","外行的"],["amaze","使惊愕"],["ambiguous","模棱两可的"],["ambitious","有野心的"],["amend","修正"],["amiable","和蔼可亲的"],["amplify","放大"],["analogy","类比"],["analyst","分析师"],["ancestor","祖先"],["anecdote","轶事"],["angel","天使"],["animate","使有生气"],["annex","附录"],["announce","宣布"],["anonymous","不具名的"],["antarctic","南极的"],["anticipate","预期"],["antique","古董"],["anxiety","渴望"],["ape","猿"],["apparatus","器械"],["appeal","吸引力"],["appendix","附属物"],["applaud","称赞"],["appliance","家用电器"],["applicable","可应用的"],["appraisal","评价"],["apt","易于……的"],["arc","弧"],["arch","拱门"],["architect","建筑师"],["archive","把……存档"],["ardent","热情的"],["arena","竞技场"],["argue","论证"],["arid","干旱的"],["aristocrat","贵族"],["armor","盔甲"],["arouse","激起"],["array","一系列"],["arrogant","傲慢的"],["artery","动脉"],["articulate","口齿清楚的"],["artillery","大炮"],["ascend","上升"],["ascertain","查明"],["ascribe","把……归于"],["ashore","在岸上"],["assault","猛烈攻击"],["assert","坚称"],["assess","评定"],["asset","资产"],["assimilate","吸收"],["associate","使联合"],["assumption","承担"],["assurance","保证"],["astronomy","天文学"],["asylum","庇护"],["athlete","运动员"],["atlas","地图集"],["atmosphere","氛围"],["atomic","原子的"],["atrocity","暴行"],["attach","使附属"],["attain","获得"],["attendant","服务员"],["attorney","律师"],["auction","拍卖"],["audit","查账"],["augment","增加"],["authentic","真正的"],["authoritative","权威的"],["authorize","授权"],["autobiography","自传"],["automate","使自动化"],["autonomous","自治的"],["auxiliary","辅助的"],["avail","有助于"],["avert","避免"],["aviation","航空"],["awaken","唤醒"],["awe","敬畏"],["awkward","笨拙的"],["axis","轴线"],["bachelor","学士"],["badge","徽章"],["baffle","使困惑"],["bait","诱饵"],["bald","秃头的"],["ballet","芭蕾"],["balloon","气球"],["ban","禁令"],["bankrupt","破产的"],["banner","横幅"],["banquet","宴会"],["bar","棒"],["barbecue","烧烤"],["bare","赤裸的"],["bargain","讨价还价"],["barren","贫瘠的"],["barrier","屏障"],["batch","一批"],["batter","连续猛击"],["bead","珠子"],["beam","梁"],["bearing","举止"],["beforehand","事先"],["behalf","利益"],["beloved","心爱的"],["beneficial","有益的"],["beneficiary","受益人"],["benign","良性的"],["besiege","围困"],["bestow","赠予"],["betray","背叛"],["beverage","饮料"],["bewilder","使迷惑"],["bias","使有偏见"],["bibliography","书目"],["bilateral","双边的"],["biography","传记"],["bizarre","奇异的"],["blackmail","勒索"],["blaze","火焰"],["bleach","漂白"],["bleak","荒凉的"],["bless","祝福"],["blink","眨眼"],["blunt","钝的"],["blur","模糊"],["blush","脸红"],["boast","自夸"],["bold","醒目的"],["bonus","奖金"],["boom","隆隆声"],["boost","提高"],["booth","小隔间"],["bound","界限"],["bowel","肠"],["boycott","抵制"],["brace","支撑"],["bracket","括号"],["brake","刹车"],["brand","烙印"],["breach","违反"],["breakdown","故障"],["breakthrough","突破"],["breed","繁殖"],["bribe","贿赂"],["briefcase","公文包"],["brink","边缘"],["brisk","轻快的"],["bronze","青铜"],["brood","沉思"],["browse","浏览"],["bruise","瘀伤"],["brutal","残忍的"],["buckle","皮带扣"],["budget","预算"],["buffer","缓冲"],["bug","虫子"],["bulb","灯泡"],["bulk","体积"],["bully","欺凌"],["bump","肿块"],["bunch","串"],["bureaucracy","官僚机构"],["burial","埋葬"],["bust","半身像"]]},{"id":"ket","name":"KET单词","words":[["again","再一次"],["afternoon","下午"],["always","总是"],["animal","动物"],["answer","答案"],["apple","苹果"],["arm","胳膊"],["ask","问"],["baby","婴儿"],["bag","包"],["ball","球"],["banana","香蕉"],["bath","洗澡"],["beach","海滩"],["beautiful","美丽的"],["bed","床"],["bedroom","卧室"],["begin","开始"],["behind","在后面"],["between","在……之间"],["bike","自行车"],["bird","鸟"],["birthday","生日"],["black","黑色的"],["blue","蓝色的"],["boat","船"],["body","身体"],["book","书"],["boot","靴子"],["boring","无聊的"],["bottle","瓶子"],["box","盒子"],["boy","男孩"],["bread","面包"],["breakfast","早餐"],["brother","哥哥"],["brown","棕色的"],["bus","公交车"],["busy","忙的"],["butter","黄油"],["buy","购买"],["cafe","咖啡馆"],["cake","蛋糕"],["camera","照相机"],["capital","首都"],["car","小汽车"],["card","卡片"],["careful","小心的"],["carry","提"],["cat","猫"],["catch","接住"],["chair","椅子"],["cheap","便宜的"],["cheese","奶酪"],["chicken","鸡肉"],["child","儿童"],["chocolate","巧克力"],["cinema","电影院"],["city","城市"],["class","课"],["classroom","教室"],["clean","清洁的"],["clever","聪明的"],["clock","钟"],["close","近的"],["clothes","衣服"],["cloudy","多云的"],["coat","大衣"],["coffee","咖啡"],["cold","寒冷的"],["colour","颜色"],["come","来"],["computer","计算机"],["cook","做饭"],["cool","凉爽的"],["country","乡村"],["course","一道菜"],["cousin","堂兄弟姐妹"],["cow","母牛"],["cross","穿过"],["cup","杯"],["dad","爸爸"],["dance","跳舞"],["dangerous","危险的"],["dark","深色的"],["daughter","女儿"],["day","白天"],["dear","亲爱的"],["desk","书桌"],["different","不一样的"],["difficult","难的"],["dinner","正餐"],["dirty","脏的"],["do","做"],["doctor","医生"],["dog","狗"],["doll","玩偶"],["door","门"],["down","向下"],["draw","画画"],["dress","连衣裙"],["drink","饮料"],["drive","驾驶"],["driver","司机"],["dry","干的"],["ear","耳朵"],["early","早的"],["easy","容易的"],["eat","吃"],["egg","蛋"],["eight","八"],["elephant","大象"],["email","电子邮件"],["end","尽头"],["english","英语"],["enjoy","喜爱"],["evening","傍晚"],["every","每个"],["example","例子"],["excited","兴奋的"],["excuse","打扰一下"],["expensive","贵的"],["eye","眼睛"],["face","面孔"],["family","家人"],["famous","有名的"],["farm","农场"],["fast","快的"],["father","爸爸"],["favourite","最喜爱的"],["feel","觉得"],["fifteen","十五"],["film","电影"],["find","找到"],["fine","晴朗的"],["finish","做完"],["fish","鱼"],["five","五"],["flat","公寓"],["floor","地板"],["flower","花朵"],["fly","飞行"],["food","食品"],["football","足球"],["forget","忘记"],["four","四"],["friend","朋友"],["from","来自"],["fruit","水果"],["fun","乐趣"],["funny","好笑的"],["game","比赛"],["garden","庭园"],["get","获得"],["girl","女孩"],["give","给"],["glass","玻璃杯"],["go","去"],["good","令人满意的"],["goodbye","再见"],["grandfather","祖父"],["grandmother","祖母"],["great","美妙的"],["green","绿色的"],["grey","灰色的"],["group","群"],["guitar","吉他"],["hair","头发"],["half","一半"],["hand","手"],["happy","快乐的"],["hat","帽子"],["have","有"],["head","头部"],["healthy","健康的"],["hear","听到"],["hello","喂"],["help","帮忙"],["here","这里"],["hi","嗨"],["holiday","假期"],["home","在家"],["homework","家庭作业"],["horse","马"],["hospital","医院"],["hot","炎热的"],["hotel","旅馆"],["hour","小时"],["house","住宅"],["how","怎样"],["hungry","饿的"],["ice","冰淇淋"],["idea","主意"],["important","重要的"],["in","在里面"],["interesting","有意思的"],["internet","因特网"],["jacket","夹克"],["jeans","牛仔裤"],["job","职业"],["juice","果汁"],["jump","跳跃"],["key","钥匙"],["kitchen","厨房"],["know","认识"],["lake","湖泊"],["lamp","台灯"],["language","语言"],["late","迟的"],["laugh","大笑"],["learn","学会"],["leave","离开"],["left","左边的"],["leg","腿"],["lesson","一堂课"],["letter","字母"],["like","喜欢"],["listen","听"],["little","幼小的"],["live","住"],["long","长时间的"],["look","瞧"],["love","热爱"],["lunch","午餐"],["make","使"],["man","成年男子"],["many","许多"],["map","地图"],["market","集市"],["meat","肉类"],["message","短信"],["milk","奶"],["minute","分钟"],["miss","想念"],["mobile","手机"],["mom","妈妈"],["money","金钱"],["month","月份"],["morning","上午"],["mother","妈妈"],["mouse","鼠标"],["mouth","嘴"],["movie","影片"],["mr","先生"],["mrs","夫人"],["much","大量"],["mum","妈妈"],["museum","博物馆"],["music","乐曲"],["name","姓名"],["near","近"],["need","需要"],["never","从不"],["new","新的"],["newspaper","报纸"],["next","紧接着的"],["nice","令人愉快的"],["night","夜晚"],["nine","九"],["no","不"],["noise","响声"],["nose","鼻"],["not","不是"],["now","现在"],["number","号码"],["nurse","护士"],["o","点钟"],["of","……的"],["off","离开"],["office","办公室"],["often","常常"],["old","年老的"],["on","在……上"],["once","一次"],["one","一"],["onion","洋葱"],["only","唯一的"],["open","开着的"],["or","或者"],["orange","柑橘"],["other","另外的"],["our","我们的"],["out","在外面"],["outside","在户外"],["page","页"],["paint","用颜料画"],["paper","纸张"],["parent","父或母"],["park","停车场"],["party","聚会"],["passport","护照"],["pen","圆珠笔"],["pencil","铅笔"],["people","人"],["pepper","胡椒粉"],["person","个人"],["pet","宠物"],["phone","打电话"],["photo","相片"],["piano","钢琴"],["picture","图画"],["piece","片"],["pizza","比萨饼"],["place","地点"],["plane","飞机"],["plant","栽种"],["play","演奏"],["player","播放机"],["please","请"],["poor","可怜的"],["post","邮寄"],["potato","土豆"],["pretty","漂亮的"],["problem","难题"],["put","放置"],["quarter","一刻钟"],["question","疑问"],["quick","迅速的"],["quiet","轻声的"],["radio","无线电"],["rain","下雨"],["read","阅读"],["ready","准备好"],["red","红色的"],["remember","记起"],["restaurant","餐馆"],["rice","大米"],["right","正确的"],["river","江"],["road","公路"],["room","室"],["ruler","尺子"],["run","奔跑"],["sad","悲哀的"],["salad","色拉"],["salt","食盐"],["same","相同的"],["sandwich","三明治"],["saturday","星期六"],["say","说"],["school","上学"],["sea","海"],["second","秒"],["see","看见"],["sell","出售"],["send","发送"],["seven","七"],["she","她"],["shirt","衬衫"],["shoe","鞋"],["shop","去购物"],["short","矮的"],["show","演出"],["shower","淋浴"],["sick","有病的"],["sing","唱歌"],["sister","姐姐"],["sit","坐下"],["six","六"],["skirt","裙子"],["sleep","睡觉"],["slow","慢的"],["small","不大的"],["smile","微笑"],["snake","蛇"],["snow","下雪"],["so","如此"],["soccer","足球"],["sock","短袜"],["some","一些"],["someone","有人"],["something","某事物"],["sometimes","有时"],["son","儿子"],["song","歌"],["sorry","对不起"],["soup","汤"],["speak","讲"],["spell","拼写"],["sport","体育运动"],["start","开始"],["station","火车站"],["stay","停留"],["stop","停下"],["street","大街"],["student","学员"],["study","学习"],["subject","学科"],["sugar","糖"],["summer","夏季"],["sun","太阳"],["sunday","星期日"],["supermarket","超市"],["sweater","毛衣"],["swim","游泳"],["table","餐桌"],["take","携带"],["talk","讲话"],["tall","高的"],["taxi","出租车"],["tea","茶水"],["teacher","教师"],["team","队"],["telephone","电话机"],["television","电视机"],["tennis","网球"],["test","测验"],["than","比"],["thank","感谢"],["that","那"],["the","这"],["their","他们的"],["them","他们"],["then","那时"],["there","在那里"],["these","这些"],["they","他们"],["thing","物品"],["think","认为"],["thirsty","渴的"],["this","这个"],["those","那些"],["three","三"],["ticket","入场券"],["time","时间"],["tired","疲倦的"],["today","今日"],["together","在一起"],["toilet","厕所"],["tomato","番茄"],["tomorrow","明日"],["tonight","今夜"],["too","太"],["tooth","牙齿"],["town","市镇"],["toy","玩具"],["train","火车"],["travel","出行"],["tree","树木"],["trousers","裤子"],["try","试图"],["tshirt","T恤"],["tuesday","星期二"],["turn","转动"],["tv","电视"],["twelve","十二"],["two","二"],["under","在……下面"],["understand","懂"],["university","大学"],["until","直到"],["up","向上"],["us","我们"],["use","使用"],["usually","通常"],["vegetable","蔬菜"],["very","很"],["video","录像"],["village","村庄"],["visit","拜访"],["wait","等候"],["waiter","男服务员"],["wake","醒来"],["walk","步行"],["wall","墙壁"],["want","想要"],["warm","暖和的"],["wash","洗"],["watch","观看"],["water","水"],["way","方法"],["we","我们"],["wear","穿"],["weather","天气"],["website","网站"],["wednesday","星期三"],["week","周"],["weekend","周末"],["well","健康的"],["west","西"],["wet","湿的"],["what","什么"],["when","何时"],["where","在哪里"],["which","哪一个"],["white","白色的"],["who","谁"],["why","为什么"],["window","窗子"],["windy","有风的"],["winter","冬季"],["with","和……一起"],["woman","成年女子"],["wonderful","令人惊叹的"],["word","词"],["work","工作"],["worker","工人"],["world","世界"],["write","写"],["wrong","不对的"],["year","年"],["yellow","黄色的"],["yes","是"],["yesterday","昨天"],["you","你"],["young","幼小的"],["your","你的"],["yours","你的"],["zoo","动物园"]]},{"id":"pet","name":"PET单词","words":[["ability","才能"],["accept","接受"],["accident","意外事故"],["according","按照"],["account","账号"],["accurate","正确无误的"],["achieve","成功地完成"],["act","行为"],["actually","事实上"],["add","增加"],["admire","佩服"],["admit","承认"],["adult","成人"],["advantage","有利条件"],["adventure","奇遇"],["advertisement","广告"],["advice","忠告"],["afford","买得起"],["afraid","害怕的"],["afterwards","以后"],["against","倚"],["age","时代"],["agent","经纪人"],["agree","赞成"],["ahead","提前"],["aim","目的"],["airline","航空公司"],["airport","航空站"],["alarm","闹钟"],["album","集邮簿"],["alive","活着的"],["allow","允许"],["almost","将近"],["alone","孤独的"],["along","向前"],["aloud","出声地"],["already","早已"],["also","此外"],["although","尽管"],["altogether","全部"],["always","一直"],["amazing","了不起的"],["ambition","雄心"],["amount","数量"],["amuse","使消遣"],["ancient","古老的"],["anger","愤怒"],["announce","公布"],["annoy","打扰"],["annual","一年一度的"],["another","又一"],["answer","答复"],["anxious","渴望的"],["any","若干"],["anybody","任何人"],["anyone","任何人"],["anything","任何东西"],["anyway","反正"],["anywhere","任何地方"],["apart","成碎片"],["apologize","赔礼"],["appear","出现"],["appearance","出现"],["apple","苹果"],["application","申请书"],["apply","适用"],["appointment","任命"],["appreciate","赏识"],["approach","走近"],["appropriate","合适的"],["approve","同意"],["architect","建筑师"],["area","区域"],["argue","争辩"],["argument","争论"],["arm","武装"],["army","陆军"],["around","四周"],["arrange","筹备"],["arrangement","安排"],["arrival","到来"],["arrive","来到"],["art","美术"],["article","条款"],["artificial","人工的"],["artist","画家"],["as","当……时"],["ashamed","羞耻的"],["ask","要求"],["asleep","睡着"],["aspect","方面"],["assistant","助理"],["association","社团"],["assume","认为"],["atmosphere","大气层"],["attach","固定"],["attack","进攻"],["attempt","努力"],["attend","出席"],["attention","关心"],["attitude","态度"],["attract","吸引"],["audience","观众"],["author","作家"],["available","可获得的"],["average","平均数"],["avoid","避免"],["awake","醒着"],["award","奖品"],["aware","意识到"],["away","不在"],["awful","极讨厌的"],["background","出身背景"],["badly","严重地"],["bag","袋"],["bake","烘烤"],["balance","平衡"],["ball","舞会"],["band","乐队"],["bank","银行"],["bar","酒吧间"],["bargain","减价品"],["base","以……为基础"],["basic","基础的"],["bath","浴缸"],["battle","战役"],["beach","沙滩"],["bean","豆"],["bear","熊"],["beat","击败"],["beautiful","悦目的"],["because","因为"],["become","成为"],["bed","花坛"],["bedroom","寝室"],["bee","蜜蜂"],["beef","牛肉"],["beer","啤酒"],["before","以前"],["begin","着手"],["behave","表现"],["behaviour","举止"],["behind","落后于"],["believe","相信"],["bell","铃"],["belong","属于"],["below","在……以下"],["belt","腰带"],["bench","长凳"],["bend","弯曲"],["benefit","好处"],["beside","在旁边"],["besides","而且"],["best","最出色的"],["better","更好的"],["between","介于……之间"],["beyond","超出"],["bicycle","自行车"],["big","重大的"],["bill","账单"],["bin","垃圾箱"],["biology","生物学"],["bird","禽"],["birth","出生"],["biscuit","饼干"],["bit","一点儿"],["bite","咬"],["bitter","苦的"],["black","黑人的"],["blame","责怪"],["blank","空白的"],["blanket","毯子"],["blind","瞎的"],["block","大楼"],["blood","血液"],["blouse","女衬衫"],["blow","吹"],["board","董事会"],["boat","小船"],["body","尸体"],["boil","沸腾"],["bomb","轰炸"],["bone","骨头"],["book","本子"],["boot","长筒靴"],["border","边界"],["bored","厌倦的"],["borrow","借入"],["boss","老板"],["both","两者都"],["bother","使烦恼"],["bottle","一瓶"],["bottom","底部"],["bowl","碗"],["box","箱"],["boy","男青年"],["brain","脑"],["branch","树枝"],["brave","勇敢的"],["bread","面包片"],["break","中断"],["breakfast","早餐会"],["breath","呼吸"],["breathe","呼吸空气"],["bridge","桥梁"],["brief","短暂的"],["bright","聪明的"],["brilliant","才华横溢的"],["bring","带来"],["broad","宽阔的"],["brochure","小册子"],["brother","弟弟"],["brown","褐色的"],["brush","刷子"],["build","建筑"],["building","楼房"],["bullet","子弹"],["bunch","束"],["burn","燃烧"],["burst","爆裂"],["bury","埋葬"],["bus","大客车"],["business","商业"],["busy","占线"],["butter","涂黄油"],["button","纽扣"],["buy","买下"],["cabinet","柜橱"],["cable","电缆"],["cafe","小餐馆"],["cage","笼子"],["cake","饼"],["calculate","核算"],["call","呼唤"],["calm","镇静的"],["camera","摄像机"],["camp","宿营"],["campaign","宣传活动"],["can","罐头"],["cancel","取消"],["cancer","癌"],["candidate","应试者"],["candle","蜡烛"],["cap","帽子"],["capable","有能力的"],["capital","大写字母"],["captain","船长"],["car","车厢"],["card","纸牌"],["care","小心"],["career","生涯"],["careful","细致的"],["careless","粗心的"],["carpet","地毯"],["carry","运送"],["case","情况"],["cash","现金"],["castle","城堡"],["casual","非正式的"],["cat","猫科动物"],["catch","赶上"],["cause","原因"],["celebrate","庆祝"],["celebrity","名人"],["cell","细胞"],["cent","分"],["central","中心的"],["centre","中心"],["century","百年"],["ceremony","典礼"],["certain","肯定的"],["certainly","当然"],["certificate","证明"],["chain","链子"],["chair","主席"],["chairman","委员长"],["challenge","挑战"],["champion","冠军"],["chance","可能性"],["change","零钱"],["channel","频道"],["chapter","章"],["character","人物"],["charge","主管"],["charity","慈善"],["chart","海图"],["cheap","劣质的"],["cheat","作弊"],["check","支票"],["cheek","面颊"],["cheer","欢呼"],["cheese","干酪"],["chef","厨师"],["chemical","化学制品"],["chemistry","化学"],["cheque","支票"],["chest","胸部"],["chicken","小鸡"],["chief","首领"],["child","子女"],["childhood","童年"],["chip","芯片"],["chocolate","巧克力糖"],["choice","选择权"],["choose","选择"],["church","教堂"],["cigarette","香烟"],["cinema","电影业"],["circle","圈子"],["circumstance","环境"],["citizen","公民"],["city","都市"],["claim","索赔"],["clap","拍手"],["class","阶级"],["classic","经典作品"],["classroom","课堂"],["clean","纯洁的"],["clear","清晰的"],["clerk","职员"],["clever","灵巧的"],["click","点击"],["client","客户"],["climate","气候"],["climb","攀登"],["clinic","诊所"],["clock","仪表"],["close","亲密的"],["cloth","布"],["clothes","衣物"],["cloud","云朵"],["club","夜总会"],["clue","线索"],["coach","长途汽车"],["coal","煤"],["coast","海滨"],["coat","涂层"],["code","密码"],["coffee","咖啡豆"],["coin","硬币"],["cold","感冒"],["collapse","倒塌"],["colleague","同僚"],["collect","收集"],["collection","收藏品"],["college","大学"],["colour","颜料"],["column","柱"],["combination","结合"],["combine","联合"],["come","发生"],["comfort","安慰"],["comfortable","使人舒服的"],["command","指挥"],["comment","议论"],["commercial","商业的"],["commission","佣金"],["commit","犯"],["committee","委员会"],["common","常见的"],["communicate","沟通"],["communication","通信"],["community","社会"],["company","陪伴"],["compare","比较"],["compete","比赛"],["competition","竞争"],["complain","投诉"],["complete","完整的"],["complex","复杂的"],["complicate","使复杂化"],["computer","电子计算机"],["concentrate","聚精会神"],["concept","观念"],["concern","担忧"],["concert","音乐会"],["conclude","结束"],["condition","状况"],["conduct","实施"],["conference","会议"],["confidence","自信心"],["confident","有把握的"],["confirm","证实"],["conflict","矛盾"],["confuse","混淆"],["congratulations","祝贺"],["connect","联结"],["conscious","意识到的"],["consider","认为"],["consist","在于"],["constant","持续的"],["construct","修建"],["contact","接触"],["contain","含有"],["content","满足的"],["contest","竞赛"],["context","上下文"],["continent","大洲"],["continue","继续做"],["contract","契约"],["contrast","对照"],["contribute","捐献"],["control","限制"],["convenient","便利的"],["conversation","交谈"],["convince","使确信"],["cook","烹调"],["cool","冷静的"],["cope","处理"],["copy","复印件"],["core","果核"],["corner","角落"],["correct","纠正"],["cost","成本"],["cottage","村舍"],["cotton","棉花"],["cough","咳嗽"],["could","可以"],["count","重要"],["country","国土"],["countryside","农村"],["couple","夫妇"],["courage","勇气"],["course","过程"],["court","球场"],["cousin","表亲"],["cover","封面"],["cow","牛"],["crack","破裂"],["craft","手艺"],["crash","撞车"],["crazy","疯狂的"],["cream","奶油"],["create","创造"],["creature","动物"],["credit","贷款"],["crew","机组人员"],["crime","罪"],["criminal","罪犯"],["crisis","危急关头"],["critic","评论家"],["critical","批评的"],["crop","作物"],["cross","十字架"],["crowd","人群"],["crown","王冠"],["crucial","决定性的"],["cruel","残暴的"],["cry","叫喊"],["cultural","文化上的"],["culture","文明"],["cup","奖杯"],["cupboard","橱柜"],["cure","疗法"],["curious","求知欲强的"],["current","水流"],["curriculum","课程"],["curtain","窗帘"],["curve","曲线"],["custom","风俗"],["customer","消费者"],["cut","削减"],["cute","可爱的"],["cycle","骑自行车"]]},{"id":"fce","name":"FCE单词","words":[["abandon","抛弃"],["ability","本领"],["abroad","到国外"],["absence","不在"],["absolute","十足的"],["absorb","吞并"],["abstract","理论上的"],["abundant","大量的"],["abuse","辱骂"],["academic","教学的"],["accelerate","加快"],["accent","重音"],["accept","认可"],["access","使用权"],["accident","机遇"],["accommodation","膳宿"],["accompany","陪同"],["accomplish","实现"],["according","据……所说"],["account","账目"],["accurate","精准的"],["accuse","谴责"],["accustomed","适应的"],["achieve","取得"],["acknowledge","告知收到"],["acquaintance","相识"],["acquire","学到"],["act","扮演"],["action","诉讼"],["active","起作用的"],["activity","活跃"],["actual","真实的"],["adapt","适应"],["add","补充说"],["addition","加法"],["address","演说"],["adequate","充足的"],["adjust","调节"],["administration","行政部门"],["admire","赞赏"],["admission","承认"],["admit","容许"],["adopt","采取"],["advance","进步"],["advantage","好处"],["adventure","冒险精神"],["advertise","做广告"],["advice","意见"],["affair","事件"],["affect","影响"],["afford","提供"],["afraid","担心"],["after","尽管"],["afterwards","此后"],["again","此外"],["against","反对"],["age","老化"],["agency","经销处"],["agenda","议事日程"],["agent","间谍"],["aggressive","有进取心的"],["ago","以前"],["agree","应允"],["agreement","同意"],["agriculture","农业"],["ahead","领先"],["aid","资助"],["aim","瞄准"],["air","神态"],["aircraft","航空器"],["airline","定期航线"],["airport","飞机场"],["alarm","惊慌"],["album","签名簿"],["alcohol","含酒精饮料"],["alert","警惕的"],["alike","同样地"],["alive","继续存在"],["alliance","联合"],["allow","给予"],["allowance","零用钱"],["ally","盟友"],["almost","简直"],["alone","仅仅"],["along","一起"],["alongside","与……一起"],["aloud","高声地"],["alphabet","字母系统"],["already","先前"],["also","而且"],["alter","改动"],["alternative","二中择一"],["although","然而"],["altogether","总而言之"],["always","永远"],["amateur","业余的"],["amaze","使诧异"],["ambassador","使节"],["ambition","志向"],["ambulance","急救车"],["amend","改进"],["amid","在其间"],["amount","合计"],["amuse","逗……笑"],["analyse","细察"],["analysis","分析结果"],["ancestor","先人"],["anchor","主播"],["ancient","过时的"],["anger","使发怒"],["angle","观点"],["angry","狂怒的"],["ankle","踝"],["anniversary","周年纪念日"],["announce","声明"],["annoy","惹恼"],["annual","年度的"],["anonymous","名字不详的"],["another","另一的"],["answer","解决办法"],["anxiety","担心"],["anxious","令人焦虑的"],["any","任一的"],["anybody","无论谁"],["anyhow","随随便便地"],["anyone","无论是谁"],["anything","重要的东西"],["anyway","尽管如此"],["anywhere","随便什么地方"],["apart","除外"],["apartment","套间"],["apologize","谢罪"],["apology","认错"],["apparent","显而易见的"],["appeal","感染力"],["appear","出版"],["appearance","外表"],["appendix","附加物"],["appetite","欲望"],["applaud","赞许"],["apple","苹果树"],["appliance","电器"],["applicable","合适的"],["applicant","申请人"],["application","勤奋"],["apply","涂"],["appoint","委任"],["appointment","预约"],["appreciate","领会"],["approach","着手处理"],["appropriate","拨出"],["approval","赞成"],["approve","批准"],["approximate","大概的"]]},{"id":"ielts","name":"雅思核心","words":[["academic","学术性的"],["access","接近"],["account","解释"],["accurate","精确无误"],["achieve","完成"],["adapt","改造"],["administration","经营"],["affect","假装"],["alternative","另类的"],["amendment","修正案"],["analysis","分解"],["approach","通路"],["area","范围"],["aspect","层面"],["assess","核定"],["assistance","帮助"],["assume","假装"],["authority","权力"],["available","有效的"],["aware","察觉到的"],["benefit","津贴"],["category","种类"],["chapter","回"],["commission","授权"],["community","共同体"],["complex","综合体"],["compute","用计算机计算"],["concept","思想"],["conclude","推断出"],["conduct","行为"],["consequence","结果"],["considerable","值得考虑的"],["consist","组成"],["constitute","设立"],["context","背景"],["contract","收缩"],["contribute","促成"],["convention","大会"],["coordinate","坐标"],["core","要点"],["create","创作"],["credit","学分"],["culture","养殖"],["data","数据"],["debate","讨论"],["decade","十年期"],["define","阐明"],["derive","得到"],["design","图案"],["distinct","有区别的"],["element","要素"],["emerge","浮现"],["emphasis","重点"],["ensure","担保"],["environment","周围"],["establish","确立"],["estimate","估价"],["evaluate","评估"],["evidence","根据"],["factor","因素"],["feature","面貌"],["final","决赛"],["focus","焦点"],["function","宴会"],["fund","资金"],["goal","球门"],["grant","拨款"],["hence","因此"],["hypothesis","假说"],["identify","识别"],["impact","冲击"],["implement","工具"],["imply","暗示"],["indicate","表明"],["individual","个人"],["initial","最初的"],["institute","协会"],["interact","相互作用"],["interpret","口译"],["involve","包含"],["issue","期号"],["item","一条"],["journal","期刊"],["labour","劳动"],["legal","法律的"],["legislate","立法"],["major","主修"],["method","办法"],["minor","较小的"],["negate","否定"],["obtain","获得"],["occur","被发现"],["percent","百分数"],["period","句号"],["policy","保险单"],["positive","明确的"],["potential","潜在的"],["previous","先前的"],["primary","主要的"],["principle","原理"],["procedure","程序"],["process","加工"],["require","规定"],["research","调查"],["resource","资源"],["respond","回答"],["role","作用"],["section","部门"],["sector","部门"],["significant","有意义的"],["similar","类似的"],["source","消息来源"],["specific","特定的"],["structure","结构"],["theory","学说"],["transfer","转移"],["variable","变量"]]},{"id":"toefl","name":"托福核心","words":[["abstract","抽象派的"],["accurate","正确的"],["adjacent","毗连的"],["advocate","拥护者"],["allocate","拨给"],["alter","变更"],["annual","年生的"],["apparent","貌似的"],["approximate","接近"],["arbitrary","武断的"],["assess","征收"],["assume","设想"],["attain","到达"],["attribute","特质"],["capacity","才能"],["cease","停止"],["coherent","连贯的"],["coincide","同时发生"],["collapse","崩溃"],["colleague","同行"],["compile","编纂"],["complement","补充物"],["component","元件"],["comprehensive","综合性的"],["comprise","构成"],["conceive","构想"],["concurrent","并存的"],["confine","界限"],["conform","符合"],["consensus","一致看法"],["consistent","连续的"],["constitute","组成"],["constrain","强迫"],["construct","构想"],["consult","查阅"],["consume","吃"],["contaminate","污染"],["contemplate","深思"],["contradict","相矛盾"],["contribute","投稿"],["convene","召集"],["converse","交谈"],["convert","皈依者"],["convey","运送"],["coordinate","使协调"],["correlate","相关"],["correspond","相当于"],["criteria","标准"],["crucial","严酷的"],["decade","十年"],["decline","谢绝"],["deduce","演绎"],["demonstrate","示威"],["deny","拒绝给予"],["depict","描画"],["derive","起源"],["detect","探测"],["deviate","偏离"],["devise","设计"],["differentiate","区分"],["diminish","降低"],["discrete","分离的"],["displace","取代"],["dispose","布置"],["distinct","清晰的"],["distort","歪曲"],["diverse","不同的"],["domain","领土"],["dramatic","引人注目的"],["duration","期间"],["dynamic","动力的"],["elaborate","精心制作"],["elicit","引出"],["eliminate","淘汰"],["emerge","暴露"],["empirical","以经验为依据的"],["enable","使成为可能"],["encompass","包围"],["enhance","提高"],["enormous","极大的"],["ensure","保证得到"],["entity","实体"],["equate","等同"],["erode","侵蚀"],["establish","使立足"],["evaluate","赋值"],["evolve","逐渐形成"],["exceed","超出"],["exclude","拒绝接纳"],["exhibit","展品"],["expand","详述"],["expert","内行"],["explicit","直言的"],["exploit","英勇事迹"],["expose","揭穿"],["external","外观"],["extract","摘录"],["facilitate","促进"],["factor","系数"],["feature","以……为特色"],["finite","有限的"],["fluctuate","波动"],["focus","集中注意力"],["format","版式"],["formula","配方"],["framework","框架"],["function","运转"],["fundamental","基本的"],["furthermore","而且"],["generate","产生"],["generation","产生"],["globe","地球"],["grade","等级"],["grant","同意"],["hence","从此"],["hypothesis","假定"],["identical","同一的"],["identify","确定"],["ideology","意识形态"],["ignorant","无知的"],["illustrate","说明"],["immigrate","移入"],["impact","撞击"],["implement","实施"],["implicate","牵连"],["implicit","含蓄的"],["imply","意味着"],["impose","把……强加于"],["incentive","刺激"],["incidence","发生率"],["incline","斜坡"],["income","收入"],["incorporate","包含"],["index","指数"],["indicate","象征"],["individual","独特的"],["induce","劝诱"],["inevitable","不可避免的"],["infer","推论"],["infrastructure","基础设施"],["inherent","固有的"],["inhibit","抑制"],["initial","首字母"],["initiate","创始"],["innovate","创新"],["input","投入"],["insert","插入"],["insight","洞察力"],["inspect","检查"],["instance","实例"],["institute","开始"],["instruct","指示"],["integral","完整的"],["integrate","使结合"],["integrity","正直"],["intelligence","智力"],["intense","强烈的"],["interact","互动"],["intermediate","中间的"],["internal","内部的"],["interpret","解释"],["interval","间隔"],["intervene","干涉"],["intrinsic","内在的"],["invest","投资"],["investigate","调查"],["invoke","援引"],["involve","使参与"],["isolate","隔离"],["issue","发行"],["item","项目"],["journal","日志"],["justify","证明正当"]]},{"id":"kaoyan","name":"考研词汇","words":[["abandon","离弃"],["abide","遵守"],["abnormal","反常的"],["abolish","取消"],["abound","富于"],["abrupt","唐突的"],["absolute","专制的"],["absorb","理解"],["abstract","提取"],["absurd","荒唐的"],["abundance","丰富"],["abuse","虐待"],["academic","学者"],["accelerate","催促"],["access","存取"],["accessory","从犯"],["accident","事故"],["acclaim","称誉"],["accommodate","顺应"],["accompany","附有"],["accomplish","走完"],["accord","协议"],["account","认为"],["accumulate","堆积"],["accurate","无误的"],["accuse","非难"],["accustom","使适应"],["achieve","获得成功"],["acid","酸的"],["acknowledge","感谢"],["acquaint","介绍"],["acquire","取得"],["acquisition","获得"],["activate","使活动"],["acute","剧烈的"],["adapt","适合"],["addict","有瘾的人"],["adhere","坚持"],["adjacent","接近的"],["adjoin","邻接"],["adjust","适应于"],["administer","执行"],["admire","欣赏"],["admission","入场费"],["admit","招收"],["adolescent","青春期的"],["adopt","选定"],["adore","爱慕"],["adorn","装饰"],["advance","提出"],["advantage","利益"],["adventure","投机"],["adverse","敌对的"],["advertise","通知"],["advice","指点"],["advisable","可取的"],["advocate","律师"],["aerial","天线"],["aesthetic","美学的"],["affair","事情"],["affect","侵害"],["affection","感情"],["affiliate","分会"],["affirm","证实"],["afflict","折磨"],["afford","抽得出时间"],["afraid","恐惧的"],["afterward","后来"],["against","碰"],["age","时代"],["agency","作用"],["agenda","日常工作事项"],["agent","动因"],["aggravate","使恶化"],["aggregate","总计"],["aggressive","敢作敢为的"],["agitate","搅动"],["agony","痛苦"],["agreeable","同意的"],["agreement","契约"],["agriculture","农艺"],["aid","救护"],["aim","针对"],["air","天空"],["aisle","通道"],["alarm","担心"],["album","粘贴簿"],["alcohol","乙醇"],["alert","留神的"],["alien","相异的"],["alienate","使疏远"],["align","使成一线"],["alike","相似的"],["alive","热闹的"],["all","全部的"],["allege","指控"],["alleviate","缓和"],["alliance","结盟"],["allocate","划拨"],["allot","分配"],["allow","承认"],["allowance","限额"],["alloy","合金钢"],["ally","使结盟"],["almost","将近"],["alone","独一无二的"],["along","顺着"],["alongside","横靠"],["aloud","响亮地"],["alphabet","入门"],["already","刚才"],["also","同样"],["alter","修改"],["alternate","间隔的"],["alternative","抉择"],["although","即使"],["altitude","高处"],["altogether","完全"],["altruism","利他主义"],["always","总是"],["amass","积聚"],["amateur","爱好者"],["amaze","使惊异"],["ambassador","大使"],["amber","琥珀"],["ambiguous","含糊的"],["ambition","热望"],["ambitious","劲头十足的"],["amend","修订"],["amiable","亲切的"],["amid","在其中"],["ammunition","弹药"],["among","在……之中"],["amount","等于"],["ample","宽敞的"],["amplify","增强"],["amuse","消磨时间"],["analogy","类似"],["analyse","分析研究"],["analysis","分解作用"],["analytic","分析的"],["anarchy","无政府状态"],["anatomy","解剖学"],["ancestor","原型"],["anchor","给……以安全感"],["ancient","年老的"],["anecdote","趣闻"],["angel","善良的人"],["anger","怒气"],["angle","钓鱼"],["angry","愤怒的"],["anguish","剧痛"],["animal","野兽"],["animate","有生命的"],["ankle","踝关节"],["annex","兼并"],["anniversary","周年的"],["announce","预告"],["annoy","使生气"],["annual","年刊"],["anonymous","无名的"],["another","不同的"],["answer","回击"],["ant","蚂蚁"],["antenna","触角"],["anticipate","早于"],["antique","古时的"],["anxiety","焦急"],["anxious","急切的"],["any","一点"],["anybody","重要人物"],["anyhow","至少"],["anyone","任何人"],["anything","任何事情"],["anyway","总之"],["anywhere","在任何地方"],["apart","有距离"],["apartment","房间"],["apologize","道歉"],["apology","辩解"],["appal","使惊骇"],["apparatus","机构"],["apparent","明白的"],["appeal","恳求"],["appear","到来"],["appearance","露面"],["appease","平息"],["appendix","附录"],["appetite","嗜好"],["applaud","向……喝彩"],["apple","苹果"],["appliance","装置"],["applicable","可实施的"],["applicant","请求者"],["application","施用"],["apply","致力于"],["appoint","装设"],["appointment","职位"],["appraisal","估计"],["appreciate","增值"],["apprehend","理解"],["approach","近似"],["appropriate","占用"],["approval","公认"],["approve","通过"],["approximate","近似值"],["apt","恰当的"],["aptitude","天资"],["arbitrary","随心所欲的"],["arc","拱形"],["arch","拱"],["architect","缔造者"],["architecture","建筑风格"],["archive","案卷"],["arctic","北极的"],["ardent","热烈的"],["arduous","艰巨的"],["area","地面"],["arena","活动场所"],["argue","主张"],["argument","理由"],["arise","起身"],["aristocracy","贵族统治"],["arithmetic","算术运算"],["arm","袖子"],["army","大批"],["around","到处"],["arouse","鼓励"],["arrange","排列"],["array","展示"],["arrest","阻止"],["arrival","到达者"],["arrive","成功"],["arrogant","自大的"],["arrow","箭号"],["art","人文学科"],["artery","干线"],["article","冠词"],["articulate","连接"],["artificial","做作的"],["artillery","炮兵"],["artist","大师"],["artistic","艺术家的"],["as","虽然"],["ascend","攀登"],["ascertain","确定"],["ascribe","认为是"],["ash","灰"],["ashamed","害臊的"],["ashore","上岸"],["aside","留着"],["ask","邀请"],["asleep","静止的"],["aspect","方位"],["aspiration","抱负"],["assassinate","暗杀"],["assault","抨击"],["assemble","装配"],["assembly","装配"],["assert","维护"],["assess","评价"],["asset","宝贵的人"],["assign","布置"],["assimilate","同化"],["assist","促进"],["assistant","副的"],["associate","交往"],["association","联想"],["assorted","各种各样的"],["assume","呈现"],["assumption","就任"],["assurance","自信"],["assure","使确信"],["astonish","使惊讶"],["astound","使震惊"],["astronomy","天体学"],["asylum","收容所"],["at","向"],["athlete","运动选手"],["atlas","图表集"],["atmosphere","空气"],["atom","微粒"],["atomic","原子能的"],["atop","在顶上"],["attach","使喜爱"],["attack","侵袭"],["attain","做到"],["attempt","试图"],["attend","致力于"],["attendant","伴随的"],["attention","立正"],["attentive","注意的"],["attest","证明"],["attitude","姿势"],["attorney","代理人"],["attract","招引"],["attraction","吸引人的事物"],["attractive","妩媚的"],["attribute","归于"],["attrition","消耗"],["auction","竞拍"],["audible","听得见的"],["audience","读者"],["audio","声音的"],["audit","审核"],["auditorium","礼堂"],["augment","增大"],["august","八月"],["aunt","伯母"],["auspicious","吉利的"],["austere","简朴的"],["authentic","可信的"],["author","创始人"],["authoritative","官方的"],["authority","威信"],["authorize","批准"],["autobiography","自传体"],["automatic","不经思索的"],["automobile","汽车"],["autonomous","自主的"],["autonomy","人身自由"],["autumn","成熟期"],["auxiliary","助动词"],["avail","效用"],["available","可买到的"],["avalanche","雪崩"],["avenge","报仇"],["avenue","途径"],["average","平均水平"],["avert","防止"],["aviation","飞机制造"],["avid","热衷的"],["avoid","回避"],["avow","公开承认"],["await","期待"],["awake","醒悟"],["award","奖学金"],["aware","明白的"],["away","去别处"],["awe","畏惧"],["awesome","令人惊叹的"],["awful","非常的"],["awkward","难处理的"],["axis","中心线"],["axle","车轴"]]},{"id":"tem4","name":"专四词汇","words":[["abbreviate","缩写"],["abduct","绑架"],["abide","容忍"],["ablaze","闪耀的"],["ablution","沐浴"],["abode","住所"],["aboriginal","土著的"],["abridge","删节"],["abscond","潜逃"],["absolve","赦免"],["abstain","戒除"],["abstruse","深奥的"],["abyss","深渊"],["accede","同意"],["acclaim","欢呼"],["accolade","赞美"],["accomplice","同谋"],["accord","一致"],["accost","搭讪"],["accredit","委任"],["accrue","积累"],["acerbic","尖酸的"],["acidulous","微酸的"],["acquiesce","默许"],["acrid","辛辣的"],["acrimony","尖刻"],["actuate","驱动"],["acumen","敏锐"],["adage","格言"],["adamant","坚定的"],["addendum","补遗"],["adept","熟练的"],["adherent","追随者"],["adjunct","附属物"],["admonish","告诫"],["adroit","灵巧的"],["adulation","奉承"],["adulterate","掺杂"],["advent","到来"],["adversary","对手"],["adversity","逆境"],["aesthetics","美学"],["affable","和蔼的"],["affinity","密切关系"],["affluence","富裕"],["aftermath","后果"],["aghast","惊骇的"],["agrarian","土地的"],["ailment","小病"],["alacrity","欣然"],["alchemy","炼金术"],["alcove","凹室"],["alias","别名"],["alibi","不在场证明"],["allay","减轻"],["allegiance","忠诚"],["alleviation","缓解"],["allure","诱惑"],["allusion","典故"],["aloof","冷淡的"],["altercation","争吵"],["altruistic","利他的"],["amalgamate","合并"],["amass","收集"],["ambience","气氛"],["ambivalent","矛盾的"],["amble","缓行"],["ambush","埋伏"],["ameliorate","改善"],["amenable","顺从的"],["amenities","便利设施"],["amiability","和蔼"],["amnesty","大赦"],["amorphous","无定形的"],["amortize","摊还"],["analogous","类似的"],["anathema","诅咒"],["anatomy","剖析"],["anecdotal","轶事的"],["animosity","仇恨"],["annals","编年史"],["annexation","兼并"],["annotate","注释"],["annuity","年金"],["anomaly","反常"],["antagonist","对手"],["antedate","早于"],["anthology","选集"],["anthropology","人类学"],["antidote","解毒剂"],["antipathy","反感"],["antiquated","陈旧的"],["antithesis","对立面"],["apathy","冷漠"],["aperture","孔径"],["apex","顶点"],["aphorism","警句"],["aplomb","沉着"],["apocalypse","启示"],["apocryphal","真实性可疑的"],["apogee","最高点"],["apostate","叛教者"],["appall","使胆寒"],["apparition","幽灵"],["appease","安抚"],["appellation","名称"],["append","附加"],["apposite","恰当的"],["appraise","估价"],["apprise","通知"],["approbation","嘉许"],["apropos","恰当的"],["aptitude","才能"],["aquatic","水生的"],["aqueduct","渡槽"],["arbitrary","专横的"],["arbiter","仲裁人"],["arcade","拱廊"],["arcane","神秘的"],["archaic","古体的"],["archetype","原型"],["archipelago","群岛"],["ardor","热情"],["arduous","费力的"],["aria","咏叹调"],["aristocratic","贵族的"],["armada","舰队"],["armistice","停战"],["aroma","香气"],["arraign","传讯"],["array","排列"],["arrears","欠款"],["arsenal","兵工厂"],["artful","狡猾的"],["articulate","清楚地表达"],["artifact","人工制品"],["artifice","诡计"],["artisan","工匠"],["ascetic","苦行的"],["ascribe","归因于"],["aseptic","无菌的"],["askance","斜视"],["asperity","严厉"],["aspersion","诽谤"],["aspire","渴望"],["assail","抨击"],["assay","化验"],["assent","赞成"],["assertive","坚定自信的"],["assiduous","勤勉的"],["assuage","缓和"],["asteroid","小行星"],["astute","精明的"],["asunder","分开"],["atone","赎罪"],["atrocity","残暴"],["atrophy","萎缩"],["attenuate","减弱"],["attest","作证"],["attire","服装"],["attrition","削弱"],["audacious","大胆的"],["augment","扩大"],["augur","预言"],["august","尊严的"],["auspices","赞助"],["austere","严峻的"],["autocrat","独裁者"],["autonomy","自治权"],["auxiliary","辅助设备"],["avarice","贪婪"],["avenge","报复"],["aver","断言"],["averse","厌恶的"],["avid","贪婪的"],["avow","承认"],["awe","使敬畏"],["awry","歪的"],["axiom","公理"],["azure","天蓝色"]]},{"id":"bec","name":"商务英语","words":[["account","账户"],["accounting","会计"],["acquire","收购"],["acquisition","并购"],["agenda","议程表"],["agreement","合同"],["airline","航空企业"],["allocate","拨出"],["allowance","津贴费"],["analyze","分析"],["annual","年度报告"],["applicant","应聘者"],["application","申请表"],["appoint","任命"],["appraisal","绩效考核"],["asset","资产项目"],["audit","审计"],["balance","余额"],["bankrupt","破产"],["bargain","谈判"],["benchmark","基准"],["benefit","福利"],["bid","投标"],["bill","发票"],["board","董事会"],["bond","债券"],["bonus","红利"],["borrow","贷款"],["brand","商标"],["budget","开支预算"],["business","企业"],["buyer","采购员"],["campaign","营销活动"],["cancel","撤销"],["candidate","应聘人选"],["capital","资本"],["career","职业发展"],["cash","现款"],["catalog","产品目录"],["certificate","证书"],["chairman","董事长"],["charge","收费"],["client","客户方"],["commerce","贸易"],["commercial","商业广告"],["commission","提成"],["commodity","商品"],["company","公司"],["compete","竞争"],["competition","竞争对手"],["competitor","竞争者"],["complaint","投诉"],["conference","洽谈会"],["confirm","确认订单"],["consumer","消费者"],["contract","合约"],["cooperate","合作"],["corporate","企业的"],["corporation","股份公司"],["cost","费用"],["credit","赊购"],["currency","货币"],["customer","顾客"],["deadline","截止日期"],["deal","交易"],["debt","债务"],["decision","决策"],["decrease","下降"],["deliver","交货"],["delivery","交付"],["demand","需求"],["department","部门"],["deposit","定金"],["design","设计方案"],["director","主管"],["discount","折扣价"],["distribute","分销"],["dividend","股息"],["document","单据"],["economy","经济"],["employee","雇员"],["employer","雇主"],["employment","就业"],["enterprise","企业单位"],["estimate","报价"],["exchange","外汇"],["expand","扩张"],["export","出口"],["factory","工厂"],["feedback","反馈"],["finance","财务"],["firm","商行"],["forecast","预测"],["fund","基金"],["goods","货物"],["headquarters","总部"],["import","进口"],["income","收入"],["increase","增长"],["industry","产业"],["insurance","保险"],["interest","利息"],["intern","实习生"],["interview","面试"],["inventory","库存"],["invest","投资"],["invoice","发货票"],["launch","推出"],["lease","租赁"],["loan","贷款额"],["logistics","物流"],["loss","亏损"],["management","管理"],["manager","经理"],["manufacture","制造"],["market","市场"],["marketing","市场营销"],["meeting","会议"],["merge","合并"],["negotiate","协商"],["offer","报价单"],["office","办事处"],["order","订单"],["organize","组织"],["overtime","加班"],["package","包装"],["partner","合伙人"],["payment","付款"],["payroll","工资单"],["performance","业绩"],["plan","计划"],["policy","政策"],["price","价格"],["product","产品"],["production","生产"],["profit","利润"],["project","工程项目"],["promote","促销"],["promotion","升职"],["proposal","提案"],["purchase","采购"],["quality","品质"],["quantity","数量"],["quote","报价"],["receipt","收据"],["recruit","招聘"],["refund","退款"],["report","报表"],["retail","零售"],["revenue","营业收入"],["salary","薪水"],["sale","销售"],["sample","样品"],["schedule","时间表"],["service","服务"],["share","股份"],["shipment","装运"],["staff","员工"],["stock","股票"],["strategy","战略"],["supplier","供应商"],["supply","供应"],["target","目标"],["tax","税收"],["team","团队"],["trade","买卖"],["trainee","受训者"],["turnover","营业额"],["union","工会"],["vacancy","空缺"],["value","价值"],["warehouse","仓库"],["wholesale","批发"],["workforce","劳动力"]]}];
|
||
</script>
|
||
<script>
|
||
(function () {
|
||
const STORAGE_KEY = "typing-practice-progress";
|
||
const LEGACY_KEYS = ["typing-progress-v2", "typing-progress-v1"];
|
||
|
||
const FINGER_INFO = {
|
||
a: { hand: "左手", finger: "小指", key: "A", color: "orange" },
|
||
s: { hand: "左手", finger: "无名指", key: "S", color: "yellow" },
|
||
d: { hand: "左手", finger: "中指", key: "D", color: "green" },
|
||
f: { hand: "左手", finger: "食指", key: "F", color: "blue" },
|
||
j: { hand: "右手", finger: "食指", key: "J", color: "blue" },
|
||
k: { hand: "右手", finger: "中指", key: "K", color: "green" },
|
||
l: { hand: "右手", finger: "无名指", key: "L", color: "yellow" },
|
||
";": { hand: "右手", finger: "小指", key: ";", color: "orange" },
|
||
q: { hand: "左手", finger: "小指", key: "Q", color: "orange" },
|
||
w: { hand: "左手", finger: "无名指", key: "W", color: "yellow" },
|
||
e: { hand: "左手", finger: "中指", key: "E", color: "green" },
|
||
r: { hand: "左手", finger: "食指", key: "R", color: "blue" },
|
||
t: { hand: "左手", finger: "食指", key: "T", color: "blue" },
|
||
y: { hand: "右手", finger: "食指", key: "Y", color: "blue" },
|
||
u: { hand: "右手", finger: "食指", key: "U", color: "blue" },
|
||
i: { hand: "右手", finger: "中指", key: "I", color: "green" },
|
||
o: { hand: "右手", finger: "无名指", key: "O", color: "yellow" },
|
||
p: { hand: "右手", finger: "小指", key: "P", color: "orange" },
|
||
" ": { hand: "双手", finger: "大拇指", key: "空格", color: "purple" }
|
||
};
|
||
const FINGER_CLASS = {
|
||
a: "finger-orange", s: "finger-yellow", d: "finger-green", f: "finger-blue",
|
||
j: "finger-blue", k: "finger-green", l: "finger-yellow", ";": "finger-orange",
|
||
q: "finger-orange", w: "finger-yellow", e: "finger-green", r: "finger-blue", t: "finger-blue",
|
||
y: "finger-blue", u: "finger-blue", i: "finger-green", o: "finger-yellow", p: "finger-orange",
|
||
" ": "finger-purple"
|
||
};
|
||
const LEFT_HOME = ["a", "s", "d", "f"];
|
||
const RIGHT_HOME = ["j", "k", "l", ";"];
|
||
|
||
const Sound = {
|
||
ctx: null,
|
||
ensure: function () {
|
||
const AC = window.AudioContext || window.webkitAudioContext;
|
||
if (!AC) return null;
|
||
if (!this.ctx) this.ctx = new AC();
|
||
if (this.ctx.state === "suspended") this.ctx.resume();
|
||
return this.ctx;
|
||
},
|
||
beep: function (freq, duration, type, volume, slideTo) {
|
||
const ctx = this.ensure();
|
||
if (!ctx) return;
|
||
const osc = ctx.createOscillator();
|
||
const gain = ctx.createGain();
|
||
osc.type = type || "sine";
|
||
osc.frequency.setValueAtTime(freq, ctx.currentTime);
|
||
if (slideTo) osc.frequency.exponentialRampToValueAtTime(Math.max(slideTo, 40), ctx.currentTime + duration);
|
||
gain.gain.setValueAtTime(volume || 0.1, ctx.currentTime);
|
||
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
|
||
osc.connect(gain);
|
||
gain.connect(ctx.destination);
|
||
osc.start();
|
||
osc.stop(ctx.currentTime + duration + 0.02);
|
||
},
|
||
ok: function () { this.beep(880, 0.07, "triangle", 0.08); this.beep(1320, 0.11, "sine", 0.05); },
|
||
bad: function () { this.beep(196, 0.16, "square", 0.07, 90); },
|
||
pass: function () {
|
||
const self = this;
|
||
this.beep(523.25, 0.1, "triangle", 0.07);
|
||
setTimeout(function () { self.beep(659.25, 0.1, "triangle", 0.07); }, 90);
|
||
setTimeout(function () { self.beep(783.99, 0.18, "triangle", 0.09); }, 180);
|
||
},
|
||
fail: function () { this.beep(311, 0.2, "sawtooth", 0.05, 150); }
|
||
};
|
||
|
||
function L(opts) {
|
||
var length = opts.length;
|
||
if (length == null && opts.generate === "repeat-char") length = opts.repeat || 10;
|
||
if (length == null && opts.generate === "repeat-sequence") {
|
||
length = opts.sequence.length * (opts.repeat || 10);
|
||
}
|
||
return Object.assign({
|
||
allowSpace: false,
|
||
caseSensitive: true,
|
||
passKpm: null,
|
||
showKeyboard: true,
|
||
playable: true,
|
||
failText: "再练一次。先求正确,再求速度。",
|
||
length: length
|
||
}, opts);
|
||
}
|
||
|
||
function single(id, ch, next, title, chapter) {
|
||
var info = FINGER_INFO[ch];
|
||
var label = info.key;
|
||
chapter = chapter || 1;
|
||
return L({
|
||
id: id,
|
||
chapter: chapter,
|
||
charset: [ch],
|
||
generate: "repeat-char",
|
||
repeat: 10,
|
||
passAccuracy: 0.9,
|
||
unlockNext: next,
|
||
cardTitle: title,
|
||
cardSubtitle: info.hand + info.finger + ",按 10 遍",
|
||
practiceTitle: "第 " + chapter + " 章 · 字母 " + label,
|
||
hint: info.finger + "放在 " + label + " 上,按 10 次。看着屏幕,不要看键盘。",
|
||
fingerLegendHTML: info.hand + ":" + info.finger + " " + label,
|
||
passText: "练完了!这个键记住了。",
|
||
unlockText: next ? "已解锁下一关" : ""
|
||
});
|
||
}
|
||
|
||
function seqLevel(id, chapter, sequence, next, cardTitle, subtitle, practiceTitle, hint, legend, acc, repeat) {
|
||
repeat = repeat || 10;
|
||
return L({
|
||
id: id,
|
||
chapter: chapter,
|
||
charset: sequence.split(""),
|
||
sequence: sequence,
|
||
generate: "repeat-sequence",
|
||
repeat: repeat,
|
||
passAccuracy: acc,
|
||
unlockNext: next,
|
||
cardTitle: cardTitle,
|
||
cardSubtitle: subtitle,
|
||
practiceTitle: practiceTitle,
|
||
hint: hint,
|
||
fingerLegendHTML: legend,
|
||
passText: "通关!连起来更顺了。",
|
||
unlockText: "已解锁下一关"
|
||
});
|
||
}
|
||
|
||
function randLevel(id, chapter, charset, length, acc, kpm, next, cardTitle, subtitle, practiceTitle, hint, legend, generate) {
|
||
return L({
|
||
id: id,
|
||
chapter: chapter,
|
||
charset: charset,
|
||
length: length,
|
||
generate: generate || "random-from-charset",
|
||
passAccuracy: acc,
|
||
passKpm: kpm,
|
||
unlockNext: next,
|
||
cardTitle: cardTitle,
|
||
cardSubtitle: subtitle,
|
||
practiceTitle: practiceTitle,
|
||
hint: hint,
|
||
fingerLegendHTML: legend,
|
||
passText: "通关!",
|
||
unlockText: next ? "已解锁下一关" : "全部关卡完成!"
|
||
});
|
||
}
|
||
|
||
const LEVELS = {
|
||
"1-1": single("1-1", "a", "1-2", "字母 A"),
|
||
"1-2": single("1-2", "s", "1-3", "字母 S"),
|
||
"1-3": single("1-3", "d", "1-4", "字母 D"),
|
||
"1-4": single("1-4", "f", "1-5", "字母 F"),
|
||
"1-5": single("1-5", "j", "1-6", "字母 J"),
|
||
"1-6": single("1-6", "k", "1-7", "字母 K"),
|
||
"1-7": single("1-7", "l", "1-8", "字母 L"),
|
||
"1-8": single("1-8", ";", "2-1", "分号 ;"),
|
||
|
||
"2-1": randLevel("2-1", 2, ["j", "k"], 20, 0.92, null, "2-2", "JK 乱序", "右手两键乱序", "第 2 章 · JK 乱序",
|
||
"J 和 K 打乱出现。", "右手:食指 J · 中指 K"),
|
||
"2-2": randLevel("2-2", 2, ["j", "k", "l"], 30, 0.92, null, "2-3", "JKL 乱序", "右手三键乱序", "第 2 章 · JKL 乱序",
|
||
"J K L 打乱出现。", "右手:J K L"),
|
||
"2-3": randLevel("2-3", 2, ["j", "k", "l", ";"], 40, 0.92, null, "2-4", "JKL; 乱序", "右手四键乱序", "第 2 章 · JKL; 乱序",
|
||
"J K L ; 打乱出现。", "右手:J K L ;"),
|
||
"2-4": randLevel("2-4", 2, ["a", "s"], 20, 0.92, null, "2-5", "AS 乱序", "左手两键乱序", "第 2 章 · AS 乱序",
|
||
"A 和 S 打乱出现。", "左手:小指 A · 无名指 S"),
|
||
"2-5": randLevel("2-5", 2, ["a", "s", "d"], 30, 0.92, null, "2-6", "ASD 乱序", "左手三键乱序", "第 2 章 · ASD 乱序",
|
||
"A S D 打乱出现。", "左手:A S D"),
|
||
"2-6": randLevel("2-6", 2, ["a", "s", "d", "f"], 40, 0.92, null, "3-1", "ASDF 乱序", "左手四键乱序", "第 2 章 · ASDF 乱序",
|
||
"A S D F 打乱出现。", "左手:A S D F"),
|
||
|
||
"3-1": randLevel("3-1", 3, ["j", "k"], 30, 0.93, null, "3-2", "右手 JK 乱序", "30 键 · 正确率 93%",
|
||
"第 3 章 · 右手 JK 乱序", "只打 J 和 K,随机出现。", "右手:J K"),
|
||
"3-2": randLevel("3-2", 3, ["j", "k", "l"], 40, 0.94, null, "3-3", "右手 JKL 乱序", "40 键 · 正确率 94%",
|
||
"第 3 章 · 右手 JKL 乱序", "J K L 随机连打。", "右手:J K L"),
|
||
"3-3": randLevel("3-3", 3, ["j", "k", "l", ";"], 50, 0.94, null, "3-4", "右手四键", "50 键 · 正确率 94%",
|
||
"第 3 章 · 右手四键", "J K L ; 随机连打。", "右手:J K L ;"),
|
||
"3-4": randLevel("3-4", 3, ["j", "k", "l", ";"], 60, 0.95, null, "4-1", "右手加长", "60 键 · 正确率 95%",
|
||
"第 3 章 · 右手加长", "继续只打右手,更长一局。", "右手:J K L ;"),
|
||
|
||
"4-1": randLevel("4-1", 4, ["a", "s"], 30, 0.93, null, "4-2", "左手 AS 乱序", "30 键 · 正确率 93%",
|
||
"第 4 章 · 左手 AS 乱序", "只打 A 和 S,随机出现。", "左手:A S"),
|
||
"4-2": randLevel("4-2", 4, ["a", "s", "d"], 40, 0.94, null, "4-3", "左手 ASD 乱序", "40 键 · 正确率 94%",
|
||
"第 4 章 · 左手 ASD 乱序", "A S D 随机连打。", "左手:A S D"),
|
||
"4-3": randLevel("4-3", 4, ["a", "s", "d", "f"], 50, 0.94, null, "4-4", "左手四键", "50 键 · 正确率 94%",
|
||
"第 4 章 · 左手四键", "A S D F 随机连打。", "左手:A S D F"),
|
||
"4-4": randLevel("4-4", 4, ["a", "s", "d", "f"], 60, 0.95, null, "5-1", "左手加长", "60 键 · 正确率 95%",
|
||
"第 4 章 · 左手加长", "继续只打左手,更长一局。", "左手:A S D F"),
|
||
|
||
"5-1": randLevel("5-1", 5, ["f", "j", "k", "l", ";"], 50, 0.95, null, "5-2",
|
||
"右手加 F", "JKL; + F · 50 键 · 95%",
|
||
"第 5 章 · 右手加 F", "右手四个基准键,再加上左手食指 F。",
|
||
"右手 JKL; + 左手食指 F"),
|
||
"5-2": randLevel("5-2", 5, ["d", "f", "j", "k", "l", ";"], 60, 0.95, null, "5-3",
|
||
"右手加 FD", "JKL; + FD · 60 键 · 95%",
|
||
"第 5 章 · 右手加 FD", "继续加入左手中指 D。",
|
||
"右手 JKL; + 左手 FD"),
|
||
"5-3": randLevel("5-3", 5, ["s", "d", "f", "j", "k", "l", ";"], 70, 0.96, null, "5-4",
|
||
"右手加 FDS", "JKL; + FDS · 70 键 · 96%",
|
||
"第 5 章 · 右手加 FDS", "再加入左手无名指 S。",
|
||
"右手 JKL; + 左手 FDS"),
|
||
"5-4": randLevel("5-4", 5, ["a", "s", "d", "f", "j", "k", "l", ";"], 80, 0.96, 40, "6-1",
|
||
"右手加 FDSA", "八键随机 · 80 键 · 96% 且 ≥ 40 键/分",
|
||
"第 5 章 · 右手加 FDSA", "最后加上左手小指 A,八个基准键都在,又要准又要快。",
|
||
"左手 ASDF · 右手 JKL;")
|
||
};
|
||
|
||
(function addChapter6() {
|
||
var keys = ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"];
|
||
var home = ["a", "s", "d", "f", "j", "k", "l", ";"];
|
||
var n = 1;
|
||
keys.forEach(function (ch, i) {
|
||
var info = FINGER_INFO[ch];
|
||
var soloId = "6-" + n;
|
||
var mixId = "6-" + (n + 1);
|
||
var afterMix = i === keys.length - 1 ? "6-21" : "6-" + (n + 2);
|
||
LEVELS[soloId] = single(soloId, ch, mixId, "字母 " + info.key, 6);
|
||
LEVELS[mixId] = randLevel(
|
||
mixId, 6,
|
||
[ch, ch, ch].concat(home),
|
||
40, 0.92, null, afterMix,
|
||
info.key + " + 基准键",
|
||
info.key + " 和 ASDF JKL; 乱序混打",
|
||
"第 6 章 · " + info.key + " 加基准键",
|
||
"刚练完 " + info.key + ",现在和 A S D F J K L ; 打乱混在一起。",
|
||
info.key + " + ASDF JKL;"
|
||
);
|
||
n += 2;
|
||
});
|
||
LEVELS["6-21"] = randLevel(
|
||
"6-21", 6,
|
||
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "j", "k", "l", ";"],
|
||
60, 0.95, null, "6-22",
|
||
"上排加基准键", "上排 + ASDF JKL; · 乱序 60 键",
|
||
"第 6 章 · 上排加基准键",
|
||
"十个上排键和八个基准键全部乱序。",
|
||
"上排 QWERTYUIOP + 基准 ASDF JKL;"
|
||
);
|
||
LEVELS["6-22"] = randLevel(
|
||
"6-22", 6,
|
||
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "j", "k", "l", ";", " "],
|
||
80, 0.96, 40, null,
|
||
"加上空格", "全部合练 + 空格 · 乱序 80 键",
|
||
"第 6 章 · 毕业关",
|
||
"上排、基准键和空格都乱序出现。",
|
||
"上排 + 基准键 + 空格",
|
||
"random-with-space"
|
||
);
|
||
})();
|
||
|
||
const CHAPTERS = [
|
||
{ id: 1, title: "第 1 章 · 单个字母", desc: "左右手每个基准键按 10 遍,看清指法", ids: ["1-1","1-2","1-3","1-4","1-5","1-6","1-7","1-8"] },
|
||
{ id: 2, title: "第 2 章 · 慢慢连起来", desc: "先右手 jkl;,再左手 asdf,全部乱序", ids: ["2-1","2-2","2-3","2-4","2-5","2-6"] },
|
||
{ id: 3, title: "第 3 章 · 连右手", desc: "右手不再按固定顺序,随机连打", ids: ["3-1","3-2","3-3","3-4"] },
|
||
{ id: 4, title: "第 4 章 · 连左手", desc: "左手同样随机连打,要求继续提高", ids: ["4-1","4-2","4-3","4-4"] },
|
||
{ id: 5, title: "第 5 章 · 右手加左手", desc: "先加 F,再加 FD,再加 FDS,最后加上 A", ids: ["5-1","5-2","5-3","5-4"] },
|
||
{ id: 6, title: "第 6 章 · 上排键", desc: "每练一个上排键,再和 ASDF JKL; 乱序合打一遍", ids: ["6-1","6-2","6-3","6-4","6-5","6-6","6-7","6-8","6-9","6-10","6-11","6-12","6-13","6-14","6-15","6-16","6-17","6-18","6-19","6-20","6-21","6-22"] }
|
||
];
|
||
|
||
const IGNORE_KEYS = new Set([
|
||
"Shift", "Control", "Alt", "Meta", "CapsLock",
|
||
"Backspace", "Enter", "Tab", "Escape",
|
||
"ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown",
|
||
"Home", "End", "PageUp", "PageDown", "Delete", "Insert",
|
||
"ContextMenu", "NumLock", "ScrollLock", "Pause", "Process", "Dead", "Unidentified"
|
||
]);
|
||
|
||
const KEYBOARD_ROWS = [
|
||
[
|
||
{ key: "`", label: "`" },
|
||
{ key: "1", label: "1" }, { key: "2", label: "2" }, { key: "3", label: "3" },
|
||
{ key: "4", label: "4" }, { key: "5", label: "5" }, { key: "6", label: "6" },
|
||
{ key: "7", label: "7" }, { key: "8", label: "8" }, { key: "9", label: "9" },
|
||
{ key: "0", label: "0" }, { key: "-", label: "-" }, { key: "=", label: "=" },
|
||
{ key: "Backspace", label: "⌫", w: 78 }
|
||
],
|
||
[
|
||
{ key: "Tab", label: "Tab", w: 70 },
|
||
{ key: "q", label: "Q" }, { key: "w", label: "W" }, { key: "e", label: "E" },
|
||
{ key: "r", label: "R" }, { key: "t", label: "T" }, { key: "y", label: "Y" },
|
||
{ key: "u", label: "U" }, { key: "i", label: "I" }, { key: "o", label: "O" },
|
||
{ key: "p", label: "P" }, { key: "[", label: "[" }, { key: "]", label: "]" },
|
||
{ key: "\\", label: "\\", w: 62 }
|
||
],
|
||
[
|
||
{ key: "CapsLock", label: "Caps", w: 78 },
|
||
{ key: "a", label: "A" }, { key: "s", label: "S" }, { key: "d", label: "D" },
|
||
{ key: "f", label: "F" }, { key: "g", label: "G" }, { key: "h", label: "H" },
|
||
{ key: "j", label: "J" }, { key: "k", label: "K" }, { key: "l", label: "L" },
|
||
{ key: ";", label: ";" }, { key: "'", label: "'" },
|
||
{ key: "Enter", label: "Enter", w: 78 }
|
||
],
|
||
[
|
||
{ key: "Shift", label: "Shift", w: 100 },
|
||
{ key: "z", label: "Z" }, { key: "x", label: "X" }, { key: "c", label: "C" },
|
||
{ key: "v", label: "V" }, { key: "b", label: "B" }, { key: "n", label: "N" },
|
||
{ key: "m", label: "M" }, { key: ",", label: "," }, { key: ".", label: "." },
|
||
{ key: "/", label: "/" },
|
||
{ key: "ShiftRight", label: "Shift", w: 100 }
|
||
],
|
||
[
|
||
{ key: "Control", label: "Ctrl", w: 70 },
|
||
{ key: "Alt", label: "Alt", w: 62 },
|
||
{ key: " ", label: "", w: 280 },
|
||
{ key: "AltRight", label: "Alt", w: 62 },
|
||
{ key: "ControlRight", label: "Ctrl", w: 70 }
|
||
]
|
||
];
|
||
|
||
function defaultProgress() {
|
||
return { unlocked: ["1-1"], records: {}, daily: {}, wordBooks: ["ket", "pet", "primary-core", "junior"], wordVoice: "woman-mid", updatedAt: null };
|
||
}
|
||
function localDateKey(date) {
|
||
var y = date.getFullYear();
|
||
var m = String(date.getMonth() + 1).padStart(2, "0");
|
||
var d = String(date.getDate()).padStart(2, "0");
|
||
return y + "-" + m + "-" + d;
|
||
}
|
||
function formatDateLabel(key) {
|
||
var parts = key.split("-");
|
||
return parseInt(parts[1], 10) + "月" + parseInt(parts[2], 10) + "号";
|
||
}
|
||
function formatDuration(ms) {
|
||
var total = Math.max(0, Math.floor((ms || 0) / 1000));
|
||
var h = Math.floor(total / 3600);
|
||
var m = Math.floor((total % 3600) / 60);
|
||
var s = total % 60;
|
||
if (h) return h + "小时" + m + "分";
|
||
if (m) return m + "分" + s + "秒";
|
||
return s + "秒";
|
||
}
|
||
function readJson(key) {
|
||
try {
|
||
var raw = localStorage.getItem(key);
|
||
if (!raw) return null;
|
||
return JSON.parse(raw);
|
||
} catch (e) { return null; }
|
||
}
|
||
function allLevelIds() {
|
||
var ids = [];
|
||
CHAPTERS.forEach(function (ch) {
|
||
ch.ids.forEach(function (id) { ids.push(id); });
|
||
});
|
||
return ids;
|
||
}
|
||
function syncUnlocked(data) {
|
||
var set = {};
|
||
(data.unlocked || []).forEach(function (id) { set[id] = true; });
|
||
set["1-1"] = true;
|
||
var ids = allLevelIds();
|
||
ids.forEach(function (id, i) {
|
||
var rec = data.records[id];
|
||
var level = LEVELS[id];
|
||
if (rec && rec.passed) {
|
||
set[id] = true;
|
||
if (level && level.unlockNext) set[level.unlockNext] = true;
|
||
if (i + 1 < ids.length) set[ids[i + 1]] = true;
|
||
}
|
||
});
|
||
data.unlocked = ids.filter(function (id) { return set[id]; });
|
||
return data;
|
||
}
|
||
function loadProgress() {
|
||
var data = readJson(STORAGE_KEY);
|
||
if (!data) {
|
||
for (var i = 0; i < LEGACY_KEYS.length; i++) {
|
||
data = readJson(LEGACY_KEYS[i]);
|
||
if (data) break;
|
||
}
|
||
}
|
||
if (!data) return defaultProgress();
|
||
data.records = data.records || {};
|
||
data.daily = data.daily || {};
|
||
if (!Array.isArray(data.wordBooks) || !data.wordBooks.length) {
|
||
data.wordBooks = ["ket", "pet", "primary-core", "junior"];
|
||
}
|
||
if (!data.wordVoice) data.wordVoice = "woman-mid";
|
||
var before = JSON.stringify(data.unlocked || []);
|
||
syncUnlocked(data);
|
||
if (JSON.stringify(data.unlocked) !== before) {
|
||
try { saveProgress(data); } catch (e) {}
|
||
}
|
||
return data;
|
||
}
|
||
function saveProgress(data) {
|
||
data.updatedAt = new Date().toISOString();
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
}
|
||
function saveRun(levelId, stats) {
|
||
var data = loadProgress();
|
||
var rec = data.records[levelId] || {
|
||
passed: false,
|
||
playCount: 0,
|
||
bestAccuracy: 0,
|
||
bestKpm: 0
|
||
};
|
||
rec.playCount = (rec.playCount || 0) + 1;
|
||
rec.bestAccuracy = Math.max(rec.bestAccuracy || 0, stats.accuracy);
|
||
rec.bestKpm = Math.max(rec.bestKpm || 0, stats.kpm);
|
||
rec.passed = Boolean(rec.passed || stats.passed);
|
||
rec.lastAccuracy = stats.accuracy;
|
||
rec.lastKpm = stats.kpm;
|
||
rec.lastElapsedMs = stats.elapsedMs;
|
||
rec.lastCorrect = stats.correctHits;
|
||
rec.lastWrong = stats.wrongHits;
|
||
rec.lastPassed = stats.passed;
|
||
rec.lastPlayedAt = new Date().toISOString();
|
||
if (stats.passed && !rec.firstPassedAt) rec.firstPassedAt = rec.lastPlayedAt;
|
||
data.records[levelId] = rec;
|
||
data.daily = data.daily || {};
|
||
var dayKey = localDateKey(new Date());
|
||
var day = data.daily[dayKey] || { elapsedMs: 0, correctHits: 0, wrongHits: 0, runs: 0 };
|
||
day.elapsedMs += stats.elapsedMs || 0;
|
||
day.correctHits += stats.correctHits || 0;
|
||
day.wrongHits += stats.wrongHits || 0;
|
||
day.runs += 1;
|
||
data.daily[dayKey] = day;
|
||
syncUnlocked(data);
|
||
saveProgress(data);
|
||
return data;
|
||
}
|
||
|
||
function generateText(charset, length, maxRun) {
|
||
maxRun = maxRun || 3;
|
||
var out = [];
|
||
for (var i = 0; i < length; i++) {
|
||
var ch, guard = 0;
|
||
do {
|
||
ch = charset[Math.floor(Math.random() * charset.length)];
|
||
guard += 1;
|
||
} while (guard < 20 && out.length >= maxRun && out.slice(-maxRun).every(function (c) { return c === ch; }));
|
||
out.push(ch);
|
||
}
|
||
return out.join("");
|
||
}
|
||
function generateAlternateHands(length) {
|
||
var out = [];
|
||
var startLeft = Math.random() < 0.5;
|
||
for (var i = 0; i < length; i++) {
|
||
var useLeft = startLeft ? (i % 2 === 0) : (i % 2 === 1);
|
||
var pool = useLeft ? LEFT_HOME : RIGHT_HOME;
|
||
var ch, guard = 0;
|
||
do {
|
||
ch = pool[Math.floor(Math.random() * pool.length)];
|
||
guard += 1;
|
||
} while (guard < 20 && out.length >= 2 && out[out.length - 2] === ch);
|
||
out.push(ch);
|
||
}
|
||
return out.join("");
|
||
}
|
||
function generateWithSpaces(letters, length) {
|
||
var out = [];
|
||
var wordLen = 0;
|
||
var targetWord = 3 + Math.floor(Math.random() * 3);
|
||
while (out.length < length) {
|
||
if (wordLen >= targetWord && out.length < length - 1) {
|
||
out.push(" ");
|
||
wordLen = 0;
|
||
targetWord = 3 + Math.floor(Math.random() * 3);
|
||
} else {
|
||
var ch = letters[Math.floor(Math.random() * letters.length)];
|
||
if (out.length >= 2 && out[out.length - 1] === ch && out[out.length - 2] === ch) continue;
|
||
out.push(ch);
|
||
wordLen += 1;
|
||
}
|
||
}
|
||
if (out[0] === " ") out[0] = letters[Math.floor(Math.random() * letters.length)];
|
||
if (out[out.length - 1] === " ") out[out.length - 1] = letters[Math.floor(Math.random() * letters.length)];
|
||
return out.join("");
|
||
}
|
||
function generateForLevel(config) {
|
||
if (config.generate === "fixed") return config.text || "";
|
||
if (config.generate === "repeat-char") return (config.charset[0]).repeat(config.repeat || 10);
|
||
if (config.generate === "repeat-sequence") return config.sequence.repeat(config.repeat || 10);
|
||
if (config.generate === "alternate-hands") return generateAlternateHands(config.length);
|
||
if (config.generate === "random-with-space") {
|
||
var letters = config.charset.filter(function (c) { return c !== " "; });
|
||
return generateWithSpaces(letters, config.length);
|
||
}
|
||
return generateText(config.charset, config.length, 3);
|
||
}
|
||
function formatTime(ms) {
|
||
var total = Math.max(0, Math.floor(ms / 1000));
|
||
return String(Math.floor(total / 60)).padStart(2, "0") + ":" + String(total % 60).padStart(2, "0");
|
||
}
|
||
function formatPct(n) { return (n * 100).toFixed(1) + "%"; }
|
||
|
||
function Engine(config) { this.config = config; this.reset(); }
|
||
Engine.prototype.reset = function () {
|
||
this.text = generateForLevel(this.config);
|
||
this.cursor = 0;
|
||
this.correctHits = 0;
|
||
this.wrongHits = 0;
|
||
this.state = "idle";
|
||
this.startedAt = null;
|
||
this.finishedAt = null;
|
||
this.lastWrong = false;
|
||
};
|
||
Engine.prototype.elapsedMs = function () {
|
||
if (!this.startedAt) return 0;
|
||
return (this.finishedAt || Date.now()) - this.startedAt;
|
||
};
|
||
Engine.prototype.accuracy = function () {
|
||
var total = this.correctHits + this.wrongHits;
|
||
return total ? this.correctHits / total : 0;
|
||
};
|
||
Engine.prototype.kpm = function () {
|
||
var ms = this.elapsedMs();
|
||
if (ms < 100) return 0;
|
||
return this.correctHits / (ms / 60000);
|
||
};
|
||
Engine.prototype.handleKey = function (key, event) {
|
||
if (this.state === "finished") return { type: "ignore" };
|
||
if (event.ctrlKey || event.metaKey || event.altKey) return { type: "ignore" };
|
||
if (IGNORE_KEYS.has(key) || (key.length > 1 && key !== " ")) return { type: "ignore" };
|
||
if (event.isComposing || event.keyCode === 229) return { type: "ime" };
|
||
if (this.state === "idle") { this.state = "playing"; this.startedAt = Date.now(); }
|
||
var target = this.text[this.cursor];
|
||
var expected = this.config.caseSensitive ? target : target.toLowerCase();
|
||
var actual = this.config.caseSensitive ? key : key.toLowerCase();
|
||
if (actual === expected) {
|
||
this.correctHits += 1;
|
||
this.cursor += 1;
|
||
this.lastWrong = false;
|
||
if (this.cursor >= this.text.length) {
|
||
this.state = "finished";
|
||
this.finishedAt = Date.now();
|
||
return { type: "correct", finished: true, target: target };
|
||
}
|
||
return { type: "correct", finished: false, target: target };
|
||
}
|
||
if (key.length === 1) {
|
||
this.wrongHits += 1;
|
||
this.lastWrong = true;
|
||
return { type: "wrong", target: target };
|
||
}
|
||
return { type: "ignore" };
|
||
};
|
||
|
||
var els = {
|
||
select: document.getElementById("view-select"),
|
||
practice: document.getElementById("view-practice"),
|
||
chapters: document.getElementById("chapter-list"),
|
||
title: document.getElementById("practice-title"),
|
||
hint: document.getElementById("practice-hint"),
|
||
legend: document.getElementById("finger-legend"),
|
||
banner: document.getElementById("finger-banner"),
|
||
textLine: document.getElementById("text-line"),
|
||
keyboard: document.getElementById("keyboard"),
|
||
progress: document.getElementById("stat-progress"),
|
||
accuracy: document.getElementById("stat-accuracy"),
|
||
kpm: document.getElementById("stat-kpm"),
|
||
time: document.getElementById("stat-time"),
|
||
bar: document.getElementById("stat-bar"),
|
||
ime: document.getElementById("ime-banner"),
|
||
mask: document.getElementById("modal-mask"),
|
||
modal: document.getElementById("result-modal"),
|
||
resultTitle: document.getElementById("result-title"),
|
||
resultMsg: document.getElementById("result-msg"),
|
||
resultAccuracy: document.getElementById("result-accuracy"),
|
||
resultAccLabel: document.getElementById("result-acc-label"),
|
||
resultKpm: document.getElementById("result-kpm"),
|
||
resultKpmLabel: document.getElementById("result-kpm-label"),
|
||
resultTime: document.getElementById("result-time"),
|
||
resultHits: document.getElementById("result-hits"),
|
||
unlockLine: document.getElementById("unlock-line"),
|
||
toast: document.getElementById("toast"),
|
||
statsMask: document.getElementById("stats-mask"),
|
||
statsList: document.getElementById("stats-list"),
|
||
wordsMask: document.getElementById("words-mask"),
|
||
wordsWarn: document.getElementById("words-warn"),
|
||
booksMask: document.getElementById("books-mask"),
|
||
bookList: document.getElementById("book-list"),
|
||
voiceList: document.getElementById("voice-list"),
|
||
wordMeaning: document.getElementById("word-meaning"),
|
||
speak: document.getElementById("btn-speak")
|
||
};
|
||
|
||
var currentView = "select";
|
||
var currentLevel = null;
|
||
var engine = null;
|
||
var rafId = 0;
|
||
var toastTimer = 0;
|
||
var imeTimer = 0;
|
||
var wordMode = false;
|
||
var wordPool = [];
|
||
var lastWordEn = "";
|
||
var wordSession = null;
|
||
var speakUtterance = null;
|
||
var VOICE_PRESETS = [
|
||
{ id: "samantha", label: "美国女声", voiceName: "Samantha", lang: "en-US", localeText: "美国" },
|
||
{ id: "kathy", label: "美国女声 Kathy", voiceName: "Kathy", lang: "en-US", localeText: "美国" },
|
||
{ id: "fred", label: "美国男声", voiceName: "Fred", lang: "en-US", localeText: "美国" },
|
||
{ id: "grandma", label: "老年女声", voiceName: "Grandma", lang: "en-US", localeText: "美国" },
|
||
{ id: "grandpa", label: "老年男声", voiceName: "Grandpa", lang: "en-US", localeText: "美国" },
|
||
{ id: "grandma-uk", label: "英式老年女声", voiceName: "Grandma", lang: "en-GB", localeText: "英国" },
|
||
{ id: "grandpa-uk", label: "英式老年男声", voiceName: "Grandpa", lang: "en-GB", localeText: "英国" },
|
||
{ id: "karen", label: "澳洲女声", voiceName: "Karen", lang: "en-AU", localeText: "澳洲" },
|
||
{ id: "lee", label: "澳洲男声", voiceName: "Lee", lang: "en-AU", localeText: "澳洲" },
|
||
{ id: "daniel", label: "英式男声", voiceName: "Daniel", lang: "en-GB", localeText: "英国" },
|
||
{ id: "moira", label: "爱尔兰女声", voiceName: "Moira", lang: "en-IE", localeText: "爱尔兰" },
|
||
{ id: "tessa", label: "南非女声", voiceName: "Tessa", lang: "en-ZA", localeText: "南非" },
|
||
{ id: "rishi", label: "印度男声", voiceName: "Rishi", lang: "en-IN", localeText: "印度" }
|
||
];
|
||
var VOICE_ID_ALIASES = {
|
||
child: "samantha",
|
||
junior: "samantha",
|
||
princess: "samantha",
|
||
"woman-mid": "samantha",
|
||
"man-mid": "fred",
|
||
"woman-young": "samantha",
|
||
"man-old": "grandpa",
|
||
"man-uk": "daniel",
|
||
"woman-soft": "samantha",
|
||
"man-news": "daniel"
|
||
};
|
||
var lockedScrollY = 0;
|
||
|
||
function getSpeechVoices() {
|
||
if (!window.speechSynthesis) return [];
|
||
return window.speechSynthesis.getVoices() || [];
|
||
}
|
||
function langNorm(lang) {
|
||
return (lang || "").toLowerCase().replace(/_/g, "-");
|
||
}
|
||
function voiceHeadName(name) {
|
||
var match = String(name || "").trim().match(/^[A-Za-z]+/);
|
||
return match ? match[0].toLowerCase() : "";
|
||
}
|
||
function voiceMatchesPreset(voice, preset) {
|
||
if (voiceHeadName(voice.name) !== preset.voiceName.toLowerCase()) return false;
|
||
var lang = langNorm(voice.lang);
|
||
var need = langNorm(preset.lang);
|
||
return lang === need || lang.indexOf(need + "-") === 0;
|
||
}
|
||
function pickSpeechVoice(preset) {
|
||
var voices = getSpeechVoices();
|
||
var i;
|
||
if (!voices.length || !preset) return null;
|
||
for (i = 0; i < voices.length; i++) {
|
||
if (voiceMatchesPreset(voices[i], preset)) return voices[i];
|
||
}
|
||
return null;
|
||
}
|
||
function resolveVoiceId(id) {
|
||
return VOICE_ID_ALIASES[id] || id || "samantha";
|
||
}
|
||
function currentVoicePreset() {
|
||
var id = resolveVoiceId(loadProgress().wordVoice);
|
||
var i;
|
||
for (i = 0; i < VOICE_PRESETS.length; i++) {
|
||
if (VOICE_PRESETS[i].id === id) return VOICE_PRESETS[i];
|
||
}
|
||
return VOICE_PRESETS[0];
|
||
}
|
||
function stopSpeak() {
|
||
speakToken += 1;
|
||
if (window.speechSynthesis) window.speechSynthesis.cancel();
|
||
speakUtterance = null;
|
||
if (els.speak) els.speak.classList.remove("is-playing");
|
||
}
|
||
var speakToken = 0;
|
||
function speakWord(text) {
|
||
if (!text || !window.speechSynthesis) return;
|
||
stopSpeak();
|
||
var token = speakToken;
|
||
var preset = currentVoicePreset();
|
||
setTimeout(function () {
|
||
if (token !== speakToken) return;
|
||
var voice = pickSpeechVoice(preset);
|
||
if (!voice) {
|
||
showToast("没有匹配的已标注人声,未朗读");
|
||
return;
|
||
}
|
||
var utter = new SpeechSynthesisUtterance(text);
|
||
utter.voice = voice;
|
||
utter.lang = voice.lang || preset.lang;
|
||
utter.pitch = 1;
|
||
utter.rate = 0.92;
|
||
utter.volume = 1;
|
||
utter.onend = function () {
|
||
if (speakUtterance === utter) {
|
||
speakUtterance = null;
|
||
els.speak.classList.remove("is-playing");
|
||
}
|
||
};
|
||
utter.onerror = function () {
|
||
if (speakUtterance === utter) {
|
||
speakUtterance = null;
|
||
els.speak.classList.remove("is-playing");
|
||
}
|
||
};
|
||
speakUtterance = utter;
|
||
els.speak.classList.add("is-playing");
|
||
window.speechSynthesis.speak(utter);
|
||
}, 40);
|
||
}
|
||
function renderVoices() {
|
||
var selected = resolveVoiceId(loadProgress().wordVoice);
|
||
var selectedOk = false;
|
||
var firstOk = "";
|
||
els.voiceList.innerHTML = VOICE_PRESETS.map(function (preset) {
|
||
var found = pickSpeechVoice(preset);
|
||
var missing = !found;
|
||
if (!missing && !firstOk) firstOk = preset.id;
|
||
if (!missing && preset.id === selected) selectedOk = true;
|
||
return (
|
||
'<label class="voice-item' + (missing ? " is-missing" : "") + '">' +
|
||
'<input type="radio" name="word-voice" value="' + preset.id + '"' +
|
||
(preset.id === selected && !missing ? " checked" : "") +
|
||
(missing ? " disabled" : "") + ">" +
|
||
"<span>" + preset.label +
|
||
'<span class="voice-sub">' + (missing ? "未安装 " + preset.voiceName : preset.voiceName + " · " + preset.localeText) + "</span>" +
|
||
"</span>" +
|
||
"</label>"
|
||
);
|
||
}).join("");
|
||
if (!selectedOk && firstOk) {
|
||
var box = els.voiceList.querySelector('input[value="' + firstOk + '"]');
|
||
if (box) box.checked = true;
|
||
}
|
||
}
|
||
function saveSelectedVoice() {
|
||
var box = els.voiceList.querySelector("input[name=word-voice]:checked:not(:disabled)");
|
||
if (!box) return;
|
||
var data = loadProgress();
|
||
data.wordVoice = box.value;
|
||
saveProgress(data);
|
||
}
|
||
function warmupVoices() {
|
||
if (!window.speechSynthesis) return;
|
||
getSpeechVoices();
|
||
if (typeof window.speechSynthesis.onvoiceschanged !== "undefined") {
|
||
window.speechSynthesis.onvoiceschanged = function () {
|
||
getSpeechVoices();
|
||
if (!els.wordsMask.classList.contains("hidden")) renderVoices();
|
||
};
|
||
}
|
||
}
|
||
|
||
function showToast(text) {
|
||
els.toast.textContent = text;
|
||
els.toast.classList.remove("hidden");
|
||
clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(function () { els.toast.classList.add("hidden"); }, 1800);
|
||
}
|
||
function showIme() {
|
||
els.ime.classList.remove("hidden");
|
||
clearTimeout(imeTimer);
|
||
imeTimer = setTimeout(function () { els.ime.classList.add("hidden"); }, 2500);
|
||
}
|
||
|
||
function renderSelect() {
|
||
var progress = loadProgress();
|
||
els.chapters.innerHTML = "";
|
||
CHAPTERS.forEach(function (ch) {
|
||
var block = document.createElement("section");
|
||
block.className = "chapter-block ch-" + ch.id;
|
||
block.innerHTML = "<h2>" + ch.title + "</h2><p>" + ch.desc + "</p>";
|
||
var grid = document.createElement("div");
|
||
grid.className = "level-grid";
|
||
ch.ids.forEach(function (id) {
|
||
var level = LEVELS[id];
|
||
var rec = progress.records[id];
|
||
var passed = rec && rec.passed;
|
||
var unlocked = passed || progress.unlocked.indexOf(id) !== -1;
|
||
var btn = document.createElement("button");
|
||
btn.type = "button";
|
||
btn.className = "level-card";
|
||
btn.disabled = !unlocked;
|
||
btn.innerHTML =
|
||
'<div class="level-id">' + id + "</div>" +
|
||
"<h3>" + level.cardTitle + "</h3>" +
|
||
'<p class="sub">' + level.cardSubtitle + "</p>" +
|
||
'<div class="level-meta">' +
|
||
(passed
|
||
? '<span class="badge ok">已通关 · ' + formatPct(rec.bestAccuracy) + "</span>"
|
||
: unlocked
|
||
? (rec && rec.bestAccuracy ? '<span class="badge">最佳 ' + formatPct(rec.bestAccuracy) + "</span>" : '<span class="badge">已解锁</span>')
|
||
: '<span class="badge">未解锁</span>') +
|
||
"</div>";
|
||
btn.addEventListener("click", function () {
|
||
if (!unlocked) return;
|
||
startLevel(id);
|
||
});
|
||
grid.appendChild(btn);
|
||
});
|
||
block.appendChild(grid);
|
||
els.chapters.appendChild(block);
|
||
});
|
||
}
|
||
|
||
function buildKeyboard(charset) {
|
||
var enabled = new Set(charset);
|
||
els.keyboard.innerHTML = "";
|
||
KEYBOARD_ROWS.forEach(function (row) {
|
||
var rowEl = document.createElement("div");
|
||
rowEl.className = "kb-row";
|
||
row.forEach(function (item) {
|
||
var keyEl = document.createElement("div");
|
||
keyEl.className = "key";
|
||
keyEl.dataset.key = item.key;
|
||
keyEl.textContent = item.label;
|
||
if (item.w) keyEl.style.setProperty("--w", item.w + "px");
|
||
if (enabled.has(item.key)) {
|
||
keyEl.classList.add("enabled");
|
||
if (FINGER_CLASS[item.key]) keyEl.classList.add(FINGER_CLASS[item.key]);
|
||
}
|
||
rowEl.appendChild(keyEl);
|
||
});
|
||
els.keyboard.appendChild(rowEl);
|
||
});
|
||
}
|
||
|
||
function updateFingerBanner() {
|
||
if (!engine || engine.state === "finished") return;
|
||
var ch = engine.text[engine.cursor];
|
||
var info = FINGER_INFO[ch];
|
||
if (!info) return;
|
||
els.banner.textContent = info.hand + " · " + info.finger + " · " + info.key;
|
||
els.banner.className = "finger-banner " + info.color;
|
||
}
|
||
|
||
function renderText() {
|
||
els.textLine.innerHTML = "";
|
||
engine.text.split("").forEach(function (ch, i) {
|
||
var span = document.createElement("span");
|
||
var cls = ch === ";" ? "semi" : (ch === " " ? "space" : ch);
|
||
span.className = "glyph ch-" + cls;
|
||
span.textContent = ch === " " ? "␣" : ch;
|
||
if (i < engine.cursor) span.classList.add("done");
|
||
else if (i === engine.cursor) {
|
||
span.classList.add("current");
|
||
if (engine.lastWrong) span.classList.add("is-wrong");
|
||
}
|
||
els.textLine.appendChild(span);
|
||
});
|
||
updateFingerBanner();
|
||
}
|
||
|
||
function highlightKeyboard() {
|
||
var keys = els.keyboard.querySelectorAll(".key");
|
||
var target = engine && engine.state !== "finished" ? engine.text[engine.cursor] : null;
|
||
keys.forEach(function (keyEl) {
|
||
keyEl.classList.toggle("target", target !== null && keyEl.dataset.key === target);
|
||
});
|
||
}
|
||
function flashKey(key, type) {
|
||
var keyEl = els.keyboard.querySelector('.key[data-key="' + CSS.escape(key) + '"]');
|
||
if (!keyEl) return;
|
||
var cls = type === "ok" ? "flash-ok" : "flash-bad";
|
||
keyEl.classList.remove("flash-ok", "flash-bad");
|
||
void keyEl.offsetWidth;
|
||
keyEl.classList.add(cls);
|
||
setTimeout(function () { keyEl.classList.remove(cls); }, 180);
|
||
}
|
||
function updateHud() {
|
||
if (!engine) return;
|
||
var len = engine.text.length || 1;
|
||
els.progress.textContent = engine.cursor + " / " + len;
|
||
els.bar.style.width = ((engine.cursor / len) * 100).toFixed(1) + "%";
|
||
if (wordMode && wordSession) {
|
||
var correct = wordSession.correctHits + engine.correctHits;
|
||
var wrong = wordSession.wrongHits + engine.wrongHits;
|
||
var total = correct + wrong;
|
||
els.accuracy.textContent = total ? formatPct(correct / total) : "--";
|
||
var ms = Date.now() - wordSession.startedAt;
|
||
if (!total) {
|
||
els.kpm.textContent = "--";
|
||
els.time.textContent = "00:00";
|
||
} else {
|
||
els.kpm.textContent = String(Math.round(correct / (ms / 60000)));
|
||
els.time.textContent = formatTime(ms);
|
||
}
|
||
return;
|
||
}
|
||
var total = engine.correctHits + engine.wrongHits;
|
||
els.accuracy.textContent = total ? formatPct(engine.accuracy()) : "--";
|
||
if (!engine.startedAt) {
|
||
els.kpm.textContent = "--";
|
||
els.time.textContent = "00:00";
|
||
} else {
|
||
els.kpm.textContent = String(Math.round(engine.kpm()));
|
||
els.time.textContent = formatTime(engine.elapsedMs());
|
||
}
|
||
}
|
||
function tick() {
|
||
if (currentView !== "practice") return;
|
||
updateHud();
|
||
rafId = requestAnimationFrame(tick);
|
||
}
|
||
function showView(name) {
|
||
currentView = name;
|
||
els.select.classList.toggle("hidden", name !== "select");
|
||
els.practice.classList.toggle("hidden", name !== "practice");
|
||
if (name !== "practice") {
|
||
cancelAnimationFrame(rafId);
|
||
els.mask.classList.add("hidden");
|
||
wordMode = false;
|
||
els.wordMeaning.classList.add("hidden");
|
||
els.speak.classList.add("hidden");
|
||
stopSpeak();
|
||
}
|
||
syncPageScrollLock();
|
||
}
|
||
function startLevel(id) {
|
||
Sound.ensure();
|
||
currentLevel = LEVELS[id];
|
||
engine = new Engine(currentLevel);
|
||
els.title.textContent = currentLevel.practiceTitle;
|
||
els.hint.textContent = currentLevel.hint;
|
||
els.legend.innerHTML = currentLevel.fingerLegendHTML;
|
||
els.ime.classList.add("hidden");
|
||
els.mask.classList.add("hidden");
|
||
els.wordMeaning.classList.add("hidden");
|
||
els.speak.classList.add("hidden");
|
||
wordMode = false;
|
||
stopSpeak();
|
||
buildKeyboard(currentLevel.charset);
|
||
renderText();
|
||
highlightKeyboard();
|
||
updateHud();
|
||
showView("practice");
|
||
cancelAnimationFrame(rafId);
|
||
rafId = requestAnimationFrame(tick);
|
||
}
|
||
function finishRun() {
|
||
if (wordMode) {
|
||
finishWord();
|
||
return;
|
||
}
|
||
var accuracy = engine.accuracy();
|
||
var kpm = engine.kpm();
|
||
var passed = accuracy >= currentLevel.passAccuracy &&
|
||
(currentLevel.passKpm == null || kpm >= currentLevel.passKpm);
|
||
try {
|
||
saveRun(currentLevel.id, {
|
||
accuracy: accuracy,
|
||
kpm: kpm,
|
||
passed: passed,
|
||
unlockNext: currentLevel.unlockNext,
|
||
elapsedMs: engine.elapsedMs(),
|
||
correctHits: engine.correctHits,
|
||
wrongHits: engine.wrongHits
|
||
});
|
||
} catch (e) {}
|
||
els.modal.classList.toggle("pass", passed);
|
||
els.modal.classList.toggle("fail", !passed);
|
||
els.resultTitle.textContent = passed ? "通关" : "未通关";
|
||
els.resultMsg.textContent = passed ? currentLevel.passText : currentLevel.failText;
|
||
els.resultAccuracy.textContent = formatPct(accuracy);
|
||
els.resultAccLabel.textContent = "正确率(门槛 ≥ " + Math.round(currentLevel.passAccuracy * 100) + "%)";
|
||
els.resultKpm.textContent = Math.round(kpm) + " 键/分";
|
||
els.resultKpmLabel.textContent = currentLevel.passKpm
|
||
? "速度(门槛 ≥ " + currentLevel.passKpm + " 键/分)"
|
||
: "速度";
|
||
els.resultTime.textContent = formatTime(engine.elapsedMs());
|
||
els.resultHits.textContent = engine.correctHits + " / " + engine.wrongHits + " / " + (engine.correctHits + engine.wrongHits);
|
||
els.unlockLine.classList.toggle("hidden", !(passed && currentLevel.unlockText));
|
||
els.unlockLine.textContent = currentLevel.unlockText || "";
|
||
var nextId = passed ? currentLevel.unlockNext : null;
|
||
var hasNext = Boolean(nextId && LEVELS[nextId]);
|
||
var btnNext = document.getElementById("btn-next");
|
||
var btnRetry = document.getElementById("btn-retry");
|
||
btnNext.classList.toggle("hidden", !hasNext);
|
||
btnNext.dataset.next = hasNext ? nextId : "";
|
||
btnRetry.classList.toggle("btn-primary", !hasNext);
|
||
btnRetry.classList.toggle("btn-secondary", hasNext);
|
||
els.mask.classList.remove("hidden");
|
||
syncPageScrollLock();
|
||
if (passed) Sound.pass(); else Sound.fail();
|
||
}
|
||
|
||
function renderStats() {
|
||
var progress = loadProgress();
|
||
var daily = progress.daily || {};
|
||
var keys = Object.keys(daily).sort().reverse();
|
||
if (!keys.length) {
|
||
els.statsList.innerHTML = '<div class="stats-empty">还没有按天记录。练完一关后,这里会记下当天的时长、敲击次数和正确率。</div>';
|
||
return;
|
||
}
|
||
els.statsList.innerHTML = keys.map(function (key) {
|
||
var day = daily[key];
|
||
var hits = (day.correctHits || 0) + (day.wrongHits || 0);
|
||
var acc = hits ? (day.correctHits || 0) / hits : 0;
|
||
return (
|
||
'<div class="stats-day">' +
|
||
"<h4>" + formatDateLabel(key) + "</h4>" +
|
||
'<div class="stats-day-grid">' +
|
||
'<div><div class="k">时长</div><div class="v">' + formatDuration(day.elapsedMs) + "</div></div>" +
|
||
'<div><div class="k">敲击次数</div><div class="v">' + hits + "</div></div>" +
|
||
'<div><div class="k">正确率</div><div class="v">' + formatPct(acc) + "</div></div>" +
|
||
"</div>" +
|
||
"</div>"
|
||
);
|
||
}).join("");
|
||
}
|
||
function openStats() {
|
||
renderStats();
|
||
els.statsMask.classList.remove("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
function closeStats() {
|
||
els.statsMask.classList.add("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
|
||
function practicedLetters(progress) {
|
||
var set = {};
|
||
Object.keys(LEVELS).forEach(function (id) {
|
||
var rec = progress.records[id];
|
||
if (!(rec && rec.passed)) return;
|
||
(LEVELS[id].charset || []).forEach(function (ch) {
|
||
if (/^[a-z]$/.test(ch)) set[ch] = true;
|
||
});
|
||
});
|
||
return Object.keys(set).sort();
|
||
}
|
||
function selectedBooks(progress) {
|
||
var ids = progress.wordBooks || [];
|
||
var list = window.WORD_BOOKS || [];
|
||
return list.filter(function (book) { return ids.indexOf(book.id) !== -1; });
|
||
}
|
||
function wordAllowed(en, letters) {
|
||
var set = {};
|
||
letters.forEach(function (ch) { set[ch] = true; });
|
||
en = String(en || "").toLowerCase();
|
||
if (!en || !/^[a-z]+$/.test(en)) return false;
|
||
for (var i = 0; i < en.length; i++) {
|
||
if (!set[en[i]]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
function buildWordPool(progress) {
|
||
var letters = practicedLetters(progress);
|
||
var map = {};
|
||
selectedBooks(progress).forEach(function (book) {
|
||
(book.words || []).forEach(function (pair) {
|
||
var en = pair[0];
|
||
if (!wordAllowed(en, letters)) return;
|
||
if (!map[en]) map[en] = { en: en, zh: pair[1], books: [] };
|
||
map[en].books.push(book.name);
|
||
});
|
||
});
|
||
return Object.keys(map).map(function (k) { return map[k]; });
|
||
}
|
||
function pickNextWord() {
|
||
if (!wordPool.length) return null;
|
||
var choices = wordPool.filter(function (w) { return w.en !== lastWordEn; });
|
||
if (!choices.length) choices = wordPool;
|
||
return choices[Math.floor(Math.random() * choices.length)];
|
||
}
|
||
function saveDailyStats(stats) {
|
||
try {
|
||
var data = loadProgress();
|
||
data.daily = data.daily || {};
|
||
var dayKey = localDateKey(new Date());
|
||
var day = data.daily[dayKey] || { elapsedMs: 0, correctHits: 0, wrongHits: 0, runs: 0 };
|
||
day.elapsedMs += stats.elapsedMs || 0;
|
||
day.correctHits += stats.correctHits || 0;
|
||
day.wrongHits += stats.wrongHits || 0;
|
||
day.runs += 1;
|
||
data.daily[dayKey] = day;
|
||
saveProgress(data);
|
||
} catch (e) {}
|
||
}
|
||
function showWord(word) {
|
||
lastWordEn = word.en;
|
||
var letters = practicedLetters(loadProgress());
|
||
currentLevel = {
|
||
id: "word-mode",
|
||
charset: letters,
|
||
generate: "fixed",
|
||
text: word.en,
|
||
meaning: word.zh,
|
||
caseSensitive: true,
|
||
passAccuracy: 0,
|
||
passKpm: null,
|
||
practiceTitle: "背单词",
|
||
hint: "屏幕上一次只打这一个单词。打完它会消失,下一个马上出现。",
|
||
fingerLegendHTML: "已学按键:" + letters.join(" ").toUpperCase()
|
||
};
|
||
engine = new Engine(currentLevel);
|
||
els.title.textContent = "背单词";
|
||
els.hint.textContent = currentLevel.hint;
|
||
els.legend.innerHTML = currentLevel.fingerLegendHTML;
|
||
els.wordMeaning.textContent = word.zh;
|
||
els.wordMeaning.classList.remove("hidden");
|
||
els.ime.classList.add("hidden");
|
||
els.mask.classList.add("hidden");
|
||
buildKeyboard(letters);
|
||
renderText();
|
||
highlightKeyboard();
|
||
updateHud();
|
||
els.speak.classList.remove("hidden");
|
||
speakWord(word.en);
|
||
}
|
||
function finishWord() {
|
||
if (engine) {
|
||
saveDailyStats({
|
||
elapsedMs: engine.elapsedMs(),
|
||
correctHits: engine.correctHits,
|
||
wrongHits: engine.wrongHits
|
||
});
|
||
if (wordSession) {
|
||
wordSession.correctHits += engine.correctHits;
|
||
wordSession.wrongHits += engine.wrongHits;
|
||
}
|
||
}
|
||
var next = pickNextWord();
|
||
if (!next) {
|
||
showToast("没有更多符合条件的单词了。");
|
||
return;
|
||
}
|
||
showWord(next);
|
||
}
|
||
function anyModalOpen() {
|
||
return [els.mask, els.statsMask, els.wordsMask, els.booksMask].some(function (el) {
|
||
return el && !el.classList.contains("hidden");
|
||
});
|
||
}
|
||
function syncPageScrollLock() {
|
||
var open = anyModalOpen();
|
||
if (open) {
|
||
if (!document.body.classList.contains("modal-lock")) {
|
||
lockedScrollY = window.scrollY || document.documentElement.scrollTop || 0;
|
||
document.documentElement.classList.add("modal-lock");
|
||
document.body.classList.add("modal-lock");
|
||
document.body.style.top = "-" + lockedScrollY + "px";
|
||
}
|
||
} else if (document.body.classList.contains("modal-lock")) {
|
||
document.documentElement.classList.remove("modal-lock");
|
||
document.body.classList.remove("modal-lock");
|
||
document.body.style.top = "";
|
||
window.scrollTo(0, lockedScrollY);
|
||
}
|
||
}
|
||
function trapMaskScroll(event) {
|
||
var panel = event.currentTarget.querySelector(".stats-panel, .modal");
|
||
if (!panel || (!panel.contains(event.target) && event.target !== panel)) {
|
||
event.preventDefault();
|
||
return;
|
||
}
|
||
var dy = event.deltaY;
|
||
var atTop = panel.scrollTop <= 0;
|
||
var atBottom = panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 1;
|
||
if ((dy < 0 && atTop) || (dy > 0 && atBottom)) event.preventDefault();
|
||
}
|
||
function openWords() {
|
||
els.wordsWarn.classList.add("hidden");
|
||
renderVoices();
|
||
saveSelectedVoice();
|
||
warmupVoices();
|
||
els.wordsMask.classList.remove("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
function closeWords() {
|
||
els.wordsMask.classList.add("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
function startWordMode() {
|
||
Sound.ensure();
|
||
saveSelectedVoice();
|
||
var progress = loadProgress();
|
||
var letters = practicedLetters(progress);
|
||
els.wordsWarn.classList.add("hidden");
|
||
if (letters.length < 9) {
|
||
els.wordsWarn.classList.remove("hidden");
|
||
return;
|
||
}
|
||
if (!selectedBooks(progress).length) {
|
||
showToast("请先选定词书范围。");
|
||
return;
|
||
}
|
||
wordPool = buildWordPool(progress);
|
||
if (!wordPool.length) {
|
||
showToast("当前已学按键和词书范围内没有合适的单词,请继续新学或换一批词书。");
|
||
return;
|
||
}
|
||
wordMode = true;
|
||
lastWordEn = "";
|
||
wordSession = { correctHits: 0, wrongHits: 0, startedAt: Date.now() };
|
||
closeWords();
|
||
closeStats();
|
||
showView("practice");
|
||
wordMode = true;
|
||
showWord(pickNextWord());
|
||
cancelAnimationFrame(rafId);
|
||
rafId = requestAnimationFrame(tick);
|
||
}
|
||
function renderBooks() {
|
||
var progress = loadProgress();
|
||
var selected = {};
|
||
(progress.wordBooks || []).forEach(function (id) { selected[id] = true; });
|
||
var books = window.WORD_BOOKS || [];
|
||
if (!books.length) {
|
||
els.bookList.innerHTML = '<div class="stats-empty">词书没有加载出来。请用文件夹里的 index.html 打开,不要只拷走这一页。</div>';
|
||
return;
|
||
}
|
||
els.bookList.innerHTML = books.map(function (book) {
|
||
return (
|
||
'<label class="book-item">' +
|
||
'<input type="checkbox" value="' + book.id + '"' + (selected[book.id] ? " checked" : "") + ">" +
|
||
"<span>" + book.name + " · " + book.words.length + " 词</span>" +
|
||
"</label>"
|
||
);
|
||
}).join("");
|
||
}
|
||
function openBooks() {
|
||
renderBooks();
|
||
els.booksMask.classList.remove("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
function closeBooks() {
|
||
els.booksMask.classList.add("hidden");
|
||
syncPageScrollLock();
|
||
}
|
||
function saveBooks() {
|
||
var ids = [];
|
||
els.bookList.querySelectorAll("input[type=checkbox]").forEach(function (box) {
|
||
if (box.checked) ids.push(box.value);
|
||
});
|
||
if (!ids.length) {
|
||
showToast("至少勾选一本词书。");
|
||
return;
|
||
}
|
||
var data = loadProgress();
|
||
data.wordBooks = ids;
|
||
saveProgress(data);
|
||
closeBooks();
|
||
showToast("已保存词书范围");
|
||
}
|
||
|
||
[els.mask, els.statsMask, els.wordsMask, els.booksMask].forEach(function (mask) {
|
||
mask.addEventListener("wheel", trapMaskScroll, { passive: false });
|
||
mask.addEventListener("touchmove", function (event) {
|
||
var panel = mask.querySelector(".stats-panel, .modal");
|
||
if (!panel || (!panel.contains(event.target) && event.target !== panel)) {
|
||
event.preventDefault();
|
||
}
|
||
}, { passive: false });
|
||
});
|
||
document.getElementById("btn-stats").addEventListener("click", openStats);
|
||
document.getElementById("btn-stats-close").addEventListener("click", closeStats);
|
||
els.statsMask.addEventListener("click", function (event) {
|
||
if (event.target === els.statsMask) closeStats();
|
||
});
|
||
document.getElementById("btn-words").addEventListener("click", openWords);
|
||
els.voiceList.addEventListener("change", saveSelectedVoice);
|
||
document.getElementById("btn-speak").addEventListener("click", function () {
|
||
if (!wordMode || !lastWordEn) return;
|
||
speakWord(lastWordEn);
|
||
});
|
||
document.getElementById("btn-words-start").addEventListener("click", startWordMode);
|
||
document.getElementById("btn-words-back").addEventListener("click", closeWords);
|
||
document.getElementById("btn-words-books").addEventListener("click", openBooks);
|
||
document.getElementById("btn-books-save").addEventListener("click", saveBooks);
|
||
document.getElementById("btn-books-cancel").addEventListener("click", closeBooks);
|
||
els.wordsMask.addEventListener("click", function (event) {
|
||
if (event.target === els.wordsMask) closeWords();
|
||
});
|
||
els.booksMask.addEventListener("click", function (event) {
|
||
if (event.target === els.booksMask) closeBooks();
|
||
});
|
||
document.getElementById("btn-back").addEventListener("click", function () {
|
||
engine = null;
|
||
wordMode = false;
|
||
stopSpeak();
|
||
showView("select");
|
||
renderSelect();
|
||
});
|
||
document.getElementById("btn-retry").addEventListener("click", function () { startLevel(currentLevel.id); });
|
||
document.getElementById("btn-next").addEventListener("click", function () {
|
||
var nextId = this.dataset.next;
|
||
if (nextId && LEVELS[nextId]) startLevel(nextId);
|
||
});
|
||
document.getElementById("btn-to-select").addEventListener("click", function () {
|
||
engine = null;
|
||
stopSpeak();
|
||
showView("select");
|
||
renderSelect();
|
||
});
|
||
window.addEventListener("keydown", function (event) {
|
||
if (currentView !== "practice" || !engine) return;
|
||
if (engine.state === "finished") return;
|
||
if (!event.metaKey && !event.ctrlKey && !event.altKey) {
|
||
if (["Shift", "CapsLock", "Control", "Alt", "Meta"].indexOf(event.key) === -1) event.preventDefault();
|
||
}
|
||
var result = engine.handleKey(event.key, event);
|
||
if (result.type === "ime") { showIme(); return; }
|
||
if (result.type === "ignore") return;
|
||
renderText();
|
||
highlightKeyboard();
|
||
updateHud();
|
||
if (result.type === "correct") { flashKey(result.target, "ok"); Sound.ok(); }
|
||
if (result.type === "wrong") { flashKey(result.target, "bad"); Sound.bad(); }
|
||
if (result.finished) finishRun();
|
||
});
|
||
window.addEventListener("compositionstart", function () {
|
||
if (currentView === "practice") showIme();
|
||
});
|
||
window.addEventListener("pointerdown", function () { Sound.ensure(); }, { once: true });
|
||
|
||
warmupVoices();
|
||
renderSelect();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|