From 18a1b361a9d6567b87c49e8bbbf0bba9ba51687f Mon Sep 17 00:00:00 2001 From: srdusr Date: Tue, 30 Sep 2025 13:15:59 +0200 Subject: TUI: fixed wpm history/made UI more identical to web/ctrl-backspace behaviour/improved accuracy Web: improved category selection/fixed endscreen responsiveness inconsistency/mistake tracking/clean game memory after use/improve general UI --- web/src/App.tsx | 17 +++++----- web/src/components/EndScreen.tsx | 43 ++++++++++++++++++++++++-- web/src/components/MainMenu.tsx | 39 ++++++++++------------- web/src/components/TypingGame.tsx | 29 ++++++++--------- web/src/hooks/useCoreGame.ts | 17 ++++------ web/src/styles.css | 65 +++++++++++++++++++-------------------- 6 files changed, 121 insertions(+), 89 deletions(-) (limited to 'web') diff --git a/web/src/App.tsx b/web/src/App.tsx index b34c4b4..9b266d9 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -108,7 +108,10 @@ function App() { const { game, resetGame, cleanupGame } = useCoreGame(); const [allTexts, setAllTexts] = useState(LOCAL_TEXTS); const [categories, setCategories] = useState(uniqueCategories(LOCAL_TEXTS)); - const [selectedCategory, setSelectedCategory] = useState('random'); + const [selectedCategory, setSelectedCategory] = useState(() => { + const saved = localStorage.getItem('typerpunk:last_mode'); + return saved || 'random'; + }); const [gameState, setGameState] = useState({ screen: 'main-menu', currentText: '', @@ -140,12 +143,7 @@ function App() { })); const testText = "This is a test sentence for the end screen. It has some text to display and check for errors."; const testUserInput = "This is a test sentance for the end screen. It has sone text to display and check for erors."; - const _testCharTimings = testText.split('').map((char, i) => ({ - time: (i / testText.length) * 60, - isCorrect: char === (testUserInput[i] || ''), - char: char, - index: i - })); + // removed unused _testCharTimings const [lastTest, setLastTest] = useState<{ stats: Stats; wpmHistory: Array<{ time: number; wpm: number; raw: number; isError: boolean }>; text: string; userInput: string; charTimings?: Array<{ time: number; isCorrect: boolean; char: string; index: number }>; keypressHistory?: Array<{ time: number; index: number; isCorrect: boolean }> } | null>(null); // Removed unused Enter key end-screen toggle handler @@ -167,6 +165,10 @@ function App() { })(); }, []); + useEffect(() => { + try { localStorage.setItem('typerpunk:last_mode', selectedCategory); } catch {} + }, [selectedCategory]); + const handleStartGame = async () => { try { // Reset game state first @@ -402,6 +404,7 @@ function App() { categories={categories} selectedCategory={selectedCategory} onSelectCategory={setSelectedCategory} + startLabel={`Start: ${selectedCategory === 'random' ? 'Random' : selectedCategory.charAt(0).toUpperCase() + selectedCategory.slice(1)}`} /> ) : gameState.screen === 'end-screen' ? ( = ({ stats, wpmHistory, onPlayAgain, onMainMen }; // --- Layout --- return ( -
+
{/* Logo at top, same as TypingGame */}
TyperPunk
{/* Main content area, all in one flex column, no fixed elements */} @@ -507,7 +507,7 @@ export const EndScreen: FC = ({ stats, wpmHistory, onPlayAgain, onMainMen boxSizing: 'border-box', }}> {/* Text */} -
+
{renderText()}
{/* Desktop: WPM | Graph | ACC */} @@ -540,6 +540,45 @@ export const EndScreen: FC = ({ stats, wpmHistory, onPlayAgain, onMainMen
)} + {/* Transparent theme toggle (no class to avoid inherited styles) */} + {/* TIME stat below graph */}
TIME
{stats.time.toFixed(1)}
diff --git a/web/src/components/MainMenu.tsx b/web/src/components/MainMenu.tsx index b8daea0..fc80039 100644 --- a/web/src/components/MainMenu.tsx +++ b/web/src/components/MainMenu.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { useTheme } from '../contexts/ThemeContext'; import { Theme } from '../types'; @@ -11,33 +11,28 @@ interface Props { const MainMenu: React.FC = ({ onStartGame, categories = [], selectedCategory = 'random', onSelectCategory }) => { const { theme, toggleTheme } = useTheme(); + const modes = useMemo(() => ['random', ...categories], [categories]); + const currentIndex = Math.max(0, modes.findIndex(m => (selectedCategory || 'random') === m)); + const currentMode = modes[currentIndex] || 'random'; + const nextMode = modes[(currentIndex + 1) % modes.length] || 'random'; return (
-

TyperPunk

-
-
- - -
- + -
-
{attribution && ( -
+
— {attribution}
)} @@ -527,7 +527,7 @@ export const TypingGame: React.FC = React.memo((props: Props): JSX.Elemen
{/* Graph center, take all available space with margin for stats */}
-
+
{/* TIME stat below graph */}
TIME
{stats.time.toFixed(1)}
@@ -537,36 +537,37 @@ export const TypingGame: React.FC = React.memo((props: Props): JSX.Elemen {/* Mobile: Graph at top, then WPM & ACC in a row, then TIME below */} {isMobileScreen && ( <> -
+
- {/* WPM (left) */} -
+ {/* WPM (left, close to edge) */} +
WPM
{Math.round(stats.wpm)}
- {/* ACC (right) */} -
+ {/* ACC (right, close to edge) */} +
ACC
{Math.round(wasmAccuracy)}%
{/* TIME stat below WPM/ACC */} -
TIME
+
TIME
{stats.time.toFixed(1)}
)}
{/* Empty space for future game modes, matches EndScreen */}
-