DEV Community

Cover image for I built a city on my GitHub profile. It grows every time I commit.
Vineet J Karni
Vineet J Karni

Posted on

I built a city on my GitHub profile. It grows every time I commit.

My GitHub profile is a city now. It's called Commit City, it looks a lot like Bengaluru, and it gets a little bigger every time I commit.

vineetjk (Vineet J Karni) · GitHub

Best things need time! . vineetjk has 80 repositories available. Follow their code on GitHub.

favicon github.com

Commit City in the daytime: a metro train on an elevated line, a half-built town hall with cranes, glass towers and apartment blocks named after my repos, and a street with a BMTC bus, autos and a coffee shop

Earlier this week I turned my contribution graph into a Namma Metro train. It ran on a viaduct past a flat skyline that didn't mean anything. A metro line needs a city around it, so I built one, and I made every piece of it out of my GitHub data.

What's in the city

  • Each named building is one of my public repos, with the repo's name on a sign on its roof.
  • Repos I pushed to in the last four months are glass towers. The ones from the last two years are apartment blocks with black water tanks on the roof, like most apartment blocks in Bengaluru. Older repos are small houses with red Mangalore-tile roofs.
  • The more I committed to a repo this year, the taller its building is and the more of its windows are lit at night.
  • The metro train is still my contribution graph: one coach per week and one window per day.
  • Every pull request I merged this year is a BMTC bus, with the repo and the PR number on its LED board. Each bus drives near the week it was merged, and the newest one waits at the bus stop.
  • My DEV posts fly over the city as banners towed by small planes.
  • Every week owns the stretch of road under its coach, and that stretch is as busy as the week was. A week with lots of commits has a crowded footpath and a cyclist. A week with none is nearly empty.

Next to the metro station there's a darshini, one of the stand-up coffee and tiffin places Bengaluru runs on, and the signs are in Kannada and English.

Street level: the BMTC bus for PR #10 of Alpha-Fin at the bus stop, an auto-rickshaw, a cyclist, Shri Commit Darshini with its menu board, and the stairs up to the metro

It grows

The city starts small and fills in as my all-time contributions add up. The first building cost 25 contributions, and each one after that costs 5 more than the one before:

export const buildingCost = (k, rules) => rules.buildingBase + rules.buildingStep * (k - 1);

let buildings = 0;
let spent = 0;
while (buildings < rules.maxBuildings && spent + buildingCost(buildings + 1, rules) <= contributions) {
  spent += buildingCost(buildings + 1, rules);
  buildings++;
}
Enter fullscreen mode Exit fullscreen mode

So the city filled in quickly at first, and now it grows more slowly. When I took these screenshots I had 925 contributions since 2018, which buys 15 buildings. The 16th is a construction site with a crane and a board that said it needed 75 more. By the time I finished writing this post it needed 67. The busiest repos get the plots next to the metro station, and the rest line up further back along the line, so the ride goes from the outskirts into downtown.

Some things only show up at milestones: the darshini at 100 contributions, autos at 300, the BMTC buses at 500 and the planes at 600. There's a new tree for every 20.

The biggest one is the town hall, Commit Soudha, a mock of Bengaluru's Vidhana Soudha. Work on it starts at 800 contributions and it opens at 1,500. In these screenshots it's 18% built, so you see a blueprint of the whole building with the walls going up inside it, two cranes and a site board. The real Vidhana Soudha has "Government work is God's work" written over its entrance. Mine says ಕಮಿಟ್ ಕೆಲಸ ದೇವರ ಕೆಲಸ, "commit work is God's work".

Here's the same city at 300, 1,000 and 2,000 contributions. The last two are still ahead of me:

The city at 300 contributions with only a blueprint of the town hall, at 1,000 with its walls going up between cranes, and at 2,000 finished and floodlit at night

The city never shrinks. Every run saves the highest all-time total it has seen, so a repo going private or a count coming back a bit lower doesn't knock buildings down.

The sky follows Bengaluru

The light changes with the time in Bengaluru: dawn from 05:30, day from 07:00, dusk from 17:45 and night from 19:00. At night the windows light up, the streetlights come on, the cars get headlights and the cranes get red warning lights. Your GitHub theme doesn't change the sky. It only changes the frame around it.

Commit City at dawn, during the day, at dusk and at night

The ride

Like the metro panel before it, the city loops every 90 seconds. It rests on Commit Street, fades back a year, and then the camera rides along the line to today, past every week of the year.

