493493 }
494494 }
495495
496- # mobile-input {
497- position : absolute;
498- opacity : 0 ;
499- height : 0 ;
500- width : 0 ;
501- top : 0 ;
502- left : 0
503- }
504-
505496 .mobile-keyboard-note {
506497 display : none
507498 }
519510 .typing-area .keyboard-focused {
520511 border : 2px solid # 2563eb
521512 }
513+
514+ .virtual-keyboard {
515+ display : grid;
516+ grid-template-columns : repeat (9 , 1fr );
517+ gap : 0.25rem ;
518+ max-width : 100% ;
519+ margin : 0 auto;
520+ padding : 0.5rem
521+ }
522+
523+ .vk-key {
524+ display : flex;
525+ justify-content : center;
526+ align-items : center;
527+ height : 3rem ;
528+ background-color : # e5e7eb ;
529+ border-radius : 0.25rem ;
530+ font-family : 'JetBrains Mono' , monospace;
531+ font-size : 1.25rem ;
532+ font-weight : bold;
533+ color : # 374151 ;
534+ user-select : none;
535+ touch-action : manipulation
536+ }
537+
538+ .vk-key : active {
539+ background-color : # d1d5db
540+ }
541+
542+ .vk-key .current {
543+ background-color : # bfdbfe ;
544+ color : # 1e40af
545+ }
546+
547+ @media (max-width : 400px ) {
548+ .virtual-keyboard {
549+ grid-template-columns : repeat (7 , 1fr )
550+ }
551+
552+ .vk-key {
553+ height : 2.5rem ;
554+ font-size : 1rem
555+ }
556+ }
522557 </ style >
523558</ head >
524559< body class ="bg-gray-50 min-h-screen py-12 px-4 sm:px-6 ">
@@ -531,8 +566,6 @@ <h1 class="text-4xl font-bold text-gray-900 mb-2">Alphabet Typing Speed Game</h1
531566
532567 < main >
533568 < section class ="typing-area p-4 sm:p-8 mb-8 relative " id ="game-area " tabindex ="0 ">
534- < input type ="text " id ="mobile-input " autocomplete ="off " autocorrect ="off " autocapitalize ="off "
535- spellcheck ="false ">
536569 < div class ="keyboard-info " id ="keyboard-info "> Press 'A' to start</ div >
537570 < div id ="alphabet "
538571 class ="text-3xl sm:text-5xl md:text-6xl font-mono tracking-wide text-gray-800 py-4 "> </ div >
@@ -542,9 +575,12 @@ <h1 class="text-4xl font-bold text-gray-900 mb-2">Alphabet Typing Speed Game</h1
542575 </ div >
543576
544577 < div id ="game-message " class ="mt-4 text-center text-lg font-medium "> Press 'A' to start typing</ div >
545- < p class ="mobile-keyboard-note "> Tap anywhere in this box to bring up the keyboard </ p >
578+ < p class ="mobile-keyboard-note "> Use the keyboard below to type </ p >
546579 </ section >
547580
581+ <!-- Virtual Keyboard for Mobile -->
582+ < section id ="virtual-keyboard " class ="mb-8 virtual-keyboard "> </ section >
583+
548584 < section class ="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8 ">
549585 < div class ="metric-card ">
550586 < div class ="text-sm text-gray-500 mb-1 "> Current Time</ div >
@@ -689,7 +725,6 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
689725 let letterTimestamps = [ ] ;
690726 let attempts = parseInt ( localStorage . getItem ( "attempts" ) || "0" ) ;
691727 const MAX_HISTORY = 10 ;
692- const lastKeyPressed = { } ;
693728 const alphabetEl = document . getElementById ( 'alphabet' ) ;
694729 const resultEl = document . getElementById ( 'result' ) ;
695730 const highscoreEl = document . getElementById ( 'highscore' ) ;
@@ -712,19 +747,12 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
712747 const improvementEl = document . getElementById ( 'improvement' ) ;
713748 const tabButtons = document . querySelectorAll ( '.tab-button' ) ;
714749 const tabContents = document . querySelectorAll ( '.tab-content' ) ;
715- const mobileInputEl = document . getElementById ( 'mobile-input ' ) ;
750+ const virtualKeyboardEl = document . getElementById ( 'virtual-keyboard ' ) ;
716751
717752 function isMobileDevice ( ) {
718753 return / A n d r o i d | w e b O S | i P h o n e | i P a d | i P o d | B l a c k B e r r y | I E M o b i l e | O p e r a M i n i / i. test ( navigator . userAgent ) || window . innerWidth < 768 ;
719754 }
720755
721- function focusMobileInput ( ) {
722- if ( isMobileDevice ( ) ) {
723- mobileInputEl . focus ( ) ;
724- gameAreaEl . classList . add ( 'keyboard-focused' ) ;
725- }
726- }
727-
728756 function initGame ( ) {
729757 const halfwayPoint = Math . ceil ( alphabet . length / 2 ) ;
730758 const firstRow = alphabet . slice ( 0 , halfwayPoint ) ;
@@ -745,11 +773,8 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
745773 if ( ! localStorage . getItem ( "totalHistory" ) ) {
746774 localStorage . setItem ( "totalHistory" , JSON . stringify ( [ ] ) ) ;
747775 }
748- if ( isMobileDevice ( ) ) {
749- setTimeout ( focusMobileInput , 500 ) ;
750- } else {
751- gameAreaEl . focus ( ) ;
752- }
776+ initVirtualKeyboard ( ) ;
777+ gameAreaEl . focus ( ) ;
753778 updateGameState ( GameState . READY ) ;
754779 updateStatistics ( ) ;
755780 tabButtons . forEach ( button => {
@@ -765,6 +790,32 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
765790 redrawCharts ( ) ;
766791 }
767792
793+ function initVirtualKeyboard ( ) {
794+ virtualKeyboardEl . innerHTML = "" ;
795+ for ( let i = 0 ; i < alphabet . length ; i ++ ) {
796+ const key = document . createElement ( 'div' ) ;
797+ key . className = 'vk-key' ;
798+ key . textContent = alphabet [ i ] ;
799+ key . setAttribute ( 'data-key' , alphabet [ i ] ) ;
800+ if ( i === 0 ) {
801+ key . classList . add ( 'current' ) ;
802+ }
803+ key . addEventListener ( 'click' , ( ) => {
804+ processKeyPress ( alphabet [ i ] ) ;
805+ } ) ;
806+ virtualKeyboardEl . appendChild ( key ) ;
807+ }
808+ }
809+
810+ function updateVirtualKeyboard ( ) {
811+ document . querySelectorAll ( '.vk-key' ) . forEach ( ( key , index ) => {
812+ key . classList . remove ( 'current' ) ;
813+ if ( index === currentIndex ) {
814+ key . classList . add ( 'current' ) ;
815+ }
816+ } ) ;
817+ }
818+
768819 function updateGameState ( newState ) {
769820 currentState = newState ;
770821 const letters = document . querySelectorAll ( '.letter' ) ;
@@ -779,6 +830,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
779830 updateProgress ( 0 ) ;
780831 letterTimings = [ ] ;
781832 letterTimestamps = [ ] ;
833+ updateVirtualKeyboard ( ) ;
782834 break ;
783835 case GameState . PLAYING :
784836 gameMessageEl . textContent = "Typing..." ;
@@ -787,6 +839,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
787839 keyboardInfoEl . textContent = "Keep typing..." ;
788840 startTimer ( ) ;
789841 highlightCurrentLetter ( ) ;
842+ updateVirtualKeyboard ( ) ;
790843 break ;
791844 case GameState . COMPLETED :
792845 gameMessageEl . textContent = "Completed! Press 'A' to try again" ;
@@ -801,6 +854,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
801854 letter . classList . remove ( 'current' )
802855 } ) ;
803856 letters [ 0 ] . classList . add ( 'current' ) ;
857+ updateVirtualKeyboard ( ) ;
804858 break ;
805859 case GameState . ERROR :
806860 gameMessageEl . textContent = "Error! Press 'A' to try again" ;
@@ -814,6 +868,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
814868 gameAreaEl . classList . remove ( 'error-shake' ) ;
815869 } , 500 ) ;
816870 stopTimer ( ) ;
871+ updateVirtualKeyboard ( ) ;
817872 break ;
818873 }
819874 }
@@ -1117,11 +1172,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
11171172 function restartGame ( ) {
11181173 stopTimer ( ) ;
11191174 updateGameState ( GameState . READY ) ;
1120- if ( isMobileDevice ( ) ) {
1121- setTimeout ( focusMobileInput , 100 ) ;
1122- } else {
1123- gameAreaEl . focus ( ) ;
1124- }
1175+ gameAreaEl . focus ( ) ;
11251176 }
11261177
11271178 function resetAllData ( ) {
@@ -1172,6 +1223,7 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
11721223 letterTimestamps [ currentIndex ] = new Date ( ) . getTime ( ) ;
11731224 const progress = ( currentIndex / alphabet . length ) * 100 ;
11741225 updateProgress ( progress ) ;
1226+ updateVirtualKeyboard ( ) ;
11751227 if ( currentIndex === alphabet . length ) {
11761228 const finalTime = new Date ( ) . getTime ( ) - startTime ;
11771229 resultEl . textContent = `${ finalTime . toLocaleString ( ) } ms` ;
@@ -1194,14 +1246,6 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
11941246 processKeyPress ( event . key ) ;
11951247 }
11961248
1197- function handleMobileInput ( event ) {
1198- const inputValue = event . target . value . toLowerCase ( ) ;
1199- for ( let i = 0 ; i < inputValue . length ; i ++ ) {
1200- processKeyPress ( inputValue [ i ] ) ;
1201- }
1202- mobileInputEl . value = '' ;
1203- }
1204-
12051249 window . addEventListener ( "resize" , ( ) => {
12061250 updateLetterHistoryChart ( ) ;
12071251 updateProgressChart ( ) ;
@@ -1211,12 +1255,6 @@ <h3 class="text-lg font-medium mb-2">Letter Performance</h3>
12111255 if ( ! document . hidden ) redrawCharts ( ) ;
12121256 } ) ;
12131257 document . addEventListener ( "keydown" , handleKeyPress ) ;
1214- mobileInputEl . addEventListener ( "input" , handleMobileInput ) ;
1215- gameAreaEl . addEventListener ( "click" , ( ) => {
1216- if ( isMobileDevice ( ) ) {
1217- focusMobileInput ( ) ;
1218- }
1219- } ) ;
12201258 restartBtn . addEventListener ( "click" , restartGame ) ;
12211259 resetHighscoreBtn . addEventListener ( "click" , resetAllData ) ;
12221260 toggleStatsBtn . addEventListener ( "click" , ( ) => {
0 commit comments