// tools · frontend
TL;DR
A single self-contained HTML file that turns “what’s your AI doom take” into an actual game: pull a lever, stop four spinning reels on the right moment, then play a hand of blackjack against the house to decide how “legendary” your prophecy is. The result renders to a <canvas> certificate you can download as a PNG. No framework, no backend, no build step.
The problem
The obvious version of this idea is a form: pick a stance from a dropdown, type your prediction into a textarea, hit submit. It works, but it’s boring the second you look at it — it’s a survey wearing a fun hat. Nobody wants to fill out a form to get a joke.
The actual goal wasn’t “collect text input,” it was “make someone discover what they think by doing something, not by typing it.” That’s a different design problem: the mechanic has to generate the content, not just decorate a text field.
What I tried
First pass: four buttons per step, tap one, watch a sentence assemble from your picks. Better than a form, still reads as a quiz — click, click, click, done. There’s no tension, no timing, nothing that feels like play instead of selection.
Second pass: keep the four-step structure, but replace “tap a button” with “stop a spinning reel.” Each step becomes a slot machine window cycling through the same option pool at ~85ms per frame, blurred and jittering to sell motion:
spinTimer = setInterval(() => {
currentDisplayIndex = (currentDisplayIndex + 1) % opts.length;
slotText.textContent = opts[currentDisplayIndex];
}, 85);
Tapping the window mid-spin locks in whatever’s showing. Don’t tap in time and it auto-stops after 2.6s. That single change — reaction instead of selection — is what made it feel like a machine instead of a menu.
Four reels build a mad-libs sentence: how the AI wins, who it gets first, when, and the ironic twist. Same options every time, but 4⁴ = 256 possible sentences, and which one you get depends on your timing, not a dropdown you scanned and picked.
What actually worked
Two ideas ended up doing most of the work:
Outcome-driven scoring, not free-form input scoring. Once the sentence is built, you play one hand of blackjack against a dealer AI. The hand’s outcome — blackjack, win, push, bust, dealer blackjack — maps to a rarity tier (Common → Rare → Legendary → Mythic), and a deterministic hash of your reel picks gives you a specific percentage inside that tier’s range:
function hashScore(picksArr) {
const str = picksArr.join('-');
let h = 0;
for (let i = 0; i < str.length; i++) {
h = (h << 5) - h + str.charCodeAt(i);
h |= 0;
}
return Math.abs(h) % 100;
}
Same picks always produce the same percentage — it’s not random noise on refresh — but the tier is earned by actually playing a hand, not by what you typed. That’s the “discover what you want by doing something” principle applied twice: once to build the sentence, once to decide how good it is.
Canvas certificate as the actual deliverable. The shareable output isn’t a screenshot of the page, it’s a purpose-built 1080×1080 canvas drawn from scratch — dotted background, rarity banner, big percentage, wrapped prophecy text, >GLB_ sign-off — then exported via canvas.toDataURL('image/png') and handed to an <a download>. Nothing to screenshot badly, nothing that looks like a UI mid-interaction.
The whole thing is one HTML file: inline CSS, inline JS, Google Fonts for type, zero build tooling. Easy to drop into public/ on any static site and iframe it, or open directly.
Key takeaways
- If a mechanic just decorates a text field, it’s still a form — the interaction has to generate the content for it to read as a game.
- Reaction beats selection. Swapping “tap the right button” for “stop the reel at the right moment” was a small code change with a disproportionate effect on how the thing feels.
- Deterministic hashing gives you “random-feeling but reproducible” scoring without a backend — useful any time you want a result that feels earned rather than rolled.
canvas.toDataURL()is still the simplest way to ship a “shareable image” feature with no server and no image library.
17 Sep 2026 · #canvas #javascript #no-build
GLB_