Lab 5 min read

Mano Loca

A 90s sticky hand toy built with Verlet integration. Drag the handle or the hand: it sticks to walls, stretches slime threads when pulled off, and vibrates on impact.

Sticky hand toys are 90s vending machine loot. Gummy rubber, a string, you throw it at a wall and it sticks. Stretch too far and it peels off trailing threads. The physics is programmable: stiffness, gravity, bounciness are just numbers.

Drag the green handle at the top or the hand at the bottom. Fling the hand at a wall. Pull it off slowly.

Mano Loca: sticky hand physics
// Verlet step: position remembers where it was.
// Difference between current and previous IS the velocity.
points.forEach(p => {
  if (p.stuck || p === grabbed) return;
  const vx = (p.x - p.oldX) * FRICTION;
  const vy = (p.y - p.oldY) * FRICTION;
  p.oldX = p.x; p.oldY = p.y;
  p.x += vx;
  p.y += vy + GRAVITY;
});

// Constraint pass, 25x per frame for stability
for (let iter = 0; iter < 25; iter++) {
  for (let i = 0; i < points.length - 1; i++) {
    const a = points[i], b = points[i + 1];
    const dx = b.x - a.x, dy = b.y - a.y;
    const dist = Math.sqrt(dx * dx + dy * dy);
    const correction = (REST_DIST - dist) / dist * STIFFNESS;
    if (!a.stuck) { a.x -= dx * correction * 0.5; a.y -= dy * correction * 0.5; }
    if (!b.stuck) { b.x += dx * correction * 0.5; b.y += dy * correction * 0.5; }
  }
}

The formula

Hover each part.

Each point stores its current position and its previous position. The difference is velocity. No separate velocity variable needed. At each frame: move by that difference, apply gravity, then pull all the constraint distances back toward their rest length.

The constraint pass runs 25 times per frame. One iteration would leave the rope wobbly. 25 makes it feel like rubber. The stiffness constant (0.95) scales how aggressively each constraint pushes back: at 1.0 it is rigid, at 0.5 it stretches and bounces.

The sticking mechanic

When the hand crosses within 30px of any edge, it locks to that position. While stuck, it follows the handle with a 5% lerp: enough to show the tension, not enough to pull free. It only peels off when the rope is stretched past 2.5x its natural length, or when you grab the handle and pull it past 90% of that limit.

The slime threads spawn at the moment of separation: 3 to 6 quadratic curves from the stuck position to the hand, each with a random midpoint offset and thickness. They decay over about 50 frames. The blobs are small filled arcs at the midpoint, just one extra arc() call per thread.

The vibration on impact (navigator.vibrate(30)) fires on mobile when the hand sticks. 30ms. Most people don’t expect it. That tap is what makes it feel like a real surface.

Tuning the feel

ParameterDefaultLowerHigher
GRAVITY1.4Floaty, slow dropHeavy, fast drop
FRICTION0.97Damps quicklySwings forever
STIFFNESS0.95Stretchy, elasticRigid, snaps back
SEGMENTS16Short, stubby ropeLong, heavy tail
REST_DIST18pxCompact coilLoose, wide swing

Slime is the same code with STIFFNESS: 0.7 and FRICTION: 0.99. Rubber band is STIFFNESS: 0.99 and GRAVITY: 0.4. One set of numbers, very different personality.