SolarumAsteridion commited on
Commit
0e5cf35
·
verified ·
1 Parent(s): 8c03bcc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +304 -22
index.html CHANGED
@@ -4,7 +4,6 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Exam Countdown</title>
7
- <!-- NEW: Updated Google Fonts Link -->
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
@@ -100,7 +99,7 @@
100
  .exams-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 2rem; grid-auto-rows: auto; align-items: start; }
101
 
102
  /* Glassmorphic exam cards */
103
- .exam-card { background: var(--glass-bg); backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%); border: 1px solid var(--glass-border); border-radius: 24px; padding: 2.5rem; text-align: center; transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1); position: relative; overflow: hidden; min-height: 220px; }
104
  .exam-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2) 50%, transparent); opacity: 0; transition: opacity 0.3s ease; }
105
  .exam-card::after { content: ''; position: absolute; inset: 0; background: radial-gradient( 600px circle at var(--mouse-x) var(--mouse-y), rgba(255, 255, 255, 0.06), transparent 40% ); opacity: 0; transition: opacity 0.3s ease; pointer-events: none; }
106
  .exam-card:hover { transform: translateY(-5px); background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.15); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1); }
@@ -128,36 +127,184 @@
128
  .countdown-label { font-size: 0.85rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 2px; font-weight: 500; }
129
  .exam-date { font-size: 0.9rem; color: var(--text-muted); margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(255, 255, 255, 0.05); }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  /* Minimal delete button */
132
- .delete-btn { position: absolute; top: 1.5rem; right: 1.5rem; width: 32px; height: 32px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; opacity: 0; }
133
  .exam-card:hover .delete-btn { opacity: 1; }
134
- .delete-btn:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.2); transform: scale(1.1); }
135
  .delete-btn svg { width: 16px; height: 16px; fill: var(--text-secondary); transition: fill 0.3s ease; pointer-events: none; }
136
- .delete-btn:hover svg { fill: var(--text-primary); }
137
 
138
  /* Glass modal */
139
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 1000; align-items: center; justify-content: center; padding: 2rem; opacity: 0; transition: opacity 0.3s ease; }
140
  .modal.active { display: flex; opacity: 1; }
141
  .modal-content { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 24px; padding: 3rem; width: 100%; max-width: 450px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1); animation: modalSlide 0.4s cubic-bezier(0.23, 1, 0.320, 1); }
 
 
 
 
 
 
 
 
142
  @keyframes modalSlide { from { transform: translateY(-20px) scale(0.95); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
143
  .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
144
  .modal-title { font-family: 'Lexend', sans-serif; font-size: 1.5rem; font-weight: 400; letter-spacing: -0.5px; }
145
- .close-btn { width: 36px; height: 36px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 10px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; }
 
146
  .close-btn:hover { background: rgba(255, 255, 255, 0.08); transform: rotate(90deg); }
147
  .close-btn svg { width: 18px; height: 18px; fill: var(--text-secondary); }
148
 
149
  /* Clean form */
150
  .form-group { margin-bottom: 2rem; }
151
  .form-group label { display: block; margin-bottom: 0.75rem; font-size: 0.9rem; color: var(--text-secondary); font-weight: 500; letter-spacing: 0.5px; }
152
- .form-group input { width: 100%; padding: 1rem 1.25rem; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; color: var(--text-primary); font-size: 1rem; transition: all 0.3s ease; font-family: 'Lexend', sans-serif; }
153
- .form-group input:focus { outline: none; background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.2); box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.02), inset 0 1px 0 rgba(255, 255, 255, 0.1); }
154
- .form-group input::placeholder { color: var(--text-muted); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  /* Sleek add button */
157
  .add-btn { width: 100%; padding: 1.25rem; background: rgba(255, 255, 255, 0.95); border: none; border-radius: 12px; color: #000; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); position: relative; overflow: hidden; letter-spacing: 0.5px; }
158
  .add-btn:hover { transform: translateY(-2px); background: white; box-shadow: 0 10px 30px rgba(255, 255, 255, 0.2), 0 1px 0 rgba(255, 255, 255, 0.2); }
159
  .add-btn:active { transform: translateY(0); }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  /* Elegant empty state */
162
  .empty-state { text-align: center; padding: 6rem 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; }
163
  .empty-icon { width: 80px; height: 80px; margin-bottom: 2rem; background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border); border-radius: 20px; display: flex; align-items: center; justify-content: center; font-size: 2rem; }
@@ -176,7 +323,8 @@
176
  h1 { font-size: 2rem; }
177
  .countdown { font-size: 3.5rem; }
178
  .exams-grid { grid-template-columns: 1fr; }
179
- .container { padding: 2rem 1rem; }
 
180
  }
