Vend

Pick a plan. Press the button.

Fig. 1 — Vend, live captureOpen the live demo ↗12 s loop · recorded frame by frame on GPU

Field notes

A pricing page that is a Japanese vending machine on a rainy night. Each plan is a shelf of cans with an LED price, a coin switches monthly and yearly, and pressing a button drops the can into the tray, spins a 7777 roulette and opens the plan. Wipe the fogged glass with your cursor.

  • 3D
  • Pointer interaction
  • SaaS
  • Dark theme

Vend is the pricing page of Afterrain, a fictional background-jobs service. The page is a Japanese street vending machine at night, in the rain, with neon on the wall and its reflection in the wet street. The three plans are three shelves of cans, each with an amber LED price strip. Drop a coin in the slot to switch to yearly billing: the LEDs flip like split-flap. Press a button and that shelf's gravity rack rolls one can forward, it drops into the tray, the camera moves in, and the plan's features and call to action open beside it. A four-digit roulette over the tray spins as the can lands; 7777 shows a coupon code for paid plans, and the demo tour always wins.

Rain fogs the glass, and dragging the cursor or a finger across it wipes a clear track that fogs back over a few seconds, while the price strips always stay clear. After the first click there are synthesized sound effects (button, coin, the can landing, the roulette, the 7777 jingle), and the sound button adds rain and the hum of the neon. A 12-second tour runs until the visitor does something and then hands over mid-motion. Every 3D control has a keyboard twin, reduced motion shows Pro already in the tray, and without WebGL the page shows three plain pricing cards. The copy, prices and coupon live in one file, plans.ts.

How it's built

  • One frame from one clock: the whole machine is a Frame computed from a small state (which plan, when it was pressed, yearly or not) and the time. The demo tour, the preview recorder and a visitor use the same code path, so frame 0 equals frame 12, and the first click adopts the tour's state mid-motion.
  • A rack that loops: the vending row sits one can back when pressed and rolls exactly one can forward while the front can drops, so every can ends in a slot and the next vend starts from the same picture.
  • A wet street without a reflection pass: the machine and the wall are drawn a second time upside down under a ground plane whose puddles are see-through. Chrome, cans and glass reflect three's RoomEnvironment, and every label, LED and sign is a canvas texture drawn after the web fonts load, so there are no image files.
  • Fog with no memory: the fog is one canvas redrawn every frame, full fog and droplets first, then every wipe cut out with destination-out at a strength set by its age. It grows back without keeping pixels, and any frame can be drawn from the list of wipes.
  • Sound from nothing: every effect is synthesized with Web Audio, oscillators and filtered noise. The audio context is created only inside a real click or key, so the tour and the recorder stay silent.

Inside the prompt

Every file below is written out in full, so your agent creates the project exactly — no guessing, no missing pieces.

vend-pricing/
├─ .gitignore
├─ index.html
├─ package.json
├─ tsconfig.json
├─ vite.config.ts
└─ src/
   ├─ App.tsx
   ├─ index.css
   ├─ main.tsx
   ├─ vite-env.d.ts
   ├─ components/
   │  ├─ Machine.tsx
   │  ├─ Panel.tsx
   │  ├─ Scene.tsx
   │  └─ Street.tsx
   └─ lib/
      ├─ labels.ts
      ├─ live.ts
      ├─ plans.ts
      ├─ sfx.ts
      ├─ time.ts
      └─ timeline.ts

Recut it

  • Rename the product and rewrite the planssrc/lib/plans.ts → PRODUCT / HEADLINE / PLANS
  • Change the roulette odds, code or rewardsrc/lib/plans.ts → LUCKY
  • Change the yearly discountsrc/lib/plans.ts + src/lib/labels.ts → yearly / drawBilling()
  • Point the buttons at your checkoutsrc/components/Panel.tsx → href="#start"
  • Recolour the neon and LEDssrc/index.css + src/lib/labels.ts → @theme / AMBER, CYAN, MAGENTA
  • Thicker or thinner fog, a wider wipesrc/lib/labels.ts + src/lib/timeline.ts → drawFog() / WIPE_R / wipeStrength
  • Retune or silence a soundsrc/lib/sfx.ts → sfx / setAmbience()
  • Vend a different plan in the tour, or retime itsrc/lib/plans.ts + src/lib/timeline.ts → TOUR_TIER / TOUR_PRESS, TOUR_CLOSE
  • Move the camerasrc/components/Scene.tsx → useShots()

Also in the vault