How I Designed a City Repair Tracker in a Weekend
How I designed a single map for everyone at HackYeah 2026.
HackYeah 2026 is a wrap. It's the biggest stationary hackathon in Europe, which in practice means three thousand participants, one Tauron Arena in Kraków, a lot of laptops, and a sleep schedule nobody should be proud of. Our category alone had over a hundred teams, and somewhere among them, there we were...
What we came up with is mRadar, a map of city faults in Kraków. You report a problem by talking to it or by dragging a pin. The app groups similar reports, an operator approves a ticket, and the right city unit gets it. Meanwhile everyone can watch the repair move along on the map.
The problem
Have you ever seen a broken window at a bus stop? Did you know where to report it? Well, I didn't. Turns out reports reach the city through several channels, or not at all. Nobody can see what's already been reported, so the same broken window gets reported over and over. Someone matches the reports up by hand, works out whose window it is, the unit hears about it late, and the people who reported it never hear anything back.
So we ended up with three screens: a public map for residents, a panel for the city's operators and an inbox for city units. My job was, as always, designing the product and building the frontend. Now, how do you let three groups of people with very different jobs look at the same broken window without building three separate products?
The rules I set
My brief for the look fit in one sentence: neutral, small corners, very little shadow, polished, and please, not much blue. A city map is busy enough already. Anything decorative would just be one more thing standing between you and the actual problem. There is a reason why management apps are boring. Who wants gradients in Jira?
Two rules survived the whole weekend, which at a hackathon is a small miracle. Color only shows up where it means something: categories, status and the heatmap. And every screen floats over the same map and reuses the same detail panel, so a broken window looks like the same broken window to everyone.
The visual system
The interface is plain gray, by design, so the only thing you notice is the only thing that matters: the reports! The main button is near-black like every other control, so nothing shouts until it really has to. A gray app with a black button, how very 2026 of me. But on top of a city map, anything with an opinion becomes noise.
I recolored the base map into the same grays and gave main roads a faint warm tint, so you can still find your way home. The heatmap uses a single rose color that gets deeper where reports pile up. One color reads as "a lot happening here". A green-to-red scale would read as "panic", and it would fight with the category colors anyway.
Each of the eight categories, from power to air and noise, has its own color and icon, and the two always travel together. Give it a minute and you start reading the city by color.
The resident map
Zoomed out, you see the heatmap. Zoom in and it fades away, and each incident becomes a marker that grows with the number of people affected. The building with the problem lights up in its category color as a 3D block. If it's a road problem, a short stretch of the road lights up instead.
Click a marker and the panel opens, with a progress timeline and the most important button in the app: "I'm affected too". It turns what would have been report number twelve into a +1 on the one that already exists.
On phones the panel turns into a bottom sheet, and the controls ride up with it so your thumb can still reach them.
Reporting
Report gives you two ways in: talk to it, or fill in a form. The form starts with your location, because that's the part everyone gets wrong. You move the map under a fixed pin, then pick a category and say what's wrong. No account, and no address field. The pin already answered that.
The voice button, powered by ElevenLabs, reminds you that real emergencies still go to 112. We are not the emergency services. While you talk, the assistant fills the form for you, shows the address it heard on the map and asks if it got it right, which beats reading a street name back to you and hoping.
The operator panel
An operator's day is basically a to-do list, so their screen is a queue next to the same map. It's Jira, except every bug is physically somewhere in Kraków. Every row has the same shape: a title, the category icon, what needs doing and how long it's been waiting. Urgent rows swap the icon for a red warning, so they're hard to miss.
On the map, a dashed circle shows the area the system used to group reports, so the operator can see why five reports turned into one incident.
The panel is the same one residents get, with exactly one next step underneath. Usually that's a ticket written as a plain sentence, "Create a service ticket for Electricity Service", the exact message that will be sent, and one button: Approve and send.
When the system can't make up its mind, the next step becomes a question instead: which incident does this report belong to, or who's responsible for this noise complaint? A sparking cable cabinet gets a red notice that a ticket won't send the emergency services, in case anyone was hoping.
The city unit inbox
The city unit's inbox is the operator screen with less stuff in it. A unit sees only its own tickets. The water company doesn't need to hear about your neighbor's late-night drilling. Again, there's one next step at a time: acknowledge, start work, mark it resolved with a note. The note goes back to the operator, and the resident's timeline moves along with it.
The whole thing hangs on one idea: an incident should look the same wherever you meet it. Same title, same color, same panel, and a timeline that moves for everyone at once.
The small stuff
Icons instead of labels. I love icons so much. Staff screens show short facts led by little icons: a clock for time, a ruler for distance, a ticket for a reference number. Screen readers still get the words. That's how I made every queue row fit on just two lines.
Phones get everything too, so a repair crew can update a ticket's status right away, from the actual hole in the road. Fast updates are the whole point of the app.
Something that is very important to me: accessibility is the foundation, not a bonus. Status never relies on color alone, because "the red one" is not a status, and everything works from the keyboard. An automated check of all three screens in 32 states found no WCAG 2.2 AA violations.
How it's built
The frontend is Next.js and React on Appica UI, which I found on Twitter a while back and wanted to try. I turned all its blue accents gray, so no stray component could sneak the blue back in. The map is MapLibre with OpenFreeMap tiles, which means no API key and no surprise bill. It shares the theme with the panels, so dark mode recolors the whole city too.
Lighting up a single building needed a workaround: the map data glues neighboring buildings together under one ID, so my first attempt lit up entire blocks. Great for a light show, bad for an outage report. Now, with a clever heuristic, the map copies just the one building into its own layer.
Finishing notes
Using one map and one panel for all three groups was the best call of the weekend. Second best was going to sleep at all. It saved a pile of time, and it made the handoff easy to follow, because everyone looking at an incident sees the same shape. And most importantly, we built one product, not three.
What's next? First up is a phone line for older residents, where the same voice assistant takes your report without an app, followed by mObywatel sign-in, tickets going straight into the city's own systems, and notifications, so you know when your bus stop gets its window back.
Keeping color for things that mean something did more for the map than any styling could. Eight categories, a heatmap and four statuses only stay readable if everything else agrees to be gray. The map might seem boring. I know, and that's by design.