Spaces:
Running
Running
| <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> | |