static-countdown / index.html
philipp-zettl's picture
Update index.html
009105c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Countdown Timer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.glass-card {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
}
.countdown-digit {
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.countdown-digit:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.firework {
position: absolute;
width: 5px;
height: 5px;
border-radius: 50%;
box-shadow: 0 0 10px 5px;
animation: firework-animation 1s ease-out;
opacity: 0;
}
@keyframes firework-animation {
0% { transform: translate(0, 0); opacity: 1; }
100% { transform: translate(var(--tx), var(--ty)); opacity: 0; }
}
.confetti {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
animation: confetti-fall 3s ease-in forwards;
}
@keyframes confetti-fall {
0% { transform: translateY(-100vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(360deg); opacity: 1; }
}
.pulse {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.flip-in {
animation: flipIn 0.5s ease-out;
}
@keyframes flipIn {
0% { transform: rotateX(90deg); opacity: 0; }
100% { transform: rotateX(0deg); opacity: 1; }
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(102, 126, 234, 0.25);
}
.input-field {
transition: all 0.3s ease;
border: 1px solid #e2e8f0;
}
.input-field:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.tab-btn {
transition: all 0.3s ease;
border-bottom: 3px solid transparent;
}
.tab-btn.active {
border-bottom-color: #667eea;
color: #667eea;
font-weight: 600;
}
.datetime-input {
position: relative;
}
.datetime-input::-webkit-calendar-picker-indicator {
background: transparent;
bottom: 0;
color: transparent;
cursor: pointer;
height: auto;
left: 0;
position: absolute;
right: 0;
top: 0;
width: auto;
}
</style>
</head>
<body class="min-h-screen text-gray-800 font-sans">
<div id="app" class="container mx-auto px-4 py-12">
<!-- Main Page -->
<div id="main-page" class="text-center max-w-3xl mx-auto">
<div class="glass-card p-8 mb-8">
<h1 class="text-4xl font-bold mb-4 text-gray-800">Countdown Timer</h1>
<p class="text-lg text-gray-600 mb-8">Create countdowns by selecting a future date or setting duration</p>
<form id="countdown-form" class="space-y-6">
<div class="text-left">
<label for="title" class="block mb-2 text-gray-700">Countdown Title</label>
<input type="text" id="title" placeholder="New Year's Eve"
class="input-field w-full px-4 py-3 rounded-lg bg-white">
</div>
<div class="flex border-b border-gray-200 mb-6">
<button type="button" id="duration-tab" class="tab-btn active py-2 px-4">Duration</button>
<button type="button" id="datetime-tab" class="tab-btn py-2 px-4">Date & Time</button>
</div>
<div id="duration-inputs">
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-left">
<label for="days" class="block mb-2 text-gray-700">Days</label>
<input type="number" id="days" min="0" value="0"
class="input-field w-full px-4 py-3 rounded-lg bg-white">
</div>
<div class="text-left">
<label for="hours" class="block mb-2 text-gray-700">Hours</label>
<input type="number" id="hours" min="0" max="23" value="0"
class="input-field w-full px-4 py-3 rounded-lg bg-white">
</div>
<div class="text-left">
<label for="minutes" class="block mb-2 text-gray-700">Minutes</label>
<input type="number" id="minutes" min="0" max="59" value="10"
class="input-field w-full px-4 py-3 rounded-lg bg-white">
</div>
<div class="text-left">
<label for="seconds" class="block mb-2 text-gray-700">Seconds</label>
<input type="number" id="seconds" min="0" max="59" value="0"
class="input-field w-full px-4 py-3 rounded-lg bg-white">
</div>
</div>
</div>
<div id="datetime-inputs" class="hidden">
<div class="text-left">
<label for="target-datetime" class="block mb-2 text-gray-700">Target Date & Time</label>
<input type="datetime-local" id="target-datetime"
class="datetime-input input-field w-full px-4 py-3 rounded-lg bg-white"
min="">
</div>
<div class="mt-4 text-left">
<label for="timezone" class="block mb-2 text-gray-700">Timezone</label>
<select id="timezone" class="input-field w-full px-4 py-3 rounded-lg bg-white">
<option value="local">Local Time</option>
<option value="utc">UTC</option>
</select>
</div>
</div>
<div class="text-left">
<label class="block mb-2 text-gray-700">Finish Animation</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<label class="flex items-center space-x-2 cursor-pointer p-3 rounded-lg hover:bg-gray-100">
<input type="radio" name="animation" value="fireworks" checked class="form-radio h-5 w-5 text-indigo-600">
<span class="text-gray-700">Fireworks</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer p-3 rounded-lg hover:bg-gray-100">
<input type="radio" name="animation" value="confetti" class="form-radio h-5 w-5 text-indigo-600">
<span class="text-gray-700">Confetti</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer p-3 rounded-lg hover:bg-gray-100">
<input type="radio" name="animation" value="pulse" class="form-radio h-5 w-5 text-indigo-600">
<span class="text-gray-700">Pulse</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer p-3 rounded-lg hover:bg-gray-100">
<input type="radio" name="animation" value="none" class="form-radio h-5 w-5 text-indigo-600">
<span class="text-gray-700">None</span>
</label>
</div>
</div>
<button type="submit" class="btn-primary mt-6 px-8 py-3 rounded-full font-semibold">
Start Countdown <i class="fas fa-play ml-2"></i>
</button>
</form>
</div>
<div class="glass-card p-6">
<h2 class="text-2xl font-semibold mb-4 text-gray-800">How to Use</h2>
<div class="text-left space-y-2 text-gray-600">
<p>1. Choose between setting a duration or selecting a specific future date/time</p>
<p>2. Add a title and choose a finish animation</p>
<p>3. Share the URL with others - all settings are included</p>
<p class="mt-4 font-medium">Example URL: <code class="bg-gray-100 px-2 py-1 rounded">?title=New+Year&target=2024-01-01T00:00&timezone=utc&animation=fireworks</code></p>
</div>
</div>
</div>
<!-- Countdown Page -->
<div id="countdown-page" class="hidden text-center">
<div class="glass-card p-8 max-w-2xl mx-auto">
<h1 id="countdown-title" class="text-3xl font-bold mb-8 text-gray-800">Countdown</h1>
<div class="flex justify-center space-x-4 mb-8">
<div class="countdown-digit bg-white rounded-lg p-4 w-20 text-center">
<div id="days-display" class="text-3xl font-bold text-indigo-600">00</div>
<div class="text-sm text-gray-500">Days</div>
</div>
<div class="countdown-digit bg-white rounded-lg p-4 w-20 text-center">
<div id="hours-display" class="text-3xl font-bold text-indigo-600">00</div>
<div class="text-sm text-gray-500">Hours</div>
</div>
<div class="countdown-digit bg-white rounded-lg p-4 w-20 text-center">
<div id="minutes-display" class="text-3xl font-bold text-indigo-600">00</div>
<div class="text-sm text-gray-500">Minutes</div>
</div>
<div class="countdown-digit bg-white rounded-lg p-4 w-20 text-center">
<div id="seconds-display" class="text-3xl font-bold text-indigo-600">00</div>
<div class="text-sm text-gray-500">Seconds</div>
</div>
</div>
<div id="target-time-display" class="text-gray-600 mb-4">
Counting down to: <span id="target-time" class="font-medium"></span>
</div>
<div id="finished-message" class="hidden text-2xl font-bold mb-6">
<div class="inline-block px-6 py-3 bg-indigo-600 text-white rounded-full">
Time's Up!
</div>
</div>
<button id="new-countdown" class="btn-primary mt-4 px-6 py-2 rounded-full font-medium">
<i class="fas fa-redo mr-2"></i> New Countdown
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Set min datetime to now
const now = new Date();
const timezoneOffset = now.getTimezoneOffset() * 60000;
const localISOTime = new Date(now - timezoneOffset).toISOString().slice(0, 16);
document.getElementById('target-datetime').min = localISOTime;
// Tab switching
const durationTab = document.getElementById('duration-tab');
const datetimeTab = document.getElementById('datetime-tab');
const durationInputs = document.getElementById('duration-inputs');
const datetimeInputs = document.getElementById('datetime-inputs');
durationTab.addEventListener('click', function() {
durationTab.classList.add('active');
datetimeTab.classList.remove('active');
durationInputs.classList.remove('hidden');
datetimeInputs.classList.add('hidden');
});
datetimeTab.addEventListener('click', function() {
datetimeTab.classList.add('active');
durationTab.classList.remove('active');
datetimeInputs.classList.remove('hidden');
durationInputs.classList.add('hidden');
});
// Check URL parameters
const urlParams = new URLSearchParams(window.location.search);
const hasDurationParams = urlParams.has('days') || urlParams.has('hours') || urlParams.has('minutes') || urlParams.has('seconds');
const hasDatetimeParam = urlParams.has('target');
if (hasDurationParams || hasDatetimeParam) {
const title = urlParams.get('title') || 'Countdown';
const animation = urlParams.get('animation') || 'fireworks';
if (hasDatetimeParam) {
// Start countdown from datetime
const target = urlParams.get('target');
const timezone = urlParams.get('timezone') || 'local';
// Switch to datetime tab
datetimeTab.click();
// Set the datetime input
document.getElementById('target-datetime').value = target;
document.getElementById('timezone').value = timezone;
// Calculate time remaining
const targetDate = new Date(target);
const now = new Date();
let diffInSeconds = Math.floor((targetDate - now) / 1000);
if (diffInSeconds > 0) {
startCountdown(title, diffInSeconds, animation, targetDate, timezone);
} else {
// If time is in the past, show finished state
document.getElementById('main-page').classList.add('hidden');
document.getElementById('countdown-page').classList.remove('hidden');
document.getElementById('countdown-title').textContent = title;
document.getElementById('finished-message').classList.remove('hidden');
countdownFinished(animation);
}
} else {
// Start countdown from duration params
const days = parseInt(urlParams.get('days')) || 0;
const hours = parseInt(urlParams.get('hours')) || 0;
const minutes = parseInt(urlParams.get('minutes')) || 0;
const seconds = parseInt(urlParams.get('seconds')) || 0;
const totalSeconds = days * 86400 + hours * 3600 + minutes * 60 + seconds;
startCountdown(title, totalSeconds, animation);
}
}
// Form submission
const form = document.getElementById('countdown-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
const title = document.getElementById('title').value || 'Countdown';
const animation = document.querySelector('input[name="animation"]:checked').value;
// Check which tab is active
const isDurationTabActive = durationTab.classList.contains('active');
if (isDurationTabActive) {
// Duration mode
const days = parseInt(document.getElementById('days').value) || 0;
const hours = parseInt(document.getElementById('hours').value) || 0;
const minutes = parseInt(document.getElementById('minutes').value) || 0;
const seconds = parseInt(document.getElementById('seconds').value) || 0;
const totalSeconds = days * 86400 + hours * 3600 + minutes * 60 + seconds;
// Update URL with parameters
const params = new URLSearchParams();
if (title !== 'Countdown') params.set('title', title);
if (days > 0) params.set('days', days);
if (hours > 0) params.set('hours', hours);
if (minutes > 0) params.set('minutes', minutes);
if (seconds > 0) params.set('seconds', seconds);
params.set('animation', animation);
window.history.pushState({}, '', `?${params.toString()}`);
startCountdown(title, totalSeconds, animation);
} else {
// Datetime mode
const targetDatetime = document.getElementById('target-datetime').value;
const timezone = document.getElementById('timezone').value;
if (!targetDatetime) {
alert('Please select a future date and time');
return;
}
// Calculate time remaining
let targetDate = new Date(targetDatetime);
const now = new Date();
let diffInSeconds = Math.floor((targetDate - now) / 1000);
if (diffInSeconds <= 0) {
alert('Please select a time in the future');
return;
}
// Format target date for URL
let targetForUrl;
if (timezone === 'utc') {
targetForUrl = targetDate.toISOString().slice(0, 16);
} else {
// Local time - need to adjust for timezone offset
const timezoneOffset = now.getTimezoneOffset() * 60000;
const localISOTime = new Date(targetDate.getTime() - timezoneOffset).toISOString().slice(0, 16);
targetForUrl = localISOTime;
}
// Update URL with parameters
const params = new URLSearchParams();
if (title !== 'Countdown') params.set('title', title);
params.set('target', targetForUrl);
params.set('timezone', timezone);
params.set('animation', animation);
window.history.pushState({}, '', `?${params.toString()}`);
startCountdown(title, diffInSeconds, animation, targetDate, timezone);
}
});
// New countdown button
document.getElementById('new-countdown').addEventListener('click', function() {
window.history.pushState({}, '', window.location.pathname);
document.getElementById('main-page').classList.remove('hidden');
document.getElementById('countdown-page').classList.add('hidden');
clearInterval(window.countdownInterval);
document.getElementById('finished-message').classList.add('hidden');
document.getElementById('target-time-display').classList.add('hidden');
});
});
function startCountdown(title, totalSeconds, animationType, targetDate = null, timezone = null) {
// Switch to countdown page
document.getElementById('main-page').classList.add('hidden');
document.getElementById('countdown-page').classList.remove('hidden');
document.getElementById('countdown-title').textContent = title;
document.getElementById('finished-message').classList.add('hidden');
// Show target time if available
if (targetDate) {
let displayDate;
if (timezone === 'utc') {
displayDate = targetDate.toUTCString();
} else {
displayDate = targetDate.toLocaleString();
}
document.getElementById('target-time').textContent = displayDate;
document.getElementById('target-time-display').classList.remove('hidden');
} else {
document.getElementById('target-time-display').classList.add('hidden');
}
// Start the countdown
updateCountdownDisplay(totalSeconds);
if (targetDate) {
// For datetime mode, we need to recalculate the remaining time each second
// to account for any time drift
window.countdownInterval = setInterval(function() {
const now = new Date();
totalSeconds = Math.floor((targetDate - now) / 1000);
updateCountdownDisplay(totalSeconds);
if (totalSeconds <= 0) {
clearInterval(window.countdownInterval);
countdownFinished(animationType);
}
}, 1000);
} else {
// For duration mode, simple countdown is sufficient
window.countdownInterval = setInterval(function() {
totalSeconds--;
updateCountdownDisplay(totalSeconds);
if (totalSeconds <= 0) {
clearInterval(window.countdownInterval);
countdownFinished(animationType);
}
}, 1000);
}
}
function updateCountdownDisplay(totalSeconds) {
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
document.getElementById('days-display').textContent = days.toString().padStart(2, '0');
document.getElementById('hours-display').textContent = hours.toString().padStart(2, '0');
document.getElementById('minutes-display').textContent = minutes.toString().padStart(2, '0');
document.getElementById('seconds-display').textContent = seconds.toString().padStart(2, '0');
}
function countdownFinished(animationType) {
document.getElementById('finished-message').classList.remove('hidden');
switch(animationType) {
case 'fireworks':
createFireworks();
break;
case 'confetti':
createConfetti();
break;
case 'pulse':
document.getElementById('finished-message').classList.add('pulse');
break;
// 'none' case does nothing
}
}
function createFireworks() {
const colors = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#ff00ff'];
const container = document.getElementById('app');
for (let i = 0; i < 30; i++) {
setTimeout(() => {
const firework = document.createElement('div');
firework.className = 'firework';
firework.style.left = `${Math.random() * 100}%`;
firework.style.top = `${Math.random() * 100}%`;
firework.style.color = colors[Math.floor(Math.random() * colors.length)];
// Random direction for particles
const angle = Math.random() * Math.PI * 2;
const distance = 30 + Math.random() * 70;
const tx = Math.cos(angle) * distance;
const ty = Math.sin(angle) * distance;
firework.style.setProperty('--tx', `${tx}px`);
firework.style.setProperty('--ty', `${ty}px`);
container.appendChild(firework);
// Remove after animation
setTimeout(() => {
firework.remove();
}, 1000);
}, i * 200);
}
}
function createConfetti() {
const colors = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#ff00ff'];
const container = document.getElementById('app');
for (let i = 0; i < 80; i++) {
setTimeout(() => {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = `${Math.random() * 100}%`;
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confetti.style.animationDelay = `${Math.random() * 2}s`;
// Random shape
if (Math.random() > 0.5) {
confetti.style.borderRadius = '50%';
} else {
confetti.style.transform = 'rotate(45deg)';
}
container.appendChild(confetti);
// Remove after animation
setTimeout(() => {
confetti.remove();
}, 3000);
}, i * 100);
}
}
</script>
</body>
</html>