mobile UI

This commit is contained in:
2025-12-30 20:17:35 +01:00
parent 7a272175d9
commit aea2442fd6
7 changed files with 102 additions and 26 deletions

View File

@@ -465,6 +465,8 @@ button {
width: 90%; width: 90%;
position: relative; position: relative;
border: 1px solid rgba(128, 128, 128, 0.1); border: 1px solid rgba(128, 128, 128, 0.1);
max-height: 85vh;
overflow-y: auto;
} }
.help-item { .help-item {
@@ -781,7 +783,7 @@ button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #2a2a2a; /* background: #2a2a2a; Removed hardcoded background to respect theme */
min-height: 100px; min-height: 100px;
/* Reduced to fit on screen */ /* Reduced to fit on screen */
max-height: 50vh; max-height: 50vh;

View File

@@ -61,7 +61,8 @@ function App() {
virtualGuitarVolume: 0.5, virtualGuitarVolume: 0.5,
virtualGuitarMute: false, virtualGuitarMute: false,
micSensitivity: 0.5, micSensitivity: 0.5,
disableAnimation: false disableAnimation: false,
theme: 'auto'
}); });
const { pitchData, error, audioLevel, debugInfo, isListening } = usePitchDetector(listening, settings.micSensitivity); const { pitchData, error, audioLevel, debugInfo, isListening } = usePitchDetector(listening, settings.micSensitivity);
@@ -85,6 +86,17 @@ function App() {
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
// Theme support
useEffect(() => {
const root = document.documentElement;
const theme = settings.theme || 'auto';
if (theme === 'auto') {
root.removeAttribute('data-theme');
} else {
root.setAttribute('data-theme', theme);
}
}, [settings.theme]);
useEffect(() => { useEffect(() => {
const handleFullscreenChange = () => { const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement); setIsFullscreen(!!document.fullscreenElement);

View File

@@ -35,6 +35,7 @@ export interface AppSettings {
micSensitivity?: number; // 0.0 to 1.0 (0=least sensitive, 1=most) micSensitivity?: number; // 0.0 to 1.0 (0=least sensitive, 1=most)
virtualGuitarMute?: boolean; virtualGuitarMute?: boolean;
disableAnimation?: boolean; disableAnimation?: boolean;
theme?: 'light' | 'dark' | 'auto';
} }
interface ControlsProps { interface ControlsProps {

View File

@@ -99,7 +99,8 @@ export function Fretboard({
y={paddingY} y={paddingY}
width={width - (paddingX * 2) - nutWidth} width={width - (paddingX * 2) - nutWidth}
height={height - (paddingY * 2)} height={height - (paddingY * 2)}
fill="#999999" fill="var(--color-text-muted)"
fillOpacity={0.3}
stroke="none" stroke="none"
/> />
@@ -109,7 +110,7 @@ export function Fretboard({
y={paddingY} y={paddingY}
width={nutWidth} width={nutWidth}
height={height - (paddingY * 2)} height={height - (paddingY * 2)}
fill="#333" fill="var(--color-primary)"
/> />
{/* Fret Markers (Dots) */} {/* Fret Markers (Dots) */}
@@ -120,13 +121,13 @@ export function Fretboard({
if (isDouble) { if (isDouble) {
return ( return (
<g key={`marker-${m}`}> <g key={`marker-${m}`}>
<circle cx={cx} cy={height / 2 - stringSpacing} r={6} fill="#777" /> <circle cx={cx} cy={height / 2 - stringSpacing} r={6} fill="var(--color-bg)" />
<circle cx={cx} cy={height / 2 + stringSpacing} r={6} fill="#777" /> <circle cx={cx} cy={height / 2 + stringSpacing} r={6} fill="var(--color-bg)" />
</g> </g>
); );
} }
return ( return (
<circle key={`marker-${m}`} cx={cx} cy={height / 2} r={6} fill="#777" /> <circle key={`marker-${m}`} cx={cx} cy={height / 2} r={6} fill="var(--color-bg)" />
); );
})} })}
@@ -141,7 +142,7 @@ export function Fretboard({
y1={paddingY} y1={paddingY}
x2={x} x2={x}
y2={height - paddingY} y2={height - paddingY}
stroke="#CCCCCC" stroke="var(--color-text-muted)"
strokeWidth={1} strokeWidth={1}
/> />
); );
@@ -158,7 +159,7 @@ export function Fretboard({
y1={y} y1={y}
x2={width - paddingX} x2={width - paddingX}
y2={y} y2={y}
stroke="#EEEEEE" stroke="var(--color-text-main)"
strokeWidth={Math.max(1.5, visualThickness)} strokeWidth={Math.max(1.5, visualThickness)}
/> />
); );

View File

@@ -157,12 +157,12 @@ export function PianoKeys({
Range: {keys.list[0]?.midi} - {keys.list[keys.list.length - 1]?.midi} Range: {keys.list[0]?.midi} - {keys.list[keys.list.length - 1]?.midi}
</div> </div>
<div className="view-toggles" style={{ display: 'flex', gap: '8px', background: '#333', padding: '2px', borderRadius: '4px' }}> <div className="view-toggles" style={{ display: 'flex', gap: '8px', background: 'var(--color-surface)', padding: '2px', borderRadius: '4px', border: '1px solid var(--color-text-muted)' }}>
<button <button
onClick={() => setViewMode('zoomed')} onClick={() => setViewMode('zoomed')}
style={{ style={{
background: viewMode === 'zoomed' ? '#666' : 'transparent', background: viewMode === 'zoomed' ? 'var(--color-primary)' : 'transparent',
border: 'none', color: 'white', padding: '2px 8px', borderRadius: '2px', cursor: 'pointer', fontSize: '10px' border: 'none', color: viewMode === 'zoomed' ? 'var(--color-bg)' : 'var(--color-text-main)', padding: '2px 8px', borderRadius: '2px', cursor: 'pointer', fontSize: '10px'
}} }}
> >
Zoom Zoom
@@ -170,8 +170,8 @@ export function PianoKeys({
<button <button
onClick={() => setViewMode('full')} onClick={() => setViewMode('full')}
style={{ style={{
background: viewMode === 'full' ? '#666' : 'transparent', background: viewMode === 'full' ? 'var(--color-primary)' : 'transparent',
border: 'none', color: 'white', padding: '2px 8px', borderRadius: '2px', cursor: 'pointer', fontSize: '10px' border: 'none', color: viewMode === 'full' ? 'var(--color-bg)' : 'var(--color-text-main)', padding: '2px 8px', borderRadius: '2px', cursor: 'pointer', fontSize: '10px'
}} }}
> >
Full Full
@@ -187,8 +187,8 @@ export function PianoKeys({
style={{ style={{
width: '100%', width: '100%',
overflowX: viewMode === 'full' ? 'hidden' : 'auto', overflowX: viewMode === 'full' ? 'hidden' : 'auto',
borderTop: '1px solid #444', borderTop: '1px solid var(--color-text-muted)',
background: '#222', background: 'var(--color-bg)',
position: 'relative' position: 'relative'
}} }}
> >
@@ -208,14 +208,14 @@ export function PianoKeys({
const isMarked = markedNotes.includes(k.midi); const isMarked = markedNotes.includes(k.midi);
// Styling // Styling
let fill = k.isBlack ? '#222' : '#fff'; let fill = k.isBlack ? 'var(--color-primary)' : 'var(--color-surface)';
if (isMarked) { if (isMarked) {
fill = k.isBlack ? '#d32f2f' : '#ffcdd2'; // Red-ish for marked fill = k.isBlack ? 'var(--color-error)' : 'var(--color-success)'; // Use theme vars
} else if (interactive && hoverMidi === k.midi) { } else if (interactive && hoverMidi === k.midi) {
fill = k.isBlack ? '#444' : '#eee'; // Subtle hover fill = k.isBlack ? 'var(--color-text-muted)' : 'var(--color-bg)'; // Subtle hover
} }
const stroke = '#000'; const stroke = 'var(--color-text-main)';
const rectHeight = k.isBlack ? BLACK_KEY_HEIGHT_PERCENT * 100 : 100; const rectHeight = k.isBlack ? BLACK_KEY_HEIGHT_PERCENT * 100 : 100;
return ( return (

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { X, Volume2, VolumeX, ChevronDown, ChevronUp, Mic, MicOff } from 'lucide-react'; import { X, Volume2, VolumeX, ChevronDown, ChevronUp, Mic, MicOff, Sun, Moon, Monitor } from 'lucide-react';
import type { AppSettings } from './Controls'; import type { AppSettings } from './Controls';
import type { MicrophoneDebugInfo } from '../hooks/usePitchDetector'; import type { MicrophoneDebugInfo } from '../hooks/usePitchDetector';
@@ -72,6 +72,47 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({
<button className="help-close" onClick={onClose}><X size={20} /></button> <button className="help-close" onClick={onClose}><X size={20} /></button>
<h2 style={{ marginTop: 0, marginBottom: '24px' }}>Settings</h2> <h2 style={{ marginTop: 0, marginBottom: '24px' }}>Settings</h2>
{/* Theme Settings */}
<div className="control-group" style={{ marginBottom: '24px' }}>
<label className="control-label" style={{ marginBottom: '8px', fontSize: '16px' }}>
<span>Theme</span>
</label>
<div style={{ display: 'flex', background: 'rgba(128,128,128,0.1)', padding: '4px', borderRadius: '8px', gap: '4px' }}>
{[
{ id: 'light', icon: Sun, label: 'Light' },
{ id: 'dark', icon: Moon, label: 'Dark' },
{ id: 'auto', icon: Monitor, label: 'Auto' }
].map(mode => (
<button
key={mode.id}
onClick={() => onUpdateSettings({ ...settings, theme: mode.id as any })}
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
padding: '8px',
border: 'none',
background: (settings.theme || 'auto') === mode.id ? 'var(--color-surface)' : 'transparent',
color: (settings.theme || 'auto') === mode.id ? 'var(--color-primary)' : 'var(--color-text-muted)',
borderRadius: '6px',
cursor: 'pointer',
boxShadow: (settings.theme || 'auto') === mode.id ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
fontWeight: 500,
fontSize: '13px',
transition: 'all 0.2s'
}}
>
<mode.icon size={16} />
{mode.label}
</button>
))}
</div>
</div>
<hr style={{ borderColor: 'rgba(255,255,255,0.1)', margin: '24px 0' }} />
{/* Animation Settings */} {/* Animation Settings */}
<div className="control-group" style={{ marginBottom: '24px' }}> <div className="control-group" style={{ marginBottom: '24px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>

View File

@@ -9,17 +9,14 @@
--hue-text: 30; --hue-text: 30;
/* Warmer text */ /* Warmer text */
/* Default Light Theme */
--color-bg: hsl(30, 20%, 97%); --color-bg: hsl(30, 20%, 97%);
/* Warm paper-like bg */ /* Warm paper-like bg */
--color-surface: hsl(0, 0%, 100%); --color-surface: hsl(0, 0%, 100%);
/* Neutral / Black Accent */
--color-primary: hsl(0, 0%, 20%); --color-primary: hsl(0, 0%, 20%);
--color-primary-dark: hsl(0, 0%, 0%); --color-primary-dark: hsl(0, 0%, 0%);
--color-text-main: hsl(var(--hue-text), 10%, 20%); --color-text-main: hsl(var(--hue-text), 10%, 20%);
--color-text-muted: hsl(var(--hue-text), 5%, 60%); --color-text-muted: hsl(var(--hue-text), 5%, 60%);
--color-success: hsl(var(--hue-success), 60%, 45%); --color-success: hsl(var(--hue-success), 60%, 45%);
--color-error: hsl(var(--hue-error), 70%, 55%); --color-error: hsl(var(--hue-error), 70%, 55%);
--color-warning: hsl(35, 90%, 50%); --color-warning: hsl(35, 90%, 50%);
@@ -52,18 +49,40 @@
--radius-lg: 24px; --radius-lg: 24px;
} }
/* Auto Dark Mode (System Preference) */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--color-bg: hsl(30, 20%, 10%); --color-bg: hsl(30, 20%, 10%);
--color-surface: hsl(30, 20%, 15%); --color-surface: hsl(30, 20%, 15%);
--color-text-main: hsl(30, 10%, 90%); --color-text-main: hsl(30, 10%, 90%);
--color-text-muted: hsl(30, 10%, 60%); --color-text-muted: hsl(30, 10%, 60%);
/* In dark mode, 'primary' (black) matches background too much, so we invert or use white */
--color-primary: hsl(0, 0%, 90%); --color-primary: hsl(0, 0%, 90%);
--color-primary-dark: hsl(0, 0%, 100%); --color-primary-dark: hsl(0, 0%, 100%);
} }
} }
/* Manual Dark Mode Override */
html[data-theme="dark"] {
--color-bg: hsl(30, 20%, 10%);
--color-surface: hsl(30, 20%, 15%);
--color-text-main: hsl(30, 10%, 90%);
--color-text-muted: hsl(30, 10%, 60%);
--color-primary: hsl(0, 0%, 90%);
--color-primary-dark: hsl(0, 0%, 100%);
}
/* Manual Light Mode Override */
html[data-theme="light"] {
--color-bg: hsl(30, 20%, 97%);
--color-surface: hsl(0, 0%, 100%);
--color-primary: hsl(0, 0%, 20%);
--color-primary-dark: hsl(0, 0%, 0%);
--color-text-main: hsl(var(--hue-text), 10%, 20%);
--color-text-muted: hsl(var(--hue-text), 5%, 60%);
}
body { body {
margin: 0; margin: 0;
font-family: var(--font-family); font-family: var(--font-family);