A rhythm-timing game in five pads. Each lily pulses on its own beat — a flower blooms briefly, the pad lifts. Tap during the bloom and the frog jumps onto it. Tap off-rhythm and you splash. The next pad is always outlined in cyan, so you know where to look.
A great distraction primitive — a 404 page, a loading state for something genuinely slow, a "wait while we process your form" room. The kind of micro-game that takes 20 seconds to learn and doesn't punish failure. Drop it where boredom would otherwise creep in.
Not for: anywhere the user is in a hurry, anywhere accuracy matters more than vibe, or
anywhere sound is unwelcome (you can disable audio via data-silent).
One pond container. Pads are generated by JS so you can configure how many and how fast.
<div class="pond" id="pond" data-pads="5" data-tempo="1100"> <div class="pond-shore-left"></div> <div class="pond-shore-right"></div> <div class="frog">...</div> <div class="win-banner">You made it</div> </div>
Each pad transitions scale + a hidden flower. The .open class is added/removed by JS in sync with each pad's own rhythm.
.pad { width: 64px; height: 46px; border-radius: 50%; background: linear-gradient(135deg, #2a6b48, #1a3f2c); transition: transform .12s ease-out; } /* JS adds .next to the pad the frog is targeting */ .pad.next { box-shadow: 0 0 0 2px #52ffe0; } /* JS adds .open during each pad's bloom window */ .pad.open { transform: translate(-50%, -50%) scale(1.18); } .pad.open .pad-flower { opacity: .95; transform: translate(-50%, -50%) scale(1); } /* JS adds .done once the frog has landed on it */ .pad.done { box-shadow: 0 0 16px rgba(182, 255, 91, .5); }
Each pad has a period and phase. A heartbeat requestAnimationFrame
loop checks every pad: are we currently inside its open window? If yes, add .open.
Click → if target pad open, hop; else splash + reset streak.
// Swamp UI — Lily Pad Stepping (function(){ const pond = document.getElementById('pond'); const N = +pond.dataset.pads || 5; const TEMPO = +pond.dataset.tempo || 1100; // ms between bloom peaks (avg) const OPEN_MS = 340; // how long each pad stays open per cycle const pads = []; // Build pads with staggered periods + phases for (let i = 0; i < N; i++) { const el = document.createElement('div'); el.className = 'pad'; el.style.left = (14 + (i+1) * (72 / (N+1))) + '%'; el.innerHTML = '<div class="pad-flower"></div>'; pond.appendChild(el); pads.push({ el, period: TEMPO + (i * 170) % 450, phase: Math.random() * TEMPO, idx: i }); } let nextIdx = 0; pads[0].el.classList.add('next'); // Heartbeat: open/close pads based on phase function tick(){ const t = performance.now(); pads.forEach(p => { const within = ((t + p.phase) % p.period) < OPEN_MS; p.el.classList.toggle('open', within); }); requestAnimationFrame(tick); } tick(); // Click handling — only the .next pad matters pads.forEach(p => { p.el.addEventListener('click', () => { if (p.idx === nextIdx && p.el.classList.contains('open')) hop(p); else splash(p.el); }); }); })();
| Where | Default | Effect |
|---|---|---|
HTML data-pads | 5 | Number of pads between shores. 3 = casual, 7 = brutal. |
HTML data-tempo | 1100 | Base period (ms). Lower = harder. |
HTML data-silent | — | Disable plop / splash sounds. |
JS OPEN_MS | 340 | How long each pad stays open per cycle. Lower = harder. |
| JS period stagger | (i·170)%450 | Each pad gets a slightly different rhythm. Set to 0 for synced pads (easier). |