azizln commited on
Commit
deb7a8a
·
verified ·
1 Parent(s): 7651e25

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1808 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Zenpomo
3
- emoji: 📚
4
- colorFrom: purple
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: zenpomo
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1808 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ZenPomo Pro - Ultimate Productivity Timer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ /* Custom CSS (for parts not covered by Tailwind) */
11
+ @keyframes float {
12
+ 0%, 100% { transform: translate(0, 0) scale(1); opacity: 0.6; }
13
+ 25% { transform: translate(15px, 10px) scale(1.1); opacity: 0.8; }
14
+ 50% { transform: translate(-10px, 15px) scale(0.9); opacity: 0.5; }
15
+ 75% { transform: translate(5px, -10px) scale(1.05); opacity: 0.7; }
16
+ }
17
+
18
+ .timer-animation .circle {
19
+ animation: float 15s infinite ease-in-out;
20
+ background: rgba(108, 92, 231, 0.05);
21
+ }
22
+
23
+ .progress-bar {
24
+ transition: width 0.5s ease, background-color 0.5s ease;
25
+ }
26
+
27
+ .task-item:hover .task-actions {
28
+ opacity: 1;
29
+ }
30
+
31
+ .modal {
32
+ opacity: 0;
33
+ visibility: hidden;
34
+ transition: opacity 0.3s ease, visibility 0s 0.3s linear;
35
+ }
36
+
37
+ .modal.open {
38
+ opacity: 1;
39
+ visibility: visible;
40
+ transition: opacity 0.3s ease, visibility 0s linear;
41
+ }
42
+
43
+ .modal-content {
44
+ transform: translateY(20px) scale(0.95);
45
+ transition: transform 0.3s ease;
46
+ }
47
+
48
+ .modal.open .modal-content {
49
+ transform: translateY(0) scale(1);
50
+ }
51
+
52
+ .notification {
53
+ transform: translateX(calc(100% + 20px));
54
+ opacity: 0;
55
+ transition: transform 0.4s ease, opacity 0.4s ease;
56
+ }
57
+
58
+ .notification.show {
59
+ transform: translateX(0);
60
+ opacity: 1;
61
+ }
62
+
63
+ .settings-content {
64
+ max-height: 0;
65
+ overflow: hidden;
66
+ transition: max-height 0.5s ease;
67
+ }
68
+
69
+ .settings-content.open {
70
+ max-height: 600px;
71
+ }
72
+
73
+ .tab-content {
74
+ display: none;
75
+ }
76
+
77
+ .tab-content.active {
78
+ display: block;
79
+ }
80
+
81
+ /* Custom task scrollbar */
82
+ .task-list::-webkit-scrollbar {
83
+ width: 6px;
84
+ }
85
+
86
+ .task-list::-webkit-scrollbar-track {
87
+ background: #e9ecef;
88
+ border-radius: 10px;
89
+ }
90
+
91
+ .task-list::-webkit-scrollbar-thumb {
92
+ background: #a29bfe;
93
+ border-radius: 10px;
94
+ }
95
+
96
+ .task-list::-webkit-scrollbar-thumb:hover {
97
+ background: #6c5ce7;
98
+ }
99
+
100
+ /* Disable selection on timer and buttons */
101
+ .timer-display, button {
102
+ user-select: none;
103
+ -webkit-user-select: none;
104
+ -moz-user-select: none;
105
+ -ms-user-select: none;
106
+ }
107
+
108
+ /* Sound wave animation when timer running */
109
+ .sound-wave {
110
+ display: flex;
111
+ justify-content: center;
112
+ align-items: center;
113
+ gap: 3px;
114
+ height: 30px;
115
+ }
116
+
117
+ .sound-wave div {
118
+ width: 3px;
119
+ height: 10px;
120
+ background-color: #6c5ce7;
121
+ animation: sound-wave 1.5s infinite ease-in-out;
122
+ }
123
+
124
+ .sound-wave div:nth-child(1) { animation-delay: 0s; }
125
+ .sound-wave div:nth-child(2) { animation-delay: 0.2s; }
126
+ .sound-wave div:nth-child(3) { animation-delay: 0.4s; }
127
+ .sound-wave div:nth-child(4) { animation-delay: 0.6s; }
128
+ .sound-wave div:nth-child(5) { animation-delay: 0.8s; }
129
+
130
+ @keyframes sound-wave {
131
+ 0%, 100% { height: 10px; background-color: #6c5ce7; }
132
+ 25% { height: 30px; background-color: #a29bfe; }
133
+ 50% { height: 15px; background-color: #5649c5; }
134
+ 75% { height: 25px; background-color: #8476f9; }
135
+ }
136
+ </style>
137
+ </head>
138
+ <body class="bg-gradient-to-br from-gray-50 to-gray-200 min-h-screen flex flex-col text-gray-800">
139
+ <!-- Header -->
140
+ <header class="bg-white shadow-md py-4 px-6 sticky top-0 z-50 flex flex-col sm:flex-row items-center justify-between gap-4">
141
+ <a href="#" class="flex items-center gap-3 text-2xl font-bold text-purple-600 no-underline">
142
+ <i class="fas fa-clock text-3xl"></i>
143
+ <span>ZenPomo Pro</span>
144
+ </a>
145
+
146
+ <nav class="flex-1">
147
+ <ul class="flex flex-wrap justify-center gap-2 sm:gap-4">
148
+ <li><a href="#" class="flex items-center gap-1.5 px-3 py-2 rounded-full transition-all bg-purple-100 text-purple-600"><i class="fas fa-home"></i> <span>Home</span></a></li>
149
+ <li><a href="#" id="nav-stats" class="flex items-center gap-1.5 px-3 py-2 rounded-full transition-all hover:bg-gray-100 hover:text-purple-600"><i class="fas fa-chart-line"></i> <span>Stats</span></a></li>
150
+ <li><a href="#" id="nav-tasks" class="flex items-center gap-1.5 px-3 py-2 rounded-full transition-all hover:bg-gray-100 hover:text-purple-600"><i class="fas fa-tasks"></i> <span>Tasks</span></a></li>
151
+ <li><a href="#" id="nav-settings" class="flex items-center gap-1.5 px-3 py-2 rounded-full transition-all hover:bg-gray-100 hover:text-purple-600"><i class="fas fa-cog"></i> <span>Settings</span></a></li>
152
+ </ul>
153
+ </nav>
154
+
155
+ <div class="flex gap-3">
156
+ <button class="flex items-center gap-2 px-4 py-2 rounded-full border-2 border-purple-600 text-purple-600 font-semibold transition-all hover:bg-purple-50 hover:shadow-sm">
157
+ <i class="fas fa-user"></i>
158
+ <span>Sign In</span>
159
+ </button>
160
+ <button class="flex items-center gap-2 px-4 py-2 rounded-full bg-purple-600 text-white font-semibold transition-all hover:bg-purple-700 hover:shadow-md">
161
+ <i class="fas fa-rocket"></i>
162
+ <span>Upgrade</span>
163
+ </button>
164
+ </div>
165
+ </header>
166
+
167
+ <!-- Main Content -->
168
+ <main class="flex-1 p-4 max-w-7xl mx-auto w-full">
169
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
170
+ <!-- Pomodoro Timer Card -->
171
+ <div class="bg-white rounded-2xl shadow-lg p-6 relative overflow-hidden">
172
+ <!-- Decorative background circles -->
173
+ <div class="timer-animation absolute inset-0 pointer-events-none overflow-hidden">
174
+ <div class="circle absolute rounded-full" style="width: 100px; height: 100px; top: 10%; left: 20%; animation-delay: 0s;"></div>
175
+ <div class="circle absolute rounded-full" style="width: 150px; height: 150px; top: 60%; left: 70%; animation-delay: 2s;"></div>
176
+ <div class="circle absolute rounded-full" style="width: 80px; height: 80px; top: 30%; left: 50%; animation-delay: 4s;"></div>
177
+ </div>
178
+
179
+ <!-- Card Header -->
180
+ <header class="flex justify-between items-center mb-6 relative z-10">
181
+ <h2 class="text-xl font-bold text-purple-600 flex items-center gap-2">
182
+ <i class="fas fa-stopwatch"></i>
183
+ <span>Pomodoro Timer</span>
184
+ </h2>
185
+ <div class="flex gap-2">
186
+ <button id="stats-btn" class="w-9 h-9 rounded-full flex items-center justify-center border-2 border-purple-600 text-purple-600 hover:bg-purple-100 transition-all" title="View Stats">
187
+ <i class="fas fa-chart-pie"></i>
188
+ </button>
189
+ <button id="fullscreen-btn" class="w-9 h-9 rounded-full flex items-center justify-center border-2 border-purple-600 text-purple-600 hover:bg-purple-100 transition-all" title="Fullscreen">
190
+ <i class="fas fa-expand"></i>
191
+ </button>
192
+ </div>
193
+ </header>
194
+
195
+ <!-- Mode Indicator -->
196
+ <div class="flex justify-center mb-6 relative z-10">
197
+ <div id="mode-text" class="px-5 py-3 rounded-full font-semibold flex items-center gap-2 bg-purple-100 text-purple-600">
198
+ <i class="fas fa-bolt"></i>
199
+ <span>Focus</span>
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Timer Display -->
204
+ <div id="timer" class="text-7xl md:text-8xl font-bold text-center my-8 text-purple-600 font-mono transition-colors">25:00</div>
205
+
206
+ <!-- Progress Bar -->
207
+ <div class="w-full h-2.5 bg-gray-200 rounded-full mb-8 overflow-hidden">
208
+ <div id="progress-bar" class="h-full bg-purple-600 w-0"></div>
209
+ </div>
210
+
211
+ <!-- Controls -->
212
+ <div class="flex flex-wrap justify-center gap-3 mb-8 relative z-10">
213
+ <button id="start-btn" class="px-6 py-3 rounded-full bg-purple-600 text-white font-semibold flex items-center gap-2 hover:bg-purple-700 hover:shadow-md transition-all">
214
+ <i class="fas fa-play"></i>
215
+ <span>Start</span>
216
+ </button>
217
+ <button id="pause-btn" disabled class="px-6 py-3 rounded-full bg-green-500 text-white font-semibold flex items-center gap-2 hover:bg-green-600 hover:shadow-md transition-all">
218
+ <i class="fas fa-pause"></i>
219
+ <span>Pause</span>
220
+ </button>
221
+ <button id="reset-btn" disabled class="px-6 py-3 rounded-full bg-pink-500 text-white font-semibold flex items-center gap-2 hover:bg-pink-600 hover:shadow-md transition-all">
222
+ <i class="fas fa-redo"></i>
223
+ <span>Reset</span>
224
+ </button>
225
+ <button id="skip-to-break-btn" class="hidden px-6 py-3 rounded-full bg-yellow-400 text-gray-800 font-semibold flex items-center gap-2 hover:bg-yellow-500 hover:shadow-md transition-all" title="Skip to next break">
226
+ <i class="fas fa-forward"></i>
227
+ <span>Skip</span>
228
+ </button>
229
+ </div>
230
+
231
+ <!-- Activity status -->
232
+ <div id="activity-status" class="flex items-center justify-center gap-2 mb-4 text-purple-600">
233
+ <div class="sound-wave hidden">
234
+ <div></div>
235
+ <div></div>
236
+ <div></div>
237
+ <div></div>
238
+ <div></div>
239
+ </div>
240
+ <span id="status-text" class="text-sm">Timer idle</span>
241
+ </div>
242
+
243
+ <!-- Settings Toggle -->
244
+ <div class="flex justify-center mb-4 relative z-10">
245
+ <button id="toggle-settings" class="px-4 py-2 rounded-full border-2 border-purple-600 text-purple-600 font-medium flex items-center gap-2 hover:bg-purple-50 transition-all">
246
+ <i class="fas fa-sliders-h"></i>
247
+ <span>Timer Settings</span>
248
+ </button>
249
+ </div>
250
+
251
+ <!-- Settings Content -->
252
+ <div id="settings-content" class="overflow-hidden">
253
+ <div class="space-y-4 mb-4">
254
+ <div>
255
+ <label for="work-duration" class="block mb-2 font-medium flex items-center gap-2">
256
+ <i class="fas fa-briefcase text-purple-600"></i>
257
+ <span>Work Duration (minutes)</span>
258
+ </label>
259
+ <input type="number" id="work-duration" min="1" max="120" value="25" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
260
+ </div>
261
+
262
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
263
+ <div>
264
+ <label for="break-duration" class="block mb-2 font-medium flex items-center gap-2">
265
+ <i class="fas fa-coffee text-pink-500"></i>
266
+ <span>Short Break (minutes)</span>
267
+ </label>
268
+ <input type="number" id="break-duration" min="1" max="30" value="5" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
269
+ </div>
270
+ <div>
271
+ <label for="long-break-duration" class="block mb-2 font-medium flex items-center gap-2">
272
+ <i class="fas fa-umbrella-beach text-green-500"></i>
273
+ <span>Long Break (minutes)</span>
274
+ </label>
275
+ <input type="number" id="long-break-duration" min="1" max="60" value="15" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
276
+ </div>
277
+ </div>
278
+
279
+ <div>
280
+ <label for="pomodoros-before-long-break" class="block mb-2 font-medium flex items-center gap-2">
281
+ <i class="fas fa-layer-group text-purple-600"></i>
282
+ <span>Pomodoros Before Long Break</span>
283
+ </label>
284
+ <input type="number" id="pomodoros-before-long-break" min="1" max="10" value="4" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
285
+ </div>
286
+
287
+ <div>
288
+ <label for="auto-start" class="block mb-2 font-medium flex items-center gap-2">
289
+ <i class="fas fa-magic text-purple-600"></i>
290
+ <span>Auto Start Next Round</span>
291
+ </label>
292
+ <select id="auto-start" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
293
+ <option value="false">Disabled</option>
294
+ <option value="true">Enabled</option>
295
+ </select>
296
+ </div>
297
+
298
+ <div>
299
+ <label for="timer-sound" class="block mb-2 font-medium flex items-center gap-2">
300
+ <i class="fas fa-volume-up text-purple-600"></i>
301
+ <span>Timer Sound</span>
302
+ </label>
303
+ <select id="timer-sound" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
304
+ <option value="bell">Classic Bell</option>
305
+ <option value="chime">Soft Chime</option>
306
+ <option value="digital">Digital Beep</option>
307
+ <option value="nature">Nature Sounds</option>
308
+ <option value="none">No Sound</option>
309
+ </select>
310
+ </div>
311
+
312
+ <div>
313
+ <label for="dark-mode" class="block mb-2 font-medium flex items-center gap-2">
314
+ <i class="fas fa-moon text-purple-600"></i>
315
+ <span>Theme</span>
316
+ </label>
317
+ <select id="dark-mode" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
318
+ <option value="light">Light</option>
319
+ <option value="dark">Dark</option>
320
+ <option value="system">System Default</option>
321
+ </select>
322
+ </div>
323
+ </div>
324
+
325
+ <button id="apply-settings-btn" class="w-full px-4 py-3 rounded-full bg-purple-600 text-white font-semibold flex items-center justify-center gap-2 hover:bg-purple-700 hover:shadow-md transition-all">
326
+ <i class="fas fa-check"></i>
327
+ <span>Apply Settings</span>
328
+ </button>
329
+ </div>
330
+
331
+ <!-- Stats Section -->
332
+ <div class="flex gap-3 mt-6 relative z-10">
333
+ <div class="bg-white rounded-xl shadow-sm p-4 text-center flex-1">
334
+ <div id="today-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
335
+ <div class="text-sm text-gray-500">Today</div>
336
+ </div>
337
+ <div class="bg-white rounded-xl shadow-sm p-4 text-center flex-1">
338
+ <div id="week-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
339
+ <div class="text-sm text-gray-500">This Week</div>
340
+ </div>
341
+ <div class="bg-white rounded-xl shadow-sm p-4 text-center flex-1">
342
+ <div id="total-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
343
+ <div class="text-sm text-gray-500">Total</div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ <!-- Task Manager Card -->
349
+ <div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col h-full">
350
+ <!-- Card Header -->
351
+ <header class="flex justify-between items-center mb-6">
352
+ <h2 class="text-xl font-bold text-purple-600 flex items-center gap-2">
353
+ <i class="fas fa-tasks"></i>
354
+ <span>Task Manager</span>
355
+ </h2>
356
+ <div class="flex gap-2">
357
+ <button id="clear-completed" class="w-9 h-9 rounded-full flex items-center justify-center border-2 border-purple-600 text-purple-600 hover:bg-purple-100 transition-all" title="Clear Completed Tasks">
358
+ <i class="fas fa-check-double"></i>
359
+ </button>
360
+ <button id="add-task-modal" class="w-9 h-9 rounded-full flex items-center justify-center bg-purple-600 text-white hover:bg-purple-700 transition-all" title="Add New Task">
361
+ <i class="fas fa-plus"></i>
362
+ </button>
363
+ </div>
364
+ </header>
365
+
366
+ <!-- Quick Task Input -->
367
+ <div class="flex mb-6">
368
+ <input type="text" id="new-task" placeholder="Add a quick task..." class="flex-1 px-4 py-3 rounded-l-xl border border-gray-300 focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all border-r-0">
369
+ <button id="add-task" class="px-4 py-3 rounded-r-xl bg-purple-600 text-white font-medium hover:bg-purple-700 transition-all">
370
+ <i class="fas fa-plus"></i>
371
+ <span class="hidden sm:inline">Add</span>
372
+ </button>
373
+ </div>
374
+
375
+ <!-- Task Categories -->
376
+ <div class="flex flex-wrap gap-2 mb-6">
377
+ <button class="px-3 py-1.5 rounded-full font-medium transition-all bg-purple-600 text-white" data-category="all">All</button>
378
+ <button class="px-3 py-1.5 rounded-full font-medium transition-all bg-gray-200 hover:bg-gray-300" data-category="work">Work</button>
379
+ <button class="px-3 py-1.5 rounded-full font-medium transition-all bg-gray-200 hover:bg-gray-300" data-category="personal">Personal</button>
380
+ <button class="px-3 py-1.5 rounded-full font-medium transition-all bg-gray-200 hover:bg-gray-300" data-category="study">Study</button>
381
+ <button class="px-3 py-1.5 rounded-full font-medium transition-all bg-gray-200 hover:bg-gray-300" data-category="other">Other</button>
382
+ </div>
383
+
384
+ <!-- Task List -->
385
+ <ul id="task-list" class="flex-1 overflow-y-auto max-h-96 mb-6">
386
+ <!-- Tasks will be rendered here -->
387
+ </ul>
388
+
389
+ <!-- Empty State -->
390
+ <div id="empty-state" class="flex flex-col items-center justify-center py-12 text-center text-gray-500">
391
+ <i class="fas fa-clipboard-list text-6xl mb-4 text-gray-300"></i>
392
+ <p class="max-w-xs">No tasks yet. Add your first task to get started!</p>
393
+ </div>
394
+
395
+ <!-- Task Stats -->
396
+ <div class="flex justify-between pt-4 border-t border-gray-200 text-sm text-gray-500">
397
+ <span id="total-tasks">0 tasks</span>
398
+ <span id="completed-tasks">0 completed</span>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ </main>
403
+
404
+ <!-- Task Modal -->
405
+ <div class="modal fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50" id="task-modal">
406
+ <div class="modal-content bg-white rounded-2xl shadow-xl w-full max-w-md max-h-[90vh] overflow-y-auto">
407
+ <header class="p-6 border-b border-gray-200 flex justify-between items-center">
408
+ <h3 id="modal-task-title" class="text-xl font-semibold text-gray-800">Add New Task</h3>
409
+ <button id="close-modal" class="text-2xl text-gray-500 hover:text-gray-700 transition-all">&times;</button>
410
+ </header>
411
+
412
+ <div class="p-6 space-y-4">
413
+ <input type="hidden" id="task-id-input">
414
+
415
+ <div>
416
+ <label for="task-name" class="block mb-2 font-medium text-gray-700">Task Name</label>
417
+ <input type="text" id="task-name" placeholder="Enter task name" required class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
418
+ </div>
419
+
420
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
421
+ <div>
422
+ <label for="task-category" class="block mb-2 font-medium text-gray-700">Category</label>
423
+ <select id="task-category" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
424
+ <option value="work">Work</option>
425
+ <option value="personal">Personal</option>
426
+ <option value="study">Study</option>
427
+ <option value="other">Other</option>
428
+ </select>
429
+ </div>
430
+
431
+ <div>
432
+ <label for="task-priority" class="block mb-2 font-medium text-gray-700">Priority</label>
433
+ <select id="task-priority" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
434
+ <option value="low">Low</option>
435
+ <option value="medium" selected>Medium</option>
436
+ <option value="high">High</option>
437
+ </select>
438
+ </div>
439
+ </div>
440
+
441
+ <div>
442
+ <label for="task-due-date" class="block mb-2 font-medium text-gray-700">Due Date (optional)</label>
443
+ <input type="date" id="task-due-date" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
444
+ </div>
445
+
446
+ <div>
447
+ <label for="task-notes" class="block mb-2 font-medium text-gray-700">Notes (optional)</label>
448
+ <textarea id="task-notes" rows="3" placeholder="Add any additional notes..." class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:border-purple-600 focus:ring-2 focus:ring-purple-200 outline-none transition-all"></textarea>
449
+ </div>
450
+ </div>
451
+
452
+ <footer class="p-6 border-t border-gray-200 flex justify-end gap-3">
453
+ <button id="cancel-task" class="px-5 py-2.5 rounded-full border-2 border-gray-300 text-gray-700 font-medium hover:bg-gray-100 transition-all">Cancel</button>
454
+ <button id="save-task" class="px-5 py-2.5 rounded-full bg-purple-600 text-white font-medium hover:bg-purple-700 transition-all">Save Task</button>
455
+ </footer>
456
+ </div>
457
+ </div>
458
+
459
+ <!-- Stats Modal -->
460
+ <div class="modal fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50" id="stats-modal">
461
+ <div class="modal-content bg-white rounded-2xl shadow-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
462
+ <header class="p-6 border-b border-gray-200 flex justify-between items-center">
463
+ <h3 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
464
+ <i class="fas fa-chart-pie text-purple-600"></i>
465
+ <span>Productivity Stats</span>
466
+ </h3>
467
+ <button id="close-stats-modal" class="text-2xl text-gray-500 hover:text-gray-700 transition-all">&times;</button>
468
+ </header>
469
+
470
+ <div class="p-6">
471
+ <div class="border-b border-gray-200 mb-6">
472
+ <div class="flex gap-1">
473
+ <button class="tab px-4 py-2 font-medium relative" data-tab="history">History</button>
474
+ <button class="tab px-4 py-2 font-medium relative" data-tab="summary">Summary</button>
475
+ </div>
476
+ </div>
477
+
478
+ <div id="history-tab" class="tab-content active">
479
+ <h4 class="text-lg font-medium mb-4 text-gray-800">Recent Pomodoro Sessions</h4>
480
+
481
+ <div id="history-list" class="max-h-60 overflow-y-auto">
482
+ <div id="history-empty-state" class="py-8 text-center text-gray-500">
483
+ No completed sessions yet.
484
+ </div>
485
+ </div>
486
+ </div>
487
+
488
+ <div id="summary-tab" class="tab-content">
489
+ <h4 class="text-lg font-medium mb-4 text-gray-800">Overall Summary</h4>
490
+
491
+ <div class="space-y-4">
492
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
493
+ <div class="stat-card bg-white rounded-xl shadow-sm p-4 text-center">
494
+ <div id="modal-today-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
495
+ <div class="text-sm text-gray-500">Pomodoros Today</div>
496
+ </div>
497
+ <div class="stat-card bg-white rounded-xl shadow-sm p-4 text-center">
498
+ <div id="modal-week-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
499
+ <div class="text-sm text-gray-500">Pomodoros This Week</div>
500
+ </div>
501
+ </div>
502
+
503
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
504
+ <div class="stat-card bg-white rounded-xl shadow-sm p-4 text-center">
505
+ <div id="modal-total-pomodoros" class="text-2xl font-bold text-purple-600">0</div>
506
+ <div class="text-sm text-gray-500">Total Pomodoros</div>
507
+ </div>
508
+ <div class="stat-card bg-white rounded-xl shadow-sm p-4 text-center">
509
+ <div id="modal-total-focus-time" class="text-2xl font-bold text-purple-600">0h 0m</div>
510
+ <div class="text-sm text-gray-500">Total Focus Time</div>
511
+ </div>
512
+ </div>
513
+
514
+ <div class="stat-card bg-white rounded-xl shadow-sm p-4 text-center">
515
+ <div id="modal-current-streak" class="text-2xl font-bold text-purple-600">0 days</div>
516
+ <div class="text-sm text-gray-500">Current Streak</div>
517
+ </div>
518
+ </div>
519
+ </div>
520
+ </div>
521
+
522
+ <footer class="p-6 border-t border-gray-200">
523
+ <button id="reset-stats-btn" class="w-full px-4 py-2.5 rounded-full border-2 border-red-500 text-red-500 font-medium hover:bg-red-50 flex items-center justify-center gap-2 transition-all">
524
+ <i class="fas fa-trash-alt"></i>
525
+ <span>Reset All Stats</span>
526
+ </button>
527
+ </footer>
528
+ </div>
529
+ </div>
530
+
531
+ <!-- Notification -->
532
+ <div id="notification" class="fixed bottom-6 right-6 bg-white rounded-lg shadow-lg p-4 flex items-center gap-3 max-w-sm w-full border-l-4 hidden">
533
+ <div class="notification-icon">
534
+ <i class="fas fa-info-circle text-blue-500 text-xl"></i>
535
+ </div>
536
+ <div class="flex-1">
537
+ <div id="notification-title" class="font-semibold">Info</div>
538
+ <div id="notification-message" class="text-sm text-gray-600">Notification message</div>
539
+ </div>
540
+ <button id="close-notification" class="text-gray-500 hover:text-gray-700 transition-all">&times;</button>
541
+ </div>
542
+
543
+ <!-- Audio Elements -->
544
+ <audio id="timer-end-sound" preload="auto"></audio>
545
+ <audio id="break-start-sound" preload="auto"></audio>
546
+ <audio id="work-start-sound" preload="auto"></audio>
547
+ <audio id="task-complete-sound" preload="auto"></audio>
548
+ <audio id="focus-alarm-sound" preload="auto"></audio>
549
+
550
+ <script>
551
+ document.addEventListener('DOMContentLoaded', function() {
552
+ // --- Constants ---
553
+ const SOUND_URLS = {
554
+ bell: 'https://assets.mixkit.co/sfx/preview/mixkit-alarm-digital-clock-beep-989.mp3',
555
+ chime: 'https://assets.mixkit.co/sfx/preview/mixkit-positive-interface-beep-221.mp3',
556
+ digital: 'https://assets.mixkit.co/sfx/preview/mixkit-correct-answer-tone-2870.mp3',
557
+ nature: 'https://assets.mixkit.co/sfx/preview/mixkit-forest-birds-singing-1238.mp3',
558
+ none: ''
559
+ };
560
+
561
+ const PRIORITY_COLORS = {
562
+ high: 'bg-red-500',
563
+ medium: 'bg-yellow-400',
564
+ low: 'bg-green-500'
565
+ };
566
+
567
+ // --- DOM Elements ---
568
+ // Timer elements
569
+ const timerDisplay = document.getElementById('timer');
570
+ const startBtn = document.getElementById('start-btn');
571
+ const pauseBtn = document.getElementById('pause-btn');
572
+ const resetBtn = document.getElementById('reset-btn');
573
+ const skipToBreakBtn = document.getElementById('skip-to-break-btn');
574
+ const modeText = document.getElementById('mode-text');
575
+ const progressBar = document.getElementById('progress-bar');
576
+ const soundWave = document.querySelector('.sound-wave');
577
+ const statusText = document.getElementById('status-text');
578
+ const fullscreenBtn = document.getElementById('fullscreen-btn');
579
+ const activityStatus = document.getElementById('activity-status');
580
+
581
+ // Settings elements
582
+ const workDurationInput = document.getElementById('work-duration');
583
+ const breakDurationInput = document.getElementById('break-duration');
584
+ const longBreakDurationInput = document.getElementById('long-break-duration');
585
+ const pomodorosBeforeLongBreakInput = document.getElementById('pomodoros-before-long-break');
586
+ const autoStartInput = document.getElementById('auto-start');
587
+ const timerSoundInput = document.getElementById('timer-sound');
588
+ const darkModeInput = document.getElementById('dark-mode');
589
+ const toggleSettingsBtn = document.getElementById('toggle-settings');
590
+ const settingsContent = document.getElementById('settings-content');
591
+ const applySettingsBtn = document.getElementById('apply-settings-btn');
592
+
593
+ // Stats elements
594
+ const todayPomodorosDisplay = document.getElementById('today-pomodoros');
595
+ const weekPomodorosDisplay = document.getElementById('week-pomodoros');
596
+ const totalPomodorosDisplay = document.getElementById('total-pomodoros');
597
+
598
+ // Task elements
599
+ const newTaskInput = document.getElementById('new-task');
600
+ const addTaskBtn = document.getElementById('add-task');
601
+ const taskList = document.getElementById('task-list');
602
+ const emptyState = document.getElementById('empty-state');
603
+ const totalTasksSpan = document.getElementById('total-tasks');
604
+ const completedTasksSpan = document.getElementById('completed-tasks');
605
+ const clearCompletedBtn = document.getElementById('clear-completed');
606
+ const categoryBtns = document.querySelectorAll('.category-btn');
607
+
608
+ // Task modal elements
609
+ const taskModal = document.getElementById('task-modal');
610
+ const addTaskModalBtn = document.getElementById('add-task-modal');
611
+ const closeModalBtn = document.getElementById('close-modal');
612
+ const cancelTaskBtn = document.getElementById('cancel-task');
613
+ const saveTaskBtn = document.getElementById('save-task');
614
+ const modalTaskTitle = document.getElementById('modal-task-title');
615
+ const taskNameInput = document.getElementById('task-name');
616
+ const taskCategoryInput = document.getElementById('task-category');
617
+ const taskPriorityInput = document.getElementById('task-priority');
618
+ const taskDueDateInput = document.getElementById('task-due-date');
619
+ const taskNotesInput = document.getElementById('task-notes');
620
+ const taskIdInput = document.getElementById('task-id-input');
621
+
622
+ // Stats modal elements
623
+ const statsModal = document.getElementById('stats-modal');
624
+ const statsBtn = document.getElementById('stats-btn');
625
+ const closeStatsModalBtn = document.getElementById('close-stats-modal');
626
+ const statsTabs = statsModal.querySelectorAll('.tab');
627
+ const statsTabContents = statsModal.querySelectorAll('.tab-content');
628
+ const historyList = document.getElementById('history-list');
629
+ const historyEmptyState = document.getElementById('history-empty-state');
630
+ const modalTodayPomodoros = document.getElementById('modal-today-pomodoros');
631
+ const modalWeekPomodoros = document.getElementById('modal-week-pomodoros');
632
+ const modalTotalPomodoros = document.getElementById('modal-total-pomodoros');
633
+ const modalTotalFocusTime = document.getElementById('modal-total-focus-time');
634
+ const modalCurrentStreak = document.getElementById('modal-current-streak');
635
+ const resetStatsBtn = document.getElementById('reset-stats-btn');
636
+
637
+ // Notification elements
638
+ const notification = document.getElementById('notification');
639
+ const notificationIcon = notification.querySelector('.notification-icon i');
640
+ const notificationTitle = document.getElementById('notification-title');
641
+ const notificationMessage = document.getElementById('notification-message');
642
+ const closeNotificationBtn = document.getElementById('close-notification');
643
+
644
+ // Audio elements
645
+ const timerEndSound = document.getElementById('timer-end-sound');
646
+ const breakStartSound = document.getElementById('break-start-sound');
647
+ const workStartSound = document.getElementById('work-start-sound');
648
+ const taskCompleteSound = document.getElementById('task-complete-sound');
649
+ const focusAlarmSound = document.getElementById('focus-alarm-sound');
650
+
651
+ // --- State Variables ---
652
+ let timerInterval;
653
+ let timeLeft;
654
+ let totalSeconds;
655
+ let isRunning = false;
656
+ let currentMode = 'work';
657
+ let pomodoroCount = 0;
658
+ let settings = loadSettings();
659
+ let tasks = loadTasks();
660
+ let stats = loadStats();
661
+ let currentTaskEditingId = null;
662
+ let currentCategoryFilter = 'all';
663
+ let notificationTimeout;
664
+ let isFullscreen = false;
665
+
666
+ // --- Initialization ---
667
+ function initializeApp() {
668
+ applySettingsToUI();
669
+ updateSoundSources();
670
+ resetTimer(true);
671
+ renderTasks();
672
+ updateTaskStats();
673
+ updateStatsDisplay();
674
+ updateButtonStates();
675
+ checkDarkMode();
676
+
677
+ // Initialize tooltips
678
+ initializeTooltips();
679
+
680
+ // Load sounds and try to unlock audio
681
+ unlockAudio();
682
+ }
683
+
684
+ // --- Init Tooltips ---
685
+ function initializeTooltips() {
686
+ // Add event listeners for all elements with title attribute
687
+ document.querySelectorAll('[title]').forEach(el => {
688
+ el.addEventListener('mouseenter', showTooltip);
689
+ el.addEventListener('mouseleave', hideTooltip);
690
+ el.addEventListener('focus', showTooltip);
691
+ el.addEventListener('blur', hideTooltip);
692
+ });
693
+ }
694
+
695
+ function showTooltip(e) {
696
+ const element = e.target;
697
+ const tooltipText = element.getAttribute('title');
698
+
699
+ if (!tooltipText) return;
700
+
701
+ // Create tooltip if it doesn't exist
702
+ let tooltip = element.querySelector('.custom-tooltip');
703
+ if (!tooltip) {
704
+ tooltip = document.createElement('div');
705
+ tooltip.className = 'custom-tooltip absolute z-50 bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap pointer-events-none hidden';
706
+ element.appendChild(tooltip);
707
+ }
708
+
709
+ // Position tooltip
710
+ tooltip.textContent = tooltipText;
711
+ tooltip.classList.remove('hidden');
712
+
713
+ const rect = element.getBoundingClientRect();
714
+ tooltip.style.left = `${rect.left + rect.width/2 - tooltip.offsetWidth/2}px`;
715
+ tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`;
716
+
717
+ // Remove title to prevent default tooltip
718
+ element.removeAttribute('title');
719
+ }
720
+
721
+ function hideTooltip(e) {
722
+ const element = e.target;
723
+ const tooltip = element.querySelector('.custom-tooltip');
724
+ if (tooltip) {
725
+ tooltip.classList.add('hidden');
726
+ }
727
+ }
728
+
729
+ // --- Settings Management ---
730
+ function defaultSettings() {
731
+ return {
732
+ workDuration: 25,
733
+ breakDuration: 5,
734
+ longBreakDuration: 15,
735
+ pomodorosBeforeLongBreak: 4,
736
+ autoStart: false,
737
+ timerSound: 'bell',
738
+ darkMode: 'system'
739
+ };
740
+ }
741
+
742
+ function loadSettings() {
743
+ const savedSettings = localStorage.getItem('zenpomoSettings');
744
+ return { ...defaultSettings(), ...(savedSettings ? JSON.parse(savedSettings) : {}) };
745
+ }
746
+
747
+ function saveSettings() {
748
+ localStorage.setItem('zenpomoSettings', JSON.stringify(settings));
749
+ checkDarkMode(); // Update dark mode when settings change
750
+ }
751
+
752
+ function applySettingsToUI() {
753
+ workDurationInput.value = settings.workDuration;
754
+ breakDurationInput.value = settings.breakDuration;
755
+ longBreakDurationInput.value = settings.longBreakDuration;
756
+ pomodorosBeforeLongBreakInput.value = settings.pomodorosBeforeLongBreak;
757
+ autoStartInput.value = settings.autoStart.toString();
758
+ timerSoundInput.value = settings.timerSound;
759
+ darkModeInput.value = settings.darkMode;
760
+ }
761
+
762
+ function checkDarkMode() {
763
+ let darkMode = false;
764
+
765
+ if (settings.darkMode === 'dark' ||
766
+ (settings.darkMode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
767
+ darkMode = true;
768
+ }
769
+
770
+ if (darkMode) {
771
+ document.documentElement.classList.add('dark');
772
+ document.body.classList.add('bg-gray-900');
773
+ } else {
774
+ document.documentElement.classList.remove('dark');
775
+ document.body.classList.remove('bg-gray-900');
776
+ }
777
+ }
778
+
779
+ function applySettings() {
780
+ // Validate inputs
781
+ const workDur = parseInt(workDurationInput.value);
782
+ const breakDur = parseInt(breakDurationInput.value);
783
+ const longBreakDur = parseInt(longBreakDurationInput.value);
784
+ const pomBeforeLong = parseInt(pomodorosBeforeLongBreakInput.value);
785
+
786
+ if (isNaN(workDur) || workDur < 1 || workDur > 120 ||
787
+ isNaN(breakDur) || breakDur < 1 || breakDur > 30 ||
788
+ isNaN(longBreakDur) || longBreakDur < 1 || longBreakDur > 60 ||
789
+ isNaN(pomBeforeLong) || pomBeforeLong < 1 || pomBeforeLong > 10) {
790
+ showNotification('Invalid Settings', 'Please enter valid numbers within allowed ranges.', 'error');
791
+ return;
792
+ }
793
+
794
+ // Update settings
795
+ settings.workDuration = workDur;
796
+ settings.breakDuration = breakDur;
797
+ settings.longBreakDuration = longBreakDur;
798
+ settings.pomodorosBeforeLongBreak = pomBeforeLong;
799
+ settings.autoStart = autoStartInput.value === 'true';
800
+ settings.timerSound = timerSoundInput.value;
801
+ settings.darkMode = darkModeInput.value;
802
+
803
+ saveSettings();
804
+ updateSoundSources();
805
+
806
+ if (!isRunning) {
807
+ resetTimer(true); // Update timer display without changing mode
808
+ }
809
+
810
+ showNotification('Settings Applied', 'New settings saved successfully.', 'success');
811
+ settingsContent.classList.remove('open');
812
+ toggleSettingsBtn.innerHTML = '<i class="fas fa-sliders-h"></i> <span>Timer Settings</span>';
813
+ }
814
+
815
+ function updateSoundSources() {
816
+ timerEndSound.src = SOUND_URLS[settings.timerSound] || '';
817
+ timerEndSound.load();
818
+ breakStartSound.load();
819
+ workStartSound.load();
820
+
821
+ // Preload task complete sound
822
+ taskCompleteSound.src = 'https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3';
823
+ taskCompleteSound.load();
824
+
825
+ // Preload focus alarm sound
826
+ focusAlarmSound.src = 'https://assets.mixkit.co/sfx/preview/mixkit-alarm-clock-beep-1103.mp3';
827
+ focusAlarmSound.load();
828
+ }
829
+
830
+ // Unlock audio on iOS/devices that require user interaction
831
+ function unlockAudio() {
832
+ const playSilentAudio = () => {
833
+ const silentAudio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
834
+ silentAudio.volume = 0;
835
+ silentAudio.play().catch(e => console.log('Audio unlock attempt failed'));
836
+ };
837
+
838
+ document.addEventListener('click', function unlockOnce() {
839
+ playSilentAudio();
840
+ document.removeEventListener('click', unlockOnce);
841
+ }, { once: true });
842
+ }
843
+
844
+ // --- Timer Logic ---
845
+ function startTimer() {
846
+ if (isRunning) return;
847
+
848
+ // Sound preload attempt (helps with iOS/Android restrictions)
849
+ try {
850
+ timerEndSound.load();
851
+ breakStartSound.load();
852
+ workStartSound.load();
853
+ const playPromise = timerEndSound.play();
854
+ if (playPromise !== undefined) {
855
+ playPromise.then(_ => {
856
+ timerEndSound.pause();
857
+ timerEndSound.currentTime = 0;
858
+ }).catch(e => {});
859
+ }
860
+ } catch (e) {}
861
+
862
+ isRunning = true;
863
+
864
+ // Update total seconds based on current mode and settings
865
+ switch (currentMode) {
866
+ case 'work': totalSeconds = settings.workDuration * 60; break;
867
+ case 'break': totalSeconds = settings.breakDuration * 60; break;
868
+ case 'longBreak': totalSeconds = settings.longBreakDuration * 60; break;
869
+ }
870
+
871
+ // Cap timeLeft if it exceeds new totalSeconds
872
+ if (timeLeft > totalSeconds) timeLeft = totalSeconds;
873
+
874
+ // Play appropriate sound
875
+ if (currentMode === 'work') playSound(workStartSound);
876
+ else playSound(breakStartSound);
877
+
878
+ timerInterval = setInterval(updateTimer, 1000);
879
+ updateButtonStates();
880
+
881
+ // Show running indicator
882
+ soundWave.classList.remove('hidden');
883
+ statusText.textContent = `${currentMode === 'work' ? 'Focusing' : 'On break'}...`;
884
+ activityStatus.classList.add('text-purple-600');
885
+
886
+ // Show desktop notification if available
887
+ if (Notification.permission === 'granted' && document.hidden) {
888
+ new Notification(`ZenPomo Pro - ${currentMode === 'work' ? 'Work' : 'Break'} Started`, {
889
+ body: `Timer started for ${formatMinutesSeconds(totalSeconds)}.`
890
+ });
891
+ }
892
+ }
893
+
894
+ function pauseTimer() {
895
+ if (!isRunning) return;
896
+
897
+ clearInterval(timerInterval);
898
+ isRunning = false;
899
+ updateButtonStates();
900
+
901
+ // Hide running indicator
902
+ soundWave.classList.add('hidden');
903
+ statusText.textContent = 'Timer paused';
904
+ activityStatus.classList.remove('text-purple-600');
905
+ }
906
+
907
+ function resetTimer(preventModeSwitch = false) {
908
+ clearInterval(timerInterval);
909
+ isRunning = false;
910
+
911
+ if (!preventModeSwitch) {
912
+ currentMode = 'work';
913
+ pomodoroCount = 0;
914
+ updateModeIndicator();
915
+ }
916
+
917
+ // Update time based on current mode and latest settings
918
+ switch (currentMode) {
919
+ case 'work': timeLeft = settings.workDuration * 60; break;
920
+ case 'break': timeLeft = settings.breakDuration * 60; break;
921
+ case 'longBreak': timeLeft = settings.longBreakDuration * 60; break;
922
+ default: timeLeft = settings.workDuration * 60;
923
+ }
924
+ totalSeconds = timeLeft;
925
+
926
+ updateTimerDisplay();
927
+ updateProgressBar(true);
928
+ updateButtonStates();
929
+
930
+ // Hide running indicator
931
+ soundWave.classList.add('hidden');
932
+ statusText.textContent = 'Timer idle';
933
+ activityStatus.classList.remove('text-purple-600');
934
+ }
935
+
936
+ function updateTimer() {
937
+ if (timeLeft > 0) {
938
+ timeLeft--;
939
+ updateTimerDisplay();
940
+ updateProgressBar();
941
+
942
+ // Alert when 1 minute remains
943
+ if (timeLeft === 60) {
944
+ playSound(focusAlarmSound);
945
+ }
946
+ } else {
947
+ // Timer reached 0
948
+ clearInterval(timerInterval);
949
+ isRunning = false;
950
+ playSound(timerEndSound);
951
+
952
+ // Determine next mode
953
+ let nextMode = 'work';
954
+ if (currentMode === 'work') {
955
+ pomodoroCount++;
956
+ recordPomodoroCompletion();
957
+ updateStatsDisplay();
958
+
959
+ if (pomodoroCount >= settings.pomodorosBeforeLongBreak) {
960
+ nextMode = 'longBreak';
961
+ pomodoroCount = 0;
962
+ } else {
963
+ nextMode = 'break';
964
+ }
965
+ } else {
966
+ nextMode = 'work';
967
+ }
968
+
969
+ // Switch mode
970
+ switchMode(nextMode);
971
+
972
+ // Auto-start if enabled
973
+ if (settings.autoStart) {
974
+ setTimeout(startTimer, 500);
975
+ } else {
976
+ updateButtonStates();
977
+ }
978
+ }
979
+ }
980
+
981
+ function skipToBreak() {
982
+ if (currentMode !== 'work') return;
983
+
984
+ clearInterval(timerInterval);
985
+ isRunning = false;
986
+
987
+ // Determine break type based on next pomodoro
988
+ let nextBreakMode = pomodoroCount + 1 >= settings.pomodorosBeforeLongBreak ? 'longBreak' : 'break';
989
+
990
+ showNotification('Work Skipped', `Starting ${nextBreakMode === 'longBreak' ? 'long break' : 'short break'}.`, 'info');
991
+ switchMode(nextBreakMode);
992
+
993
+ if (settings.autoStart) {
994
+ setTimeout(startTimer, 500);
995
+ } else {
996
+ updateButtonStates();
997
+ }
998
+ }
999
+
1000
+ function switchMode(newMode) {
1001
+ currentMode = newMode;
1002
+
1003
+ // Set time left based on new mode and current settings
1004
+ switch (newMode) {
1005
+ case 'work': timeLeft = settings.workDuration * 60; break;
1006
+ case 'break': timeLeft = settings.breakDuration * 60; break;
1007
+ case 'longBreak': timeLeft = settings.longBreakDuration * 60; break;
1008
+ default: timeLeft = settings.workDuration * 60;
1009
+ }
1010
+ totalSeconds = timeLeft;
1011
+
1012
+ updateModeIndicator();
1013
+ updateTimerDisplay();
1014
+ updateProgressBar(true);
1015
+ updateButtonStates();
1016
+
1017
+ // Update status text
1018
+ if (!isRunning) {
1019
+ statusText.textContent = 'Timer idle';
1020
+ } else {
1021
+ statusText.textContent = `${currentMode === 'work' ? 'Focusing' : 'On break'}...`;
1022
+ }
1023
+
1024
+ // Request notification permission if needed
1025
+ if (Notification.permission !== 'granted' && Notification.permission !== 'denied') {
1026
+ Notification.requestPermission().then(permission => {
1027
+ console.log('Notification permission:', permission);
1028
+ });
1029
+ }
1030
+
1031
+ // Send desktop notification if enabled
1032
+ if (Notification.permission === 'granted' && document.hidden) {
1033
+ new Notification(`ZenPomo Pro - ${newMode === 'work' ? 'Work' : 'Break'} Time!`, {
1034
+ body: `It's time to ${newMode === 'work' ? 'focus' : 'take a break'}!`,
1035
+ });
1036
+ }
1037
+ }
1038
+
1039
+ function updateModeIndicator() {
1040
+ let modeClass = 'px-5 py-3 rounded-full font-semibold flex items-center gap-2 transition-colors';
1041
+ let progressColor = 'bg-purple-600';
1042
+ let icon, modeName;
1043
+
1044
+ switch (currentMode) {
1045
+ case 'work':
1046
+ icon = 'fa-bolt';
1047
+ modeName = 'Focus';
1048
+ modeClass += ' bg-purple-100 text-purple-600';
1049
+ break;
1050
+ case 'break':
1051
+ icon = 'fa-coffee';
1052
+ modeName = 'Break';
1053
+ modeClass += ' bg-pink-100 text-pink-500';
1054
+ progressColor = 'bg-pink-500';
1055
+ break;
1056
+ case 'longBreak':
1057
+ icon = 'fa-umbrella-beach';
1058
+ modeName = 'Long Break';
1059
+ modeClass += ' bg-green-100 text-green-500';
1060
+ progressColor = 'bg-green-500';
1061
+ break;
1062
+ }
1063
+
1064
+ modeText.innerHTML = `<i class="fas ${icon}"></i> <span>${modeName}</span>`;
1065
+ modeText.className = modeClass;
1066
+ progressBar.className = progressColor;
1067
+ }
1068
+
1069
+ function updateButtonStates() {
1070
+ if (isRunning) {
1071
+ startBtn.disabled = true;
1072
+ startBtn.innerHTML = '<i class="fas fa-play"></i> <span class="hidden sm:inline">Running...</span>';
1073
+ pauseBtn.disabled = false;
1074
+ resetBtn.disabled = false;
1075
+ skipToBreakBtn.disabled = currentMode !== 'work';
1076
+ skipToBreakBtn.classList.toggle('hidden', currentMode !== 'work');
1077
+ } else {
1078
+ const isAtStart = timeLeft >= getCurrentPhaseTotalSeconds();
1079
+
1080
+ startBtn.disabled = false;
1081
+ startBtn.innerHTML = isAtStart ?
1082
+ '<i class="fas fa-play"></i> <span class="hidden sm:inline">Start</span>' :
1083
+ '<i class="fas fa-play"></i> <span class="hidden sm:inline">Resume</span>';
1084
+
1085
+ pauseBtn.disabled = true;
1086
+ resetBtn.disabled = isAtStart;
1087
+ skipToBreakBtn.disabled = true;
1088
+ skipToBreakBtn.classList.add('hidden');
1089
+ }
1090
+ }
1091
+
1092
+ function getCurrentPhaseTotalSeconds() {
1093
+ switch (currentMode) {
1094
+ case 'work': return settings.workDuration * 60;
1095
+ case 'break': return settings.breakDuration * 60;
1096
+ case 'longBreak': return settings.longBreakDuration * 60;
1097
+ default: return 25 * 60;
1098
+ }
1099
+ }
1100
+
1101
+ function updateTimerDisplay() {
1102
+ // Format time
1103
+ const minutes = Math.floor(timeLeft / 60);
1104
+ const seconds = timeLeft % 60;
1105
+ const displayTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
1106
+ timerDisplay.textContent = displayTime;
1107
+
1108
+ // Update document title
1109
+ const modeTitle = currentMode === 'work' ? 'Work' : currentMode === 'break' ? 'Short Break' : 'Long Break';
1110
+ document.title = `(${displayTime}) ${modeTitle} - ZenPomo Pro`;
1111
+
1112
+ // Change color based on time and mode
1113
+ if (timeLeft <= 60 && timeLeft > 0) {
1114
+ timerDisplay.className = 'text-red-500 text-7xl md:text-8xl font-bold text-center my-8 font-mono transition-colors';
1115
+ } else {
1116
+ let colorClass;
1117
+ switch (currentMode) {
1118
+ case 'work': colorClass = 'text-purple-600'; break;
1119
+ case 'break': colorClass = 'text-pink-500'; break;
1120
+ case 'longBreak': colorClass = 'text-green-500'; break;
1121
+ }
1122
+ timerDisplay.className = `${colorClass} text-7xl md:text-8xl font-bold text-center my-8 font-mono transition-colors`;
1123
+ }
1124
+ }
1125
+
1126
+ function updateProgressBar(reset = false) {
1127
+ if (reset || totalSeconds <= 0 || timeLeft > totalSeconds) {
1128
+ progressBar.style.width = '0%';
1129
+ return;
1130
+ }
1131
+
1132
+ const currentTimeLeft = Math.max(0, Math.min(timeLeft, totalSeconds));
1133
+ const progress = totalSeconds > 0 ? ((totalSeconds - currentTimeLeft) / totalSeconds) * 100 : 0;
1134
+ progressBar.style.width = `${progress}%`;
1135
+ }
1136
+
1137
+ function formatMinutesSeconds(seconds) {
1138
+ const mins = Math.floor(seconds / 60);
1139
+ const secs = seconds % 60;
1140
+ return `${mins} minute${mins !== 1 ? 's' : ''} ${secs} second${secs !== 1 ? 's' : ''}`;
1141
+ }
1142
+
1143
+ function playSound(soundElement) {
1144
+ if ((!soundElement.src && soundElement !== taskCompleteSound) ||
1145
+ (settings.timerSound === 'none' && soundElement !== taskCompleteSound)) {
1146
+ return;
1147
+ }
1148
+
1149
+ try {
1150
+ soundElement.currentTime = 0;
1151
+ const playPromise = soundElement.play();
1152
+
1153
+ if (playPromise !== undefined) {
1154
+ playPromise.then(_ => {
1155
+ // console.log(`Sound ${soundElement.id} played`);
1156
+ }).catch(error => {
1157
+ console.error('Sound play failed:', error);
1158
+ });
1159
+ }
1160
+ } catch (error) {
1161
+ console.error('Sound error:', error);
1162
+ }
1163
+ }
1164
+
1165
+ // --- Task Management ---
1166
+ function loadTasks() {
1167
+ const saved = localStorage.getItem('zenpomoTasks');
1168
+ return saved ? JSON.parse(saved) : [];
1169
+ }
1170
+
1171
+ function saveTasks() {
1172
+ localStorage.setItem('zenpomoTasks', JSON.stringify(tasks));
1173
+ }
1174
+
1175
+ function addTask(taskData) {
1176
+ const newTask = {
1177
+ id: taskData.id || Date.now(),
1178
+ text: taskData.text,
1179
+ category: taskData.category || 'other',
1180
+ priority: taskData.priority || 'medium',
1181
+ dueDate: taskData.dueDate || null,
1182
+ notes: taskData.notes || '',
1183
+ completed: taskData.completed || false,
1184
+ createdAt: taskData.createdAt || new Date().toISOString()
1185
+ };
1186
+
1187
+ tasks.push(newTask);
1188
+ saveTasks();
1189
+ renderTasks();
1190
+ updateTaskStats();
1191
+ scrollToTask(newTask.id);
1192
+ return newTask;
1193
+ }
1194
+
1195
+ function updateTask(taskId, updatedData) {
1196
+ tasks = tasks.map(task =>
1197
+ task.id === taskId ? { ...task, ...updatedData } : task
1198
+ );
1199
+ saveTasks();
1200
+ renderTasks();
1201
+ updateTaskStats();
1202
+ scrollToTask(taskId);
1203
+ }
1204
+
1205
+ function deleteTask(taskId) {
1206
+ tasks = tasks.filter(task => task.id !== taskId);
1207
+ saveTasks();
1208
+ renderTasks();
1209
+ updateTaskStats();
1210
+ showNotification('Task Deleted', 'The task has been removed.', 'info');
1211
+ }
1212
+
1213
+ function toggleTaskComplete(taskId) {
1214
+ let taskCompleted = false;
1215
+
1216
+ tasks = tasks.map(task => {
1217
+ if (task.id === taskId) {
1218
+ taskCompleted = !task.completed;
1219
+ return { ...task, completed: taskCompleted };
1220
+ }
1221
+ return task;
1222
+ });
1223
+
1224
+ saveTasks();
1225
+ renderTasks();
1226
+ updateTaskStats();
1227
+
1228
+ if (taskCompleted) {
1229
+ playSound(taskCompleteSound);
1230
+ showNotification('Task Completed!', 'Well done!', 'success');
1231
+ }
1232
+ }
1233
+
1234
+ function clearCompletedTasks() {
1235
+ const completedCount = tasks.filter(t => t.completed).length;
1236
+
1237
+ if (completedCount === 0) {
1238
+ showNotification('No Completed Tasks', 'Nothing to clear.', 'info');
1239
+ return;
1240
+ }
1241
+
1242
+ tasks = tasks.filter(task => !task.completed);
1243
+ saveTasks();
1244
+ renderTasks();
1245
+ updateTaskStats();
1246
+ showNotification('Tasks Cleared', `${completedCount} completed task(s) removed.`, 'success');
1247
+ }
1248
+
1249
+ function scrollToTask(taskId) {
1250
+ const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`);
1251
+ if (taskElement) {
1252
+ taskElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
1253
+
1254
+ // Add highlight effect
1255
+ taskElement.classList.add('bg-yellow-50');
1256
+ setTimeout(() => taskElement.classList.remove('bg-yellow-50'), 1000);
1257
+ }
1258
+ }
1259
+
1260
+ function renderTasks() {
1261
+ // Filter by current category
1262
+ const filtered = tasks.filter(t =>
1263
+ currentCategoryFilter === 'all' || t.category === currentCategoryFilter
1264
+ );
1265
+
1266
+ // Sort: incomplete first, then by priority (high to low), then by creation date (newest first)
1267
+ filtered.sort((a, b) =>
1268
+ (a.completed - b.completed) ||
1269
+ ({ high: 1, medium: 2, low: 3 }[a.priority] - { high: 1, medium: 2, low: 3 }[b.priority]) ||
1270
+ (new Date(b.createdAt) - new Date(a.createdAt))
1271
+ );
1272
+
1273
+ taskList.innerHTML = '';
1274
+
1275
+ if (filtered.length === 0) {
1276
+ emptyState.classList.remove('hidden');
1277
+ taskList.classList.add('hidden');
1278
+ } else {
1279
+ emptyState.classList.add('hidden');
1280
+ taskList.classList.remove('hidden');
1281
+
1282
+ filtered.forEach(task => {
1283
+ const isOverdue = task.dueDate && !task.completed &&
1284
+ new Date(task.dueDate + 'T00:00:00') < new Date().setHours(0,0,0,0);
1285
+
1286
+ const formattedDueDate = task.dueDate ?
1287
+ new Date(task.dueDate + 'T00:00:00').toLocaleDateString(undefined, {
1288
+ year: 'numeric',
1289
+ month: 'short',
1290
+ day: 'numeric'
1291
+ }) : '';
1292
+
1293
+ const taskItem = document.createElement('li');
1294
+ taskItem.className = `task-item flex items-center p-4 bg-gray-50 rounded-xl mb-3 transition-all hover:translate-x-1 ${task.completed ? 'opacity-80 bg-gray-100' : ''}`;
1295
+ taskItem.dataset.id = task.id;
1296
+
1297
+ // Priority indicator
1298
+ taskItem.innerHTML = `
1299
+ <div class="priority-dot w-2 h-2 rounded-full mr-3 ${PRIORITY_COLORS[task.priority]}" title="Priority: ${task.priority}"></div>
1300
+ <input type="checkbox" class="w-5 h-5 mr-3 cursor-pointer" ${task.completed ? 'checked' : ''}>
1301
+ <div class="flex-1 overflow-hidden">
1302
+ <span class="task-text block mb-1 ${task.completed ? 'line-through text-gray-500' : 'text-gray-800'}">${escapeHTML(task.text)}</span>
1303
+ <div class="flex flex-wrap items-center gap-2 text-xs">
1304
+ ${task.category ? `<span class="task-category px-2 py-0.5 rounded-full bg-purple-100 text-purple-600">${task.category}</span>` : ''}
1305
+ ${formattedDueDate ? `
1306
+ <span class="flex items-center gap-1 ${isOverdue ? 'text-red-500 font-semibold' : 'text-gray-500'}">
1307
+ <i class="far fa-calendar-alt"></i>
1308
+ <span>${formattedDueDate} ${isOverdue ? '(Overdue)' : ''}</span>
1309
+ </span>` : ''}
1310
+ ${task.notes ? `<span class="flex items-center gap-1 text-gray-500"><i class="far fa-sticky-note"></i> <span>Has notes</span></span>` : ''}
1311
+ </div>
1312
+ </div>
1313
+ <div class="task-actions flex gap-1 opacity-0 transition-opacity">
1314
+ <button class="edit-btn w-7 h-7 rounded-full flex items-center justify-center hover:bg-gray-200 transition-all text-gray-500 hover:text-blue-500" title="Edit Task">
1315
+ <i class="far fa-edit"></i>
1316
+ </button>
1317
+ <button class="delete-btn w-7 h-7 rounded-full flex items-center justify-center hover:bg-gray-200 transition-all text-gray-500 hover:text-red-500" title="Delete Task">
1318
+ <i class="far fa-trash-alt"></i>
1319
+ </button>
1320
+ </div>
1321
+ `;
1322
+
1323
+ // Add event listeners
1324
+ taskItem.querySelector('input[type="checkbox"]').addEventListener('change', () => toggleTaskComplete(task.id));
1325
+ taskItem.querySelector('.edit-btn').addEventListener('click', (e) => {
1326
+ e.stopPropagation();
1327
+ openEditTaskModal(task.id);
1328
+ });
1329
+ taskItem.querySelector('.delete-btn').addEventListener('click', (e) => {
1330
+ e.stopPropagation();
1331
+ deleteTask(task.id);
1332
+ });
1333
+
1334
+ // Click anywhere on task to toggle (unless clicking buttons)
1335
+ taskItem.addEventListener('click', (e) => {
1336
+ if (!e.target.closest('button') && !e.target.closest('input[type="checkbox"]')) {
1337
+ toggleTaskComplete(task.id);
1338
+ }
1339
+ });
1340
+
1341
+ taskList.appendChild(taskItem);
1342
+ });
1343
+ }
1344
+
1345
+ updateTaskStats();
1346
+ }
1347
+
1348
+ function updateTaskStats() {
1349
+ const total = tasks.length;
1350
+ const completed = tasks.filter(t => t.completed).length;
1351
+ totalTasksSpan.textContent = `${total} ${total === 1 ? 'task' : 'tasks'}`;
1352
+ completedTasksSpan.textContent = `${completed} completed`;
1353
+ }
1354
+
1355
+ function handleAddQuickTask() {
1356
+ const text = newTaskInput.value.trim();
1357
+ if (!text) {
1358
+ showNotification('Empty Task', 'Please enter task text.', 'error');
1359
+ newTaskInput.focus();
1360
+ return;
1361
+ }
1362
+
1363
+ const category = currentCategoryFilter !== 'all' ? currentCategoryFilter : 'other';
1364
+ addTask({ text, category });
1365
+ newTaskInput.value = '';
1366
+ showNotification('Task Added', 'Quick task added successfully!', 'success');
1367
+ }
1368
+
1369
+ // --- Task Modal Functions ---
1370
+ function openAddTaskModal() {
1371
+ currentTaskEditingId = null;
1372
+ modalTaskTitle.textContent = 'Add New Task';
1373
+ taskIdInput.value = '';
1374
+ taskNameInput.value = '';
1375
+ taskCategoryInput.value = currentCategoryFilter !== 'all' ? currentCategoryFilter : 'work';
1376
+ taskPriorityInput.value = 'medium';
1377
+ taskDueDateInput.value = '';
1378
+ taskNotesInput.value = '';
1379
+ taskModal.classList.add('open');
1380
+ taskNameInput.focus();
1381
+ }
1382
+
1383
+ function openEditTaskModal(taskId) {
1384
+ const task = tasks.find(t => t.id === taskId);
1385
+ if (!task) return;
1386
+
1387
+ currentTaskEditingId = taskId;
1388
+ modalTaskTitle.textContent = 'Edit Task';
1389
+ taskIdInput.value = task.id;
1390
+ taskNameInput.value = task.text;
1391
+ taskCategoryInput.value = task.category;
1392
+ taskPriorityInput.value = task.priority;
1393
+ taskDueDateInput.value = task.dueDate || '';
1394
+ taskNotesInput.value = task.notes || '';
1395
+ taskModal.classList.add('open');
1396
+ taskNameInput.focus();
1397
+ }
1398
+
1399
+ function closeTaskModal() {
1400
+ taskModal.classList.remove('open');
1401
+
1402
+ // Reset modal after animation
1403
+ setTimeout(() => {
1404
+ currentTaskEditingId = null;
1405
+ modalTaskTitle.textContent = 'Add New Task';
1406
+ taskIdInput.value = '';
1407
+ taskNameInput.value = '';
1408
+ taskCategoryInput.value = 'work';
1409
+ taskPriorityInput.value = 'medium';
1410
+ taskDueDateInput.value = '';
1411
+ taskNotesInput.value = '';
1412
+ }, 300);
1413
+ }
1414
+
1415
+ function saveTaskFromModal() {
1416
+ const text = taskNameInput.value.trim();
1417
+ if (!text) {
1418
+ showNotification('Error', 'Task name cannot be empty!', 'error');
1419
+ taskNameInput.focus();
1420
+ return;
1421
+ }
1422
+
1423
+ const taskData = {
1424
+ text,
1425
+ category: taskCategoryInput.value,
1426
+ priority: taskPriorityInput.value,
1427
+ dueDate: taskDueDateInput.value || null,
1428
+ notes: taskNotesInput.value.trim()
1429
+ };
1430
+
1431
+ if (currentTaskEditingId) {
1432
+ updateTask(currentTaskEditingId, taskData);
1433
+ showNotification('Task Updated', 'Task details saved.', 'success');
1434
+ } else {
1435
+ addTask(taskData);
1436
+ showNotification('Task Added', 'New task created.', 'success');
1437
+ }
1438
+
1439
+ closeTaskModal();
1440
+ }
1441
+
1442
+ // --- Statistics Management ---
1443
+ function defaultStats() {
1444
+ return {
1445
+ pomodoros: {},
1446
+ totalPomodoros: 0,
1447
+ streak: 0,
1448
+ lastActivityDate: null,
1449
+ lastResetDate: new Date().toISOString()
1450
+ };
1451
+ }
1452
+
1453
+ function loadStats() {
1454
+ const saved = localStorage.getItem('zenpomoStats');
1455
+ let loaded = saved ? JSON.parse(saved) : defaultStats();
1456
+ loaded = { ...defaultStats(), ...loaded };
1457
+
1458
+ // Ensure required properties exist
1459
+ if (!loaded.pomodoros) loaded.pomodoros = {};
1460
+ if (!loaded.totalPomodoros) loaded.totalPomodoros = 0;
1461
+ if (!loaded.lastActivityDate) loaded.lastActivityDate = null;
1462
+
1463
+ // Update streak if needed
1464
+ updateStreak(loaded);
1465
+
1466
+ return loaded;
1467
+ }
1468
+
1469
+ function updateStreak(statsObj) {
1470
+ if (!statsObj.lastActivityDate) {
1471
+ statsObj.streak = 0;
1472
+ return;
1473
+ }
1474
+
1475
+ const lastDate = new Date(statsObj.lastActivityDate);
1476
+ const today = new Date();
1477
+ const yesterday = new Date(today);
1478
+ yesterday.setDate(yesterday.getDate() - 1);
1479
+
1480
+ // Reset streak if last activity was before yesterday
1481
+ if (lastDate.toDateString() !== today.toDateString() &&
1482
+ lastDate.toDateString() !== yesterday.toDateString()) {
1483
+ statsObj.streak = 0;
1484
+ }
1485
+ }
1486
+
1487
+ function saveStats() {
1488
+ localStorage.setItem('zenpomoStats', JSON.stringify(stats));
1489
+ }
1490
+
1491
+ function recordPomodoroCompletion() {
1492
+ const today = new Date();
1493
+ const dateStr = today.toISOString().split('T')[0];
1494
+
1495
+ // Update count for today
1496
+ stats.pomodoros[dateStr] = (stats.pomodoros[dateStr] || 0) + 1;
1497
+ stats.totalPomodoros = (stats.totalPomodoros || 0) + 1;
1498
+
1499
+ // Update streak
1500
+ const yesterday = new Date(today);
1501
+ yesterday.setDate(yesterday.getDate() - 1);
1502
+ const yesterdayStr = yesterday.toISOString().split('T')[0];
1503
+
1504
+ if (!stats.lastActivityDate ||
1505
+ stats.lastActivityDate === yesterdayStr ||
1506
+ stats.lastActivityDate === dateStr) {
1507
+ // Continue streak
1508
+ stats.lastActivityDate = dateStr;
1509
+ stats.streak = (stats.streak || 0) + 1;
1510
+ } else {
1511
+ // Reset streak
1512
+ stats.lastActivityDate = dateStr;
1513
+ stats.streak = 1;
1514
+ }
1515
+
1516
+ saveStats();
1517
+ }
1518
+
1519
+ function getPomodorosForDate(dateStr) {
1520
+ return stats.pomodoros[dateStr] || 0;
1521
+ }
1522
+
1523
+ function getPomodorosThisWeek() {
1524
+ const today = new Date();
1525
+ const startOfWeek = new Date(today);
1526
+ startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()); // Sunday
1527
+
1528
+ let count = 0;
1529
+
1530
+ for (let i = 0; i < 7; i++) {
1531
+ const date = new Date(startOfWeek);
1532
+ date.setDate(date.getDate() + i);
1533
+ const dateStr = date.toISOString().split('T')[0];
1534
+ count += getPomodorosForDate(dateStr);
1535
+ }
1536
+
1537
+ return count;
1538
+ }
1539
+
1540
+ function getTotalFocusTime() {
1541
+ return (stats.totalPomodoros || 0) * settings.workDuration;
1542
+ }
1543
+
1544
+ function updateStatsDisplay() {
1545
+ const todayStr = new Date().toISOString().split('T')[0];
1546
+ const todayCount = getPomodorosForDate(todayStr);
1547
+ const weekCount = getPomodorosThisWeek();
1548
+ const totalCount = stats.totalPomodoros || 0;
1549
+
1550
+ // Format total focus time
1551
+ const totalMinutes = getTotalFocusTime();
1552
+ const hours = Math.floor(totalMinutes / 60);
1553
+ const minutes = totalMinutes % 60;
1554
+
1555
+ // Update main display
1556
+ todayPomodorosDisplay.textContent = todayCount;
1557
+ weekPomodorosDisplay.textContent = weekCount;
1558
+ totalPomodorosDisplay.textContent = totalCount;
1559
+
1560
+ // Update modal display
1561
+ modalTodayPomodoros.textContent = todayCount;
1562
+ modalWeekPomodoros.textContent = weekCount;
1563
+ modalTotalPomodoros.textContent = totalCount;
1564
+ modalTotalFocusTime.textContent = `${hours}h ${minutes}m`;
1565
+ modalCurrentStreak.textContent = `${stats.streak || 0} ${stats.streak === 1 ? 'day' : 'days'}`;
1566
+
1567
+ renderHistoryList();
1568
+ }
1569
+
1570
+ function renderHistoryList() {
1571
+ const sortedDates = Object.keys(stats.pomodoros)
1572
+ .sort((a, b) => new Date(b) - new Date(a))
1573
+ .slice(0, 20); // Show only recent 20 items
1574
+
1575
+ historyList.innerHTML = '';
1576
+
1577
+ if (sortedDates.length === 0) {
1578
+ historyEmptyState.classList.remove('hidden');
1579
+ return;
1580
+ }
1581
+
1582
+ historyEmptyState.classList.add('hidden');
1583
+
1584
+ sortedDates.forEach(dateStr => {
1585
+ const count = stats.pomodoros[dateStr];
1586
+ const date = new Date(dateStr + 'T00:00:00');
1587
+ const formattedDate = date.toLocaleDateString(undefined, {
1588
+ weekday: 'short',
1589
+ year: 'numeric',
1590
+ month: 'short',
1591
+ day: 'numeric'
1592
+ });
1593
+
1594
+ const item = document.createElement('div');
1595
+ item.className = 'history-item flex justify-between items-center px-4 py-3 border-b border-gray-200 last:border-b-0';
1596
+ item.innerHTML = `
1597
+ <span class="history-date font-medium">${formattedDate}</span>
1598
+ <span class="history-count px-2 py-0.5 rounded-full bg-purple-100 text-purple-600 font-semibold">
1599
+ ${count} ${count === 1 ? 'session' : 'sessions'}
1600
+ </span>
1601
+ `;
1602
+
1603
+ historyList.appendChild(item);
1604
+ });
1605
+ }
1606
+
1607
+ function resetAllStats() {
1608
+ if (confirm("Are you sure you want to reset ALL statistics? This cannot be undone.")) {
1609
+ stats = defaultStats();
1610
+ stats.lastResetDate = new Date().toISOString();
1611
+ saveStats();
1612
+ updateStatsDisplay();
1613
+ showNotification('Stats Reset', 'All statistics have been cleared.', 'success');
1614
+ }
1615
+ }
1616
+
1617
+ // --- Stats Modal Functions ---
1618
+ function openStatsModal() {
1619
+ updateStatsDisplay();
1620
+ statsModal.classList.add('open');
1621
+ activateStatsTab('history');
1622
+ }
1623
+
1624
+ function closeStatsModal() {
1625
+ statsModal.classList.remove('open');
1626
+ }
1627
+
1628
+ function activateStatsTab(tabId) {
1629
+ // Update tabs
1630
+ statsTabs.forEach(tab => {
1631
+ tab.classList.toggle('text-purple-600', tab.dataset.tab === tabId);
1632
+ tab.classList.toggle('border-b-2', tab.dataset.tab === tabId);
1633
+ tab.classList.toggle('border-purple-600', tab.dataset.tab === tabId);
1634
+ });
1635
+
1636
+ // Update content
1637
+ statsTabContents.forEach(content => {
1638
+ content.classList.toggle('hidden', content.id !== `${tabId}-tab`);
1639
+ });
1640
+ }
1641
+
1642
+ // --- Notifications ---
1643
+ function showNotification(title, message, type = 'info') {
1644
+ clearTimeout(notificationTimeout);
1645
+
1646
+ // Set notification content
1647
+ notificationTitle.textContent = title;
1648
+ notificationMessage.textContent = message;
1649
+
1650
+ // Set notification style
1651
+ notification.className = 'fixed bottom-6 right-6 bg-white rounded-lg shadow-lg p-4 flex items-center gap-3 max-w-sm w-full border-l-4';
1652
+
1653
+ switch (type) {
1654
+ case 'success':
1655
+ notificationIcon.className = 'fas fa-check-circle text-green-500 text-xl';
1656
+ notification.classList.add('border-green-500');
1657
+ break;
1658
+ case 'error':
1659
+ notificationIcon.className = 'fas fa-times-circle text-red-500 text-xl';
1660
+ notification.classList.add('border-red-500');
1661
+ break;
1662
+ case 'info':
1663
+ notificationIcon.className = 'fas fa-info-circle text-blue-500 text-xl';
1664
+ notification.classList.add('border-blue-500');
1665
+ break;
1666
+ }
1667
+
1668
+ // Show notification
1669
+ notification.classList.remove('hidden', 'opacity-0');
1670
+ notification.classList.add('show');
1671
+
1672
+ // Auto-hide after 5 seconds
1673
+ notificationTimeout = setTimeout(() => {
1674
+ notification.classList.remove('show');
1675
+ }, 5000);
1676
+ }
1677
+
1678
+ function hideNotification() {
1679
+ clearTimeout(notificationTimeout);
1680
+ notification.classList.remove('show');
1681
+ }
1682
+
1683
+ // --- Utility Functions ---
1684
+ function escapeHTML(str) {
1685
+ const div = document.createElement('div');
1686
+ div.textContent = str;
1687
+ return div.innerHTML;
1688
+ }
1689
+
1690
+ // --- Fullscreen Mode ---
1691
+ function toggleFullscreen() {
1692
+ if (!document.fullscreenElement) {
1693
+ document.documentElement.requestFullscreen().catch(err => {
1694
+ console.error('Fullscreen error:', err);
1695
+ showNotification('Fullscreen Error', 'Could not enter fullscreen mode.', 'error');
1696
+ });
1697
+ isFullscreen = true;
1698
+ } else {
1699
+ if (document.exitFullscreen) {
1700
+ document.exitFullscreen();
1701
+ isFullscreen = false;
1702
+ }
1703
+ }
1704
+ }
1705
+
1706
+ // --- Event Listeners ---
1707
+ // Timer controls
1708
+ startBtn.addEventListener('click', startTimer);
1709
+ pauseBtn.addEventListener('click', pauseTimer);
1710
+ resetBtn.addEventListener('click', () => resetTimer(false));
1711
+ skipToBreakBtn.addEventListener('click', skipToBreak);
1712
+ fullscreenBtn.addEventListener('click', toggleFullscreen);
1713
+
1714
+ // Settings
1715
+ applySettingsBtn.addEventListener('click', applySettings);
1716
+ toggleSettingsBtn.addEventListener('click', function() {
1717
+ settingsContent.classList.toggle('open');
1718
+ this.innerHTML = settingsContent.classList.contains('open') ?
1719
+ '<i class="fas fa-chevron-up"></i> <span>Hide Settings</span>' :
1720
+ '<i class="fas fa-sliders-h"></i> <span>Timer Settings</span>';
1721
+ });
1722
+
1723
+ // Tasks
1724
+ addTaskBtn.addEventListener('click', handleAddQuickTask);
1725
+ newTaskInput.addEventListener('keypress', (e) => {
1726
+ if (e.key === 'Enter') handleAddQuickTask();
1727
+ });
1728
+ clearCompletedBtn.addEventListener('click', clearCompletedTasks);
1729
+ categoryBtns.forEach(btn => {
1730
+ btn.addEventListener('click', function() {
1731
+ categoryBtns.forEach(b => {
1732
+ b.classList.remove('bg-purple-600', 'text-white');
1733
+ b.classList.add('bg-gray-200', 'hover:bg-gray-300');
1734
+ });
1735
+ this.classList.remove('bg-gray-200', 'hover:bg-gray-300');
1736
+ this.classList.add('bg-purple-600', 'text-white');
1737
+ currentCategoryFilter = this.dataset.category;
1738
+ renderTasks();
1739
+ });
1740
+ });
1741
+
1742
+ // Task modal
1743
+ addTaskModalBtn.addEventListener('click', openAddTaskModal);
1744
+ closeModalBtn.addEventListener('click', closeTaskModal);
1745
+ cancelTaskBtn.addEventListener('click', closeTaskModal);
1746
+ saveTaskBtn.addEventListener('click', saveTaskFromModal);
1747
+ taskModal.addEventListener('click', (e) => {
1748
+ if (e.target === taskModal) closeTaskModal();
1749
+ });
1750
+
1751
+ // Stats modal
1752
+ statsBtn.addEventListener('click', openStatsModal);
1753
+ closeStatsModalBtn.addEventListener('click', closeStatsModal);
1754
+ statsModal.addEventListener('click', (e) => {
1755
+ if (e.target === statsModal) closeStatsModal();
1756
+ });
1757
+ resetStatsBtn.addEventListener('click', resetAllStats);
1758
+
1759
+ // Tabs in stats modal
1760
+ statsTabs.forEach(tab => {
1761
+ tab.addEventListener('click', function() {
1762
+ activateStatsTab(this.dataset.tab);
1763
+ });
1764
+ });
1765
+
1766
+ // Notification
1767
+ closeNotificationBtn.addEventListener('click', hideNotification);
1768
+
1769
+ // Navigation links
1770
+ document.getElementById('nav-stats')?.addEventListener('click', (e) => {
1771
+ e.preventDefault();
1772
+ statsBtn.click();
1773
+ });
1774
+ document.getElementById('nav-tasks')?.addEventListener('click', (e) => {
1775
+ e.preventDefault();
1776
+ document.querySelector('.todo-card').scrollIntoView({ behavior: 'smooth' });
1777
+ });
1778
+ document.getElementById('nav-settings')?.addEventListener('click', (e) => {
1779
+ e.preventDefault();
1780
+ document.querySelector('.pomodoro-card').scrollIntoView({ behavior: 'smooth' });
1781
+ if (!settingsContent.classList.contains('open')) {
1782
+ toggleSettingsBtn.click();
1783
+ }
1784
+ });
1785
+
1786
+ // Fullscreen change listener
1787
+ document.addEventListener('fullscreenchange', () => {
1788
+ isFullscreen = !!document.fullscreenElement;
1789
+ fullscreenBtn.innerHTML = isFullscreen ?
1790
+ '<i class="fas fa-compress"></i>' :
1791
+ '<i class="fas fa-expand"></i>';
1792
+ fullscreenBtn.title = isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen';
1793
+ });
1794
+
1795
+ // Page visibility change (for notifications)
1796
+ document.addEventListener('visibilitychange', () => {
1797
+ if (!document.hidden && isRunning) {
1798
+ const modeName = currentMode === 'work' ? 'Work' : currentMode === 'break' ? 'Short Break' : 'Long Break';
1799
+ showNotification('ZenPomo Pro Reminder', `${modeName} timer is running: ${timerDisplay.textContent} remaining`, 'info');
1800
+ }
1801
+ });
1802
+
1803
+ // Initialize the app
1804
+ initializeApp();
1805
+ });
1806
+ </script>
1807
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=azizln/zenpomo" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1808
+ </html>