ExamTimer / index.html
SolarumAsteridion's picture
Update index.html
69c0dff verified
raw
history blame
35.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Exam Countdown</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&family=Orbitron:wght@400;600;700;900&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: #000000;
min-height: 100vh;
color: #fff;
overflow-x: hidden;
position: relative;
}
/* Animated Background */
.animated-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
background: radial-gradient(ellipse at top, #1a0a2e 0%, #0a0a0f 50%, #000000 100%);
}
.animated-bg::before {
content: '';
position: absolute;
width: 200%;
height: 200%;
top: -50%;
left: -50%;
background:
radial-gradient(circle at 20% 50%, rgba(0, 212, 255, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255, 0, 128, 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 20%, rgba(0, 255, 136, 0.1) 0%, transparent 50%);
animation: bgRotate 20s linear infinite;
}
@keyframes bgRotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Enhanced Particles */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1;
pointer-events: none;
}
.particle {
position: absolute;
border-radius: 50%;
animation: float linear infinite;
filter: blur(1px);
}
.particle:nth-child(3n) {
background: radial-gradient(circle, rgba(0, 212, 255, 0.8) 0%, transparent 70%);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}
.particle:nth-child(3n+1) {
background: radial-gradient(circle, rgba(255, 0, 128, 0.8) 0%, transparent 70%);
box-shadow: 0 0 10px rgba(255, 0, 128, 0.5);
}
.particle:nth-child(3n+2) {
background: radial-gradient(circle, rgba(0, 255, 136, 0.8) 0%, transparent 70%);
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
@keyframes float {
from {
transform: translateY(100vh) translateX(0) scale(0);
opacity: 0;
}
10% {
opacity: 1;
transform: translateY(90vh) translateX(20px) scale(1);
}
90% {
opacity: 1;
}
to {
transform: translateY(-10vh) translateX(100px) scale(0);
opacity: 0;
}
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 3rem 2rem;
position: relative;
z-index: 2;
}
/* Enhanced Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4rem;
padding: 2rem;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px);
border-radius: 25px;
border: 1px solid rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
animation: shimmer 3s infinite;
}
@keyframes shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
h1 {
font-family: 'Orbitron', sans-serif;
font-size: 3.5rem;
font-weight: 900;
background: linear-gradient(135deg, #00D4FF 0%, #FF0080 25%, #7928FF 50%, #00FF88 75%, #00D4FF 100%);
background-size: 300% 100%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: gradientShift 5s ease infinite;
filter: drop-shadow(0 0 30px rgba(0, 212, 255, 0.5));
position: relative;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.settings-btn {
width: 60px;
height: 60px;
background: linear-gradient(135deg, rgba(0, 212, 255, 0.2) 0%, rgba(255, 0, 128, 0.2) 100%);
backdrop-filter: blur(10px);
border: 2px solid rgba(0, 212, 255, 0.5);
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}
.settings-btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: radial-gradient(circle, rgba(0, 212, 255, 0.4) 0%, transparent 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.settings-btn:hover::before {
width: 200px;
height: 200px;
}
.settings-btn:hover {
background: linear-gradient(135deg, rgba(0, 212, 255, 0.4) 0%, rgba(255, 0, 128, 0.4) 100%);
border-color: #00D4FF;
box-shadow: 0 0 40px rgba(0, 212, 255, 0.8), 0 0 80px rgba(255, 0, 128, 0.4);
transform: scale(1.15) rotate(90deg);
}
.settings-btn svg {
width: 28px;
height: 28px;
fill: #00D4FF;
filter: drop-shadow(0 0 5px rgba(0, 212, 255, 0.8));
position: relative;
z-index: 1;
}
/* Enhanced Grid */
.exams-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 2.5rem;
}
/* Enhanced Exam Card */
.exam-card {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(30px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 30px;
padding: 2.5rem;
text-align: center;
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.exam-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(0, 212, 255, 0.1) 0%, rgba(255, 0, 128, 0.1) 100%);
opacity: 0;
transition: opacity 0.5s;
}
.exam-card:hover::before {
opacity: 1;
}
.exam-card::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(
transparent,
rgba(0, 212, 255, 0.3),
transparent 30%
);
animation: rotate 4s linear infinite;
opacity: 0;
}
.exam-card:hover::after {
opacity: 1;
}
@keyframes rotate {
100% { transform: rotate(360deg); }
}
.exam-card-inner {
position: relative;
z-index: 1;
background: rgba(10, 10, 15, 0.8);
border-radius: 25px;
padding: 2rem;
}
.exam-card:hover {
transform: translateY(-10px) scale(1.02);
border-color: rgba(0, 212, 255, 0.5);
box-shadow:
0 20px 60px rgba(0, 212, 255, 0.4),
0 0 100px rgba(255, 0, 128, 0.2),
inset 0 0 50px rgba(0, 212, 255, 0.1);
}
.exam-card.urgent {
border-color: rgba(255, 0, 128, 0.5);
}
.exam-card.urgent:hover {
box-shadow:
0 20px 60px rgba(255, 0, 128, 0.4),
0 0 100px rgba(255, 0, 128, 0.3);
}
.exam-icon {
width: 80px;
height: 80px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, #00D4FF 0%, #FF0080 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
animation: pulse 2s ease-in-out infinite;
box-shadow: 0 10px 30px rgba(0, 212, 255, 0.4);
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.exam-name {
font-family: 'Orbitron', sans-serif;
font-size: 1.8rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: #fff;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
letter-spacing: 1px;
}
/* Detailed Countdown */
.countdown-wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin: 2rem 0;
}
.countdown-item {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 1rem 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s;
}
.countdown-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-3px);
}
.countdown-number {
font-family: 'Orbitron', sans-serif;
font-size: 2.5rem;
font-weight: 900;
background: linear-gradient(135deg, #00D4FF 0%, #00FF88 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 0 30px rgba(0, 212, 255, 0.8);
line-height: 1;
margin-bottom: 0.5rem;
}
.exam-card.urgent .countdown-number {
background: linear-gradient(135deg, #FF0080 0%, #FF6B35 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.countdown-label {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.6);
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
/* Progress Bar */
.progress-container {
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
margin: 1.5rem 0;
position: relative;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #00D4FF 0%, #FF0080 50%, #00FF88 100%);
background-size: 200% 100%;
border-radius: 10px;
transition: width 1s ease;
animation: progressShimmer 2s linear infinite;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.6);
}
@keyframes progressShimmer {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
.exam-date {
font-size: 1rem;
color: rgba(255, 255, 255, 0.6);
margin-top: 1.5rem;
padding: 0.8rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
font-weight: 600;
}
.delete-btn {
position: absolute;
top: 1.5rem;
right: 1.5rem;
width: 40px;
height: 40px;
background: rgba(255, 0, 80, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 0, 80, 0.5);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
opacity: 0;
z-index: 10;
}
.exam-card:hover .delete-btn {
opacity: 1;
}
.delete-btn:hover {
background: rgba(255, 0, 80, 0.6);
transform: scale(1.2) rotate(90deg);
box-shadow: 0 0 20px rgba(255, 0, 80, 0.8);
}
.delete-btn svg {
width: 20px;
height: 20px;
fill: #fff;
}
/* Enhanced Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
z-index: 1000;
align-items: center;
justify-content: center;
padding: 2rem;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal.active {
display: flex;
}
.modal-content {
background: linear-gradient(135deg, rgba(20, 20, 30, 0.95) 0%, rgba(10, 10, 20, 0.95) 100%);
backdrop-filter: blur(30px);
border: 2px solid rgba(0, 212, 255, 0.3);
border-radius: 30px;
padding: 3rem;
width: 100%;
max-width: 500px;
box-shadow:
0 30px 60px rgba(0, 0, 0, 0.8),
0 0 100px rgba(0, 212, 255, 0.3),
inset 0 0 50px rgba(0, 212, 255, 0.05);
animation: modalSlideIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}
.modal-content::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(
transparent,
rgba(0, 212, 255, 0.1),
transparent 30%
);
animation: rotate 4s linear infinite;
}
@keyframes modalSlideIn {
from {
transform: translateY(-100px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2.5rem;
position: relative;
z-index: 1;
}
.modal-title {
font-family: 'Orbitron', sans-serif;
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, #00D4FF 0%, #FF0080 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.close-btn {
width: 40px;
height: 40px;
background: rgba(255, 0, 80, 0.2);
border: 1px solid rgba(255, 0, 80, 0.5);
border-radius: 12px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.close-btn:hover {
transform: rotate(90deg) scale(1.1);
background: rgba(255, 0, 80, 0.4);
box-shadow: 0 0 20px rgba(255, 0, 80, 0.6);
}
/* Enhanced Form */
#addExamForm {
position: relative;
z-index: 1;
}
.form-group {
margin-bottom: 2rem;
position: relative;
}
.form-group label {
display: block;
margin-bottom: 0.8rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.form-group input {
width: 100%;
padding: 1.2rem 1.5rem;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 15px;
color: #fff;
font-size: 1.1rem;
font-family: 'Inter', sans-serif;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.form-group input:focus {
outline: none;
border-color: #00D4FF;
background: rgba(255, 255, 255, 0.08);
box-shadow:
0 0 0 4px rgba(0, 212, 255, 0.1),
0 0 30px rgba(0, 212, 255, 0.3);
transform: translateY(-2px);
}
.form-group input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.add-btn {
width: 100%;
padding: 1.3rem;
background: linear-gradient(135deg, #00D4FF 0%, #FF0080 50%, #00FF88 100%);
background-size: 200% 100%;
border: none;
border-radius: 15px;
color: #fff;
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
transition: all 0.4s ease;
text-transform: uppercase;
letter-spacing: 2px;
font-family: 'Orbitron', sans-serif;
position: relative;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 212, 255, 0.4);
}
.add-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
.add-btn:hover::before {
left: 100%;
}
.add-btn:hover {
transform: translateY(-3px);
box-shadow:
0 15px 40px rgba(0, 212, 255, 0.6),
0 0 60px rgba(255, 0, 128, 0.4);
background-position: 100% 0;
}
.add-btn:active {
transform: translateY(-1px);
}
/* Empty State */
.empty-state {
text-align: center;
padding: 6rem 2rem;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px);
border-radius: 30px;
border: 2px dashed rgba(255, 255, 255, 0.2);
}
.empty-state-icon {
font-size: 5rem;
margin-bottom: 2rem;
animation: float-icon 3s ease-in-out infinite;
}
@keyframes float-icon {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.empty-state p {
font-size: 1.3rem;
color: rgba(255, 255, 255, 0.5);
line-height: 1.8;
}
/* Responsive */
@media (max-width: 768px) {
h1 { font-size: 2.5rem; }
.exams-grid { grid-template-columns: 1fr; }
.countdown-wrapper { grid-template-columns: repeat(2, 1fr); }
.countdown-number { font-size: 2rem; }
.header { padding: 1.5rem; }
}
/* Color Input */
input[type="color"] {
width: 100%;
height: 60px;
border: none;
border-radius: 15px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="particles" id="particles"></div>
<div class="container">
<div class="header"><h1>Exam Countdown</h1><div class="settings-btn" id="openModalBtn"><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></div></div>
<div class="exams-grid" id="examsGrid"></div>
<div class="empty-state" id="emptyState" style="display: none;"><p>No exams scheduled yet. Click the + button to add your first exam!</p></div>
</div>
<div class="modal" id="modal">
<div class="modal-content">
<div class="modal-header"><h2 class="modal-title">Add New Exam</h2><button class="close-btn" id="closeModalBtn"><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></button></div>
<form id="addExamForm">
<div class="form-group"><label for="examName">Exam Name</label><input type="text" id="examName" required placeholder="e.g., Physics Final"></div>
<div class="form-group"><label for="examDate">Exam Date</label><input type="date" id="examDate" required></div>
<button type="submit" class="add-btn">Add Exam</button>
</form>
</div>
</div>
<script>
// Create particles
const particlesContainer = document.getElementById('particles');
for (let i = 0; i < 50; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.width = Math.random() * 6 + 2 + 'px';
particle.style.height = particle.style.width;
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDuration = Math.random() * 15 + 10 + 's';
particle.style.animationDelay = Math.random() * 5 + 's';
particlesContainer.appendChild(particle);
}
// Exam icons
const examIcons = ['📚', '📖', '✏️', '📝', '🎓', '📊', '🧮', '🔬', '💻', '🎨'];
// Modal functionality
const modal = document.getElementById('modal');
const openModalBtn = document.getElementById('openModalBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const addExamForm = document.getElementById('addExamForm');
const examsGrid = document.getElementById('examsGrid');
const emptyState = document.getElementById('emptyState');
openModalBtn.addEventListener('click', () => modal.classList.add('active'));
closeModalBtn.addEventListener('click', () => modal.classList.remove('active'));
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.classList.remove('active');
});
// Load exams from localStorage
let exams = JSON.parse(localStorage.getItem('exams')) || [];
// Add exam
addExamForm.addEventListener('submit', (e) => {
e.preventDefault();
const name = document.getElementById('examName').value;
const date = document.getElementById('examDate').value;
const icon = examIcons[Math.floor(Math.random() * examIcons.length)];
const exam = {
id: Date.now(),
name,
date: new Date(date).getTime(),
icon,
createdAt: Date.now()
};
exams.push(exam);
localStorage.setItem('exams', JSON.stringify(exams));
addExamForm.reset();
modal.classList.remove('active');
renderExams();
});
// Delete exam
function deleteExam(id) {
exams = exams.filter(exam => exam.id !== id);
localStorage.setItem('exams', JSON.stringify(exams));
renderExams();
}
// Calculate countdown
function getCountdown(targetDate) {
const now = new Date().getTime();
const distance = targetDate - now;
if (distance < 0) {
return { days: 0, hours: 0, minutes: 0, seconds: 0, isPast: true };
}
return {
days: Math.floor(distance / (1000 * 60 * 60 * 24)),
hours: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((distance % (1000 * 60)) / 1000),
isPast: false
};
}
// Calculate progress
function getProgress(createdAt, targetDate) {
const now = new Date().getTime();
const total = targetDate - createdAt;
const elapsed = now - createdAt;
return Math.min(100, Math.max(0, (elapsed / total) * 100));
}
// Render exams
function renderExams() {
if (exams.length === 0) {
examsGrid.innerHTML = '';
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
examsGrid.innerHTML = exams.map(exam => {
const countdown = getCountdown(exam.date);
const progress = getProgress(exam.createdAt, exam.date);
const isUrgent = countdown.days < 7 && !countdown.isPast;
const examDate = new Date(exam.date);
return `
<div class="exam-card ${isUrgent ? 'urgent' : ''}" data-id="${exam.id}">
<div class="exam-card-inner">
<div class="delete-btn" onclick="deleteExam(${exam.id})">
<svg width="20" height="20" 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>
</div>
<div class="exam-icon">${exam.icon}</div>
<div class="exam-name">${exam.name}</div>
<div class="countdown-wrapper">
<div class="countdown-item">
<div class="countdown-number">${countdown.days}</div>
<div class="countdown-label">Days</div>
</div>
<div class="countdown-item">
<div class="countdown-number">${countdown.hours}</div>
<div class="countdown-label">Hours</div>
</div>
<div class="countdown-item">
<div class="countdown-number">${countdown.minutes}</div>
<div class="countdown-label">Minutes</div>
</div>
<div class="countdown-item">
<div class="countdown-number">${countdown.seconds}</div>
<div class="countdown-label">Seconds</div>
</div>
</div>
<div class="progress-container">
<div class="progress-bar" style="width: ${progress}%"></div>
</div>
<div class="exam-date">
📅 ${examDate.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</div>
</div>
</div>
`;
}).join('');
}
// Update countdowns every second
setInterval(renderExams, 1000);
// Initial render
renderExams();
</script>
</body>
</html>
<script type="module">
// Firebase SDK modules
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
import { getDatabase, ref, onValue, push, remove } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "AIzaSyCBGYdGdPjYJiKsTMjVYZ9mf9F82ns7g4Q",
authDomain: "pikachu-rxppbp.firebaseapp.com",
databaseURL: "https://pikachu-rxppbp.firebaseio.com",
projectId: "pikachu-rxppbp",
storageBucket: "pikachu-rxppbp.appspot.com",
messagingSenderId: "241970333280",
appId: "1:241970333280:web:704e8930bd591c138d6505"
};
// Initialize Firebase and Database
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);
const examsRef = ref(database, 'exams');
// DOM Element References
const elements = {
openModalBtn: document.getElementById('openModalBtn'),
closeModalBtn: document.getElementById('closeModalBtn'),
modal: document.getElementById('modal'),
addExamForm: document.getElementById('addExamForm'),
examNameInput: document.getElementById('examName'),
examDateInput: document.getElementById('examDate'),
examsGrid: document.getElementById('examsGrid'),
emptyState: document.getElementById('emptyState'),
particlesContainer: document.getElementById('particles')
};
// --- Core Functions ---
const openModal = () => elements.modal.classList.add('active');
const closeModal = () => {
elements.modal.classList.remove('active');
elements.addExamForm.reset();
};
const addExam = (event) => {
event.preventDefault();
const newExam = {
name: elements.examNameInput.value,
date: elements.examDateInput.value
};
push(examsRef, newExam).catch(err => console.error("Failed to add exam:", err));
closeModal();
};
const deleteExam = (id) => {
if (!id) return;
remove(ref(database, `exams/${id}`)).catch(err => console.error("Failed to delete exam:", err));
};
// --- Rendering ---
const calculateDaysUntil = (examDate) => {
const now = new Date();
const todayIST = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Kolkata' }));
todayIST.setHours(0, 0, 0, 0);
const targetDate = new Date(examDate);
// The date from the input is already in the user's local timezone,
// but we treat it as a date in IST for calculation.
const targetDateIST = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate());
return Math.ceil((targetDateIST - todayIST) / 86400000); // 1000*60*60*24
};
const renderExams = (exams) => {
elements.examsGrid.innerHTML = '';
const hasExams = exams && exams.length > 0;
elements.emptyState.style.display = hasExams ? 'none' : 'block';
if (!hasExams) return;
exams.sort((a, b) => new Date(a.date) - new Date(b.date)).forEach(exam => {
const daysUntil = calculateDaysUntil(exam.date);
const card = document.createElement('div');
card.className = 'exam-card';
const color = daysUntil <= 3 ? '#FF0080' : (daysUntil <= 7 ? '#00FF88' : '#00D4FF');
card.innerHTML = `
<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>
<h3 class="exam-name">${exam.name}</h3>
<div class="countdown" style="color: ${color};">${daysUntil}</div>
<div class="countdown-label">days remaining</div>
<div class="exam-date">${new Date(exam.date).toLocaleDateString('en-IN', { timeZone: 'Asia/Kolkata', weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</div>`;
card.querySelector('.delete-btn').addEventListener('click', () => deleteExam(exam.id));
elements.examsGrid.appendChild(card);
});
};
// --- Particle Effect ---
const createParticles = () => {
if (elements.particlesContainer.children.length > 0) return;
let particleHtml = '';
for (let i = 0; i < 50; i++) {
const style = `left:${Math.random()*100}%;animation-delay:${Math.random()*20}s;animation-duration:${15+Math.random()*10}s;`;
particleHtml += `<div class="particle" style="${style}"></div>`;
}
elements.particlesContainer.innerHTML = particleHtml;
};
// --- App Initialization ---
createParticles();
elements.examDateInput.min = new Date().toISOString().split('T')[0];
// Event Listeners
elements.openModalBtn.addEventListener('click', openModal);
elements.closeModalBtn.addEventListener('click', closeModal);
elements.addExamForm.addEventListener('submit', addExam);
window.addEventListener('click', e => { if (e.target === elements.modal) closeModal(); });
// Firebase real-time data listener
onValue(examsRef, (snapshot) => {
const data = snapshot.val();
const examList = data ? Object.entries(data).map(([id, value]) => ({ id, ...value })) : [];
renderExams(examList);
}, (error) => {
console.error("Firebase read failed:", error);
alert("Could not connect to the database. Check console for errors and verify database rules.");
});
</script>
</body>
</html>