Date & Time
COUNTDOWN TIMER
Set a countdown to any future date. Live days, hours, minutes, seconds. Save multiple countdowns.
Quick presets
Saved Countdowns
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
let timer = null, startTs = null, targetTs = null;
let prevValues = { days: null, hours: null, mins: null, secs: null };
function pad(n) { return String(Math.floor(n)).padStart(2, '0'); }
function toDatetimeLocalValue(ts) {
const d = new Date(ts);
return new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
}
function setPreset(key) {
const now = new Date();
let target;
if (key === 'newyear') target = new Date(now.getFullYear() + 1, 0, 1, 0, 0, 0);
else if (key === '1week') target = new Date(now.getTime() + 7 * 86400000);
else if (key === '1month') { target = new Date(now); target.setMonth(target.getMonth() + 1); }
else if (key === '24h') target = new Date(now.getTime() + 86400000);
else if (key === '1h') target = new Date(now.getTime() + 3600000);
else if (key === '10m') target = new Date(now.getTime() + 600000);
document.getElementById('targetDate').value = toDatetimeLocalValue(target.getTime());
start();
}
function getUrgencyLevel(remaining) {
if (remaining <= 0) return 'done';
if (remaining < 60 * 60 * 1000) return 'crit'; // < 1 hour
if (remaining < 24 * 60 * 60 * 1000) return 'warn'; // < 24 hours
return 'normal';
}
function applyUrgency(level) {
const display = document.getElementById('countdownDisplay');
const bar = document.getElementById('progressBar');
const badge = document.getElementById('urgencyBadge');
const nums = document.querySelectorAll('.countdown-num');
const pulse = document.getElementById('secPulse');
display.classList.remove('urgency-warn', 'urgency-crit');
bar.classList.remove('urgency-warn', 'urgency-crit');
badge.classList.remove('show', 'warn', 'crit');
nums.forEach(n => n.classList.remove('urgency-warn', 'urgency-crit'));
pulse.classList.remove('urgency-crit');
if (level === 'warn') {
display.classList.add('urgency-warn');
bar.classList.add('urgency-warn');
nums.forEach(n => n.classList.add('urgency-warn'));
badge.textContent = '< 24 hours remaining';
badge.classList.add('show', 'warn');
} else if (level === 'crit') {
display.classList.add('urgency-crit');
bar.classList.add('urgency-crit');
nums.forEach(n => n.classList.add('urgency-crit'));
pulse.classList.add('urgency-crit');
badge.textContent = '< 1 hour remaining';
badge.classList.add('show', 'crit');
}
}
function flipNum(el, newVal) {
if (el.textContent === newVal) return;
el.classList.remove('flip-out', 'flip-in');
el.classList.add('flip-out');
setTimeout(() => {
el.textContent = newVal;
el.classList.remove('flip-out');
el.classList.add('flip-in');
setTimeout(() => el.classList.remove('flip-in'), 200);
}, 120);
}
function start(fromHash = false) {
const val = document.getElementById('targetDate').value;
if (!val) {
alert('Please select a target date and time.');
return;
}
const ts = new Date(val).getTime();
if (isNaN(ts)) {
alert('Invalid date format.');
return;
}
targetTs = ts;
startTs = Date.now();
prevValues = { days: null, hours: null, mins: null, secs: null };
document.getElementById('setupArea').style.display = 'none';
document.getElementById('activeArea').style.display = 'block';
document.getElementById('countdownDisplay').style.display = '';
document.getElementById('timesup').style.display = 'none';
const ev = document.getElementById('eventName').value || 'COUNTDOWN';
document.getElementById('displayEvent').textContent = ev.toUpperCase();
document.getElementById('timesupEvent').textContent = ev.toUpperCase();
document.getElementById('targetDateDisplay').textContent = 'Target: ' + new Date(targetTs).toLocaleString();
// Only update hash when not triggered by hash change (prevents double-start)
if (!fromHash) {
const hashData = { t: targetTs, n: ev };
const hashStr = encodeURIComponent(JSON.stringify(hashData));
history.replaceState(null, '', '#' + hashStr);
}
if (timer) clearInterval(timer);
tick();
timer = setInterval(tick, 1000);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function reset() {
if (timer) clearInterval(timer);
timer = null; targetTs = null;
document.getElementById('setupArea').style.display = 'block';
document.getElementById('activeArea').style.display = 'none';
history.replaceState(null, '', location.pathname);
applyUrgency('normal');
}
function tick() {
const remaining = targetTs - Date.now();
if (remaining <= 0) {
clearInterval(timer);
timer = null;
document.getElementById('countdownDisplay').style.display = 'none';
document.getElementById('timesup').style.display = '';
document.getElementById('progressBar').style.width = '100%';
launchConfetti();
return;
}
const totalDuration = targetTs - startTs;
const elapsed = Date.now() - startTs;
const pct = Math.min(100, (elapsed / totalDuration) * 100);
document.getElementById('progressBar').style.width = pct + '%';
const secs = remaining / 1000;
const days = Math.floor(secs / 86400);
const hrs = Math.floor((secs % 86400) / 3600);
const mins = Math.floor((secs % 3600) / 60);
const s = Math.floor(secs % 60);
const dStr = pad(days), hStr = pad(hrs), mStr = pad(mins), sStr = pad(s);
flipNum(document.getElementById('cdDays'), dStr);
flipNum(document.getElementById('cdHours'), hStr);
flipNum(document.getElementById('cdMins'), mStr);
flipNum(document.getElementById('cdSecs'), sStr);
// Pulse dot on every second
const pulse = document.getElementById('secPulse');
pulse.classList.remove('beat');
void pulse.offsetWidth; // reflow to restart animation
pulse.classList.add('beat');
applyUrgency(getUrgencyLevel(remaining));
}
// ── Confetti ──
function launchConfetti() {
const container = document.getElementById('confettiContainer');
const colors = ['#ff2200', '#ff5555', '#00c896', '#f5c518', '#e8e0d5', '#ff8800'];
container.innerHTML = '';
for (let i = 0; i < 80; i++) {
const el = document.createElement('div');
el.className = 'confetti-piece';
const color = colors[Math.floor(Math.random() * colors.length)];
const left = Math.random() * 100;
const dur = 2 + Math.random() * 2;
const delay = Math.random() * 1.2;
const spin = (Math.random() > 0.5 ? '' : '-') + (180 + Math.floor(Math.random() * 540)) + 'deg';
const shape = Math.random() > 0.5 ? '50%' : '0';
el.style.cssText = `left:${left}%;top:-20px;background:${color};border-radius:${shape};--dur:${dur}s;--delay:${delay}s;--spin:${spin};animation-delay:${delay}s;`;
container.appendChild(el);
}
setTimeout(() => { container.innerHTML = ''; }, 5000);
}
// ── Save / Load / Share ──
function saveCountdown() {
const val = document.getElementById('targetDate').value;
if (!val) return;
const name = document.getElementById('eventName').value || 'Untitled';
const saved = JSON.parse(localStorage.getItem('countdowns') || '[]');
// Avoid duplicates by target timestamp
const ts = new Date(val).getTime();
const exists = saved.some(s => s.target === ts && s.name === name);
if (!exists) {
saved.unshift({ name, target: ts });
localStorage.setItem('countdowns', JSON.stringify(saved.slice(0, 10)));
}
renderSaved();
}
function renderSaved() {
const saved = JSON.parse(localStorage.getItem('countdowns') || '[]');
const el = document.getElementById('savedList');
if (!saved.length) { el.innerHTML = '<div style="font-size:12px;color:var(--muted);padding:8px 0;">No saved countdowns yet.</div>'; return; }
el.innerHTML = saved.map((s, i) => `
<div class="saved-item" onclick="loadSaved(${i})">
<div>
<div class="saved-name">${s.name.replace(/</g,'<')}</div>
<div class="saved-date">${new Date(s.target).toLocaleString()}</div>
</div>
<button class="saved-del" onclick="deleteSaved(event,${i})" title="Remove">×</button>
</div>`).join('');
}
function loadSaved(i) {
const saved = JSON.parse(localStorage.getItem('countdowns') || '[]');
const s = saved[i];
document.getElementById('eventName').value = s.name;
document.getElementById('targetDate').value = toDatetimeLocalValue(s.target);
start();
}
function deleteSaved(e, i) {
e.stopPropagation();
const saved = JSON.parse(localStorage.getItem('countdowns') || '[]');
saved.splice(i, 1);
localStorage.setItem('countdowns', JSON.stringify(saved));
renderSaved();
}
function copyAndFlash(btnId) {
navigator.clipboard.writeText(location.href).catch(() => {});
const el = document.getElementById(btnId);
if (!el) return;
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 2000);
}
function shareLink() { copyAndFlash('shareCopied2'); }
function shareFromSetup() {
const val = document.getElementById('targetDate').value;
if (val) {
const ev = document.getElementById('eventName').value || 'COUNTDOWN';
const ts = new Date(val).getTime();
if (!isNaN(ts)) {
const hashStr = encodeURIComponent(JSON.stringify({ t: ts, n: ev }));
history.replaceState(null, '', '#' + hashStr);
}
}
copyAndFlash('shareCopied');
}
function loadFromHash() {
try {
const hash = decodeURIComponent(location.hash.slice(1));
if (!hash) return;
const data = JSON.parse(hash);
if (data.t && data.n) {
// Guard: if already running this exact countdown, do nothing
if (targetTs === data.t) return;
document.getElementById('eventName').value = data.n === 'COUNTDOWN' ? '' : data.n;
document.getElementById('targetDate').value = toDatetimeLocalValue(data.t);
start(true); // fromHash=true → won't re-set hash
}
} catch(e) {
// malformed hash — ignore
}
}
window.addEventListener('hashchange', loadFromHash);
loadFromHash();
renderSaved();