diff --git a/web/src/components/CountdownRing.tsx b/web/src/components/CountdownRing.tsx
new file mode 100644
index 0000000..6ac8358
--- /dev/null
+++ b/web/src/components/CountdownRing.tsx
@@ -0,0 +1,68 @@
+'use client';
+
+interface CountdownRingProps {
+ progress: number; // 0-1, where 1 = full, 0 = empty
+ size?: number;
+ strokeWidth?: number;
+ color?: string;
+ trackColor?: string;
+ children?: React.ReactNode;
+}
+
+export function CountdownRing({
+ progress,
+ size = 200,
+ strokeWidth = 8,
+ color,
+ trackColor = 'var(--cm-surface-muted)',
+ children,
+}: CountdownRingProps) {
+ const radius = (size - strokeWidth) / 2;
+ const circumference = 2 * Math.PI * radius;
+ const offset = circumference * (1 - Math.min(1, Math.max(0, progress)));
+
+ // Color transitions: green → yellow → orange → red
+ const dynamicColor = color ?? getProgressColor(progress);
+
+ return (
+
+
+ {/* Center content */}
+ {children && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+function getProgressColor(progress: number): string {
+ if (progress > 0.6) return 'var(--cm-gentle)'; // green
+ if (progress > 0.3) return 'var(--cm-standard)'; // yellow
+ if (progress > 0.1) return 'var(--cm-important)'; // orange
+ return 'var(--cm-critical)'; // red
+}
diff --git a/web/src/components/Dashboard.tsx b/web/src/components/Dashboard.tsx
index b33fbd9..908c74d 100644
--- a/web/src/components/Dashboard.tsx
+++ b/web/src/components/Dashboard.tsx
@@ -1,24 +1,50 @@
'use client';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { useTimerStore } from '@/lib/store';
import { useTickLoop } from '@/lib/use-tick';
+import { useKeyboardShortcuts, SHORTCUT_MAP } from '@/lib/use-keyboard-shortcuts';
import { TimerCard } from './TimerCard';
+import { PomodoroView } from './PomodoroView';
+import { QuickTimerBar } from './QuickTimerBar';
import { CreateTimerModal } from './CreateTimerModal';
import { AlarmOverlay } from './AlarmOverlay';
import { requestNotificationPermission } from '@/lib/notifications';
import { formatTime, formatDate } from '@/lib/format';
-import { Plus, Clock, Bell } from 'lucide-react';
+import { Plus, Clock, Bell, Keyboard } from 'lucide-react';
export function Dashboard() {
const [isCreateOpen, setIsCreateOpen] = useState(false);
+ const [showShortcuts, setShowShortcuts] = useState(false);
const [mounted, setMounted] = useState(false);
const timers = useTimerStore((s) => s.timers);
const now = useTimerStore((s) => s.now);
+ const { pause, resume } = useTimerStore();
// Start the tick loop
useTickLoop();
+ // Keyboard shortcuts
+ const getFirstActiveTimer = useCallback(() => {
+ return timers.find((t) => ['active', 'warning', 'paused'].includes(t.state));
+ }, [timers]);
+
+ useKeyboardShortcuts({
+ onNewTimer: () => setIsCreateOpen(true),
+ onQuickTimer: () => setIsCreateOpen(true),
+ onTogglePause: () => {
+ const t = getFirstActiveTimer();
+ if (!t) return;
+ if (t.state === 'paused') resume(t.id);
+ else if (t.type !== 'alarm') pause(t.id);
+ },
+ onDismiss: () => {
+ if (isCreateOpen) setIsCreateOpen(false);
+ else if (showShortcuts) setShowShortcuts(false);
+ },
+ onShowHelp: () => setShowShortcuts((p) => !p),
+ });
+
// Hydration guard
useEffect(() => {
setMounted(true);
@@ -84,6 +110,14 @@ export function Dashboard() {
{formatTime(now)} · {formatDate(now)}
+