<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>舒尔特方格训练</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: #f5f5f5; min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; user-select: none; } .container { width: 100%; max-width: 520px; background: #fff; border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,.06); padding: 32px 28px; } .header { text-align: center; margin-bottom: 24px; } .header h1 { font-size: 22px; font-weight: 700; color: #1a1a1a; letter-spacing: .5px; } .controls { display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; } .size-selector { display: flex; gap: 4px; background: #f0f0f0; border-radius: 10px; padding: 4px; } .size-btn { padding: 6px 16px; border: none; background: transparent; border-radius: 8px; font-size: 15px; font-weight: 500; color: #666; cursor: pointer; transition: all .18s; } .size-btn:hover { color: #333; } .size-btn.active { background: #fff; color: #1a1a1a; font-weight: 600; box-shadow: 0 1px 4px rgba(0,0,0,.08); } .restart-btn { padding: 7px 20px; border: 1.5px solid #ddd; background: #fff; border-radius: 8px; font-size: 14px; font-weight: 500; color: #444; cursor: pointer; transition: all .18s; } .restart-btn:hover { border-color: #bbb; background: #fafafa; } .status-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; padding: 0 4px; } .target-info { font-size: 15px; color: #555; } .target-info strong { font-size: 22px; color: #e53935; margin: 0 4px; } .timer { font-size: 28px; font-weight: 700; color: #1a1a1a; font-variant-numeric: tabular-nums; letter-spacing: 1px; } .grid-wrapper { display: flex; justify-content: center; } .grid { display: grid; gap: 6px; width: 100%; max-width: 400px; aspect-ratio: 1 / 1; } .cell { display: flex; align-items: center; justify-content: center; background: #f8f9fa; border: 1.5px solid #e8e8e8; border-radius: 10px; font-size: 24px; font-weight: 700; color: #2c2c2c; cursor: pointer; transition: all .12s; aspect-ratio: 1 / 1; position: relative; } .cell:hover:not(.done):not(.wrong-flash) { background: #eef1f5; transform: scale(1.03); } .cell:active:not(.done) { transform: scale(.95); } .cell.done { background: #e8e8e8; color: #b0b0b0; border-color: #e0e0e0; cursor: default; pointer-events: none; } .cell.wrong-flash { animation: shake .4s ease; background: #ffebee; border-color: #ef9a9a; } @keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-5px); } 40% { transform: translateX(5px); } 60% { transform: translateX(-4px); } 80% { transform: translateX(4px); } } .grid.size-3 .cell { font-size: 32px; } .grid.size-4 .cell { font-size: 26px; } .grid.size-5 .cell { font-size: 22px; } .grid.size-6 .cell { font-size: 18px; } .overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 100; justify-content: center; align-items: center; } .overlay.show { display: flex; } .modal { background: #fff; border-radius: 16px; padding: 32px 36px; text-align: center; box-shadow: 0 8px 40px rgba(0,0,0,.15); max-width: 340px; width: 90%; animation: popIn .3s ease; } @keyframes popIn { 0% { transform: scale(.9); opacity: 0; } 100% { transform: scale(1); opacity: 1; } } .modal .grade-icon { font-size: 48px; margin-bottom: 8px; } .modal h2 { font-size: 20px; color: #1a1a1a; margin-bottom: 8px; } .modal .time-big { font-size: 36px; font-weight: 800; color: #e53935; margin-bottom: 6px; font-variant-numeric: tabular-nums; } .modal .grade-tag { display: inline-block; padding: 4px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; margin-bottom: 18px; } .grade-tag.excellent { background: #e8f5e9; color: #2e7d32; } .grade-tag.medium { background: #fff8e1; color: #f9a825; } .grade-tag.poor { background: #ffebee; color: #c62828; } .modal button { padding: 10px 32px; border: none; background: #1a1a1a; color: #fff; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background .18s; } .modal button:hover { background: #333; } @media (max-width: 480px) { .container { padding: 20px 16px; border-radius: 12px; } .header h1 { font-size: 19px; } .size-btn { padding: 5px 12px; font-size: 14px; } .restart-btn { padding: 6px 14px; font-size: 13px; } .timer { font-size: 24px; } .grid { gap: 4px; } .cell { border-radius: 8px; } .grid.size-3 .cell { font-size: 26px; } .grid.size-4 .cell { font-size: 20px; } .grid.size-5 .cell { font-size: 18px; } .grid.size-6 .cell { font-size: 15px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>舒尔特方格训练</h1> </div> <div class="controls"> <div class="size-selector"> <button class="size-btn" data-size="3">3×3</button> <button class="size-btn active" data-size="5">5×5</button> <button class="size-btn" data-size="4">4×4</button> <button class="size-btn" data-size="6">6×6</button> </div> <button class="restart-btn" id="restartBtn">重新开始</button> </div> <div class="status-bar"> <div class="target-info">目标锛?#x3C;strong id="targetNum">1</strong></div> <div class="timer" id="timer">00.00</div> </div> <div class="grid-wrapper"> <div class="grid size-5" id="grid"></div> </div> </div> <div class="overlay" id="overlay"> <div class="modal"> <div class="grade-icon" id="gradeIcon"></div> <h2 id="gradeTitle"></h2> <div class="time-big" id="gradeTime"></div> <div class="grade-tag" id="gradeTag"></div> <button id="closeModal">再来一次</button> </div> </div> <script> (function() { const gridEl = document.getElementById('grid'); const timerEl = document.getElementById('timer'); const targetNumEl = document.getElementById('targetNum'); const restartBtn = document.getElementById('restartBtn'); const overlay = document.getElementById('overlay'); const closeModal = document.getElementById('closeModal'); const gradeIcon = document.getElementById('gradeIcon'); const gradeTitle = document.getElementById('gradeTitle'); const gradeTime = document.getElementById('gradeTime'); const gradeTag = document.getElementById('gradeTag'); const sizeBtns = document.querySelectorAll('.size-btn'); let gridSize = 5; let totalCells = 25; let numbers = []; let currentTarget = 1; let timerInterval = null; let startTime = null; let elapsedMs = 0; let started = false; let finished = false; const gradeMap = { 3: { excellent: 9, medium: 15 }, 4: { excellent: 16, medium: 26 }, 5: { excellent: 26, medium: 42 }, 6: { excellent: 36, medium: 54 } }; function shuffle(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } function stopTimer() { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } function formatTime(ms) { const sec = (ms / 1000).toFixed(2); return sec.toString().padStart(5, '0'); } function updateTimerDisplay() { if (!startTime) return; elapsedMs = Date.now() - startTime; timerEl.textContent = formatTime(elapsedMs); } function startTimer() { if (started || finished) return; started = true; startTime = Date.now(); timerEl.textContent = '00.00'; timerInterval = setInterval(updateTimerDisplay, 50); } function renderGrid() { gridEl.innerHTML = ''; gridEl.className = 'grid size-' + gridSize; gridEl.style.gridTemplateColumns = `repeat(${gridSize}, 1fr)`; gridEl.style.gridTemplateRows = `repeat(${gridSize}, 1fr)`; numbers = shuffle( Array.from({ length: totalCells }, (_, i) => i + 1) ); numbers.forEach(num => { const cell = document.createElement('div'); cell.className = 'cell'; cell.textContent = num; cell.dataset.num = num; cell.addEventListener('click', () => onCellClick(cell, num)); gridEl.appendChild(cell); }); } function onCellClick(cell, num) { if (finished) return; if (cell.classList.contains('done')) return; if (!started) startTimer(); if (num === currentTarget) { cell.classList.add('done'); currentTarget++; if (currentTarget > totalCells) { finishGame(); } else { targetNumEl.textContent = currentTarget; } } else { cell.classList.add('wrong-flash'); setTimeout(() => cell.classList.remove('wrong-flash'), 400); } } function finishGame() { finished = true; stopTimer(); elapsedMs = Date.now() - startTime; timerEl.textContent = formatTime(elapsedMs); targetNumEl.textContent = '✓'; const totalSec = elapsedMs / 1000; const g = gradeMap[gridSize]; let grade, icon, title, tagClass; if (totalSec <= g.excellent) { grade = '优秀'; icon = '馃専'; title = '非常出色锛?; tagClass = 'excellent'; } else if (totalSec <= g.medium) { grade = '中等'; icon = '馃憤'; title = '表现不错'; tagClass = 'medium'; } else { grade = '需加油'; icon = '馃挭'; title = '继续努力'; tagClass = 'poor'; } gradeIcon.textContent = icon; gradeTitle.textContent = title; gradeTime.textContent = totalSec.toFixed(2) + ' 秒'; gradeTag.textContent = (gridSize * gridSize) + '格 · ' + grade; gradeTag.className = 'grade-tag ' + tagClass; overlay.classList.add('show'); } function reset() { stopTimer(); started = false; finished = false; currentTarget = 1; elapsedMs = 0; startTime = null; timerEl.textContent = '00.00'; targetNumEl.textContent = '1'; overlay.classList.remove('show'); renderGrid(); } function setGridSize(size) { if (gridSize === size) return; gridSize = size; totalCells = size * size; sizeBtns.forEach(b => b.classList.toggle('active', Number(b.dataset.size) === size)); reset(); } sizeBtns.forEach(btn => { btn.addEventListener('click', () => setGridSize(Number(btn.dataset.size))); }); restartBtn.addEventListener('click', reset); closeModal.addEventListener('click', reset); renderGrid(); })(); </script> </body> </html>