Jelly Nails

jellynails

FableCarbon
Fable$46.00 · 52m00s
Claude Fable 5.5$46.00 · 52m00s
Claude Fable 5.5 $46.00 · 52m00s
Winner
Carbon$1.50 · 24m00s
Gemini Carbon 4$1.50 · 24m00s
Gemini Carbon 4 $1.50 · 24m00s
CheapestFastest

Fable wins. Fable fills the jelly with fruit and gives the nails threaded heads and a weighty mallet; Carbon plays the same game in a plainer, flatter block

Remix in Claude

The prompt

Build **"Jelly Nails"**: the Japanese nail-hammering game, where you drive a row of nails flush in as few blows as possible, **except the block of wood is a giant wobbly jelly**. People **charge**, **swing** and **strike** a hammer at ten nails, the jelly **squashes, bounces and rocks** under every blow, and the jelly's own motion decides how deep each nail goes. One round is 10 nails, scored out of **100**. As ONE self-contained HTML file with **synthesised sound**.

It should feel like a glossy candy macro photograph that you can play: a heavy steel hammer, bright steel nails you can see through the translucent jelly, and a block that answers every blow with a deep, satisfying jiggle. The whole game is one idea: **power alone is not enough, you have to hit when the jelly is with you.**

## Tech

- three.js r170 with `WebGPURenderer` (automatic WebGL2 fallback). Load it with an importmap from jsDelivr: map "three", "three/webgpu" and "three/tsl" to `https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.webgpu.min.js`, and "three/addons/" to `https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/`. These are the **only** network requests allowed: no other libraries, fonts, images, models, textures or audio files. Use TSL node materials. Physics and simulation are your own code (no physics libraries).
- Start with `<!doctype html>`, `<meta charset="utf-8">` and a viewport meta tag. System font stacks only.
- It must run inside `<iframe sandbox="allow-scripts allow-pointer-lock">` (no `allow-same-origin`): `localStorage` and cookies throw there, so wrap every storage access in `try/catch` and keep the session best in memory. No `alert`, pop-ups or top-level navigation.
- Listen for keys on `window`. For the hammer accept `event.key === ' '` **or** `event.code === 'Space'` (the hosting page sends synthetic key events that only carry `key`); read letters by `event.code` (`KeyR`, `KeyM`, `KeyC`) so they work on any keyboard layout. `preventDefault` on Space so the page never scrolls.
- Fixed simulation timestep (120 Hz) with bounded substeps, decoupled from frame rate; on a slow frame, drop simulation time rather than exploding. The jelly at rest still breathes (see below), so it never fully sleeps, but keep the per-frame cost tiny.
- Preallocate every buffer, geometry and material at start-up (block, 10 nails, hammer, particle pool); nothing is created during play.
- A quality scaler that lowers pixel ratio, then shadows, then post effects, then particle counts when frames get slow, never the timing or the feel. Target 60 fps on a mid-range phone.
- Known three r170 pitfalls, avoid them:
  - the scene background is drawn after transparent objects: draw the backdrop as a mesh (or give transparent sky objects `depthWrite: true`);
  - never `.clone()` a `CanvasTexture` (the clone uploads black);
  - create every `InstancedMesh` at its full count and never draw one with fewer than 2 instances (wrong-size uniform buffer, black screen): hide unused instances by scaling them to zero;
  - never let a helper overwrite a shared temp vector its caller still uses.

## Priorities

Build it in this order, and finish each tier properly before starting the next. A polished tier 1 beats a broken everything.

1. **Must be excellent:** the charge-and-strike feel (instant, heavy, no lag between release and impact); the jelly's squash, bounce and rock after every blow, readable enough that a player learns to time it; the exact scoring below; the nails visible inside the jelly; the score card at the end; the demo mode.
2. **Must work:** bent nails, the jelly pushing a shallow nail back up, the per-nail scores strip, session best, toasts, sound, touch, the interface, the Easter egg.
3. **Polish:** jelly droplets on hard blows, coloured light through the jelly onto the table, a tiny camera kick on a perfect blow, the score card counting up.

Never ship a feature half-working: if something from tier 3 can't be done well, leave it out cleanly.

## The block and the nails

- **The block:** one big jelly slab like a fat wooden log cut flat: about **3.2 long x 1.1 tall x 1.5 deep** (world units), well-rounded edges (radius about 0.18). A deep **raspberry-red** jelly, translucent, darker where it's thicker, with a few tiny suspended air bubbles. It sits on a thick oiled-oak workbench top with visible grain.
- **The nails:** **10 steel nails** in two staggered rows of 5 on the top face, fixed positions (the same every round). Each nail is **1.0 long** (shank radius about 0.035, a flat round head of radius about 0.09, a sharp point), polished steel with a slightly bluish sheen. Every nail starts **pre-set: 0.38 inside the jelly, 0.62 sticking out**, perfectly upright. The parts inside the jelly must be **visible through it**, refracted and tinted red.
- Nails are played **in order, 1 to 10** (back row left to right, then front row left to right). The current nail has a soft white ring on the jelly around its base; finished nails stay where they ended.

