cd ../projects

Project 7 // _rypc

RateYourPkmnCards

Turning a huge card catalogue into a community — ratings, reviews and shareable binders.

client

personal product

timeline

2024

role

solo build · full-stack

scope

product designweb appcard data pipelineratings & binders

## the-problem

Pokémon TCG collectors have endless places to check what a card is worth, but nowhere opinionated to say whether a card is actually good — the art, the chase factor, how it feels to pull. That signal was scattered across Reddit threads, Discords and YouTube, and none of it was in one place.

The catalogue itself is huge and image-heavy: thousands of cards across hundreds of sets, with inconsistent naming and big artwork. Any community layer had to sit on top of a fast, searchable, image-friendly catalogue first — otherwise browsing would crawl.

## the-approach

  • normalized the full TCG catalogue — every set, card, type and rarity — into one searchable, filterable data layer
  • a rating + review system that rolls individual scores up into community averages and top-rated leaderboards
  • a binder builder: arrange favourite cards into customizable virtual binders and share them by link
  • accounts that tie each member's ratings, reviews and binders together
  • image-performance work — lazy loading, responsive sizes and caching — so browsing thousands of cards stays instant

// ranking — two Bayesian passes: votes first, then catalogue size

// (C × m + R × v) / (C + v). Two five-star votes shouldn't
// outrank four hundred good ones, so sparse scores are pulled
// toward the global mean until the votes earn their weight.
export function calculateBayesianScore(
averageRating: number,
ratingsCount: number,
globalMean = BAYESIAN_CONFIG.GLOBAL_MEAN, // 3.5
confidence = BAYESIAN_CONFIG.CONFIDENCE_THRESHOLD, // 20 votes
): number {
if (ratingsCount === 0) return globalMean;
if (averageRating < 0 || averageRating > 5) return globalMean;
return (confidence * globalMean + averageRating * ratingsCount)
/ (confidence + ratingsCount);
}
// Artists and sets get a second pass with the same shape, where
// the catalogue is the vote count — one perfect card is not a
// body of work.
export const calculateCatalogAdjustedScore = (
score: number, cardCount: number, mean: number, M: number,
) => (score * cardCount + mean * M) / (cardCount + M);

## the-architecture

// architecture — catalogue in, ratings & binders out

client

web app

Next.js · SSR

  • browse by set
  • search + rarity filters
backend

app API

ratings · binders · auth

  • review + score writes
  • aggregate rollups
backend

catalogue sync

cards · sets · images

  • normalizes card data
  • keeps sets current
external

card source

TCG data

  • every set + card
  • official artwork
data

database

users · ratings · binders

  • community scores
  • per-user binders
clientbackendexternaldata

## the-results

every set

in the catalogue

1000s

cards rateable

public

shareable binders

  • the whole catalogue — hundreds of sets — browsable and filterable in one place
  • community ratings surface genuinely top-rated cards, not just expensive ones
  • binders give collectors a reason to come back and something to share
nextreactmongodbrealtimetypescriptaws

// want something like this built for you?

start-a-project