Spaces:
Running
Running
File size: 13,903 Bytes
89e5f5f 8a0958f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CountdownChronicle</title>
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⏱️</text></svg>">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
undefined: {
500: '#6366f1',
}
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background-color: #f9fafb;
}
.timer-card {
transition: all 0.3s ease;
}
.timer-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.countdown-number {
font-weight: 700;
font-size: 2.5rem;
line-height: 1;
}
@media (max-width: 640px) {
.countdown-number {
font-size: 1.75rem;
}
}
</style>
</head>
<body class="min-h-screen bg-gray-50">
<!-- Header -->
<header class="bg-white shadow-sm">
<div class="container mx-auto px-4 py-6 flex justify-between items-center">
<div class="flex items-center space-x-2">
<div class="bg-undefined-500 p-2 rounded-lg">
<i data-feather="clock" class="text-white"></i>
</div>
<h1 class="text-2xl font-bold text-gray-800">CountdownChronicle</h1>
</div>
<button id="addTimerBtn" class="bg-undefined-500 hover:bg-undefined-600 text-white font-medium py-2 px-4 rounded-lg flex items-center transition duration-300">
<i data-feather="plus" class="mr-1"></i> New Timer
</button>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<div id="timersContainer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Timer cards will be dynamically inserted here -->
</div>
<!-- Empty state -->
<div id="emptyState" class="text-center py-16 hidden">
<div class="bg-gray-200 border-2 border-dashed rounded-xl w-16 h-16 mx-auto mb-4"></div>
<h2 class="text-xl font-semibold text-gray-700 mb-2">No timers yet</h2>
<p class="text-gray-500 mb-6">Create your first countdown timer to get started</p>
<button id="createFirstTimerBtn" class="bg-undefined-500 hover:bg-undefined-600 text-white font-medium py-2 px-4 rounded-lg transition duration-300">
Create Timer
</button>
</div>
</main>
<!-- Modal -->
<div id="timerModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl w-full max-w-md p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">Create New Timer</h3>
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700">
<i data-feather="x"></i>
</button>
</div>
<form id="timerForm">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-medium mb-2" for="timerName">
Event Name
</label>
<input type="text" id="timerName" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-undefined-500" placeholder="e.g. Summer Vacation" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-medium mb-2" for="targetDate">
Target Date
</label>
<input type="datetime-local" id="targetDate" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-undefined-500" required>
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-medium mb-2">
Background Color
</label>
<div class="grid grid-cols-6 gap-2">
<div class="color-option w-8 h-8 rounded-full bg-red-400 cursor-pointer border-2 border-transparent" data-color="bg-red-400"></div>
<div class="color-option w-8 h-8 rounded-full bg-blue-400 cursor-pointer border-2 border-transparent" data-color="bg-blue-400"></div>
<div class="color-option w-8 h-8 rounded-full bg-green-400 cursor-pointer border-2 border-transparent" data-color="bg-green-400"></div>
<div class="color-option w-8 h-8 rounded-full bg-yellow-400 cursor-pointer border-2 border-transparent" data-color="bg-yellow-400"></div>
<div class="color-option w-8 h-8 rounded-full bg-purple-400 cursor-pointer border-2 border-transparent" data-color="bg-purple-400"></div>
<div class="color-option w-8 h-8 rounded-full bg-pink-400 cursor-pointer border-2 border-transparent" data-color="bg-pink-400"></div>
</div>
<input type="hidden" id="selectedColor" value="bg-blue-400">
</div>
<div class="flex justify-end space-x-3">
<button type="button" id="cancelTimerBtn" class="px-4 py-2 text-gray-600 font-medium rounded-lg hover:bg-gray-100 transition duration-300">
Cancel
</button>
<button type="submit" class="bg-undefined-500 hover:bg-undefined-600 text-white font-medium py-2 px-4 rounded-lg transition duration-300">
Create Timer
</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
// Initialize feather icons
feather.replace();
// DOM Elements
const timersContainer = document.getElementById('timersContainer');
const emptyState = document.getElementById('emptyState');
const timerModal = document.getElementById('timerModal');
const timerForm = document.getElementById('timerForm');
const addTimerBtn = document.getElementById('addTimerBtn');
const createFirstTimerBtn = document.getElementById('createFirstTimerBtn');
const closeModalBtn = document.getElementById('closeModalBtn');
const cancelTimerBtn = document.getElementById('cancelTimerBtn');
const colorOptions = document.querySelectorAll('.color-option');
const selectedColorInput = document.getElementById('selectedColor');
// State
let timers = JSON.parse(localStorage.getItem('countdownTimers')) || [];
// Set min date for datetime picker to today
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
document.getElementById('targetDate').min = `${year}-${month}-${day}T${hours}:${minutes}`;
// Event Listeners
addTimerBtn.addEventListener('click', () => {
timerForm.reset();
document.getElementById('targetDate').min = `${year}-${month}-${day}T${hours}:${minutes}`;
timerModal.classList.remove('hidden');
});
createFirstTimerBtn.addEventListener('click', () => {
addTimerBtn.click();
});
closeModalBtn.addEventListener('click', () => {
timerModal.classList.add('hidden');
});
cancelTimerBtn.addEventListener('click', () => {
timerModal.classList.add('hidden');
});
// Color selection
colorOptions.forEach(option => {
option.addEventListener('click', () => {
colorOptions.forEach(opt => opt.classList.remove('border-white'));
option.classList.add('border-white');
selectedColorInput.value = option.dataset.color;
});
});
// Form submission
timerForm.addEventListener('submit', (e) => {
e.preventDefault();
const name = document.getElementById('timerName').value;
const targetDate = new Date(document.getElementById('targetDate').value);
const backgroundColor = selectedColorInput.value;
if (targetDate <= new Date()) {
alert('Please select a future date and time.');
return;
}
const newTimer = {
id: Date.now(),
name,
targetDate: targetDate.toISOString(),
backgroundColor
};
timers.push(newTimer);
saveTimers();
renderTimers();
timerModal.classList.add('hidden');
});
// Timer functions
function saveTimers() {
localStorage.setItem('countdownTimers', JSON.stringify(timers));
}
function deleteTimer(id) {
timers = timers.filter(timer => timer.id !== id);
saveTimers();
renderTimers();
}
function updateCountdown(timer) {
const now = new Date();
const target = new Date(timer.targetDate);
const diff = target - now;
if (diff <= 0) {
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return { days, hours, minutes, seconds };
}
function renderTimers() {
timersContainer.innerHTML = '';
if (timers.length === 0) {
emptyState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
timers.forEach(timer => {
const countdown = updateCountdown(timer);
const timerCard = document.createElement('div');
timerCard.className = `timer-card rounded-xl p-6 text-white ${timer.backgroundColor} shadow-md`;
timerCard.innerHTML = `
<div class="flex justify-between items-start mb-4">
<h3 class="text-xl font-bold">${timer.name}</h3>
<button class="delete-timer text-white hover:text-gray-200" data-id="${timer.id}">
<i data-feather="x"></i>
</button>
</div>
<div class="grid grid-cols-4 gap-2 text-center">
<div>
<div class="countdown-number">${countdown.days}</div>
<div class="text-sm opacity-90">Days</div>
</div>
<div>
<div class="countdown-number">${countdown.hours}</div>
<div class="text-sm opacity-90">Hours</div>
</div>
<div>
<div class="countdown-number">${countdown.minutes}</div>
<div class="text-sm opacity-90">Minutes</div>
</div>
<div>
<div class="countdown-number">${countdown.seconds}</div>
<div class="text-sm opacity-90">Seconds</div>
</div>
</div>
<div class="mt-4 text-sm opacity-90">
Target: ${new Date(timer.targetDate).toLocaleString()}
</div>
`;
timersContainer.appendChild(timerCard);
});
// Add event listeners to delete buttons
document.querySelectorAll('.delete-timer').forEach(button => {
button.addEventListener('click', (e) => {
const id = parseInt(e.currentTarget.dataset.id);
deleteTimer(id);
});
});
// Reinitialize feather icons
feather.replace();
}
// Update countdowns every second
setInterval(() => {
renderTimers();
}, 1000);
// Initial render
renderTimers();
</script>
</body>
</html>
|