AnySue commited on
Commit
469f625
Β·
verified Β·
1 Parent(s): 56d658d

Create a to-do list that sends me an email reminder with a button in the email that I can click when I no longer need reminders.

Browse files
Files changed (6) hide show
  1. README.md +8 -5
  2. components/navbar.js +129 -0
  3. components/task-card.js +209 -0
  4. index.html +210 -19
  5. script.js +271 -0
  6. style.css +133 -19
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Taskmail Reminders
3
- emoji: πŸƒ
4
- colorFrom: yellow
5
- colorTo: red
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: TaskMail Reminders πŸ“§
3
+ colorFrom: pink
4
+ colorTo: blue
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/navbar.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class NavHeader extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .navbar {
7
+ background: rgba(255, 255, 255, 0.95);
8
+ backdrop-filter: blur(10px);
9
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
10
+ }
11
+
12
+ .logo {
13
+ font-size: 1.5rem;
14
+ font-weight: 700;
15
+ background: linear-gradient(135deg, #3b82f6 0%, #14b8a6 100%);
16
+ -webkit-background-clip: text;
17
+ -webkit-text-fill-color: transparent;
18
+ background-clip: text;
19
+ }
20
+
21
+ .nav-link {
22
+ position: relative;
23
+ transition: color 0.3s ease;
24
+ }
25
+
26
+ .nav-link::after {
27
+ content: '';
28
+ position: absolute;
29
+ width: 0;
30
+ height: 2px;
31
+ bottom: -4px;
32
+ left: 50%;
33
+ background-color: #3b82f6;
34
+ transition: all 0.3s ease;
35
+ }
36
+
37
+ .nav-link:hover::after {
38
+ width: 100%;
39
+ left: 0;
40
+ }
41
+
42
+ @media (max-width: 768px) {
43
+ .mobile-menu {
44
+ transform: translateX(100%);
45
+ transition: transform 0.3s ease;
46
+ }
47
+
48
+ .mobile-menu.active {
49
+ transform: translateX(0);
50
+ }
51
+ }
52
+ </style>
53
+
54
+ <nav class="navbar sticky top-0 z-40 px-4 md:px-6 py-4">
55
+ <div class="container mx-auto flex justify-between items-center">
56
+ <div class="flex items-center gap-2">
57
+ <i data-feather="check-square" class="w-8 h-8 text-primary-500"></i>
58
+ <span class="logo">TaskMail</span>
59
+ </div>
60
+
61
+ <div class="hidden md:flex items-center gap-6">
62
+ <a href="#" class="nav-link text-gray-700 hover:text-primary-500 font-medium">
63
+ Tasks
64
+ </a>
65
+ <a href="#" class="nav-link text-gray-700 hover:text-primary-500 font-medium">
66
+ Calendar
67
+ </a>
68
+ <a href="#" class="nav-link text-gray-700 hover:text-primary-500 font-medium">
69
+ Analytics
70
+ </a>
71
+ <button class="bg-primary-500 text-white px-4 py-2 rounded-lg hover:bg-primary-600 transition-colors flex items-center gap-2">
72
+ <i data-feather="user" class="w-4 h-4"></i>
73
+ Profile
74
+ </button>
75
+ </div>
76
+
77
+ <button class="md:hidden" id="mobileMenuBtn">
78
+ <i data-feather="menu" class="w-6 h-6 text-gray-700"></i>
79
+ </button>
80
+ </div>
81
+
82
+ <!-- Mobile Menu -->
83
+ <div class="mobile-menu fixed top-0 right-0 w-64 h-full bg-white shadow-2xl md:hidden" id="mobileMenu">
84
+ <div class="p-6">
85
+ <button class="absolute top-4 right-4" id="closeMobileMenu">
86
+ <i data-feather="x" class="w-6 h-6 text-gray-700"></i>
87
+ </button>
88
+
89
+ <div class="mt-8 space-y-4">
90
+ <a href="#" class="block text-gray-700 hover:text-primary-500 font-medium py-2">
91
+ Tasks
92
+ </a>
93
+ <a href="#" class="block text-gray-700 hover:text-primary-500 font-medium py-2">
94
+ Calendar
95
+ </a>
96
+ <a href="#" class="block text-gray-700 hover:text-primary-500 font-medium py-2">
97
+ Analytics
98
+ </a>
99
+ <button class="w-full bg-primary-500 text-white px-4 py-2 rounded-lg hover:bg-primary-600 transition-colors flex items-center justify-center gap-2 mt-4">
100
+ <i data-feather="user" class="w-4 h-4"></i>
101
+ Profile
102
+ </button>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </nav>
107
+ `;
108
+
109
+ // Mobile menu functionality
110
+ const mobileMenuBtn = this.shadowRoot.getElementById('mobileMenuBtn');
111
+ const closeMobileMenu = this.shadowRoot.getElementById('closeMobileMenu');
112
+ const mobileMenu = this.shadowRoot.getElementById('mobileMenu');
113
+
114
+ mobileMenuBtn.addEventListener('click', () => {
115
+ mobileMenu.classList.add('active');
116
+ });
117
+
118
+ closeMobileMenu.addEventListener('click', () => {
119
+ mobileMenu.classList.remove('active');
120
+ });
121
+
122
+ // Initialize feather icons
123
+ setTimeout(() => {
124
+ feather.replace();
125
+ }, 0);
126
+ }
127
+ }
128
+
129
+ customElements.define('nav-header', NavHeader);
components/task-card.js ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class TaskCard extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+
5
+ const id = this.getAttribute('id');
6
+ const text = this.getAttribute('text');
7
+ const email = this.getAttribute('email');
8
+ const completed = this.getAttribute('completed') === 'true';
9
+ const hasReminder = this.getAttribute('has-reminder') === 'true';
10
+ const reminderStopped = this.getAttribute('reminder-stopped') === 'true';
11
+ const createdAt = this.getAttribute('created-at');
12
+
13
+ const createdDate = new Date(createdAt).toLocaleDateString();
14
+ const timeAgo = this.getTimeAgo(createdAt);
15
+
16
+ this.shadowRoot.innerHTML = `
17
+ <style>
18
+ .task-card {
19
+ background: white;
20
+ border-radius: 12px;
21
+ padding: 20px;
22
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
23
+ transition: all 0.3s ease;
24
+ position: relative;
25
+ overflow: hidden;
26
+ }
27
+
28
+ .task-card:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
31
+ }
32
+
33
+ .task-card.completed {
34
+ opacity: 0.7;
35
+ background: #f9fafb;
36
+ }
37
+
38
+ .task-text {
39
+ transition: all 0.3s ease;
40
+ }
41
+
42
+ .task-card.completed .task-text {
43
+ text-decoration: line-through;
44
+ color: #9ca3af;
45
+ }
46
+
47
+ .task-checkbox {
48
+ width: 24px;
49
+ height: 24px;
50
+ cursor: pointer;
51
+ border: 2px solid #d1d5db;
52
+ border-radius: 6px;
53
+ position: relative;
54
+ transition: all 0.2s ease;
55
+ }
56
+
57
+ .task-checkbox.checked {
58
+ background-color: #10b981;
59
+ border-color: #10b981;
60
+ }
61
+
62
+ .task-checkbox.checked::after {
63
+ content: '';
64
+ position: absolute;
65
+ left: 7px;
66
+ top: 3px;
67
+ width: 6px;
68
+ height: 12px;
69
+ border: solid white;
70
+ border-width: 0 2px 2px 0;
71
+ transform: rotate(45deg);
72
+ }
73
+
74
+ .action-btn {
75
+ padding: 8px;
76
+ border-radius: 8px;
77
+ transition: all 0.2s ease;
78
+ cursor: pointer;
79
+ }
80
+
81
+ .action-btn:hover {
82
+ background-color: #f3f4f6;
83
+ }
84
+
85
+ .action-btn.delete:hover {
86
+ background-color: #fee2e2;
87
+ color: #dc2626;
88
+ }
89
+
90
+ .reminder-btn {
91
+ position: relative;
92
+ }
93
+
94
+ .reminder-btn.active {
95
+ color: #3b82f6;
96
+ }
97
+
98
+ .reminder-pulse {
99
+ position: absolute;
100
+ top: 0;
101
+ right: 0;
102
+ width: 10px;
103
+ height: 10px;
104
+ background-color: #10b981;
105
+ border-radius: 50%;
106
+ animation: pulse 2s infinite;
107
+ }
108
+
109
+ @keyframes pulse {
110
+ 0% {
111
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
112
+ }
113
+ 70% {
114
+ box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
115
+ }
116
+ 100% {
117
+ box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
118
+ }
119
+ }
120
+
121
+ .badge {
122
+ display: inline-block;
123
+ padding: 2px 8px;
124
+ border-radius: 12px;
125
+ font-size: 0.75rem;
126
+ font-weight: 500;
127
+ }
128
+
129
+ .badge-reminder {
130
+ background-color: #dbeafe;
131
+ color: #1e40af;
132
+ }
133
+
134
+ .badge-stopped {
135
+ background-color: #fef3c7;
136
+ color: #92400e;
137
+ }
138
+ </style>
139
+
140
+ <div class="task-card ${completed ? 'completed' : ''}">
141
+ <div class="flex items-start gap-3">
142
+ <div class="task-checkbox ${completed ? 'checked' : ''}" data-action="toggle"></div>
143
+
144
+ <div class="flex-1">
145
+ <h3 class="task-text font-semibold text-gray-800 mb-2">${text}</h3>
146
+
147
+ <div class="flex flex-wrap items-center gap-2 mb-3">
148
+ <div class="flex items-center gap-1 text-xs text-gray-500">
149
+ <i data-feather="mail" class="w-3 h-3"></i>
150
+ <span class="truncate max-w-[150px]">${email}</span>
151
+ </div>
152
+ <div class="flex items-center gap-1 text-xs text-gray-500">
153
+ <i data-feather="calendar" class="w-3 h-3"></i>
154
+ <span>${timeAgo}</span>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="flex items-center justify-between">
159
+ <div class="flex gap-2">
160
+ ${hasReminder && !reminderStopped ?
161
+ '<span class="badge badge-reminder">Active Reminder</span>' : ''}
162
+ ${reminderStopped ?
163
+ '<span class="badge badge-stopped">Reminder Stopped</span>' : ''}
164
+ </div>
165
+
166
+ <div class="flex items-center gap-1">
167
+ ${!completed ? `
168
+ <button class="action-btn reminder-btn ${hasReminder && !reminderStopped ? 'active' : ''}"
169
+ data-action="reminder"
170
+ title="${hasReminder && !reminderStopped ? 'Stop Reminder' : 'Set Reminder'}">
171
+ <i data-feather="bell" class="w-4 h-4"></i>
172
+ ${hasReminder && !reminderStopped ? '<div class="reminder-pulse"></div>' : ''}
173
+ </button>
174
+ ` : ''}
175
+ <button class="action-btn delete" data-action="delete" title="Delete Task">
176
+ <i data-feather="trash-2" class="w-4 h-4"></i>
177
+ </button>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ `;
184
+ }
185
+
186
+ getTimeAgo(dateString) {
187
+ const date = new Date(dateString);
188
+ const seconds = Math.floor((new Date() - date) / 1000);
189
+
190
+ let interval = seconds / 31536000;
191
+ if (interval > 1) return Math.floor(interval) + 'y ago';
192
+
193
+ interval = seconds / 2592000;
194
+ if (interval > 1) return Math.floor(interval) + 'mo ago';
195
+
196
+ interval = seconds / 86400;
197
+ if (interval > 1) return Math.floor(interval) + 'd ago';
198
+
199
+ interval = seconds / 3600;
200
+ if (interval > 1) return Math.floor(interval) + 'h ago';
201
+
202
+ interval = seconds / 60;
203
+ if (interval > 1) return Math.floor(interval) + 'm ago';
204
+
205
+ return 'Just now';
206
+ }
207
+ }
208
+
209
+ customElements.define('task-card', TaskCard);
index.html CHANGED
@@ -1,19 +1,210 @@
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>TaskMail - Smart To-Do List with Email Reminders</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: {
17
+ 50: '#eff6ff',
18
+ 100: '#dbeafe',
19
+ 200: '#bfdbfe',
20
+ 300: '#93c5fd',
21
+ 400: '#60a5fa',
22
+ 500: '#3b82f6',
23
+ 600: '#2563eb',
24
+ 700: '#1d4ed8',
25
+ 800: '#1e40af',
26
+ 900: '#1e3a8a',
27
+ },
28
+ secondary: {
29
+ 50: '#f0fdfa',
30
+ 100: '#ccfbf1',
31
+ 200: '#99f6e4',
32
+ 300: '#5eead4',
33
+ 400: '#2dd4bf',
34
+ 500: '#14b8a6',
35
+ 600: '#0d9488',
36
+ 700: '#0f766e',
37
+ 800: '#115e59',
38
+ 900: '#134e4a',
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ </script>
45
+ </head>
46
+ <body class="bg-gradient-to-br from-primary-50 to-secondary-50 min-h-screen">
47
+ <!-- Navigation Component -->
48
+ <nav-header></nav-header>
49
+
50
+ <!-- Main Content -->
51
+ <main class="container mx-auto px-4 py-8 max-w-6xl">
52
+ <!-- Header Section -->
53
+ <div class="text-center mb-10">
54
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-800 mb-3">
55
+ Your Smart To-Do List
56
+ </h1>
57
+ <p class="text-gray-600 text-lg">Stay organized with email reminders</p>
58
+ </div>
59
+
60
+ <!-- Add Task Section -->
61
+ <div class="bg-white rounded-2xl shadow-xl p-6 mb-8">
62
+ <div class="flex flex-col md:flex-row gap-4">
63
+ <input
64
+ type="text"
65
+ id="taskInput"
66
+ placeholder="What needs to be done?"
67
+ class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
68
+ >
69
+ <input
70
+ type="email"
71
+ id="emailInput"
72
+ placeholder="Email for reminders"
73
+ class="md:w-64 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
74
+ >
75
+ <button
76
+ id="addTaskBtn"
77
+ class="px-6 py-3 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors duration-200 flex items-center justify-center gap-2 font-medium"
78
+ >
79
+ <i data-feather="plus-circle" class="w-5 h-5"></i>
80
+ Add Task
81
+ </button>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Filters and Stats -->
86
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
87
+ <div class="bg-white rounded-xl p-4 shadow-lg">
88
+ <div class="flex items-center justify-between">
89
+ <div>
90
+ <p class="text-gray-500 text-sm">Total Tasks</p>
91
+ <p class="text-2xl font-bold text-gray-800" id="totalTasks">0</p>
92
+ </div>
93
+ <div class="bg-blue-100 p-3 rounded-lg">
94
+ <i data-feather="clipboard" class="w-6 h-6 text-blue-600"></i>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <div class="bg-white rounded-xl p-4 shadow-lg">
99
+ <div class="flex items-center justify-between">
100
+ <div>
101
+ <p class="text-gray-500 text-sm">Pending</p>
102
+ <p class="text-2xl font-bold text-orange-600" id="pendingTasks">0</p>
103
+ </div>
104
+ <div class="bg-orange-100 p-3 rounded-lg">
105
+ <i data-feather="clock" class="w-6 h-6 text-orange-600"></i>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ <div class="bg-white rounded-xl p-4 shadow-lg">
110
+ <div class="flex items-center justify-between">
111
+ <div>
112
+ <p class="text-gray-500 text-sm">Completed</p>
113
+ <p class="text-2xl font-bold text-green-600" id="completedTasks">0</p>
114
+ </div>
115
+ <div class="bg-green-100 p-3 rounded-lg">
116
+ <i data-feather="check-circle" class="w-6 h-6 text-green-600"></i>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ <div class="bg-white rounded-xl p-4 shadow-lg">
121
+ <div class="flex items-center justify-between">
122
+ <div>
123
+ <p class="text-gray-500 text-sm">Active Reminders</p>
124
+ <p class="text-2xl font-bold text-purple-600" id="activeReminders">0</p>
125
+ </div>
126
+ <div class="bg-purple-100 p-3 rounded-lg">
127
+ <i data-feather="bell" class="w-6 h-6 text-purple-600"></i>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Filter Buttons -->
134
+ <div class="flex flex-wrap gap-2 mb-6">
135
+ <button data-filter="all" class="filter-btn px-4 py-2 bg-primary-500 text-white rounded-lg transition-colors">
136
+ All Tasks
137
+ </button>
138
+ <button data-filter="pending" class="filter-btn px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">
139
+ Pending
140
+ </button>
141
+ <button data-filter="completed" class="filter-btn px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">
142
+ Completed
143
+ </button>
144
+ <button data-filter="reminders" class="filter-btn px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">
145
+ With Reminders
146
+ </button>
147
+ </div>
148
+
149
+ <!-- Tasks Container -->
150
+ <div id="tasksContainer" class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
151
+ <!-- Tasks will be dynamically added here -->
152
+ </div>
153
+
154
+ <!-- Empty State -->
155
+ <div id="emptyState" class="text-center py-20">
156
+ <div class="inline-flex items-center justify-center w-24 h-24 bg-gray-100 rounded-full mb-4">
157
+ <i data-feather="inbox" class="w-12 h-12 text-gray-400"></i>
158
+ </div>
159
+ <h3 class="text-xl font-semibold text-gray-600 mb-2">No tasks yet</h3>
160
+ <p class="text-gray-500">Add your first task to get started!</p>
161
+ </div>
162
+ </main>
163
+
164
+ <!-- Email Preview Modal -->
165
+ <div id="emailModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
166
+ <div class="bg-white rounded-2xl max-w-2xl w-full max-h-[90vh] overflow-auto">
167
+ <div class="p-6 border-b border-gray-200">
168
+ <div class="flex justify-between items-center">
169
+ <h3 class="text-2xl font-bold text-gray-800">πŸ“§ Email Preview</h3>
170
+ <button id="closeEmailModal" class="text-gray-500 hover:text-gray-700">
171
+ <i data-feather="x" class="w-6 h-6"></i>
172
+ </button>
173
+ </div>
174
+ </div>
175
+ <div class="p-6">
176
+ <div class="bg-gray-50 rounded-lg p-4 mb-4">
177
+ <p class="text-sm text-gray-600 mb-2">From: noreply@taskmail.app</p>
178
+ <p class="text-sm text-gray-600 mb-2">To: <span id="emailTo">email@example.com</span></p>
179
+ <p class="text-sm text-gray-600">Subject: Reminder: <span id="emailSubject">Task</span></p>
180
+ </div>
181
+ <div class="prose max-w-none">
182
+ <p class="mb-4">Hi there! πŸ‘‹</p>
183
+ <p class="mb-4">This is a friendly reminder about your task:</p>
184
+ <div class="bg-blue-50 border-l-4 border-blue-500 p-4 mb-4">
185
+ <p class="font-semibold text-gray-800" id="emailTask">Task description</p>
186
+ </div>
187
+ <p class="mb-6">Don't forget to complete it! You're doing great! πŸ’ͺ</p>
188
+ <button class="bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600 transition-colors">
189
+ βœ… Mark as Done & Stop Reminders
190
+ </button>
191
+ </div>
192
+ </div>
193
+ <div class="p-6 border-t border-gray-200 bg-gray-50 rounded-b-2xl">
194
+ <p class="text-sm text-gray-500 text-center">
195
+ πŸ’‘ <strong>Note:</strong> This is a preview. In production, this email would be sent to your inbox.
196
+ </p>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <!-- Components -->
202
+ <script src="components/navbar.js"></script>
203
+ <script src="components/task-card.js"></script>
204
+
205
+ <!-- Main Script -->
206
+ <script src="script.js"></script>
207
+ <script>feather.replace();</script>
208
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
209
+ </body>
210
+ </html>
script.js ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Task Manager Class
2
+ class TaskManager {
3
+ constructor() {
4
+ this.tasks = this.loadTasks();
5
+ this.currentFilter = 'all';
6
+ this.init();
7
+ }
8
+
9
+ init() {
10
+ this.setupEventListeners();
11
+ this.renderTasks();
12
+ this.updateStats();
13
+ this.startReminderScheduler();
14
+ }
15
+
16
+ setupEventListeners() {
17
+ // Add task
18
+ document.getElementById('addTaskBtn').addEventListener('click', () => this.addTask());
19
+ document.getElementById('taskInput').addEventListener('keypress', (e) => {
20
+ if (e.key === 'Enter') this.addTask();
21
+ });
22
+
23
+ // Filter buttons
24
+ document.querySelectorAll('.filter-btn').forEach(btn => {
25
+ btn.addEventListener('click', (e) => {
26
+ this.setFilter(e.target.dataset.filter);
27
+ });
28
+ });
29
+
30
+ // Email modal
31
+ document.getElementById('closeEmailModal').addEventListener('click', () => {
32
+ document.getElementById('emailModal').classList.add('hidden');
33
+ });
34
+
35
+ // Close modal on outside click
36
+ document.getElementById('emailModal').addEventListener('click', (e) => {
37
+ if (e.target.id === 'emailModal') {
38
+ document.getElementById('emailModal').classList.add('hidden');
39
+ }
40
+ });
41
+ }
42
+
43
+ addTask() {
44
+ const taskInput = document.getElementById('taskInput');
45
+ const emailInput = document.getElementById('emailInput');
46
+ const taskText = taskInput.value.trim();
47
+ const email = emailInput.value.trim();
48
+
49
+ if (!taskText) {
50
+ this.showToast('Please enter a task', 'error');
51
+ return;
52
+ }
53
+
54
+ if (!email || !this.isValidEmail(email)) {
55
+ this.showToast('Please enter a valid email address', 'error');
56
+ return;
57
+ }
58
+
59
+ const task = {
60
+ id: Date.now(),
61
+ text: taskText,
62
+ email: email,
63
+ completed: false,
64
+ hasReminder: false,
65
+ reminderStopped: false,
66
+ createdAt: new Date().toISOString(),
67
+ completedAt: null
68
+ };
69
+
70
+ this.tasks.push(task);
71
+ this.saveTasks();
72
+ this.renderTasks();
73
+ this.updateStats();
74
+
75
+ // Clear inputs
76
+ taskInput.value = '';
77
+ emailInput.value = '';
78
+
79
+ this.showToast('Task added successfully!', 'success');
80
+ }
81
+
82
+ toggleTask(id) {
83
+ const task = this.tasks.find(t => t.id === id);
84
+ if (task) {
85
+ task.completed = !task.completed;
86
+ task.completedAt = task.completed ? new Date().toISOString() : null;
87
+ this.saveTasks();
88
+ this.renderTasks();
89
+ this.updateStats();
90
+
91
+ if (task.completed) {
92
+ this.showToast('Task completed! Great job! πŸŽ‰', 'success');
93
+ }
94
+ }
95
+ }
96
+
97
+ toggleReminder(id) {
98
+ const task = this.tasks.find(t => t.id === id);
99
+ if (task) {
100
+ if (task.hasReminder && !task.reminderStopped) {
101
+ // Stop reminder
102
+ task.hasReminder = false;
103
+ task.reminderStopped = true;
104
+ this.showToast('Reminder stopped', 'info');
105
+ } else if (!task.completed) {
106
+ // Start reminder
107
+ task.hasReminder = true;
108
+ task.reminderStopped = false;
109
+ this.showToast('Reminder started! You\'ll receive email notifications', 'success');
110
+ this.showEmailPreview(task);
111
+ }
112
+ this.saveTasks();
113
+ this.renderTasks();
114
+ this.updateStats();
115
+ }
116
+ }
117
+
118
+ deleteTask(id) {
119
+ if (confirm('Are you sure you want to delete this task?')) {
120
+ this.tasks = this.tasks.filter(t => t.id !== id);
121
+ this.saveTasks();
122
+ this.renderTasks();
123
+ this.updateStats();
124
+ this.showToast('Task deleted', 'info');
125
+ }
126
+ }
127
+
128
+ setFilter(filter) {
129
+ this.currentFilter = filter;
130
+
131
+ // Update button styles
132
+ document.querySelectorAll('.filter-btn').forEach(btn => {
133
+ if (btn.dataset.filter === filter) {
134
+ btn.className = 'filter-btn px-4 py-2 bg-primary-500 text-white rounded-lg transition-colors';
135
+ } else {
136
+ btn.className = 'filter-btn px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors';
137
+ }
138
+ });
139
+
140
+ this.renderTasks();
141
+ }
142
+
143
+ getFilteredTasks() {
144
+ switch (this.currentFilter) {
145
+ case 'pending':
146
+ return this.tasks.filter(t => !t.completed);
147
+ case 'completed':
148
+ return this.tasks.filter(t => t.completed);
149
+ case 'reminders':
150
+ return this.tasks.filter(t => t.hasReminder && !t.reminderStopped && !t.completed);
151
+ default:
152
+ return this.tasks;
153
+ }
154
+ }
155
+
156
+ renderTasks() {
157
+ const container = document.getElementById('tasksContainer');
158
+ const emptyState = document.getElementById('emptyState');
159
+ const filteredTasks = this.getFilteredTasks();
160
+
161
+ if (filteredTasks.length === 0) {
162
+ container.innerHTML = '';
163
+ emptyState.style.display = 'block';
164
+ return;
165
+ }
166
+
167
+ emptyState.style.display = 'none';
168
+ container.innerHTML = filteredTasks.map(task => `
169
+ <task-card
170
+ id="${task.id}"
171
+ text="${this.escapeHtml(task.text)}"
172
+ email="${this.escapeHtml(task.email)}"
173
+ completed="${task.completed}"
174
+ has-reminder="${task.hasReminder}"
175
+ reminder-stopped="${task.reminderStopped}"
176
+ created-at="${task.createdAt}"
177
+ ></task-card>
178
+ `).join('');
179
+
180
+ // Re-initialize feather icons for new elements
181
+ feather.replace();
182
+ }
183
+
184
+ updateStats() {
185
+ const total = this.tasks.length;
186
+ const completed = this.tasks.filter(t => t.completed).length;
187
+ const pending = total - completed;
188
+ const reminders = this.tasks.filter(t => t.hasReminder && !t.reminderStopped && !t.completed).length;
189
+
190
+ document.getElementById('totalTasks').textContent = total;
191
+ document.getElementById('completedTasks').textContent = completed;
192
+ document.getElementById('pendingTasks').textContent = pending;
193
+ document.getElementById('activeReminders').textContent = reminders;
194
+ }
195
+
196
+ showEmailPreview(task) {
197
+ document.getElementById('emailTo').textContent = task.email;
198
+ document.getElementById('emailSubject').textContent = task.text;
199
+ document.getElementById('emailTask').textContent = task.text;
200
+ document.getElementById('emailModal').classList.remove('hidden');
201
+ }
202
+
203
+ startReminderScheduler() {
204
+ // Simulate sending reminders every 30 seconds for demo
205
+ setInterval(() => {
206
+ this.tasks.forEach(task => {
207
+ if (task.hasReminder && !task.reminderStopped && !task.completed) {
208
+ // In a real app, this would send an email
209
+ console.log(`Sending reminder for task: ${task.text} to ${task.email}`);
210
+ }
211
+ });
212
+ }, 30000);
213
+ }
214
+
215
+ showToast(message, type = 'info') {
216
+ const toast = document.createElement('div');
217
+ const bgColor = type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500';
218
+
219
+ toast.className = `fixed bottom-4 right-4 ${bgColor} text-white px-6 py-3 rounded-lg shadow-lg z-50 flex items-center gap-2 task-card-enter`;
220
+
221
+ const icon = type === 'success' ? 'check-circle' : type === 'error' ? 'alert-circle' : 'info';
222
+ toast.innerHTML = `
223
+ <i data-feather="${icon}" class="w-5 h-5"></i>
224
+ <span>${message}</span>
225
+ `;
226
+
227
+ document.body.appendChild(toast);
228
+ feather.replace();
229
+
230
+ setTimeout(() => {
231
+ toast.style.opacity = '0';
232
+ setTimeout(() => toast.remove(), 300);
233
+ }, 3000);
234
+ }
235
+
236
+ isValidEmail(email) {
237
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
238
+ }
239
+
240
+ escapeHtml(text) {
241
+ const div = document.createElement('div');
242
+ div.textContent = text;
243
+ return div.innerHTML;
244
+ }
245
+
246
+ saveTasks() {
247
+ localStorage.setItem('tasks', JSON.stringify(this.tasks));
248
+ }
249
+
250
+ loadTasks() {
251
+ const saved = localStorage.getItem('tasks');
252
+ return saved ? JSON.parse(saved) : [];
253
+ }
254
+ }
255
+
256
+ // Custom event delegation for dynamic task cards
257
+ document.addEventListener('click', (e) => {
258
+ if (e.target.closest('[data-action="toggle"]')) {
259
+ const card = e.target.closest('task-card');
260
+ taskManager.toggleTask(parseInt(card.id));
261
+ } else if (e.target.closest('[data-action="reminder"]')) {
262
+ const card = e.target.closest('task-card');
263
+ taskManager.toggleReminder(parseInt(card.id));
264
+ } else if (e.target.closest('[data-action="delete"]')) {
265
+ const card = e.target.closest('task-card');
266
+ taskManager.deleteTask(parseInt(card.id));
267
+ }
268
+ });
269
+
270
+ // Initialize the app
271
+ const taskManager = new TaskManager();
style.css CHANGED
@@ -1,28 +1,142 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Custom animations */
2
+ @keyframes slideIn {
3
+ from {
4
+ opacity: 0;
5
+ transform: translateY(-10px);
6
+ }
7
+ to {
8
+ opacity: 1;
9
+ transform: translateY(0);
10
+ }
11
  }
12
 
13
+ @keyframes pulse {
14
+ 0%, 100% {
15
+ opacity: 1;
16
+ }
17
+ 50% {
18
+ opacity: 0.5;
19
+ }
20
  }
21
 
22
+ .task-card-enter {
23
+ animation: slideIn 0.3s ease-out;
 
 
 
24
  }
25
 
26
+ .reminder-pulse {
27
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
 
 
 
 
28
  }
29
 
30
+ /* Smooth transitions */
31
+ * {
32
+ transition: all 0.2s ease;
33
  }
34
+
35
+ /* Custom scrollbar */
36
+ ::-webkit-scrollbar {
37
+ width: 8px;
38
+ height: 8px;
39
+ }
40
+
41
+ ::-webkit-scrollbar-track {
42
+ background: #f1f1f1;
43
+ border-radius: 10px;
44
+ }
45
+
46
+ ::-webkit-scrollbar-thumb {
47
+ background: #888;
48
+ border-radius: 10px;
49
+ }
50
+
51
+ ::-webkit-scrollbar-thumb:hover {
52
+ background: #555;
53
+ }
54
+
55
+ /* Focus styles */
56
+ :focus-visible {
57
+ outline: 2px solid #3b82f6;
58
+ outline-offset: 2px;
59
+ }
60
+
61
+ /* Checkbox custom styles */
62
+ .custom-checkbox {
63
+ appearance: none;
64
+ width: 24px;
65
+ height: 24px;
66
+ border: 2px solid #d1d5db;
67
+ border-radius: 6px;
68
+ cursor: pointer;
69
+ position: relative;
70
+ transition: all 0.2s ease;
71
+ }
72
+
73
+ .custom-checkbox:checked {
74
+ background-color: #10b981;
75
+ border-color: #10b981;
76
+ }
77
+
78
+ .custom-checkbox:checked::after {
79
+ content: '';
80
+ position: absolute;
81
+ left: 7px;
82
+ top: 3px;
83
+ width: 6px;
84
+ height: 12px;
85
+ border: solid white;
86
+ border-width: 0 2px 2px 0;
87
+ transform: rotate(45deg);
88
+ }
89
+
90
+ /* Email button hover effects */
91
+ .email-button {
92
+ position: relative;
93
+ overflow: hidden;
94
+ }
95
+
96
+ .email-button::before {
97
+ content: '';
98
+ position: absolute;
99
+ top: 50%;
100
+ left: 50%;
101
+ width: 0;
102
+ height: 0;
103
+ border-radius: 50%;
104
+ background: rgba(255, 255, 255, 0.5);
105
+ transform: translate(-50%, -50%);
106
+ transition: width 0.6s, height 0.6s;
107
+ }
108
+
109
+ .email-button:hover::before {
110
+ width: 300px;
111
+ height: 300px;
112
+ }
113
+
114
+ /* Glass morphism effect */
115
+ .glass-effect {
116
+ background: rgba(255, 255, 255, 0.95);
117
+ backdrop-filter: blur(10px);
118
+ border: 1px solid rgba(255, 255, 255, 0.2);
119
+ }
120
+
121
+ /* Mobile responsive adjustments */
122
+ @media (max-width: 768px) {
123
+ .container {
124
+ padding-left: 1rem;
125
+ padding-right: 1rem;
126
+ }
127
+ }
128
+
129
+ /* Loading spinner */
130
+ .spinner {
131
+ border: 3px solid #f3f3f3;
132
+ border-top: 3px solid #3b82f6;
133
+ border-radius: 50%;
134
+ width: 20px;
135
+ height: 20px;
136
+ animation: spin 1s linear infinite;
137
+ }
138
+
139
+ @keyframes spin {
140
+ 0% { transform: rotate(0deg); }
141
+ 100% { transform: rotate(360deg); }
142
+ }