181
  </style>
182
  </head>
@@ -201,6 +349,7 @@
201
  </div>
202
  </div>
203
 
 
204
  <div class="modal" id="modal">
205
  <div class="modal-content">
206
  <div class="modal-header">
@@ -226,11 +375,70 @@
226
  </div>
227
  </div>
228
 
229
- <!-- The Javascript below is unchanged and remains fully functional -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  <script type="module">
231
  // Firebase SDK modules
232
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
233
- import { getDatabase, ref, onValue, push, remove } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js";
234
 
235
  // Your web app's Firebase configuration
236
  const firebaseConfig = {
@@ -248,6 +456,9 @@
248
  const database = getDatabase(app);
249
  const examsRef = ref(database, 'exams');
250
 
 
 
 
251
  // DOM Elements
252
  const elements = {
253
  modal: document.getElementById('modal'),
@@ -258,7 +469,16 @@
258
  examDateInput: document.getElementById('examDate'),
259
  examsGrid: document.getElementById('examsGrid'),
260
  emptyState: document.getElementById('emptyState'),
261
- particlesContainer: document.getElementById('particles')
 
 
 
 
 
 
 
 
 
262
  };
263
 
264
  // --- Core Functions ---
@@ -268,12 +488,52 @@
268
  elements.addExamForm.reset();
269
  };
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  const addExam = (event) => {
272
  event.preventDefault();
273
- // Save the date as a numeric timestamp for consistency and accuracy
274
  const newExam = {
275
  name: elements.examNameInput.value,
276
- date: new Date(elements.examDateInput.value).getTime()
 
277
  };
278
  push(examsRef, newExam);
279
  closeModal();
@@ -284,9 +544,8 @@
284
  // --- Rendering Logic ---
285
  const calculateCountdown = (examDate) => {
286
  const now = new Date();
287
- now.setHours(0, 0, 0, 0); // Set to midnight for accurate day comparison
288
 
289
- // This constructor handles both numbers (timestamps) and "YYYY-MM-DD" strings
290
  const targetDate = new Date(examDate);
291
  targetDate.setHours(0, 0, 0, 0);
292
 
@@ -300,32 +559,40 @@
300
  };
301
 
302
  const renderExams = (exams) => {
 
303
  const hasExams = exams && exams.length > 0;
304
  elements.examsGrid.style.display = hasExams ? 'grid' : 'none';
305
  elements.emptyState.style.display = hasExams ? 'none' : 'flex';
306
 
307
  if (!hasExams) return;
308
 
309
- // Sort by date so the soonest exam is first
310
  exams.sort((a, b) => new Date(a.date) - new Date(b.date));
311
 
312
  elements.examsGrid.innerHTML = exams.map(exam => {
313
  const countdown = calculateCountdown(exam.date);
314
- const dateObject = new Date(exam.date); // Works for both old and new data
 
315
 
316
  return `
317
- <div class="exam-card">
318
  <button class="delete-btn" data-delete-id="${exam.id}">
319
  <svg viewBox="0 0 24 24">
320
  <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"/>
321
  </svg>
322
  </button>
 
 
 
 
 
 
323
  <div class="exam-name">${exam.name}</div>
324
  <div class="countdown">${countdown.days}</div>
325
  <div class="countdown-label">${countdown.label}</div>
326
  <div class="exam-date">${dateObject.toLocaleDateString('en-US', {
327
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
328
  })}</div>
 
329
  </div>`;
330
  }).join('');
331
  };
@@ -366,13 +633,28 @@
366
  elements.openModalBtn.addEventListener('click', openModal);
367
  elements.closeModalBtn.addEventListener('click', closeModal);
368
  elements.addExamForm.addEventListener('submit', addExam);
369
- window.addEventListener('click', e => { if (e.target === elements.modal) closeModal(); });
 
 
 
 
 
 
 
370
 
371
- // Delete button listener (using event delegation)
372
  elements.examsGrid.addEventListener('click', (event) => {
373
  const deleteBtn = event.target.closest('.delete-btn');
374
  if (deleteBtn) {
 
375
  deleteExam(deleteBtn.dataset.deleteId);
 
 
 
 
 
 
 
376
  }
377
  });
378
 
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Exam Countdown</title>
 
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
  <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
 
99
  .exams-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 2rem; grid-auto-rows: auto; align-items: start; }
