SKIP TO CONTENT
GW

W-05CASE FILE

SocialCards

A FIFA-style player card generated from your own platform data — parsed in a Web Worker on your machine, with the zero-server guarantee enforced by CI rather than promised in a README.

ROLEDesign & Engineering
YEAR2026
CATEGORYClient-side data product
STATUSLIVE
STACKTypeScript · Next.js 16 · Web Workers · IndexedDB · GitHub Actions
W-05 / LIVE SIMULATIONCARD.SIM
01

Overview

SocialCards turns a data export into a football-card-style rating card: GitHub, Spotify, Discord, Instagram, Claude, and Messages each get an adapter that reads the platform's real export format and scores it. Every stat on a card traces back to something in the file you uploaded. Nothing is fabricated, and for every upload-based platform, nothing is transmitted.

02

Context

This began as a fork of GitFut (MIT licensed, original notice preserved), which scores a single platform. The work here was extracting its scoring philosophy into a platform-agnostic engine — a shared card model, formula primitives, and a theme contract — and then proving the abstraction by adding five more platforms to it, each with a genuinely different source format.

03

Adapter pipeline

Adapter pipelineFIG.
  1. 01

    Export file

    platform's own format

  2. 02

    Validate

    shape check, typed errors

  3. 03

    Parse

    Web Worker, off the main thread

  4. 04

    Metrics

    per-platform

  5. 05

    Stats

    shared formula primitives

  6. 06

    Overall

    tiered rating

  7. 07

    Card

    renderer reads theme only

  8. everything above runs on the device — no network calls

Every adapter implements the same six-step shape. The renderer never branches on platform — it branches on the CardTheme an adapter hands it, which is why adding a platform doesn't touch rendering code.

04

Decisions

  1. D-01

    Zero-server is a CI gate, not a convention

    The build log is parsed for Next.js's dynamic-route marker; if any route stops being static, the workflow fails with an explanatory error. The architectural property is enforced on every push rather than trusted to reviewer memory.

    INSTEAD OFA note in the README, which decays the first time someone adds a convenient API route.

  2. D-02

    Query parameters instead of dynamic route segments

    Gallery detail, comparison, and share views all take ?id= or ?d= rather than /[id]. A dynamic segment would turn each into a serverless function and break the guarantee above. Sharing state through the URL keeps every route in the static table.

  3. D-03

    Parsing in a Web Worker, not on the main thread

    A Spotify extended-streaming-history export is large enough to freeze the tab. Worker parsing keeps the UI responsive and has a useful side effect: the parsing code has no ambient access to the network layer.

  4. D-04

    Spotify OAuth via Authorization Code + PKCE

    PKCE has no client secret, so there is nothing that needs a server to hold it — the browser talks to Spotify directly. Without a configured client id, the OAuth tab degrades to an explanatory message instead of erroring.

    INSTEAD OFA standard authorization-code flow, which needs a secret, which needs a backend, which would have broken the zero-server property for one optional feature.

  5. D-05

    Date ranges recompute rather than filter

    Selecting a range re-runs the calculation over cached parsed data instead of masking pre-computed totals, so a range's stats are the stats for that range. Boundary behaviour — inclusive end-of-day, invalid dates, missing timestamps — has its own test file.

05

Accessibility pass

Measured rather than eyeballed. Three of these were real bugs, not polish.

  • hidden on a file input removes it from the tab order entirely — switched to sr-only, which stays visually hidden but keyboard-reachable
  • The card exposes one composed aria-label via role="img" instead of making screen readers walk dozens of unlabelled nested elements
  • Several selects and custom date inputs had outline-none with no replacement indicator; every interactive control now has a visible focus ring
  • Secondary text was checked against WCAG AA by computing contrast, not by looking: one token measured about 3:1 and was raised to roughly 5.3:1
  • At 320–390px, several button rows had no flex-wrap — no page-level overflow, but buttons shrank and wrapped their own labels internally, which is broken in a way an overflow check doesn't catch
06

Results

Platforms
6
GitHub, Spotify, Discord, Instagram, Claude, Messages
Serverless functions
0
Verified by the CI route-table check on every push
Test files
11
Metrics correctness, formulas, date-range boundaries, snapshot validation
Non-local requests during parsing
0
Observed end-to-end for every upload-based platform
07

Lesson

If an architectural property matters, encode it as a failing build. A README promise decays; a CI gate argues back.

BACK TO INDEX