Isometric city animation for a website
A small city seen from above at an angle, with no vanishing point: blocks of towers, streets with traffic, windows switching on at dusk. How an isometric city is drawn, a canvas starter you can paste and run, and the skill that builds a finished city hero.
Free skills to start. Pro is $79 a year for every skill, or $9.99 a month for 10. See pricing
What is a isometric city?
An isometric city shows buildings from above and to one side, with every parallel line kept parallel. Nothing shrinks with distance, so the whole city stays readable at once, like a map that has grown up into 3D. It is the view of SimCity 2000 and a long line of city builders since, and it reads as planned, tidy and a little toy-like.
On a website it suits proptech, real estate, urban planning, mobility, logistics, smart-city and mapping products: anything about places, infrastructure or many things happening at once. Motion brings it to life: towers rising, windows lighting floor by floor, cars running the streets.
How the effect works
Isometric projection turns a grid position into a screen position with two sums. Screen x is (x − y) × half the tile width; screen y is (x + y) × half the tile height, minus the height above the ground. With tiles twice as wide as they are tall you get the 2:1 angle that almost all isometric game and pixel art uses, because its edges step cleanly across pixels.
Each building is a box: draw its two front faces and its roof as three filled shapes, the lit face lighter than the shaded one. A 2D canvas has no depth buffer, so draw from back to front: sort buildings and cars by x + y and paint them in that order, and nearer things cover farther ones. That is the painter’s algorithm.
01
Lay out the plan
A grid of lots with a street every few rows and columns. Heights rise toward the middle for a downtown, and a few lots stay parks.
02
Project to the screen
screenX = (x − y) × tileW / 2 and screenY = (x + y) × tileH / 2 − z, with tileH half of tileW.
03
Draw back to front
Sort everything by x + y and draw each box as two side faces and a roof, shaded by which way they face.
04
Animate the details
Windows that switch on and off on their own slow clocks, and cars that move along the street lines, headlights coming toward you and tail lights going away.
HTML
An isometric city on a 2D canvas
A repeatable city plan of extruded blocks around a taller downtown, drawn back to front, with windows switching on and off and cars running the streets. Change N for a bigger city and CARS for more traffic. Reduced motion gets one still frame.
<canvas id="city" style="position:fixed;inset:0;width:100%;height:100%;background:#0e1228"></canvas>
<script>
const canvas = document.getElementById('city')
const g = canvas.getContext('2d')
const N = 13, CARS = 26 // N cells a side; a street every 4th row and column
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
const rand = (i) => { const x = Math.sin(i * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x) } // repeatable random
const isStreet = (i) => i % 4 === 0
// The plan: every lot off the streets gets a height, tallest near the middle; a few are parks
const lots = []
for (let i = 0; i < N; i++) for (let j = 0; j < N; j++) {
if (isStreet(i) || isStreet(j)) continue
const d = Math.hypot(i - N / 2, j - N / 2) / (N / 2)
const r = rand(i * 31 + j)
lots.push({ i, j, park: r < 0.1, floors: Math.round(1 + Math.max(0, 1 - d) * 7 * (0.4 + r) + rand(i + j * 17) * 2), warm: r > 0.5 })
}
// Cars drive along the streets; dir is +1 or -1 along the street, lane keeps them apart
const streets = []
for (let k = 0; k < N; k += 4) streets.push(k)
const cars = Array.from({ length: CARS }, (_, n) => ({
alongX: n % 2 === 0, line: streets[n % streets.length], dir: rand(n) < 0.5 ? 1 : -1,
start: rand(n + 99) * N, speed: 0.6 + rand(n + 7) * 0.6,
}))
let w, h, dpr, tw, th, fh, ox, oy // tile width and height, floor height, origin
function size() {
dpr = Math.min(devicePixelRatio, 2)
w = canvas.width = innerWidth * dpr
h = canvas.height = innerHeight * dpr
tw = Math.min(w * 0.85, h * 1.25) / N; th = tw / 2; fh = th * 0.5
ox = w / 2; oy = h / 2 - (N * th) / 2 + fh * 3 // nudged down to leave room for the towers
}
size()
// Isometric projection: grid x and y run down-right and down-left, z goes straight up
const iso = (x, y, z = 0) => [ox + (x - y) * tw / 2, oy + (x + y) * th / 2 - z]
function poly(color, ...pts) {
g.fillStyle = color; g.beginPath(); g.moveTo(...pts[0])
for (const p of pts.slice(1)) g.lineTo(...p)
g.closePath(); g.fill()
}
function building(b, t) {
const m = 0.12, i = b.i + m, j = b.j + m, s = 1 - 2 * m, z = b.floors * fh // inset from the lot edge
if (b.park) { poly('#1f3a3a', iso(i, j), iso(i + s, j), iso(i + s, j + s), iso(i, j + s)); return }
poly('#2b3260', iso(i, j + s), iso(i + s, j + s), iso(i + s, j + s, z), iso(i, j + s, z)) // left face, in shade
poly('#4a5389', iso(i + s, j), iso(i + s, j + s), iso(i + s, j + s, z), iso(i + s, j, z)) // right face, lit
poly('#6b75b0', iso(i, j, z), iso(i + s, j, z), iso(i + s, j + s, z), iso(i, j + s, z)) // roof
// Windows: two per face per floor, each switching on and off on its own slow clock
for (let f = 0; f < b.floors; f++) for (let c = 0; c < 2; c++) for (let side = 0; side < 2; side++) {
const seed = b.i * 977 + b.j * 131 + f * 7 + c * 3 + side
const on = rand(seed + Math.floor(t / (4 + rand(seed) * 6) + rand(seed + 1))) > 0.55
if (!on) continue
g.fillStyle = b.warm ? '#ffd38a' : '#bfe3ff'
const u = (0.2 + c * 0.4) * s, z0 = f * fh + fh * 0.3, z1 = z0 + fh * 0.45
const at = side ? (a, zz) => iso(i + a, j + s, zz) : (a, zz) => iso(i + s, j + a, zz)
poly(g.fillStyle, at(u, z0), at(u + 0.2 * s, z0), at(u + 0.2 * s, z1), at(u, z1))
}
}
function car(c, t) {
const along = (((c.start + c.dir * c.speed * t) % N) + N) % N
const lane = c.line + 0.5 + c.dir * 0.18
const [x, y] = c.alongX ? [along, lane] : [lane, along]
const [sx, sy] = iso(x, y, 1.5 * dpr)
g.fillStyle = c.dir > 0 ? '#fff4d8' : '#ff5d5d' // coming toward you: headlights; going away: tail lights
g.shadowColor = g.fillStyle; g.shadowBlur = 8 * dpr
g.beginPath(); g.arc(sx, sy, 2 * dpr, 0, Math.PI * 2); g.fill()
g.shadowBlur = 0
}
function frame(now) {
const t = still ? 6 : now / 1000
g.fillStyle = '#0e1228'; g.fillRect(0, 0, w, h)
poly('#141933', iso(0, 0), iso(N, 0), iso(N, N), iso(0, N)) // streets
for (const b of lots) poly('#1d2342', iso(b.i, b.j), iso(b.i + 1, b.j), iso(b.i + 1, b.j + 1), iso(b.i, b.j + 1))
// Painter's order: draw from the back (small x + y) to the front, cars and buildings together
const items = lots.map((b) => ({ k: b.i + b.j + 1, draw: () => building(b, t) }))
for (const c of cars) {
const along = (((c.start + c.dir * c.speed * t) % N) + N) % N
items.push({ k: along + c.line + 0.5 + 0.01, draw: () => car(c, t) })
}
items.sort((a, b) => a.k - b.k).forEach((it) => it.draw())
if (!still) requestAnimationFrame(frame)
}
addEventListener('resize', () => { size(); if (still) frame(0) })
requestAnimationFrame(frame)
</script>A starting point written for this page, free to use in your own projects. The skills below build the finished version with checks, fallbacks and a recorded video.
What to get right on a real page
01
Keep one angle
Mixing true 30° isometric with 2:1 tiles, or adding any perspective, makes a city look warped. Pick one projection and use it for every building, car and shadow.
02
Sort with care
Ordering by x + y works for boxes that fit inside one tile. Buildings that span several tiles, or cars crossing between tiles, can draw in the wrong order: split big ones into tile-sized pieces, or move to WebGL and its depth buffer.
03
Keep the copy readable
A busy city full of lights fights a headline set over it. Put the city beside the copy, or wash it toward the background behind the text.
04
Hold a still frame for reduced motion
Respect prefers-reduced-motion: show one finished frame instead of the loop. A visitor who turned motion off still gets the look.
Skills that build a isometric city
A finished isometric city hero with your colors, fonts, logo and words, and a seamless 1080p loop for launch posts.
- Isometric City HeroClaude CodeCodexCursor+3 more agents
Motion Heroes · Pro
Isometric City Hero
Animated WebGL hero · page + embed + 1080p video
A small city on a survey grid, planned from a seed, beside your headline. The blocks rise out of the plan from the middle outward, the sun sets and long shadows sweep across the streets, windows light up floor by floor (warm homes, cool offices, shop fronts), street lamps come on and cars run every street with headlights and tail lights. On one parcel a proposed tower in your accent color fills in floor by floor each loop, with a ring pulsing round its lot. Drawn in raw WebGL1, with 27 documented settings.
Worth knowing: The plan is generated from a seed, so it will not map a real city or address, and the buildings are simplified blocks.
Needs: Node.js 18+, Chrome (or Chromium/Edge), and ffmpeg for the video
See what Isometric City Hero makes
Install
Build a isometric city with your agent
Each skill is a SKILL.md folder, so it installs the same way in Claude Code, Codex, Cursor and other agents. The example below uses Isometric City Hero; each skill page has its own prompt.
1. Paste the install prompt
Your agent runs the installer. The first time, it shows a sign-in link so you can approve it in the browser.
Install the "Isometric City Hero" skill from Lookbk (https://lookbk.dev/skills/isometric-city-hero). 1. Run: curl -fsSL https://lookbk.dev/cli.js | node - add isometric-city-hero 2. If it prints a sign-in link, show it to me. When I say I approved it, run the same command again. 3. When the install finishes, read the SKILL.md it installed and tell me what you need from me to make the first result.
Or run it in a terminal
curl -fsSL https://lookbk.dev/cli.js | node - add isometric-city-hero2. Start a new session
Agents load skills when a session starts. The installer writes the folder where your agent looks:
- Claude Code
- ~/.claude/skills/isometric-city-hero
- Codex
- ~/.agents/skills/isometric-city-hero
- Cursor
- ~/.agents/skills/isometric-city-hero
3. Ask for the result
Describe it in plain words, for example:
Build an Isometric City hero for Parcelwise, a zoning and site-modelling platform: midnight navy, mint accent, the proposed tower on lot 14-B, headline 'See the block before it's built.', and record a 12-second loop.
Another agent? Install a skill in any agent
FAQ
Questions
Still stuck? The guides cover installing, agents and licensing in more detail.
Read the guidesWhat is the difference between isometric and perspective?
Perspective makes far things smaller, and parallel lines meet at a vanishing point. Isometric keeps every size and every parallel line the same across the scene, so a building at the back is drawn exactly like one at the front.
Is isometric art really 30 degrees?
True isometric uses 30° lines. Most game and pixel art uses 2:1 tiles, about 26.6°, which step cleanly across pixels and are still called isometric. The starter uses 2:1.
Canvas, SVG or WebGL for an isometric city?
SVG or CSS transforms are fine for a few dozen still buildings. A 2D canvas handles hundreds of boxes with moving lights. WebGL adds a depth buffer and per-pixel lighting, which suits shadows and thousands of windows; Isometric City Hero is drawn in raw WebGL1.
Can the city show a real place?
Not in Isometric City Hero: it plans its city from a seed, and the same seed always gives the same city, so you try seeds until the skyline suits you. To draw a real place, read building footprints and heights from map data and project them the same way.
Can I get the city as a video?
Yes. Isometric City Hero records a frame-perfect 1080p MP4 that loops seamlessly, and a poster, with portrait sizes too.
