英语单词 Web Page

文件结构

文件夹

word.html

data.js

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>考研英语单词背诵</title>
    <script src="data.js"></script>
    <style>
        /* ===== 全局重置 & 基础 ===== */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', 'PingFang SC', Roboto, 'Helvetica Neue', sans-serif;
            background: #eef5ff;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 24px;
        }

        .app-container {
            width: 1400px;
            max-width: 100%;
            background: #ffffff;
            border-radius: 32px;
            box-shadow: 0 20px 60px rgba(44, 107, 255, 0.15);
            overflow: hidden;
            display: flex;
            flex-direction: column;
        }

        /* ===== 顶部栏 ===== */
        .header {
            background: linear-gradient(135deg, #2b6cff 0%, #1a4fc3 100%);
            padding: 20px 36px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 12px;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 16px;
        }

        .header-left h1 {
            color: #fff;
            font-size: 26px;
            font-weight: 700;
            letter-spacing: 0.5px;
        }

        .header-left .badge {
            background: rgba(255, 255, 255, 0.2);
            color: #fff;
            padding: 4px 14px;
            border-radius: 20px;
            font-size: 14px;
            font-weight: 500;
            backdrop-filter: blur(4px);
        }

        .header-right {
            display: flex;
            align-items: center;
            gap: 24px;
            color: rgba(255, 255, 255, 0.9);
            font-size: 15px;
        }

        .header-right .progress-text {
            background: rgba(255, 255, 255, 0.15);
            padding: 6px 18px;
            border-radius: 20px;
            font-weight: 500;
        }

        .header-right .mastery-count {
            background: rgba(255, 255, 255, 0.15);
            padding: 6px 18px;
            border-radius: 20px;
            font-weight: 500;
        }

        /* ===== 主体 ===== */
        .main-area {
            display: flex;
            flex: 1;
            min-height: 600px;
        }

        /* ===== 学习区 ===== */
        .learning-area {
            flex: 1;
            padding: 40px 44px 32px;
            display: flex;
            flex-direction: column;
            background: #fafcff;
            border-right: 1px solid #e8eff9;
            min-width: 0;
        }

        .word-display {
            text-align: center;
            padding: 8px 0 20px;
        }

        .word-display .part-of-speech {
            font-size: 22px;
            font-weight: 600;
            color: #2b6cff;
            background: #e8f0ff;
            padding: 4px 18px;
            border-radius: 30px;
            display: inline-block;
            margin-bottom: 8px;
        }

        .word-display .meaning {
            font-size: 38px;
            font-weight: 700;
            color: #1a2a4a;
            display: block;
            margin-top: 4px;
            letter-spacing: 1px;
        }

        /* 字母输入区 */
        .input-area {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin: 16px 0 8px;
            padding: 20px 0;
            position: relative;
        }

        .letter-container {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            justify-content: center;
            padding: 12px 20px;
            background: #f5f9ff;
            border-radius: 20px;
            border: 2px dashed #c8daf5;
            min-height: 80px;
            align-items: center;
            cursor: text;
            transition: border-color 0.25s, background 0.25s;
            width: 100%;
            max-width: 700px;
        }

        .letter-container:focus-within {
            border-color: #2b6cff;
            background: #f0f6ff;
            box-shadow: 0 0 0 4px rgba(43, 108, 255, 0.08);
        }

        .letter-container .letter-slot {
            display: inline-flex;
            justify-content: center;
            align-items: center;
            width: 44px;
            height: 56px;
            font-size: 30px;
            font-weight: 700;
            color: #1a2a4a;
            background: #ffffff;
            border-radius: 10px;
            border: 2px solid #d4e4ff;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
            transition: all 0.15s;
            font-family: 'Courier New', monospace;
            text-transform: lowercase;
        }

        .letter-container .letter-slot.filled {
            border-color: #2b6cff;
            background: #eaf2ff;
            color: #1a3a8a;
        }

        .letter-container .letter-slot.correct-slot {
            border-color: #22c55e;
            background: #e6f9ec;
            color: #16a34a;
        }

        .letter-container .letter-slot.wrong-slot {
            border-color: #ef4444;
            background: #fee9e9;
            color: #dc2626;
            animation: shake 0.3s ease;
        }

        @keyframes shake {
            0%,
            100% {
                transform: translateX(0);
            }
            25% {
                transform: translateX(-6px);
            }
            75% {
                transform: translateX(6px);
            }
        }

        #hiddenInput {
            position: absolute;
            opacity: 0;
            width: 0;
            height: 0;
            pointer-events: none;
        }

        .input-hint {
            margin-top: 12px;
            font-size: 14px;
            color: #8899bb;
            letter-spacing: 0.3px;
            user-select: none;
        }

        .input-hint kbd {
            background: #eef3fc;
            padding: 2px 10px;
            border-radius: 6px;
            font-size: 13px;
            border: 1px solid #d4e0f0;
            color: #2b4a7a;
            font-weight: 600;
        }

        /* 例句区域 */
        .example-area {
            margin: 12px 0 16px;
            padding: 16px 24px;
            background: #f5f9ff;
            border-radius: 16px;
            border-left: 5px solid #2b6cff;
            min-height: 70px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            transition: all 0.3s;
            opacity: 0;
            transform: translateY(8px);
            max-width: 700px;
            align-self: center;
            width: 100%;
        }

        .example-area.visible {
            opacity: 1;
            transform: translateY(0);
        }

        .example-area .example-en {
            font-size: 18px;
            color: #1a2a4a;
            font-weight: 500;
            line-height: 1.6;
        }

        .example-area .example-zh {
            font-size: 16px;
            color: #5a6f8a;
            margin-top: 4px;
            line-height: 1.5;
        }

        .example-area .example-label {
            font-size: 13px;
            color: #2b6cff;
            font-weight: 600;
            margin-bottom: 4px;
            letter-spacing: 0.5px;
        }

        /* 控制按钮 */
        .controls {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 12px;
            margin-top: 20px;
            padding-top: 16px;
            border-top: 1px solid #e8eff9;
        }

        .controls button {
            padding: 10px 24px;
            border: none;
            border-radius: 30px;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            display: inline-flex;
            align-items: center;
            gap: 6px;
            background: #f0f6ff;
            color: #1a3a6b;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
        }

        .controls button:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(43, 108, 255, 0.15);
        }

        .controls button:active {
            transform: scale(0.96);
        }

        .controls .btn-primary {
            background: #2b6cff;
            color: #fff;
        }

        .controls .btn-primary:hover {
            background: #1a5ae0;
            box-shadow: 0 8px 24px rgba(43, 108, 255, 0.30);
        }

        .controls .btn-outline {
            background: transparent;
            border: 2px solid #c8daf5;
            color: #2b4a7a;
        }

        .controls .btn-outline:hover {
            border-color: #2b6cff;
            background: #f0f6ff;
        }

        .controls .btn-warning {
            background: #f59e0b;
            color: #fff;
        }

        .controls .btn-warning:hover {
            background: #d97706;
            box-shadow: 0 8px 24px rgba(245, 158, 11, 0.30);
        }

        .controls .btn-success {
            background: #22c55e;
            color: #fff;
        }

        .controls .btn-success:hover {
            background: #16a34a;
            box-shadow: 0 8px 24px rgba(34, 197, 94, 0.30);
        }

        /* ===== 单词列表 ===== */
        .word-list-area {
            width: 320px;
            min-width: 260px;
            background: #fafcff;
            display: flex;
            flex-direction: column;
            padding: 20px 16px 16px;
            flex-shrink: 0;
        }

        .word-list-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-bottom: 12px;
            border-bottom: 2px solid #e8eff9;
            margin-bottom: 12px;
            flex-shrink: 0;
        }

        .word-list-header .list-title {
            font-size: 17px;
            font-weight: 700;
            color: #1a2a4a;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .word-list-header .list-title span {
            background: #2b6cff;
            color: #fff;
            font-size: 12px;
            padding: 1px 10px;
            border-radius: 20px;
            font-weight: 600;
        }

        .word-list-header .page-info {
            font-size: 14px;
            color: #6a7f9a;
            font-weight: 500;
        }

        .word-list-body {
            flex: 1;
            overflow-y: auto;
            padding-right: 4px;
            min-height: 0;
        }

        .word-list-body::-webkit-scrollbar {
            width: 4px;
        }

        .word-list-body::-webkit-scrollbar-track {
            background: #eef3fc;
            border-radius: 10px;
        }

        .word-list-body::-webkit-scrollbar-thumb {
            background: #c8daf5;
            border-radius: 10px;
        }

        .word-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 8px 14px;
            margin-bottom: 4px;
            border-radius: 12px;
            font-size: 15px;
            font-weight: 500;
            color: #1a2a4a;
            transition: all 0.15s;
            cursor: default;
            border: 1px solid transparent;
        }

        .word-item .word-text {
            font-weight: 600;
            font-size: 16px;
            color: #1a2a4a;
        }

        .word-item .word-status {
            font-size: 14px;
            opacity: 0.7;
        }

        .word-item.current {
            background: #e8f0ff;
            border-color: #2b6cff;
            box-shadow: 0 2px 8px rgba(43, 108, 255, 0.08);
        }

        .word-item.current .word-text {
            color: #1a4fc3;
        }

        .word-item.mastered {
            background: #e6f9ec;
            border-color: #86d9a4;
        }

        .word-item.mastered .word-text {
            color: #16a34a;
        }

        .word-item.mastered .word-status {
            color: #22c55e;
        }

        .word-list-footer {
            display: flex;
            justify-content: space-between;
            gap: 8px;
            padding-top: 12px;
            border-top: 2px solid #e8eff9;
            margin-top: 12px;
            flex-shrink: 0;
        }

        .word-list-footer button {
            flex: 1;
            padding: 8px 0;
            border: none;
            border-radius: 30px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            background: #f0f6ff;
            color: #1a3a6b;
            transition: all 0.2s;
        }

        .word-list-footer button:hover {
            background: #e0ebff;
            transform: translateY(-1px);
        }

        .word-list-footer button:active {
            transform: scale(0.96);
        }

        .word-list-footer button:disabled {
            opacity: 0.4;
            cursor: not-allowed;
            transform: none;
        }

        /* ===== 完成模态框 ===== */
        .completion-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            backdrop-filter: blur(8px);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 9999;
            animation: fadeInOverlay 0.5s ease;
        }

        .completion-overlay.active {
            display: flex;
        }

        @keyframes fadeInOverlay {
            from {
                opacity: 0;
            }
            to {
                opacity: 1;
            }
        }

        .completion-card {
            background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%);
            border-radius: 48px;
            padding: 56px 64px;
            max-width: 550px;
            width: 90%;
            text-align: center;
            box-shadow: 0 40px 80px rgba(43, 108, 255, 0.3);
            position: relative;
            animation: popIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            border: 2px solid rgba(43, 108, 255, 0.2);
        }

        @keyframes popIn {
            0% {
                transform: scale(0.5);
                opacity: 0;
            }
            100% {
                transform: scale(1);
                opacity: 1;
            }
        }

        .completion-card .emoji-big {
            font-size: 72px;
            display: block;
            margin-bottom: 8px;
        }

        .completion-card h2 {
            font-size: 34px;
            color: #1a2a4a;
            margin: 16px 0 8px;
            font-weight: 800;
        }

        .completion-card p {
            font-size: 18px;
            color: #4a5f7a;
            margin: 8px 0 24px;
            line-height: 1.6;
        }

        .completion-card .btn-celebrate {
            background: linear-gradient(135deg, #2b6cff, #1a4fc3);
            color: #fff;
            border: none;
            padding: 14px 44px;
            border-radius: 60px;
            font-size: 18px;
            font-weight: 700;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 10px 30px rgba(43, 108, 255, 0.3);
        }

        .completion-card .btn-celebrate:hover {
            transform: translateY(-3px) scale(1.02);
            box-shadow: 0 16px 40px rgba(43, 108, 255, 0.4);
        }

        /* ===== 五彩纸屑 ===== */
        .confetti-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 10000;
            overflow: hidden;
        }

        .confetti-piece {
            position: absolute;
            width: 12px;
            height: 12px;
            opacity: 0.9;
            animation: confettiFall linear forwards;
        }

        @keyframes confettiFall {
            0% {
                transform: translateY(-20vh) rotate(0deg) scale(1);
                opacity: 1;
            }
            100% {
                transform: translateY(110vh) rotate(720deg) scale(0.5);
                opacity: 0;
            }
        }

        /* ===== 响应式 ===== */
        @media (max-width: 1024px) {
            .main-area {
                flex-direction: column;
            }
            .word-list-area {
                width: 100%;
                min-width: 0;
                border-top: 1px solid #e8eff9;
                max-height: 280px;
            }
            .learning-area {
                border-right: none;
                padding: 28px 24px 20px;
            }
            .letter-container .letter-slot {
                width: 36px;
                height: 48px;
                font-size: 24px;
            }
            .word-display .meaning {
                font-size: 30px;
            }
        }

        @media (max-width: 600px) {
            .header {
                padding: 14px 18px;
            }
            .header-left h1 {
                font-size: 20px;
            }
            .header-right .progress-text,
            .header-right .mastery-count {
                font-size: 12px;
                padding: 4px 12px;
            }
            .learning-area {
                padding: 20px 16px 16px;
            }
            .word-display .meaning {
                font-size: 26px;
            }
            .word-display .part-of-speech {
                font-size: 18px;
            }
            .letter-container {
                gap: 6px;
                padding: 10px 12px;
            }
            .letter-container .letter-slot {
                width: 30px;
                height: 40px;
                font-size: 20px;
            }
            .controls button {
                padding: 8px 16px;
                font-size: 13px;
            }
            .example-area .example-en {
                font-size: 16px;
            }
            .word-list-area {
                max-height: 200px;
                padding: 12px 12px 12px;
            }
            .word-item {
                font-size: 13px;
                padding: 6px 10px;
            }
            .completion-card {
                padding: 40px 28px;
            }
            .completion-card h2 {
                font-size: 28px;
            }
        }

        .fade-in {
            animation: fadeIn 0.3s ease;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .celebration {
            animation: pop 0.4s ease;
        }

        @keyframes pop {
            0% {
                transform: scale(0.9);
                opacity: 0.5;
            }
            50% {
                transform: scale(1.05);
            }
            100% {
                transform: scale(1);
                opacity: 1;
            }
        }
    </style>
</head>
<body>

    <div class="app-container">
        <header class="header">
            <div class="header-left">
                <h1>📖 考研英语</h1>
                <span class="badge">单词背诵</span>
            </div>
            <div class="header-right">
                <span class="progress-text" id="progressText">第 1 / 22 个</span>
                <span class="mastery-count" id="masteryCount">✅ 已掌握 0</span>
            </div>
        </header>

        <div class="main-area">
            <!-- 学习区 -->
            <section class="learning-area">
                <div class="word-display">
                    <span class="part-of-speech" id="partOfSpeech">v.</span>
                    <span class="meaning" id="meaning">放弃,遗弃</span>
                </div>

                <div class="input-area">
                    <div class="letter-container" id="letterContainer" tabindex="0">
                        <!-- 由 JS 动态生成 -->
                    </div>
                    <input type="text" id="hiddenInput" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
                    <div class="input-hint">
                        输入单词 &nbsp;·&nbsp; <kbd>Enter</kbd> 确认 &nbsp;·&nbsp; <kbd>⌫</kbd> 删除
                    </div>
                </div>

                <div class="example-area" id="exampleArea">
                    <div class="example-label">📌 例句</div>
                    <div class="example-en" id="exampleEn">He had to abandon his car in the snow.</div>
                    <div class="example-zh" id="exampleZh">他不得不把车丢弃在雪地里。</div>
                </div>

                <div class="controls">
                    <button class="btn-outline" id="prevBtn">◀ 上一个</button>
                    <button class="btn-outline" id="nextBtn">下一个 ▶</button>
                    <button class="btn-success" id="showAnswerBtn">👁️ 显示答案</button>
                    <button class="btn-warning" id="resetBtn">🔄 重新开始</button>
                    <button class="btn-primary" id="shuffleBtn">🎲 打乱顺序</button>
                </div>
            </section>

            <!-- 单词列表 -->
            <aside class="word-list-area">
                <div class="word-list-header">
                    <div class="list-title">
                        📋 单词一览
                        <span id="totalCount">22</span>
                    </div>
                    <span class="page-info" id="pageInfo">1 / 3</span>
                </div>

                <div class="word-list-body" id="wordListBody">
                    <!-- 由 JS 动态生成 -->
                </div>

                <div class="word-list-footer">
                    <button id="prevPageBtn">◀ 上一页</button>
                    <button id="nextPageBtn">下一页 ▶</button>
                </div>
            </aside>
        </div>
    </div>

    <!-- ===== 完成模态框 ===== -->
    <div class="completion-overlay" id="completionOverlay">
        <div class="completion-card">
            <span class="emoji-big">🎉</span>
            <h2>太棒了!全部完成!</h2>
            <p>你已掌握所有 <strong id="totalWordsDisplay">22</strong> 个考研单词,<br />继续加油,胜利就在前方!</p>
            <button class="btn-celebrate" id="celebrationRestartBtn">🚀 重新挑战</button>
        </div>
    </div>

    <div class="confetti-container" id="confettiContainer"></div>

    <script>
        // ================================================================
        //  主逻辑
        // ================================================================

        const allWords = window.wordData || [];

        const state = {
            words: [],
            currentIndex: 0,
            currentInput: [],
            isCorrect: false,
            mastered: new Set(),
            pageSize: 10,             // ✅ 已改为每页10个
            currentPage: 0,
            totalPages: 1,
            isWaitingForNext: false,
            isShuffled: false,
            completionShown: false,
            showingAnswer: false,
        };

        // ---------- DOM ----------
        const $ = (id) => document.getElementById(id);
        const letterContainer = $('letterContainer');
        const hiddenInput = $('hiddenInput');
        const partOfSpeech = $('partOfSpeech');
        const meaning = $('meaning');
        const exampleArea = $('exampleArea');
        const exampleEn = $('exampleEn');
        const exampleZh = $('exampleZh');
        const progressText = $('progressText');
        const masteryCount = $('masteryCount');
        const wordListBody = $('wordListBody');
        const pageInfo = $('pageInfo');
        const totalCount = $('totalCount');
        const prevBtn = $('prevBtn');
        const nextBtn = $('nextBtn');
        const resetBtn = $('resetBtn');
        const shuffleBtn = $('shuffleBtn');
        const showAnswerBtn = $('showAnswerBtn');
        const prevPageBtn = $('prevPageBtn');
        const nextPageBtn = $('nextPageBtn');
        const completionOverlay = $('completionOverlay');
        const totalWordsDisplay = $('totalWordsDisplay');
        const celebrationRestartBtn = $('celebrationRestartBtn');
        const confettiContainer = $('confettiContainer');

        // ---------- 初始化 ----------
        function init() {
            if (!allWords.length) {
                letterContainer.innerHTML = `<div class="empty-state">⚠️ 请确保 data.js 已加载,且包含有效的单词数据。</div>`;
                return;
            }
            state.words = [...allWords];
            state.totalPages = Math.ceil(state.words.length / state.pageSize);
            totalCount.textContent = state.words.length;
            totalWordsDisplay.textContent = state.words.length;
            state.currentIndex = 0;
            state.mastered = new Set();
            state.isShuffled = false;
            state.isWaitingForNext = false;
            state.currentPage = 0;
            state.completionShown = false;
            state.showingAnswer = false;
            completionOverlay.classList.remove('active');
            confettiContainer.innerHTML = '';
            renderAll();
            focusInput();
        }

        // ---------- 渲染 ----------
        function renderAll() {
            renderWord();
            renderLetterSlots();
            renderWordList();
            renderPageInfo();
            renderProgress();
            renderMastery();
        }

        function renderWord() {
            const word = getCurrentWord();
            if (!word) return;
            partOfSpeech.textContent = word.partOfSpeech || '';
            meaning.textContent = word.meaning || '';
            if (state.isCorrect || state.showingAnswer) {
                exampleEn.textContent = word.example || '';
                exampleZh.textContent = word.exampleTranslation || '';
                exampleArea.classList.add('visible');
            } else {
                exampleArea.classList.remove('visible');
            }
        }

        function renderLetterSlots() {
            const word = getCurrentWord();
            if (!word) {
                letterContainer.innerHTML = '';
                return;
            }
            const wordLen = word.word.length;
            const input = state.currentInput;
            const isCorrect = state.isCorrect;
            const showing = state.showingAnswer;

            let html = '';
            for (let i = 0; i < wordLen; i++) {
                let char = '';
                let cls = 'letter-slot';
                if (showing) {
                    char = word.word[i];
                    cls += ' correct-slot filled';
                } else if (isCorrect) {
                    char = word.word[i];
                    cls += ' correct-slot filled';
                } else {
                    char = input[i] || '';
                    if (char) cls += ' filled';
                }
                html += `<span class="${cls}" data-index="${i}">${char || '_'}</span>`;
            }
            letterContainer.innerHTML = html;
        }

        function renderWordList() {
            const start = state.currentPage * state.pageSize;
            const end = Math.min(start + state.pageSize, state.words.length);
            const pageWords = state.words.slice(start, end);
            const currentIdx = state.currentIndex;

            if (!pageWords.length) {
                wordListBody.innerHTML = `<div class="empty-state">暂无单词</div>`;
                return;
            }

            let html = '';
            pageWords.forEach((w, idx) => {
                const globalIdx = start + idx;
                const isCurrent = globalIdx === currentIdx;
                const isMastered = state.mastered.has(globalIdx);
                let cls = 'word-item';
                if (isCurrent) cls += ' current';
                if (isMastered) cls += ' mastered';
                const statusIcon = isMastered ? '✅' : '○';
                html += `
                    <div class="${cls}" data-index="${globalIdx}">
                        <span class="word-text">${globalIdx + 1}</span>
                        <span class="word-status">${statusIcon}</span>
                    </div>
                `;
            });
            wordListBody.innerHTML = html;

            wordListBody.querySelectorAll('.word-item').forEach((el) => {
                el.addEventListener('click', () => {
                    const idx = parseInt(el.dataset.index, 10);
                    if (!isNaN(idx) && idx !== state.currentIndex) {
                        goToWord(idx);
                    }
                });
            });
        }

        function renderPageInfo() {
            const total = state.totalPages || 1;
            const current = state.currentPage + 1;
            pageInfo.textContent = `${current} / ${total}`;
            prevPageBtn.disabled = state.currentPage === 0;
            nextPageBtn.disabled = state.currentPage >= total - 1;
        }

        function renderProgress() {
            const total = state.words.length;
            const current = state.currentIndex + 1;
            progressText.textContent = `第 ${current} / ${total} 个`;
        }

        function renderMastery() {
            masteryCount.textContent = `✅ 已掌握 ${state.mastered.size}`;
        }

        // ---------- 工具 ----------
        function getCurrentWord() {
            return state.words[state.currentIndex] || null;
        }

        function focusInput() {
            hiddenInput.focus();
        }

        function goToWord(index) {
            if (index < 0 || index >= state.words.length) return;
            state.currentIndex = index;
            state.currentInput = [];
            state.isCorrect = false;
            state.isWaitingForNext = false;
            state.showingAnswer = false;
            const page = Math.floor(index / state.pageSize);
            if (page !== state.currentPage) {
                state.currentPage = page;
            }
            renderAll();
            focusInput();
        }

        // ---------- 检查答案 ----------
        function checkAnswer() {
            const word = getCurrentWord();
            if (!word) return;
            const userInput = state.currentInput.join('');
            const target = word.word.toLowerCase();
            if (userInput.toLowerCase() === target) {
                state.isCorrect = true;
                state.isWaitingForNext = true;
                state.mastered.add(state.currentIndex);
                state.showingAnswer = false;
                renderAll();
                letterContainer.classList.add('celebration');
                setTimeout(() => letterContainer.classList.remove('celebration'), 400);
                focusInput();

                if (state.mastered.size === state.words.length && !state.completionShown) {
                    state.completionShown = true;
                    showCompletionModal();
                }
            } else {
                state.currentInput = [];
                state.isCorrect = false;
                state.showingAnswer = false;
                renderAll();
                const slots = letterContainer.querySelectorAll('.letter-slot');
                slots.forEach((slot) => {
                    slot.classList.add('wrong-slot');
                    setTimeout(() => slot.classList.remove('wrong-slot'), 400);
                });
                focusInput();
            }
        }

        // ---------- 显示答案 ----------
        function showAnswer() {
            const word = getCurrentWord();
            if (!word) return;
            if (state.showingAnswer || state.isCorrect) return;
            state.showingAnswer = true;
            state.currentInput = [];
            renderAll();
            letterContainer.classList.add('celebration');
            setTimeout(() => letterContainer.classList.remove('celebration'), 400);
            focusInput();
        }

        // ---------- 完成模态框 ----------
        function showCompletionModal() {
            completionOverlay.classList.add('active');
            launchConfetti();
        }

        function launchConfetti() {
            const container = confettiContainer;
            container.innerHTML = '';
            const colors = ['#2b6cff', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
            const count = 80;
            for (let i = 0; i < count; i++) {
                const piece = document.createElement('div');
                piece.className = 'confetti-piece';
                const size = 6 + Math.random() * 12;
                const isCircle = Math.random() > 0.5;
                piece.style.width = isCircle ? size + 'px' : size * 0.6 + 'px';
                piece.style.height = isCircle ? size + 'px' : size * 1.2 + 'px';
                piece.style.borderRadius = isCircle ? '50%' : '2px';
                piece.style.background = colors[Math.floor(Math.random() * colors.length)];
                piece.style.left = Math.random() * 100 + '%';
                piece.style.animationDuration = 2 + Math.random() * 3 + 's';
                piece.style.animationDelay = Math.random() * 2 + 's';
                piece.style.transform = `rotate(${Math.random() * 360}deg)`;
                container.appendChild(piece);
            }
            setTimeout(() => {
                container.innerHTML = '';
            }, 5000);
        }

        // ---------- 导航 ----------
        function goToNextWord() {
            if (state.currentIndex < state.words.length - 1) {
                goToWord(state.currentIndex + 1);
            } else {
                goToWord(0);
            }
        }

        function goToPrevWord() {
            if (state.currentIndex > 0) {
                goToWord(state.currentIndex - 1);
            } else {
                goToWord(state.words.length - 1);
            }
        }

        function resetAll() {
            state.currentIndex = 0;
            state.currentInput = [];
            state.isCorrect = false;
            state.isWaitingForNext = false;
            state.mastered = new Set();
            state.currentPage = 0;
            state.completionShown = false;
            state.showingAnswer = false;
            completionOverlay.classList.remove('active');
            confettiContainer.innerHTML = '';
            if (state.isShuffled) {
                state.words = [...allWords];
                state.isShuffled = false;
            }
            renderAll();
            focusInput();
        }

        function shuffleWords() {
            const arr = [...state.words];
            for (let i = arr.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [arr[i], arr[j]] = [arr[j], arr[i]];
            }
            state.words = arr;
            state.isShuffled = true;
            state.currentIndex = 0;
            state.currentInput = [];
            state.isCorrect = false;
            state.isWaitingForNext = false;
            state.mastered = new Set();
            state.currentPage = 0;
            state.completionShown = false;
            state.showingAnswer = false;
            completionOverlay.classList.remove('active');
            confettiContainer.innerHTML = '';
            renderAll();
            focusInput();
        }

        // ---------- 键盘事件 ----------
        hiddenInput.addEventListener('keydown', (e) => {
            const word = getCurrentWord();
            if (!word) return;

            if (state.showingAnswer) {
                state.showingAnswer = false;
                state.currentInput = [];
                state.isCorrect = false;
                exampleArea.classList.remove('visible');
                renderAll();
                if (e.key.length === 1 && /[a-zA-Z]/.test(e.key)) {
                    e.preventDefault();
                    state.currentInput.push(e.key.toLowerCase());
                    renderLetterSlots();
                    focusInput();
                    return;
                } else if (e.key === 'Enter' || e.key === 'Backspace') {
                    e.preventDefault();
                    focusInput();
                    return;
                }
                return;
            }

            if (state.isWaitingForNext) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    state.isWaitingForNext = false;
                    state.isCorrect = false;
                    state.currentInput = [];
                    goToNextWord();
                }
                return;
            }

            if (e.key === 'Backspace') {
                e.preventDefault();
                if (state.currentInput.length > 0) {
                    state.currentInput.pop();
                    renderLetterSlots();
                    if (state.isCorrect) {
                        state.isCorrect = false;
                        exampleArea.classList.remove('visible');
                        renderWord();
                    }
                }
                return;
            }

            if (e.key === 'Enter') {
                e.preventDefault();
                if (state.currentInput.length === word.word.length) {
                    checkAnswer();
                } else {
                    state.currentInput = [];
                    renderLetterSlots();
                    focusInput();
                }
                return;
            }

            if (e.key.length === 1 && /[a-zA-Z]/.test(e.key)) {
                e.preventDefault();
                if (state.isCorrect) return;
                if (state.currentInput.length < word.word.length) {
                    state.currentInput.push(e.key.toLowerCase());
                    renderLetterSlots();
                }
            }
        });

        letterContainer.addEventListener('click', () => {
            focusInput();
        });

        document.addEventListener('click', (e) => {
            if (e.target.closest('.app-container')) {
                if (!e.target.closest('input') && !e.target.closest('button')) {
                    focusInput();
                }
            }
        });

        // ---------- 按钮绑定 ----------
        prevBtn.addEventListener('click', goToPrevWord);
        nextBtn.addEventListener('click', () => {
            if (state.isWaitingForNext) {
                state.isWaitingForNext = false;
                state.isCorrect = false;
                state.currentInput = [];
                goToNextWord();
            } else {
                goToNextWord();
            }
        });

        showAnswerBtn.addEventListener('click', showAnswer);

        resetBtn.addEventListener('click', resetAll);
        shuffleBtn.addEventListener('click', shuffleWords);

        prevPageBtn.addEventListener('click', () => {
            if (state.currentPage > 0) {
                state.currentPage--;
                renderWordList();
                renderPageInfo();
                const start = state.currentPage * state.pageSize;
                const end = start + state.pageSize;
                if (state.currentIndex < start || state.currentIndex >= end) {
                    goToWord(start);
                } else {
                    renderAll();
                }
            }
        });

        nextPageBtn.addEventListener('click', () => {
            const total = state.totalPages || 1;
            if (state.currentPage < total - 1) {
                state.currentPage++;
                renderWordList();
                renderPageInfo();
                const start = state.currentPage * state.pageSize;
                const end = start + state.pageSize;
                if (state.currentIndex < start || state.currentIndex >= end) {
                    goToWord(start);
                } else {
                    renderAll();
                }
            }
        });

        celebrationRestartBtn.addEventListener('click', resetAll);

        // ---------- 启动 ----------
        document.addEventListener('DOMContentLoaded', () => {
            if (typeof window.wordData === 'undefined') {
                letterContainer.innerHTML = `
                    <div class="empty-state" style="padding:30px 0;">
                        ⚠️ 未找到单词数据,请确保 data.js 已正确加载。<br />
                        <small style="color:#8899bb;">请将 data.js 放在同一目录下,并包含 window.wordData 数组。</small>
                    </div>
                `;
                return;
            }
            init();
        });

        window.addEventListener('focus', () => {
            setTimeout(focusInput, 100);
        });

        console.log('📚 考研英语单词背诵已启动!');
        console.log(`📝 共加载 ${allWords.length} 个单词。`);
    </script>

