From fb736eddbcb3cfdd91ad22c2e08666dad6d0fc6c Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 11:02:14 -0700 Subject: [PATCH 01/10] Full screen timer option --- client/src/pages/tools/timer.tsx | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 75130d96..45eb4040 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -23,6 +23,7 @@ import { X, Timer as TimerIcon, VolumeX, + Expand, } from "lucide-react"; import { formatTimerTime, @@ -62,6 +63,9 @@ export default function Timer() { const [newTimerAlarmCount, setNewTimerAlarmCount] = useState(3); const [timers, setTimers] = useState([]); const [showAddTimer, setShowAddTimer] = useState(false); + const [fullscreenTimerId, setFullscreenTimerId] = useState( + null + ); // Track which timers were paused by the global pause so we can resume only those const [pausedByGlobal, setPausedByGlobal] = useState>(new Set()); @@ -75,6 +79,43 @@ export default function Timer() { release: releaseWakeLock, } = useWakeLock(); + const toggleFullscreen = (id: string) => { + if (fullscreenTimerId === id) { + setFullscreenTimerId(null); + if (document.fullscreenElement) { + void document.exitFullscreen(); + } + } else { + setFullscreenTimerId(id); + } + }; + + useEffect(() => { + if (fullscreenTimerId) { + const element = document.getElementById( + `timer-card-${fullscreenTimerId}` + ); + if (element && !document.fullscreenElement) { + void element.requestFullscreen().catch(err => { + console.error("Fullscreen request failed:", err); + setFullscreenTimerId(null); // Reset on failure + }); + } + } else if (document.fullscreenElement) { + void document.exitFullscreen(); + } + + const handleFullscreenChange = () => { + if (!document.fullscreenElement) { + setFullscreenTimerId(null); + } + }; + + document.addEventListener("fullscreenchange", handleFullscreenChange); + return () => + document.removeEventListener("fullscreenchange", handleFullscreenChange); + }, [fullscreenTimerId]); + // Effect to manage the wake lock useEffect(() => { const hasRunningTimers = timers.some(timer => timer.isRunning); @@ -623,6 +664,29 @@ export default function Timer() { // Check if there's modified data const hasModifiedData = timers.length > 0 || showAddTimer; + const fullscreenTimer = timers.find(t => t.id === fullscreenTimerId); + + if (fullscreenTimer) { + return ( +
+
+
+ {formatTimerTime(fullscreenTimer.timeLeft)} +
+
+ {fullscreenTimer.name} +
+ +
+
+ ); + } + return (
{/* Header */} @@ -839,6 +903,7 @@ export default function Timer() { {timers.map(timer => ( @@ -859,6 +924,15 @@ export default function Timer() {
Alarm: {getAlarmText(timer.alarmCount)}
+ - -
- - - -
-
- {formatTimerTime(timer.timeLeft)} -
- -
- {!timer.isFinished ? ( + {timers.map(timer => { + const percentComplete = + timer.duration > 0 && timer.leadTimeRemaining === 0 + ? Math.min( + 100, + Math.round( + ((timer.duration - Math.max(0, timer.timeLeft)) / + timer.duration) * + 100 + ) + ) + : 0; + let progressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; + if (timer.isFinished) progressColor = "#ef4444"; + else if (timer.leadTimeRemaining > 0) progressColor = "#f59e0b"; + const trackColor = theme === "dark" ? "#334155" : "#e2e8f0"; + + return ( + + +
+ + + {timer.name} + {(() => { + if (timer.isFinished) { + if (timer.overrunEnabled && timer.isRunning) { + return ( + + Overtime + + ); + } + return ( + + Finished + + ); + } + if (timer.isRunning && timer.leadTimeRemaining > 0) { + return ( + + Starting + + ); + } + if (timer.isRunning) { + return Running; + } + return null; + })()} + +
+
+ Alarm: {getAlarmText(timer.alarmCount)} +
+ +
+
+
+ +
+ {/* Time display + optional pie chart */} +
+ {showPieChart ? ( +
+ + + + + + +
+ + {percentComplete}% + + + done + +
+
+ ) : null} +
+ {timer.leadTimeRemaining > 0 ? ( +
+
+ {timer.leadTimeRemaining} +
+
+ Starting soon… +
+
) : ( - <> - - {timer.timeLeft < timer.duration - ? "Continue" - : "Start"} - +
+ {timer.timeLeft < 0 ? "-" : ""} + {formatTimerTime(Math.abs(timer.timeLeft))} +
)} - - ) : ( -
- - {alarmIntervalsRef.current.has(timer.id) && ( +
+
+ + {/* Control buttons */} +
+ {(() => { + if (!timer.isFinished) { + return ( + + ); + } + const stopAlarmButton = alarmIntervalsRef.current.has( + timer.id + ) ? ( - )} -
- )} -
+ ) : null; + if (timer.overrunEnabled) { + return ( +
+ + + {stopAlarmButton} +
+ ); + } + return ( +
+ + {stopAlarmButton} +
+ ); + })()} +
-
- Original duration: {formatTimerTime(timer.duration)} +
+ Original duration: {formatTimerTime(timer.duration)} + {timer.leadTime > 0 ? ` • Lead: ${timer.leadTime}s` : ""} +
-
- - - ))} + + + ); + })}
{/* Empty State */} From bdc1e6bdb88e74ba96adfba87596da316838b754 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 12:04:08 -0700 Subject: [PATCH 03/10] pie chart on fulls creen --- client/src/pages/tools/timer.tsx | 52 +++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 5ed69f0c..2d94278d 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -790,11 +790,56 @@ export default function Timer() { const fullscreenTimer = timers.find(t => t.id === fullscreenTimerId); if (fullscreenTimer) { + const fsPercent = + fullscreenTimer.duration > 0 && fullscreenTimer.leadTimeRemaining === 0 + ? Math.min( + 100, + Math.round( + ((fullscreenTimer.duration - + Math.max(0, fullscreenTimer.timeLeft)) / + fullscreenTimer.duration) * + 100 + ) + ) + : 0; + let fsProgressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; + if (fullscreenTimer.isFinished) fsProgressColor = "#ef4444"; + else if (fullscreenTimer.leadTimeRemaining > 0) fsProgressColor = "#f59e0b"; + const fsTrackColor = theme === "dark" ? "#334155" : "#e2e8f0"; + return (
+ {showPieChart ? ( +
+ + + + + + +
+ + {fsPercent}% + +
+
+ ) : null}
{fullscreenTimer.timeLeft < 0 ? "-" : ""} @@ -1204,13 +1249,10 @@ export default function Timer() { -
+
{percentComplete}% - - done -
) : null} From 419a0902bc3c72a8d4be8d9b0e7afa0427253099 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 12:42:36 -0700 Subject: [PATCH 04/10] Put the donut around timer --- client/src/pages/tools/timer.tsx | 166 ++++++++++++++----------------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 2d94278d..355a8f8a 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -73,7 +73,6 @@ export default function Timer() { const [newTimerOverrunEnabled, setNewTimerOverrunEnabled] = useState(false); const [newTimerLeadTime, setNewTimerLeadTime] = useState(0); const [newTimerBeepAtQuartiles, setNewTimerBeepAtQuartiles] = useState(true); - const [showPieChart, setShowPieChart] = useState(true); const [timers, setTimers] = useState([]); const [showAddTimer, setShowAddTimer] = useState(false); const [fullscreenTimerId, setFullscreenTimerId] = useState( @@ -812,34 +811,32 @@ export default function Timer() { id={`timer-card-${fullscreenTimer.id}`} className="bg-white dark:bg-black w-screen h-screen flex flex-col items-center justify-center gap-10" > - {showPieChart ? ( -
- - - - - - -
- - {fsPercent}% - -
+
+ + + + + + +
+ + {fsPercent}% +
- ) : null} +
{fullscreenTimer.timeLeft < 0 ? "-" : ""} @@ -968,22 +965,6 @@ export default function Timer() { - {/* Display options */} -
- - -
- {/* Add Timer Form */} {showAddTimer ? ( @@ -1226,58 +1207,55 @@ export default function Timer() {
- {/* Time display + optional pie chart */} -
- {showPieChart ? ( -
- - +
+ + + + + + +
+ {timer.leadTimeRemaining > 0 ? ( + <> +
+ {timer.leadTimeRemaining} +
+
+ Starting soon… +
+ + ) : ( +
- - - - -
- - {percentComplete}% - -
-
- ) : null} -
- {timer.leadTimeRemaining > 0 ? ( -
-
- {timer.leadTimeRemaining} + {timer.timeLeft < 0 ? "-" : ""} + {formatTimerTime(Math.abs(timer.timeLeft))}
-
- Starting soon… -
-
- ) : ( -
- {timer.timeLeft < 0 ? "-" : ""} - {formatTimerTime(Math.abs(timer.timeLeft))} -
- )} + )} +
From 6d3421299cf2331cb8d8ad0ffb85e2042ce35f74 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 14:01:46 -0700 Subject: [PATCH 05/10] Add default lead time --- client/src/pages/tools/timer.tsx | 67 ++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index 355a8f8a..bd35b40f 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -71,7 +71,7 @@ export default function Timer() { const [newTimerName, setNewTimerName] = useState(""); const [newTimerAlarmCount, setNewTimerAlarmCount] = useState(3); const [newTimerOverrunEnabled, setNewTimerOverrunEnabled] = useState(false); - const [newTimerLeadTime, setNewTimerLeadTime] = useState(0); + const [newTimerLeadTime, setNewTimerLeadTime] = useState(5); const [newTimerBeepAtQuartiles, setNewTimerBeepAtQuartiles] = useState(true); const [timers, setTimers] = useState([]); const [showAddTimer, setShowAddTimer] = useState(false); @@ -80,10 +80,16 @@ export default function Timer() { ); // Track which timers were paused by the global pause so we can resume only those const [pausedByGlobal, setPausedByGlobal] = useState>(new Set()); + // Track which timers are currently flashing red (milestone or finish) + const [flashingTimers, setFlashingTimers] = useState>(new Set()); const intervalRef = useRef(null); const audioContextRef = useRef(null); const alarmIntervalsRef = useRef>(new Map()); + const flashClearTimeoutsRef = useRef>(new Map()); + const prevTimerStateRef = useRef< + Map + >(new Map()); const { toast } = useToast(); const { theme } = useTheme(); const { @@ -264,7 +270,7 @@ export default function Timer() { setNewTimerSeconds(0); setNewTimerAlarmCount(3); setNewTimerOverrunEnabled(false); - setNewTimerLeadTime(0); + setNewTimerLeadTime(5); setNewTimerBeepAtQuartiles(true); setShowAddTimer(false); @@ -504,9 +510,54 @@ export default function Timer() { // Clear all alarm intervals using captured ref value intervals.forEach(interval => clearAlarmHandle(interval)); intervals.clear(); + // Clear all flash timeouts using captured ref value + const flashTimeouts = flashClearTimeoutsRef.current; + flashTimeouts.forEach(t => clearTimeout(t as unknown as number)); + flashTimeouts.clear(); }; }, [clearAlarmHandle]); + // Detect milestone/finish transitions and trigger a 1-second red flash + useEffect(() => { + timers.forEach(timer => { + const prev = prevTimerStateRef.current.get(timer.id); + const prevLen = prev?.quartileLength ?? 0; + const prevFinished = prev?.isFinished ?? false; + + const justHitMilestone = timer.quartilesBeeped.length > prevLen; + const justFinished = !prevFinished && timer.isFinished; + + if (justHitMilestone || justFinished) { + // Cancel any in-progress flash clear for this timer + const existing = flashClearTimeoutsRef.current.get(timer.id); + if (existing) { + clearTimeout(existing as unknown as number); + flashClearTimeoutsRef.current.delete(timer.id); + } + + setFlashingTimers(prev => new Set(Array.from(prev).concat(timer.id))); + + // Milestones clear after 1s; finished state stays red permanently via isFinished + if (!justFinished) { + const t = setTimeout(() => { + setFlashingTimers(prev => { + const next = new Set(prev); + next.delete(timer.id); + return next; + }); + flashClearTimeoutsRef.current.delete(timer.id); + }, 1000) as unknown as NodeJS.Timeout; + flashClearTimeoutsRef.current.set(timer.id, t); + } + } + + prevTimerStateRef.current.set(timer.id, { + quartileLength: timer.quartilesBeeped.length, + isFinished: timer.isFinished, + }); + }); + }, [timers]); + // Main timer update loop useEffect(() => { if (timers.some(timer => timer.isRunning)) { @@ -752,7 +803,7 @@ export default function Timer() { setNewTimerName(""); setNewTimerAlarmCount(3); setNewTimerOverrunEnabled(false); - setNewTimerLeadTime(0); + setNewTimerLeadTime(5); setNewTimerBeepAtQuartiles(true); setShowAddTimer(false); toast({ @@ -778,7 +829,7 @@ export default function Timer() { newTimerSeconds === 0 && newTimerName === "" && newTimerAlarmCount === 3 && - newTimerLeadTime === 0 && + newTimerLeadTime === 5 && !newTimerOverrunEnabled && newTimerBeepAtQuartiles && !showAddTimer; @@ -801,8 +852,9 @@ export default function Timer() { ) ) : 0; + const fsIsFlashing = flashingTimers.has(fullscreenTimer.id); let fsProgressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; - if (fullscreenTimer.isFinished) fsProgressColor = "#ef4444"; + if (fullscreenTimer.isFinished || fsIsFlashing) fsProgressColor = "#ef4444"; else if (fullscreenTimer.leadTimeRemaining > 0) fsProgressColor = "#f59e0b"; const fsTrackColor = theme === "dark" ? "#334155" : "#e2e8f0"; @@ -1136,8 +1188,9 @@ export default function Timer() { ) ) : 0; + const isFlashing = flashingTimers.has(timer.id); let progressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; - if (timer.isFinished) progressColor = "#ef4444"; + if (timer.isFinished || isFlashing) progressColor = "#ef4444"; else if (timer.leadTimeRemaining > 0) progressColor = "#f59e0b"; const trackColor = theme === "dark" ? "#334155" : "#e2e8f0"; @@ -1246,7 +1299,7 @@ export default function Timer() { ) : (
Date: Thu, 16 Jul 2026 14:13:11 -0700 Subject: [PATCH 06/10] show percent complete at center --- client/src/pages/tools/timer.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index bd35b40f..a55823d0 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -864,7 +864,11 @@ export default function Timer() { className="bg-white dark:bg-black w-screen h-screen flex flex-col items-center justify-center gap-10" >
- + -
+
{fsPercent}% @@ -1309,6 +1313,19 @@ export default function Timer() {
)}
+ {timer.leadTimeRemaining === 0 && ( +
+ + {percentComplete}% + +
+ )}
From 83302c91d0e6b0a53f1dd7880318e4c41d291f48 Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 14:25:42 -0700 Subject: [PATCH 07/10] make lead time and other things part of URL --- client/src/pages/tools/timer.tsx | 47 +++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index a55823d0..ea810e71 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -274,12 +274,16 @@ export default function Timer() { setNewTimerBeepAtQuartiles(true); setShowAddTimer(false); - // Update URL with latest timer + // Update URL with latest timer settings updateURL({ h: newTimerHours, m: newTimerMinutes, s: newTimerSeconds, name, + alarm: newTimerAlarmCount, + overrun: newTimerOverrunEnabled, + lead: newTimerLeadTime, + beep: newTimerBeepAtQuartiles, }); toast({ @@ -472,14 +476,19 @@ export default function Timer() { useEffect(() => { audioContextRef.current = createTimerSound(); - // Capture ref value at effect start + // Capture ref values at effect start const intervals = alarmIntervalsRef.current; + const flashTimeouts = flashClearTimeoutsRef.current; // Load from URL parameters or create default timer const urlHours = getParam("h", 0); const urlMinutes = getParam("m", 5); const urlSeconds = getParam("s", 0); const urlName = getParam("name", ""); + const urlOverrun = getParam("overrun", false); + const urlLead = getParam("lead", 5); + const urlBeep = getParam("beep", true); + const urlAlarm = getParam("alarm", 3); if (urlHours > 0 || urlMinutes > 0 || urlSeconds > 0) { const duration = urlHours * 3600 + urlMinutes * 60 + urlSeconds; @@ -492,12 +501,12 @@ export default function Timer() { timeLeft: duration, isRunning: false, // Don't auto-start per STYLE.md for tools that make sound isFinished: false, - alarmCount: 3, + alarmCount: urlAlarm, currentAlarmPlays: 0, - overrunEnabled: false, - leadTime: 0, - leadTimeRemaining: 0, - beepAtQuartiles: true, + overrunEnabled: urlOverrun, + leadTime: urlLead, + leadTimeRemaining: urlLead, + beepAtQuartiles: urlBeep, quartilesBeeped: [], }; setTimers([defaultTimer]); @@ -511,7 +520,6 @@ export default function Timer() { intervals.forEach(interval => clearAlarmHandle(interval)); intervals.clear(); // Clear all flash timeouts using captured ref value - const flashTimeouts = flashClearTimeoutsRef.current; flashTimeouts.forEach(t => clearTimeout(t as unknown as number)); flashTimeouts.clear(); }; @@ -745,12 +753,25 @@ export default function Timer() { const hours = Math.floor(timer.duration / 3600); const minutes = Math.floor((timer.duration % 3600) / 60); const seconds = timer.duration % 60; - copyShareableURL({ h: hours, m: minutes, s: seconds, name: timer.name }); + copyShareableURL({ + h: hours, + m: minutes, + s: seconds, + name: timer.name, + alarm: timer.alarmCount, + overrun: timer.overrunEnabled, + lead: timer.leadTime, + beep: timer.beepAtQuartiles, + }); } else { copyShareableURL({ h: newTimerHours, m: newTimerMinutes, s: newTimerSeconds, + alarm: newTimerAlarmCount, + overrun: newTimerOverrunEnabled, + lead: newTimerLeadTime, + beep: newTimerBeepAtQuartiles, }); } @@ -758,7 +779,7 @@ export default function Timer() { title: "URL Copied", description: "Timer URL has been copied to clipboard", }); - }, [timers, newTimerHours, newTimerMinutes, newTimerSeconds, toast]); + }, [timers, newTimerHours, newTimerMinutes, newTimerSeconds, newTimerAlarmCount, newTimerOverrunEnabled, newTimerLeadTime, newTimerBeepAtQuartiles, toast]); const getAlarmText = (count: number) => { switch (count) { @@ -1194,7 +1215,7 @@ export default function Timer() { : 0; const isFlashing = flashingTimers.has(timer.id); let progressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; - if (timer.isFinished || isFlashing) progressColor = "#ef4444"; + if (isFlashing || (timer.isFinished && !timer.isRunning)) progressColor = "#ef4444"; else if (timer.leadTimeRemaining > 0) progressColor = "#f59e0b"; const trackColor = theme === "dark" ? "#334155" : "#e2e8f0"; @@ -1303,7 +1324,7 @@ export default function Timer() { ) : (
Date: Thu, 16 Jul 2026 14:31:20 -0700 Subject: [PATCH 08/10] show lead time properly --- client/src/pages/tools/timer.tsx | 41 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index ea810e71..f3be7d11 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -1312,21 +1312,18 @@ export default function Timer() {
- {timer.leadTimeRemaining > 0 ? ( - <> -
- {timer.leadTimeRemaining} -
-
- Starting soon… -
- + {timer.isRunning && timer.leadTimeRemaining > 0 ? ( +
+ {timer.leadTimeRemaining} +
) : (
0 + ? "text-slate-400 dark:text-slate-500" + : "text-slate-900 dark:text-slate-100" }`} > {timer.timeLeft < 0 ? "-" : ""} @@ -1334,19 +1331,17 @@ export default function Timer() {
)}
- {timer.leadTimeRemaining === 0 && ( -
- - {percentComplete}% - -
- )} +
+ + {percentComplete}% + +
From a8ef8836aeab73c0c53c60c92e36aa098c3f561a Mon Sep 17 00:00:00 2001 From: Ryan Maleki Date: Thu, 16 Jul 2026 14:40:29 -0700 Subject: [PATCH 09/10] beep when not exactly divisible by quartiles --- client/src/pages/tools/timer.tsx | 67 +++++++++++++------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/client/src/pages/tools/timer.tsx b/client/src/pages/tools/timer.tsx index f3be7d11..36203090 100644 --- a/client/src/pages/tools/timer.tsx +++ b/client/src/pages/tools/timer.tsx @@ -605,7 +605,7 @@ export default function Timer() { for (const { pct, threshold } of milestones) { if ( threshold > 0 && - newTimeLeft === threshold && + newTimeLeft <= threshold && !timer.quartilesBeeped.includes(pct) ) { updatedQuartilesBeeped = [...updatedQuartilesBeeped, pct]; @@ -779,7 +779,28 @@ export default function Timer() { title: "URL Copied", description: "Timer URL has been copied to clipboard", }); - }, [timers, newTimerHours, newTimerMinutes, newTimerSeconds, newTimerAlarmCount, newTimerOverrunEnabled, newTimerLeadTime, newTimerBeepAtQuartiles, toast]); + }, [ + timers, + newTimerHours, + newTimerMinutes, + newTimerSeconds, + newTimerAlarmCount, + newTimerOverrunEnabled, + newTimerLeadTime, + newTimerBeepAtQuartiles, + toast, + ]); + + const getTimerTextColor = ( + flashing: boolean, + isFinished: boolean, + isRunning: boolean, + leadTimeRemaining: number + ) => { + if (flashing || (isFinished && !isRunning)) return "text-red-600"; + if (leadTimeRemaining > 0) return "text-slate-400 dark:text-slate-500"; + return "text-slate-900 dark:text-slate-100"; + }; const getAlarmText = (count: number) => { switch (count) { @@ -1215,7 +1236,8 @@ export default function Timer() { : 0; const isFlashing = flashingTimers.has(timer.id); let progressColor = theme === "dark" ? "#3b82f6" : "#2563eb"; - if (isFlashing || (timer.isFinished && !timer.isRunning)) progressColor = "#ef4444"; + if (isFlashing || (timer.isFinished && !timer.isRunning)) + progressColor = "#ef4444"; else if (timer.leadTimeRemaining > 0) progressColor = "#f59e0b"; const trackColor = theme === "dark" ? "#334155" : "#e2e8f0"; @@ -1318,13 +1340,7 @@ export default function Timer() {
) : (
0 - ? "text-slate-400 dark:text-slate-500" - : "text-slate-900 dark:text-slate-100" - }`} + className={`text-6xl font-mono font-bold ${getTimerTextColor(isFlashing, timer.isFinished, timer.isRunning, timer.leadTimeRemaining)}`} > {timer.timeLeft < 0 ? "-" : ""} {formatTimerTime(Math.abs(timer.timeLeft))} @@ -1390,37 +1406,6 @@ export default function Timer() { Stop Alarm ) : null; - if (timer.overrunEnabled) { - return ( -
- - - {stopAlarmButton} -
- ); - } return (