Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24, Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login, self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the blog series, safe markdown links in the frontends, and many smaller bugs — every project was built, run and exercised end to end. Adds scripts/publish.sh + a CI publish job that splits every folder into its own repo at git.devai.io/templates/<folder>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
50 lines
1.8 KiB
JavaScript
50 lines
1.8 KiB
JavaScript
const targetInput = document.getElementById("target");
|
|
const daysEl = document.getElementById("days");
|
|
const hoursEl = document.getElementById("hours");
|
|
const minutesEl = document.getElementById("minutes");
|
|
const secondsEl = document.getElementById("seconds");
|
|
const messageEl = document.getElementById("message");
|
|
|
|
function nextNewYear() {
|
|
return new Date(new Date().getFullYear() + 1, 0, 1); // Jan 1st, midnight
|
|
}
|
|
let target = nextNewYear();
|
|
|
|
// setInterval hands back an id. We keep it so clearInterval can stop the clock.
|
|
let timer = null;
|
|
|
|
// tick() runs once a second. Subtracting two Dates gives the milliseconds
|
|
// between them; the rest is dividing that gap into days, hours, minutes, seconds.
|
|
function tick() {
|
|
const msLeft = target - new Date();
|
|
|
|
if (msLeft <= 0) {
|
|
daysEl.textContent = hoursEl.textContent = minutesEl.textContent = secondsEl.textContent = "0";
|
|
messageEl.textContent = "🎉 It's time!";
|
|
clearInterval(timer); // we're done — stop calling tick()
|
|
return;
|
|
}
|
|
|
|
messageEl.textContent = "";
|
|
const totalSeconds = Math.floor(msLeft / 1000);
|
|
daysEl.textContent = Math.floor(totalSeconds / 86400); // 86,400 seconds in a day
|
|
hoursEl.textContent = Math.floor((totalSeconds % 86400) / 3600);
|
|
minutesEl.textContent = Math.floor((totalSeconds % 3600) / 60);
|
|
secondsEl.textContent = totalSeconds % 60;
|
|
}
|
|
|
|
// Always clear the old interval before starting a new one — otherwise every
|
|
// date change would stack up another timer, all running at once.
|
|
function start() {
|
|
clearInterval(timer);
|
|
tick(); // show a value now instead of waiting a full second
|
|
timer = setInterval(tick, 1000);
|
|
}
|
|
|
|
targetInput.addEventListener("change", () => {
|
|
const picked = new Date(targetInput.value);
|
|
target = isNaN(picked.getTime()) ? nextNewYear() : picked; // empty box → New Year
|
|
start();
|
|
});
|
|
|
|
start();
|