A browser-based isometric island builder with the soft, sun-bleached look of Mykonos. Vanilla ES modules, no bundler, mobile-friendly.
See the codeA browser-based isometric island builder with the soft, sun-bleached Mediterranean look of Mykonos: cobalt-blue domes on whitewashed walls, bougainvillea spilling over stone, olive trees, windmills, narrow cobble paths, and a sea you can carve with a click.
It's a small, self-contained creative toy — drop blocks on a 14×14 grid and a tiny village builds itself in front of you. There's no goal, no resource grind, no scoring; just the puzzle-piece pleasure of arranging things until they look right.
🌐 Play it: https://mykonos-island-voxels.netlify.app

localStorage and
re-loaded on the next visit.node_modules —
open index.html and it runs.| Input | Action |
|---|---|
| Click | Place selected asset |
| Drag | Brush-place across cells |
| Right click | Erase tile |
| Right drag | Brush-erase |
| Shift + drag | Pan camera |
| Scroll wheel | Zoom |
H / V | Flip the placement preview |
E | Toggle erase mode |
G | Toggle grid overlay |
1–5 | Switch palette categories |
S / R | Save / reset |
| Gesture | Action |
|---|---|
| Tap | Place selected asset |
| Drag | Brush-place across cells |
| Long-press (~420 ms) | Erase the tile under your finger |
| Two-finger pinch | Zoom |
| Two-finger drag | Pan camera |
The project is plain HTML / CSS / ES modules — there's no build step
required to develop on it. Because browsers refuse to load ES modules
from file:// URLs, you do need to serve it over HTTP. Pick whichever
of these is easiest:
# any one of these from the project root:
python3 -m http.server 8000
npx serve .
npx http-server -c-1 .
Then open http://localhost:8000.
The site is deployed to Netlify. The included netlify.toml and
netlify-build.mjs produce a clean dist/ folder containing only the
runtime files (no design references, no .DS_Store, no .webp
duplicates) and ship the right cache headers (immutable for assets,
must-revalidate for HTML/CSS/JS).
netlify deploy --prod
.
├── index.html # entry point
├── styles.css # the entire UI (no framework)
├── src/
│ ├── main.js # boot, asset loading, starter scene
│ ├── config.js # grid size, tile dims, palette, debug flags
│ ├── core/
│ │ ├── Game.js # game state + tool dispatch
│ │ ├── Camera.js # pan / zoom / change notifications
│ │ ├── Renderer.js # layered canvas caching + animations
│ │ └── InputManager.js # mouse + touch + keyboard
│ ├── grid/
│ │ ├── IsoGrid.js # screen ↔ cell math
│ │ └── TileMap.js # terrain + objects, occupancy index
│ ├── building/
│ │ └── PlacementSystem.js
│ ├── assets/
│ │ ├── assetManifest.js # the 75+ asset definitions
│ │ ├── assetLoader.js # PNG → display canvas + shadow canvas
│ │ ├── imageToAsset.js # silhouette extraction, anchor inference
│ │ └── voxelRenderer.js # procedural fallback when PNGs missing
│ ├── ui/
│ │ ├── UIManager.js
│ │ ├── Toolbar.js
│ │ ├── AssetPalette.js
│ │ ├── HUD.js
│ │ └── Audio.js # WebAudio clip routing + debouncing
│ └── persistence/
│ └── SaveSystem.js
├── assets/ # PNG asset pack (pre-generated)
├── *.ogg # placement / UI sound effects
├── netlify.toml
└── netlify-build.mjs
A few choices worth flagging if you want to hack on the renderer:
Renderer.js keeps four cache canvases:
a screen-space backdrop + vignette pair (rebuilt on resize), a
world-space platform (rebuilt on grid resize), a world-space terrain
layer (rebuilt when the terrain version counter changes), and a
world-space static-objects layer (rebuilt on add/remove). Each frame
the renderer composites these caches and overlays only the currently
animating tiles. Idle frames are essentially free.world × CACHE_SCALE (2× on standard displays, 3× on retina) so
that even after the camera scales them up, the pixels are at or
near final on-screen resolution. The asset displayCanvases are
pre-rendered at up to 6× their reference size, so detail flows
through without any "softening" intermediate.TileMap. Object lookup and free-cell
checks are O(1) per cell instead of O(N) over the object list.markDirty() is called from camera /
input / tool transitions; the loop early-exits when the scene is
static and no animations are pending.PRs welcome. Please:
node_modules for the runtime.ctx.filter, ImageBitmap shenanigans, or
anything that would re-introduce frame drops at heavy scenes.
The renderer's caching invariants are load-bearing.MIT — see LICENSE.
The PNG asset pack in assets/ is released under the same license,
generated for this project. Audio clips were authored separately;
see file metadata if you need to attribute them.
JavaScript
85.8%
CSS
10.1%
Python
2.2%
HTML
1.9%
A browser-based isometric island builder with the soft, sun-bleached look of Mykonos. Vanilla ES modules, no bundler, mobile-friendly.
See the codeA browser-based isometric island builder with the soft, sun-bleached Mediterranean look of Mykonos: cobalt-blue domes on whitewashed walls, bougainvillea spilling over stone, olive trees, windmills, narrow cobble paths, and a sea you can carve with a click.
It's a small, self-contained creative toy — drop blocks on a 14×14 grid and a tiny village builds itself in front of you. There's no goal, no resource grind, no scoring; just the puzzle-piece pleasure of arranging things until they look right.
🌐 Play it: https://mykonos-island-voxels.netlify.app

