Click a lily pad. He jumps. Sometimes he lands. Sometimes the pad sinks.
True parabolic motion via requestAnimationFrame. Each frame computes x as linear interpolation, y as lerp_y − sin(t·π) · arcHeight — so the frog rises and falls in a clean arc, not a bezier approximation.
Each pad has 12% chance of being rotten. Land on a rotten pad → it sinks, frog splashes, miss counter ticks. Risk + delight.
A CSS cubic-bezier curve can fake an arc but breaks if you change start/end mid-flight. RAF lets you retarget instantly.
Three-keyframe scale animation sells weight without physics — the frog feels like it has mass.
Without failure, hopping has no texture. 12% spice rate = enough surprise, not enough to frustrate.
// parabolic arc — t goes 0→1, sin(t*π) peaks at .5 function hop(frog, fromX, fromY, toX, toY, arcHeight, duration){ const t0 = performance.now(); function step(now){ const t = Math.min(1, (now - t0) / duration); const x = fromX + (toX - fromX) * t; const y = fromY + (toY - fromY) * t - Math.sin(t * Math.PI) * arcHeight; frog.style.transform = `translate(${x}px, ${y}px)`; if (t < 1) requestAnimationFrame(step); else frog.classList.add('land'); } requestAnimationFrame(step); }
Drop in any container with class .pond. Frog + pads render themselves.