100
 
101
  /* Glassmorphic exam cards */
102
+ .exam-card { background: var(--glass-bg); backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%); border: 1px solid var(--glass-border); border-radius: 24px; padding: 2.5rem; text-align: center; transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1); position: relative; overflow: hidden; min-height: 220px; cursor: pointer; }
103
  .exam-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2) 50%, transparent); opacity: 0; transition: opacity 0.3s ease; }
104
  .exam-card::after { content: ''; position: absolute; inset: 0; background: radial-gradient( 600px circle at var(--mouse-x) var(--mouse-y), rgba(255, 255, 255, 0.06), transparent 40% ); opacity: 0; transition: opacity 0.3s ease; pointer-events: none; }
105
  .exam-card:hover { transform: translateY(-5px); background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.15); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1); }
 
127
  .countdown-label { font-size: 0.85rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 2px; font-weight: 500; }
128
  .exam-date { font-size: 0.9rem; color: var(--text-muted); margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(255, 255, 255, 0.05); }
129
 
130
+ /* Syllabus indicator badge */
131
+ .syllabus-badge {
132
+ position: absolute;
133
+ top: 1.5rem;
134
+ left: 1.5rem;
135
+ padding: 0.4rem 0.8rem;
136
+ background: rgba(255, 255, 255, 0.08);
137
+ border: 1px solid rgba(255, 255, 255, 0.1);
138
+ border-radius: 8px;
139
+ font-size: 0.7rem;
140
+ color: var(--text-secondary);
141
+ text-transform: uppercase;
142
+ letter-spacing: 1px;
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 0.4rem;
146
+ opacity: 0;
147
+ transition: opacity 0.3s ease;
148
+ }
149
+ .syllabus-badge.has-syllabus {
150
+ opacity: 1;
151
+ background: rgba(100, 200, 100, 0.15);
152
+ border-color: rgba(100, 200, 100, 0.3);
153
+ color: rgba(150, 255, 150, 0.9);
154
+ }
155
+ .exam-card:hover .syllabus-badge {
156
+ opacity: 1;
157
+ }
158
+ .syllabus-badge svg {
159
+ width: 12px;
160
+ height: 12px;
161
+ fill: currentColor;
162
+ }
163
+
164
+ /* Click hint */
165
+ .click-hint {
166
+ position: absolute;
167
+ bottom: 1rem;
168
+ left: 50%;
169
+ transform: translateX(-50%);
170
+ font-size: 0.7rem;
171
+ color: var(--text-muted);
172
+ opacity: 0;
173
+ transition: opacity 0.3s ease;
174
+ text-transform: uppercase;
175
+ letter-spacing: 1px;
176
+ }
177
+ .exam-card:hover .click-hint {
178
+ opacity: 0.6;
179
+ }
180
+
181
  /* Minimal delete button */
182
+ .delete-btn { position: absolute; top: 1.5rem; right: 1.5rem; width: 32px; height: 32px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; opacity: 0; z-index: 10; }
183
  .exam-card:hover .delete-btn { opacity: 1; }
