SolarumAsteridion commited on
Commit
77ed0b7
ยท
verified ยท
1 Parent(s): f9e7215

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +581 -319
index.html CHANGED
@@ -3,42 +3,74 @@
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@300;400;500;600;700&display=swap" rel="stylesheet">
8
  <style>
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
 
 
 
 
 
 
 
 
 
 
11
  body {
12
- font-family: 'Inter', sans-serif;
13
- background: #000000;
14
  min-height: 100vh;
15
  color: #fff;
16
  overflow-x: hidden;
17
- position: relative;
18
  }
19
 
20
- /* Subtle Background */
21
- .animated-bg {
 
22
  position: fixed;
23
  top: 0;
24
  left: 0;
25
  width: 100%;
26
  height: 100%;
 
 
 
 
 
 
27
  z-index: 0;
28
- background: #000000;
29
  }
30
 
31
- .animated-bg::before {
 
 
 
 
 
 
 
32
  content: '';
33
- position: absolute;
 
 
34
  width: 100%;
35
  height: 100%;
36
- background:
37
- radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 50%),
38
- radial-gradient(circle at 80% 80%, rgba(255, 255, 255, 0.02) 0%, transparent 50%);
 
 
 
 
 
 
 
 
39
  }
40
 
41
- /* Minimal Particles */
42
  .particles {
43
  position: fixed;
44
  top: 0;
@@ -47,246 +79,347 @@
47
  height: 100%;
48
  overflow: hidden;
49
  z-index: 1;
50
- pointer-events: none;
51
  }
52
 
53
  .particle {
54
  position: absolute;
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  width: 2px;
56
  height: 2px;
57
- background: rgba(255, 255, 255, 0.3);
58
- border-radius: 50%;
59
- animation: float linear infinite;
60
  }
61
 
62
- @keyframes float {
63
  from {
64
- transform: translateY(100vh) translateX(0);
65
  opacity: 0;
66
  }
67
- 20% {
68
  opacity: 1;
 
69
  }
70
- 80% {
71
  opacity: 1;
 
72
  }
73
  to {
74
- transform: translateY(-10vh) translateX(50px);
75
  opacity: 0;
76
  }
77
  }
78
 
 
 
 
 
 
79
  .container {
80
- max-width: 1200px;
81
  margin: 0 auto;
82
- padding: 4rem 2rem;
83
  position: relative;
84
  z-index: 2;
85
  }
86
 
