diff --git a/src/App.tsx b/src/App.tsx index e077b14..0d28687 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,20 +31,19 @@ import { clamp } from './utils/math'; import './App.css'; function App() { - // Core state + // Simulation state const [targets, setTargets] = useState([]); const [currentTarget, setCurrentTarget] = useState(null); const [zoomLevel, setZoomLevel] = useState(CONSTANTS.ZOOM_LEVELS.default); const [stats, setStats] = useState(createInitialStats()); - // Study mode state + // Experiment mode configuration const [mode, setMode] = useState('freestyle'); const [currentPreset, setCurrentPreset] = useState('A'); const [presetsCompleted, setPresetsCompleted] = useState([]); const [targetCount, setTargetCount] = useState(0); const [studyComplete, setStudyComplete] = useState(false); - // Load settings from localStorage or use defaults const [settings, setSettings] = useState(() => { const savedState = loadState(); const defaultSettings = createDefaultSettings(); @@ -56,30 +55,28 @@ function App() { return savedState?.rounds ?? []; }); - // UI state + // UI visibility states const [showSettings, setShowSettings] = useState(false); const [showRounds, setShowRounds] = useState(false); const [showInstructions, setShowInstructions] = useState(false); + const [showResizeWarning, setShowResizeWarning] = useState(false); const [viewportOffset, setViewportOffset] = useState({ x: 0, y: 0, }); - // Add state to track drag operations + // Interaction tracking refs const isDragging = useRef(false); const dragStartTime = useRef(0); const dragStartPos = useRef({ x: 0, y: 0 }); const DRAG_THRESHOLD = 5; // pixels - // Refs for handling zoom behavior const containerRef = useRef(null); const zoomSpeedSamples = useRef([]); const touchStartDistance = useRef(0); const wasReset = useRef(false); - // Add a new state for the resize warning popup - const [showResizeWarning, setShowResizeWarning] = useState(false); - + // Load saved state on initial mount useEffect(() => { const savedState = loadState(); if (savedState?.stats) { @@ -88,6 +85,7 @@ function App() { setShowInstructions(!savedState?.rounds || savedState.rounds.length === 0); }, []); + // Persist state when it changes useEffect(() => { saveState({ rounds, @@ -96,6 +94,7 @@ function App() { }); }, [rounds, stats, settings]); + // Initialize the simulation on first load useEffect(() => { initializeGame(); }, []); @@ -115,7 +114,7 @@ function App() { setStudyComplete(false); }; - // Apply preset when it changes + // Configure settings when preset changes useEffect(() => { if (currentPreset) { setSettings(prev => ({ @@ -127,32 +126,29 @@ function App() { } }, [currentPreset]); - // Handle moving to next preset in study mode + // Progress through presets in study mode useEffect(() => { if (mode !== 'study') return; if (targetCount >= CONSTANTS.STUDY_TARGETS_PER_PRESET) { - // Add current preset to completed list if (!presetsCompleted.includes(currentPreset)) { setPresetsCompleted(prev => [...prev, currentPreset]); } - // Directly move to the next preset without showing break screen const allPresets: ZoomPreset[] = ['A', 'B', 'C', 'D']; const currentIndex = allPresets.indexOf(currentPreset); if (currentIndex < allPresets.length - 1) { - // Move to next preset const nextPreset = allPresets[currentIndex + 1]; setCurrentPreset(nextPreset); setTargetCount(0); } else { - // All presets completed setStudyComplete(true); } } }, [targetCount, currentPreset, mode, presetsCompleted]); + // Handle zoom wheel events and track timing data useEffect(() => { const container = containerRef.current; if (!container) return; @@ -171,15 +167,12 @@ function App() { const scrollAmount = processWheelDelta(-event.deltaY); const now = Date.now(); - // Determine zoom direction (positive scrollAmount = zoom in, negative = zoom out) const direction = scrollAmount > 0 ? 'in' : 'out'; - // Update zoom timing stats setStats(previous => { - // If we're already zooming in the same direction, add to the time if (previous.isZooming && previous.zoomDirection === direction && previous.lastZoomTime) { const timeDelta = now - previous.lastZoomTime; - // Only count if it's a reasonable time (less than 500ms between events) + // Only accumulate time if events are close together (continuous zooming) const shouldCount = timeDelta < 500; return { @@ -191,7 +184,6 @@ function App() { }; } - // Otherwise start new zoom tracking return { ...previous, isZooming: true, @@ -233,7 +225,6 @@ function App() { } }; - // Stop zoom timing when wheel events stop const handleZoomEnd = () => { setStats(previous => ({ ...previous, @@ -245,7 +236,7 @@ function App() { container.addEventListener('wheel', handleWheelEvent, { passive: false }); - // Add a listener to detect when zooming stops (after 300ms of no wheel events) + // Detect when zooming stops after a brief pause let zoomEndTimer: number; container.addEventListener('wheel', () => { clearTimeout(zoomEndTimer); @@ -334,7 +325,6 @@ function App() { if (distanceToTarget <= target.radius) { const timeElapsed = Date.now() - stats.startTime; - // Create and save the round with zoom timing data const newRound = createNewRound( timeElapsed, stats.misclicks, @@ -348,7 +338,6 @@ function App() { setRounds(previous => [...previous, newRound]); - // Update stats and reset zoom timing data setStats({ startTime: Date.now(), misclicks: 0, @@ -361,10 +350,8 @@ function App() { zoomDirection: null }); - // Set next target setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT)); - // If in study mode, increment target count if (mode === 'study') { setTargetCount(prev => prev + 1); } @@ -384,24 +371,19 @@ function App() { wasReset.current = true; initializeGame(); - // Close any open panels setShowSettings(false); setShowRounds(false); - - // Show the initial instructions screen setShowInstructions(true); } }; const handleModeChange = (newMode: AppMode) => { - // Reset data when changing modes if (newMode !== mode) { if (window.confirm('Changing modes will reset your current data. Continue?')) { setMode(newMode); setRounds([]); setStats(createInitialStats()); - // Reset study mode stats when changing modes if (newMode === 'study') { setTargetCount(0); setPresetsCompleted([]); @@ -409,7 +391,6 @@ function App() { setStudyComplete(false); } - // Reset the game initializeGame(); } } else { @@ -417,15 +398,13 @@ function App() { } }; - // Calculate average time const averageTime = calculateAverageTime(stats.times); - // Add a resize handler to regenerate targets when window size changes + // Regenerate targets when window is resized useEffect(() => { let resizeTimer: number; const handleResize = () => { - // Don't regenerate if in study mode to avoid disrupting the test if (mode !== 'study') { clearTimeout(resizeTimer); @@ -433,14 +412,12 @@ function App() { const newTargets = generateTargets(); setTargets(newTargets); - // If we're targeting something, make sure it's still valid if (currentTarget !== null) { setCurrentTarget(getRandomTarget(currentTarget, CONSTANTS.TARGET_COUNT)); } - // Show resize warning setShowResizeWarning(true); - }, 500); // Add a delay to detect when resizing is "done" + }, 500); // Delay to detect when resizing is complete } }; @@ -569,7 +546,6 @@ function App() { )} - {/* Resize warning popup */} {showResizeWarning && (