Spaces:
Running
Running
create your touch typing game with a blank QWERTY keyboard and color-coded feedback: ```prompt Create a web-based touch typing game with these core features: 1. **Blank QWERTY Keyboard** - Render a standard QWERTY keyboard layout using CSS Grid - Absolutely NO labels/symbols on any keys - completely blank - Use 100% empty keycaps (no letters, numbers, or symbols) - Realistic key spacing and sizing matching physical keyboards 2. **Visual Feedback System** - When correct key pressed: highlight physical key position GREEN - When wrong key pressed: highlight pressed key position RED - Highlight duration: 300ms before fading to neutral state - Add smooth transition effect for color changes 3. **Game Interface** - Top section: • Target text (large font) • Progress bar below text - Center: Blank keyboard visualization - Bottom panel: • Timer (seconds with 2 decimals) • Error counter • Start/Restart button 4. **Core Game Logic** - Start button initializes game: • Random text selection from predefined array • Reset timer (00.00), errors (0), progress (0%) - Real-time typing detection: • Match physical key presses against target characters • Update progress bar percentage as user types - On completion: • Stop timer • Show modal with stats: time, errors, accuracy % • Enable restart button 5. **Technical Implementation** - Use this keyboard structure (no labels!): ```html <div class="keyboard"> <!-- Row 1 --> <div class="key" data-code="Backquote"></div> <div class="key" data-code="Digit1"></div> <!-- ... all keys ... --> <div class="key key-wide" data-code="Backspace"></div> <!-- Row 2 --> <div class="key key-wide" data-code="Tab"></div> <div class="key" data-code="KeyQ"></div> <!-- ... all keys ... --> <!-- Include all standard keys (Enter, Shift, Ctrl, Alt, Space, etc.) --> </div> ``` - Key data attributes should use KeyboardEvent.code values: • Letters: `KeyQ`, `KeyW`, `KeyE`... • Numbers: `Digit1`, `Digit2`... • Special: `Space`, `Enter`, `ShiftLeft`... - CSS requirements: ```css .keyboard { display: grid; grid-template-columns: repeat(15, 1fr); gap: 4px; max-width: 900px; } .key { aspect-ratio: 1/0.8; background: #2c3e50; border-radius: 5px; transition: background 0.3s ease; } .key.correct { background: #2ecc71; } .key.incorrect { background: #e74c3c; } /* Special key sizing */ .key-wide { grid-column: span 1.5; } .key-extra-wide { grid-column: span 2; } .key-space { grid-column: span 6; } ``` - JavaScript requirements: ```javascript // Key mapping example const keyMap = { 'KeyQ': document.querySelector('[data-code="KeyQ"]'), 'KeyW': document.querySelector('[data-code="KeyW"]'), // ... all keys }; // Highlight function function highlightKey(code, isCorrect) { const key = keyMap[code]; key.classList.remove('correct', 'incorrect'); void key.offsetWidth; // Trigger reflow key.classList.add(isCorrect ? 'correct' : 'incorrect'); setTimeout(() => { key.classList.remove('correct', 'incorrect'); }, 300); } // In keydown handler: document.addEventListener('keydown', (e) => { if (e.code in keyMap) { const isCorrect = (e.key.toLowerCase() === currentText[currentIndex]); highlightKey(e.code, isCorrect); // ... rest of game logic } }); ``` 6. **Additional Requirements** - Responsive design (keyboard scales down on mobile) - Dark theme as default - Smooth animations for all transitions - Error prevention for repeated key presses - Backspace support for error correction - Case-insensitive comparison - Sample text array: ```javascript const texts = [ "The quick brown fox jumps over the lazy dog", "Programming is the process of creating instructions", "Mastering touch typing improves productivity", "Blank keyboards challenge muscle memory", "Practice makes perfect in any skill" ]; ``` Generate complete HTML/CSS/JS implementation in a single file. ``` This prompt includes: 1. Strict blank keyboard requirement (no visible labels) 2. Precise QWERTY physical layout 3. Color-coded feedback system (green/red highlights) 4. Game state management 5. KeyboardEvent.code based mapping 6. Responsive design considerations 7. Performance optimizations (reflow trigger for animations) 8. Complete CSS specifications 9. JS implementation details 10. Sample content The keyboard will appear completely blank while providing visual feedback through temporary color changes when keys are pressed, maintaining your core game mechanic while delivering polished UX. - Initial Deployment
093e1bd verified | <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Слепая Печать</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| .keyboard { | |
| display: grid; | |
| grid-template-columns: repeat(10, 1fr); | |
| gap: 5px; | |
| margin: 20px auto; | |
| max-width: 800px; | |
| } | |
| .key { | |
| height: 50px; | |
| border: 2px solid #4b5563; | |
| border-radius: 5px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| } | |
| .key.correct { | |
| background-color: rgba(74, 222, 128, 0.3); | |
| border-color: rgb(74, 222, 128); | |
| } | |
| .key.incorrect { | |
| background-color: rgba(248, 113, 113, 0.3); | |
| border-color: rgb(248, 113, 113); | |
| } | |
| .key.wide { | |
| grid-column: span 2; | |
| } | |
| .key.extra-wide { | |
| grid-column: span 4; | |
| } | |
| .target-text { | |
| font-size: 1.5rem; | |
| letter-spacing: 1px; | |
| line-height: 2.5rem; | |
| } | |
| .current-char { | |
| position: relative; | |
| } | |
| .current-char::after { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| bottom: -2px; | |
| width: 100%; | |
| height: 2px; | |
| background-color: #3b82f6; | |
| animation: blink 1s infinite; | |
| } | |
| .correct-char { | |
| color: #4ade80; | |
| } | |
| .incorrect-char { | |
| color: #f87171; | |
| text-decoration: underline; | |
| } | |
| @keyframes blink { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0; } | |
| } | |
| .progress-container { | |
| height: 10px; | |
| background-color: #374151; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background-color: #3b82f6; | |
| width: 0%; | |
| transition: width 0.1s ease; | |
| } | |
| @media (max-width: 768px) { | |
| .keyboard { | |
| grid-template-columns: repeat(8, 1fr); | |
| } | |
| .key { | |
| height: 40px; | |
| font-size: 0.8rem; | |
| } | |
| .target-text { | |
| font-size: 1.2rem; | |
| line-height: 2rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <h1 class="text-3xl font-bold text-center mb-8 text-blue-400">Слепая Печать</h1> | |
| <div class="target-text mb-8 text-center" id="targetText"></div> | |
| <div class="progress-container w-full"> | |
| <div class="progress-bar" id="progressBar"></div> | |
| </div> | |
| <div class="keyboard" id="keyboard"> | |
| <!-- Первый ряд --> | |
| <div class="key" data-key="`">~</div> | |
| <div class="key" data-key="1">1</div> | |
| <div class="key" data-key="2">2</div> | |
| <div class="key" data-key="3">3</div> | |
| <div class="key" data-key="4">4</div> | |
| <div class="key" data-key="5">5</div> | |
| <div class="key" data-key="6">6</div> | |
| <div class="key" data-key="7">7</div> | |
| <div class="key" data-key="8">8</div> | |
| <div class="key" data-key="9">9</div> | |
| <div class="key" data-key="0">0</div> | |
| <div class="key" data-key="-">-</div> | |
| <div class="key" data-key="=">=</div> | |
| <div class="key wide" data-key="Backspace">Backspace</div> | |
| <!-- Второй ряд --> | |
| <div class="key wide" data-key="Tab">Tab</div> | |
| <div class="key" data-key="q">Q</div> | |
| <div class="key" data-key="w">W</div> | |
| <div class="key" data-key="e">E</div> | |
| <div class="key" data-key="r">R</div> | |
| <div class="key" data-key="t">T</div> | |
| <div class="key" data-key="y">Y</div> | |
| <div class="key" data-key="u">U</div> | |
| <div class="key" data-key="i">I</div> | |
| <div class="key" data-key="o">O</div> | |
| <div class="key" data-key="p">P</div> | |
| <div class="key" data-key="[">[</div> | |
| <div class="key" data-key="]">]</div> | |
| <div class="key" data-key="\\">\</div> | |
| <!-- Третий ряд --> | |
| <div class="key wide" data-key="CapsLock">Caps</div> | |
| <div class="key" data-key="a">A</div> | |
| <div class="key" data-key="s">S</div> | |
| <div class="key" data-key="d">D</div> | |
| <div class="key" data-key="f">F</div> | |
| <div class="key" data-key="g">G</div> | |
| <div class="key" data-key="h">H</div> | |
| <div class="key" data-key="j">J</div> | |
| <div class="key" data-key="k">K</div> | |
| <div class="key" data-key="l">L</div> | |
| <div class="key" data-key=";">;</div> | |
| <div class="key" data-key="'">'</div> | |
| <div class="key wide" data-key="Enter">Enter</div> | |
| <!-- Четвертый ряд --> | |
| <div class="key extra-wide" data-key="Shift">Shift</div> | |
| <div class="key" data-key="z">Z</div> | |
| <div class="key" data-key="x">X</div> | |
| <div class="key" data-key="c">C</div> | |
| <div class="key" data-key="v">V</div> | |
| <div class="key" data-key="b">B</div> | |
| <div class="key" data-key="n">N</div> | |
| <div class="key" data-key="m">M</div> | |
| <div class="key" data-key=",">,</div> | |
| <div class="key" data-key=".">.</div> | |
| <div class="key" data-key="/">/</div> | |
| <div class="key extra-wide" data-key="Shift">Shift</div> | |
| <!-- Пятый ряд --> | |
| <div class="key" data-key="Control">Ctrl</div> | |
| <div class="key" data-key="Meta">Win</div> | |
| <div class="key" data-key="Alt">Alt</div> | |
| <div class="key extra-wide" data-key=" ">Space</div> | |
| <div class="key" data-key="Alt">Alt</div> | |
| <div class="key" data-key="Meta">Win</div> | |
| <div class="key" data-key="Control">Ctrl</div> | |
| </div> | |
| <div class="flex justify-between items-center mt-6 mb-8"> | |
| <div class="text-xl"> | |
| Время: <span id="timer">00:00.00</span> | |
| </div> | |
| <div class="text-xl"> | |
| Ошибки: <span id="errors">0</span> | |
| </div> | |
| </div> | |
| <div class="flex justify-center gap-4"> | |
| <button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition"> | |
| Старт | |
| </button> | |
| <button id="restartBtn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-6 rounded-lg transition hidden"> | |
| Заново | |
| </button> | |
| </div> | |
| <div id="results" class="mt-8 text-center hidden"> | |
| <h2 class="text-2xl font-bold mb-4">Результаты</h2> | |
| <div class="grid grid-cols-3 gap-4 text-xl"> | |
| <div> | |
| <div class="text-gray-400">Время</div> | |
| <div id="resultTime">00:00.00</div> | |
| </div> | |
| <div> | |
| <div class="text-gray-400">Ошибки</div> | |
| <div id="resultErrors">0</div> | |
| </div> | |
| <div> | |
| <div class="text-gray-400">Точность</div> | |
| <div id="resultAccuracy">100%</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const texts = [ | |
| "Привет, мир!", | |
| "Клавиатура вслепую.", | |
| "JavaScript и HTML.", | |
| "Практика ведет к совершенству.", | |
| "Горячие клавиши.", | |
| "Отличная скорость печати.", | |
| "Тренируйся каждый день.", | |
| "Слепая печать - это просто.", | |
| "Набери текст без ошибок.", | |
| "Скорость и точность важны." | |
| ]; | |
| // DOM элементы | |
| const targetTextElement = document.getElementById('targetText'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const timerElement = document.getElementById('timer'); | |
| const errorsElement = document.getElementById('errors'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const restartBtn = document.getElementById('restartBtn'); | |
| const resultsElement = document.getElementById('results'); | |
| const resultTimeElement = document.getElementById('resultTime'); | |
| const resultErrorsElement = document.getElementById('resultErrors'); | |
| const resultAccuracyElement = document.getElementById('resultAccuracy'); | |
| const keyboardKeys = document.querySelectorAll('.key'); | |
| // Состояние игры | |
| let currentText = ''; | |
| let currentPosition = 0; | |
| let errors = 0; | |
| let startTime = 0; | |
| let timerInterval = null; | |
| let isGameActive = false; | |
| let totalChars = 0; | |
| // Запуск игры | |
| function startGame() { | |
| // Выбираем случайный текст | |
| currentText = texts[Math.floor(Math.random() * texts.length)]; | |
| currentPosition = 0; | |
| errors = 0; | |
| totalChars = currentText.length; | |
| // Отображаем текст | |
| displayText(); | |
| // Сбрасываем таймер | |
| startTime = Date.now(); | |
| if (timerInterval) clearInterval(timerInterval); | |
| timerInterval = setInterval(updateTimer, 10); | |
| // Сбрасываем прогресс | |
| progressBar.style.width = '0%'; | |
| errorsElement.textContent = '0'; | |
| // Показываем/скрываем кнопки | |
| startBtn.classList.add('hidden'); | |
| restartBtn.classList.add('hidden'); | |
| resultsElement.classList.add('hidden'); | |
| isGameActive = true; | |
| } | |
| // Отображение текста для набора | |
| function displayText() { | |
| let html = ''; | |
| for (let i = 0; i < currentText.length; i++) { | |
| let char = currentText[i]; | |
| let charClass = ''; | |
| if (i < currentPosition) { | |
| charClass = 'correct-char'; | |
| } else if (i === currentPosition) { | |
| charClass = 'current-char'; | |
| } | |
| // Заменяем пробелы на видимый символ | |
| if (char === ' ') { | |
| char = '·'; | |
| } | |
| html += `<span class="${charClass}">${char}</span>`; | |
| } | |
| targetTextElement.innerHTML = html; | |
| } | |
| // Обновление таймера | |
| function updateTimer() { | |
| const elapsed = Date.now() - startTime; | |
| const minutes = Math.floor(elapsed / 60000); | |
| const seconds = Math.floor((elapsed % 60000) / 1000); | |
| const milliseconds = Math.floor((elapsed % 1000) / 10); | |
| timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(2, '0')}`; | |
| } | |
| // Обработка нажатия клавиши | |
| function handleKeyDown(event) { | |
| if (!isGameActive) return; | |
| // Игнорируем служебные клавиши | |
| if (event.ctrlKey || event.altKey || event.metaKey) return; | |
| const key = event.key; | |
| // Обработка Backspace | |
| if (key === 'Backspace') { | |
| if (currentPosition > 0) { | |
| currentPosition--; | |
| displayText(); | |
| } | |
| return; | |
| } | |
| // Проверяем правильность ввода | |
| const expectedChar = currentText[currentPosition].toLowerCase(); | |
| const pressedChar = key.toLowerCase(); | |
| // Находим элемент клавиши на виртуальной клавиатуре | |
| let keyElement = null; | |
| for (const k of keyboardKeys) { | |
| if (k.dataset.key.toLowerCase() === pressedChar.toLowerCase()) { | |
| keyElement = k; | |
| break; | |
| } | |
| } | |
| if (expectedChar === pressedChar) { | |
| // Правильный ввод | |
| currentPosition++; | |
| progressBar.style.width = `${(currentPosition / totalChars) * 100}%`; | |
| // Подсвечиваем клавишу | |
| if (keyElement) { | |
| keyElement.classList.add('correct'); | |
| setTimeout(() => { | |
| keyElement.classList.remove('correct'); | |
| }, 300); | |
| } | |
| // Проверяем завершение текста | |
| if (currentPosition === totalChars) { | |
| finishGame(); | |
| } | |
| } else { | |
| // Неправильный ввод | |
| errors++; | |
| errorsElement.textContent = errors; | |
| // Подсвечиваем клавишу | |
| if (keyElement) { | |
| keyElement.classList.add('incorrect'); | |
| setTimeout(() => { | |
| keyElement.classList.remove('incorrect'); | |
| }, 300); | |
| } | |
| // Подсвечиваем символ в тексте | |
| const spans = targetTextElement.querySelectorAll('span'); | |
| if (spans[currentPosition]) { | |
| spans[currentPosition].classList.add('incorrect-char'); | |
| } | |
| } | |
| displayText(); | |
| } | |
| // Завершение игры | |
| function finishGame() { | |
| isGameActive = false; | |
| clearInterval(timerInterval); | |
| // Показываем результаты | |
| resultTimeElement.textContent = timerElement.textContent; | |
| resultErrorsElement.textContent = errors; | |
| const accuracy = Math.max(0, Math.round(((totalChars - errors) / totalChars) * 100)); | |
| resultAccuracyElement.textContent = `${accuracy}%`; | |
| resultsElement.classList.remove('hidden'); | |
| restartBtn.classList.remove('hidden'); | |
| } | |
| // События | |
| startBtn.addEventListener('click', startGame); | |
| restartBtn.addEventListener('click', startGame); | |
| document.addEventListener('keydown', handleKeyDown); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gloker1613/blankout" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |