Colorfield logo

Stackcurious

Published on

Pocket Switzerland: a paper country in 3D, explored by hot-air balloon

Authors
The map at dusk: the red hot-air balloon drifts over the Swiss Plateau between Bern and Gruyères, a red train follows the line, and the violet Alps stand out against an orange sky.

Pocket Switzerland is a simplified Switzerland in 3D that you explore by hot-air balloon. It runs in the browser, on a laptop as well as on a phone, in five languages. It started as a map to find my way between the valleys, and it has become a small paper country with towns to discover, hidden chests, a nature guide and even a snowboard mini-game.

Here is the story, a look under the hood, and a few things I learned along the way.

The story of the project

Switzerland has been my home for a few years now (the move is another story), and I still discover it every day as if it were the first.

With this project, I wanted to share a feeling that comes from childhood: the urge to look at everything with fresh eyes, and the sleep that will not come on the night before a hike. But also the richness of the heritage, and the little hidden treasures you find in every town.

Maps have always fascinated me. The Dufour maps, the old maps hanging in railway stations: run your finger over the place names and your imagination starts wandering. To find that feeling again, I went back to the old role-playing games of my childhood, whose spare graphics left everything to the imagination. Hence a low-poly map, made of a few facets rather than thousands of details.

When I arrived in Switzerland, the landscapes amazed me, but they also left me a bit lost. Picturing the lie of the land here has nothing to do with what I knew in Belgium. Hike after hike, I started to connect the valleys, but placing myself on the scale of the whole country remained hit-and-miss. The swisstopo and PeakFinder apps helped a lot. Still, I wanted a pared-down view, on the scale of the valleys. So the project started with a very simple map and a few markers.

Then I thought it could get a more playful side, with a hot-air balloon to get around. The first balloon was truly ugly, but something had been set in motion. The next evening, I worked on nothing but the balloon: its shape, the way it moves, the sound of the burner and the wind. Then came a few buildings for the towns, the lakes, the rivers, the clouds, the night, the winter, the IC1 line from Geneva to St. Gallen... It started to look like a miniature world, so I added routes to follow through the towns.

The last piece was a soul for this little world. I found it in Château-d'Œx, in the Pays-d'Enhaut, the Swiss capital of hot-air ballooning, where paper cutting has been practised for generations. The rest followed: the logbook, the stamps, the stitched patches of the backpack, the relief folded like paper. Paper cutting met low poly, and Pocket Switzerland was born.

Under the hood

The map is a React Three Fiber scene written in strict TypeScript, built with Vite. drei brings the helpers, @react-three/rapier the physics (the balloon bumps into the relief, and it can land on the roof of a moving train), and zustand holds the game state.

One mesh for a whole country

The relief comes from swisstopo's DHM200 elevation model. A Python pipeline turns it into a PNG heightmap where the red and green channels hold the altitude as a 16-bit integer, in meters, and the blue channel is a mask (outside the country, land, lake). The client builds a single terrain mesh from that grid, at 200 m or 500 m resolution depending on the quality setting. One unit is one kilometer, and the altitude is exaggerated by 1.6 so that the Alps read better from above. The Matterhorn and the Jungfrau only have to be recognisable by their shape.

Lakes, rivers, glaciers and the railway line come from the same kind of pipeline: small Python scripts whose output is committed, most of them with a --check mode that verifies it without rewriting anything.

Performance is a feature

The reference machine is a MacBook Air with 8 GB of RAM. The budgets are written down: 60 fps nominal and never below 30, fewer than 300 draw calls, a heap under 500 MB, an initial load under 5 seconds. A performance regression is a blocking bug, and the fix is to lower a setting (pixel ratio, frame cap, effect density, streaming distance). A special URL, ?perf, flies the most demanding route, Interlaken to Zermatt by the Jungfrau, and reports the numbers.

A monorepo with one-way dependencies

The project is a pnpm monorepo with six workspaces. The map, the content site, the mini-games, the UI package, the content and a few shared utilities. Dependencies only go one way: the site depends on the map, the map on the mini-games, the mini-games on the UI.

The interface lives in its own package, with about a hundred Storybook stories. A component receives props, and a thin connector on the app side reads the store and does the wiring. It sounds bureaucratic, but it is what lets the gallery show every window, toast and card of the game without the game around it.

