Building a Balatro-lite game with vanilla web tech

I’ve spent countless hours playing Balatro, and I’ve always wanted to create my own game with similar mechanics. When TrinketOS announced a game jam, it was the perfect opportunity to make it happen.

TrinketOS is an Android launcher with built-in lore and a meta-game. The jam required building a “Tapp”—an HTML/JS/CSS app that runs inside TrinketOS’s webview. The month-long event ended up stretching to six weeks due to fewer entries than expected, which gave me more time to polish things.

The result was Stellante - a space zodiac themed card game. Play poker hands to progress through antes, buy jokers that modify scoring, and face boss blinds based on zodiac signs reimagined as spacecraft. The name comes from “Stellar” (space) and “Ante” (the poker term for progression).

You can play it at omgmog.net/stellante.

Building the foundation

I didn’t have a clear vision of where this would end up. I just knew I wanted to capture Balatro’s feel - that rough, shaky pixel aesthetic, the satisfying card animations, the way scoring builds up. I’d worked on web-based casino games professionally before, so I had a decent grasp of card rendering, deck management, standard card dimensions. That helped.

I made a list of the core features the game would need: card renderer, deck system, animation queue, poker hand evaluation, joker modifiers, shop phase, progression system, save/load. Then I started building them one by one. First the renderer - drawing random hands of cards on click. Then a proper deck to draw from. Animation queueing for card movements. Basic game loop with menu, play, and game over phases.

Next came scoring for poker hands (pairs, straights, flushes, etc.) and the Balatro-inspired bits: jokers with score modifying effects, a buy phase to purchase them between rounds, and a save/load system using localStorage. The save system also handles basic preferences for sound and music.

At this point it was functional but generic. Just poker with modifiers.

Space zodiac theming

Rather than copying Balatro’s aesthetic directly, I wanted something different. I settled on a space zodiac theme - reimagining the twelve zodiac signs as spacecraft types. Aries became a corvette (fast, assault-focused), Taurus a cruiser (industrial, scaling power), Leo a battlecruiser (commanding, demanding uniqueness).

I replaced the traditional card suits with elemental symbols. Fire, Earth, Water, and Air instead of hearts, diamonds, clubs, and spades. Abstract geometric shapes that matched the space setting. This tied directly into the faction system - jokers came in elemental variants, and the suits on the cards mattered for scoring.

I used ChatGPT to craft prompts, fed them into PixelLab for image generation, used Python for palette alignment, then back to PixelLab’s image-to-image generation for elemental variations. Tweaked prompts as I went, kept them consistent - same style, similar detail level, cohesive pixel-crunch aesthetic across all 12 zodiac ships and their variants.

I put face cards through the same process, with a final step in Photoshop to create the traditional mirrored/split layout for Jacks, Queens, and Kings. Backgrounds matched the elemental joker backgrounds.

I grabbed audio from itch.io - an 8-bit platformer music kit and various sound effect packs.

The jokers ended up being the most important part. 12 zodiac types, each with five versions: a prototype plus four elemental variants (Fire, Earth, Air, Water). That’s 60 jokers total. Each zodiac has its own scoring mechanic, and the elemental variants each have different effects from their prototype.

I built a set bonus system that rewards collecting either all four jokers of the same element (faction sets like “Fire Legion”) or all four elemental variants of one zodiac sign (line sets like “Aries Line”).

This changed the feel completely. Instead of playing cards, it felt like commanding a fleet. Instead of collecting jokers, I was acquiring ships with special abilities. Playing cards with flame symbols whilst commanding fire-element ships made more sense than traditional hearts and spades in a space setting.

Shop mechanics

Between each blind, there’s a buy phase for purchasing jokers. Runs start with 20 chips, earning more by beating blinds.

The shop offers a limited selection each round - just two jokers in the first two antes, then three afterwards. In ante 1, only prototype jokers appear. From ante 2 onwards, all 60 jokers become available. The shop won’t offer duplicates of jokers already owned, and there’s a limit of five jokers total.

I implemented faction discounts for pricing. Base cost for most jokers is 12 chips, but owning jokers of the same element makes matching variants cheaper - up to 2 chips off per owned joker of that faction. Committing to one element becomes financially rewarding, but limits strategic options.

Unwanted jokers sell for half their base cost. Rerolling the entire shop costs 5 chips and gives three new options. Adds another layer of resource management.

I wanted tension between short-term needs and long-term strategy. Early discounts push towards mono-element builds, but that can lock out zodiac abilities needed for later boss blinds.

Progression system

I moved from continuous rounds to Balatro’s ante and blind system. Each ante has three blinds with escalating score requirements. The third blind in each ante is a boss blind with special effects.

The boss blinds are zodiac-themed modifiers:

  • Aries: forces exactly 5 cards to be played
  • Gemini: deals some cards face down
  • Leo: makes royal cards (J/Q/K) score zero
  • Libra: treats duplicate ranks as singles
  • Pisces: only counts the lowest 5 ranks

Getting these effects working was fiddly. Boss blinds don’t just change scoring - they modify which cards can participate in hands. Libra, for example, breaks pairs by making duplicates count separately. Had to rewrite the hand evaluation logic to handle card filtering and per-card scoring modifications.

Boss blinds reuse the prototype/base ship sprite for each zodiac, with intro and outro animations when the blind starts and completes.

Tech stack

Building as a Tapp meant working within TrinketOS’s webview constraints. No native game engines, no heavy frameworks - just HTML/JS/CSS that could run efficiently inside the webview on Android handhelds with varying performance.

I built the game in TypeScript with modular architecture - separate systems for state management, input handling, rendering, animations, and game logic. For rendering, I used Canvas 2D for the cards and UI, with a WebGL shader for the cosmic background effect. The WebGL background has a fallback to a simple starfield for devices that don’t support it.

I used Vite for the fast local development server with hot module replacement, and to compile my TypeScript source into a portable JavaScript bundle. I used dirty rectangles to minimise redraws, animation pooling to reduce garbage collection, and a frame rate manager that adapts to device performance.

My build workflow was: run Vite build, then adb push the newly generated files to a connected Android device. This let me iterate quickly - make changes, rebuild, push to device, see results in situ within seconds. Development was mostly local using Chrome DevTools for initial testing, then I’d push builds to a few different Android devices and emulators to check real-world performance.

Some things didn’t translate well - text and UI scale gets messy on smaller screens, and I never finished gamepad support. It works fine with touch, mouse, and keyboard though.

Shipping it

I finished it enough to submit to the TrinketOS jam. The final build came to 7.4MB - compact enough to run smoothly as a Tapp. The game’s playable with 60 jokers, 12 zodiac boss blinds, progression through 12 antes, a working shop system, and save/load functionality.

Winners were decided by community votes on the TrinketOS Matrix. The creator of TrinketOS reviewed all the entries in this video:

I didn’t win, and I haven’t touched it since the jam ended. Mobile controls could be better, and the balance needs work. But it runs, the core loop works, and building a full game with vanilla web tech without a game engine was exactly the kind of challenge I wanted.

The space zodiac theme gave it enough personality to stand out from other Balatro-likes. I got to mess around with AI image generation workflows, proper state management in TypeScript, and all the fiddly bits of card game logic. Worth the six weeks.

You can play it in the browser at omgmog.net/stellante.

[Comments]

Want to comment? You can do so via Github.
Comments via Github are currently closed.

[Webmentions]

Want to reply? I've hooked up Webmentions, so give it a go!