Alessandro Latour

Product Designer

myst.so

Date: June 22, 2025

myst.so Demo

Intro

After finishing up my DS project, I was looking for something fun to work on next and kept coming back to Pokémon. The whole idea actually started when I was looking for a new profile picture and found a Latios one from Mystery Dungeon. I figured there are probably some people out there also wishing a DB existed where they could get a new PFP of their favorite Pokemon easily.

The app itself is pretty straightforward - just click and get a random Pokémon portrait. But there is also a ton going on in the app with thousands of images, different generations, various moods, shiny variants, and all the optimization work to make everything load quickly (which is where all of the fun is).

The Collection

myst.so is a collection of Pokémon portraits from the first 5 generations. I chose to focus on Gen 1-5 (for MVP) since these are the main generations that I know and grew up playing. I plan on adding the new generations down the road of updates.

The portraits are organized by:

  • Generation (1-5)
  • Type (Fire, Water, Grass, etc.)
  • Mood (Happy, Serious, Cute, etc.)
  • Shiny variants
  • Special forms (like Deoxys, Castform, Rotom, etc.)

And the file structure is organized by National Dex number, making it easy to navigate and maintain:

public/portrait/
├── 0001/ (Bulbasaur)
│   ├── 0001_happy.png
│   ├── 0001_serious.png
│   └── _shiny/
│       ├── 0001_happy.png
│       └── 0001_serious.png
├── 0002/ (Ivysaur)
└── ...

Performance Optimizations

I knew that having thousands of photos in my /public folder was going to require extensive focusing on optimization and speed. One main optimization was ensuring that the component prevented unnecessary re-renders when users click "Generate". Which is where React memo comes in.

Memo Block

This memoization tells the component not to re-render unless the filtered list of images (for example, when you select 'Generation 1') has actually changed. This prevents a lot of unnecessary work and keeps the app running quickly.

  • Without memo: Every time you click "Generate," the component would re-process the entire list of thousands of images, even if your filters haven't changed. This is like re-reading a whole phone book just to pick a random name.
  • With memo: After you've picked your filters, clicking "Generate" is fast because the component doesn't re-process the list. It already knows the list is the same and just picks a random portrait from it.

Performance Timeline

With the help of some great prompt engineering and Cursor, I was able to drastically improve the image generation timeline over multiple iterations. The goal was to make generation feel snappy and never leave users waiting around for an image to load. I think the very first iteration of Myst.so generated an image every couple seconds. It was quite slow. But after memoization and some API improvements, load times got way better.

Image Generation Flow

Performance Timeline
0.000s
User Click
0.051s
Path Selection
0.040s
CDN Load
0.031s
Render
Total: 0.122s

Image Export & Enhancement

The app creates two-tiers of images that balances performance with quality. Previews show an optimized 40x40px image for browsing, but when you want to export an image, it enhances the quality for actual use as a profile picture.

Preview vs. Enhanced View

When you click to export an image, two things happen to improve quality:

  1. Display Enhancement: The modal uses imageRendering: 'pixelated' CSS to scale up the 40x40 preview
  2. Export Enhancement: The download system uses ctx.imageSmoothingEnabled = false to create high-resolution exports (100x100, 200x200, or 500x500)

Image Enhancement Process

Preview State

Performance optimized

Shiny Latias Preview
Fast Load

Enhanced State

SVG Level render

Shiny Latias Enhanced
High Res Export
CSS Enhancement
imageRendering: 'pixelated'
Canvas Export
ctx.imageSmoothingEnabled = false

This approach solves the classic performance vs. quality problem: fast loading for browsing, high quality for downloads.

Credits

All of the pixel art sprites featured in myst.so come from the wonderful artists at PMD Collab. This app serves as a random generator that showcases their incredible work. I just wanted to build something that made all of these portraits more accessible to people. 🙂

For artist credits, each portrait links directly back to the original sprite in the PMD Collab database. The URL generation dynamically updates the link based on the national Pokedex number:

Credit Block

The dexNumber.toString().padStart(4, '0') takes the Pokédex number and formats it as a 4-digit string (like 0001, 0025, 0150), so clicking on Pikachu's credit will take you directly to https://sprites.pmdcollab.org/#/0025 in the original database.


myst.so might be a niche little web app with 5 monthly users (hopefully more!), but the approach — combining nostalgic ideas with modern performance — hopefully demonstrates that even the smallest projects can be built with care.