1
Fork 0
char-counter/app.js
Leonardo Devai 991acc190f Review and modernize all 42 projects to the updated standard
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
2026-09-27 21:10:38 +02:00

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();