Astro and Starlight for the content

The content site is Astro, with Starlight for the pages, the search and the dark mode. It is also the only thing we deploy: the 3D map is a client-only React island inside an Astro page. One domain, the language in the path (/fr/, /de/, /it/, /en/, /rm/).

The content is plain Markdown in its own package, with French as the reference. Each translation carries a hash of the French it was translated from, so when I change a French page, the check fails until the German, Italian and English are reviewed again. Romansh is translated by Apertus, the Swiss open model, through Public AI, with a lock file that keeps the corrections written by hand.

There is no server function at all. The sites from Switzerland Tourism are fetched locally and committed as snapshots, so the API key never leaves my machine. Every page has a Content Security Policy, and an end-to-end test fails when a page breaks it.

Tests, decisions and AI

Logic goes into small pure functions, written test first with Vitest. Playwright only covers the critical journeys, a bit more than a hundred tests. Every structural decision is an architecture decision record: there are 67 of them now, in French, like the rest of the documentation.

The code is written with Claude Code, and the illustrations are generated with gpt-image. Two rules came out of that. First, nothing is copied: references are only there for general inspiration (paper, grain, folds, light). Second, no registered trademark appears without a partnership, so the specialities and transports are named after what they are.

Accessibility

A 3D canvas is a black box for a screen reader, so the rule is simple: every piece of information in the scene has an equivalent in the DOM.

The labels floating over the towns have a mirror list of the places in view, which is also what makes discovering a place by click possible with the keyboard. Discoveries are announced in an aria-live region, all of them when you find several at once, joined with Intl.ListFormat in the current language. You can fly with the keyboard only (WASD, ZQSD or the arrows, Space for the burner), and Playwright tests check the tab order, for instance that decorative elements never steal the focus.

Contrast is measured. A small tool reads the stylesheet itself, resolves var() and color-mix() like the browser does, and gives the WCAG ratio for any pair of tokens, by day and by night. Every number written in a CSS comment comes from it.

prefers-reduced-motion is checked by an end-to-end test that emulates the preference and asserts it reached the page. The rule we settled on: a shape stays, a movement goes.

One lesson from the first audit: an aria-label replaces the content it names. The approach banner said "Open the logbook" and hid the name of the place, even though the name was right there in the DOM. A label on an element that carries information has to contain that information.

Each phase ends with a manual audit (contrast, visible focus, keyboard, VoiceOver, reduced motion). The VoiceOver pass is still on my list for the most recent features. And the Romansh is a machine translation that no Romansh speaker has reviewed yet, which the credits say plainly.

Behind the scenes

The behind the scenes page tells the curious what the map can do and where its data comes from. In short: 63 places to find, chests hidden in the landscape, quests that lead from one step to the next, a speciality to pick up in each canton, a ticket for each transport, and a nature guide with 31 species you meet depending on the altitude and the slope. The chough, a small mountain crow, can become your flying companion, and Clotilde the marmot takes you through your first flight.

The data is open, and free. The relief and the glaciers from swisstopo, the border and the lakes from the Federal Statistical Office (through the swiss-maps package by Interactive Things), the railway network from the Federal Office of Transport on opendata.swiss, the places from Switzerland Tourism. Sometimes you have to ask for an access key, and that is the only hurdle. In Switzerland, access to open data is exceptional.

During development, a toolbar opens in the bottom right corner. It tunes the seven numbers of the flight feel, teleports to any chest or place, forgets a discovery to replay it, gives or takes a reward, triggers a shooting star, and compares draw calls, triangles and memory with the budgets.

Releases

The game is in alpha, and each release has its notes on the what's new page.

  • Alpha 0.1 (25.09.2026): the foundations, Switzerland in 3D, the balloon, the destinations and the flight log.
  • Alpha 0.2 (09.10.2026): XP, levels and titles, feats, rare treasures and a first quest in the footsteps of William Tell (video).
  • Alpha 0.3 (15.10.2026): transports to land your basket on, from the Landwasser viaduct to the Lake Geneva steamer, and a speciality in every canton (video).
  • Alpha 0.4 (22.10.2026): "Schuss! with Clotilde", a snowboard run down from the Diablerets glacier, also playable on its own at pocketsnowboard.com (video). Take off from www.suissedepoche.ch, and tell me what you found up there.