Deciding what to watch was harder than watching it

Deciding what to watch was harder than watching it

I love movies and shows.

I love movies and shows.

But finding something to watch often meant jumping between platforms, scrolling for too long, and still not finding anything.

But finding something to watch often meant jumping between platforms, scrolling for too long, and still not finding anything.

That frustration made me wonder:

That frustration made me wonder:

"What if discovering titles felt less like searching and more like exploring?"

"What if discovering titles felt less like searching and more like exploring?"

So I built Atlas.

So I built Atlas.

I started by removing the feed

I started by removing the feed

I didn't want to build another page of recommendations.

I didn't want to build another page of recommendations.

I wanted movies to exist in a space you could explore.

I wanted movies to exist in a space you could explore.

Atlas is built around an infinite canvas inspired by Figma and Google Maps.

Atlas is built around an infinite canvas inspired by Figma and Google Maps.

Drag to explore.

Drag to explore.

Search when you know what you want.

Search when you know what you want.

Filter when you need more control.

Filter when you need more control.

My first infinite canvas wasn't actually infinite

My first infinite canvas wasn't actually infinite

My first version duplicated the grid to create the illusion of endless space.

My first version duplicated the grid to create the illusion of endless space.

It worked until posters started repeating, which did not feel like an infinite canvas.

It worked until posters started repeating, which did not feel like an infinite canvas.

So I replaced it with a spiral coordinate system where every movie gets a unique position radiating outward from the center.

So I replaced it with a spiral coordinate system where every movie gets a unique position radiating outward from the center.

This gave Atlas a world that could keep expanding without visible edges.

This gave Atlas a world that could keep expanding without visible edges.

const ring = Math.ceil((Math.sqrt(n + 1) - 1) / 2)
const position = n - (2 * ring - 1) ** 2
 
const side = Math.floor(position / (2 * ring))
const offset = position % (2 * ring)
 
// Every movie gets a unique position
// radiating outward from the center.
return getCoordinate(ring, side, offset)

Exploration still needed control

Exploration still needed control

Exploring works when you don't know what you want.

Exploring works when you don't know what you want.

Sometimes you do.

Sometimes you do.

So I added Search for movies, shows, and filmographies, alongside Filters for genre, language, year and rating.

So I added Search for movies, shows, and filmographies, alongside Filters for genre, language, year and rating.

const [query, setQuery] = useState('')
const [filters, setFilters] = useState({
genre: null,
language: null,
year: null,
rating: null,
})
 
const results = applySearchAndFilters(movies, query, filters)

Small details made Atlas feel alive

Small details made Atlas feel alive

Selecting a poster opens the Inspector, bringing together cast, trailers, streaming options, and ratings from multiple sources.

Selecting a poster opens the Inspector, bringing together cast, trailers, streaming options, and ratings from multiple sources.

But I didn't want it to feel like opening a database.

But I didn't want it to feel like opening a database.

Atlas samples each movie's artwork and uses its color as ambient lighting.

Atlas samples each movie's artwork and uses its color as ambient lighting.

Every movie subtly changes the atmosphere of the interface.

Every movie subtly changes the atmosphere of the interface.

// Sample color from the movie artwork
const pixels = ctx.getImageData(
centerX - 20,
centerY - 20,
40,
40
).data
 
const ambientColor = averagePixels(pixels)
 
setInspectorColor(ambientColor)

I wanted interactions to feel physical

I wanted interactions to feel physical

Saving a movie originally just updated the watchlist.

It worked, but it didn't feel like Atlas.

So I made the poster travel along a curved path toward the watchlist.

This helped the product feel tactile instead of flat.

const x = bezier(startX, midX, targetX, t)
const y = bezier(startY, midY, targetY, t)
 
gsap.set(thumbnail, {
x,
y,
scale: 1 - t * 0.7,
opacity: 1 - t * 0.45
})

Then more movies made everything slower

Then more movies made everything slower

With posters being rendered almost immediately, panning started dropping frames.

