SolarumAsteridion commited on
Commit
b5b94fb
·
verified ·
1 Parent(s): 7dfe2c3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +459 -227
index.html CHANGED
@@ -3,21 +3,65 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Neon Exam Countdown</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
8
  <style>
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
 
 
 
 
 
 
 
 
 
10
  body {
11
  font-family: 'Inter', sans-serif;
12
- background: radial-gradient(circle at 50% 0%, rgba(10, 10, 15, 0.8) 0%, #0A0A0B 40%, #141419 100%);
13
  min-height: 100vh;
14
- color: #fff;
15
  overflow-x: hidden;
16
- position: relative;
17
- padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
 
20
- /* --- PARTICLE EFFECTS --- */
21
  .particles {
22
  position: fixed;
23
  top: 0;
@@ -26,167 +70,264 @@
26
  height: 100%;
27
  overflow: hidden;
28
  z-index: 1;
29
- pointer-events: none; /* Ensures particles don't interfere with clicks */
30
  }
 
31
  .particle {
32
  position: absolute;
33
- width: 4px;
34
- height: 4px;
35
- background: radial-gradient(circle, rgba(0, 212, 255, 0.4) 0%, transparent 70%);
36
  border-radius: 50%;
37
- animation: float 25s infinite linear;
38
- box-shadow: 0 0 5px rgba(0, 212, 255, 0.3);
39
- /* --x is set by JS */
40
  }
41
- @keyframes float {
42
- from { transform: translate(var(--x), 100vh); opacity: 0; }
43
- 5% { opacity: 0.6; }
44
- 95% { opacity: 0.6; }
45
- to { transform: translate(var(--x), -100vh); opacity: 0; }
46
  }
47
 
48
- /* --- MAIN LAYOUT --- */
49
- .container { max-width: 1400px; margin: 0 auto; padding: 1rem 0; position: relative; z-index: 2; }
 
 
 
 
 
 
 
50
  .header {
51
  display: flex;
52
  justify-content: space-between;
53
  align-items: center;
54
- margin-bottom: 3rem;
55
- padding: 0 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
 
57
  h1 {
58
- font-size: 3rem;
59
- font-weight: 700;
60
- letter-spacing: 3px;
61
- background: linear-gradient(135deg, #00D4FF 0%, #FF0080 50%, #00FF88 100%);
 
62
  -webkit-background-clip: text;
63
  -webkit-text-fill-color: transparent;
64
- background-clip: text;
65
- text-shadow: 0 0 15px rgba(0, 212, 255, 0.7), 0 0 30px rgba(255, 0, 128, 0.3);
66
  }
67
 
68
- /* --- SETTINGS BUTTON (Neon Glow Focus) --- */
69
  .settings-btn {
70
  width: 50px;
71
  height: 50px;
72
- background: rgba(255, 255, 255, 0.08);
73
- backdrop-filter: blur(15px);
74
- border: 1px solid rgba(0, 212, 255, 0.3);
75
- border-radius: 15px;
 
76
  display: flex;
77
  align-items: center;
78
  justify-content: center;
79
  cursor: pointer;
80
- transition: all 0.3s ease;
81
- box-shadow: 0 0 10px rgba(0, 212, 255, 0.4);
 
82
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  .settings-btn:hover {
84
- background: rgba(0, 212, 255, 0.15);
85
- border-color: #00FF88;
86
- box-shadow: 0 0 30px rgba(0, 255, 136, 0.8), 0 0 10px rgba(0, 212, 255, 0.6);
87
- transform: scale(1.05);
 
 
 
88
  }
 
89
  .settings-btn svg {
90
- width: 26px;
91
- height: 26px;
92
- fill: #00D4FF;
93
- filter: drop-shadow(0 0 5px rgba(0, 212, 255, 0.9));
 
 
 
 
94
  }
95
 
96
- /* --- EXAM GRID --- */
97
  .exams-grid {
98
  display: grid;
99
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
100
- gap: 2rem;
101
- padding: 0 1rem;
102
  }
 
 
103
  .exam-card {
104
- background: rgba(10, 10, 15, 0.7); /* Darker base */
105
- backdrop-filter: blur(10px);
106
- border: 1px solid rgba(0, 255, 136, 0.2); /* Subtle green-cyan border */
107
- border-radius: 25px;
 
108
  padding: 2.5rem;
109
  text-align: center;
110
- transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
111
  position: relative;
112
  overflow: hidden;
113
- box-shadow:
114
- 0 4px 15px rgba(0, 0, 0, 0.5), /* Outer shadow for depth */
115
- inset 0 0 15px rgba(0, 255, 136, 0.1); /* Subtle inner glow */
116
  }
117
 
118
- /* Card Hover Effect: More dramatic lift and glow */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  .exam-card:hover {
120
- transform: translateY(-8px) scale(1.01);
 
 
121
  box-shadow:
122
- 0 15px 40px rgba(0, 212, 255, 0.5),
123
- 0 0 50px rgba(0, 255, 136, 0.3),
124
- inset 0 0 20px rgba(255, 0, 128, 0.2);
125
- border-color: #00FF88;
 
 
 
 
 
 
126
  }
127
 
128
  .exam-name {
129
- font-size: 1.6rem;
130
- font-weight: 600;
131
- margin-bottom: 1.5rem;
132
- color: #00D4FF;
133
- text-transform: uppercase;
134
- letter-spacing: 1px;
135
  }
136
 
137
- /* Countdown Numbers: Brightest element */
138
  .countdown {
139
- font-size: 4.5rem;
140
- font-weight: 700;
141
- text-shadow:
142
- 0 0 25px rgba(255, 0, 128, 1),
143
- 0 0 50px rgba(255, 0, 128, 0.7);
144
- margin-bottom: 0.75rem;
145
- line-height: 1;
146
- color: #fff;
147
  }
 
148
  .countdown-label {
149
- font-size: 1.1rem;
150
- color: rgba(255, 255, 255, 0.8);
151
  text-transform: uppercase;
152
- letter-spacing: 1px;
 
153
  }
 
154
  .exam-date {
155
- font-size: 0.95rem;
156
- color: rgba(0, 212, 255, 0.6);
157
- margin-top: 1.5rem;
158
- border-top: 1px dashed rgba(0, 212, 255, 0.1);
159
- padding-top: 1rem;
160
  }
161
 
162
- /* Delete Button (Danger Neon) */
163
  .delete-btn {
164
  position: absolute;
165
- top: 1.2rem;
166
- right: 1.2rem;
167
- width: 35px;
168
- height: 35px;
169
- background: rgba(255, 0, 128, 0.25);
170
- border: 1px solid rgba(255, 0, 128, 0.6);
171
  border-radius: 10px;
172
  display: flex;
173
  align-items: center;
174
  justify-content: center;
175
  cursor: pointer;
176
  transition: all 0.3s ease;
177
- opacity: 0;
178
- z-index: 10;
179
- box-shadow: 0 0 10px rgba(255, 0, 128, 0.5);
180
  }
181
- .exam-card:hover .delete-btn { opacity: 1; }
 
 
 
 
182
  .delete-btn:hover {
183
- background: rgba(255, 0, 128, 0.6);
184
- box-shadow: 0 0 25px rgba(255, 0, 128, 1);
185
- transform: scale(1.15);
 
 
 
 
 
 
 
 
186
  }
187
- .delete-btn svg { width: 18px; height: 18px; fill: #fff; pointer-events: none; }
188
 
189
- /* --- MODAL STYLES (Holographic Look) --- */
 
 
 
 
190
  .modal {
191
  display: none;
192
  position: fixed;
@@ -194,164 +335,245 @@
194
  left: 0;
195
  width: 100%;
196
  height: 100%;
197
- background: rgba(0, 0, 0, 0.75);
 
 
198
  z-index: 1000;
199
  align-items: center;
200
  justify-content: center;
201
  padding: 2rem;
202
- animation: fadeIn 0.3s ease-out;
 
203
  }
204
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
205
 
206
- .modal.active {
207
  display: flex;
 
208
  }
209
 
210
  .modal-content {
211
- background: rgba(15, 15, 20, 0.97);
212
- backdrop-filter: blur(30px);
213
- border: 2px solid;
214
- border-image: linear-gradient(45deg, #00D4FF, #FF0080, #00FF88) 1;
215
- border-radius: 30px;
216
- padding: 2.5rem;
217
  width: 100%;
218
  max-width: 450px;
219
- box-shadow: 0 10px 60px rgba(0, 0, 0, 0.8), 0 0 30px rgba(0, 212, 255, 0.2);
220
- animation: popIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  }
222
- @keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
223
 
224
  .modal-header {
225
  display: flex;
226
  justify-content: space-between;
227
  align-items: center;
228
- margin-bottom: 2rem;
229
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
230
- padding-bottom: 1rem;
231
  }
 
232
  .modal-title {
233
- font-size: 1.75rem;
234
- font-weight: 600;
235
- color: #00D4FF;
236
- text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
237
  }
 
238
  .close-btn {
239
- width: 35px;
240
- height: 35px;
241
- background: rgba(255, 255, 255, 0.05);
242
- border: 1px solid rgba(255, 0, 128, 0.5);
243
- border-radius: 50%;
244
  cursor: pointer;
245
- transition: all 0.3s ease;
246
  display: flex;
247
  align-items: center;
248
  justify-content: center;
 
249
  }
 
250
  .close-btn:hover {
251
- transform: rotate(180deg);
252
- background: rgba(255, 0, 128, 0.3);
253
- box-shadow: 0 0 15px rgba(255, 0, 128, 0.8);
254
  }
255
- .close-btn svg { fill: #FF0080; pointer-events: none;}
256
 
257
- /* --- EMPTY STATE --- */
258
- .empty-state {
259
- text-align: center;
260
- padding: 6rem 2rem;
261
- border: 2px dashed rgba(0, 212, 255, 0.2);
262
- border-radius: 20px;
263
- margin: 2rem;
264
  }
265
- .empty-state p {
266
- font-size: 1.4rem;
267
- color: rgba(0, 212, 255, 0.6);
268
  margin-bottom: 2rem;
269
- text-shadow: 0 0 5px rgba(0, 212, 255, 0.3);
270
  }
271
- #openModalForEmpty {
272
- padding: 0.9rem 2rem;
273
- font-size: 1rem;
 
 
 
 
 
274
  }
275
 
276
- /* --- FORM STYLING --- */
277
- .form-group { margin-bottom: 1.25rem; }
278
- .form-group label { display: block; margin-bottom: 0.5rem; font-size: 0.9rem; font-weight: 400; color: #ddd; }
279
-
280
- input[type="text"], input[type="date"] {
281
- width: 100%;
282
- padding: 0.85rem;
283
- border-radius: 8px;
284
- border: 1px solid rgba(255, 255, 255, 0.15);
285
- background: rgba(0, 0, 0, 0.6);
286
- color: #fff;
287
- font-family: inherit;
288
  font-size: 1rem;
289
- transition: border-color 0.3s, box-shadow 0.3s;
 
290
  }
291
- input[type="text"]:focus, input[type="date"]:focus {
 
292
  outline: none;
293
- border-color: #FF0080;
294
- box-shadow: 0 0 15px rgba(255, 0, 128, 0.6);
 
 
 
 
 
 
 
295
  }
296
 
 
297
  .add-btn {
298
  width: 100%;
299
- padding: 1rem;
300
- background: linear-gradient(135deg, #00D4FF 0%, #FF0080 100%);
301
  border: none;
302
  border-radius: 12px;
303
- color: #fff;
304
- font-size: 1.1rem;
305
- font-weight: 700;
306
  cursor: pointer;
307
- transition: all 0.3s ease;
308
- box-shadow: 0 5px 20px rgba(0, 212, 255, 0.4);
 
309
  letter-spacing: 0.5px;
310
  }
 
311
  .add-btn:hover {
312
- transform: translateY(-3px);
313
- box-shadow: 0 8px 30px rgba(255, 0, 128, 0.8), 0 0 15px #00D4FF;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  }
315
 
 
316
  @media (max-width: 768px) {
317
  h1 { font-size: 2rem; }
318
- .header { flex-direction: column; gap: 1rem; text-align: center; }
 
 
319
  }
320
  </style>
321
  </head>
322
  <body>
323
  <div class="particles" id="particles"></div>
 
324
  <div class="container">
325
  <div class="header">
326
- <h1>NEON CHRONOS</h1>
327
  <div class="settings-btn" id="openModalBtn">
328
- <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/></svg>
 
 
329
  </div>
330
  </div>
 
331
  <div class="exams-grid" id="examsGrid"></div>
 
332
  <div class="empty-state" id="emptyState" style="display: none;">
333
- <p>SYSTEM OFFLINE: Input target dates below.</p>
334
- <button id="openModalForEmpty" class="add-btn" style="max-width: 300px; margin: 1rem auto;">INITIATE NEW TARGET</button>
335
  </div>
336
  </div>
 
337
  <div class="modal" id="modal">
338
  <div class="modal-content">
339
  <div class="modal-header">
340
- <h2 class="modal-title">Add New Target</h2>
341
  <button class="close-btn" id="closeModalBtn">
342
- <svg width="24" height="24" viewBox="0 0 24 24" fill="#fff"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
 
 
343
  </button>
344
  </div>
 
345
  <form id="addExamForm">
346
  <div class="form-group">
347
- <label for="examName">Designation</label>
348
- <input type="text" id="examName" required placeholder="e.g., Quantum Dynamics Final">
349
  </div>
350
  <div class="form-group">
351
- <label for="examDate">Execute Date</label>
352
  <input type="date" id="examDate" required>
353
  </div>
354
- <button type="submit" class="add-btn">Activate Countdown</button>
355
  </form>
356
  </div>
357
  </div>
@@ -381,7 +603,6 @@
381
  const elements = {
382
  modal: document.getElementById('modal'),
383
  openModalBtn: document.getElementById('openModalBtn'),
384
- openModalForEmpty: document.getElementById('openModalForEmpty'),
385
  closeModalBtn: document.getElementById('closeModalBtn'),
386
  addExamForm: document.getElementById('addExamForm'),
387
  examNameInput: document.getElementById('examName'),
@@ -393,11 +614,14 @@
393
 
394
  // --- Core Functions ---
395
  const openModal = () => elements.modal.classList.add('active');
396
- const closeModal = () => elements.modal.classList.remove('active');
 
 
 
397
 
398
  const addExam = (event) => {
399
  event.preventDefault();
400
- // *** FIX: Save the new date as a number (timestamp) for future consistency ***
401
  const newExam = {
402
  name: elements.examNameInput.value,
403
  date: new Date(elements.examDateInput.value).getTime()
@@ -407,89 +631,94 @@
407
  };
408
 
409
  const deleteExam = (id) => remove(ref(database, `exams/${id}`));
410
-
411
  // --- Rendering Logic ---
412
-
413
- // *** FIX: This function now handles both old string dates and new number dates ***
414
- const calculateDaysUntil = (examDate) => {
415
  const now = new Date();
416
  now.setHours(0, 0, 0, 0); // Set to midnight for accurate day comparison
417
 
418
- // The new Date() constructor can handle both numbers (timestamps) and "YYYY-MM-DD" strings.
419
  const targetDate = new Date(examDate);
420
  targetDate.setHours(0, 0, 0, 0);
421
 
422
  const diff = targetDate.getTime() - now.getTime();
423
- return Math.ceil(diff / (1000 * 60 * 60 * 24));
 
 
 
 
 
424
  };
425
 
426
  const renderExams = (exams) => {
427
  const hasExams = exams && exams.length > 0;
428
- elements.emptyState.style.display = hasExams ? 'none' : 'block';
429
  elements.examsGrid.style.display = hasExams ? 'grid' : 'none';
 
430
 
431
  if (!hasExams) return;
432
 
433
- exams.sort((a, b) => a.date - b.date);
 
434
 
435
  elements.examsGrid.innerHTML = exams.map(exam => {
436
- const daysUntil = calculateDaysUntil(exam.date);
437
- const color = daysUntil <= 3 ? '#FF0080' : (daysUntil <= 7 ? '#00FF88' : '#00D4FF');
438
-
439
- // Create a clean date object that works for both old and new data
440
- const dateObject = new Date(exam.date);
441
-
442
- // Handle completed exams
443
- let countdownContent;
444
- if (daysUntil < 0) {
445
- countdownContent = `
446
- <div class="countdown" style="font-size: 1.8rem; color: #aaa;">COMPLETE</div>
447
- <div class="countdown-label">on</div>
448
- `;
449
- } else {
450
- countdownContent = `
451
- <div class="countdown" style="color: ${color};">${daysUntil}</div>
452
- <div class="countdown-label">days remaining</div>
453
- `;
454
- }
455
-
456
  return `
457
  <div class="exam-card">
458
- <div class="delete-btn" data-delete-id="${exam.id}">
459
- <svg viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
460
- </div>
461
- <h3 class="exam-name">${exam.name || 'Unnamed Target'}</h3>
462
- ${countdownContent}
463
- <div class="exam-date">${dateObject.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</div>
 
 
 
 
 
464
  </div>`;
465
  }).join('');
466
  };
467
 
468
- // --- Particle Effect ---
469
- const createParticles = () => {
 
470
  let particleHtml = '';
471
- for (let i = 0; i < 50; i++) {
 
 
472
  const style = `
473
- --x: ${Math.random() * 100}vw;
474
- animation-delay: ${Math.random() * 20}s;
475
- animation-duration: ${15 + Math.random() * 10}s;
 
476
  `;
477
  particleHtml += `<div class="particle" style="${style}"></div>`;
478
  }
479
  elements.particlesContainer.innerHTML = particleHtml;
480
- };
481
-
 
 
 
 
 
 
 
 
 
 
482
  // --- App Initialization & Event Listeners ---
483
  createParticles();
484
  elements.examDateInput.min = new Date().toISOString().split('T')[0];
485
 
486
  // Modal Listeners
487
  elements.openModalBtn.addEventListener('click', openModal);
488
- elements.openModalForEmpty.addEventListener('click', openModal);
489
  elements.closeModalBtn.addEventListener('click', closeModal);
490
  elements.addExamForm.addEventListener('submit', addExam);
491
  window.addEventListener('click', e => { if (e.target === elements.modal) closeModal(); });
492
-
493
  // Delete button listener (using event delegation)
494
  elements.examsGrid.addEventListener('click', (event) => {
495
  const deleteBtn = event.target.closest('.delete-btn');
@@ -503,6 +732,9 @@
503
  const data = snapshot.val();
504
  const examList = data ? Object.entries(data).map(([id, value]) => ({ id, ...value })) : [];
505
  renderExams(examList);
 
 
 
506
  });
507
 
508
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Exam Countdown</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800&family=Poppins:wght@200;300;400;500;600;700;800&display=swap" rel="stylesheet">
8
  <style>
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
+
11
+ :root {
12
+ --glass-bg: rgba(255, 255, 255, 0.03);
13
+ --glass-border: rgba(255, 255, 255, 0.08);
14
+ --text-primary: rgba(255, 255, 255, 0.95);
15
+ --text-secondary: rgba(255, 255, 255, 0.6);
16
+ --text-muted: rgba(255, 255, 255, 0.4);
17
+ --accent: #ffffff;
18
+ }
19
+
20
  body {
21
  font-family: 'Inter', sans-serif;
22
+ background: #000000;
23
  min-height: 100vh;
24
+ color: var(--text-primary);
25
  overflow-x: hidden;
26
+ position: relative;
27
+ }
28
+
29
+ /* Subtle animated gradient orbs */
30
+ body::before,
31
+ body::after {
32
+ content: '';
33
+ position: fixed;
34
+ border-radius: 50%;
35
+ filter: blur(150px);
36
+ opacity: 0.15;
37
+ animation: float 20s infinite ease-in-out;
38
+ pointer-events: none;
39
+ }
40
+
41
+ body::before {
42
+ width: 600px;
43
+ height: 600px;
44
+ background: radial-gradient(circle, #ffffff 0%, transparent 70%);
45
+ top: -200px;
46
+ right: -200px;
47
+ }
48
+
49
+ body::after {
50
+ width: 800px;
51
+ height: 800px;
52
+ background: radial-gradient(circle, #ffffff 0%, transparent 70%);
53
+ bottom: -300px;
54
+ left: -300px;
55
+ animation-delay: 10s;
56
+ }
57
+
58
+ @keyframes float {
59
+ 0%, 100% { transform: translate(0, 0) scale(1); }
60
+ 33% { transform: translate(30px, -30px) scale(1.1); }
61
+ 66% { transform: translate(-20px, 20px) scale(0.9); }
62
  }
63
 
64
+ /* Subtle particles */
65
  .particles {
66
  position: fixed;
67
  top: 0;
 
70
  height: 100%;
71
  overflow: hidden;
72
  z-index: 1;
73
+ pointer-events: none;
74
  }
75
+
76
  .particle {
77
  position: absolute;
78
+ width: 1px;
79
+ height: 1px;
80
+ background: white;
81
  border-radius: 50%;
82
+ opacity: 0;
83
+ animation: twinkle 5s infinite;
 
84
  }
85
+
86
+ @keyframes twinkle {
87
+ 0%, 100% { opacity: 0; transform: scale(0.5); }
88
+ 50% { opacity: 0.8; transform: scale(1); }
 
89
  }
90
 
91
+ .container {
92
+ max-width: 1200px;
93
+ margin: 0 auto;
94
+ padding: 3rem 2rem;
95
+ position: relative;
96
+ z-index: 2;
97
+ }
98
+
99
+ /* Clean header */
100
  .header {
101
  display: flex;
102
  justify-content: space-between;
103
  align-items: center;
104
+ margin-bottom: 5rem;
105
+ padding-bottom: 2rem;
106
+ position: relative;
107
+ }
108
+
109
+ .header::after {
110
+ content: '';
111
+ position: absolute;
112
+ bottom: 0;
113
+ left: 0;
114
+ right: 0;
115
+ height: 1px;
116
+ background: linear-gradient(90deg,
117
+ transparent,
118
+ rgba(255, 255, 255, 0.1) 20%,
119
+ rgba(255, 255, 255, 0.1) 80%,
120
+ transparent);
121
  }
122
+
123
  h1 {
124
+ font-family: 'Poppins', sans-serif;
125
+ font-size: 2.5rem;
126
+ font-weight: 200;
127
+ letter-spacing: -1px;
128
+ background: linear-gradient(135deg, #ffffff 0%, rgba(255, 255, 255, 0.6) 100%);
129
  -webkit-background-clip: text;
130
  -webkit-text-fill-color: transparent;
131
+ background-clip: text;
 
132
  }
133
 
134
+ /* Glass settings button */
135
  .settings-btn {
136
  width: 50px;
137
  height: 50px;
138
+ background: var(--glass-bg);
139
+ backdrop-filter: blur(20px) saturate(180%);
140
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
141
+ border: 1px solid var(--glass-border);
142
+ border-radius: 16px;
143
  display: flex;
144
  align-items: center;
145
  justify-content: center;
146
  cursor: pointer;
147
+ transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1);
148
+ position: relative;
149
+ overflow: hidden;
150
  }
151
+
152
+ .settings-btn::before {
153
+ content: '';
154
+ position: absolute;
155
+ top: 0;
156
+ left: 0;
157
+ right: 0;
158
+ bottom: 0;
159
+ background: radial-gradient(circle at center,
160
+ rgba(255, 255, 255, 0.1) 0%,
161
+ transparent 70%);
162
+ opacity: 0;
163
+ transition: opacity 0.3s ease;
164
+ }
165
+
166
  .settings-btn:hover {
167
+ transform: scale(1.05);
168
+ background: rgba(255, 255, 255, 0.06);
169
+ border-color: rgba(255, 255, 255, 0.2);
170
+ }
171
+
172
+ .settings-btn:hover::before {
173
+ opacity: 1;
174
  }
175
+
176
  .settings-btn svg {
177
+ width: 24px;
178
+ height: 24px;
179
+ fill: var(--text-primary);
180
+ transition: transform 0.4s ease;
181
+ }
182
+
183
+ .settings-btn:hover svg {
184
+ transform: rotate(90deg);
185
  }
186
 
187
+ /* Glass exam grid */
188
  .exams-grid {
189
  display: grid;
190
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
191
+ gap: 2rem;
 
192
  }
193
+
194
+ /* Glassmorphic exam cards */
195
  .exam-card {
196
+ background: var(--glass-bg);
197
+ backdrop-filter: blur(40px) saturate(180%);
198
+ -webkit-backdrop-filter: blur(40px) saturate(180%);
199
+ border: 1px solid var(--glass-border);
200
+ border-radius: 24px;
201
  padding: 2.5rem;
202
  text-align: center;
203
+ transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1);
204
  position: relative;
205
  overflow: hidden;
 
 
 
206
  }
207
 
208
+ .exam-card::before {
209
+ content: '';
210
+ position: absolute;
211
+ top: 0;
212
+ left: 0;
213
+ right: 0;
214
+ height: 1px;
215
+ background: linear-gradient(90deg,
216
+ transparent,
217
+ rgba(255, 255, 255, 0.2) 50%,
218
+ transparent);
219
+ opacity: 0;
220
+ transition: opacity 0.3s ease;
221
+ }
222
+
223
+ .exam-card::after {
224
+ content: '';
225
+ position: absolute;
226
+ inset: 0;
227
+ background: radial-gradient(
228
+ 600px circle at var(--mouse-x) var(--mouse-y),
229
+ rgba(255, 255, 255, 0.06),
230
+ transparent 40%
231
+ );
232
+ opacity: 0;
233
+ transition: opacity 0.3s ease;
234
+ pointer-events: none;
235
+ }
236
+
237
  .exam-card:hover {
238
+ transform: translateY(-5px);
239
+ background: rgba(255, 255, 255, 0.05);
240
+ border-color: rgba(255, 255, 255, 0.15);
241
  box-shadow:
242
+ 0 20px 40px rgba(0, 0, 0, 0.5),
243
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
244
+ }
245
+
246
+ .exam-card:hover::before {
247
+ opacity: 1;
248
+ }
249
+
250
+ .exam-card:hover::after {
251
+ opacity: 1;
252
  }
253
 
254
  .exam-name {
255
+ font-family: 'Poppins', sans-serif;
256
+ font-size: 1.4rem;
257
+ font-weight: 300;
258
+ margin-bottom: 2rem;
259
+ color: var(--text-primary);
260
+ letter-spacing: -0.5px;
261
  }
262
 
 
263
  .countdown {
264
+ font-size: 3.5rem;
265
+ font-weight: 200;
266
+ margin-bottom: 0.5rem;
267
+ letter-spacing: -2px;
268
+ background: linear-gradient(180deg, #ffffff 0%, rgba(255, 255, 255, 0.7) 100%);
269
+ -webkit-background-clip: text;
270
+ -webkit-text-fill-color: transparent;
271
+ background-clip: text;
272
  }
273
+
274
  .countdown-label {
275
+ font-size: 0.85rem;
276
+ color: var(--text-secondary);
277
  text-transform: uppercase;
278
+ letter-spacing: 2px;
279
+ font-weight: 500;
280
  }
281
+
282
  .exam-date {
283
+ font-size: 0.9rem;
284
+ color: var(--text-muted);
285
+ margin-top: 2rem;
286
+ padding-top: 1.5rem;
287
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
288
  }
289
 
290
+ /* Minimal delete button */
291
  .delete-btn {
292
  position: absolute;
293
+ top: 1.5rem;
294
+ right: 1.5rem;
295
+ width: 32px;
296
+ height: 32px;
297
+ background: rgba(255, 255, 255, 0.03);
298
+ border: 1px solid rgba(255, 255, 255, 0.08);
299
  border-radius: 10px;
300
  display: flex;
301
  align-items: center;
302
  justify-content: center;
303
  cursor: pointer;
304
  transition: all 0.3s ease;
305
+ opacity: 0;
 
 
306
  }
307
+
308
+ .exam-card:hover .delete-btn {
309
+ opacity: 1;
310
+ }
311
+
312
  .delete-btn:hover {
313
+ background: rgba(255, 255, 255, 0.08);
314
+ border-color: rgba(255, 255, 255, 0.2);
315
+ transform: scale(1.1);
316
+ }
317
+
318
+ .delete-btn svg {
319
+ width: 16px;
320
+ height: 16px;
321
+ fill: var(--text-secondary);
322
+ transition: fill 0.3s ease;
323
+ pointer-events: none; /* Important for event delegation */
324
  }
 
325
 
326
+ .delete-btn:hover svg {
327
+ fill: var(--text-primary);
328
+ }
329
+
330
+ /* Glass modal */
331
  .modal {
332
  display: none;
333
  position: fixed;
 
335
  left: 0;
336
  width: 100%;
337
  height: 100%;
338
+ background: rgba(0, 0, 0, 0.7);
339
+ backdrop-filter: blur(10px);
340
+ -webkit-backdrop-filter: blur(10px);
341
  z-index: 1000;
342
  align-items: center;
343
  justify-content: center;
344
  padding: 2rem;
345
+ opacity: 0;
346
+ transition: opacity 0.3s ease;
347
  }
 
348
 
349
+ .modal.active {
350
  display: flex;
351
+ opacity: 1;
352
  }
353
 
354
  .modal-content {
355
+ background: rgba(255, 255, 255, 0.03);
356
+ backdrop-filter: blur(40px) saturate(180%);
357
+ -webkit-backdrop-filter: blur(40px) saturate(180%);
358
+ border: 1px solid rgba(255, 255, 255, 0.1);
359
+ border-radius: 24px;
360
+ padding: 3rem;
361
  width: 100%;
362
  max-width: 450px;
363
+ box-shadow:
364
+ 0 20px 60px rgba(0, 0, 0, 0.5),
365
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
366
+ animation: modalSlide 0.4s cubic-bezier(0.23, 1, 0.320, 1);
367
+ }
368
+
369
+ @keyframes modalSlide {
370
+ from {
371
+ transform: translateY(-20px) scale(0.95);
372
+ opacity: 0;
373
+ }
374
+ to {
375
+ transform: translateY(0) scale(1);
376
+ opacity: 1;
377
+ }
378
  }
 
379
 
380
  .modal-header {
381
  display: flex;
382
  justify-content: space-between;
383
  align-items: center;
384
+ margin-bottom: 2.5rem;
385
+ padding-bottom: 1.5rem;
386
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
387
  }
388
+
389
  .modal-title {
390
+ font-family: 'Poppins', sans-serif;
391
+ font-size: 1.5rem;
392
+ font-weight: 300;
393
+ letter-spacing: -0.5px;
394
  }
395
+
396
  .close-btn {
397
+ width: 36px;
398
+ height: 36px;
399
+ background: rgba(255, 255, 255, 0.03);
400
+ border: 1px solid rgba(255, 255, 255, 0.08);
401
+ border-radius: 10px;
402
  cursor: pointer;
 
403
  display: flex;
404
  align-items: center;
405
  justify-content: center;
406
+ transition: all 0.3s ease;
407
  }
408
+
409
  .close-btn:hover {
410
+ background: rgba(255, 255, 255, 0.08);
411
+ transform: rotate(90deg);
 
412
  }
 
413
 
414
+ .close-btn svg {
415
+ width: 18px;
416
+ height: 18px;
417
+ fill: var(--text-secondary);
 
 
 
418
  }
419
+
420
+ /* Clean form */
421
+ .form-group {
422
  margin-bottom: 2rem;
 
423
  }
424
+
425
+ .form-group label {
426
+ display: block;
427
+ margin-bottom: 0.75rem;
428
+ font-size: 0.9rem;
429
+ color: var(--text-secondary);
430
+ font-weight: 500;
431
+ letter-spacing: 0.5px;
432
  }
433
 
434
+ .form-group input {
435
+ width: 100%;
436
+ padding: 1rem 1.25rem;
437
+ background: rgba(255, 255, 255, 0.03);
438
+ border: 1px solid rgba(255, 255, 255, 0.08);
439
+ border-radius: 12px;
440
+ color: var(--text-primary);
 
 
 
 
 
441
  font-size: 1rem;
442
+ transition: all 0.3s ease;
443
+ font-family: 'Inter', sans-serif;
444
  }
445
+
446
+ .form-group input:focus {
447
  outline: none;
448
+ background: rgba(255, 255, 255, 0.05);
449
+ border-color: rgba(255, 255, 255, 0.2);
450
+ box-shadow:
451
+ 0 0 0 4px rgba(255, 255, 255, 0.02),
452
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
453
+ }
454
+
455
+ .form-group input::placeholder {
456
+ color: var(--text-muted);
457
  }
458
 
459
+ /* Sleek add button */
460
  .add-btn {
461
  width: 100%;
462
+ padding: 1.25rem;
463
+ background: rgba(255, 255, 255, 0.95);
464
  border: none;
465
  border-radius: 12px;
466
+ color: #000;
467
+ font-size: 1rem;
468
+ font-weight: 600;
469
  cursor: pointer;
470
+ transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1);
471
+ position: relative;
472
+ overflow: hidden;
473
  letter-spacing: 0.5px;
474
  }
475
+
476
  .add-btn:hover {
477
+ transform: translateY(-2px);
478
+ background: white;
479
+ box-shadow:
480
+ 0 10px 30px rgba(255, 255, 255, 0.2),
481
+ 0 1px 0 rgba(255, 255, 255, 0.2);
482
+ }
483
+
484
+ .add-btn:active {
485
+ transform: translateY(0);
486
+ }
487
+
488
+ /* Elegant empty state */
489
+ .empty-state {
490
+ text-align: center;
491
+ padding: 6rem 2rem;
492
+ display: flex;
493
+ flex-direction: column;
494
+ align-items: center;
495
+ justify-content: center;
496
+ }
497
+
498
+ .empty-icon {
499
+ width: 80px;
500
+ height: 80px;
501
+ margin-bottom: 2rem;
502
+ background: var(--glass-bg);
503
+ backdrop-filter: blur(20px);
504
+ border: 1px solid var(--glass-border);
505
+ border-radius: 20px;
506
+ display: flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ font-size: 2rem;
510
+ }
511
+
512
+ .empty-state p {
513
+ font-size: 1.1rem;
514
+ color: var(--text-secondary);
515
+ max-width: 400px;
516
+ line-height: 1.6;
517
+ font-weight: 300;
518
+ }
519
+
520
+ /* Input date styling for webkit browsers */
521
+ input[type="date"]::-webkit-calendar-picker-indicator {
522
+ filter: invert(1);
523
+ cursor: pointer;
524
  }
525
 
526
+ /* Responsive */
527
  @media (max-width: 768px) {
528
  h1 { font-size: 2rem; }
529
+ .countdown { font-size: 2.5rem; }
530
+ .exams-grid { grid-template-columns: 1fr; }
531
+ .container { padding: 2rem 1rem; }
532
  }
533
  </style>
534
  </head>
535
  <body>
536
  <div class="particles" id="particles"></div>
537
+
538
  <div class="container">
539
  <div class="header">
540
+ <h1>Exam Countdown</h1>
541
  <div class="settings-btn" id="openModalBtn">
542
+ <svg viewBox="0 0 24 24">
543
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
544
+ </svg>
545
  </div>
546
  </div>
547
+
548
  <div class="exams-grid" id="examsGrid"></div>
549
+
550
  <div class="empty-state" id="emptyState" style="display: none;">
551
+ <div class="empty-icon">📚</div>
552
+ <p>No exams scheduled yet. Add your first exam to start tracking.</p>
553
  </div>
554
  </div>
555
+
556
  <div class="modal" id="modal">
557
  <div class="modal-content">
558
  <div class="modal-header">
559
+ <h2 class="modal-title">Add New Exam</h2>
560
  <button class="close-btn" id="closeModalBtn">
561
+ <svg viewBox="0 0 24 24">
562
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
563
+ </svg>
564
  </button>
565
  </div>
566
+
567
  <form id="addExamForm">
568
  <div class="form-group">
569
+ <label for="examName">Exam Name</label>
570
+ <input type="text" id="examName" required placeholder="e.g., Mathematics Final">
571
  </div>
572
  <div class="form-group">
573
+ <label for="examDate">Exam Date</label>
574
  <input type="date" id="examDate" required>
575
  </div>
576
+ <button type="submit" class="add-btn">Add Exam</button>
577
  </form>
578
  </div>
579
  </div>
 
603
  const elements = {
604
  modal: document.getElementById('modal'),
605
  openModalBtn: document.getElementById('openModalBtn'),
 
606
  closeModalBtn: document.getElementById('closeModalBtn'),
607
  addExamForm: document.getElementById('addExamForm'),
608
  examNameInput: document.getElementById('examName'),
 
614
 
615
  // --- Core Functions ---
616
  const openModal = () => elements.modal.classList.add('active');
617
+ const closeModal = () => {
618
+ elements.modal.classList.remove('active');
619
+ elements.addExamForm.reset();
620
+ };
621
 
622
  const addExam = (event) => {
623
  event.preventDefault();
624
+ // Save the date as a numeric timestamp for consistency and accuracy
625
  const newExam = {
626
  name: elements.examNameInput.value,
627
  date: new Date(elements.examDateInput.value).getTime()
 
631
  };
632
 
633
  const deleteExam = (id) => remove(ref(database, `exams/${id}`));
634
+
635
  // --- Rendering Logic ---
636
+ const calculateCountdown = (examDate) => {
 
 
637
  const now = new Date();
638
  now.setHours(0, 0, 0, 0); // Set to midnight for accurate day comparison
639
 
640
+ // This constructor handles both numbers (timestamps) and "YYYY-MM-DD" strings
641
  const targetDate = new Date(examDate);
642
  targetDate.setHours(0, 0, 0, 0);
643
 
644
  const diff = targetDate.getTime() - now.getTime();
645
+ const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
646
+
647
+ if (days < 0) {
648
+ return { days: '✔', label: 'COMPLETED' };
649
+ }
650
+ return { days, label: days === 1 ? 'DAY LEFT' : 'DAYS LEFT' };
651
  };
652
 
653
  const renderExams = (exams) => {
654
  const hasExams = exams && exams.length > 0;
 
655
  elements.examsGrid.style.display = hasExams ? 'grid' : 'none';
656
+ elements.emptyState.style.display = hasExams ? 'none' : 'flex';
657
 
658
  if (!hasExams) return;
659
 
660
+ // Sort by date so the soonest exam is first
661
+ exams.sort((a, b) => new Date(a.date) - new Date(b.date));
662
 
663
  elements.examsGrid.innerHTML = exams.map(exam => {
664
+ const countdown = calculateCountdown(exam.date);
665
+ const dateObject = new Date(exam.date); // Works for both old and new data
666
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  return `
668
  <div class="exam-card">
669
+ <button class="delete-btn" data-delete-id="${exam.id}">
670
+ <svg viewBox="0 0 24 24">
671
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
672
+ </svg>
673
+ </button>
674
+ <div class="exam-name">${exam.name}</div>
675
+ <div class="countdown">${countdown.days}</div>
676
+ <div class="countdown-label">${countdown.label}</div>
677
+ <div class="exam-date">${dateObject.toLocaleDateString('en-US', {
678
+ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
679
+ })}</div>
680
  </div>`;
681
  }).join('');
682
  };
683
 
684
+ // --- Visual Effects ---
685
+ function createParticles() {
686
+ const particleCount = 50;
687
  let particleHtml = '';
688
+ for (let i = 0; i < particleCount; i++) {
689
+ const delay = Math.random() * 5;
690
+ const duration = 5 + Math.random() * 5;
691
  const style = `
692
+ left: ${Math.random() * 100}%;
693
+ top: ${Math.random() * 100}%;
694
+ animation-delay: ${delay}s;
695
+ animation-duration: ${duration}s;
696
  `;
697
  particleHtml += `<div class="particle" style="${style}"></div>`;
698
  }
699
  elements.particlesContainer.innerHTML = particleHtml;
700
+ }
701
+
702
+ document.addEventListener('mousemove', (e) => {
703
+ document.querySelectorAll('.exam-card').forEach(card => {
704
+ const rect = card.getBoundingClientRect();
705
+ const x = e.clientX - rect.left;
706
+ const y = e.clientY - rect.top;
707
+ card.style.setProperty('--mouse-x', `${x}px`);
708
+ card.style.setProperty('--mouse-y', `${y}px`);
709
+ });
710
+ });
711
+
712
  // --- App Initialization & Event Listeners ---
713
  createParticles();
714
  elements.examDateInput.min = new Date().toISOString().split('T')[0];
715
 
716
  // Modal Listeners
717
  elements.openModalBtn.addEventListener('click', openModal);
 
718
  elements.closeModalBtn.addEventListener('click', closeModal);
719
  elements.addExamForm.addEventListener('submit', addExam);
720
  window.addEventListener('click', e => { if (e.target === elements.modal) closeModal(); });
721
+
722
  // Delete button listener (using event delegation)
723
  elements.examsGrid.addEventListener('click', (event) => {
724
  const deleteBtn = event.target.closest('.delete-btn');
 
732
  const data = snapshot.val();
733
  const examList = data ? Object.entries(data).map(([id, value]) => ({ id, ...value })) : [];
734
  renderExams(examList);
735
+ }, (error) => {
736
+ console.error("Firebase read failed:", error);
737
+ alert("Could not connect to the database. Please check your configuration.");
738
  });
739
 
740
  </script>