Add mobile remote control interface
This commit is contained in:
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-19
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+19
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -25,8 +25,8 @@
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-Cax2IsI8.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CxSjc_eW.css">
|
||||
<script type="module" crossorigin src="/assets/index-DKJsxQTf.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CFG2ZxhD.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+131
@@ -6,6 +6,10 @@ import { soundEngine } from './utils/sound';
|
||||
type TimerMode = 'clock' | 'countdown' | 'interval';
|
||||
type FocusTarget = 'menu-clock' | 'menu-countdown' | 'menu-interval' | 'menu-sound' | 'start' | 'pause' | 'resume' | 'stop' | 'skip-back' | 'skip-next' | 'work-dec' | 'work-inc' | 'rest-dec' | 'rest-inc' | 'rounds-dec' | 'rounds-inc' | null;
|
||||
|
||||
function isMobileDevice(): boolean {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 768;
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
@@ -38,6 +42,11 @@ export default function App() {
|
||||
const [clockTime, setClockTime] = useState(new Date());
|
||||
const [showControls, setShowControls] = useState(true);
|
||||
const [focusedEl, setFocusedEl] = useState<FocusTarget>('start');
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMobile(isMobileDevice());
|
||||
}, []);
|
||||
|
||||
const timerMode: 'interval' | 'countdown' = mode === 'clock' ? 'interval' : mode;
|
||||
|
||||
@@ -610,6 +619,128 @@ export default function App() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile Remote Control */}
|
||||
{isMobile && (
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-[#0F172A] border-t border-[#334155] p-4 pb-8 z-40">
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Mode Selection */}
|
||||
<div className="flex justify-center gap-2">
|
||||
{(['clock', 'countdown', 'interval'] as TimerMode[]).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => handleModeChange(m)}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all ${
|
||||
mode === m
|
||||
? 'bg-[#34D399] text-[#0F172A]'
|
||||
: 'bg-[#1E293B] text-[#94A3B8] border border-[#334155]'
|
||||
}`}
|
||||
>
|
||||
{m === 'clock' ? 'UHR' : m === 'countdown' ? 'COUNTDOWN' : 'INTERVAL'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timer Display */}
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-[#94A3B8]">{getPhaseLabel(phase)}</div>
|
||||
<div className="text-5xl font-bold text-white my-2">{formatTime(secondsRemaining)}</div>
|
||||
{mode === 'interval' && phase !== 'idle' && (
|
||||
<div className="text-[#94A3B8]">RUNDE {currentRound} / {config.rounds}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex justify-center gap-4">
|
||||
{phase === 'idle' || phase === 'complete' ? (
|
||||
<button
|
||||
onClick={() => { soundEngine.init(); start(); }}
|
||||
className="flex-1 bg-[#34D399] text-[#0F172A] py-4 rounded-xl font-bold text-xl"
|
||||
>
|
||||
START
|
||||
</button>
|
||||
) : isRunning ? (
|
||||
<button
|
||||
onClick={pause}
|
||||
className="flex-1 bg-[#F59E0B] text-[#0F172A] py-4 rounded-xl font-bold text-xl"
|
||||
>
|
||||
PAUSE
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => { soundEngine.init(); resume(); }}
|
||||
className="flex-1 bg-[#34D399] text-[#0F172A] py-4 rounded-xl font-bold text-xl"
|
||||
>
|
||||
WEITER
|
||||
</button>
|
||||
)}
|
||||
{phase !== 'idle' && (
|
||||
<button
|
||||
onClick={stop}
|
||||
className="px-8 bg-red-600 text-white py-4 rounded-xl font-bold text-xl"
|
||||
>
|
||||
STOPP
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Settings */}
|
||||
{mode !== 'clock' && phase === 'idle' && (
|
||||
<div className="bg-[#1E293B] rounded-xl p-4">
|
||||
{mode === 'interval' ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[#94A3B8]">ARBEIT</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => handleConfigChange('workSeconds', Math.max(5, config.workSeconds - 5))} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">-</button>
|
||||
<span className="w-16 text-center text-white font-bold">{config.workSeconds}s</span>
|
||||
<button onClick={() => handleConfigChange('workSeconds', config.workSeconds + 5)} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[#94A3B8]">PAUSE</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => handleConfigChange('restSeconds', Math.max(0, config.restSeconds - 5))} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">-</button>
|
||||
<span className="w-16 text-center text-white font-bold">{config.restSeconds}s</span>
|
||||
<button onClick={() => handleConfigChange('restSeconds', config.restSeconds + 5)} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[#94A3B8]">RUNDEN</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => handleConfigChange('rounds', Math.max(1, config.rounds - 1))} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">-</button>
|
||||
<span className="w-16 text-center text-white font-bold">{config.rounds}</span>
|
||||
<button onClick={() => handleConfigChange('rounds', config.rounds + 1)} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[#94A3B8]">DAUER</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => handleConfigChange('workSeconds', Math.max(5, config.workSeconds - 5))} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">-</button>
|
||||
<span className="w-20 text-center text-white font-bold">{config.workSeconds}s</span>
|
||||
<button onClick={() => handleConfigChange('workSeconds', config.workSeconds + 5)} className="w-10 h-10 bg-[#334155] rounded-lg text-white text-xl font-bold">+</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sound Toggle */}
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
onClick={() => setSoundEnabled(!soundEnabled)}
|
||||
className={`px-6 py-2 rounded-lg font-medium ${
|
||||
soundEnabled ? 'bg-[#34D399] text-[#0F172A]' : 'bg-[#334155] text-[#94A3B8]'
|
||||
}`}
|
||||
>
|
||||
SOUND {soundEnabled ? 'AN' : 'AUS'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user