</body>
</html>
javascript 复制代码
// data.js
window.wordData = [
      // ----- 新增 12 个考研词汇 -----
    {
        "word": "autonomous",
        "partOfSpeech": "adj.",
        "meaning": "自治的,自主的",
        "example": "The region is an autonomous republic.",
        "exampleTranslation": "该地区是一个自治共和国。"
    },
    {
        "word": "colony",
        "partOfSpeech": "n.",
        "meaning": "殖民地,群体",
        "example": "The former colony gained independence in 1960.",
        "exampleTranslation": "这个前殖民地于1960年获得独立。"
    },
    {
        "word": "diplomacy",
        "partOfSpeech": "n.",
        "meaning": "外交,策略",
        "example": "Diplomacy is essential to avoid conflict.",
        "exampleTranslation": "外交是避免冲突的关键。"
    },
    {
        "word": "independent",
        "partOfSpeech": "adj.",
        "meaning": "独立的,自主的",
        "example": "She is financially independent.",
        "exampleTranslation": "她在经济上独立。"
    },
    {
        "word": "self-reliant",
        "partOfSpeech": "adj.",
        "meaning": "自立的,自力更生的",
        "example": "The community is self-reliant in food production.",
        "exampleTranslation": "这个社区在粮食生产上自给自足。"
    },
    {
        "word": "self-sufficient",
        "partOfSpeech": "adj.",
        "meaning": "自给自足的",
        "example": "The country aims to be self-sufficient in energy.",
        "exampleTranslation": "该国力求能源自给自足。"
    },
    {
        "word": "sovereign",
        "partOfSpeech": "adj./n.",
        "meaning": "至高无上的,主权的;君主",
        "example": "The nation is a sovereign state.",
        "exampleTranslation": "该国是一个主权国家。"
    },
    {
        "word": "reign",
        "partOfSpeech": "v./n.",
        "meaning": "统治,支配;统治时期",
        "example": "The king reigned for over forty years.",
        "exampleTranslation": "这位国王统治了四十多年。"
    },
    {
        "word": "conservative",
        "partOfSpeech": "adj.",
        "meaning": "保守的,守旧的",
        "example": "He holds conservative views on social issues.",
        "exampleTranslation": "他在社会问题上持保守观点。"
    },
    {
        "word": "democracy",
        "partOfSpeech": "n.",
        "meaning": "民主,民主制度",
        "example": "Democracy allows citizens to vote.",
        "exampleTranslation": "民主制度允许公民投票。"
    },
    {
        "word": "liberal",
        "partOfSpeech": "adj.",
        "meaning": "自由的,开明的;自由主义的",
        "example": "She has a liberal attitude toward education.",
        "exampleTranslation": "她对教育持开明态度。"
    },
    {
        "word": "republic",
        "partOfSpeech": "n.",
        "meaning": "共和国,共和政体",
        "example": "France is a republic.",
        "exampleTranslation": "法国是一个共和国。"
    }
];
相关推荐
随风一样自由1 小时前
【前端+Vitest测试框架】Vitest | Playwright | WebdriverIO | Preview
前端·前端框架·可用性测试
前端之虎陈随易10 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he10 小时前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen11 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒11 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
大圣编程12 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang13 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆13 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜14 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端