localStorage and
re-loaded on the next visit.node_modules —
open index.html and it runs.| Input | Action |
|---|---|
| Click | Place selected asset |
| Drag | Brush-place across cells |
| Right click | Erase tile |
| Right drag | Brush-erase |
| Shift + drag | Pan camera |
| Scroll wheel | Zoom |
H / V | Flip the placement preview |
E | Toggle erase mode |
G | Toggle grid overlay |
1–5 | Switch palette categories |
S / R | Save / reset |
| Gesture | Action |
|---|---|
| Tap | Place selected asset |
| Drag | Brush-place across cells |
| Long-press (~420 ms) | Erase the tile under your finger |
| Two-finger pinch | Zoom |
| Two-finger drag | Pan camera |
The project is plain HTML / CSS / ES modules — there's no build step
required to develop on it. Because browsers refuse to load ES modules
from file:// URLs, you do need to serve it over HTTP. Pick whichever
of these is easiest:
# any one of these from the project root:
python3 -m http.server 8000
npx serve .
npx http-server -c-1 .
Then open http://localhost:8000.
The site is deployed to Netlify. The included netlify.toml and
netlify-build.mjs produce a clean dist/ folder containing only the
runtime files (no design references, no .DS_Store, no .webp
duplicates) and ship the right cache headers (immutable for assets,
must-revalidate for HTML/CSS/JS).
netlify deploy --prod
.
├── index.html # entry point
├── styles.css # the entire UI (no framework)
├── src/
│ ├── main.js # boot, asset loading, starter scene
│ ├── config.js # grid size, tile dims, palette, debug flags
│ ├── core/
│ │ ├── Game.js # game state + tool dispatch
│ │ ├── Camera.js # pan / zoom / change notifications
│ │ ├── Renderer.js # layered canvas caching + animations
│ │ └── InputManager.js # mouse + touch + keyboard
│ ├── grid/
│ │ ├── IsoGrid.js # screen ↔ cell math
│ │ └── TileMap.js # terrain + objects, occupancy index
│ ├── building/
│ │ └── PlacementSystem.js
│ ├── assets/
│ │ ├── assetManifest.js # the 75+ asset definitions
│ │ ├── assetLoader.js # PNG → display canvas + shadow canvas
│ │ ├── imageToAsset.js # silhouette extraction, anchor inference
│ │ └── voxelRenderer.js # procedural fallback when PNGs missing
│ ├── ui/
│ │ ├── UIManager.js
│ │ ├── Toolbar.js
│ │ ├── AssetPalette.js
│ │ ├── HUD.js
│ │ └── Audio.js # WebAudio clip routing + debouncing
│ └── persistence/
│ └── SaveSystem.js
├── assets/ # PNG asset pack (pre-generated)
├── *.ogg # placement / UI sound effects
├── netlify.toml
└── netlify-build.mjs
A few choices worth flagging if you want to hack on the renderer:
Renderer.js keeps four cache canvases:
a screen-space backdrop + vignette pair (rebuilt on resize), a
world-space platform (rebuilt on grid resize), a world-space terrain
layer (rebuilt when the terrain version counter changes), and a
world-space static-objects layer (rebuilt on add/remove). Each frame
the renderer composites these caches and overlays only the currently
animating tiles. Idle frames are essentially free.world × CACHE_SCALE (2× on standard displays, 3× on retina) so
that even after the camera scales them up, the pixels are at or
near final on-screen resolution. The asset displayCanvases are
pre-rendered at up to 6× their reference size, so detail flows
through without any "softening" intermediate.TileMap. Object lookup and free-cell
checks are O(1) per cell instead of O(N) over the object list.markDirty() is called from camera /
input / tool transitions; the loop early-exits when the scene is
static and no animations are pending.PRs welcome. Please:
node_modules for the runtime.ctx.filter, ImageBitmap shenanigans, or
anything that would re-introduce frame drops at heavy scenes.
The renderer's caching invariants are load-bearing.MIT — see LICENSE.
The PNG asset pack in assets/ is released under the same license,
generated for this project. Audio clips were authored separately;
see file metadata if you need to attribute them.
JavaScript
85.8%
CSS
10.1%
Python
2.2%
HTML
1.9%