mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-09-02 01:24:33 +02:00
Improve mobile UI (resizing)
This commit is contained in:
103
src/App.css
103
src/App.css
@@ -1,9 +1,11 @@
|
||||
/* App.css */
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: block;
|
||||
/* Changed from flex to allow natural document flow */
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-bg);
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
@@ -38,13 +40,16 @@
|
||||
}
|
||||
|
||||
.main-stage {
|
||||
flex: 1;
|
||||
/* Natural height - not flex to prevent shrinking when footer is present */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
padding: var(--spacing-md);
|
||||
gap: var(--spacing-xl);
|
||||
overflow-x: hidden;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -58,6 +63,8 @@
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
transition: max-width 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.card.has-hint {
|
||||
@@ -357,7 +364,8 @@ button {
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
/* Compact padding by default */
|
||||
border-radius: 30px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
@@ -365,7 +373,8 @@ button {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.8rem;
|
||||
/* Compact font size by default */
|
||||
}
|
||||
|
||||
.hint-button:hover {
|
||||
@@ -387,7 +396,8 @@ button {
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--color-text-muted);
|
||||
color: var(--color-text-muted);
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
/* Compact padding by default */
|
||||
border-radius: 30px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
@@ -395,7 +405,8 @@ button {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.8rem;
|
||||
/* Compact font size by default */
|
||||
}
|
||||
|
||||
.zen-button:hover {
|
||||
@@ -608,6 +619,20 @@ button {
|
||||
.header-controls {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Fix action row overflow on narrow screens */
|
||||
.action-row {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px !important;
|
||||
padding: 0 8px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Reduce card padding on mobile */
|
||||
.card {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-option.active {
|
||||
@@ -653,26 +678,29 @@ button {
|
||||
|
||||
.main-stage {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* Center vertically */
|
||||
justify-content: flex-start;
|
||||
gap: 0;
|
||||
/* Allow scrolling */
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
/* Prevent scroll - fit everything on one screen */
|
||||
}
|
||||
|
||||
.card.sheet-music-card {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
/* Fit within viewport */
|
||||
max-height: 100vh;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
align-items: stretch;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Staff Row: Horizontal Layout for Staff + Note Name */
|
||||
@@ -701,10 +729,11 @@ button {
|
||||
.sheet-music-container {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
min-height: 180px;
|
||||
/* Increased from 150px */
|
||||
transform: scale(1.1);
|
||||
/* Scale UP */
|
||||
min-height: 120px;
|
||||
/* Reduced to allow more space for fretboard */
|
||||
max-height: 180px;
|
||||
transform: scale(1.0);
|
||||
/* No scaling to prevent overflow */
|
||||
transform-origin: center center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
@@ -747,14 +776,17 @@ button {
|
||||
transform: none;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
flex: 1;
|
||||
/* Take remaining space */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #2a2a2a;
|
||||
/* distinct bg for full width feel */
|
||||
min-height: 200px;
|
||||
padding: 10px 0;
|
||||
min-height: 100px;
|
||||
/* Reduced to fit on screen */
|
||||
max-height: 50vh;
|
||||
/* Limit height */
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
/* Hide Instruction Text ("Play note above") */
|
||||
@@ -784,32 +816,33 @@ button {
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Push settings way down so they don't interfere with main view */
|
||||
/* Hide settings footer in landscape - accessible via settings modal */
|
||||
.settings-footer {
|
||||
margin-top: 50px;
|
||||
padding-bottom: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Make main stage scrollable to reach settings */
|
||||
/* App container in landscape */
|
||||
.app-container {
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
display: block;
|
||||
/* Break flex height lock */
|
||||
}
|
||||
|
||||
.action-row {
|
||||
margin-top: 20px !important;
|
||||
margin-top: 5px !important;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
max-width: 100vw;
|
||||
padding: 5px;
|
||||
position: static;
|
||||
/* Flow naturally below */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
gap: 8px;
|
||||
background: var(--color-bg);
|
||||
pointer-events: auto;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
/* Don't shrink */
|
||||
}
|
||||
|
||||
.action-row button {
|
||||
|
||||
12
src/App.tsx
12
src/App.tsx
@@ -75,6 +75,15 @@ function App() {
|
||||
const [hoveredMidi, setHoveredMidi] = useState<number | null>(null);
|
||||
const feedbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
@@ -485,7 +494,7 @@ function App() {
|
||||
keySignature={settings.keySignature}
|
||||
clef={activeClef}
|
||||
transpose={activeTranspose}
|
||||
width={Math.min(window.innerWidth - 40, 500)}
|
||||
width={Math.min(windowWidth - 40, 500)}
|
||||
height={activeClef === 'grand' ? 260 : 180}
|
||||
hideTargetNote={settings.gameMode === 'ear_training' && !revealed}
|
||||
hoverMidi={settings.showHint ? hoveredMidi : null}
|
||||
@@ -708,6 +717,7 @@ function App() {
|
||||
audioLevel={audioLevel}
|
||||
debugInfo={debugInfo}
|
||||
isListening={isListening}
|
||||
onMicToggle={() => setListening(l => !l)}
|
||||
/>
|
||||
|
||||
<OpenSourceModal
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, Volume2, VolumeX, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { X, Volume2, VolumeX, ChevronDown, ChevronUp, Mic, MicOff } from 'lucide-react';
|
||||
import type { AppSettings } from './Controls';
|
||||
import type { MicrophoneDebugInfo } from '../hooks/usePitchDetector';
|
||||
|
||||
@@ -11,6 +11,7 @@ interface SettingsModalProps {
|
||||
audioLevel?: number;
|
||||
debugInfo?: MicrophoneDebugInfo | null;
|
||||
isListening?: boolean;
|
||||
onMicToggle?: () => void;
|
||||
}
|
||||
|
||||
// Level meter component
|
||||
@@ -58,7 +59,8 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
|
||||
onUpdateSettings,
|
||||
audioLevel = 0,
|
||||
debugInfo,
|
||||
isListening = false
|
||||
isListening = false,
|
||||
onMicToggle
|
||||
}) => {
|
||||
const [showDebugInfo, setShowDebugInfo] = useState(false);
|
||||
|
||||
@@ -166,6 +168,26 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
|
||||
|
||||
<hr style={{ borderColor: 'rgba(255,255,255,0.1)', margin: '24px 0' }} />
|
||||
|
||||
{/* Microphone Toggle */}
|
||||
<div className="control-group" style={{ marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<label className="control-label" style={{ marginBottom: 0, fontSize: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
{isListening ? <Mic size={18} /> : <MicOff size={18} />}
|
||||
<span>Microphone</span>
|
||||
</label>
|
||||
<button
|
||||
className={`switch-button ${isListening ? 'active' : ''}`}
|
||||
onClick={onMicToggle}
|
||||
title={isListening ? "Turn Off Microphone" : "Turn On Microphone"}
|
||||
>
|
||||
<div className="switch-thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<p style={{ fontSize: '12px', opacity: 0.7, margin: 0 }}>
|
||||
{isListening ? 'Microphone is active and listening for notes.' : 'Enable microphone to detect your playing.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Mic Sensitivity */}
|
||||
<div className="control-group">
|
||||
<label className="control-label" style={{ marginBottom: '12px', fontSize: '16px' }}>
|
||||
|
||||
Reference in New Issue
Block a user