184
+ .delete-btn:hover { background: rgba(255, 100, 100, 0.15); border-color: rgba(255, 100, 100, 0.3); transform: scale(1.1); }
185
  .delete-btn svg { width: 16px; height: 16px; fill: var(--text-secondary); transition: fill 0.3s ease; pointer-events: none; }
186
+ .delete-btn:hover svg { fill: rgba(255, 150, 150, 1); }
187
 
188
  /* Glass modal */
189
  .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 1000; align-items: center; justify-content: center; padding: 2rem; opacity: 0; transition: opacity 0.3s ease; }
190
  .modal.active { display: flex; opacity: 1; }
191
  .modal-content { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 24px; padding: 3rem; width: 100%; max-width: 450px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1); animation: modalSlide 0.4s cubic-bezier(0.23, 1, 0.320, 1); }
192
+
193
+ /* Syllabus modal is wider */
194
+ .modal-content.syllabus-modal-content {
195
+ max-width: 650px;
196
+ max-height: 85vh;
197
+ overflow-y: auto;
198
+ }
199
+
200
  @keyframes modalSlide { from { transform: translateY(-20px) scale(0.95); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
201
  .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
202
  .modal-title { font-family: 'Lexend', sans-serif; font-size: 1.5rem; font-weight: 400; letter-spacing: -0.5px; }
203
+ .modal-subtitle { font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem; }
204
+ .close-btn { width: 36px; height: 36px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 10px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; flex-shrink: 0; }
205
  .close-btn:hover { background: rgba(255, 255, 255, 0.08); transform: rotate(90deg); }
206
  .close-btn svg { width: 18px; height: 18px; fill: var(--text-secondary); }
207
 
208
  /* Clean form */
209
  .form-group { margin-bottom: 2rem; }
210
  .form-group label { display: block; margin-bottom: 0.75rem; font-size: 0.9rem; color: var(--text-secondary); font-weight: 500; letter-spacing: 0.5px; }
211
+ .form-group input,
212
+ .form-group textarea {
213
+ width: 100%;
214
+ padding: 1rem 1.25rem;
215
+ background: rgba(255, 255, 255, 0.03);
216
+ border: 1px solid rgba(255, 255, 255, 0.08);
217
+ border-radius: 12px;
218
+ color: var(--text-primary);
219
+ font-size: 1rem;
220
+ transition: all 0.3s ease;
221
+ font-family: 'Lexend', sans-serif;
222
+ }
223
+ .form-group textarea {
224
+ resize: vertical;
225
+ min-height: 200px;
226
+ line-height: 1.7;
227
+ }
228
+ .form-group input:focus,
229
+ .form-group textarea:focus {
230
+ outline: none;
231
+ background: rgba(255, 255, 255, 0.05);
232
+ border-color: rgba(255, 255, 255, 0.2);
233
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.02), inset 0 1px 0 rgba(255, 255, 255, 0.1);
234
+ }
235
+ .form-group input::placeholder,
236
+ .form-group textarea::placeholder {
237
+ color: var(--text-muted);
238
+ }
239
+
240
+ /* Syllabus tips */
241
+ .syllabus-tips {
242
+ background: rgba(255, 255, 255, 0.02);
243
+ border: 1px solid rgba(255, 255, 255, 0.05);
244
+ border-radius: 12px;
245
+ padding: 1.25rem;
246
+ margin-bottom: 2rem;
247
+ }
248
+ .syllabus-tips-title {
249
+ font-size: 0.8rem;
250
+ color: var(--text-secondary);
251
+ text-transform: uppercase;
252
+ letter-spacing: 1px;
253
+ margin-bottom: 0.75rem;
254
+ display: flex;
255
+ align-items: center;
256
+ gap: 0.5rem;
257
+ }
258
+ .syllabus-tips-title svg {
259
+ width: 14px;
260
+ height: 14px;
261
+ fill: var(--text-secondary);
262
+ }
263
+ .syllabus-tips ul {
264
+ list-style: none;
265
+ font-size: 0.85rem;
266
+ color: var(--text-muted);
267
+ line-height: 1.8;
268
+ }
269
+ .syllabus-tips li::before {
270
+ content: '→';
271
+ margin-right: 0.5rem;
272
+ color: var(--text-secondary);
273
+ }
274
 
275
  /* Sleek add button */
276
  .add-btn { width: 100%; padding: 1.25rem; background: rgba(255, 255, 255, 0.95); border: none; border-radius: 12px; color: #000; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); position: relative; overflow: hidden; letter-spacing: 0.5px; }
277
  .add-btn:hover { transform: translateY(-2px); background: white; box-shadow: 0 10px 30px rgba(255, 255, 255, 0.2), 0 1px 0 rgba(255, 255, 255, 0.2); }
278
  .add-btn:active { transform: translateY(0); }
279
 
280
+ /* Saved indicator */
281
+ .saved-indicator {
282
+ display: none;
283
+ align-items: center;
284
+ justify-content: center;
285
+ gap: 0.5rem;
286
+ padding: 1rem;
287
+ background: rgba(100, 200, 100, 0.1);
288
+ border: 1px solid rgba(100, 200, 100, 0.2);
289
+ border-radius: 12px;
290
+ color: rgba(150, 255, 150, 0.9);
291
+ font-size: 0.9rem;
292
+ margin-top: 1rem;
293
+ animation: fadeIn 0.3s ease;
294
+ }
295
+ .saved-indicator.show {
296
+ display: flex;
297
+ }
298
+ .saved-indicator svg {
299
+ width: 18px;
300
+ height: 18px;
301
+ fill: currentColor;
302
+ }
303
+ @keyframes fadeIn {
304
+ from { opacity: 0; transform: translateY(-10px); }
305
+ to { opacity: 1; transform: translateY(0); }
306
+ }
307
+
308
  /* Elegant empty state */
309
  .empty-state { text-align: center; padding: 6rem 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; }
310
  .empty-icon { width: 80px; height: 80px; margin-bottom: 2rem; background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border); border-radius: 20px; display: flex; align-items: center; justify-content: center; font-size: 2rem; }
 
323
  h1 { font-size: 2rem; }
324
  .countdown { font-size: 3.5rem; }
325
  .exams-grid { grid-template-columns: 1fr; }
326
+ .container { padding: 2rem 1rem; }
327
+ .modal-content.syllabus-modal-content { max-width: 100%; }
328
  }
