launchtimer: Embeddable Countdown for Product Hunt Launches
SvelteKit static export needed a launch countdown with timezone display and localStorage dismiss — 95 lines, embed via iframe or snippet.
Launch: Tuesday 9am PT. Marketing wanted countdown on Hugo landing page without React hydration.
Full widget
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Launch Countdown</title>
<style>
body { font-family: system-ui; margin: 0; padding: 1.5rem; text-align: center; background: #0f172a; color: #f8fafc; }
.grid { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.unit { background: #1e293b; padding: 1rem 1.25rem; border-radius: 12px; min-width: 70px; }
.unit strong { display: block; font-size: 2rem; }
.unit span { font-size: 12px; opacity: .7; text-transform: uppercase; }
.done { font-size: 1.5rem; color: #4ade80; }
</style>
</head>
<body>
<h1 id="title">Launching in</h1>
<div class="grid" id="grid">
<div class="unit"><strong id="d">0</strong><span>days</span></div>
<div class="unit"><strong id="h">0</strong><span>hours</span></div>
<div class="unit"><strong id="m">0</strong><span>mins</span></div>
<div class="unit"><strong id="s">0</strong><span>secs</span></div>
</div>
<p id="tz"></p>
<script>
const TARGET = new Date('2026-06-15T16:00:00Z');
const KEY = 'launch-dismissed';
if (localStorage.getItem(KEY)) {
document.body.innerHTML = '<p class="done">We\'re live — thanks for waiting!</p>';
} else {
document.getElementById('tz').textContent = 'Target: ' + TARGET.toLocaleString();
function pad(n) { return String(n).padStart(2, '0'); }
function tick() {
const now = Date.now();
let diff = TARGET - now;
if (diff <= 0) {
document.getElementById('grid').innerHTML = '<p class="done">🚀 We\'re live!</p>';
localStorage.setItem(KEY, '1');
return;
}
const sec = Math.floor(diff / 1000);
document.getElementById('d').textContent = Math.floor(sec / 86400);
document.getElementById('h').textContent = pad(Math.floor((sec % 86400) / 3600));
document.getElementById('m').textContent = pad(Math.floor((sec % 3600) / 60));
document.getElementById('s').textContent = pad(sec % 60);
}
tick();
setInterval(tick, 1000);
}
</script>
</body>
</html>
Safari Date parse
Use ISO 2026-06-15T16:00:00Z — avoid 06/15/2026 which parses as local inconsistently.
Embed on static site
<iframe src="/tools/countdown.html" width="100%" height="200" style="border:0"></iframe>
Tab throttle
Browsers throttle setInterval in background tabs. On return, tick() runs once — drift max 1s, acceptable.
Verify
Set TARGET 2 min ahead → counts down After zero → shows live message Reload after dismiss → stays on live message