▶️ Play it first (10 seconds): https://emaadshamsi.github.io/paper-hands/
It's called PAPER HANDS. One button. The line goes up while you hold — your
multiplier climbs, and so do the odds it all rugs. Let go to bank it. Hold too
long and you lose the whole run. Pure greed, distilled.
No engine, no build step, no dependencies — one index.html, ~250 lines of Canvas.
Here's how it works.
The whole game is one loop: greed vs. risk
The mechanic is a single tension: every moment you don't sell, you earn more — and
get closer to losing everything.
if (held) {
const rate = 1.1 + mult * 0.16; // climbs faster the higher it goes
mult += rate * dt;
// near-safe early, risk ramps steeply as you get greedy:
const pct = (0.0028 + Math.pow(Math.max(mult - 1, 0), 1.6) * 0.0015) * (dt * 60);
if (t > 0.6 && Math.random() < pct) gameOver(); // 0.6s grace so you never insta-rug
}
That Math.pow(mult-1, 1.6) curve is the entire feel of the game. My first version
used a flat crash chance and players rugged in the first second — brutal, not fun.
Swapping to a curve that's almost-safe at low multipliers and punishing only when you
get greedy (plus a 0.6s grace per pump) turned it from frustrating into "one more run."
Balance is a one-line change you only find by playing.
Juice with zero assets
No sprites, no audio files. Everything is procedural:
- Sound = WebAudio oscillators — a rising blip while you pump, a noise burst + a detuned saw on the rug.
-
Feel = screen shake (
ctx.translate(rand, rand)scaled by a decayingshake), particle bursts on bank/crash, a glowing price marker, CRT scanlines via a CSSrepeating-linear-gradientoverlay.
function tone(freq, dur, type='square', vol=.16){
const o=ac().createOscillator(), g=ac().createGain();
o.type=type; o.frequency.value=freq; g.gain.value=vol;
o.connect(g); g.connect(ac().destination);
const t=ac().currentTime;
g.gain.exponentialRampToValueAtTime(.0001, t+dur);
o.start(t); o.stop(t+dur);
}
A little juice on a trivial mechanic does more for "fun" than a complex mechanic with
none.
The viral hook is one URL param
On game over you can copy a brag link — ?s=<score> — and whoever opens it sees
"a friend banked $4,200 — beat them" on the menu. No backend, no accounts:
const beatTarget = +(new URLSearchParams(location.search).get('s') || 0);
Why single-file?
It deploys anywhere static — I dropped it on GitHub Pages and it was live in a minute.
Whole thing (HTML + CSS + JS) is one file you can read top to bottom.
Play: https://emaadshamsi.github.io/paper-hands/
Code: https://github.com/emaadshamsi/paper-hands
Curious what scores people get — drop yours in the comments. 📈
Top comments (1)
Around 2k, hehe. Very fun!