329
  </style>
330
  </head>
 
349
  </div>
350
  </div>
351
 
352
+ <!-- Add Exam Modal -->
353
  <div class="modal" id="modal">
354
  <div class="modal-content">
355
  <div class="modal-header">
 
375
  </div>
376
  </div>
377
 
378
+ <!-- Syllabus Modal -->
379
+ <div class="modal" id="syllabusModal">
380
+ <div class="modal-content syllabus-modal-content">
381
+ <div class="modal-header">
382
+ <div>
383
+ <h2 class="modal-title" id="syllabusExamName">Exam Syllabus</h2>
384
+ <p class="modal-subtitle" id="syllabusExamDate"></p>
385
+ </div>
386
+ <button class="close-btn" id="closeSyllabusModalBtn">
387
+ <svg viewBox="0 0 24 24">
388
+ <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"/>
389
+ </svg>
390
+ </button>
391
+ </div>
392
+
393
+ <div class="syllabus-tips">
394
+ <div class="syllabus-tips-title">
395
+ <svg viewBox="0 0 24 24">
396
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
397
+ </svg>
398
+ Tips for your syllabus
399
+ </div>
400
+ <ul>
401
+ <li>List all chapters and topics</li>
402
+ <li>Add important formulas or concepts</li>
403
+ <li>Include page numbers from textbooks</li>
404
+ <li>Mark high-priority topics with ★</li>
405
+ </ul>
406
+ </div>
407
+
408
+ <form id="syllabusForm">
409
+ <div class="form-group">
410
+ <label for="syllabusContent">Syllabus / Topics to Study</label>
411
+ <textarea id="syllabusContent" rows="12" placeholder="Enter your syllabus here...
412
+
413
+ Example:
414
+ Chapter 1: Introduction to Calculus
415
+ - Limits and Continuity (pg 1-25)
416
+ - Derivatives (pg 26-50) ★
417
+
418
+ Chapter 2: Integration
419
+ - Definite Integrals (pg 51-75)
420
+ - Applications (pg 76-100)
421
+
422
+ Important Formulas:
423
+ - d/dx(x^n) = nx^(n-1)
424
+ - ∫x^n dx = x^(n+1)/(n+1) + C"></textarea>
425
+ </div>
426
+ <input type="hidden" id="syllabusExamId">
427
+ <button type="submit" class="add-btn">Save Syllabus</button>
428
+ <div class="saved-indicator" id="savedIndicator">
429
+ <svg viewBox="0 0 24 24">
430
+ <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
431
+ </svg>
432
+ Syllabus saved successfully!
433
+ </div>
434
+ </form>
435
+ </div>
436
+ </div>
437
+
438
  <script type="module">
