diff --git a/src/App.tsx b/src/App.tsx index 09556e3..d00c1ed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { SheetMusic } from './components/SheetMusic'; import { Controls, type AppSettings } from './components/Controls'; +import { SettingsModal } from './components/SettingsModal'; import { usePitchDetector } from './hooks/usePitchDetector'; import { useMetronome } from './hooks/useMetronome'; import { useAudioPlayer } from './hooks/useAudioPlayer'; @@ -17,7 +18,7 @@ import { import { INSTRUMENT_DEFINITIONS } from './music/InstrumentConfigs'; import { Fretboard } from './components/Fretboard'; -import { Mic, MicOff, SkipForward, HelpCircle, Volume2, X, Guitar } from 'lucide-react'; +import { Mic, MicOff, SkipForward, HelpCircle, Volume2, X, Guitar, Settings } from 'lucide-react'; import './App.css'; import './styles/skip-button.css'; @@ -51,13 +52,15 @@ function App() { customMinFret: 0, customMaxFret: 12, autoPlaySightReading: false, - autoPlayVolume: 0.5 + autoPlayVolume: 0.5, + disableAnimation: false }); const [matchStartTime, setMatchStartTime] = useState(null); const [feedbackMessage, setFeedbackMessage] = useState(""); const [revealed, setRevealed] = useState(false); const [virtualNote, setVirtualNote] = useState(null); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); const currentTuning = TUNINGS[settings.tuningId]; const currentInstrumentDef = INSTRUMENT_DEFINITIONS[settings.instrument]; @@ -218,12 +221,15 @@ function App() { // Immediate transition generateNewNote(true); // Keep "Good!" message - // Allow visual feedback to persist for a moment before clearing text (optional, but requested layout changes handle visuals) - // We rely on CSS overlay fading or just keeping it briefly? - // With the new generateNewNote(true), "Good!" stays. We need to clear it eventually. - setTimeout(() => { + if (settings.disableAnimation) { setFeedbackMessage(""); - }, 1500); + setRevealed(false); + } else { + // Allow visual feedback to persist for a moment before clearing text + setTimeout(() => { + setFeedbackMessage(""); + }, 1500); + } } else { // Rhythm Active AND Auto-Advance @@ -386,6 +392,13 @@ function App() { + - - {(settings.gameMode === 'ear_training' || settings.autoPlaySightReading) && ( -
- - onUpdateSettings({ ...settings, autoPlayVolume: parseFloat(e.target.value) })} - style={{ flex: 1 }} - /> -
- )} - ); diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx new file mode 100644 index 0000000..3166bc2 --- /dev/null +++ b/src/components/SettingsModal.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { X, Volume2 } from 'lucide-react'; +import type { AppSettings } from './Controls'; + +interface SettingsModalProps { + isOpen: boolean; + onClose: () => void; + settings: AppSettings; + onUpdateSettings: (s: AppSettings) => void; +} + +export const SettingsModal: React.FC = ({ isOpen, onClose, settings, onUpdateSettings }) => { + if (!isOpen) return null; + + return ( +
+
e.stopPropagation()}> + +

Settings

+ + {/* Animation Settings */} +
+
+ + +
+

+ Removes the "Good!" overlay to allow faster playing. +

+
+ +
+ + {/* Auto Play Settings */} +
+
+ + +
+ + {(settings.gameMode === 'ear_training' || settings.autoPlaySightReading) && ( +
+ + onUpdateSettings({ ...settings, autoPlayVolume: parseFloat(e.target.value) })} + style={{ flex: 1 }} + /> +
+ )} +

+ {settings.gameMode === 'ear_training' + ? "Automatically plays the note audio (Required for Ear Training)." + : "Automatically plays the note audio when a new note appears."} +

+
+ +
+
+ ); +};