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
66 lines
2.1 KiB
JavaScript
66 lines
2.1 KiB
JavaScript
const display = document.getElementById("display");
|
|
const startStopBtn = document.getElementById("startStop");
|
|
const lapBtn = document.getElementById("lap");
|
|
const resetBtn = document.getElementById("reset");
|
|
const lapList = document.getElementById("laps");
|
|
|
|
// The key idea: never COUNT ticks. setInterval ticks arrive late and a busy or
|
|
// background tab skips them, so a counter drifts. Instead, remember WHEN this
|
|
// run started and subtract. performance.now() is a clock made for measuring:
|
|
// it only moves forward, even if the computer's date and time get adjusted.
|
|
let elapsed = 0; // ms banked from earlier runs, before the last Stop
|
|
let startTime = 0; // when the current run began
|
|
let timer = null; // the interval id; null means stopped
|
|
|
|
function currentTime() {
|
|
return elapsed + (timer ? performance.now() - startTime : 0);
|
|
}
|
|
|
|
function format(ms) {
|
|
const minutes = Math.floor(ms / 60000);
|
|
const seconds = Math.floor((ms % 60000) / 1000);
|
|
const centis = Math.floor((ms % 1000) / 10);
|
|
const pad = (n) => String(n).padStart(2, "0");
|
|
return `${pad(minutes)}:${pad(seconds)}.${pad(centis)}`;
|
|
}
|
|
|
|
// The interval only decides how often we REPAINT. What we show always comes
|
|
// from currentTime(), so a slow tick can never make the clock wrong.
|
|
function render() {
|
|
display.textContent = format(currentTime());
|
|
}
|
|
|
|
function toggle() {
|
|
if (timer) {
|
|
elapsed = currentTime();
|
|
clearInterval(timer);
|
|
timer = null;
|
|
startStopBtn.textContent = "Start";
|
|
render(); // show the exact stopping time, not the last repaint
|
|
} else {
|
|
startTime = performance.now();
|
|
timer = setInterval(render, 30);
|
|
startStopBtn.textContent = "Stop";
|
|
}
|
|
}
|
|
|
|
function reset() {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
elapsed = 0;
|
|
startStopBtn.textContent = "Start";
|
|
lapList.replaceChildren();
|
|
render();
|
|
}
|
|
|
|
function addLap() {
|
|
const item = document.createElement("li");
|
|
item.textContent = format(currentTime());
|
|
lapList.append(item);
|
|
}
|
|
|
|
startStopBtn.addEventListener("click", toggle);
|
|
lapBtn.addEventListener("click", addLap);
|
|
resetBtn.addEventListener("click", reset);
|
|
|
|
render();
|