439
  // Firebase SDK modules
440
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
441
+ import { getDatabase, ref, onValue, push, remove, update } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js";
442
 
443
  // Your web app's Firebase configuration
444
  const firebaseConfig = {
 
456
  const database = getDatabase(app);
457
  const examsRef = ref(database, 'exams');
458
 
459
+ // Store exams data globally for easy access
460
+ let examsData = [];
461
+
462
  // DOM Elements
463
  const elements = {
464
  modal: document.getElementById('modal'),
 
469
  examDateInput: document.getElementById('examDate'),
470
  examsGrid: document.getElementById('examsGrid'),
471
  emptyState: document.getElementById('emptyState'),
472
+ particlesContainer: document.getElementById('particles'),
473
+ // Syllabus modal elements
474
+ syllabusModal: document.getElementById('syllabusModal'),
475
+ closeSyllabusModalBtn: document.getElementById('closeSyllabusModalBtn'),
476
+ syllabusForm: document.getElementById('syllabusForm'),
477
+ syllabusExamName: document.getElementById('syllabusExamName'),
478
+ syllabusExamDate: document.getElementById('syllabusExamDate'),
479
+ syllabusContent: document.getElementById('syllabusContent'),
480
+ syllabusExamId: document.getElementById('syllabusExamId'),
481
+ savedIndicator: document.getElementById('savedIndicator')
482
  };
483
 
484
  // --- Core Functions ---
 
488
  elements.addExamForm.reset();
489
  };
490
 
