Phrase mode UI fixes: visible current-note accent, staff headroom, reused virtual-instrument/hint toggles, zen decluttering

This commit is contained in:
2026-09-14 01:15:52 +02:00
parent 4d6e205b07
commit 0520cb08b4
2 changed files with 68 additions and 27 deletions

View File

@@ -105,8 +105,8 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
const barsPerRow = Math.max(1, Math.min(4, Math.floor((width - 40) / 210)));
const rowCount = Math.max(1, Math.ceil(totalBars / barsPerRow));
const isGrand = clef === 'grand';
const rowHeight = isGrand ? 200 : 105;
const height = rowCount * rowHeight + 14;
const rowHeight = isGrand ? 250 : 140;
const height = rowCount * rowHeight + 30;
useEffect(() => {
const container = containerRef.current;
@@ -120,7 +120,9 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
const css = getComputedStyle(document.documentElement);
const resolve = (name: string, fallback: string) => css.getPropertyValue(name).trim() || fallback;
const textColor = resolve('--color-text-main', '#333333');
const currentColor = resolve('--color-primary', '#e63946');
// --color-primary is near-black in light mode — invisible as a highlight.
// Use the warm accent that reads clearly in both themes.
const currentColor = resolve('--color-warning', '#f59e0b');
const successColor = resolve('--color-success', '#22c55e');
const errorColor = resolve('--color-error', '#ef4444');
const mutedColor = resolve('--color-text-muted', '#888888');
@@ -222,7 +224,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
// ---- Staves, voices, formatting, drawing -----------------------------
for (let r = 0; r < rowFragmentList.length; r++) {
const y = r * rowHeight + 10;
const y = r * rowHeight + 26;
const rowMeasureNumbers: number[] = [];
for (let b = 0; b < barsPerRow; b++) {
const m = r * barsPerRow + b;
@@ -248,7 +250,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
}
let bass: Stave | null = null;
if (isGrand) {
bass = new Stave(x, y + 110, barWidth);
bass = new Stave(x, y + 130, barWidth);
bass.setDefaultLedgerLineStyle(ledgerStyle);
if (isFirstOfRow) {
bass.addClef('bass').addKeySignature(vexKeySpec);

View File

@@ -27,7 +27,7 @@ import { Fretboard } from './Fretboard';
import { PianoKeys } from './PianoKeys';
import { TUNINGS, getFretboardPositions } from '../music/Tunings';
import { INSTRUMENT_DEFINITIONS } from '../music/InstrumentConfigs';
import { Play, Pause, RotateCcw, SkipForward, Volume2, Square, ChevronDown, Music2, Upload } from 'lucide-react';
import { Play, Pause, RotateCcw, SkipForward, Volume2, Square, ChevronDown, Music2, Upload, HelpCircle, Guitar } from 'lucide-react';
export interface PhraseHandle {
pauseToggle: () => void;
@@ -318,34 +318,73 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
<div className="instruction-text phrase-status">{statusText}</div>
</div>
{/* Transport */}
{/* Transport — reuses the single-note button styles/behavior */}
<div className="action-row phrase-transport">
<button className="skip-button" onClick={startAction} title="Start / Pause (Space)">
{trainer.phase === 'playing' || trainer.phase === 'countIn' ? <Pause size={18} /> : <Play size={18} />}
{startLabel}
</button>
<button
className="hint-button"
onClick={trainer.previewToggle}
title="Preview the phrase (P)"
>
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
</button>
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
<RotateCcw size={18} />
Retry
</button>
{(phrase.material === 'melody' || fixedMaterial) && (
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
<SkipForward size={18} />
{phrase.material === 'melody' ? 'New' : 'Next'}
{!settings.zenMode && (
<>
<button
className="hint-button"
onClick={trainer.previewToggle}
title="Preview the phrase (P)"
>
{trainer.phase === 'preview' ? <Square size={18} /> : <Volume2 size={18} />}
{trainer.phase === 'preview' ? 'Stop' : 'Preview'}
</button>
<button className="hint-button" onClick={trainer.retry} title="Retry from the start (R)">
<RotateCcw size={18} />
Retry
</button>
{(phrase.material === 'melody' || fixedMaterial) && (
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
<SkipForward size={18} />
{phrase.material === 'melody' ? 'New' : 'Next'}
</button>
)}
{phrase.pace === 'step' && (
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
<SkipForward size={18} />
Skip
</button>
)}
</>
)}
{/* Same virtual-instrument / hint toggles as single-note mode */}
{trainer.phase === 'done' && settings.zenMode && (
<>
<button className="hint-button" onClick={trainer.retry} title="Retry (R)">
<RotateCcw size={18} />
Retry
</button>
{(phrase.material === 'melody' || fixedMaterial) && (
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
<SkipForward size={18} />
{phrase.material === 'melody' ? 'New' : 'Next'}
</button>
)}
</>
)}
{!settings.zenMode && currentInstrumentDef.showTuning && (
<button
className={`hint-button ${settings.showFretboard ? 'active' : ''}`}
onClick={() => setSettings(s => ({ ...s, showFretboard: !s.showFretboard }))}
title={`Toggle Virtual ${currentInstrumentDef.displayName} (Keyboard Shortcut: V)`}
>
<Guitar size={18} />
{currentInstrumentDef.id === 'piano' ? 'Piano' : 'Guitar'}
</button>
)}
{phrase.pace === 'step' && (
<button className="hint-button" onClick={trainer.skip} title="Skip this note (S)">
<SkipForward size={18} />
Skip
{!settings.zenMode && (
<button
className={`hint-button ${settings.showHint ? 'active' : ''}`}
onClick={() => setSettings(s => ({ ...s, showHint: !s.showHint }))}
title="Keyboard Shortcut: H"
>
<HelpCircle size={18} />
{settings.showHint ? "Hide Hint" : "Show Hint"}
</button>
)}
</div>