With posters being rendered almost immediately, panning started dropping frames.

For Atlas, that wasn't just an engineering problem.

For Atlas, that wasn't just an engineering problem.

Movement was the experience.

Movement was the experience.

I rebuilt the canvas to render only what's visible and preload content in the direction people are moving.

I rebuilt the canvas to render only what's visible and preload content in the direction people are moving.

That keeps the experience responsive even with a large number of posters on the backend.

That keeps the experience responsive even with a large number of posters on the backend.

const visibleCells = getVisibleCells({
panX,
panY,
viewport,
buffer
})
 
render(visibleCells)
 
// Load posters before they enter the viewport
prefetch(getCellsInDirection(dragVelocity))

Then mobile changed the rules

Atlas started desktop first, but mobile forced me to rethink the interaction model.

Atlas started desktop first, but mobile forced me to rethink the interaction model.

Hover does not exist on touch. Poster sizes had to stay usable. Gestures needed to feel natural.

Hover does not exist on touch. Poster sizes had to stay usable. Gestures needed to feel natural.

So I designed Atlas to work across mouse, trackpad, and touch while keeping the same spatial logic underneath.

So I designed Atlas to work across mouse, trackpad, and touch while keeping the same spatial logic underneath.

I wanted insight without surveillance

I wanted insight without surveillance

Once Atlas was live, I needed to understand how people used it.

Once Atlas was live, I needed to understand how people used it.

Were they exploring? Searching? Saving? Returning?

Were they exploring? Searching? Saving? Returning?

So I added privacy first analytics with no cookies, no persistent identifiers, no autocapture, and no full IP storage.

So I added privacy first analytics with no cookies, no persistent identifiers, no autocapture, and no full IP storage.

This gives me enough signal to improve Atlas without collecting data I don't need.

This gives me enough signal to improve Atlas without collecting data I don't need.

posthog.init(key, {
persistence: "memory",
autocapture: false,
disable_session_recording: true,
ip: false,
})

What Atlas taught me

What Atlas taught me

Atlas started as a personal frustration and became a product about a different kind of discovery.

Atlas started as a personal frustration and became a product about a different kind of discovery.

The value is not just the movie data.

The value is not just the movie data.

It is the feeling of moving through it.

It is the feeling of moving through it.

The canvas, shortcuts, search, filters, inspector, watchlist animation, touch support, and performance work all exist to protect that feeling.

The canvas, shortcuts, search, filters, inspector, watchlist animation, touch support, and performance work all exist to protect that feeling.

The interface wasn't supporting the product.

The interface wasn't supporting the product.

The interface was the product.

The interface was the product.

What's next

What's next

Atlas is live, but I still want to keep improving it.

Atlas is live, but I still want to keep improving it.

The next step is to test whether spatial discovery actually helps people find better things to watch, then use that feedback to refine the canvas, the shortcuts, and the recommendation flow.

The next step is to test whether spatial discovery actually helps people find better things to watch, then use that feedback to refine the canvas, the shortcuts, and the recommendation flow.

Atlas

Movie and Show Discovery

Turning movie and TV show browsing into exploration

Turning movie and TV show browsing into exploration

Designed and built an infinite canvas experience for discovering movies and shows beyond the traditional feed.

Designed and built an infinite canvas experience for discovering movies and shows beyond the traditional feed.

Role

Role

Design Engineer

Design Engineer

Team

Team

Solo project

Solo project

Solo project

Tech Stack

Tech Stack

Next.js 14 • TypeScript • Tailwind CSS • Framer Motion • GSAP • Vercel • PostHog

Next.js 14 • TypeScript • Tailwind CSS • Framer Motion • GSAP • Vercel • PostHog

Next.js 14 • TypeScript • Tailwind CSS • Framer Motion • GSAP • Vercel • PostHog

AI Tools

Cursor • Claude Code • Kiro

AI Tools

Cursor • Claude Code • Kiro

Cursor • Claude Code • Kiro

Status

Status

Live

Live