## The jelly (this is the game)

The jelly has three motions, each a damped spring at 120 Hz. They move the whole block (render with these offsets, vertex deformation in the shader or on the CPU, with the nails riding on the deformed surface):

- **Bounce** (vertical squash and stretch, volume preserved: flatter is wider): natural frequency about **2.3 Hz**, damping ratio about **0.12**, so a hard blow keeps it bouncing for 2 to 3 seconds.
- **Rock** (the top shears sideways along the block's length, nails tilting with it): about **1.5 Hz**, damping ratio about **0.10**.
- **Dent** (a local dip around the struck nail): about **6 Hz**, damping ratio about **0.3**.

A blow kicks all three: bounce down by power, rock by power times how far the nail is from the block's centre (the far ends rock it most), dent by power. Because bounce and rock run at different frequencies, the good moments come and go: that rhythm is what players learn.

**Breathing:** the jelly never stands perfectly still. Each nail has a fixed idle sway (bounce and rock amplitudes that never decay below a floor) that **grows from nail 1 (gentle) to nail 10 (wild)**. Use the same fixed values every round.

## Hammer and strike

- **Charge:** press and hold (mouse, touch anywhere on the stage, or Space). The hammer lifts back and a power meter fills from 0 to 1 in **0.6 s**, then falls back to 0 in the next 0.6 s and repeats (ping-pong), so holding too long loses power. A sweet band from **0.85 to 1.0** is marked on the meter.
- **Release** swings the hammer down in about **70 ms** onto the current nail's head (the hammer always aims at the nail; the player only chooses power and timing). The impact is computed **at the moment of contact**, from the jelly's state at that instant.
- **Drive:** each blow drives the nail in by

  `drive = 0.42 x power^1.5 x (1 + 0.8 x lift)`

  where `lift` is the jelly surface's vertical velocity under the nail at impact, normalised to -1..1 (+1 = rising fastest toward the hammer, -1 = falling away). Hitting while the jelly rises can sink a nail in **one blow**; hitting while it falls away wastes the blow. A nail is **flush** when 0.02 or less sticks out: it snaps flush with a small settle.
- **Bend:** if `power > 0.6` and the nail's tilt at impact (from rock and dent) is more than **55%** of the rock's normal maximum, the nail **bends**: it kinks over sideways with a twang, stays bent in the jelly, scores 0, and play moves to the next nail.
- **Spit back:** after a blow, a nail that is still less than half in springs back up by **25%** of that blow's drive over about 0.3 s (the jelly pushes it out). Visible and audible, it punishes timid taps.
- A nail allows at most **6 blows**; after the 6th it counts as done with whatever score it has.

## Scoring (exact, out of 100)

- Per nail: flush in **1** blow = **10**, **2** = **8**, **3** = **6**, **4** = **4**, **5** = **2**, **6** blows or not flush after 6 = **1**, **bent** = **0**.
- Round score = the sum of the 10 nails, **0 to 100**. 100 is possible but very hard.
- Under the score, a strip of 10 small slots fills in as you go: each shows the nail's points, a gold star for 10, a red kink for bent.
- Toasts (top centre): "One blow!" on a 10, "Bent!" on a bend, "Three tens in a row" (once per round), "Halfway: 47" after nail 5 (with the real score).
- **End of round:** the jelly does one big happy bounce, then a **big score card**: **"87 / 100"** counting up quickly, the 10-slot strip, the session best (**"New best!"** when beaten), a one-line rank ("Carpenter" 90+, "Handy" 70+, "Wobbly" 40+, "Jelly hands" below), and a big **Play again** button (also `R` or Space). Make the card look great in a screenshot: it is what people post.
- Session best in memory (try `localStorage` inside `try/catch`; fine if it throws).

## Easter egg

- Nail 7 is secretly a **golden nail** (looks like steel). Sink it in one blow and it turns gold with a sparkle and a small bell, and its slot gets a gold nail instead of a star. No extra points, never mentioned in the UI.

## Sound

- Synthesize everything with WebAudio; create the `AudioContext` on the first gesture only; a mute button and `M`.
- Charge: a soft rising whoosh that follows the meter. Strike: a heavy wet **thwock** (low thump + short squelch of filtered noise) plus a bright metallic **tink** from the nail head, both scaled by power. Flush: a clean ringing "ding" with a soft plop. Bend: a falling **twang**. Spit back: a rubbery "bloop". One-blow 10: the ding an octave up with a sparkle. End card: a short rising arpeggio, a bigger one for a new best.
- A master gain into a compressor, no clipping; cap simultaneous voices at about 12.

## Look

Aim for a glossy candy macro photograph, not a game asset.

- **Colour:** sRGB output, AgX tone mapping, exposure 1.0.
- **Lighting:** a procedural environment (`RoomEnvironment` through `PMREMGenerator`, no HDR files) plus a warm key from the upper left, a soft cool fill and a strong rim light from behind so the jelly's edges glow; soft shadows (2048, 1024 on phones) onto the bench, and a contact shadow under the block so it sits on the wood.
- **Materials:** jelly is `MeshPhysicalNodeMaterial` with transmission, thickness, IOR about 1.35 and a raspberry attenuation colour (absorption deepens where it's thicker), a clearcoat for the wet shine, faint procedural variation. If full transmission is too slow, fake it convincingly (Fresnel rim, thickness-tinted colour, refracted backdrop) but **the nails inside must stay visible**. Steel: metalness 1, low roughness, crisp environment reflections. Hammer: a forged steel head with a satin face and a varnished wooden handle.
- **Post:** one subtle bloom (strength ~0.4, threshold above 1), a light vignette. No heavy chromatic aberration or grain.
- **Backdrop:** a soft studio gradient in a **warm mid grey-teal** (around `#5f7774` to `#3f5150`), never pure white or pure black.
- **Framing:** a three-quarter view slightly above, the block filling about 75% of the width of a square frame, the hammer entering from the right. Every nail must be clearly visible and the current one easy to read.
- **Motion:** everything eases, overshoots a little and settles; nothing pops or teleports.

## Interface

- Calm, small and out of the way: top left `POKEABLE · JELLY NAILS` and a big two-line title; top right a status pill `WEBGPU · LIVE` or `WEBGL2 · LIVE` and a mute button.
- Bottom centre: **Nail 4 / 10**, **Blows 1**, **Score 26**, **Best 71**, the 10-slot strip, and the **power meter** (a chunky horizontal bar with the sweet band marked) that appears while charging.
- A one-line hint that fades after the first strike: "Hold to charge, release to strike. Hit when the jelly rises."
- At phone width everything collapses to one line above a big stage; the whole stage is the touch target. Nothing is hover-only.
- If neither WebGPU nor WebGL2 works, a clear, designed message.

## Camera and keys

- The camera is fixed during play (timing needs a steady view), with a tiny kick on impact (none with `prefers-reduced-motion`). Dragging is not used: every press on the stage charges the hammer.
- `Space` hold to charge and release to strike, `R` restart the round, `M` mute, `C` cinema (hide all UI).

## Test hook

If the URL has `?test=1`, expose `window.__toy` with no visible change: `restart()`, `strike(power)` (strikes the current nail now with that power), `wait(seconds)` (advances the simulation), and `getState()` returning `{ renderer, fps, nail, blows, score, best, done, nails: [{ out, blows, points, bent }], lift, tilt }`.

## Demo mode (required)

If the URL has `?demo=1`, auto-play a 4-second loop with no user input: the object drops in, wobbles, gets poked or sliced once, then settles.

For this toy that means:
- Hide all UI and the cursor. Never create an `AudioContext`.
- One fixed quality preset, fixed jelly state, default camera.
- A deterministic loop restarting every 4.0 s of real time from the identical state (nails 1 to 3 already flush, nail 4 current and pre-set, the jelly gently breathing):
  - **0.0 to 0.6 s:** the hammer lifts back over nail 4 (charging).
  - **0.6 to 0.7 s:** it swings down and strikes as the jelly rises: the nail sinks flush in one blow, the block squashes hard and the nails visibly tilt with the rock.
  - **0.7 to 2.4 s:** the jelly bounces and rocks, settling; the hammer lifts away and rests to the side.
  - **2.4 to 3.6 s:** nail 4 slides smoothly back up to its pre-set height (a guided demo-only motion) while the jelly calms.
  - **3.6 to 4.0 s:** everything back to the state at 0.0 s.
- Start moving within 1 s of load; the strike and the bounce must read clearly at 15 fps (the demo is recorded in a slow software renderer).
- Support `?demo=1&t=<seconds>` to freeze the demo at that time.

## Before you finish

Self-check against this list and fix anything that fails:
- Scoring: play full rounds with `__toy.strike()`: one-blow 10s happen only with high power while the jelly rises, weak taps get spat back, strikes on a strong rock bend the nail, the 6-blow cap works, the total always equals the sum of the strip and never exceeds 100.
- Feel: there is no delay between release and impact; the jelly's bounce and rock are clearly visible and readable at every nail; nails 1 to 10 get harder; the nails stay attached to the deformed surface (never floating above it or sliding through it).
- Charge ping-pong, Play again, `R`, `M`, `C`, Space (including a synthetic `window.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))` then `keyup`), and resizing to phone width with touch.
- Sound: every action has its effect, nothing plays before the first gesture, mute works.
- It works inside `<iframe sandbox="allow-scripts allow-pointer-lock">` with storage blocked.
- `?demo=1` matches the timeline, loops cleanly, shows no UI and plays no sound; `?test=1` exposes `window.__toy`.
- No console errors or WebGPU validation errors on WebGPU **and** on WebGL2 (force the fallback to check), and it stays smooth on a mid-range phone.

Put a heavy, instant strike and a readable jelly rhythm first. The one-blow 10 should be the best part.

More like this