The city is drawn in three layers that move at different speeds. The far skyline moves at 30% of the camera's speed, the buildings at 75%, and the line and the street at full speed. That's most of what makes it feel like a real place instead of a flat picture. This is the last few seconds of the ride, arriving downtown:

The camera riding into downtown past the town hall's cranes and into Commit Street station

How it works

Still one SVG

GitHub doesn't run JavaScript in a README, so like everything on my profile, the city is an SVG loaded through an <img> tag and animated with CSS. A Node script draws it from my data on GitHub Actions and commits it. It comes to about 270 KB and around 3,000 shapes. Anything that repeats along the street, like pillars, kerbs, hedges and streetlights, is an SVG pattern, so a longer street doesn't cost more.

The far layer moves by a fraction of the camera's offset. The buildings need one more step: they have to line up with the street exactly when the camera rests on Commit Street, so their offset is measured from there:

const pan = {
  near: (u) => panAt(u),
  mid: (u) => panAt(last) + PARALLAX.mid * (A - world(u)),
  far: (u) => panAt(u) * PARALLAX.far,
};
Enter fullscreen mode Exit fullscreen mode

u is the week in focus and A is where Commit Street is. Each of these becomes a CSS keyframe animation with the same timing as the train, so everything stays in step.

Timing the buses

A bus can't drive forever. It has to loop, and if it jumps back to the start while the camera is looking at it, you see it teleport. So every vehicle drives a stretch of road around its spot, once per 90-second loop, and the loop is timed so the vehicle is in the middle of its stretch at the moment the camera passes:

/** A vehicle loop that is mid-stretch at time `at` (seconds), its spot being (x, y). */
function vehicle(kind, dir, x, y, at, extra = '', attrs = '') {
  const delay = (((at - DURATION / 2) % DURATION) + DURATION) % DURATION - DURATION;
  return `<g class="mv ${kind}${dir > 0 ? 'R' : 'L'}" style="animation-delay:${n(delay)}s"><use href="#${kind === 'cycle' ? 'cyc' : kind}${dir > 0 ? 'R' : 'L'}" x="${n(x)}" y="${n(y)}"${attrs}/>${extra}</g>`;
}
Enter fullscreen mode Exit fullscreen mode

at comes from the camera's keyframes. The jump back happens 45 seconds later, when the camera is on the other side of the year. Walkers don't need any of this. They stroll a few steps one way, turn around and come back, so their loop has no jump at all.

It works. When the camera reaches July, the buses for the Alpha-Fin PRs I merged that week are right there under the gantry signs:

Mid-ride in July: a convoy of BMTC buses for Alpha-Fin pull requests on both sides of the road, under the Alpha-Fin and Vyuha-Network signs, past tile-roof houses for my older repos

India drives on the left, so traffic on the far side of the road heads right and traffic on the near side heads left.

Kannada in an SVG

An SVG in a README can't load web fonts, so every font has to be embedded. Noto Sans Kannada is far too big for that, so I cut it down to the glyphs of the nine Kannada signs in the city. That's 18 KB. Kannada isn't monospaced like the rest of my text, so I measured each sign's width in Chrome once and keep the numbers in a table to lay the signs out.

The same picture every time

Everything that looks random, like which windows are lit, where the trees go and who's walking where, comes from a seeded random generator. The same data always draws the same SVG, byte for byte, so when nothing has changed, the workflow has nothing to commit.

GitHub's scheduler runs late

The sky depends on the workflow running when the light changes, and that turned out to be the weak spot. In its first two days the workflow had about 30 scheduled runs. GitHub started eight of them, every one late. On the day the city went live, the evening redraws, due at 17:50 and 19:05, started at 22:53 and 23:01. If I hadn't started one by hand, the city would have stayed in daylight until almost eleven at night. Scheduled workflows are best effort, and GitHub drops some of them when Actions is busy.

Runs started by a push, an issue or the API start within seconds. Only the schedule is unreliable. If you build something like this, don't count on the cron to run on time.

The stock market is still there

The $VJK stock market from my first post still runs right under the city, so you can still buy shares in me. Its price board is in the city too, hanging at Commit Street station.

Try it

Commit City is at the top of my profile. The code lives in the same repo, mostly in scripts/lib/render/city.js and scripts/lib/render/city/. Running node scripts/preview.js --phases draws the city in all four lights, so you can see it before you push anything.

My next goal is 1,500 contributions, so the town hall can open.

Top comments (0)