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
40 lines
1.5 KiB
JavaScript
40 lines
1.5 KiB
JavaScript
const row = document.getElementById("stars");
|
|
const stars = [...row.querySelectorAll(".star")];
|
|
const output = document.getElementById("output");
|
|
|
|
// Two values, two jobs. `selected` is the COMMITTED rating: set by a click,
|
|
// saved in localStorage. Hovering only PREVIEWS: it repaints the stars but
|
|
// never touches `selected`, so leaving the row snaps back to the real rating.
|
|
let selected = Number(localStorage.getItem("rating")) || 0;
|
|
|
|
function paint(value) {
|
|
stars.forEach((star, index) => star.classList.toggle("on", index < value));
|
|
}
|
|
|
|
function commit(value) {
|
|
selected = value;
|
|
localStorage.setItem("rating", String(value));
|
|
output.textContent = value ? `You rated: ${value}/5` : "Click a star to rate.";
|
|
paint(value);
|
|
}
|
|
|
|
stars.forEach((star, index) => {
|
|
star.addEventListener("mouseenter", () => paint(index + 1)); // preview
|
|
star.addEventListener("click", () => commit(index + 1)); // commit
|
|
});
|
|
row.addEventListener("mouseleave", () => paint(selected));
|
|
|
|
// Each star is a real <button>, so Tab, Enter and Space already work. The arrow
|
|
// keys add a quicker way to move the rating up and down.
|
|
row.addEventListener("keydown", (event) => {
|
|
let next;
|
|
if (event.key === "ArrowRight" || event.key === "ArrowUp") next = Math.min(5, selected + 1);
|
|
else if (event.key === "ArrowLeft" || event.key === "ArrowDown") next = Math.max(1, selected - 1);
|
|
else return;
|
|
|
|
event.preventDefault(); // don't also scroll the page
|
|
commit(next);
|
|
stars[next - 1].focus();
|
|
});
|
|
|
|
commit(selected);
|