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
44 lines
1.5 KiB
JavaScript
44 lines
1.5 KiB
JavaScript
const LIMIT = 280;
|
|
|
|
const textarea = document.getElementById("text");
|
|
const remaining = document.getElementById("remaining");
|
|
const postBtn = document.getElementById("post");
|
|
const ring = document.getElementById("ringProgress");
|
|
const status = document.getElementById("status");
|
|
|
|
// A circle's outline is 2πr long. Dash it into one dash that long, then slide
|
|
// it with stroke-dashoffset: offset = full length shows nothing, 0 shows the
|
|
// whole ring. So the ring is just "how much of the length to hide".
|
|
const circumference = 2 * Math.PI * ring.r.baseVal.value;
|
|
ring.style.strokeDasharray = String(circumference);
|
|
|
|
// Everything on screen is derived from ONE number — the text length — and
|
|
// update() recomputes all of it on every keystroke.
|
|
function update() {
|
|
const used = textarea.value.length;
|
|
const left = LIMIT - used;
|
|
const warn = left <= 20 && left >= 0;
|
|
const over = left < 0;
|
|
|
|
remaining.textContent = String(left);
|
|
remaining.classList.toggle("warn", warn);
|
|
remaining.classList.toggle("over", over);
|
|
postBtn.disabled = over;
|
|
|
|
const fraction = Math.min(used / LIMIT, 1);
|
|
ring.style.strokeDashoffset = String(circumference * (1 - fraction));
|
|
ring.classList.toggle("warn", warn);
|
|
ring.classList.toggle("over", over);
|
|
}
|
|
|
|
// "input" fires for typing, pasting, deleting and dictation alike.
|
|
textarea.addEventListener("input", update);
|
|
|
|
postBtn.addEventListener("click", () => {
|
|
status.textContent = "Posted! ✓";
|
|
textarea.value = "";
|
|
update();
|
|
setTimeout(() => (status.textContent = ""), 1500);
|
|
});
|
|
|
|
update();
|