Croissant Adventure: how I built a full web game in vanilla JavaScript
How I built Croissant Adventure, a vanilla JavaScript web game with a central world, minigames, characters, AI chat, Firebase and a custom engine.
Croissant Adventure is one of those projects that started as a playful idea and ended up becoming a fairly complete web game: a central world called Migalandia, selectable characters, outfits, minigames, login, persistence, music, parental controls and an AI-powered narrative assistant.
The goal was not to build a quick clone of an existing game. I wanted to create a small playable universe from scratch using vanilla JavaScript, HTML5 Canvas and CSS, without relying on engines such as Phaser or Unity. That made the project harder, but also much more valuable as a learning experience: I had to think about scenes, input, camera, assets, audio, state, menus, saving and user experience.
The idea: Migalandia as an adventure hub
The premise of Croissant Adventure is simple: the player enters Migalandia and can move through different areas of the map to discover adventures. Instead of launching a single minigame directly, the project has a structure closer to a lightweight RPG:
- a welcome screen,
- login and registration,
- character selection between Croiso and Croisa,
- outfit selection,
- a central map with regions,
- interactive zones that open minigames,
- score, coins and progress,
- a narrative chat that recommends adventures.
That approach helped me practise something very important in software development: separating the game into systems. Not everything lives in one file and not everything depends on one button. There is a base architecture that works like a game engine, and then each minigame plugs into that structure.
Technical stack
The project is built with direct web technologies:
- HTML5 for the main screens.
- CSS3 for UI, responsive layout, animations and world styling.
- Vanilla JavaScript for all game logic.
- HTML5 Canvas as the main rendering surface.
- Firebase for authentication, database and progress saving.
- localStorage for session, character selection, outfits and preferences.
- WebAudio / audio assets for music, effects and feedback.
- AI chat with an internal narrative system and an external integration.
Choosing not to use a framework had a cost: many things that a game engine gives you for free had to be built by hand. But it also forced me to understand much better how a web game works internally.
The custom engine
The central piece of the project is the Game class. It coordinates the canvas, main loop, input,
scenes, score, audio and part of the persistent state.
The canvas uses a base resolution of 1200 x 800, and the game scales mouse coordinates so clicks still work correctly even when the canvas is rendered at a different size on screen. That detail may look small, but it matters: without proper input scaling, a button can be displayed in one position and receive the click somewhere else.
The engine also registers scenes through a simple system:
registerScene(name, scene)
switchScene(name)
Every important screen is a scene: main menu, world map, minigames, admin panel or tutorial. This allows the game to enter and exit each experience without breaking the global state.
World map: regions, camera and minigames
The map of Migalandia is designed as a world with multiple regions. There is a central plaza and areas such as Chocolate Forest, Cookie Mountain, Caramelized Beach, Pastry City, Meringue Lake, Caramel Cave, Sugary Meadow and Brownie Volcano.
Each region has its own color, description, music, visual effects and minigame zones. The player can move through the map with the keyboard and approach interactive points to start an adventure.
The map also includes:
- a camera that follows the player,
- transitions between regions,
- a minimap,
- environmental effects,
- obstacles,
- coins distributed across the world,
- interaction prompts,
- routes between minigame zones.
This was one of the clearest lessons of the project: a game starts feeling larger when it has a place that connects its systems. The hub turns separate minigames into an experience with continuity.
A collection of minigames
Croissant Adventure is not built around just one mechanic. The repository includes a wide collection of minigames, each one with its own rules, state and scoring logic.
Some examples:
- Coin Collector: collect coins against the clock.
- Roulette: prize wheel and randomness.
- Chess: a chess match against a simple AI.
- Maze: a generated maze with a time limit.
- Shooter: targets, shooting and high score.
- Platform: platforms, jumps, coins and goal.
- Platform Lava: a more intense variant with lava.
- Memory: card matching scored by moves and time.
- Snake: classic snake with food and growth.
- Puzzle: sliding puzzle mechanics.
- Rhythm: musical minigame with notes, combo and accuracy.
- Tower Defense: waves, enemies, bosses, towers, money and lives.
- Fishing: arcade fishing with score.
- Surfing: obstacles and collectibles.
- Cave Explorer: cave exploration with a grid and objective.
- Paint Game: an internal drawing canvas with brushes, colors and challenges.
- Trivia Game: questions and answers.
- Story Teller: subgames such as Tic-Tac-Toe, Connect 4, Memory and Battleship.
- Tutorial: guided introduction to the game.
- Admin Panel: stats and configuration screen.
The challenge was not only programming mechanics. It was making all of them coexist inside the same engine, with clean entry and exit points, shared scoring, music and a consistent way to return to the map.
Characters, outfits and session
Personalization is an important part of the experience. The player can choose a character and an outfit before entering the game. That selection is saved and the engine then loads the correct sprites to render the player inside the canvas.
The web app also includes login, registration and special test access. The game stores data in
localStorage and can synchronize progress with Firebase: score, coins, achievements and parental
control data.
That makes the project feel closer to a playable web app than to an isolated demo.
AI chat and narrator
One of the most interesting systems is the narrative chat. The game includes an assistant called Trash who acts as a guide inside Migalandia. The user can write what kind of adventure they want, and the system detects keywords to recommend related minigames.
For example, if the player talks about strategy, it can suggest chess or tower defense. If they talk about rhythm, it can guide them to the music minigame. If they mention mazes, the system connects that intent with Maze.
The architecture combines:
- conversational memory,
- keyword detection,
- predefined contextual responses,
- game recommendations,
- quick-access buttons,
- a local fallback if the external AI fails.
I found this especially interesting because it brings together narrative, UX and product logic. The chat is not just decoration: it tries to help the player find the adventure that matches their mood.
Parental controls and admin panel
I also added a parental control system. It can define time limits, age and educational tasks. When time runs out, the game can show adapted activities, such as writing exercises.
The admin panel and statistics make it possible to review progress, minigames played, usage time and configuration. For a game aimed at a young audience, that adds a real product layer: not only entertainment, but also control over the experience.
Technical challenges
The biggest challenge was accumulated complexity. Each minigame in isolation is manageable, but once you combine map, scenes, login, audio, Firebase, chat, assets and responsive behavior, architectural problems start appearing.
Some especially delicate points were:
- cleaning state correctly when changing minigames,
- preventing inputs from one scene from affecting another,
- scaling canvas clicks and coordinates,
- keeping performance acceptable with many assets,
- avoiding too much duplicated logic between minigames,
- coordinating audio, regional music and effects,
- making the map feel like a real hub instead of a simple menu.
I also learned that small game projects easily fall into "while I am here, I can add one more mechanic". That gives the project life, but it also forces you to be more disciplined with the structure.
What I would improve for production
Croissant Adventure works as a web project and learning experience, but if I wanted to turn it into a more serious product I would make several changes:
- migrate part of the code to ES modules or TypeScript,
- keep sensitive configuration away from the client,
- optimize image and audio assets,
- add lazy loading per minigame,
- define a stricter minigame API,
- create a more robust save-state system,
- add tests for critical rules,
- improve accessibility and keyboard navigation,
- prepare a PWA for offline play.
The interesting part is that many of these improvements are natural because the project already has a modular base. I would not need to reinvent it from scratch, only organize and harden what already exists.
What I learned
This project taught me much more than "building a game". It forced me to think like a product developer:
- how to turn a playful idea into a usable system,
- how to split a large project into maintainable pieces,
- how to design an experience so the user understands where they are,
- how to combine narrative, gameplay, AI and persistence,
- how to decide what stays as a demo and what needs more serious architecture.
It also confirmed something I really like about programming: when you build without too many layers on top, you truly understand what is happening. Vanilla JavaScript can be less comfortable than a framework, but for learning engine logic, state and interaction, it is incredibly useful.
Project links
For me, Croissant Adventure is a mix of game, technical laboratory and small fictional universe. It is not just a minigame: it is proof that HTML, CSS and JavaScript can support a fairly ambitious web experience when the problem is split into the right systems.