491
+ const openSyllabusModal = (examId) => {
492
+ const exam = examsData.find(e => e.id === examId);
493
+ if (!exam) return;
494
+
495
+ elements.syllabusExamName.textContent = exam.name;
496
+ const dateObject = new Date(exam.date);
497
+ elements.syllabusExamDate.textContent = dateObject.toLocaleDateString('en-US', {
498
+ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
499
+ });
500
+ elements.syllabusContent.value = exam.syllabus || '';
501
+ elements.syllabusExamId.value = examId;
502
+ elements.savedIndicator.classList.remove('show');
503
+ elements.syllabusModal.classList.add('active');
504
+ };
505
+
506
+ const closeSyllabusModal = () => {
507
+ elements.syllabusModal.classList.remove('active');
508
+ elements.savedIndicator.classList.remove('show');
509
+ };
510
+
511
+ const saveSyllabus = (event) => {
512
+ event.preventDefault();
513
+ const examId = elements.syllabusExamId.value;
514
+ const syllabusContent = elements.syllabusContent.value;
515
+
516
+ // Update the syllabus in Firebase
517
+ const examRef = ref(database, `exams/${examId}`);
518
+ update(examRef, { syllabus: syllabusContent })
519
+ .then(() => {
520
+ elements.savedIndicator.classList.add('show');
521
+ setTimeout(() => {
522
+ elements.savedIndicator.classList.remove('show');
523
+ }, 3000);
524
+ })
525
+ .catch((error) => {
526
+ console.error("Error saving syllabus:", error);
527
+ alert("Failed to save syllabus. Please try again.");
528
+ });
529
+ };
530
+
531
  const addExam = (event) => {
532
  event.preventDefault();
 
533
  const newExam = {
534
  name: elements.examNameInput.value,
535
+ date: new Date(elements.examDateInput.value).getTime(),
536
+ syllabus: ''
537
  };
538
  push(examsRef, newExam);
539
  closeModal();
 
544
  // --- Rendering Logic ---
545
  const calculateCountdown = (examDate) => {
546
  const now = new Date();
547
+ now.setHours(0, 0, 0, 0);
548
 
 
549
  const targetDate = new Date(examDate);
550
  targetDate.setHours(0, 0, 0, 0);
551
 
 
559
  };
560
 
561
  const renderExams = (exams) => {
562
+ examsData = exams; // Store for later access
563
  const hasExams = exams && exams.length > 0;
564
  elements.examsGrid.style.display = hasExams ? 'grid' : 'none';
565
  elements.emptyState.style.display = hasExams ? 'none' : 'flex';
566
 
567
  if (!hasExams) return;
568
 
 
569
  exams.sort((a, b) => new Date(a.date) - new Date(b.date));
570
 
571
  elements.examsGrid.innerHTML = exams.map(exam => {
572
  const countdown = calculateCountdown(exam.date);
573
+ const dateObject = new Date(exam.date);
574
+ const hasSyllabus = exam.syllabus && exam.syllabus.trim().length > 0;
575
 
576
  return `
577
+ <div class="exam-card" data-exam-id="${exam.id}">
578
  <button class="delete-btn" data-delete-id="${exam.id}">
579
  <svg viewBox="0 0 24 24">
580
  <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"/>
581
  </svg>
582
  </button>
583
+ <div class="syllabus-badge ${hasSyllabus ? 'has-syllabus' : ''}">
584
+ <svg viewBox="0 0 24 24">
585
+ <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
586
+ </svg>
587
+ ${hasSyllabus ? 'Syllabus Added' : 'Add Syllabus'}
588
+ </div>
589
  <div class="exam-name">${exam.name}</div>
590
  <div class="countdown">${countdown.days}</div>
591
  <div class="countdown-label">${countdown.label}</div>
592
  <div class="exam-date">${dateObject.toLocaleDateString('en-US', {
593
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
594
  })}</div>
595
+ <div class="click-hint">Click to ${hasSyllabus ? 'view' : 'add'} syllabus</div>
596
  </div>`;
597
  }).join('');
598
  };
 
633
  elements.openModalBtn.addEventListener('click', openModal);
634
  elements.closeModalBtn.addEventListener('click', closeModal);
635
  elements.addExamForm.addEventListener('submit', addExam);
636
+ window.addEventListener('click', e => {
637
+ if (e.target === elements.modal) closeModal();
638
+ if (e.target === elements.syllabusModal) closeSyllabusModal();
639
+ });
640
+
641
+ // Syllabus Modal Listeners
642
+ elements.closeSyllabusModalBtn.addEventListener('click', closeSyllabusModal);
643
+ elements.syllabusForm.addEventListener('submit', saveSyllabus);
644
 
645
+ // Exam card click listener (using event delegation)
646
  elements.examsGrid.addEventListener('click', (event) => {
647
  const deleteBtn = event.target.closest('.delete-btn');
648
  if (deleteBtn) {
649
+ event.stopPropagation();
650
  deleteExam(deleteBtn.dataset.deleteId);
651
+ return;
652
+ }
653
+
654
+ const examCard = event.target.closest('.exam-card');
655
+ if (examCard) {
656
+ const examId = examCard.dataset.examId;
657
+ openSyllabusModal(examId);
658
  }
659
  });
660