-
Clear All Entries
-
Are you sure you want to delete all entries? This cannot be undone.
-
-
-
+
+
Clear All Entries
+
Are you sure you want to delete all entries? This action cannot be undone.
+
+
+
+
)}
diff --git a/src/components/ThemeToggle.module.css b/src/components/ThemeToggle.module.css
new file mode 100644
index 0000000..f1036e1
--- /dev/null
+++ b/src/components/ThemeToggle.module.css
@@ -0,0 +1,25 @@
+.toggle {
+ background: none;
+ border: none;
+ padding: 8px;
+ cursor: pointer;
+ border-radius: 50%;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.toggle:hover {
+ background: var(--hover-bg);
+}
+
+.toggle:active {
+ transform: scale(0.95);
+}
+
+.icon {
+ font-size: 1.25rem;
+ line-height: 1;
+ display: flex;
+}
\ No newline at end of file
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
new file mode 100644
index 0000000..f4709a5
--- /dev/null
+++ b/src/components/ThemeToggle.tsx
@@ -0,0 +1,20 @@
+import styles from './ThemeToggle.module.css'
+
+interface ThemeToggleProps {
+ theme: 'light' | 'dark'
+ onToggle: () => void
+}
+
+export function ThemeToggle({ theme, onToggle }: ThemeToggleProps) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css
index 67e5530..5fa6228 100644
--- a/src/components/Timer.module.css
+++ b/src/components/Timer.module.css
@@ -10,7 +10,7 @@
justify-content: center;
z-index: 2000;
backdrop-filter: blur(8px);
- animation: dialogAppear 0.2s ease;
+ animation: fadeIn 0.3s ease;
}
.overlay.light {
@@ -117,20 +117,22 @@
}
.timerButton {
- background: transparent;
- border: none;
- color: inherit;
+ all: unset;
cursor: pointer;
- font-size: 1rem;
- padding: 8px;
- transition: opacity 0.2s;
+ padding: 4px 8px;
+ border-radius: 6px;
+ transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 4px;
}
.timerButton:hover {
- opacity: 0.8;
+ background: rgba(255, 255, 255, 0.1);
+}
+
+:global(.lightTheme) .timerButton:hover {
+ background: rgba(0, 0, 0, 0.05);
}
.timerPopup {
@@ -154,10 +156,14 @@
}
.timerDisplay {
- font-size: 2rem;
- font-weight: 500;
- text-align: center;
- font-variant-numeric: tabular-nums;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.clockIcon {
+ font-size: 14px;
+ opacity: 0.7;
}
.timerControls {
@@ -255,6 +261,17 @@
.timerContainer {
position: relative;
+ display: flex;
+ align-items: center;
+ min-width: 50px;
+ font-variant-numeric: tabular-nums;
+ font-size: 15px;
+ opacity: 0.85;
+ transition: opacity 0.2s ease;
+}
+
+.timerContainer:hover {
+ opacity: 1;
}
.timerButton.active {
@@ -295,14 +312,19 @@
text-align: center;
}
+/* Style .timerDialog as a modal, removing absolute positioning */
.timerDialog {
+ /* Removed absolute positioning */
+ /* transform: translateX(-50%); */
+ /* Removed transform */
background: rgba(30, 30, 30, 0.95);
- border-radius: 16px;
+ border-radius: 12px; /* Adjusted from 16px */
padding: 24px;
- width: 90%;
- max-width: 400px;
+ width: 90%; /* Use width instead of min-width for responsiveness */
+ max-width: 360px; /* Limit max width */
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
- animation: dialogSlide 0.2s ease;
+ animation: dialogAppear 0.2s ease; /* Re-use dialogAppear animation */
+ /* Removed z-index, handled by overlay */
}
.dialogHeader {
@@ -346,111 +368,91 @@
}
.presetButtons {
+ position: absolute;
+ bottom: 100%;
+ right: 0;
+ margin-bottom: 8px;
+ background: var(--nav-bg);
+ border-radius: 8px;
+ padding: 8px;
+ box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
display: flex;
- gap: 8px;
+ flex-direction: column-reverse;
+ gap: 4px;
+ min-width: 100px;
+ z-index: 10;
+ animation: slideUp 0.2s ease;
}
.presetButton {
all: unset;
cursor: pointer;
- padding: 6px 12px;
+ padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
- background: rgba(255, 255, 255, 0.1);
+ opacity: 0.8;
transition: all 0.2s ease;
+ text-align: center;
+ background: rgba(255, 255, 255, 0.1);
}
.presetButton:hover {
+ opacity: 1;
background: rgba(255, 255, 255, 0.15);
+ transform: translateX(2px);
}
-.presetButton.active {
- background: rgba(74, 158, 255, 0.2);
- color: #4a9eff;
-}
-
-.customTime {
- display: flex;
- flex-direction: column;
- gap: 16px;
+:global(.lightTheme) .presetButton {
+ background: rgba(0, 0, 0, 0.05);
+ color: #333;
}
-.timeInputs {
- display: flex;
- justify-content: center;
- gap: 12px;
+:global(.lightTheme) .presetButton:hover {
+ background: rgba(0, 0, 0, 0.1);
}
-.inputGroup {
+.activeTimer {
display: flex;
- flex-direction: column;
+ align-items: center;
gap: 8px;
}
-.timeInput {
- background: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 8px;
- padding: 12px;
- color: rgba(255, 255, 255, 0.9);
- font-size: 16px;
- transition: all 0.2s ease;
-}
-
-.timeInput:hover {
- border-color: rgba(255, 255, 255, 0.3);
-}
-
-.timeInput:focus {
- outline: none;
- border-color: #4a9eff;
- background: rgba(255, 255, 255, 0.15);
-}
-
-.timeUnit {
- font-size: 13px;
- opacity: 0.7;
+.time {
+ font-size: 15px;
+ font-variant-numeric: tabular-nums;
}
-.dialogActions {
+.controls {
display: flex;
- gap: 12px;
- margin-top: 8px;
+ gap: 4px;
+ align-items: center;
}
-.cancelButton,
-.startButton {
- flex: 1;
- padding: 12px;
- border: none;
- border-radius: 8px;
- font-size: 15px;
- font-weight: 500;
+.control {
+ all: unset;
cursor: pointer;
+ padding: 4px;
+ border-radius: 4px;
+ opacity: 0.7;
transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
}
-.cancelButton {
+.control:hover {
+ opacity: 1;
background: rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.9);
-}
-
-.cancelButton:hover {
- background: rgba(255, 255, 255, 0.15);
+ transform: translateY(-1px);
}
-.startButton {
- background: #4a9eff;
- color: white;
+.control:active {
+ transform: translateY(0);
}
-.startButton:hover {
- background: #3d8be6;
-}
-
-.startButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
+:global(.lightTheme) .control:hover {
+ background: rgba(0, 0, 0, 0.1);
}
@keyframes dialogAppear {
@@ -462,20 +464,21 @@
}
}
-@keyframes dialogSlide {
+@keyframes slideUp {
from {
- transform: translateY(20px);
opacity: 0;
+ transform: translateY(10px);
}
to {
- transform: translateY(0);
opacity: 1;
+ transform: translateY(0);
}
}
/* Light theme */
[data-theme='light'] .timerDialog {
background: rgba(255, 252, 242, 0.95);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); /* Adjusted shadow */
}
[data-theme='light'] .dialogHeader h3 {
@@ -527,168 +530,289 @@
background: #0052a3;
}
-.activeTimer {
+.customTime {
display: flex;
- align-items: center;
- gap: 8px;
+ flex-direction: column;
+ gap: 16px;
}
-.time {
- font-size: 15px;
- font-variant-numeric: tabular-nums;
- min-width: 45px;
+.timeInputs {
+ display: flex;
+ justify-content: center;
+ gap: 12px;
}
-.controls {
+.inputGroup {
display: flex;
- gap: 4px;
+ flex-direction: column;
+ gap: 8px;
}
-.control {
- all: unset;
- cursor: pointer;
- padding: 4px;
- border-radius: 4px;
+.timeInput {
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 8px;
+ padding: 12px;
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 16px;
transition: all 0.2s ease;
}
-.control:hover {
- background: rgba(255, 255, 255, 0.1);
+.timeInput:hover {
+ border-color: rgba(255, 255, 255, 0.3);
}
-:global(.lightTheme) .control:hover {
- background: rgba(0, 0, 0, 0.1);
+.timeInput:focus {
+ outline: none;
+ border-color: #4a9eff;
+ background: rgba(255, 255, 255, 0.15);
}
-.presetButtons {
+.timeUnit {
+ font-size: 13px;
+ opacity: 0.7;
+}
+
+.dialogActions {
display: flex;
- gap: 8px;
+ gap: 12px;
+ margin-top: 8px;
}
-.presetButton {
- all: unset;
+.cancelButton,
+.startButton {
+ flex: 1;
+ padding: 12px;
+ border: none;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 500;
cursor: pointer;
- padding: 6px 12px;
- border-radius: 6px;
- font-size: 14px;
- background: rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
-.presetButton:hover {
+.cancelButton {
+ background: rgba(255, 255, 255, 0.1);
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.cancelButton:hover {
background: rgba(255, 255, 255, 0.15);
}
-:global(.lightTheme) .presetButton {
+.startButton {
+ background: #4a9eff;
+ color: white;
+}
+
+.startButton:hover {
+ background: #3d8be6;
+}
+
+.startButton:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Light theme styles */
+:global(.lightTheme) .customTimeInput {
background: rgba(0, 0, 0, 0.05);
}
-:global(.lightTheme) .presetButton:hover {
- background: rgba(0, 0, 0, 0.1);
+:global(.lightTheme) .customTimeInput:focus {
+ background: rgba(0, 0, 0, 0.08);
}
-.customInput {
- display: flex;
- flex-direction: column;
- gap: 12px;
- min-width: 200px;
+:global(.lightTheme) .customTimeInput::placeholder {
+ color: rgba(0, 0, 0, 0.4);
}
-.customTimeInput {
- all: unset;
- padding: 8px 12px;
- border-radius: 6px;
- background: rgba(255, 255, 255, 0.1);
- color: inherit;
- font-size: 14px;
- text-align: center;
- transition: all 0.2s ease;
+:global(.lightTheme) .customSubmit {
+ background: rgba(0, 102, 204, 0.1);
+ color: #0066cc;
}
-.customTimeInput:focus {
- background: rgba(255, 255, 255, 0.15);
+:global(.lightTheme) .customSubmit:hover {
+ background: rgba(0, 102, 204, 0.15);
}
-.customTimeInput::placeholder {
- color: rgba(255, 255, 255, 0.5);
+:global(.lightTheme) .customCancel {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+:global(.lightTheme) .customCancel:hover {
+ background: rgba(0, 0, 0, 0.08);
+}
+
+:global(.lightTheme) .customButton:hover {
+ border-color: rgba(0, 0, 0, 0.3) !important;
+}
+
+/* Ensure custom button visibility in light theme */
+:global(.lightTheme) .presetButton.customButton {
+ color: rgba(0, 0, 0, 0.7); /* Make text darker */
+ border: 1px solid rgba(0, 0, 0, 0.15); /* Add subtle border */
}
-.customButtons {
+:global(.lightTheme) .presetButton.customButton:hover {
+ color: rgba(0, 0, 0, 0.9);
+ border-color: rgba(0, 0, 0, 0.25);
+ background: rgba(0, 0, 0, 0.08);
+}
+
+/* Ensure overlay is always full-screen and blurred */
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
display: flex;
- gap: 8px;
+ align-items: center;
+ justify-content: center;
+ z-index: 2000;
+ backdrop-filter: blur(8px);
+ animation: fadeIn 0.3s ease;
}
-.customSubmit,
-.customCancel {
- all: unset;
- flex: 1;
- padding: 6px 12px;
- border-radius: 6px;
- font-size: 14px;
+[data-theme='light'] .overlay {
+ background: rgba(0,0,0,0.18);
+ backdrop-filter: blur(8px);
+}
+
+.notification {
+ background: rgba(30, 30, 30, 0.95);
+ border-radius: 16px;
+ padding: 32px;
+ max-width: 400px;
+ width: 90%;
text-align: center;
- cursor: pointer;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
+ animation: slideUp 0.3s ease;
transition: all 0.2s ease;
}
-.customSubmit {
- background: rgba(74, 158, 255, 0.2);
- color: #4a9eff;
+.content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
}
-.customSubmit:hover {
- background: rgba(74, 158, 255, 0.3);
+.icon {
+ font-size: 48px;
+ margin-bottom: 8px;
+ animation: bounce 1s ease infinite;
+ opacity: 0.9;
}
-.customCancel {
- background: rgba(255, 255, 255, 0.1);
+.notification h2 {
+ margin: 0;
+ font-size: 24px;
+ font-weight: 600;
+ color: rgba(255, 255, 255, 0.95);
+ transition: color 0.2s ease;
}
-.customCancel:hover {
- background: rgba(255, 255, 255, 0.15);
+.notification p {
+ margin: 0;
+ font-size: 16px;
+ color: rgba(255, 255, 255, 0.8);
+ transition: color 0.2s ease;
}
-.customButton {
- border: 1px dashed rgba(255, 255, 255, 0.2) !important;
+.subtext {
+ font-size: 14px !important;
+ color: rgba(255, 255, 255, 0.6) !important;
+ transition: color 0.2s ease;
}
-.customButton:hover {
- border-color: rgba(255, 255, 255, 0.3) !important;
+.closeButton {
+ all: unset;
+ margin-top: 8px;
+ padding: 12px 32px;
+ background: rgba(74, 158, 255, 0.2);
+ color: #4a9eff;
+ border-radius: 8px;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
}
-/* Light theme styles */
-:global(.lightTheme) .customTimeInput {
- background: rgba(0, 0, 0, 0.05);
+.closeButton:hover {
+ background: rgba(74, 158, 255, 0.3);
+ transform: translateY(-1px);
}
-:global(.lightTheme) .customTimeInput:focus {
- background: rgba(0, 0, 0, 0.08);
+.closeButton:active {
+ transform: translateY(0);
}
-:global(.lightTheme) .customTimeInput::placeholder {
- color: rgba(0, 0, 0, 0.4);
+/* Light theme */
+.lightTheme {
+ background: rgba(255, 252, 242, 0.95);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
-:global(.lightTheme) .customSubmit {
+.lightTheme h2 {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.lightTheme p {
+ color: rgba(0, 0, 0, 0.7);
+}
+
+.lightTheme .subtext {
+ color: rgba(0, 0, 0, 0.5) !important;
+}
+
+.lightTheme .closeButton {
background: rgba(0, 102, 204, 0.1);
color: #0066cc;
}
-:global(.lightTheme) .customSubmit:hover {
+.lightTheme .closeButton:hover {
background: rgba(0, 102, 204, 0.15);
}
-:global(.lightTheme) .customCancel {
- background: rgba(0, 0, 0, 0.05);
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
}
-:global(.lightTheme) .customCancel:hover {
- background: rgba(0, 0, 0, 0.08);
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
}
-:global(.lightTheme) .customButton {
- border: 1px dashed rgba(0, 0, 0, 0.2) !important;
+@keyframes bounce {
+ 0%, 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-10px);
+ }
}
-:global(.lightTheme) .customButton:hover {
- border-color: rgba(0, 0, 0, 0.3) !important;
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
\ No newline at end of file
diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx
index 3306e07..fe36232 100644
--- a/src/components/Timer.tsx
+++ b/src/components/Timer.tsx
@@ -1,4 +1,5 @@
-import React, { useState } from 'react';
+import React, { useState, useRef, useEffect } from 'react';
+import { FiPause, FiPlay, FiStopCircle, FiClock } from 'react-icons/fi';
import styles from './Timer.module.css';
interface TimerProps {
@@ -10,7 +11,7 @@ interface TimerProps {
isRunning: boolean;
}
-const TIMER_PRESETS = [5, 15, 25, 30];
+const TIMER_PRESETS = [15, 30, 45, 60];
export const Timer: React.FC
= ({
onStart,
@@ -20,8 +21,20 @@ export const Timer: React.FC = ({
timeLeft,
isRunning
}) => {
- const [showCustomInput, setShowCustomInput] = useState(false);
- const [customMinutes, setCustomMinutes] = useState('');
+ const [showPresets, setShowPresets] = useState(false);
+ const containerRef = useRef(null);
+
+ // Handle clicks outside to close the preset menu
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
+ setShowPresets(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
const formatTime = (seconds: number): string => {
const minutes = Math.floor(seconds / 60);
@@ -29,107 +42,83 @@ export const Timer: React.FC = ({
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
};
- const handleCustomSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- const minutes = parseInt(customMinutes);
- if (!isNaN(minutes) && minutes > 0) {
+ const handleTimerClick = (minutes?: number) => {
+ if (minutes) {
onStart(minutes);
- setShowCustomInput(false);
- setCustomMinutes('');
+ setShowPresets(false);
+ } else if (timeLeft === 0) {
+ setShowPresets(prev => !prev);
}
};
- // If there's no time left, show presets and custom input
- if (timeLeft === 0) {
- return (
-
- {showCustomInput ? (
-
setShowCustomInput(false)}>
-
e.stopPropagation()}
- >
-
-
Set Custom Timer
+ return (
+
+
handleTimerClick()}
+ >
+ {timeLeft === 0 ? (
+
+
+ 15m
+
+ ) : (
+
+
{formatTime(timeLeft)}
+
+ {isRunning ? (
-
-
-
-
- ) : (
-
- {TIMER_PRESETS.map((minutes) => (
-
)}
- );
- }
-
- // If timer is set (timeLeft > 0), show the active timer
- return (
-
-
-
{formatTime(timeLeft)}
-
- {isRunning ? (
-
â¸ī¸
- ) : (
-
âļī¸
- )}
-
âšī¸
+ {timeLeft === 0 && showPresets && (
+
+ {TIMER_PRESETS.map((minutes) => (
+ handleTimerClick(minutes)}
+ className={styles.presetButton}
+ >
+ {minutes}m
+
+ ))}
-
+ )}
);
};
\ No newline at end of file
diff --git a/src/components/TodoPanel.module.css b/src/components/TodoPanel.module.css
new file mode 100644
index 0000000..d53a728
--- /dev/null
+++ b/src/components/TodoPanel.module.css
@@ -0,0 +1,161 @@
+.container {
+ max-width: 600px;
+ margin: 2rem auto;
+ padding: var(--spacing-lg);
+ background: var(--bg-secondary);
+ border-radius: var(--border-radius-lg);
+ border: 1px solid var(--border-color);
+ box-shadow: var(--shadow-md);
+}
+
+.form {
+ display: flex;
+ gap: var(--spacing-sm);
+ margin-bottom: var(--spacing-lg);
+}
+
+.input {
+ flex: 1;
+ padding: var(--spacing-md);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius-md);
+ background: var(--bg-primary);
+ color: var(--color-primary);
+ font-size: var(--font-base);
+ transition: var(--transition-base);
+}
+
+.input:hover {
+ border-color: var(--border-hover);
+}
+
+.input:focus {
+ outline: none;
+ border-color: var(--color-accent);
+ box-shadow: 0 0 0 2px rgba(255, 59, 48, 0.2);
+}
+
+.addButton {
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--color-accent);
+ color: white;
+ border: none;
+ border-radius: var(--border-radius-md);
+ font-size: var(--font-base);
+ font-weight: 500;
+ cursor: pointer;
+ transition: var(--transition-base);
+}
+
+.addButton:hover {
+ opacity: 0.9;
+ transform: translateY(-1px);
+}
+
+.addButton:active {
+ transform: translateY(0);
+}
+
+.todoList {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.todoItem {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-md);
+ border-bottom: 1px solid var(--border-color);
+ transition: var(--transition-base);
+}
+
+.todoItem:last-child {
+ border-bottom: none;
+}
+
+.todoLabel {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+ flex: 1;
+ cursor: pointer;
+}
+
+.checkbox {
+ width: 20px;
+ height: 20px;
+ border: 2px solid var(--border-color);
+ border-radius: var(--border-radius-sm);
+ cursor: pointer;
+ transition: var(--transition-base);
+}
+
+.checkbox:checked {
+ background: var(--color-accent);
+ border-color: var(--color-accent);
+}
+
+.todoText {
+ font-size: var(--font-base);
+ color: var(--color-primary);
+ transition: var(--transition-base);
+}
+
+.completed {
+ text-decoration: line-through;
+ opacity: 0.6;
+}
+
+.deleteButton {
+ background: none;
+ border: none;
+ color: var(--color-primary);
+ opacity: 0.5;
+ font-size: 1.5rem;
+ cursor: pointer;
+ padding: var(--spacing-xs);
+ border-radius: 50%;
+ transition: var(--transition-base);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.deleteButton:hover {
+ opacity: 1;
+ background: var(--bg-hover);
+}
+
+.footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: var(--spacing-lg);
+ margin-top: var(--spacing-lg);
+ border-top: 1px solid var(--border-color);
+}
+
+.count {
+ color: var(--color-primary);
+ opacity: 0.7;
+ font-size: var(--font-sm);
+}
+
+.clearButton {
+ background: none;
+ border: none;
+ color: var(--color-primary);
+ opacity: 0.7;
+ font-size: var(--font-sm);
+ cursor: pointer;
+ padding: var(--spacing-xs) var(--spacing-sm);
+ border-radius: var(--border-radius-sm);
+ transition: var(--transition-base);
+}
+
+.clearButton:hover {
+ opacity: 1;
+ background: var(--bg-hover);
+}
\ No newline at end of file
diff --git a/src/components/TodoPanel.tsx b/src/components/TodoPanel.tsx
new file mode 100644
index 0000000..645363f
--- /dev/null
+++ b/src/components/TodoPanel.tsx
@@ -0,0 +1,92 @@
+import { useState } from 'react'
+import styles from './TodoPanel.module.css'
+
+interface Todo {
+ id: string
+ text: string
+ completed: boolean
+}
+
+export function TodoPanel() {
+ const [todos, setTodos] = useState
([])
+ const [newTodo, setNewTodo] = useState('')
+
+ const addTodo = (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!newTodo.trim()) return
+
+ setTodos(prev => [...prev, {
+ id: crypto.randomUUID(),
+ text: newTodo.trim(),
+ completed: false
+ }])
+ setNewTodo('')
+ }
+
+ const toggleTodo = (id: string) => {
+ setTodos(prev => prev.map(todo =>
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
+ ))
+ }
+
+ const deleteTodo = (id: string) => {
+ setTodos(prev => prev.filter(todo => todo.id !== id))
+ }
+
+ const clearCompleted = () => {
+ setTodos(prev => prev.filter(todo => !todo.completed))
+ }
+
+ return (
+
+
+
+
+
+ {todos.length > 0 && (
+
+
+ {todos.filter(t => !t.completed).length} items left
+
+
+ Clear completed
+
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/src/styles/theme.css b/src/styles/theme.css
new file mode 100644
index 0000000..4d49c23
--- /dev/null
+++ b/src/styles/theme.css
@@ -0,0 +1,83 @@
+:root {
+ /* Base transitions */
+ --transition-base: all 0.15s ease;
+
+ /* Default theme (light) */
+ --bg-primary: rgb(255, 252, 242);
+ --color-primary: rgba(0, 0, 0, 0.9);
+ --nav-bg: rgba(255, 252, 242, 0.95);
+ --border-color: rgba(0, 0, 0, 0.1);
+
+ /* Common variables */
+ --border-radius-sm: 4px;
+ --border-radius-md: 8px;
+ --border-radius-lg: 12px;
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 16px;
+ --spacing-lg: 24px;
+ --spacing-xl: 32px;
+
+ /* Colors */
+ --color-secondary: rgba(0, 0, 0, 0.7);
+ --color-muted: rgba(0, 0, 0, 0.4);
+ --color-accent: #ff3b30;
+
+ /* Backgrounds */
+ --bg-secondary: rgba(0, 0, 0, 0.03);
+ --bg-hover: rgba(0, 0, 0, 0.05);
+ --bg-active: rgba(0, 0, 0, 0.1);
+
+ /* Borders */
+ --border-hover: rgba(0, 0, 0, 0.2);
+
+ /* Shadows */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.25);
+
+ /* Typography */
+ --font-sm: 13px;
+ --font-base: 14px;
+ --font-md: 15px;
+ --font-lg: 18px;
+ --font-xl: 24px;
+
+ /* Layout */
+ --nav-height: 60px;
+ --sidebar-width: 340px;
+ --border-radius: 8px;
+
+ /* Animation */
+ --transition-fast: 0.15s ease;
+ --transition-slow: 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+[data-theme="dark"] {
+ --bg-primary: #1a1a1a;
+ --color-primary: rgba(255, 255, 255, 0.9);
+ --nav-bg: rgba(0, 0, 0, 0.85);
+ --border-color: rgba(255, 255, 255, 0.1);
+ --color-secondary: rgba(255, 255, 255, 0.7);
+ --color-muted: rgba(255, 255, 255, 0.4);
+ --bg-secondary: rgba(255, 255, 255, 0.05);
+ --bg-hover: rgba(255, 255, 255, 0.08);
+ --bg-active: rgba(255, 255, 255, 0.12);
+ --border-hover: rgba(255, 255, 255, 0.2);
+}
+
+/* Accessibility */
+@media (prefers-reduced-motion: reduce) {
+ * {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+
+/* Focus styles */
+*:focus-visible {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+}
\ No newline at end of file