87
- /* Clean Header */
88
  .header {
89
  display: flex;
90
  justify-content: space-between;
91
  align-items: center;
92
- margin-bottom: 4rem;
93
- padding-bottom: 2rem;
94
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
 
97
  h1 {
98
- font-size: 2.5rem;
99
- font-weight: 600;
100
- color: #ffffff;
101
- letter-spacing: -0.5px;
102
- }
103
-
104
- .add-btn-header {
105
- width: 56px;
106
- height: 56px;
107
- background: rgba(255, 255, 255, 0.1);
108
- backdrop-filter: blur(20px);
109
- border: 1px solid rgba(255, 255, 255, 0.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  border-radius: 16px;
111
  display: flex;
112
  align-items: center;
113
  justify-content: center;
114
  cursor: pointer;
115
- transition: all 0.3s ease;
 
 
116
  }
117
 
118
- .add-btn-header:hover {
119
- background: rgba(255, 255, 255, 0.15);
120
- border-color: rgba(255, 255, 255, 0.3);
121
- transform: translateY(-2px);
122
- box-shadow: 0 10px 40px rgba(255, 255, 255, 0.1);
 
 
 
 
 
 
123
  }
124
 
125
- .add-btn-header svg {
126
- width: 24px;
127
- height: 24px;
128
- fill: #ffffff;
 
 
129
  }
130
 
131
- /* Grid */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  .exams-grid {
133
  display: grid;
134
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
135
- gap: 1.5rem;
 
136
  }
137
 
138
- /* Glassmorphic Card */
139
  .exam-card {
140
- background: rgba(255, 255, 255, 0.05);
141
- backdrop-filter: blur(30px) saturate(150%);
142
- border: 1px solid rgba(255, 255, 255, 0.1);
 
 
 
 
 
 
 
143
  border-radius: 24px;
144
- padding: 2rem;
145
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
 
146
  position: relative;
147
  overflow: hidden;
 
 
148
  }
149
 
150
  .exam-card::before {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  content: '';
152
  position: absolute;
153
  top: 0;
154
  left: 0;
155
  right: 0;
156
- height: 1px;
157
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
158
- opacity: 0;
159
- transition: opacity 0.4s;
 
 
 
 
 
 
160
  }
161
 
162
- .exam-card:hover::before {
163
- opacity: 1;
 
164
  }
165
 
166
  .exam-card:hover {
167
- background: rgba(255, 255, 255, 0.08);
168
- border-color: rgba(255, 255, 255, 0.2);
169
- transform: translateY(-8px);
170
  box-shadow:
171
- 0 20px 60px rgba(0, 0, 0, 0.5),
172
- 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
 
 
 
 
173
  }
174
 
175
- .exam-header {
176
- display: flex;
177
- justify-content: space-between;
178
- align-items: flex-start;
179
- margin-bottom: 2rem;
180
  }
181
 
182
- .exam-info {
183
- flex: 1;
 
 
 
 
 
 
 
 
 
184
  }
185
 
186
- .exam-name {
187
- font-size: 1.5rem;
188
- font-weight: 600;
189
- margin-bottom: 0.5rem;
190
- color: #ffffff;
191
- line-height: 1.3;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
- .exam-date-small {
195
- font-size: 0.875rem;
196
- color: rgba(255, 255, 255, 0.5);
197
- font-weight: 400;
 
 
 
 
 
198
  }
199
 
 
200
  .delete-btn {
 
 
 
201
  width: 36px;
202
  height: 36px;
203
- background: rgba(255, 255, 255, 0.05);
204
- backdrop-filter: blur(10px);
205
- border: 1px solid rgba(255, 255, 255, 0.1);
206
- border-radius: 10px;
 
207
  display: flex;
208
  align-items: center;
209
  justify-content: center;
210
  cursor: pointer;
211
  transition: all 0.3s ease;
212
  opacity: 0;
213
- flex-shrink: 0;
214
  }
215
 
216
  .exam-card:hover .delete-btn {
217
- opacity: 1;
 
218
  }
219
 
220
  .delete-btn:hover {
221
- background: rgba(255, 255, 255, 0.1);
222
- border-color: rgba(255, 255, 255, 0.2);
223
- transform: scale(1.1);
224
- }
225
-
226
- .delete-btn svg {
227
- width: 16px;
228
- height: 16px;
229
- stroke: rgba(255, 255, 255, 0.7);
230
- fill: none;
231
- stroke-width: 2;
232
- }
233
-
234
- /* Countdown */
235
- .countdown-wrapper {
236
- display: grid;
237
- grid-template-columns: repeat(4, 1fr);
238
- gap: 0.75rem;
239
- margin: 1.5rem 0;
240
- }
241
-
242
- .countdown-item {
243
- background: rgba(255, 255, 255, 0.03);
244
- border: 1px solid rgba(255, 255, 255, 0.08);
245
- border-radius: 12px;
246
- padding: 1rem 0.5rem;
247
- text-align: center;
248
- transition: all 0.3s ease;
249
  }
250
 
251
- .countdown-item:hover {
252
- background: rgba(255, 255, 255, 0.05);
253
- border-color: rgba(255, 255, 255, 0.12);
254
- }
255
-
256
- .countdown-number {
257
- font-size: 2rem;
258
- font-weight: 700;
259
- color: #ffffff;
260
- line-height: 1;
261
- margin-bottom: 0.4rem;
262
- }
263
-
264
- .countdown-label {
265
- font-size: 0.7rem;
266
- color: rgba(255, 255, 255, 0.5);
267
- text-transform: uppercase;
268
- letter-spacing: 0.5px;
269
- font-weight: 500;
270
- }
271
-
272
- /* Progress Bar */
273
- .progress-container {
274
- width: 100%;
275
- height: 6px;
276
- background: rgba(255, 255, 255, 0.05);
277
- border-radius: 10px;
278
- overflow: hidden;
279
- margin-top: 1.5rem;
280
- }
281
-
282
- .progress-bar {
283
- height: 100%;
284
- background: linear-gradient(90deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8));
285
- border-radius: 10px;
286
- transition: width 1s ease;
287
- }
288
-
289
- /* Modal */
290
  .modal {
291
  display: none;
292
  position: fixed;
@@ -294,16 +427,16 @@
294
  left: 0;
295
  width: 100%;
296
  height: 100%;
297
- background: rgba(0, 0, 0, 0.8);
298
  backdrop-filter: blur(10px);
299
  z-index: 1000;
300
  align-items: center;
301
  justify-content: center;
302
  padding: 2rem;
303
- animation: fadeIn 0.3s ease;
304
  }
305
 
306
- @keyframes fadeIn {
307
  from { opacity: 0; }
308
  to { opacity: 1; }
309
  }
@@ -313,49 +446,76 @@
313
  }
314
 
315
  .modal-content {
316
- background: rgba(15, 15, 15, 0.95);
317
- backdrop-filter: blur(40px) saturate(150%);
318
- border: 1px solid rgba(255, 255, 255, 0.15);
319
- border-radius: 24px;
320
- padding: 2.5rem;
 
 
 
 
 
321
  width: 100%;
322
- max-width: 480px;
323
  box-shadow:
324
- 0 30px 80px rgba(0, 0, 0, 0.8),
325
- 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
326
- animation: modalSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
 
329
  @keyframes modalSlideIn {
330
- from {
331
- transform: translateY(20px);
332
- opacity: 0;
333
- }
334
- to {
335
- transform: translateY(0);
336
- opacity: 1;
337
- }
338
  }
339
 
340
  .modal-header {
341
  display: flex;
342
  justify-content: space-between;
343
  align-items: center;
344
- margin-bottom: 2rem;
 
 
345
  }
346
 
347
  .modal-title {
348
- font-size: 1.75rem;
349
- font-weight: 600;
350
- color: #ffffff;
 
 
 
 
 
351
  }
352
 
353
  .close-btn {
354
- width: 36px;
355
- height: 36px;
356
- background: rgba(255, 255, 255, 0.05);
357
- border: 1px solid rgba(255, 255, 255, 0.1);
358
- border-radius: 10px;
359
  cursor: pointer;
360
  display: flex;
361
  align-items: center;
@@ -364,123 +524,158 @@
364
  }
365
 
366
  .close-btn:hover {
367
- background: rgba(255, 255, 255, 0.1);
368
- border-color: rgba(255, 255, 255, 0.2);
369
- }
370
-
371
- .close-btn svg {
372
- stroke: rgba(255, 255, 255, 0.7);
373
- fill: none;
374
- stroke-width: 2;
375
  }
376
 
377
- /* Form */
378
  .form-group {
379
- margin-bottom: 1.5rem;
 
 
380
  }
381
 
382
  .form-group label {
383
  display: block;
384
- margin-bottom: 0.6rem;
 
 
 
 
385
  font-weight: 500;
386
- color: rgba(255, 255, 255, 0.7);
387
- font-size: 0.9rem;
388
  }
389
 
390
  .form-group input {
391
  width: 100%;
392
- padding: 1rem 1.25rem;
393
- background: rgba(255, 255, 255, 0.05);
394
- border: 1px solid rgba(255, 255, 255, 0.1);
395
  border-radius: 12px;
396
  color: #fff;
397
- font-size: 1rem;
398
- font-family: 'Inter', sans-serif;
399
  transition: all 0.3s ease;
400
- backdrop-filter: blur(10px);
401
  }
402
 
403
  .form-group input:focus {
404
  outline: none;
405
- border-color: rgba(255, 255, 255, 0.3);
406
- background: rgba(255, 255, 255, 0.08);
407
- box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.05);
 
 
408
  }
409
 
410
  .form-group input::placeholder {
411
  color: rgba(255, 255, 255, 0.3);
412
  }
413
 
414
- .submit-btn {
 
415
  width: 100%;
416
- padding: 1.1rem;
417
- background: rgba(255, 255, 255, 0.1);
418
- border: 1px solid rgba(255, 255, 255, 0.2);
419
- border-radius: 12px;
420
  color: #fff;
421
- font-size: 1rem;
422
- font-weight: 600;
423
  cursor: pointer;
424
- transition: all 0.3s ease;
425
- font-family: 'Inter', sans-serif;
426
- backdrop-filter: blur(10px);
427
- margin-top: 1rem;
 
 
428
  }
429
 
430
- .submit-btn:hover {
431
- background: rgba(255, 255, 255, 0.15);
432
- border-color: rgba(255, 255, 255, 0.3);
433
- transform: translateY(-2px);
434
- box-shadow: 0 10px 30px rgba(255, 255, 255, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
- .submit-btn:active {
438
- transform: translateY(0);
 
439
  }
440
 
441
- /* Empty State */
442
  .empty-state {
443
  text-align: center;
444
  padding: 6rem 2rem;
445
- background: rgba(255, 255, 255, 0.03);
446
- backdrop-filter: blur(20px);
447
- border-radius: 24px;
448
- border: 1px dashed rgba(255, 255, 255, 0.1);
449
  }
450
 
451
- .empty-state-icon {
452
- font-size: 4rem;
453
- margin-bottom: 1.5rem;
454
- opacity: 0.3;
 
 
455
  }
456
 
457
- .empty-state p {
458
- font-size: 1.1rem;
459
- color: rgba(255, 255, 255, 0.4);
460
- line-height: 1.6;
461
- font-weight: 400;
462
  }
463
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  /* Responsive */
465
  @media (max-width: 768px) {
466
- h1 { font-size: 2rem; }
467
- .container { padding: 2rem 1.5rem; }
468
  .exams-grid { grid-template-columns: 1fr; }
469
- .countdown-number { font-size: 1.75rem; }
470
  .modal-content { padding: 2rem; }
471
  }
472
  </style>
473
  </head>
474
  <body>
475
- <div class="animated-bg"></div>
 
 
 
 
 
 
 
 
476
  <div class="particles" id="particles"></div>
477
 
478
  <div class="container">
479
  <div class="header">
480
  <h1>Exam Countdown</h1>
481
- <div class="add-btn-header" id="openModalBtn">
482
  <svg viewBox="0 0 24 24">
483
- <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>
484
  </svg>
485
  </div>
486
  </div>
@@ -488,47 +683,35 @@
488
  <div class="exams-grid" id="examsGrid"></div>
489
 
490
  <div class="empty-state" id="emptyState" style="display: none;">
491
- <div class="empty-state-icon">๐Ÿ“š</div>
492
- <p>No exams yet. Add your first exam to get started.</p>
493
  </div>
494
  </div>
495
-
496
  <div class="modal" id="modal">
497
  <div class="modal-content">
498
  <div class="modal-header">
499
- <h2 class="modal-title">Add Exam</h2>
500
  <button class="close-btn" id="closeModalBtn">
501
- <svg width="20" height="20" viewBox="0 0 24 24">
502
- <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
503
  </svg>
504
  </button>
505
  </div>
 
506
  <form id="addExamForm">
507
  <div class="form-group">
508
  <label for="examName">Exam Name</label>
509
- <input type="text" id="examName" required placeholder="Physics Final">
510
  </div>
511
  <div class="form-group">
512
- <label for="examDate">Date & Time</label>
513
  <input type="datetime-local" id="examDate" required>
514
  </div>
515
- <button type="submit" class="submit-btn">Add Exam</button>
516
  </form>
517
  </div>
518
  </div>
519
 
520
- <script>
521
- // Create minimal particles
522
- const particlesContainer = document.getElementById('particles');
523
- for (let i = 0; i < 30; i++) {
524
- const particle = document.createElement('div');
525
- particle.className = 'particle';
526
- particle.style.left = Math.random() * 100 + '%';
527
- particle.style.animationDuration = Math.random() * 20 + 15 + 's';
528
- particle.style.animationDelay = Math.random() * 5 + 's';
529
- particlesContainer.appendChild(particle);
530
- }
531
-
532
  <script type="module">
533
  // Firebase SDK modules
534
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
@@ -562,8 +745,40 @@
562
  emptyState: document.getElementById('emptyState'),
563
  particlesContainer: document.getElementById('particles')
564
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
 
566
- // --- Core Functions ---
567
  const openModal = () => elements.modal.classList.add('active');
568
  const closeModal = () => {
569
  elements.modal.classList.remove('active');
@@ -572,9 +787,11 @@
572
 
573
  const addExam = (event) => {
574
  event.preventDefault();
 
575
  const newExam = {
576
  name: elements.examNameInput.value,
577
- date: elements.examDateInput.value
 
578
  };
579
  push(examsRef, newExam).catch(err => console.error("Failed to add exam:", err));
580
  closeModal();
@@ -585,59 +802,98 @@
585
  remove(ref(database, `exams/${id}`)).catch(err => console.error("Failed to delete exam:", err));
586
  };
587
 
588
- // --- Rendering ---
589
- const calculateDaysUntil = (examDate) => {
590
- const now = new Date();
591
- const todayIST = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Kolkata' }));
592
- todayIST.setHours(0, 0, 0, 0);
 
 
 
 
 
 
 
 
 
 
 
 
593
 
594
- const targetDate = new Date(examDate);
595
- // The date from the input is already in the user's local timezone,
596
- // but we treat it as a date in IST for calculation.
597
- const targetDateIST = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate());
 
 
598
 
599
- return Math.ceil((targetDateIST - todayIST) / 86400000); // 1000*60*60*24
600
- };
 
601
 
602
- const renderExams = (exams) => {
603
- elements.examsGrid.innerHTML = '';
604
- const hasExams = exams && exams.length > 0;
605
- elements.emptyState.style.display = hasExams ? 'none' : 'block';
606
- if (!hasExams) return;
607
-
608
- exams.sort((a, b) => new Date(a.date) - new Date(b.date)).forEach(exam => {
609
- const daysUntil = calculateDaysUntil(exam.date);
610
- const card = document.createElement('div');
611
- card.className = 'exam-card';
612
- const color = daysUntil <= 3 ? '#FF0080' : (daysUntil <= 7 ? '#00FF88' : '#00D4FF');
613
-
614
- card.innerHTML = `
615
- <div class="delete-btn" title="Delete Exam"><svg width="16" height="16" viewBox="0 0 24 24" fill="#FF0080"><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></div>
616
- <h3 class="exam-name">${exam.name}</h3>
617
- <div class="countdown" style="color: ${color};">${daysUntil}</div>
618
- <div class="countdown-label">days remaining</div>
619
- <div class="exam-date">${new Date(exam.date).toLocaleDateString('en-IN', { timeZone: 'Asia/Kolkata', weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</div>`;
620
 
621
- card.querySelector('.delete-btn').addEventListener('click', () => deleteExam(exam.id));
622
- elements.examsGrid.appendChild(card);
623
- });
624
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
 
626
- // --- Particle Effect ---
627
- const createParticles = () => {
628
- if (elements.particlesContainer.children.length > 0) return;
629
- let particleHtml = '';
630
- for (let i = 0; i < 50; i++) {
631
- const style = `left:${Math.random()*100}%;animation-delay:${Math.random()*20}s;animation-duration:${15+Math.random()*10}s;`;
632
- particleHtml += `<div class="particle" style="${style}"></div>`;
633
  }
634
- elements.particlesContainer.innerHTML = particleHtml;
635
- };
 
636
  // --- App Initialization ---
637
  createParticles();
638
- elements.examDateInput.min = new Date().toISOString().split('T')[0];
639
 
640
- // Event Listeners
641
  elements.openModalBtn.addEventListener('click', openModal);
642
  elements.closeModalBtn.addEventListener('click', closeModal);
643
  elements.addExamForm.addEventListener('submit', addExam);
@@ -646,12 +902,18 @@
646
  // Firebase real-time data listener
647
  onValue(examsRef, (snapshot) => {
648
  const data = snapshot.val();
649
- const examList = data ? Object.entries(data).map(([id, value]) => ({ id, ...value })) : [];
650
- renderExams(examList);
 
 
651
  }, (error) => {
652
  console.error("Firebase read failed:", error);
653
  alert("Could not connect to the database. Check console for errors and verify database rules.");
654
  });
 
 
 
 
655
  </script>
656
  </body>
657
  </html>
 
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=Space+Grotesk:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
8
  <style>
9
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
 
11
+ :root {
12
+ --neon-pink: #FF006E;
13
+ --neon-blue: #00D9FF;
14
+ --neon-purple: #8B39FF;
15
+ --neon-green: #39FF14;
16
+ --neon-yellow: #FFFF00;
17
+ --dark-bg: #0A0A0F;
18
+ }
19
+
20
  body {
21
+ font-family: 'Space Grotesk', sans-serif;
22
+ background: var(--dark-bg);
23
  min-height: 100vh;
24
  color: #fff;
25
  overflow-x: hidden;
26
+ position: relative;
27
  }
28
 
29
+ /* Animated gradient background */
30
+ body::before {
31
+ content: '';
32
  position: fixed;
33
  top: 0;
34
  left: 0;
35
  width: 100%;
36
  height: 100%;
37
+ background:
38
+ radial-gradient(circle at 20% 80%, rgba(255, 0, 110, 0.1) 0%, transparent 50%),
39
+ radial-gradient(circle at 80% 20%, rgba(0, 217, 255, 0.1) 0%, transparent 50%),
40
+ radial-gradient(circle at 40% 40%, rgba(139, 57, 255, 0.1) 0%, transparent 50%),
41
+ radial-gradient(circle at 90% 70%, rgba(57, 255, 20, 0.05) 0%, transparent 50%);
42
+ animation: gradientShift 15s ease infinite;
43
  z-index: 0;
 
44
  }
45
 
46
+ @keyframes gradientShift {
47
+ 0%, 100% { transform: rotate(0deg) scale(1); }
48
+ 33% { transform: rotate(120deg) scale(1.1); }
49
+ 66% { transform: rotate(240deg) scale(1.05); }
50
+ }
51
+
52
+ /* Grid background */
53
+ body::after {
54
  content: '';
55
+ position: fixed;
56
+ top: 0;
57
+ left: 0;
58
  width: 100%;
59
  height: 100%;
60
+ background-image:
61
+ linear-gradient(rgba(0, 217, 255, 0.03) 1px, transparent 1px),
62
+ linear-gradient(90deg, rgba(0, 217, 255, 0.03) 1px, transparent 1px);
63
+ background-size: 50px 50px;
64
+ animation: gridMove 10s linear infinite;
65
+ z-index: 0;
66
+ }
67
+
68
+ @keyframes gridMove {
69
+ 0% { transform: translate(0, 0); }
70
+ 100% { transform: translate(50px, 50px); }
71
  }
72
 
73
+ /* Enhanced particles */
74
  .particles {
75
  position: fixed;
76
  top: 0;
 
79
  height: 100%;
80
  overflow: hidden;
81
  z-index: 1;
 
82
  }
83
 
84
  .particle {
85
  position: absolute;
86
+ border-radius: 50%;
87
+ pointer-events: none;
88
+ }
89
+
90
+ .particle.orb {
91
+ width: 6px;
92
+ height: 6px;
93
+ background: radial-gradient(circle, var(--neon-blue) 0%, transparent 70%);
94
+ box-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue);
95
+ animation: floatOrb 20s infinite linear;
96
+ }
97
+
98
+ .particle.star {
99
  width: 2px;
100
  height: 2px;
101
+ background: white;
102
+ box-shadow: 0 0 6px white;
103
+ animation: twinkle 3s infinite;
104
  }
105
 
106
+ @keyframes floatOrb {
107
  from {
108
+ transform: translateY(100vh) translateX(0) scale(0);
109
  opacity: 0;
110
  }
111
+ 10% {
112
  opacity: 1;
113
+ transform: translateY(90vh) translateX(10px) scale(1);
114
  }
115
+ 90% {
116
  opacity: 1;
117
+ transform: translateY(10vh) translateX(-10px) scale(1);
118
  }
119
  to {
120
+ transform: translateY(-100vh) translateX(100px) scale(0);
121
  opacity: 0;
122
  }
123
  }
124
 
125
+ @keyframes twinkle {
126
+ 0%, 100% { opacity: 0; transform: scale(0.5); }
127
+ 50% { opacity: 1; transform: scale(1); }
128
+ }
129
+
130
  .container {
131
+ max-width: 1400px;
132
  margin: 0 auto;
133
+ padding: 2rem;
134
  position: relative;
135
  z-index: 2;
136
  }
137
 
138
+ /* Enhanced header */
139
  .header {
140
  display: flex;
141
  justify-content: space-between;
142
  align-items: center;
143
+ margin-bottom: 4rem;
144
+ padding: 2rem 0;
145
+ position: relative;
146
+ }
147
+
148
+ .header::after {
149
+ content: '';
150
+ position: absolute;
151
+ bottom: 0;
152
+ left: 50%;
153
+ transform: translateX(-50%);
154
+ width: 100%;
155
+ height: 1px;
156
+ background: linear-gradient(90deg,
157
+ transparent,
158
+ var(--neon-blue) 20%,
159
+ var(--neon-pink) 50%,
160
+ var(--neon-purple) 80%,
161
+ transparent);
162
+ animation: lineGlow 3s ease-in-out infinite;
163
+ }
164
+
165
+ @keyframes lineGlow {
166
+ 0%, 100% { opacity: 0.3; }
167
+ 50% { opacity: 1; }
168
  }
169
 
170
  h1 {
171
+ font-family: 'Orbitron', monospace;
172
+ font-size: 3.5rem;
173
+ font-weight: 900;
174
+ text-transform: uppercase;
175
+ letter-spacing: 3px;
176
+ background: linear-gradient(135deg,
177
+ var(--neon-blue) 0%,
178
+ var(--neon-pink) 25%,
179
+ var(--neon-purple) 50%,
180
+ var(--neon-green) 75%,
181
+ var(--neon-blue) 100%);
182
+ background-size: 300% 300%;
183
+ -webkit-background-clip: text;
184
+ -webkit-text-fill-color: transparent;
185
+ background-clip: text;
186
+ animation: gradientFlow 4s ease infinite;
187
+ filter: drop-shadow(0 0 30px rgba(0, 217, 255, 0.5))
188
+ drop-shadow(0 0 60px rgba(255, 0, 110, 0.3));
189
+ }
190
+
191
+ @keyframes gradientFlow {
192
+ 0% { background-position: 0% 50%; }
193
+ 50% { background-position: 100% 50%; }
194
+ 100% { background-position: 0% 50%; }
195
+ }
196
+
197
+ /* Enhanced settings button */
198
+ .settings-btn {
199
+ width: 60px;
200
+ height: 60px;
201
+ background: linear-gradient(135deg,
202
+ rgba(0, 217, 255, 0.1) 0%,
203
+ rgba(255, 0, 110, 0.1) 100%);
204
+ backdrop-filter: blur(20px);
205
+ border: 2px solid transparent;
206
+ background-origin: border-box;
207
+ background-clip: padding-box, border-box;
208
+ border-image: linear-gradient(135deg, var(--neon-blue), var(--neon-pink)) 1;
209
  border-radius: 16px;
210
  display: flex;
211
  align-items: center;
212
  justify-content: center;
213
  cursor: pointer;
214
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
215
+ position: relative;
216
+ overflow: hidden;
217
  }
218
 
219
+ .settings-btn::before {
220
+ content: '';
221
+ position: absolute;
222
+ top: 50%;
223
+ left: 50%;
224
+ width: 100%;
225
+ height: 100%;
226
+ background: radial-gradient(circle, var(--neon-blue) 0%, transparent 70%);
227
+ transform: translate(-50%, -50%) scale(0);
228
+ transition: transform 0.5s ease;
229
+ z-index: -1;
230
  }
231
 
232
+ .settings-btn:hover {
233
+ transform: scale(1.1) rotate(90deg);
234
+ box-shadow:
235
+ 0 0 30px rgba(0, 217, 255, 0.6),
236
+ 0 0 60px rgba(255, 0, 110, 0.4),
237
+ inset 0 0 20px rgba(0, 217, 255, 0.2);
238
  }
239
 
240
+ .settings-btn:hover::before {
241
+ transform: translate(-50%, -50%) scale(2);
242
+ }
243
+
244
+ .settings-btn svg {
245
+ width: 28px;
246
+ height: 28px;
247
+ fill: url(#iconGradient);
248
+ filter: drop-shadow(0 0 10px var(--neon-blue));
249
+ transition: transform 0.4s ease;
250
+ }
251
+
252
+ .settings-btn:hover svg {
253
+ transform: rotate(-90deg);
254
+ }
255
+
256
+ /* Enhanced exam grid */
257
  .exams-grid {
258
  display: grid;
259
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
260
+ gap: 2.5rem;
261
+ padding: 2rem 0;
262
  }
263
 
264
+ /* Ultra enhanced exam cards */
265
  .exam-card {
266
+ background: linear-gradient(135deg,
267
+ rgba(0, 217, 255, 0.05) 0%,
268
+ rgba(255, 0, 110, 0.05) 100%);
269
+ backdrop-filter: blur(40px) saturate(150%);
270
+ border: 1px solid transparent;
271
+ background-origin: border-box;
272
+ background-clip: padding-box, border-box;
273
+ border-image: linear-gradient(135deg,
274
+ rgba(0, 217, 255, 0.3),
275
+ rgba(255, 0, 110, 0.3)) 1;
276
  border-radius: 24px;
277
+ padding: 2.5rem;
278
+ text-align: center;
279
+ transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
280
  position: relative;
281
  overflow: hidden;
282
+ transform-style: preserve-3d;
283
+ perspective: 1000px;
284
  }
285
 
286
  .exam-card::before {
287
+ content: '';
288
+ position: absolute;
289
+ top: -50%;
290
+ left: -50%;
291
+ width: 200%;
292
+ height: 200%;
293
+ background: radial-gradient(circle,
294
+ rgba(0, 217, 255, 0.1) 0%,
295
+ transparent 40%);
296
+ animation: cardPulse 4s ease-in-out infinite;
297
+ pointer-events: none;
298
+ }
299
+
300
+ .exam-card::after {
301
  content: '';
302
  position: absolute;
303
  top: 0;
304
  left: 0;
305
  right: 0;
306
+ bottom: 0;
307
+ background: linear-gradient(105deg,
308
+ transparent 40%,
309
+ rgba(255, 255, 255, 0.05) 45%,
310
+ rgba(255, 255, 255, 0.1) 50%,
311
+ rgba(255, 255, 255, 0.05) 55%,
312
+ transparent 60%);
313
+ transform: translateX(-100%);
314
+ transition: transform 0.6s;
315
+ pointer-events: none;
316
  }
317
 
318
+ @keyframes cardPulse {
319
+ 0%, 100% { transform: scale(1) rotate(0deg); opacity: 0.5; }
320
+ 50% { transform: scale(1.2) rotate(180deg); opacity: 0.8; }
321
  }
322
 
323
  .exam-card:hover {
324
+ transform: translateY(-10px) rotateX(5deg) scale(1.02);
 
 
325
  box-shadow:
326
+ 0 20px 40px rgba(0, 217, 255, 0.3),
327
+ 0 40px 80px rgba(255, 0, 110, 0.2),
328
+ inset 0 0 30px rgba(0, 217, 255, 0.1);
329
+ border-image: linear-gradient(135deg,
330
+ var(--neon-blue),
331
+ var(--neon-pink)) 1;
332
  }
333
 
334
+ .exam-card:hover::after {
335
+ transform: translateX(100%);
 
 
 
336
  }
337
 
338
+ .exam-name {
339
+ font-size: 1.8rem;
340
+ font-weight: 700;
341
+ margin-bottom: 1.5rem;
342
+ background: linear-gradient(135deg, #fff 0%, #00D9FF 100%);
343
+ -webkit-background-clip: text;
344
+ -webkit-text-fill-color: transparent;
345
+ background-clip: text;
346
+ text-transform: uppercase;
347
+ letter-spacing: 2px;
348
+ filter: drop-shadow(0 2px 10px rgba(0, 217, 255, 0.5));
349
  }
350
 
351
+ .countdown-wrapper {
352
+ display: flex;
353
+ justify-content: center;
354
+ gap: 1.5rem;
355
+ margin-bottom: 2rem;
356
+ }
357
+ .countdown-item {
358
+ display: flex;
359
+ flex-direction: column;
360
+ }
361
+ .countdown-number {
362
+ font-family: 'Orbitron', monospace;
363
+ font-size: 3rem;
364
+ font-weight: 900;
365
+ color: var(--neon-blue);
366
+ text-shadow: 0 0 20px var(--neon-blue);
367
+ line-height: 1;
368
+ }
369
+ .countdown-label {
370
+ font-size: 0.8rem;
371
+ color: rgba(255, 255, 255, 0.7);
372
+ text-transform: uppercase;
373
+ letter-spacing: 2px;
374
+ font-weight: 300;
375
  }
376
 
377
+ .exam-date {
378
+ font-size: 0.95rem;
379
+ color: rgba(0, 217, 255, 0.7);
380
+ margin-top: 1.5rem;
381
+ padding: 0.5rem 1rem;
382
+ background: rgba(0, 217, 255, 0.1);
383
+ border-radius: 20px;
384
+ display: inline-block;
385
+ border: 1px solid rgba(0, 217, 255, 0.3);
386
  }
387
 
388
+ /* Enhanced delete button */
389
  .delete-btn {
390
+ position: absolute;
391
+ top: 1.5rem;
392
+ right: 1.5rem;
393
  width: 36px;
394
  height: 36px;
395
+ background: linear-gradient(135deg,
396
+ rgba(255, 0, 110, 0.2) 0%,
397
+ rgba(255, 0, 0, 0.2) 100%);
398
+ border: 2px solid rgba(255, 0, 110, 0.5);
399
+ border-radius: 12px;
400
  display: flex;
401
  align-items: center;
402
  justify-content: center;
403
  cursor: pointer;
404
  transition: all 0.3s ease;
405
  opacity: 0;
406
+ transform: scale(0.8);
407
  }
408
 
409
  .exam-card:hover .delete-btn {
410
+ opacity: 1;
411
+ transform: scale(1);
412
  }
413
 
414
  .delete-btn:hover {
415
+ background: linear-gradient(135deg,
416
+ rgba(255, 0, 110, 0.4) 0%,
417
+ rgba(255, 0, 0, 0.4) 100%);
418
+ transform: scale(1.2) rotate(90deg);
419
+ box-shadow: 0 0 20px rgba(255, 0, 110, 0.6);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  }
421
 
422
+ /* Enhanced modal */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  .modal {
424
  display: none;
425
  position: fixed;
 
427
  left: 0;
428
  width: 100%;
429
  height: 100%;
430
+ background: rgba(0, 0, 0, 0.9);
431
  backdrop-filter: blur(10px);
432
  z-index: 1000;
433
  align-items: center;
434
  justify-content: center;
435
  padding: 2rem;
436
+ animation: modalFadeIn 0.3s ease;
437
  }
438
 
439
+ @keyframes modalFadeIn {
440
  from { opacity: 0; }
441
  to { opacity: 1; }
442
  }
 
446
  }
447
 
448
  .modal-content {
449
+ background: linear-gradient(135deg,
450
+ rgba(20, 20, 30, 0.98) 0%,
451
+ rgba(30, 20, 40, 0.98) 100%);
452
+ backdrop-filter: blur(40px) saturate(150%);
453
+ border: 2px solid transparent;
454
+ background-origin: border-box;
455
+ background-clip: padding-box, border-box;
456
+ border-image: linear-gradient(135deg, var(--neon-blue), var(--neon-pink)) 1;
457
+ border-radius: 30px;
458
+ padding: 3rem;
459
  width: 100%;
460
+ max-width: 500px;
461
  box-shadow:
462
+ 0 30px 60px rgba(0, 0, 0, 0.5),
463
+ 0 0 100px rgba(0, 217, 255, 0.2),
464
+ inset 0 0 30px rgba(0, 217, 255, 0.05);
465
+ animation: modalSlideIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
466
+ position: relative;
467
+ overflow: hidden;
468
+ }
469
+
470
+ .modal-content::before {
471
+ content: '';
472
+ position: absolute;
473
+ top: -50%;
474
+ left: -50%;
475
+ width: 200%;
476
+ height: 200%;
477
+ background: radial-gradient(circle,
478
+ rgba(0, 217, 255, 0.1) 0%,
479
+ transparent 40%);
480
+ animation: modalGlow 4s ease-in-out infinite;
481
  }
482
 
483
  @keyframes modalSlideIn {
484
+ from { transform: translateY(-50px) scale(0.9); opacity: 0; }
485
+ to { transform: translateY(0) scale(1); opacity: 1; }
486
+ }
487
+
488
+ @keyframes modalGlow {
489
+ 0%, 100% { transform: rotate(0deg); }
490
+ 50% { transform: rotate(180deg); }
 
491
  }
492
 
493
  .modal-header {
494
  display: flex;
495
  justify-content: space-between;
496
  align-items: center;
497
+ margin-bottom: 2.5rem;
498
+ position: relative;
499
+ z-index: 1;
500
  }
501
 
502
  .modal-title {
503
+ font-size: 2rem;
504
+ font-weight: 700;
505
+ background: linear-gradient(135deg, var(--neon-blue) 0%, var(--neon-pink) 100%);
506
+ -webkit-background-clip: text;
507
+ -webkit-text-fill-color: transparent;
508
+ background-clip: text;
509
+ text-transform: uppercase;
510
+ letter-spacing: 2px;
511
  }
512
 
513
  .close-btn {
514
+ width: 40px;
515
+ height: 40px;
516
+ background: rgba(255, 0, 110, 0.1);
517
+ border: 2px solid rgba(255, 0, 110, 0.3);
518
+ border-radius: 12px;
519
  cursor: pointer;
520
  display: flex;
521
  align-items: center;
 
524
  }
525
 
526
  .close-btn:hover {
527
+ transform: rotate(90deg) scale(1.1);
528
+ background: rgba(255, 0, 110, 0.2);
529
+ box-shadow: 0 0 20px rgba(255, 0, 110, 0.5);
 
 
 
 
 
530
  }
531
 
532
+ /* Enhanced form */
533
  .form-group {
534
+ margin-bottom: 2rem;
535
+ position: relative;
536
+ z-index: 1;
537
  }
538
 
539
  .form-group label {
540
  display: block;
541
+ margin-bottom: 0.75rem;
542
+ font-size: 0.95rem;
543
+ color: var(--neon-blue);
544
+ text-transform: uppercase;
545
+ letter-spacing: 2px;
546
  font-weight: 500;
 
 
547
  }
548
 
549
  .form-group input {
550
  width: 100%;
551
+ padding: 1.2rem;
552
+ background: rgba(0, 217, 255, 0.05);
553
+ border: 2px solid rgba(0, 217, 255, 0.2);
554
  border-radius: 12px;
555
  color: #fff;
556
+ font-size: 1.1rem;
 
557
  transition: all 0.3s ease;
558
+ font-family: 'Space Grotesk', sans-serif;
559
  }
560
 
561
  .form-group input:focus {
562
  outline: none;
563
+ border-color: var(--neon-blue);
564
+ background: rgba(0, 217, 255, 0.1);
565
+ box-shadow:
566
+ 0 0 20px rgba(0, 217, 255, 0.3),
567
+ inset 0 0 10px rgba(0, 217, 255, 0.1);
568
  }
569
 
570
  .form-group input::placeholder {
571
  color: rgba(255, 255, 255, 0.3);
572
  }
573
 
574
+ /* Enhanced add button */
575
+ .add-btn {
576
  width: 100%;
577
+ padding: 1.5rem;
578
+ background: linear-gradient(135deg, var(--neon-blue) 0%, var(--neon-pink) 100%);
579
+ border: none;
580
+ border-radius: 16px;
581
  color: #fff;
582
+ font-size: 1.2rem;
583
+ font-weight: 700;
584
  cursor: pointer;
585
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
586
+ text-transform: uppercase;
587
+ letter-spacing: 2px;
588
+ position: relative;
589
+ overflow: hidden;
590
+ z-index: 1;
591
  }
592
 
593
+ .add-btn::before {
594
+ content: '';
595
+ position: absolute;
596
+ top: 50%;
597
+ left: 50%;
598
+ width: 0;
599
+ height: 0;
600
+ background: radial-gradient(circle,
601
+ rgba(255, 255, 255, 0.3) 0%,
602
+ transparent 70%);
603
+ transform: translate(-50%, -50%);
604
+ transition: width 0.6s, height 0.6s;
605
+ }
606
+
607
+ .add-btn:hover {
608
+ transform: translateY(-3px) scale(1.02);
609
+ box-shadow:
610
+ 0 10px 30px rgba(0, 217, 255, 0.5),
611
+ 0 20px 60px rgba(255, 0, 110, 0.3);
612
  }
613
 
614
+ .add-btn:hover::before {
615
+ width: 300px;
616
+ height: 300px;
617
  }
618
 
619
+ /* Enhanced empty state */
620
  .empty-state {
621
  text-align: center;
622
  padding: 6rem 2rem;
623
+ position: relative;
 
 
 
624
  }
625
 
626
+ .empty-state::before {
627
+ content: '๐Ÿ“š';
628
+ font-size: 6rem;
629
+ display: block;
630
+ margin-bottom: 2rem;
631
+ animation: bounce 2s ease-in-out infinite;
632
  }
633
 
634
+ @keyframes bounce {
635
+ 0%, 100% { transform: translateY(0); }
636
+ 50% { transform: translateY(-20px); }
 
 
637
  }
638
 
639
+ .empty-state p {
640
+ font-size: 1.4rem;
641
+ background: linear-gradient(135deg,
642
+ rgba(255, 255, 255, 0.8) 0%,
643
+ rgba(0, 217, 255, 0.8) 100%);
644
+ -webkit-background-clip: text;
645
+ -webkit-text-fill-color: transparent;
646
+ background-clip: text;
647
+ margin-bottom: 2rem;
648
+ font-weight: 500;
649
+ }
650
+
651
  /* Responsive */
652
  @media (max-width: 768px) {
653
+ h1 { font-size: 2.5rem; }
654
+ .countdown-number { font-size: 2rem; }
655
  .exams-grid { grid-template-columns: 1fr; }
656
+ .header { flex-direction: column; gap: 1rem; text-align: center;}
657
  .modal-content { padding: 2rem; }
658
  }
659
  </style>
660
  </head>
661
  <body>
662
+ <svg width="0" height="0">
663
+ <defs>
664
+ <linearGradient id="iconGradient" x1="0%" y1="0%" x2="100%" y2="100%">
665
+ <stop offset="0%" style="stop-color:#00D9FF;stop-opacity:1" />
666
+ <stop offset="100%" style="stop-color:#FF006E;stop-opacity:1" />
667
+ </linearGradient>
668
+ </defs>
669
+ </svg>
670
+
671
  <div class="particles" id="particles"></div>
672
 
673
  <div class="container">
674
  <div class="header">
675
  <h1>Exam Countdown</h1>
676
+ <div class="settings-btn" id="openModalBtn">
677
  <svg viewBox="0 0 24 24">
678
+ <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"/>
679
  </svg>
680
  </div>
681
  </div>
 
683
  <div class="exams-grid" id="examsGrid"></div>
684
 
685
  <div class="empty-state" id="emptyState" style="display: none;">
686
+ <p>No exams scheduled yet. Click the + button to add your first exam!</p>
 
687
  </div>
688
  </div>
689
+
690
  <div class="modal" id="modal">
691
  <div class="modal-content">
692
  <div class="modal-header">
693
+ <h2 class="modal-title">Add New Exam</h2>
694
  <button class="close-btn" id="closeModalBtn">
695
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="#FF006E">
696
+ <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"/>
697
  </svg>
698
  </button>
699
  </div>
700
+
701
  <form id="addExamForm">
702
  <div class="form-group">
703
  <label for="examName">Exam Name</label>
704
+ <input type="text" id="examName" required placeholder="e.g., Physics Final">
705
  </div>
706
  <div class="form-group">
707
+ <label for="examDate">Exam Date & Time</label>
708
  <input type="datetime-local" id="examDate" required>
709
  </div>
710
+ <button type="submit" class="add-btn">Add Exam</button>
711
  </form>
712
  </div>
713
  </div>
714
 
 
 
 
 
 
 
 
 
 
 
 
 
715
  <script type="module">
716
  // Firebase SDK modules
717
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
 
745
  emptyState: document.getElementById('emptyState'),
746
  particlesContainer: document.getElementById('particles')
747
  };
748
+
749
+ let exams = []; // This will hold our exams from Firebase
750
+
751
+ // --- Enhanced Particle System ---
752
+ function createParticles() {
753
+ if (elements.particlesContainer.children.length > 0) return;
754
+ // Create orbs
755
+ for (let i = 0; i < 15; i++) {
756
+ const particle = document.createElement('div');
757
+ particle.classList.add('particle', 'orb');
758
+ particle.style.left = Math.random() * 100 + '%';
759
+ particle.style.animationDelay = Math.random() * 20 + 's';
760
+ particle.style.animationDuration = (15 + Math.random() * 10) + 's';
761
+
762
+ const colors = ['#00D9FF', '#FF006E', '#8B39FF', '#39FF14'];
763
+ const color = colors[Math.floor(Math.random() * colors.length)];
764
+ particle.style.background = `radial-gradient(circle, ${color} 0%, transparent 70%)`;
765
+ particle.style.boxShadow = `0 0 10px ${color}, 0 0 20px ${color}`;
766
+
767
+ elements.particlesContainer.appendChild(particle);
768
+ }
769
+
770
+ // Create stars
771
+ for (let i = 0; i < 30; i++) {
772
+ const star = document.createElement('div');
773
+ star.classList.add('particle', 'star');
774
+ star.style.left = Math.random() * 100 + '%';
775
+ star.style.top = Math.random() * 100 + '%';
776
+ star.style.animationDelay = Math.random() * 3 + 's';
777
+ elements.particlesContainer.appendChild(star);
778
+ }
779
+ }
780
 
781
+ // --- Modal & Form Logic ---
782
  const openModal = () => elements.modal.classList.add('active');
783
  const closeModal = () => {
784
  elements.modal.classList.remove('active');
 
787
 
788
  const addExam = (event) => {
789
  event.preventDefault();
790
+ const examIcons = ['๐Ÿ“š', '๐Ÿ“–', 'โœ๏ธ', '๐Ÿ“', '๐ŸŽ“', '๐Ÿ“Š', '๐Ÿงฎ', '๐Ÿ”ฌ', '๐Ÿ’ป', '๐ŸŽจ'];
791
  const newExam = {
792
  name: elements.examNameInput.value,
793
+ date: new Date(elements.examDateInput.value).getTime(),
794
+ icon: examIcons[Math.floor(Math.random() * examIcons.length)]
795
  };
796
  push(examsRef, newExam).catch(err => console.error("Failed to add exam:", err));
797
  closeModal();
 
802
  remove(ref(database, `exams/${id}`)).catch(err => console.error("Failed to delete exam:", err));
803
  };
804
 
805
+ // --- Rendering Logic ---
806
+ function getCountdown(targetDate) {
807
+ const now = new Date().getTime();
808
+ const distance = targetDate - now;
809
+
810
+ if (distance < 0) {
811
+ return { days: 0, hours: 0, minutes: 0, seconds: 0, isPast: true };
812
+ }
813
+
814
+ return {
815
+ days: Math.floor(distance / (1000 * 60 * 60 * 24)),
816
+ hours: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
817
+ minutes: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
818
+ seconds: Math.floor((distance % (1000 * 60)) / 1000),
819
+ isPast: false
820
+ };
821
+ }
822
 
823
+ function renderExams() {
824
+ if (exams.length === 0) {
825
+ elements.examsGrid.innerHTML = '';
826
+ elements.emptyState.style.display = 'block';
827
+ return;
828
+ }
829
 
830
+ elements.emptyState.style.display = 'none';
831
+ // Sort by date so the soonest is first
832
+ const sortedExams = [...exams].sort((a, b) => a.date - b.date);
833
 
834
+ elements.examsGrid.innerHTML = sortedExams.map(exam => {
835
+ const countdown = getCountdown(exam.date);
836
+ const examDate = new Date(exam.date);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
 
838
+ let content;
839
+ if(countdown.isPast){
840
+ content = `<div style="font-size: 2rem; color: var(--neon-pink); font-family: 'Orbitron', monospace;">EXAM COMPLETE</div>`;
841
+ } else {
842
+ content = `
843
+ <div class="countdown-wrapper">
844
+ <div class="countdown-item">
845
+ <div class="countdown-number">${String(countdown.days).padStart(2, '0')}</div>
846
+ <div class="countdown-label">Days</div>
847
+ </div>
848
+ <div class="countdown-item">
849
+ <div class="countdown-number">${String(countdown.hours).padStart(2, '0')}</div>
850
+ <div class="countdown-label">Hours</div>
851
+ </div>
852
+ <div class="countdown-item">
853
+ <div class="countdown-number">${String(countdown.minutes).padStart(2, '0')}</div>
854
+ <div class="countdown-label">Minutes</div>
855
+ </div>
856
+ <div class="countdown-item">
857
+ <div class="countdown-number">${String(countdown.seconds).padStart(2, '0')}</div>
858
+ <div class="countdown-label">Seconds</div>
859
+ </div>
860
+ </div>`;
861
+ }
862
+
863
+ return `
864
+ <div class="exam-card" data-id="${exam.id}">
865
+ <div class="delete-btn" data-delete-id="${exam.id}">
866
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="#fff" style="pointer-events: none;">
867
+ <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"/>
868
+ </svg>
869
+ </div>
870
+ <div class="exam-name">${exam.icon} ${exam.name}</div>
871
+ ${content}
872
+ <div class="exam-date">
873
+ ๐Ÿ“… ${examDate.toLocaleDateString('en-US', {
874
+ weekday: 'long', month: 'long', day: 'numeric',
875
+ hour: '2-digit', minute: '2-digit'
876
+ })}
877
+ </div>
878
+ </div>
879
+ `;
880
+ }).join('');
881
+ }
882
 
883
+ // --- Event Delegation for Delete Buttons ---
884
+ elements.examsGrid.addEventListener('click', (event) => {
885
+ if(event.target.matches('.delete-btn')) {
886
+ const idToDelete = event.target.dataset.deleteId;
887
+ deleteExam(idToDelete);
 
 
888
  }
889
+ });
890
+
891
+
892
  // --- App Initialization ---
893
  createParticles();
894
+ elements.examDateInput.min = new Date().toISOString().slice(0, 16);
895
 
896
+ // Event Listeners for Modal
897
  elements.openModalBtn.addEventListener('click', openModal);
898
  elements.closeModalBtn.addEventListener('click', closeModal);
899
  elements.addExamForm.addEventListener('submit', addExam);
 
902
  // Firebase real-time data listener
903
  onValue(examsRef, (snapshot) => {
904
  const data = snapshot.val();
905
+ // Convert Firebase object to an array and store it
906
+ exams = data ? Object.entries(data).map(([id, value]) => ({ id, ...value })) : [];
907
+ // Do an initial render as soon as data is received
908
+ renderExams();
909
  }, (error) => {
910
  console.error("Firebase read failed:", error);
911
  alert("Could not connect to the database. Check console for errors and verify database rules.");
912
  });
913
+
914
+ // Set an interval to update the countdowns every second
915
+ // This makes the timers tick without re-fetching from Firebase
916
+ setInterval(renderExams, 1000);
917
  </script>
918
  </body>
919
  </html>