SWAMP UI / COMPONENTS / LILY PAD STEPPING

Lily Pad Stepping

tap each pad in its own window

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.

No dependencies
~80 lines of JS
Audio feedback
Configurable tempo
LIVE PREVIEW · TAP THE NEXT PAD WHEN IT BLOOMS
REACHED THE BANK ★
the swamp claps in fireflies
PROGRESS
0 / 5
STREAK
0
BEST
0

When to use

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).

Skip to

1. HTML

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>

2. CSS

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

3. JavaScript

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

4. Parameters you can tweak

WhereDefaultEffect
HTML data-pads5Number of pads between shores. 3 = casual, 7 = brutal.
HTML data-tempo1100Base period (ms). Lower = harder.
HTML data-silentDisable plop / splash sounds.
JS OPEN_MS340How long each pad stays open per cycle. Lower = harder.
JS period stagger(i·170)%450Each pad gets a slightly different rhythm. Set to 0 for synced pads (easier).

Reached the bank?

Feed the frog. (And tip its owner a milk tea.)

🧋 TREAT THE SWAMP →
More in the garden → Swamp Tarot, Pond Ripple, Firefly Cursor, Frog Hop, Lure Cast.
Browse all →