Public
Fun merge puzzle game with cosmetic shop
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Merge Blossom 🌸

A fun, fast merge-puzzle game (2048-style: slide, match, grow blooms) with a cosmetic skin shop and real microtransactions via Stripe Checkout.

Live app: this val's HTTP endpoint (see the val's page on val.town for the exact URL — click the file index.tsx to see it, or check "Endpoints").

How it works

  • Game: 4x4 sliding merge grid. Arrow keys or swipe. Matching blooms merge and grow through stages (seed → sprout → ... → legendary bloom). Combos (multiple merges in one move) give a score multiplier.
  • Soft currency (🌷 Petals): earned automatically at the end of every game (floor(score / 50)). Spend them on a few of the skins.
  • Hard currency (💎 Gems): bought with real money via Stripe Checkout, in three packs. Spend them on premium skins.
  • Skins are 100% cosmetic — board/tile color themes only. No skin changes scoring or gameplay, so nobody is paying to win.
  • Players are tracked by an anonymous cookie (pid), no login required. State (petals, gems, best score, unlocked/active skin) lives in std/sqlite.

Turning on real payments

The shop UI and free (petal-based) skins work out of the box. Gem purchases are disabled until a Stripe test (or live) secret key is set:

👉 Add STRIPE_TEST_API_KEY here: https://www.val.town/x/dusty_medic/hono-jsx-starter/environment-variables?key=STRIPE_TEST_API_KEY

Once that's set, the "Buy Gems" section appears in the shop automatically (shopEnabled flips to true) and /api/checkout creates real Stripe Checkout sessions.

Fulfillment (crediting gems after a successful payment) happens two ways, either is enough on its own:

  1. /confirmation redirect — Stripe sends the shopper back here after checkout; the val verifies the session server-side and credits gems once (idempotent on stripe_session_id). This works with zero extra setup.
  2. Webhook (optional, more robust against a shopper closing the tab before the redirect) — point a Stripe webhook at /api/stripe-webhook for the checkout.session.completed event and set:

👉 Add STRIPE_WEBHOOK_SECRET here: https://www.val.town/x/dusty_medic/hono-jsx-starter/environment-variables?key=STRIPE_WEBHOOK_SECRET

Swap STRIPE_TEST_API_KEY for a live secret key when ready to charge real cards — no code changes needed.

What's here

index.tsx          Hono app: game page, player/session API, shop, Stripe checkout + webhook
public/
  favicon.svg       Static asset
  game.css          All styling (skin themes are CSS custom properties)
  game.js           Client-side game logic (vanilla JS, no build step)

Data model (std/sqlite, val-scoped)

  • players — id (cookie-based), petals, gems, best_score, games_played, unlocked_skins (JSON array), active_skin
  • purchases — one row per Stripe Checkout session, used to fulfill gems exactly once per payment

Extending this (Growth phase ideas, not built yet)

  • Daily challenge / streaks
  • Leaderboard (would need a display name, currently fully anonymous)
  • Push notifications for "come back and play"
  • Real accounts so progress isn't tied to one browser's cookie