diff --git a/src/App.css b/src/App.css index 6dbdcf0..83c91eb 100644 --- a/src/App.css +++ b/src/App.css @@ -1040,9 +1040,9 @@ button { } .phrase-setup { - margin-top: 16px; - border-top: 1px solid color-mix(in srgb, var(--color-text-main) 12%, transparent); - padding-top: 12px; + margin-bottom: 16px; + border-bottom: 1px solid color-mix(in srgb, var(--color-text-main) 12%, transparent); + padding-bottom: 12px; } .phrase-setup summary { diff --git a/src/components/Controls.tsx b/src/components/Controls.tsx index 9090d65..185eb7a 100644 --- a/src/components/Controls.tsx +++ b/src/components/Controls.tsx @@ -30,17 +30,23 @@ export const Controls: React.FC = ({ currentPitch }) => { }); }; + const inPhraseMode = settings.gameMode === 'phrase'; + const pf = getPracticeFilter(settings); const activeFilterCount = (pf.keyEnabled ? 1 : 0) + (pf.strings.length > 0 ? 1 : 0) + (pf.fretWindowEnabled ? 1 : 0); - const inPhraseMode = settings.gameMode === 'phrase'; + // Phrase Mode hosts the filter inside its Setup panel (above the + // notation) — no footer filter button there. + const showFilterButton = !inPhraseMode; + const shouldShowFilters = showFilterButton && filtersOpen; return (
{/* Note filter: collapsed behind a button — visible only when needed */} + {showFilterButton && (
- {filtersOpen && } + )} + {shouldShowFilters && } {/* Bottom Section: Tools Grid (2 Columns now) */}
diff --git a/src/components/PhraseTrainer.tsx b/src/components/PhraseTrainer.tsx index 2d4ff56..a695d9e 100644 --- a/src/components/PhraseTrainer.tsx +++ b/src/components/PhraseTrainer.tsx @@ -28,6 +28,7 @@ import { getNoteDetails } from '../music/NoteUtils'; import { EXERCISES, loadExercise } from '../exercises/library'; import { parseAbc } from '../exercises/abcParser'; import { PhraseSheetMusic } from './PhraseSheetMusic'; +import { TargetNoteControls } from './TargetNoteControls'; import { Fretboard } from './Fretboard'; import { PianoKeys } from './PianoKeys'; import { TUNINGS, getFretboardPositions } from '../music/Tunings'; @@ -435,118 +436,8 @@ export const PhraseTrainer = forwardRef(
-
- {scoreOk ? ( - - ) : ( -
{materialError}
- )} -
- - {trainer.error &&
{trainer.error}
} - {importError &&
{importError}
} - {rangeWarning &&
{rangeWarning}
} - -
- {trainer.phase === 'countIn' && ( - {trainer.countInLeft} - )} -
{statusText}
-
- - {/* Transport — reuses the single-note button styles/behavior */} -
- - {!settings.zenMode && ( - <> - - - {(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && ( - - )} - {phrase.pace === 'step' && ( - - )} - - )} - {/* Same virtual-instrument / hint toggles as single-note mode */} - {trainer.phase === 'done' && settings.zenMode && ( - <> - - {(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && ( - - )} - - )} - {!settings.zenMode && currentInstrumentDef.showTuning && ( - - )} - {!settings.zenMode && ( - - )} -
- - {/* Setup panel */} + {/* Setup panel — above the notation, holds material options + plus the shared note filter (key/strings/fret window) */} {!settings.zenMode && (
Setup @@ -562,8 +453,6 @@ export const PhraseTrainer = forwardRef( - {(phrase.material === 'melody' || phrase.material === 'scale' || phrase.material === 'arpeggio') && null} - {phrase.material === 'melody' && ( <> )} - {fretted && null} - {/* Pace lives in the metronome widget now: 'Sync to exercise' checked = tempo pace, unchecked = at-your-pace (step). */} @@ -815,9 +702,122 @@ export const PhraseTrainer = forwardRef( Matching checks pitch, not fingering — several fretboard positions produce the same pitch. For repeated identical notes, briefly release between notes so each attack is detected.

+
)} +
+ {scoreOk ? ( + + ) : ( +
{materialError}
+ )} +
+ + {trainer.error &&
{trainer.error}
} + {importError &&
{importError}
} + {rangeWarning &&
{rangeWarning}
} + +
+ {trainer.phase === 'countIn' && ( + {trainer.countInLeft} + )} +
{statusText}
+
+ + {/* Transport — reuses the single-note button styles/behavior */} +
+ + {!settings.zenMode && ( + <> + + + {(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && ( + + )} + {phrase.pace === 'step' && ( + + )} + + )} + {/* Same virtual-instrument / hint toggles as single-note mode */} + {trainer.phase === 'done' && settings.zenMode && ( + <> + + {(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && ( + + )} + + )} + {!settings.zenMode && currentInstrumentDef.showTuning && ( + + )} + {!settings.zenMode && ( + + )} +
+ + {/* Hint instruments */} {(settings.showHint || settings.showFretboard) && currentInstrumentDef.showTuning && (
diff --git a/src/components/TargetNoteControls.tsx b/src/components/TargetNoteControls.tsx index 79d1641..2d82f13 100644 --- a/src/components/TargetNoteControls.tsx +++ b/src/components/TargetNoteControls.tsx @@ -6,8 +6,10 @@ * - Strings: pick which strings contribute target notes (fretted instruments). * - Fret window: restrict target notes to a fret range (fretted instruments). * - * In Phrase Mode the same controls bind to the shared practice filter - * (mirrored into phrase settings), so state stays consistent across modes. + * In Phrase Mode the Key section binds to the generator key (tonic+mode are + * always applied; no toggle), and the display-signature sync checkbox is + * hidden (phrase notation takes its signature from the score's key). + * Rendered inside the per-mode Setup panel above the notation. */ import { useMemo } from 'react'; import { Key, Guitar, Music, SlidersHorizontal } from 'lucide-react'; @@ -46,6 +48,8 @@ const MINOR_TONIC_TO_SIGNATURE: Record = { export const TargetNoteControls: React.FC = () => { const { settings, updateSettings } = useSettings(); const pf = getPracticeFilter(settings); + // Phrase Mode: key tonic/mode are the generator key — always in effect. + const inPhraseMode = settings.gameMode === 'phrase'; const setFilter = (updates: Partial) => { updateSettings(s => { @@ -81,7 +85,8 @@ export const TargetNoteControls: React.FC = () => { const allSelected = pf.strings.length === 0; - const anyActive = pf.keyEnabled || pf.fretWindowEnabled || pf.strings.length > 0; + const anyActive = + (!inPhraseMode && pf.keyEnabled) || pf.fretWindowEnabled || pf.strings.length > 0; return (
{/* Section header: unified with Tuner/Metronome style */} @@ -101,35 +106,6 @@ export const TargetNoteControls: React.FC = () => { )}
- {/* Display key signature (sheet notation) */} -
- - -
- {/* Key filter */}
@@ -137,15 +113,17 @@ export const TargetNoteControls: React.FC = () => { Key - + {!inPhraseMode && ( + + )}
- {pf.keyEnabled && ( + {(pf.keyEnabled || inPhraseMode) && ( <>
- + {!inPhraseMode && ( + + )} )}